mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 13:01:12 +00:00
Compare commits
57 Commits
85c6f4e688
...
workstatio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6413828c59 | ||
|
|
5072f00836 | ||
|
|
9dfbe3246e | ||
|
|
bef69db3b6 | ||
|
|
a061bc2942 | ||
|
|
8c9e11c04f | ||
|
|
e4e3ec805a | ||
|
|
d634316bce | ||
|
|
f5446c6480 | ||
|
|
a98d25c16d | ||
|
|
80b9589973 | ||
|
|
4d4bbcbae8 | ||
|
|
fa9b2a08f2 | ||
|
|
929d50f954 | ||
|
|
e60bf29a7f | ||
|
|
2e17dee121 | ||
|
|
c03abb341a | ||
|
|
b97be6a5d4 | ||
|
|
44f830cf00 | ||
|
|
04b578a68b | ||
|
|
39a799cabd | ||
|
|
0d64563fb6 | ||
|
|
fbb9e0963d | ||
|
|
af411ddfe6 | ||
|
|
f5dbcb1bfc | ||
|
|
1ecf89ea27 | ||
|
|
6efdf6e5a6 | ||
|
|
e32dc55db0 | ||
|
|
acc45b716d | ||
|
|
017eaefb8d | ||
|
|
9e8c692702 | ||
|
|
beb90f20d2 | ||
|
|
7a284069d2 | ||
|
|
4a2d862333 | ||
|
|
538891fcbe | ||
|
|
a0e92b8e9b | ||
|
|
1d77225912 | ||
|
|
06e6ab0b7f | ||
|
|
5399c6c1cf | ||
|
|
f872d3ef56 | ||
|
|
9bba4620b7 | ||
|
|
d7494ca458 | ||
|
|
85dc46cd38 | ||
|
|
5a0c2f9850 | ||
|
|
d897d70c3e | ||
|
|
d9dffc6bf8 | ||
|
|
30b202bea0 | ||
|
|
1b2c0dbcd7 | ||
|
|
0f341e9b4d | ||
|
|
4c3972820b | ||
|
|
a2a8ee9088 | ||
|
|
966b51042d | ||
|
|
d81638e20b | ||
|
|
3c583008aa | ||
|
|
1d4e4c8377 | ||
|
|
54f749bcdb | ||
|
|
16ad4bbecc |
@@ -99,7 +99,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine_four",
|
"type": "MagazineHolder_4",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -235,7 +235,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -330,7 +330,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -425,7 +425,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -523,7 +523,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine_four",
|
"type": "MagazineHolder_4",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -564,7 +564,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -659,7 +659,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -754,7 +754,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -849,7 +849,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -949,7 +949,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -992,7 +992,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1087,7 +1087,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1182,7 +1182,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1277,7 +1277,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1372,7 +1372,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1467,7 +1467,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1567,7 +1567,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -1610,7 +1610,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1705,7 +1705,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1800,7 +1800,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1895,7 +1895,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1990,7 +1990,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2085,7 +2085,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2185,7 +2185,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -2228,7 +2228,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2323,7 +2323,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2418,7 +2418,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2513,7 +2513,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2608,7 +2608,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2703,7 +2703,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2803,7 +2803,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -2846,7 +2846,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2941,7 +2941,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3036,7 +3036,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3131,7 +3131,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3226,7 +3226,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3321,7 +3321,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3421,7 +3421,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -3464,7 +3464,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3559,7 +3559,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3654,7 +3654,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3749,7 +3749,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3844,7 +3844,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3939,7 +3939,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4039,7 +4039,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -4082,7 +4082,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4177,7 +4177,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4272,7 +4272,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4367,7 +4367,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4462,7 +4462,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4557,7 +4557,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
|
|||||||
54
new_cellconfig.json
Normal file
54
new_cellconfig.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "BatteryStation",
|
||||||
|
"name": "扣电工作站",
|
||||||
|
"parent": null,
|
||||||
|
"children": [
|
||||||
|
"coin_cell_deck"
|
||||||
|
],
|
||||||
|
"type": "device",
|
||||||
|
"class":"coincellassemblyworkstation_device",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"deck": {
|
||||||
|
"data": {
|
||||||
|
"_resource_child_name": "YB_YH_Deck",
|
||||||
|
"_resource_type": "unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:CoincellDeck"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"debug_mode": true,
|
||||||
|
"protocol_type": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "YB_YH_Deck",
|
||||||
|
"name": "YB_YH_Deck",
|
||||||
|
"children": [],
|
||||||
|
"parent": "BatteryStation",
|
||||||
|
"type": "deck",
|
||||||
|
"class": "CoincellDeck",
|
||||||
|
"position": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"type": "CoincellDeck",
|
||||||
|
"setup": true,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
{
|
{
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
@@ -47,22 +48,90 @@
|
|||||||
{
|
{
|
||||||
"id": "BatteryStation",
|
"id": "BatteryStation",
|
||||||
"name": "扣电工作站",
|
"name": "扣电工作站",
|
||||||
|
"parent": null,
|
||||||
"children": [
|
"children": [
|
||||||
"coin_cell_deck"
|
"coin_cell_deck"
|
||||||
],
|
],
|
||||||
"parent": null,
|
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "coincellassemblyworkstation_device",
|
"class":"coincellassemblyworkstation_device",
|
||||||
"position": {
|
|
||||||
"x": 600,
|
|
||||||
"y": 400,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
"config": {
|
||||||
"debug_mode": false,
|
"deck": {
|
||||||
|
"data": {
|
||||||
|
"_resource_child_name": "YB_YH_Deck",
|
||||||
|
"_resource_type": "unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:CoincellDeck"
|
||||||
|
}
|
||||||
|
},
|
||||||
"protocol_type": []
|
"protocol_type": []
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"size": {"height": 1450, "width": 1450, "depth": 2100},
|
||||||
|
"position": {
|
||||||
|
"x": -1500,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "YB_YH_Deck",
|
||||||
|
"name": "YB_YH_Deck",
|
||||||
|
"children": [],
|
||||||
|
"parent": "BatteryStation",
|
||||||
|
"type": "deck",
|
||||||
|
"class": "CoincellDeck",
|
||||||
|
"config": {
|
||||||
|
"type": "CoincellDeck",
|
||||||
|
"setup": true,
|
||||||
|
"rotation": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0,
|
||||||
|
"type": "Rotation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "NewareTester",
|
||||||
|
"name": "新威电池测试系统",
|
||||||
|
"parent": null,
|
||||||
|
"children": [],
|
||||||
|
"type": "device",
|
||||||
|
"class": "neware_battery_test_system",
|
||||||
|
"config": {
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"port": 502,
|
||||||
|
"machine_id": 1,
|
||||||
|
"devtype": "27",
|
||||||
|
"timeout": 20,
|
||||||
|
"size_x": 500.0,
|
||||||
|
"size_y": 500.0,
|
||||||
|
"size_z": 2000.0
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"size": {
|
||||||
|
"height": 1600,
|
||||||
|
"width": 1200,
|
||||||
|
"depth": 800
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"x": 1500,
|
||||||
|
"y": 0,
|
||||||
|
"z": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"功能说明": "新威电池测试系统,提供720通道监控和CSV批量提交功能",
|
||||||
|
"监控功能": "支持720个通道的实时状态监控、2盘电池物料管理、状态导出等",
|
||||||
|
"提交功能": "通过submit_from_csv action从CSV文件批量提交测试任务"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"links": []
|
"links": []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "NEWARE_BATTERY_TEST_SYSTEM",
|
|
||||||
"name": "Neware Battery Test System",
|
|
||||||
"parent": null,
|
|
||||||
"type": "device",
|
|
||||||
"class": "neware_battery_test_system",
|
|
||||||
"position": {
|
|
||||||
"x": 620.6111111111111,
|
|
||||||
"y": 171,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"ip": "127.0.0.1",
|
|
||||||
"port": 502,
|
|
||||||
"machine_id": 1,
|
|
||||||
"devtype": "27",
|
|
||||||
"timeout": 20,
|
|
||||||
"size_x": 500.0,
|
|
||||||
"size_y": 500.0,
|
|
||||||
"size_z": 2000.0
|
|
||||||
},
|
|
||||||
"data": {},
|
|
||||||
"children": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": []
|
|
||||||
}
|
|
||||||
8
unilabos/devices/neware_battery_test_system/__init__.py
Normal file
8
unilabos/devices/neware_battery_test_system/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from .neware_battery_test_system import NewareBatteryTestSystem
|
||||||
|
from .neware_driver import build_start_command, start_test
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"NewareBatteryTestSystem",
|
||||||
|
"build_start_command",
|
||||||
|
"start_test",
|
||||||
|
]
|
||||||
3
unilabos/devices/neware_battery_test_system/demo.csv
Normal file
3
unilabos/devices/neware_battery_test_system/demo.csv
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Timestamp,Battery_Count,Assembly_Time,Open_Circuit_Voltage,Pole_Weight,Assembly_Pressure,Battery_Code,Electrolyte_Code,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʺ<EFBFBD><EFBFBD><EFBFBD>,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>mah/g,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϵ,<EFBFBD>豸<EFBFBD><EFBFBD>,<EFBFBD>ź<EFBFBD>,ͨ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
|
2025/10/29 17:32,7,5,0.11299999803304672,18.049999237060547,3593,Li000595,Si-Gr001,9.2,0.954,469,SiGr_Li,1,1,2
|
||||||
|
2025/10/30 17:49,2,5,0,13.109999895095825,4094,YS101224,NoRead88,5.2,0.92,190,SiGr_Li,2,1,1
|
||||||
|
33
unilabos/devices/neware_battery_test_system/device.json
Normal file
33
unilabos/devices/neware_battery_test_system/device.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "NEWARE_BATTERY_TEST_SYSTEM",
|
||||||
|
"name": "Neware Battery Test System",
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "neware_battery_test_system",
|
||||||
|
"position": {
|
||||||
|
"x": 620.0,
|
||||||
|
"y": 200.0,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"port": 502,
|
||||||
|
"machine_id": 1,
|
||||||
|
"devtype": "27",
|
||||||
|
"timeout": 20,
|
||||||
|
"size_x": 500.0,
|
||||||
|
"size_y": 500.0,
|
||||||
|
"size_z": 2000.0
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"功能说明": "新威电池测试系统,提供720通道监控和CSV批量提交功能",
|
||||||
|
"监控功能": "支持720个通道的实时状态监控、2盘电池物料管理、状态导出等",
|
||||||
|
"提交功能": "通过submit_from_csv action从CSV文件批量提交测试任务。CSV必须包含: Battery_Code, Pole_Weight, 集流体质量, 活性物质含量, 克容量mah/g, 电池体系, 设备号, 排号, 通道号"
|
||||||
|
},
|
||||||
|
"children": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
1100
unilabos/devices/neware_battery_test_system/generate_xml_content.py
Normal file
1100
unilabos/devices/neware_battery_test_system/generate_xml_content.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,8 @@
|
|||||||
- 状态类型: working/stop/finish/protect/pause/false/unknown
|
- 状态类型: working/stop/finish/protect/pause/false/unknown
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
import socket
|
import socket
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
import json
|
import json
|
||||||
@@ -21,7 +23,6 @@ from dataclasses import dataclass
|
|||||||
from typing import Any, Dict, List, Optional, TypedDict
|
from typing import Any, Dict, List, Optional, TypedDict
|
||||||
|
|
||||||
from pylabrobot.resources import ResourceHolder, Coordinate, create_ordered_items_2d, Deck, Plate
|
from pylabrobot.resources import ResourceHolder, Coordinate, create_ordered_items_2d, Deck, Plate
|
||||||
|
|
||||||
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode
|
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode
|
||||||
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
|
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
|
||||||
|
|
||||||
@@ -56,13 +57,6 @@ class BatteryTestPositionState(TypedDict):
|
|||||||
status: str # 通道状态
|
status: str # 通道状态
|
||||||
color: str # 状态对应颜色
|
color: str # 状态对应颜色
|
||||||
|
|
||||||
# 额外的inquire协议字段
|
|
||||||
relativetime: float # 相对时间 (s)
|
|
||||||
open_or_close: int # 0=关闭, 1=打开
|
|
||||||
step_type: str # 步骤类型
|
|
||||||
cycle_id: int # 循环ID
|
|
||||||
step_id: int # 步骤ID
|
|
||||||
log_code: str # 日志代码
|
|
||||||
|
|
||||||
|
|
||||||
class BatteryTestPosition(ResourceHolder):
|
class BatteryTestPosition(ResourceHolder):
|
||||||
@@ -142,9 +136,9 @@ class NewareBatteryTestSystem:
|
|||||||
devtype: str = None,
|
devtype: str = None,
|
||||||
timeout: int = None,
|
timeout: int = None,
|
||||||
|
|
||||||
size_x: float = 500.0,
|
size_x: float = 50,
|
||||||
size_y: float = 500.0,
|
size_y: float = 50,
|
||||||
size_z: float = 2000.0,
|
size_z: float = 20,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
初始化新威电池测试系统
|
初始化新威电池测试系统
|
||||||
@@ -162,6 +156,12 @@ class NewareBatteryTestSystem:
|
|||||||
self.machine_id = machine_id
|
self.machine_id = machine_id
|
||||||
self.devtype = devtype or self.DEVTYPE
|
self.devtype = devtype or self.DEVTYPE
|
||||||
self.timeout = timeout or self.TIMEOUT
|
self.timeout = timeout or self.TIMEOUT
|
||||||
|
|
||||||
|
# 存储设备物理尺寸
|
||||||
|
self.size_x = size_x
|
||||||
|
self.size_y = size_y
|
||||||
|
self.size_z = size_z
|
||||||
|
|
||||||
self._last_status_update = None
|
self._last_status_update = None
|
||||||
self._cached_status = {}
|
self._cached_status = {}
|
||||||
self._ros_node: Optional[ROS2WorkstationNode] = None # ROS节点引用,由框架设置
|
self._ros_node: Optional[ROS2WorkstationNode] = None # ROS节点引用,由框架设置
|
||||||
@@ -192,8 +192,9 @@ class NewareBatteryTestSystem:
|
|||||||
def _setup_material_management(self):
|
def _setup_material_management(self):
|
||||||
"""设置物料管理系统"""
|
"""设置物料管理系统"""
|
||||||
# 第1盘:5行8列网格 (A1-E8) - 5行对应subdevid 1-5,8列对应chlid 1-8
|
# 第1盘:5行8列网格 (A1-E8) - 5行对应subdevid 1-5,8列对应chlid 1-8
|
||||||
# 先给物料设置一个最大的Deck
|
# 先给物料设置一个最大的Deck,并设置其在空间中的位置
|
||||||
deck_main = Deck("ADeckName", 200, 200, 200)
|
|
||||||
|
deck_main = Deck("ADeckName", 2000, 1800, 100, origin=Coordinate(2000,2000,0))
|
||||||
|
|
||||||
plate1_resources: Dict[str, BatteryTestPosition] = create_ordered_items_2d(
|
plate1_resources: Dict[str, BatteryTestPosition] = create_ordered_items_2d(
|
||||||
BatteryTestPosition,
|
BatteryTestPosition,
|
||||||
@@ -202,8 +203,8 @@ class NewareBatteryTestSystem:
|
|||||||
dx=10,
|
dx=10,
|
||||||
dy=10,
|
dy=10,
|
||||||
dz=0,
|
dz=0,
|
||||||
item_dx=45,
|
item_dx=65,
|
||||||
item_dy=45
|
item_dy=65
|
||||||
)
|
)
|
||||||
plate1 = Plate("P1", 400, 300, 50, ordered_items=plate1_resources)
|
plate1 = Plate("P1", 400, 300, 50, ordered_items=plate1_resources)
|
||||||
deck_main.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
|
deck_main.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
|
||||||
@@ -232,11 +233,15 @@ class NewareBatteryTestSystem:
|
|||||||
num_items_y=5, # 5行(对应subdevid 6-10,即A-E)
|
num_items_y=5, # 5行(对应subdevid 6-10,即A-E)
|
||||||
dx=10,
|
dx=10,
|
||||||
dy=10,
|
dy=10,
|
||||||
dz=100, # Z轴偏移100mm
|
dz=0,
|
||||||
item_dx=65,
|
item_dx=65,
|
||||||
item_dy=65
|
item_dy=65
|
||||||
)
|
)
|
||||||
|
|
||||||
|
plate2 = Plate("P2", 400, 300, 50, ordered_items=plate2_resources)
|
||||||
|
deck_main.assign_child_resource(plate2, location=Coordinate(0, 350, 0))
|
||||||
|
|
||||||
|
|
||||||
# 为第2盘资源添加P2_前缀
|
# 为第2盘资源添加P2_前缀
|
||||||
self.station_resources_plate2 = {}
|
self.station_resources_plate2 = {}
|
||||||
for name, resource in plate2_resources.items():
|
for name, resource in plate2_resources.items():
|
||||||
@@ -306,55 +311,132 @@ class NewareBatteryTestSystem:
|
|||||||
|
|
||||||
def _update_plate_resources(self, subunits: Dict):
|
def _update_plate_resources(self, subunits: Dict):
|
||||||
"""更新两盘电池资源的状态"""
|
"""更新两盘电池资源的状态"""
|
||||||
# 第1盘:subdevid 1-5 映射到 P1_A1-P1_E8 (5行8列)
|
# 第1盘:subdevid 1-5 映射到 8列5行网格 (列0-7, 行0-4)
|
||||||
for subdev_id in range(1, 6): # subdevid 1-5
|
for subdev_id in range(1, 6): # subdevid 1-5
|
||||||
status_row = subunits.get(subdev_id, {})
|
status_row = subunits.get(subdev_id, {})
|
||||||
|
|
||||||
for chl_id in range(1, 9): # chlid 1-8
|
for chl_id in range(1, 9): # chlid 1-8
|
||||||
try:
|
try:
|
||||||
# 计算在5×8网格中的位置
|
# 根据用户描述:第一个是(0,0),最后一个是(7,4)
|
||||||
row_idx = (subdev_id - 1) # 0-4 (对应A-E)
|
# 说明是8列5行,列从0开始,行从0开始
|
||||||
col_idx = (chl_id - 1) # 0-7 (对应1-8)
|
col_idx = (chl_id - 1) # 0-7 (chlid 1-8 -> 列0-7)
|
||||||
resource_name = f"P1_{self.LETTERS[row_idx]}{col_idx + 1}"
|
row_idx = (subdev_id - 1) # 0-4 (subdevid 1-5 -> 行0-4)
|
||||||
|
|
||||||
|
# 尝试多种可能的资源命名格式
|
||||||
|
possible_names = [
|
||||||
|
f"P1_batterytestposition_{col_idx}_{row_idx}", # 用户提到的格式
|
||||||
|
f"P1_{self.LETTERS[row_idx]}{col_idx + 1}", # 原有的A1-E8格式
|
||||||
|
f"P1_{self.LETTERS[row_idx].lower()}{col_idx + 1}", # 小写字母格式
|
||||||
|
]
|
||||||
|
|
||||||
|
r = None
|
||||||
|
resource_name = None
|
||||||
|
for name in possible_names:
|
||||||
|
if name in self.station_resources:
|
||||||
|
r = self.station_resources[name]
|
||||||
|
resource_name = name
|
||||||
|
break
|
||||||
|
|
||||||
r = self.station_resources.get(resource_name)
|
|
||||||
if r:
|
if r:
|
||||||
status_channel = status_row.get(chl_id, {})
|
status_channel = status_row.get(chl_id, {})
|
||||||
|
metrics = status_channel.get("metrics", {})
|
||||||
|
# 构建BatteryTestPosition状态数据(移除capacity和energy)
|
||||||
channel_state = {
|
channel_state = {
|
||||||
|
# 基本测量数据
|
||||||
|
"voltage": metrics.get("voltage_V", 0.0),
|
||||||
|
"current": metrics.get("current_A", 0.0),
|
||||||
|
"time": metrics.get("totaltime_s", 0.0),
|
||||||
|
|
||||||
|
# 状态信息
|
||||||
"status": status_channel.get("state", "unknown"),
|
"status": status_channel.get("state", "unknown"),
|
||||||
"color": status_channel.get("color", self.STATUS_COLOR["unknown"]),
|
"color": status_channel.get("color", self.STATUS_COLOR["unknown"]),
|
||||||
"voltage": status_channel.get("voltage_V", 0.0),
|
|
||||||
"current": status_channel.get("current_A", 0.0),
|
# 通道名称标识
|
||||||
"time": status_channel.get("totaltime_s", 0.0),
|
"Channel_Name": f"{self.machine_id}-{subdev_id}-{chl_id}",
|
||||||
|
|
||||||
}
|
}
|
||||||
r.load_state(channel_state)
|
r.load_state(channel_state)
|
||||||
except (KeyError, IndexError):
|
|
||||||
|
# 调试信息
|
||||||
|
if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
|
||||||
|
self._ros_node.lab_logger().debug(
|
||||||
|
f"更新P1资源状态: {resource_name} <- subdev{subdev_id}/chl{chl_id} "
|
||||||
|
f"状态:{channel_state['status']}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# 如果找不到资源,记录调试信息
|
||||||
|
if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
|
||||||
|
self._ros_node.lab_logger().debug(
|
||||||
|
f"P1未找到资源: subdev{subdev_id}/chl{chl_id} -> 尝试的名称: {possible_names}"
|
||||||
|
)
|
||||||
|
except (KeyError, IndexError) as e:
|
||||||
|
if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
|
||||||
|
self._ros_node.lab_logger().debug(f"P1映射错误: subdev{subdev_id}/chl{chl_id} - {e}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 第2盘:subdevid 6-10 映射到 P2_A1-P2_E8 (5行8列)
|
# 第2盘:subdevid 6-10 映射到 8列5行网格 (列0-7, 行0-4)
|
||||||
for subdev_id in range(6, 11): # subdevid 6-10
|
for subdev_id in range(6, 11): # subdevid 6-10
|
||||||
status_row = subunits.get(subdev_id, {})
|
status_row = subunits.get(subdev_id, {})
|
||||||
|
|
||||||
for chl_id in range(1, 9): # chlid 1-8
|
for chl_id in range(1, 9): # chlid 1-8
|
||||||
try:
|
try:
|
||||||
# 计算在5×8网格中的位置
|
col_idx = (chl_id - 1) # 0-7 (chlid 1-8 -> 列0-7)
|
||||||
row_idx = (subdev_id - 6) # 0-4 (subdevid 6->0, 7->1, ..., 10->4) (对应A-E)
|
row_idx = (subdev_id - 6) # 0-4 (subdevid 6-10 -> 行0-4)
|
||||||
col_idx = (chl_id - 1) # 0-7 (对应1-8)
|
|
||||||
resource_name = f"P2_{self.LETTERS[row_idx]}{col_idx + 1}"
|
# 尝试多种可能的资源命名格式
|
||||||
|
possible_names = [
|
||||||
|
f"P2_batterytestposition_{col_idx}_{row_idx}", # 用户提到的格式
|
||||||
|
f"P2_{self.LETTERS[row_idx]}{col_idx + 1}", # 原有的A1-E8格式
|
||||||
|
f"P2_{self.LETTERS[row_idx].lower()}{col_idx + 1}", # 小写字母格式
|
||||||
|
]
|
||||||
|
|
||||||
|
r = None
|
||||||
|
resource_name = None
|
||||||
|
for name in possible_names:
|
||||||
|
if name in self.station_resources:
|
||||||
|
r = self.station_resources[name]
|
||||||
|
resource_name = name
|
||||||
|
break
|
||||||
|
|
||||||
r = self.station_resources.get(resource_name)
|
|
||||||
if r:
|
if r:
|
||||||
status_channel = status_row.get(chl_id, {})
|
status_channel = status_row.get(chl_id, {})
|
||||||
|
metrics = status_channel.get("metrics", {})
|
||||||
|
# 构建BatteryTestPosition状态数据(移除capacity和energy)
|
||||||
channel_state = {
|
channel_state = {
|
||||||
|
# 基本测量数据
|
||||||
|
"voltage": metrics.get("voltage_V", 0.0),
|
||||||
|
"current": metrics.get("current_A", 0.0),
|
||||||
|
"time": metrics.get("totaltime_s", 0.0),
|
||||||
|
|
||||||
|
# 状态信息
|
||||||
"status": status_channel.get("state", "unknown"),
|
"status": status_channel.get("state", "unknown"),
|
||||||
"color": status_channel.get("color", self.STATUS_COLOR["unknown"]),
|
"color": status_channel.get("color", self.STATUS_COLOR["unknown"]),
|
||||||
"voltage": status_channel.get("voltage_V", 0.0),
|
|
||||||
"current": status_channel.get("current_A", 0.0),
|
# 通道名称标识
|
||||||
"time": status_channel.get("totaltime_s", 0.0),
|
"Channel_Name": f"{self.machine_id}-{subdev_id}-{chl_id}",
|
||||||
|
|
||||||
}
|
}
|
||||||
r.load_state(channel_state)
|
r.load_state(channel_state)
|
||||||
except (KeyError, IndexError):
|
|
||||||
|
# 调试信息
|
||||||
|
if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
|
||||||
|
self._ros_node.lab_logger().debug(
|
||||||
|
f"更新P2资源状态: {resource_name} <- subdev{subdev_id}/chl{chl_id} "
|
||||||
|
f"状态:{channel_state['status']}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# 如果找不到资源,记录调试信息
|
||||||
|
if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
|
||||||
|
self._ros_node.lab_logger().debug(
|
||||||
|
f"P2未找到资源: subdev{subdev_id}/chl{chl_id} -> 尝试的名称: {possible_names}"
|
||||||
|
)
|
||||||
|
except (KeyError, IndexError) as e:
|
||||||
|
if self._ros_node and hasattr(self._ros_node, 'lab_logger'):
|
||||||
|
self._ros_node.lab_logger().debug(f"P2映射错误: subdev{subdev_id}/chl{chl_id} - {e}")
|
||||||
continue
|
continue
|
||||||
|
ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
||||||
|
"resources": list(self.station_resources.values())
|
||||||
|
})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def connection_info(self) -> Dict[str, str]:
|
def connection_info(self) -> Dict[str, str]:
|
||||||
@@ -490,6 +572,45 @@ class NewareBatteryTestSystem:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def debug_resource_names(self) -> dict:
|
||||||
|
"""
|
||||||
|
调试方法:显示所有资源的实际名称(ROS2动作)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: ROS2动作结果格式,包含所有资源名称信息
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
debug_info = {
|
||||||
|
"total_resources": len(self.station_resources),
|
||||||
|
"plate1_resources": len(self.station_resources_plate1),
|
||||||
|
"plate2_resources": len(self.station_resources_plate2),
|
||||||
|
"plate1_names": list(self.station_resources_plate1.keys())[:10], # 显示前10个
|
||||||
|
"plate2_names": list(self.station_resources_plate2.keys())[:10], # 显示前10个
|
||||||
|
"all_resource_names": list(self.station_resources.keys())[:20], # 显示前20个
|
||||||
|
}
|
||||||
|
|
||||||
|
# 检查是否有用户提到的命名格式
|
||||||
|
batterytestposition_names = [name for name in self.station_resources.keys()
|
||||||
|
if "batterytestposition" in name]
|
||||||
|
debug_info["batterytestposition_names"] = batterytestposition_names[:10]
|
||||||
|
|
||||||
|
success_msg = f"资源调试信息获取成功,共{debug_info['total_resources']}个资源"
|
||||||
|
if self._ros_node:
|
||||||
|
self._ros_node.lab_logger().info(success_msg)
|
||||||
|
self._ros_node.lab_logger().info(f"调试信息: {debug_info}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"return_info": success_msg,
|
||||||
|
"success": True,
|
||||||
|
"debug_data": debug_info
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"获取资源调试信息失败: {str(e)}"
|
||||||
|
if self._ros_node:
|
||||||
|
self._ros_node.lab_logger().error(error_msg)
|
||||||
|
return {"return_info": error_msg, "success": False}
|
||||||
|
|
||||||
# ========================
|
# ========================
|
||||||
# 辅助方法
|
# 辅助方法
|
||||||
# ========================
|
# ========================
|
||||||
@@ -538,6 +659,228 @@ class NewareBatteryTestSystem:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" 获取状态失败: {e}")
|
print(f" 获取状态失败: {e}")
|
||||||
|
|
||||||
|
# ========================
|
||||||
|
# CSV批量提交功能(新增)
|
||||||
|
# ========================
|
||||||
|
|
||||||
|
def _ensure_local_import_path(self):
|
||||||
|
"""确保本地模块导入路径"""
|
||||||
|
base_dir = os.path.dirname(__file__)
|
||||||
|
if base_dir not in sys.path:
|
||||||
|
sys.path.insert(0, base_dir)
|
||||||
|
|
||||||
|
def _canon(self, bs: str) -> str:
|
||||||
|
"""规范化电池体系名称"""
|
||||||
|
return str(bs).strip().replace('-', '_').upper()
|
||||||
|
|
||||||
|
def _compute_values(self, row):
|
||||||
|
"""
|
||||||
|
计算活性物质质量和容量
|
||||||
|
|
||||||
|
Args:
|
||||||
|
row: DataFrame行数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (活性物质质量mg, 容量mAh)
|
||||||
|
"""
|
||||||
|
pw = float(row['Pole_Weight'])
|
||||||
|
cm = float(row['集流体质量'])
|
||||||
|
am = row['活性物质含量']
|
||||||
|
if isinstance(am, str) and am.endswith('%'):
|
||||||
|
amv = float(am.rstrip('%')) / 100.0
|
||||||
|
else:
|
||||||
|
amv = float(am)
|
||||||
|
act_mass = (pw - cm) * amv
|
||||||
|
sc = float(row['克容量mah/g'])
|
||||||
|
cap = act_mass * sc / 1000.0
|
||||||
|
return round(act_mass, 2), round(cap, 3)
|
||||||
|
|
||||||
|
def _get_xml_builder(self, gen_mod, key: str):
|
||||||
|
"""
|
||||||
|
获取对应电池体系的XML生成函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gen_mod: generate_xml_content模块
|
||||||
|
key: 电池体系标识
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
callable: XML生成函数
|
||||||
|
"""
|
||||||
|
fmap = {
|
||||||
|
'LB6': gen_mod.xml_LB6,
|
||||||
|
'GR_LI': gen_mod.xml_Gr_Li,
|
||||||
|
'LFP_LI': gen_mod.xml_LFP_Li,
|
||||||
|
'LFP_GR': gen_mod.xml_LFP_Gr,
|
||||||
|
'811_LI_002': gen_mod.xml_811_Li_002,
|
||||||
|
'811_LI_005': gen_mod.xml_811_Li_005,
|
||||||
|
'SIGR_LI_STEP': gen_mod.xml_SiGr_Li_Step,
|
||||||
|
'SIGR_LI': gen_mod.xml_SiGr_Li_Step,
|
||||||
|
'811_SIGR': gen_mod.xml_811_SiGr,
|
||||||
|
}
|
||||||
|
if key not in fmap:
|
||||||
|
raise ValueError(f"未定义电池体系映射: {key}")
|
||||||
|
return fmap[key]
|
||||||
|
|
||||||
|
def _save_xml(self, xml: str, path: str):
|
||||||
|
"""
|
||||||
|
保存XML文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
xml: XML内容
|
||||||
|
path: 文件路径
|
||||||
|
"""
|
||||||
|
with open(path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(xml)
|
||||||
|
|
||||||
|
def submit_from_csv(self, csv_path: str, output_dir: str = ".") -> dict:
|
||||||
|
"""
|
||||||
|
从CSV文件批量提交Neware测试任务(设备动作)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
csv_path (str): 输入CSV文件路径
|
||||||
|
output_dir (str): 输出目录,用于存储XML文件和备份,默认当前目录
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: 执行结果 {"return_info": str, "success": bool, "submitted_count": int}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 确保可以导入本地模块
|
||||||
|
self._ensure_local_import_path()
|
||||||
|
import pandas as pd
|
||||||
|
import generate_xml_content as gen_mod
|
||||||
|
from neware_driver import start_test
|
||||||
|
|
||||||
|
if self._ros_node:
|
||||||
|
self._ros_node.lab_logger().info(f"开始从CSV文件提交任务: {csv_path}")
|
||||||
|
|
||||||
|
# 读取CSV文件
|
||||||
|
if not os.path.exists(csv_path):
|
||||||
|
error_msg = f"CSV文件不存在: {csv_path}"
|
||||||
|
if self._ros_node:
|
||||||
|
self._ros_node.lab_logger().error(error_msg)
|
||||||
|
return {"return_info": error_msg, "success": False, "submitted_count": 0}
|
||||||
|
|
||||||
|
df = pd.read_csv(csv_path, encoding='gbk')
|
||||||
|
|
||||||
|
# 验证必需列
|
||||||
|
required = [
|
||||||
|
'Battery_Code', 'Pole_Weight', '集流体质量', '活性物质含量',
|
||||||
|
'克容量mah/g', '电池体系', '设备号', '排号', '通道号'
|
||||||
|
]
|
||||||
|
missing = [c for c in required if c not in df.columns]
|
||||||
|
if missing:
|
||||||
|
error_msg = f"CSV缺少必需列: {missing}"
|
||||||
|
if self._ros_node:
|
||||||
|
self._ros_node.lab_logger().error(error_msg)
|
||||||
|
return {"return_info": error_msg, "success": False, "submitted_count": 0}
|
||||||
|
|
||||||
|
# 创建输出目录
|
||||||
|
xml_dir = os.path.join(output_dir, 'xml_dir')
|
||||||
|
backup_dir = os.path.join(output_dir, 'backup_dir')
|
||||||
|
os.makedirs(xml_dir, exist_ok=True)
|
||||||
|
os.makedirs(backup_dir, exist_ok=True)
|
||||||
|
|
||||||
|
if self._ros_node:
|
||||||
|
self._ros_node.lab_logger().info(
|
||||||
|
f"输出目录: XML={xml_dir}, 备份={backup_dir}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 逐行处理CSV数据
|
||||||
|
submitted_count = 0
|
||||||
|
results = []
|
||||||
|
|
||||||
|
for idx, row in df.iterrows():
|
||||||
|
try:
|
||||||
|
coin_id = str(row['Battery_Code'])
|
||||||
|
|
||||||
|
# 计算活性物质质量和容量
|
||||||
|
act_mass, cap_mAh = self._compute_values(row)
|
||||||
|
|
||||||
|
if cap_mAh < 0:
|
||||||
|
error_msg = (
|
||||||
|
f"容量为负数: Battery_Code={coin_id}, "
|
||||||
|
f"活性物质质量mg={act_mass}, 容量mah={cap_mAh}"
|
||||||
|
)
|
||||||
|
if self._ros_node:
|
||||||
|
self._ros_node.lab_logger().warning(error_msg)
|
||||||
|
results.append(f"行{idx+1} 失败: {error_msg}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 获取电池体系对应的XML生成函数
|
||||||
|
key = self._canon(row['电池体系'])
|
||||||
|
builder = self._get_xml_builder(gen_mod, key)
|
||||||
|
|
||||||
|
# 生成XML内容
|
||||||
|
xml_content = builder(act_mass, cap_mAh)
|
||||||
|
|
||||||
|
# 获取设备信息
|
||||||
|
devid = int(row['设备号'])
|
||||||
|
subdevid = int(row['排号'])
|
||||||
|
chlid = int(row['通道号'])
|
||||||
|
|
||||||
|
# 保存XML文件
|
||||||
|
recipe_path = os.path.join(
|
||||||
|
xml_dir,
|
||||||
|
f"{coin_id}_{devid}_{subdevid}_{chlid}.xml"
|
||||||
|
)
|
||||||
|
self._save_xml(xml_content, recipe_path)
|
||||||
|
|
||||||
|
# 提交测试任务
|
||||||
|
resp = start_test(
|
||||||
|
ip=self.ip,
|
||||||
|
port=self.port,
|
||||||
|
devid=devid,
|
||||||
|
subdevid=subdevid,
|
||||||
|
chlid=chlid,
|
||||||
|
CoinID=coin_id,
|
||||||
|
recipe_path=recipe_path,
|
||||||
|
backup_dir=backup_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
submitted_count += 1
|
||||||
|
results.append(f"行{idx+1} {coin_id}: {resp}")
|
||||||
|
|
||||||
|
if self._ros_node:
|
||||||
|
self._ros_node.lab_logger().info(
|
||||||
|
f"已提交 {coin_id} (设备{devid}-{subdevid}-{chlid}): {resp}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"行{idx+1} 处理失败: {str(e)}"
|
||||||
|
results.append(error_msg)
|
||||||
|
if self._ros_node:
|
||||||
|
self._ros_node.lab_logger().error(error_msg)
|
||||||
|
|
||||||
|
# 汇总结果
|
||||||
|
success_msg = (
|
||||||
|
f"批量提交完成: 成功{submitted_count}个,共{len(df)}行。"
|
||||||
|
f"\n详细结果:\n" + "\n".join(results)
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._ros_node:
|
||||||
|
self._ros_node.lab_logger().info(
|
||||||
|
f"批量提交完成: 成功{submitted_count}/{len(df)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"return_info": success_msg,
|
||||||
|
"success": True,
|
||||||
|
"submitted_count": submitted_count,
|
||||||
|
"total_count": len(df),
|
||||||
|
"results": results
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"批量提交失败: {str(e)}"
|
||||||
|
if self._ros_node:
|
||||||
|
self._ros_node.lab_logger().error(error_msg)
|
||||||
|
return {
|
||||||
|
"return_info": error_msg,
|
||||||
|
"success": False,
|
||||||
|
"submitted_count": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_device_summary(self) -> dict:
|
def get_device_summary(self) -> dict:
|
||||||
"""
|
"""
|
||||||
获取设备级别的摘要统计(设备动作)
|
获取设备级别的摘要统计(设备动作)
|
||||||
49
unilabos/devices/neware_battery_test_system/neware_driver.py
Normal file
49
unilabos/devices/neware_battery_test_system/neware_driver.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import socket
|
||||||
|
END_MARKS = [b"\r\n#\r\n", b"</bts>"] # 读到任一标志即可判定完整响应
|
||||||
|
|
||||||
|
def build_start_command(devid, subdevid, chlid, CoinID,
|
||||||
|
ip_in_xml="127.0.0.1",
|
||||||
|
devtype:int=27,
|
||||||
|
recipe_path:str=f"D:\\HHM_test\\A001.xml",
|
||||||
|
backup_dir:str=f"D:\\HHM_test\\backup") -> str:
|
||||||
|
lines = [
|
||||||
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||||
|
'<bts version="1.0">',
|
||||||
|
' <cmd>start</cmd>',
|
||||||
|
' <list count="1">',
|
||||||
|
f' <start ip="{ip_in_xml}" devtype="{devtype}" devid="{devid}" subdevid="{subdevid}" chlid="{chlid}" barcode="{CoinID}">{recipe_path}</start>',
|
||||||
|
f' <backup backupdir="{backup_dir}" remotedir="" filenametype="1" customfilename="" createdirbydate="0" filetype="0" backupontime="1" backupontimeinterval="1" backupfree="0" />',
|
||||||
|
' </list>',
|
||||||
|
'</bts>',
|
||||||
|
]
|
||||||
|
# TCP 模式:请求必须以 #\r\n 结束(协议要求)
|
||||||
|
return "\r\n".join(lines) + "\r\n#\r\n"
|
||||||
|
|
||||||
|
def recv_until_marks(sock: socket.socket, timeout=60):
|
||||||
|
sock.settimeout(timeout) # 上限给足,协议允许到 30s:contentReference[oaicite:2]{index=2}
|
||||||
|
buf = bytearray()
|
||||||
|
while True:
|
||||||
|
chunk = sock.recv(8192)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
buf += chunk
|
||||||
|
# 读到结束标志就停,避免等对端断开
|
||||||
|
for m in END_MARKS:
|
||||||
|
if m in buf:
|
||||||
|
return bytes(buf)
|
||||||
|
# 保险:读到完整 XML 结束标签也停
|
||||||
|
if b"</bts>" in buf:
|
||||||
|
return bytes(buf)
|
||||||
|
return bytes(buf)
|
||||||
|
|
||||||
|
def start_test(ip="127.0.0.1", port=502, devid=3, subdevid=2, chlid=1, CoinID="A001", recipe_path=f"D:\\HHM_test\\A001.xml", backup_dir=f"D:\\HHM_test\\backup"):
|
||||||
|
xml_cmd = build_start_command(devid=devid, subdevid=subdevid, chlid=chlid, CoinID=CoinID, recipe_path=recipe_path, backup_dir=backup_dir)
|
||||||
|
#print(xml_cmd)
|
||||||
|
with socket.create_connection((ip, port), timeout=60) as s:
|
||||||
|
s.sendall(xml_cmd.encode("utf-8"))
|
||||||
|
data = recv_until_marks(s, timeout=60)
|
||||||
|
return data.decode("utf-8", errors="replace")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
resp = start_test(ip="127.0.0.1", port=502, devid=4, subdevid=10, chlid=1, CoinID="A001", recipe_path=f"D:\\HHM_test\\A001.xml", backup_dir=f"D:\\HHM_test\\backup")
|
||||||
|
print(resp)
|
||||||
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from cgi import print_arguments
|
from cgi import print_arguments
|
||||||
from doctest import debug
|
from doctest import debug
|
||||||
from typing import Dict, Any, List, Optional
|
from typing import Dict, Any, List, Optional, Tuple
|
||||||
import requests
|
import requests
|
||||||
from pylabrobot.resources.resource import Resource as ResourcePLR
|
from pylabrobot.resources.resource import Resource as ResourcePLR
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -11,14 +11,30 @@ from datetime import datetime, timedelta
|
|||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
import json
|
import json
|
||||||
|
from copy import deepcopy
|
||||||
from urllib3 import response
|
from urllib3 import response
|
||||||
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation, BioyondResourceSynchronizer
|
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation, BioyondResourceSynchronizer
|
||||||
from unilabos.devices.workstation.bioyond_studio.config import (
|
from unilabos.devices.workstation.bioyond_studio.config import (
|
||||||
API_CONFIG, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING, SOLID_LIQUID_MAPPINGS
|
API_CONFIG, MATERIAL_TYPE_MAPPINGS, WAREHOUSE_MAPPING, SOLID_LIQUID_MAPPINGS
|
||||||
)
|
)
|
||||||
from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService
|
from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService
|
||||||
|
from unilabos.resources.bioyond.decks import BIOYOND_YB_Deck
|
||||||
|
from unilabos.resources.graphio import resource_bioyond_to_plr
|
||||||
from unilabos.utils.log import logger
|
from unilabos.utils.log import logger
|
||||||
from unilabos.registry.registry import lab_registry
|
from unilabos.registry.registry import lab_registry
|
||||||
|
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode
|
||||||
|
|
||||||
|
|
||||||
|
class device(BIOYOND_YB_Deck):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def deserialize(cls, data, allow_marshal=False): # type: ignore[override]
|
||||||
|
patched = dict(data)
|
||||||
|
if patched.get("type") == "device":
|
||||||
|
patched["type"] = "Deck"
|
||||||
|
if patched.get("category") == "device":
|
||||||
|
patched["category"] = "deck"
|
||||||
|
return super().deserialize(patched, allow_marshal=allow_marshal)
|
||||||
|
|
||||||
def _iso_local_now_ms() -> str:
|
def _iso_local_now_ms() -> str:
|
||||||
# 文档要求:到毫秒 + Z,例如 2025-08-15T05:43:22.814Z
|
# 文档要求:到毫秒 + Z,例如 2025-08-15T05:43:22.814Z
|
||||||
@@ -38,12 +54,14 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
def __init__(self, config: dict = None, deck=None, protocol_type=None, **kwargs):
|
def __init__(self, config: dict = None, deck=None, protocol_type=None, **kwargs):
|
||||||
|
|
||||||
# 使用统一配置,支持自定义覆盖, 从 config.py 加载完整配置
|
# 使用统一配置,支持自定义覆盖, 从 config.py 加载完整配置
|
||||||
self.bioyond_config ={
|
self.bioyond_config = {
|
||||||
**API_CONFIG,
|
**API_CONFIG,
|
||||||
"material_type_mappings": MATERIAL_TYPE_MAPPINGS,
|
"material_type_mappings": MATERIAL_TYPE_MAPPINGS,
|
||||||
"warehouse_mapping": WAREHOUSE_MAPPING,
|
"warehouse_mapping": WAREHOUSE_MAPPING,
|
||||||
"debug_mode": False
|
"debug_mode": False,
|
||||||
}
|
}
|
||||||
|
if config:
|
||||||
|
self.bioyond_config.update(config)
|
||||||
|
|
||||||
# "material_type_mappings": MATERIAL_TYPE_MAPPINGS
|
# "material_type_mappings": MATERIAL_TYPE_MAPPINGS
|
||||||
# "warehouse_mapping": WAREHOUSE_MAPPING
|
# "warehouse_mapping": WAREHOUSE_MAPPING
|
||||||
@@ -54,6 +72,12 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
self.http_service_started = self.debug_mode
|
self.http_service_started = self.debug_mode
|
||||||
self._device_id = "bioyond_cell_workstation" # 默认值,后续会从_ros_node获取
|
self._device_id = "bioyond_cell_workstation" # 默认值,后续会从_ros_node获取
|
||||||
super().__init__(bioyond_config=config, deck=deck)
|
super().__init__(bioyond_config=config, deck=deck)
|
||||||
|
self.transfer_target_device_id = self.bioyond_config.get("transfer_target_device_id", "BatteryStation")
|
||||||
|
self.transfer_target_parent = self.bioyond_config.get("transfer_target_parent", "YB_YH_Deck")
|
||||||
|
self.transfer_timeout = float(self.bioyond_config.get("transfer_timeout", 180.0))
|
||||||
|
self.coin_cell_workflow_config = self.bioyond_config.get("coin_cell_workflow_config", {})
|
||||||
|
self.pending_transfer_materials: List[Dict[str, Any]] = []
|
||||||
|
self.pending_transfer_plr: List[ResourcePLR] = []
|
||||||
self.update_push_ip() #直接修改奔耀端的报送ip地址
|
self.update_push_ip() #直接修改奔耀端的报送ip地址
|
||||||
logger.info("已更新奔耀端推送 IP 地址")
|
logger.info("已更新奔耀端推送 IP 地址")
|
||||||
|
|
||||||
@@ -322,6 +346,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
"posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]),
|
"posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]),
|
||||||
"materialName": str(row[5]).strip(),
|
"materialName": str(row[5]).strip(),
|
||||||
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
|
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
|
||||||
|
"temperature": 0,
|
||||||
})
|
})
|
||||||
# 四号手套箱原液瓶面
|
# 四号手套箱原液瓶面
|
||||||
for _, row in df.iloc[14:23, 2:9].iterrows():
|
for _, row in df.iloc[14:23, 2:9].iterrows():
|
||||||
@@ -333,6 +358,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
|
"quantity": float(row[6]) if pd.notna(row[6]) else 0.0,
|
||||||
"materialType": str(row[7]).strip() if pd.notna(row[7]) else "",
|
"materialType": str(row[7]).strip() if pd.notna(row[7]) else "",
|
||||||
"targetWH": str(row[8]).strip() if pd.notna(row[8]) else "",
|
"targetWH": str(row[8]).strip() if pd.notna(row[8]) else "",
|
||||||
|
"temperature": 0,
|
||||||
})
|
})
|
||||||
# 三号手套箱人工堆栈
|
# 三号手套箱人工堆栈
|
||||||
for _, row in df.iloc[25:40, 2:7].iterrows():
|
for _, row in df.iloc[25:40, 2:7].iterrows():
|
||||||
@@ -342,11 +368,12 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
"posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]),
|
"posX": int(row[2]), "posY": int(row[3]), "posZ": int(row[4]),
|
||||||
"materialType": str(row[5]).strip() if pd.notna(row[5]) else "",
|
"materialType": str(row[5]).strip() if pd.notna(row[5]) else "",
|
||||||
"materialId": str(row[6]).strip() if pd.notna(row[6]) else "",
|
"materialId": str(row[6]).strip() if pd.notna(row[6]) else "",
|
||||||
"quantity": 1
|
"quantity": 1,
|
||||||
|
"temperature": 0,
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
logger.warning(f"未找到 Excel 文件 {xlsx_path},自动切换到手动参数模式。")
|
logger.warning(f"未找到 Excel 文件 {xlsx_path},自动切换到手动参数模式。")
|
||||||
|
# TODO: 温度下面手动模式没改,上面的改了
|
||||||
# ---------- 模式 2: 手动填写 ----------
|
# ---------- 模式 2: 手动填写 ----------
|
||||||
if not items:
|
if not items:
|
||||||
params = locals()
|
params = locals()
|
||||||
@@ -389,10 +416,14 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
order_code = response.get("data", {}).get("orderCode")
|
order_code = response.get("data", {}).get("orderCode")
|
||||||
if not order_code:
|
if not order_code:
|
||||||
logger.error("上料任务未返回有效 orderCode!")
|
logger.error("上料任务未返回有效 orderCode!")
|
||||||
return response
|
return {"api_response": response, "order_finish": None}
|
||||||
# 等待完成报送
|
# 等待完成报送
|
||||||
result = self.wait_for_order_finish(order_code)
|
result = self.wait_for_order_finish(order_code)
|
||||||
return result
|
return {
|
||||||
|
"api_response": response,
|
||||||
|
"order_finish": result,
|
||||||
|
"items": items,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def auto_batch_outbound_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]:
|
def auto_batch_outbound_from_xlsx(self, xlsx_path: str) -> Dict[str, Any]:
|
||||||
@@ -463,7 +494,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
# 2.14 新建实验
|
# 2.14 新建实验
|
||||||
def create_orders(self, xlsx_path: str) -> Dict[str, Any]:
|
def create_orders(self, xlsx_path: str, *, material_filter: Optional[str] = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
从 Excel 解析并创建实验(2.14)
|
从 Excel 解析并创建实验(2.14)
|
||||||
约定:
|
约定:
|
||||||
@@ -543,6 +574,14 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
def _as_float(val, default=0.0) -> float:
|
||||||
|
try:
|
||||||
|
if pd.isna(val):
|
||||||
|
return default
|
||||||
|
return float(val)
|
||||||
|
except Exception:
|
||||||
|
return default
|
||||||
|
|
||||||
def _as_str(val, default="") -> str:
|
def _as_str(val, default="") -> str:
|
||||||
if val is None or (isinstance(val, float) and pd.isna(val)):
|
if val is None or (isinstance(val, float) and pd.isna(val)):
|
||||||
return default
|
return default
|
||||||
@@ -576,9 +615,9 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
"createTime": _to_ymd_slash(row[col_create_time]) if col_create_time else _to_ymd_slash(None),
|
"createTime": _to_ymd_slash(row[col_create_time]) if col_create_time else _to_ymd_slash(None),
|
||||||
"bottleType": _as_str(row[col_bottle_type], default="配液小瓶") if col_bottle_type else "配液小瓶",
|
"bottleType": _as_str(row[col_bottle_type], default="配液小瓶") if col_bottle_type else "配液小瓶",
|
||||||
"mixTime": _as_int(row[col_mix_time]) if col_mix_time else 0,
|
"mixTime": _as_int(row[col_mix_time]) if col_mix_time else 0,
|
||||||
"loadSheddingInfo": _as_int(row[col_load]) if col_load else 0,
|
"loadSheddingInfo": _as_float(row[col_load]) if col_load else 0.0,
|
||||||
"pouchCellInfo": _as_int(row[col_pouch]) if col_pouch else 0,
|
"pouchCellInfo": _as_float(row[col_pouch]) if col_pouch else 0,
|
||||||
"conductivityInfo": _as_int(row[col_cond]) if col_cond else 0,
|
"conductivityInfo": _as_float(row[col_cond]) if col_cond else 0,
|
||||||
"conductivityBottleCount": _as_int(row[col_cond_cnt]) if col_cond_cnt else 0,
|
"conductivityBottleCount": _as_int(row[col_cond_cnt]) if col_cond_cnt else 0,
|
||||||
"materialInfos": mats,
|
"materialInfos": mats,
|
||||||
"totalMass": round(total_mass, 4) # 自动汇总
|
"totalMass": round(total_mass, 4) # 自动汇总
|
||||||
@@ -594,7 +633,8 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
print(f"[create_orders] ⚠️ 第 {idx+1} 行未找到有效物料")
|
print(f"[create_orders] ⚠️ 第 {idx+1} 行未找到有效物料")
|
||||||
|
|
||||||
orders.append(order_data)
|
orders.append(order_data)
|
||||||
|
print("================================================")
|
||||||
|
print("orders:", orders)
|
||||||
|
|
||||||
print(f"[create_orders] 即将提交订单数量: {len(orders)}")
|
print(f"[create_orders] 即将提交订单数量: {len(orders)}")
|
||||||
response = self._post_lims("/api/lims/order/orders", orders)
|
response = self._post_lims("/api/lims/order/orders", orders)
|
||||||
@@ -611,7 +651,34 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
return response
|
return response
|
||||||
# 等待完成报送
|
# 等待完成报送
|
||||||
result = self.wait_for_order_finish(order_code)
|
result = self.wait_for_order_finish(order_code)
|
||||||
return result
|
report_data = result.get("report") if isinstance(result, dict) else None
|
||||||
|
materials_from_report = (
|
||||||
|
report_data.get("usedMaterials") if isinstance(report_data, dict) else None
|
||||||
|
)
|
||||||
|
if materials_from_report:
|
||||||
|
materials = materials_from_report
|
||||||
|
logger.info(
|
||||||
|
"[create_orders] 使用订单完成报送中的物料信息: "
|
||||||
|
f"{len(materials)} 条"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
materials = self._fetch_bioyond_materials(filter_keyword=material_filter)
|
||||||
|
logger.info(
|
||||||
|
"[create_orders] 未收到订单报送物料信息,回退到实时查询"
|
||||||
|
)
|
||||||
|
print("materials_from_report:", materials_from_report)
|
||||||
|
# TODO: 需要将 materials 字典转换为 ResourceSlot 对象后才能转运
|
||||||
|
# self.transfer_resource_to_another(
|
||||||
|
# resource=[materials],
|
||||||
|
# mount_resource=["YB_YH_Deck"],
|
||||||
|
# sites=[None],
|
||||||
|
# mount_device_id="BatteryStation"
|
||||||
|
# )
|
||||||
|
return {
|
||||||
|
"api_response": response,
|
||||||
|
"order_finish": result,
|
||||||
|
"materials": materials,
|
||||||
|
}
|
||||||
|
|
||||||
# 2.7 启动调度
|
# 2.7 启动调度
|
||||||
def scheduler_start(self) -> Dict[str, Any]:
|
def scheduler_start(self) -> Dict[str, Any]:
|
||||||
@@ -683,6 +750,7 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
return response
|
return response
|
||||||
# 等待完成报送
|
# 等待完成报送
|
||||||
result = self.wait_for_order_finish(order_code)
|
result = self.wait_for_order_finish(order_code)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# 2.5 批量查询实验报告(post过滤关键字查询)
|
# 2.5 批量查询实验报告(post过滤关键字查询)
|
||||||
@@ -1075,7 +1143,12 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
if bottle_moudle == moudle_name:
|
if bottle_moudle == moudle_name:
|
||||||
bottle_type = key
|
bottle_type = key
|
||||||
break
|
break
|
||||||
self.create_sample(plr_resource.name, board_type,bottle_type,site)
|
|
||||||
|
# 从 parent_resource 获取仓库名称
|
||||||
|
warehouse_name = parent_resource.name if parent_resource else "手动堆栈"
|
||||||
|
logger.info(f"拖拽上料: {plr_resource.name} -> {warehouse_name} / {site}")
|
||||||
|
|
||||||
|
self.create_sample(plr_resource.name, board_type, bottle_type, site, warehouse_name)
|
||||||
return
|
return
|
||||||
self.lab_logger().warning(f"无库位的上料,不处理,{plr_resource} 挂载到 {parent_resource}")
|
self.lab_logger().warning(f"无库位的上料,不处理,{plr_resource} 挂载到 {parent_resource}")
|
||||||
|
|
||||||
@@ -1084,17 +1157,31 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
name: str,
|
name: str,
|
||||||
board_type: str,
|
board_type: str,
|
||||||
bottle_type: str,
|
bottle_type: str,
|
||||||
location_code: str
|
location_code: str,
|
||||||
|
warehouse_name: str = "手动堆栈"
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""创建配液板物料并自动入库。
|
"""创建配液板物料并自动入库。
|
||||||
Args:
|
Args:
|
||||||
material_name: 物料名称,支持 "5ml分液瓶板"/"5ml分液瓶"、"配液瓶(小)板"/"配液瓶(小)"。
|
name: 物料名称
|
||||||
quantity: 主物料与明细的数量,默认 1。
|
board_type: 板类型,如 "5ml分液瓶板"、"配液瓶(小)板"
|
||||||
location_code: 库位编号,例如 "A01",将自动映射为 "手动堆栈" 下的 UUID。
|
bottle_type: 瓶类型,如 "5ml分液瓶"、"配液瓶(小)"
|
||||||
|
location_code: 库位编号,例如 "A01"
|
||||||
|
warehouse_name: 仓库名称,默认为 "手动堆栈",支持 "自动堆栈-左"、"自动堆栈-右" 等
|
||||||
"""
|
"""
|
||||||
carrier_type_id = MATERIAL_TYPE_MAPPINGS[board_type][1]
|
carrier_type_id = MATERIAL_TYPE_MAPPINGS[board_type][1]
|
||||||
bottle_type_id = MATERIAL_TYPE_MAPPINGS[bottle_type][1]
|
bottle_type_id = MATERIAL_TYPE_MAPPINGS[bottle_type][1]
|
||||||
location_id = WAREHOUSE_MAPPING["手动堆栈"]["site_uuids"][location_code]
|
|
||||||
|
# 从指定仓库获取库位UUID
|
||||||
|
if warehouse_name not in WAREHOUSE_MAPPING:
|
||||||
|
logger.error(f"未找到仓库: {warehouse_name},回退到手动堆栈")
|
||||||
|
warehouse_name = "手动堆栈"
|
||||||
|
|
||||||
|
if location_code not in WAREHOUSE_MAPPING[warehouse_name]["site_uuids"]:
|
||||||
|
logger.error(f"仓库 {warehouse_name} 中未找到库位 {location_code}")
|
||||||
|
raise ValueError(f"库位 {location_code} 在仓库 {warehouse_name} 中不存在")
|
||||||
|
|
||||||
|
location_id = WAREHOUSE_MAPPING[warehouse_name]["site_uuids"][location_code]
|
||||||
|
logger.info(f"创建样品入库: {name} -> {warehouse_name}/{location_code} (UUID: {location_id})")
|
||||||
|
|
||||||
# 新建小瓶
|
# 新建小瓶
|
||||||
details = []
|
details = []
|
||||||
@@ -1132,33 +1219,221 @@ class BioyondCellWorkstation(BioyondWorkstation):
|
|||||||
})
|
})
|
||||||
return final_result
|
return final_result
|
||||||
|
|
||||||
|
def _fetch_bioyond_materials(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
filter_keyword: Optional[str] = None,
|
||||||
|
type_mode: int = 2,
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
query: Dict[str, Any] = {
|
||||||
|
"typeMode": type_mode,
|
||||||
|
"includeDetail": True,
|
||||||
|
}
|
||||||
|
if filter_keyword:
|
||||||
|
query["filter"] = filter_keyword
|
||||||
|
|
||||||
|
response = self._post_lims("/api/lims/storage/stock-material", query)
|
||||||
|
raw_materials = response.get("data")
|
||||||
|
if not isinstance(raw_materials, list):
|
||||||
|
raw_materials = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
resource_bioyond_to_plr(
|
||||||
|
raw_materials,
|
||||||
|
type_mapping=self.bioyond_config.get("material_type_mappings", MATERIAL_TYPE_MAPPINGS),
|
||||||
|
deck=self.deck,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(f"转换奔曜物料到 PLR 失败: {exc}", exc_info=True)
|
||||||
|
|
||||||
|
return raw_materials
|
||||||
|
|
||||||
|
def _convert_materials_to_plr(self, materials: List[Dict[str, Any]]) -> List[ResourcePLR]:
|
||||||
|
try:
|
||||||
|
return resource_bioyond_to_plr(
|
||||||
|
deepcopy(materials),
|
||||||
|
type_mapping=self.bioyond_config.get("material_type_mappings", MATERIAL_TYPE_MAPPINGS),
|
||||||
|
deck=self.deck,
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"物料转换为 PLR 失败: {exc}", exc_info=True)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _wait_for_future(self, future, stage: str, timeout: Optional[float] = None):
|
||||||
|
if future is None:
|
||||||
|
return None
|
||||||
|
timeout = timeout or self.transfer_timeout
|
||||||
|
start = time.time()
|
||||||
|
while not future.done():
|
||||||
|
if (time.time() - start) > timeout:
|
||||||
|
raise TimeoutError(f"{stage} 超时 {timeout}s")
|
||||||
|
time.sleep(0.05)
|
||||||
|
return future.result()
|
||||||
|
|
||||||
|
def _register_plr_resources(self, resources: List[ResourcePLR]) -> None:
|
||||||
|
if not resources or not hasattr(self, "_ros_node") or self._ros_node is None:
|
||||||
|
return
|
||||||
|
future = ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, resources=resources)
|
||||||
|
self._wait_for_future(future, "update_resource")
|
||||||
|
|
||||||
|
def _get_target_resource(self, name: str) -> ResourcePLR:
|
||||||
|
if not hasattr(self, "_ros_node") or self._ros_node is None:
|
||||||
|
raise RuntimeError("ROS 节点未初始化,无法获取资源")
|
||||||
|
resource = self._ros_node.resource_tracker.figure_resource({"name": name}, try_mode=False) # type: ignore
|
||||||
|
if resource is None:
|
||||||
|
raise ValueError(f"未找到目标资源: {name}")
|
||||||
|
return resource
|
||||||
|
|
||||||
|
def _allocate_sites(self, parent_resource: ResourcePLR, count: int) -> List[str]:
|
||||||
|
if not hasattr(parent_resource, "get_free_sites"):
|
||||||
|
raise ValueError(f"资源 {parent_resource} 不支持自动分配站位")
|
||||||
|
free_indices = list(parent_resource.get_free_sites())
|
||||||
|
if len(free_indices) < count:
|
||||||
|
raise ValueError(f"{parent_resource.name} 可用站位不足 (need {count}, have {len(free_indices)})")
|
||||||
|
ordering = list(getattr(parent_resource, "_ordering", {}).keys())
|
||||||
|
sites: List[str] = []
|
||||||
|
for idx in free_indices[:count]:
|
||||||
|
if ordering and idx < len(ordering):
|
||||||
|
sites.append(ordering[idx])
|
||||||
|
else:
|
||||||
|
sites.append(str(idx))
|
||||||
|
return sites
|
||||||
|
|
||||||
|
def _invoke_coin_cell_workflow(self, material_payload: List[Dict[str, Any]]) -> Any:
|
||||||
|
timeout = float(self.bioyond_config.get("coin_cell_workflow_timeout", 300.0))
|
||||||
|
workflow_payload: Dict[str, Any] = {}
|
||||||
|
if isinstance(self.coin_cell_workflow_config, dict):
|
||||||
|
workflow_payload.update(deepcopy(self.coin_cell_workflow_config))
|
||||||
|
workflow_payload["materials"] = deepcopy(material_payload)
|
||||||
|
return self._call_remote_device_method(
|
||||||
|
self.transfer_target_device_id,
|
||||||
|
"run_coin_cell_assembly_workflow",
|
||||||
|
timeout=timeout,
|
||||||
|
workflow_config=workflow_payload,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _call_remote_device_method(
|
||||||
|
self,
|
||||||
|
device_id: str,
|
||||||
|
method: str,
|
||||||
|
*,
|
||||||
|
timeout: Optional[float] = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> Any:
|
||||||
|
if not hasattr(self, "_ros_node") or self._ros_node is None:
|
||||||
|
raise RuntimeError("ROS 节点未初始化,无法调用远程设备")
|
||||||
|
if not device_id:
|
||||||
|
raise ValueError("device_id 不能为空")
|
||||||
|
if not method:
|
||||||
|
raise ValueError("method 不能为空")
|
||||||
|
|
||||||
|
timeout = timeout or self.transfer_timeout
|
||||||
|
payload = json.dumps(
|
||||||
|
{
|
||||||
|
"function_name": method,
|
||||||
|
"function_args": kwargs,
|
||||||
|
},
|
||||||
|
ensure_ascii=False,
|
||||||
|
)
|
||||||
|
future = ROS2DeviceNode.run_async_func(
|
||||||
|
self._ros_node.execute_single_action,
|
||||||
|
True,
|
||||||
|
device_id=device_id,
|
||||||
|
action_name="_execute_driver_command_async",
|
||||||
|
action_kwargs={"string": payload},
|
||||||
|
)
|
||||||
|
result = self._wait_for_future(future, f"{device_id}.{method}", timeout)
|
||||||
|
if hasattr(result, "return_info"):
|
||||||
|
try:
|
||||||
|
return json.loads(result.return_info)
|
||||||
|
except Exception:
|
||||||
|
return result.return_info
|
||||||
|
return result
|
||||||
|
|
||||||
|
def run_feeding_stage(self) -> Dict[str, Any]:
|
||||||
|
self.create_sample(
|
||||||
|
board_type="配液瓶(小)板",
|
||||||
|
bottle_type="配液瓶(小)",
|
||||||
|
location_code="B01",
|
||||||
|
name="配液瓶",
|
||||||
|
warehouse_name="手动堆栈"
|
||||||
|
)
|
||||||
|
self.create_sample(
|
||||||
|
board_type="5ml分液瓶板",
|
||||||
|
bottle_type="5ml分液瓶",
|
||||||
|
location_code="B02",
|
||||||
|
name="分液瓶",
|
||||||
|
warehouse_name="手动堆栈"
|
||||||
|
)
|
||||||
|
self.scheduler_start()
|
||||||
|
feeding_task = self.auto_feeding4to3(
|
||||||
|
xlsx_path="/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx"
|
||||||
|
)
|
||||||
|
feeding_materials = self._fetch_bioyond_materials()
|
||||||
|
return {
|
||||||
|
"feeding_materials": feeding_materials,
|
||||||
|
"feeding_items": feeding_task.get("items", []),
|
||||||
|
"feeding_task": feeding_task,
|
||||||
|
}
|
||||||
|
|
||||||
|
def run_liquid_preparation_stage(
|
||||||
|
self,
|
||||||
|
feeding_materials: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
) -> Dict[str, List[Dict[str, Any]]]:
|
||||||
|
result = self.create_orders(
|
||||||
|
xlsx_path="/Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025092701.xlsx"
|
||||||
|
)
|
||||||
|
filter_keyword = self.bioyond_config.get("mixing_material_filter") or None
|
||||||
|
materials = result.get("materials")
|
||||||
|
if materials is None:
|
||||||
|
materials = self._fetch_bioyond_materials(filter_keyword=filter_keyword)
|
||||||
|
return {
|
||||||
|
"feeding_materials": feeding_materials or [],
|
||||||
|
"liquid_materials": materials,
|
||||||
|
}
|
||||||
|
|
||||||
|
def run_transfer_stage(
|
||||||
|
self,
|
||||||
|
liquid_materials: Optional[List[Dict[str, Any]]] = None,
|
||||||
|
source_wh_id: Optional[str] = '3a19debc-84b4-0359-e2d4-b3beea49348b',
|
||||||
|
source_x: int = 1,
|
||||||
|
source_y: int = 1,
|
||||||
|
source_z: int = 1
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""转运阶段:调用transfer_3_to_2_to_1执行3到2到1转运"""
|
||||||
|
logger.info("开始执行转运阶段 (run_transfer_stage)")
|
||||||
|
|
||||||
|
# 暂时注释掉物料转换和跨工站转运逻辑
|
||||||
|
# transfer_summary: Dict[str, Any] = {}
|
||||||
|
# try:
|
||||||
|
# source_materials = liquid_materials or self._fetch_bioyond_materials()
|
||||||
|
# transfer_plr = self._convert_materials_to_plr(source_materials)
|
||||||
|
# transfer_summary["plr_count"] = len(transfer_plr)
|
||||||
|
# ...
|
||||||
|
# except Exception as exc:
|
||||||
|
# transfer_summary["error"] = str(exc)
|
||||||
|
# logger.error(f"跨工站转运失败: {exc}", exc_info=True)
|
||||||
|
|
||||||
|
# 只执行核心的3到2到1转运
|
||||||
|
transfer_result = self.transfer_3_to_2_to_1(
|
||||||
|
source_wh_id=source_wh_id,
|
||||||
|
source_x=source_x,
|
||||||
|
source_y=source_y,
|
||||||
|
source_z=source_z
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("转运阶段执行完成")
|
||||||
|
return {
|
||||||
|
"success": True,
|
||||||
|
"stage": "transfer",
|
||||||
|
"transfer_result": transfer_result
|
||||||
|
}
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
lab_registry.setup()
|
deck = BIOYOND_YB_Deck(setup=True)
|
||||||
ws = BioyondCellWorkstation()
|
w = BioyondCellWorkstation(deck=deck, address="172.16.28.102", port="502", debug_mode=False)
|
||||||
# ws.create_sample(name="test", board_type="配液瓶(小)板", bottle_type="配液瓶(小)", location_code="B01")
|
feeding = w.run_feeding_stage()
|
||||||
# logger.info(ws.scheduler_stop())
|
liquid = w.run_liquid_preparation_stage(feeding.get("feeding_materials"))
|
||||||
# logger.info(ws.scheduler_start())
|
transfer = w.run_transfer_stage(liquid.get("liquid_materials"))
|
||||||
|
|
||||||
# results = ws.create_materials(SOLID_LIQUID_MAPPINGS)
|
|
||||||
# for r in results:
|
|
||||||
# logger.info(r)
|
|
||||||
# 从CSV文件读取物料列表并批量创建入库
|
|
||||||
# result = ws.create_and_inbound_materials()
|
|
||||||
|
|
||||||
# 继续后续流程
|
|
||||||
# logger.info(ws.auto_feeding4to3()) #搬运物料到3号箱
|
|
||||||
# # # 使用正斜杠或 Path 对象来指定文件路径
|
|
||||||
# excel_path = Path("unilabos\\devices\\workstation\\bioyond_studio\\bioyond_cell\\2025092701.xlsx")
|
|
||||||
# logger.info(ws.create_orders(excel_path))
|
|
||||||
# logger.info(ws.transfer_3_to_2_to_1())
|
|
||||||
|
|
||||||
# logger.info(ws.transfer_1_to_2())
|
|
||||||
# logger.info(ws.scheduler_start())
|
|
||||||
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
# re=ws.scheduler_stop()
|
# re=ws.scheduler_stop()
|
||||||
|
|||||||
@@ -113,7 +113,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine_four",
|
"type": "MagazineHolder_4",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -249,7 +249,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -344,7 +344,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -439,7 +439,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -537,7 +537,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine_four",
|
"type": "MagazineHolder_4",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -578,7 +578,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -673,7 +673,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -768,7 +768,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -863,7 +863,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -963,7 +963,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -1006,7 +1006,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1101,7 +1101,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1196,7 +1196,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1291,7 +1291,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1386,7 +1386,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1481,7 +1481,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1581,7 +1581,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -1624,7 +1624,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1719,7 +1719,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1814,7 +1814,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1909,7 +1909,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2004,7 +2004,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2099,7 +2099,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2199,7 +2199,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -2242,7 +2242,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2337,7 +2337,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2432,7 +2432,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2527,7 +2527,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2622,7 +2622,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2717,7 +2717,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2817,7 +2817,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -2860,7 +2860,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2955,7 +2955,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3050,7 +3050,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3145,7 +3145,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3240,7 +3240,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3335,7 +3335,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3435,7 +3435,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -3478,7 +3478,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3573,7 +3573,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3668,7 +3668,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3763,7 +3763,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3858,7 +3858,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3953,7 +3953,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4053,7 +4053,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -4096,7 +4096,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4191,7 +4191,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4286,7 +4286,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4381,7 +4381,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4476,7 +4476,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4571,7 +4571,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
|
|||||||
Binary file not shown.
@@ -9,7 +9,7 @@ import os
|
|||||||
API_CONFIG = {
|
API_CONFIG = {
|
||||||
# API 连接配置
|
# API 连接配置
|
||||||
# "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.1.143:44389"),#实机
|
# "api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.1.143:44389"),#实机
|
||||||
"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.7.149:44388"),# 仿真机
|
"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"),# 仿真机
|
||||||
"api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"),
|
"api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"),
|
||||||
"timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")),
|
"timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")),
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ API_CONFIG = {
|
|||||||
"report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"),
|
"report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"),
|
||||||
|
|
||||||
# HTTP 服务配置
|
# HTTP 服务配置
|
||||||
"HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.2.140"), # HTTP服务监听地址,监听计算机飞连ip地址
|
"HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.11.2"), # HTTP服务监听地址,监听计算机飞连ip地址
|
||||||
"HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")),
|
"HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")),
|
||||||
"debug_mode": False,# 调试模式
|
"debug_mode": False,# 调试模式
|
||||||
}
|
}
|
||||||
@@ -237,7 +237,7 @@ MATERIAL_TYPE_MAPPINGS = {
|
|||||||
"100ml液体": ("YB_100ml_yeti", "d37166b3-ecaa-481e-bd84-3032b795ba07"),
|
"100ml液体": ("YB_100ml_yeti", "d37166b3-ecaa-481e-bd84-3032b795ba07"),
|
||||||
"液": ("YB_ye", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"),
|
"液": ("YB_ye", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"),
|
||||||
"高粘液": ("YB_gaonianye", "abe8df30-563d-43d2-85e0-cabec59ddc16"),
|
"高粘液": ("YB_gaonianye", "abe8df30-563d-43d2-85e0-cabec59ddc16"),
|
||||||
"加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
"加样头(大)": ("YB_jia_yang_tou_da_Carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||||
# "加样头(大)板": ("YB_jia_yang_tou_da", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"),
|
# "加样头(大)板": ("YB_jia_yang_tou_da", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"),
|
||||||
"5ml分液瓶板": ("YB_5ml_fenyepingban", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"),
|
"5ml分液瓶板": ("YB_5ml_fenyepingban", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"),
|
||||||
"5ml分液瓶": ("YB_5ml_fenyeping", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"),
|
"5ml分液瓶": ("YB_5ml_fenyeping", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"),
|
||||||
|
|||||||
@@ -18,67 +18,11 @@ from pylabrobot.resources.tip_rack import TipRack, TipSpot
|
|||||||
from pylabrobot.resources.trash import Trash
|
from pylabrobot.resources.trash import Trash
|
||||||
from pylabrobot.resources.utils import create_ordered_items_2d
|
from pylabrobot.resources.utils import create_ordered_items_2d
|
||||||
|
|
||||||
|
from unilabos.resources.battery.magazine import MagazineHolder_4_Cathode, MagazineHolder_6_Cathode, MagazineHolder_6_Anode, MagazineHolder_6_Battery
|
||||||
|
from unilabos.resources.battery.bottle_carriers import YIHUA_Electrolyte_12VialCarrier
|
||||||
|
from unilabos.resources.battery.electrode_sheet import ElectrodeSheet
|
||||||
|
|
||||||
class ElectrodeSheetState(TypedDict):
|
|
||||||
diameter: float # 直径 (mm)
|
|
||||||
thickness: float # 厚度 (mm)
|
|
||||||
mass: float # 质量 (g)
|
|
||||||
material_type: str # 材料类型(正极、负极、隔膜、弹片、垫片、铝箔等)
|
|
||||||
height: float
|
|
||||||
electrolyte_name: str
|
|
||||||
data_electrolyte_code: str
|
|
||||||
open_circuit_voltage: float
|
|
||||||
assembly_pressure: float
|
|
||||||
electrolyte_volume: float
|
|
||||||
|
|
||||||
info: Optional[str] # 附加信息
|
|
||||||
|
|
||||||
class ElectrodeSheet(Resource):
|
|
||||||
"""极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str = "极片",
|
|
||||||
size_x=10,
|
|
||||||
size_y=10,
|
|
||||||
size_z=10,
|
|
||||||
category: str = "electrode_sheet",
|
|
||||||
model: Optional[str] = None,
|
|
||||||
):
|
|
||||||
"""初始化极片
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: 极片名称
|
|
||||||
category: 类别
|
|
||||||
model: 型号
|
|
||||||
"""
|
|
||||||
super().__init__(
|
|
||||||
name=name,
|
|
||||||
size_x=size_x,
|
|
||||||
size_y=size_y,
|
|
||||||
size_z=size_z,
|
|
||||||
category=category,
|
|
||||||
model=model,
|
|
||||||
)
|
|
||||||
self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState(
|
|
||||||
diameter=14,
|
|
||||||
thickness=0.1,
|
|
||||||
mass=0.5,
|
|
||||||
material_type="copper",
|
|
||||||
info=None
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: 这个还要不要?给self._unilabos_state赋值的?
|
|
||||||
def load_state(self, state: Dict[str, Any]) -> None:
|
|
||||||
"""格式不变"""
|
|
||||||
super().load_state(state)
|
|
||||||
self._unilabos_state = state
|
|
||||||
#序列化
|
|
||||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
|
||||||
"""格式不变"""
|
|
||||||
data = super().serialize_state()
|
|
||||||
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
|
||||||
return data
|
|
||||||
|
|
||||||
# TODO: 这个应该只能放一个极片
|
# TODO: 这个应该只能放一个极片
|
||||||
class MaterialHoleState(TypedDict):
|
class MaterialHoleState(TypedDict):
|
||||||
@@ -165,7 +109,6 @@ class MaterialHole(Resource):
|
|||||||
return self.children[index]
|
return self.children[index]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class MaterialPlateState(TypedDict):
|
class MaterialPlateState(TypedDict):
|
||||||
hole_spacing_x: float
|
hole_spacing_x: float
|
||||||
hole_spacing_y: float
|
hole_spacing_y: float
|
||||||
@@ -327,132 +270,6 @@ class PlateSlot(ResourceStack):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ClipMagazineHole(Container):
|
|
||||||
"""子弹夹洞位类"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
diameter: float,
|
|
||||||
depth: float,
|
|
||||||
max_sheets: int = 100,
|
|
||||||
category: str = "clip_magazine_hole",
|
|
||||||
):
|
|
||||||
"""初始化子弹夹洞位
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: 洞位名称
|
|
||||||
diameter: 洞直径 (mm)
|
|
||||||
depth: 洞深度 (mm)
|
|
||||||
max_sheets: 最大极片数量
|
|
||||||
category: 类别
|
|
||||||
"""
|
|
||||||
super().__init__(
|
|
||||||
name=name,
|
|
||||||
size_x=diameter,
|
|
||||||
size_y=diameter,
|
|
||||||
size_z=depth,
|
|
||||||
category=category,
|
|
||||||
)
|
|
||||||
self.diameter = diameter
|
|
||||||
self.depth = depth
|
|
||||||
self.max_sheets = max_sheets
|
|
||||||
self._sheets: List[ElectrodeSheet] = []
|
|
||||||
|
|
||||||
def can_add_sheet(self, sheet: ElectrodeSheet) -> bool:
|
|
||||||
"""检查是否可以添加极片"""
|
|
||||||
return (len(self._sheets) < self.max_sheets and
|
|
||||||
sheet.diameter <= self.diameter)
|
|
||||||
|
|
||||||
def add_sheet(self, sheet: ElectrodeSheet) -> None:
|
|
||||||
"""添加极片"""
|
|
||||||
if not self.can_add_sheet(sheet):
|
|
||||||
raise ValueError(f"无法向洞位 {self.name} 添加极片")
|
|
||||||
self._sheets.append(sheet)
|
|
||||||
|
|
||||||
def take_sheet(self) -> ElectrodeSheet:
|
|
||||||
"""取出极片"""
|
|
||||||
if len(self._sheets) == 0:
|
|
||||||
raise ValueError(f"洞位 {self.name} 没有极片")
|
|
||||||
return self._sheets.pop()
|
|
||||||
|
|
||||||
def get_sheet_count(self) -> int:
|
|
||||||
"""获取极片数量"""
|
|
||||||
return len(self._sheets)
|
|
||||||
|
|
||||||
def serialize_state(self) -> Dict[str, Any]:
|
|
||||||
return {
|
|
||||||
"sheet_count": len(self._sheets),
|
|
||||||
"sheets": [sheet.serialize() for sheet in self._sheets],
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO: 这个要改
|
|
||||||
class ClipMagazine(ItemizedResource[ClipMagazineHole]):
|
|
||||||
"""子弹夹类 - 有6个洞位,每个洞位放多个极片"""
|
|
||||||
children: List[ClipMagazineHole]
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
size_x: float,
|
|
||||||
size_y: float,
|
|
||||||
size_z: float,
|
|
||||||
hole_diameter: float = 14.0,
|
|
||||||
hole_depth: float = 10.0,
|
|
||||||
hole_spacing: float = 25.0,
|
|
||||||
max_sheets_per_hole: int = 100,
|
|
||||||
category: str = "clip_magazine",
|
|
||||||
model: Optional[str] = None,
|
|
||||||
):
|
|
||||||
"""初始化子弹夹
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: 子弹夹名称
|
|
||||||
size_x: 长度 (mm)
|
|
||||||
size_y: 宽度 (mm)
|
|
||||||
size_z: 高度 (mm)
|
|
||||||
hole_diameter: 洞直径 (mm)
|
|
||||||
hole_depth: 洞深度 (mm)
|
|
||||||
hole_spacing: 洞位间距 (mm)
|
|
||||||
max_sheets_per_hole: 每个洞位最大极片数量
|
|
||||||
category: 类别
|
|
||||||
model: 型号
|
|
||||||
"""
|
|
||||||
# 创建6个洞位,排成2x3布局
|
|
||||||
holes = create_ordered_items_2d(
|
|
||||||
klass=ClipMagazineHole,
|
|
||||||
num_items_x=3,
|
|
||||||
num_items_y=2,
|
|
||||||
dx=(size_x - 2 * hole_spacing) / 2, # 居中
|
|
||||||
dy=(size_y - hole_spacing) / 2, # 居中
|
|
||||||
dz=size_z - 0,
|
|
||||||
item_dx=hole_spacing,
|
|
||||||
item_dy=hole_spacing,
|
|
||||||
diameter=hole_diameter,
|
|
||||||
depth=hole_depth,
|
|
||||||
)
|
|
||||||
|
|
||||||
super().__init__(
|
|
||||||
name=name,
|
|
||||||
size_x=size_x,
|
|
||||||
size_y=size_y,
|
|
||||||
size_z=size_z,
|
|
||||||
ordered_items=holes,
|
|
||||||
category=category,
|
|
||||||
model=model,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 保存洞位的直径和深度
|
|
||||||
self.hole_diameter = hole_diameter
|
|
||||||
self.hole_depth = hole_depth
|
|
||||||
self.max_sheets_per_hole = max_sheets_per_hole
|
|
||||||
|
|
||||||
def serialize(self) -> dict:
|
|
||||||
return {
|
|
||||||
**super().serialize(),
|
|
||||||
"hole_diameter": self.hole_diameter,
|
|
||||||
"hole_depth": self.hole_depth,
|
|
||||||
"max_sheets_per_hole": self.max_sheets_per_hole,
|
|
||||||
}
|
|
||||||
#是一种类型注解,不用self
|
#是一种类型注解,不用self
|
||||||
class BatteryState(TypedDict):
|
class BatteryState(TypedDict):
|
||||||
"""电池状态字典"""
|
"""电池状态字典"""
|
||||||
@@ -595,42 +412,19 @@ class BatteryPressSlot(Resource):
|
|||||||
def get_battery_info(self, index: int) -> Battery:
|
def get_battery_info(self, index: int) -> Battery:
|
||||||
return self.children[0]
|
return self.children[0]
|
||||||
|
|
||||||
# TODO:这个移液枪架子看一下从哪继承
|
|
||||||
class TipBox64State(TypedDict):
|
|
||||||
"""电池状态字典"""
|
|
||||||
tip_diameter: float = 5.0
|
|
||||||
tip_length: float = 50.0
|
|
||||||
with_tips: bool = True
|
|
||||||
|
|
||||||
class TipBox64(TipRack):
|
def TipBox64(
|
||||||
"""64孔枪头盒类"""
|
|
||||||
|
|
||||||
children: List[TipSpot] = []
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
name: str,
|
||||||
size_x: float = 127.8,
|
size_x: float = 127.8,
|
||||||
size_y: float = 85.5,
|
size_y: float = 85.5,
|
||||||
size_z: float = 60.0,
|
size_z: float = 60.0,
|
||||||
category: str = "tip_box_64",
|
category: str = "tip_rack",
|
||||||
model: Optional[str] = None,
|
model: Optional[str] = None,
|
||||||
):
|
):
|
||||||
"""初始化64孔枪头盒
|
"""64孔枪头盒类"""
|
||||||
|
|
||||||
Args:
|
|
||||||
name: 枪头盒名称
|
|
||||||
size_x: 长度 (mm)
|
|
||||||
size_y: 宽度 (mm)
|
|
||||||
size_z: 高度 (mm)
|
|
||||||
tip_diameter: 枪头直径 (mm)
|
|
||||||
tip_length: 枪头长度 (mm)
|
|
||||||
category: 类别
|
|
||||||
model: 型号
|
|
||||||
with_tips: 是否带枪头
|
|
||||||
"""
|
|
||||||
from pylabrobot.resources.tip import Tip
|
from pylabrobot.resources.tip import Tip
|
||||||
|
|
||||||
# 创建8x8=64个枪头位
|
# 创建12x8=96个枪头位
|
||||||
def make_tip():
|
def make_tip():
|
||||||
return Tip(
|
return Tip(
|
||||||
has_filter=False,
|
has_filter=False,
|
||||||
@@ -641,7 +435,7 @@ class TipBox64(TipRack):
|
|||||||
|
|
||||||
tip_spots = create_ordered_items_2d(
|
tip_spots = create_ordered_items_2d(
|
||||||
klass=TipSpot,
|
klass=TipSpot,
|
||||||
num_items_x=8,
|
num_items_x=12,
|
||||||
num_items_y=8,
|
num_items_y=8,
|
||||||
dx=8.0,
|
dx=8.0,
|
||||||
dy=8.0,
|
dy=8.0,
|
||||||
@@ -653,18 +447,21 @@ class TipBox64(TipRack):
|
|||||||
size_z=0.0,
|
size_z=0.0,
|
||||||
make_tip=make_tip,
|
make_tip=make_tip,
|
||||||
)
|
)
|
||||||
self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate()
|
idx_available = list(range(0, 32)) + list(range(64, 96))
|
||||||
super().__init__(
|
tip_spots_available = {k: v for i, (k, v) in enumerate(tip_spots.items()) if i in idx_available}
|
||||||
|
tip_rack = TipRack(
|
||||||
name=name,
|
name=name,
|
||||||
size_x=size_x,
|
size_x=size_x,
|
||||||
size_y=size_y,
|
size_y=size_y,
|
||||||
size_z=size_z,
|
size_z=size_z,
|
||||||
|
# ordered_items=tip_spots_available,
|
||||||
ordered_items=tip_spots,
|
ordered_items=tip_spots,
|
||||||
category=category,
|
category=category,
|
||||||
model=model,
|
model=model,
|
||||||
with_tips=True,
|
with_tips=False,
|
||||||
)
|
)
|
||||||
|
tip_rack.set_tip_state([True]*32 + [False]*32 + [True]*32) # 前32和后32个有枪头,中间32个无枪头
|
||||||
|
return tip_rack
|
||||||
|
|
||||||
|
|
||||||
class WasteTipBoxstate(TypedDict):
|
class WasteTipBoxstate(TypedDict):
|
||||||
@@ -682,8 +479,12 @@ class WasteTipBox(Trash):
|
|||||||
size_x: float = 127.8,
|
size_x: float = 127.8,
|
||||||
size_y: float = 85.5,
|
size_y: float = 85.5,
|
||||||
size_z: float = 60.0,
|
size_z: float = 60.0,
|
||||||
category: str = "waste_tip_box",
|
material_z_thickness=0,
|
||||||
model: Optional[str] = None,
|
max_volume=float("inf"),
|
||||||
|
category="trash",
|
||||||
|
model=None,
|
||||||
|
compute_volume_from_height=None,
|
||||||
|
compute_height_from_volume=None,
|
||||||
):
|
):
|
||||||
"""初始化废枪头盒
|
"""初始化废枪头盒
|
||||||
|
|
||||||
@@ -733,263 +534,16 @@ class WasteTipBox(Trash):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class BottleRackState(TypedDict):
|
|
||||||
""" bottle_diameter: 瓶子直径 (mm)
|
|
||||||
bottle_height: 瓶子高度 (mm)
|
|
||||||
position_spacing: 位置间距 (mm)"""
|
|
||||||
bottle_diameter: float
|
|
||||||
bottle_height: float
|
|
||||||
name_to_index: dict
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BottleRack(Resource):
|
|
||||||
"""瓶架类 - 12个待配位置+12个已配位置"""
|
|
||||||
children: List[Resource] = []
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
size_x: float,
|
|
||||||
size_y: float,
|
|
||||||
size_z: float,
|
|
||||||
category: str = "bottle_rack",
|
|
||||||
model: Optional[str] = None,
|
|
||||||
num_items_x: int = 3,
|
|
||||||
num_items_y: int = 4,
|
|
||||||
position_spacing: float = 35.0,
|
|
||||||
orientation: str = "horizontal",
|
|
||||||
padding_x: float = 20.0,
|
|
||||||
padding_y: float = 20.0,
|
|
||||||
):
|
|
||||||
"""初始化瓶架
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: 瓶架名称
|
|
||||||
size_x: 长度 (mm)
|
|
||||||
size_y: 宽度 (mm)
|
|
||||||
size_z: 高度 (mm)
|
|
||||||
category: 类别
|
|
||||||
model: 型号
|
|
||||||
"""
|
|
||||||
super().__init__(
|
|
||||||
name=name,
|
|
||||||
size_x=size_x,
|
|
||||||
size_y=size_y,
|
|
||||||
size_z=size_z,
|
|
||||||
category=category,
|
|
||||||
model=model,
|
|
||||||
)
|
|
||||||
# 初始化状态
|
|
||||||
self._unilabos_state: BottleRackState = BottleRackState(
|
|
||||||
bottle_diameter=30.0,
|
|
||||||
bottle_height=100.0,
|
|
||||||
position_spacing=position_spacing,
|
|
||||||
name_to_index={},
|
|
||||||
)
|
|
||||||
# 基于网格生成瓶位坐标映射(居中摆放)
|
|
||||||
# 使用内边距,避免点跑到容器外(前端渲染不按mm等比缩放时更稳妥)
|
|
||||||
origin_x = padding_x
|
|
||||||
origin_y = padding_y
|
|
||||||
self.index_to_pos = {}
|
|
||||||
for j in range(num_items_y):
|
|
||||||
for i in range(num_items_x):
|
|
||||||
idx = j * num_items_x + i
|
|
||||||
if orientation == "vertical":
|
|
||||||
# 纵向:沿 y 方向优先排列
|
|
||||||
self.index_to_pos[idx] = Coordinate(
|
|
||||||
x=origin_x + j * position_spacing,
|
|
||||||
y=origin_y + i * position_spacing,
|
|
||||||
z=0,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# 横向(默认):沿 x 方向优先排列
|
|
||||||
self.index_to_pos[idx] = Coordinate(
|
|
||||||
x=origin_x + i * position_spacing,
|
|
||||||
y=origin_y + j * position_spacing,
|
|
||||||
z=0,
|
|
||||||
)
|
|
||||||
self.name_to_index = {}
|
|
||||||
self.name_to_pos = {}
|
|
||||||
self.num_items_x = num_items_x
|
|
||||||
self.num_items_y = num_items_y
|
|
||||||
self.orientation = orientation
|
|
||||||
self.padding_x = padding_x
|
|
||||||
self.padding_y = padding_y
|
|
||||||
|
|
||||||
def load_state(self, state: Dict[str, Any]) -> None:
|
|
||||||
"""格式不变"""
|
|
||||||
super().load_state(state)
|
|
||||||
self._unilabos_state = state
|
|
||||||
|
|
||||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
|
||||||
"""格式不变"""
|
|
||||||
data = super().serialize_state()
|
|
||||||
data.update(
|
|
||||||
self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
|
||||||
return data
|
|
||||||
|
|
||||||
# TODO: 这里有些问题要重新写一下
|
|
||||||
def assign_child_resource_old(self, resource: Resource, location=Coordinate.zero(), reassign=True):
|
|
||||||
capacity = self.num_items_x * self.num_items_y
|
|
||||||
assert len(self.children) < capacity, "瓶架已满,无法添加更多瓶子"
|
|
||||||
index = len(self.children)
|
|
||||||
location = self.index_to_pos.get(index, Coordinate.zero())
|
|
||||||
self.name_to_pos[resource.name] = location
|
|
||||||
self.name_to_index[resource.name] = index
|
|
||||||
return super().assign_child_resource(resource, location, reassign)
|
|
||||||
|
|
||||||
def assign_child_resource(self, resource: Resource, index: int):
|
|
||||||
capacity = self.num_items_x * self.num_items_y
|
|
||||||
assert 0 <= index < capacity, "无效的瓶子索引"
|
|
||||||
self.name_to_index[resource.name] = index
|
|
||||||
location = self.index_to_pos[index]
|
|
||||||
return super().assign_child_resource(resource, location)
|
|
||||||
|
|
||||||
def unassign_child_resource(self, resource: Bottle):
|
|
||||||
super().unassign_child_resource(resource)
|
|
||||||
self.index_to_pos.pop(self.name_to_index.pop(resource.name, None), None)
|
|
||||||
|
|
||||||
def serialize(self) -> dict:
|
|
||||||
return {
|
|
||||||
**super().serialize(),
|
|
||||||
"num_items_x": self.num_items_x,
|
|
||||||
"num_items_y": self.num_items_y,
|
|
||||||
"position_spacing": self._unilabos_state.get("position_spacing", 35.0),
|
|
||||||
"orientation": self.orientation,
|
|
||||||
"padding_x": self.padding_x,
|
|
||||||
"padding_y": self.padding_y,
|
|
||||||
}
|
|
||||||
|
|
||||||
class BottleState(TypedDict):
|
|
||||||
diameter: float
|
|
||||||
height: float
|
|
||||||
electrolyte_name: str
|
|
||||||
electrolyte_volume: float
|
|
||||||
max_volume: float
|
|
||||||
|
|
||||||
class Bottle(Resource):
|
|
||||||
"""瓶子类 - 容纳电解液"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
category: str = "bottle",
|
|
||||||
):
|
|
||||||
"""初始化瓶子
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: 瓶子名称
|
|
||||||
diameter: 直径 (mm)
|
|
||||||
height: 高度 (mm)
|
|
||||||
max_volume: 最大体积 (μL)
|
|
||||||
barcode: 二维码
|
|
||||||
category: 类别
|
|
||||||
model: 型号
|
|
||||||
"""
|
|
||||||
super().__init__(
|
|
||||||
name=name,
|
|
||||||
size_x=1,
|
|
||||||
size_y=1,
|
|
||||||
size_z=1,
|
|
||||||
category=category,
|
|
||||||
)
|
|
||||||
self._unilabos_state: BottleState = BottleState()
|
|
||||||
|
|
||||||
def aspirate_electrolyte(self, volume: float) -> bool:
|
|
||||||
current_volume = self._unilabos_state["electrolyte_volume"]
|
|
||||||
assert current_volume > volume, f"Cannot aspirate {volume}μL, only {current_volume}μL available."
|
|
||||||
self._unilabos_state["electrolyte_volume"] -= volume
|
|
||||||
return True
|
|
||||||
|
|
||||||
def load_state(self, state: Dict[str, Any]) -> None:
|
|
||||||
"""格式不变"""
|
|
||||||
super().load_state(state)
|
|
||||||
self._unilabos_state = state
|
|
||||||
|
|
||||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
|
||||||
"""格式不变"""
|
|
||||||
data = super().serialize_state()
|
|
||||||
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
|
||||||
return data
|
|
||||||
|
|
||||||
class ClipMagazine_four(ItemizedResource[ClipMagazineHole]):
|
|
||||||
"""子弹夹类 - 有4个洞位,每个洞位放多个极片"""
|
|
||||||
children: List[ClipMagazineHole]
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
size_x: float,
|
|
||||||
size_y: float,
|
|
||||||
size_z: float,
|
|
||||||
hole_diameter: float = 14.0,
|
|
||||||
hole_depth: float = 10.0,
|
|
||||||
hole_spacing: float = 25.0,
|
|
||||||
max_sheets_per_hole: int = 100,
|
|
||||||
category: str = "clip_magazine_four",
|
|
||||||
model: Optional[str] = None,
|
|
||||||
):
|
|
||||||
"""初始化子弹夹
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: 子弹夹名称
|
|
||||||
size_x: 长度 (mm)
|
|
||||||
size_y: 宽度 (mm)
|
|
||||||
size_z: 高度 (mm)
|
|
||||||
hole_diameter: 洞直径 (mm)
|
|
||||||
hole_depth: 洞深度 (mm)
|
|
||||||
hole_spacing: 洞位间距 (mm)
|
|
||||||
max_sheets_per_hole: 每个洞位最大极片数量
|
|
||||||
category: 类别
|
|
||||||
model: 型号
|
|
||||||
"""
|
|
||||||
# 创建4个洞位,排成2x2布局
|
|
||||||
holes = create_ordered_items_2d(
|
|
||||||
klass=ClipMagazineHole,
|
|
||||||
num_items_x=2,
|
|
||||||
num_items_y=2,
|
|
||||||
dx=(size_x - 2 * hole_spacing) / 2, # 居中
|
|
||||||
dy=(size_y - hole_spacing) / 2, # 居中
|
|
||||||
dz=size_z - 0,
|
|
||||||
item_dx=hole_spacing,
|
|
||||||
item_dy=hole_spacing,
|
|
||||||
diameter=hole_diameter,
|
|
||||||
depth=hole_depth,
|
|
||||||
)
|
|
||||||
|
|
||||||
super().__init__(
|
|
||||||
name=name,
|
|
||||||
size_x=size_x,
|
|
||||||
size_y=size_y,
|
|
||||||
size_z=size_z,
|
|
||||||
ordered_items=holes,
|
|
||||||
category=category,
|
|
||||||
model=model,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 保存洞位的直径和深度
|
|
||||||
self.hole_diameter = hole_diameter
|
|
||||||
self.hole_depth = hole_depth
|
|
||||||
self.max_sheets_per_hole = max_sheets_per_hole
|
|
||||||
|
|
||||||
def serialize(self) -> dict:
|
|
||||||
return {
|
|
||||||
**super().serialize(),
|
|
||||||
"hole_diameter": self.hole_diameter,
|
|
||||||
"hole_depth": self.hole_depth,
|
|
||||||
"max_sheets_per_hole": self.max_sheets_per_hole,
|
|
||||||
}
|
|
||||||
|
|
||||||
class CoincellDeck(Deck):
|
class CoincellDeck(Deck):
|
||||||
"""纽扣电池组装工作站台面类"""
|
"""纽扣电池组装工作站台面类"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name: str = "coin_cell_deck",
|
name: str = "coin_cell_deck",
|
||||||
size_x: float = 3650.0, # 1m
|
size_x: float = 1450.0, # 1m
|
||||||
size_y: float = 1550.0, # 1m
|
size_y: float = 1450.0, # 1m
|
||||||
size_z: float = 2100.0, # 0.9m
|
size_z: float = 100.0, # 0.9m
|
||||||
origin: Coordinate = Coordinate(-4000, 2000, 0),
|
origin: Coordinate = Coordinate(-2200, 0, 0),
|
||||||
category: str = "coin_cell_deck",
|
category: str = "coin_cell_deck",
|
||||||
setup: bool = False, # 是否自动执行 setup
|
setup: bool = False, # 是否自动执行 setup
|
||||||
):
|
):
|
||||||
@@ -1006,11 +560,10 @@ class CoincellDeck(Deck):
|
|||||||
"""
|
"""
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=name,
|
name=name,
|
||||||
size_x=size_x,
|
size_x=1450.0,
|
||||||
size_y=size_y,
|
size_y=1450.0,
|
||||||
size_z=size_z,
|
size_z=100.0,
|
||||||
origin=origin,
|
origin=origin,
|
||||||
category=category,
|
|
||||||
)
|
)
|
||||||
if setup:
|
if setup:
|
||||||
self.setup()
|
self.setup()
|
||||||
@@ -1018,146 +571,67 @@ class CoincellDeck(Deck):
|
|||||||
def setup(self) -> None:
|
def setup(self) -> None:
|
||||||
"""设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置"""
|
"""设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置"""
|
||||||
# ====================================== 子弹夹 ============================================
|
# ====================================== 子弹夹 ============================================
|
||||||
zip_dan_jia = ClipMagazine_four("zi_dan_jia", 80, 80, 10)
|
|
||||||
self.assign_child_resource(zip_dan_jia, Coordinate(x=1400, y=50, z=0))
|
|
||||||
zip_dan_jia2 = ClipMagazine_four("zi_dan_jia2", 80, 80, 10)
|
|
||||||
self.assign_child_resource(zip_dan_jia2, Coordinate(x=1600, y=200, z=0))
|
|
||||||
zip_dan_jia3 = ClipMagazine("zi_dan_jia3", 80, 80, 10)
|
|
||||||
self.assign_child_resource(zip_dan_jia3, Coordinate(x=1500, y=200, z=0))
|
|
||||||
zip_dan_jia4 = ClipMagazine("zi_dan_jia4", 80, 80, 10)
|
|
||||||
self.assign_child_resource(zip_dan_jia4, Coordinate(x=1500, y=300, z=0))
|
|
||||||
zip_dan_jia5 = ClipMagazine("zi_dan_jia5", 80, 80, 10)
|
|
||||||
self.assign_child_resource(zip_dan_jia5, Coordinate(x=1600, y=300, z=0))
|
|
||||||
zip_dan_jia6 = ClipMagazine("zi_dan_jia6", 80, 80, 10)
|
|
||||||
self.assign_child_resource(zip_dan_jia6, Coordinate(x=1530, y=500, z=0))
|
|
||||||
zip_dan_jia7 = ClipMagazine("zi_dan_jia7", 80, 80, 10)
|
|
||||||
self.assign_child_resource(zip_dan_jia7, Coordinate(x=1180, y=400, z=0))
|
|
||||||
zip_dan_jia8 = ClipMagazine("zi_dan_jia8", 80, 80, 10)
|
|
||||||
self.assign_child_resource(zip_dan_jia8, Coordinate(x=1280, y=400, z=0))
|
|
||||||
|
|
||||||
# 为子弹夹添加极片
|
# 正极片(4个洞位,2x2布局)
|
||||||
for i in range(4):
|
zhengji_zip = MagazineHolder_4_Cathode("正极&铝箔弹夹")
|
||||||
jipian = ElectrodeSheet(name=f"zi_dan_jia_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
self.assign_child_resource(zhengji_zip, Coordinate(x=402.0, y=830.0, z=0))
|
||||||
zip_dan_jia2.children[i].assign_child_resource(jipian, location=None)
|
|
||||||
for i in range(4):
|
# 正极壳、平垫片(6个洞位,2x2+2布局)
|
||||||
jipian2 = ElectrodeSheet(name=f"zi_dan_jia2_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
zhengjike_zip = MagazineHolder_6_Cathode("正极壳&平垫片弹夹")
|
||||||
zip_dan_jia.children[i].assign_child_resource(jipian2, location=None)
|
self.assign_child_resource(zhengjike_zip, Coordinate(x=566.0, y=272.0, z=0))
|
||||||
for i in range(6):
|
|
||||||
jipian3 = ElectrodeSheet(name=f"zi_dan_jia3_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
# 负极壳、弹垫片(6个洞位,2x2+2布局)
|
||||||
zip_dan_jia3.children[i].assign_child_resource(jipian3, location=None)
|
fujike_zip = MagazineHolder_6_Anode("负极壳&弹垫片弹夹")
|
||||||
for i in range(6):
|
self.assign_child_resource(fujike_zip, Coordinate(x=474.0, y=276.0, z=0))
|
||||||
jipian4 = ElectrodeSheet(name=f"zi_dan_jia4_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
|
||||||
zip_dan_jia4.children[i].assign_child_resource(jipian4, location=None)
|
# 成品弹夹(6个洞位,3x2布局)
|
||||||
for i in range(6):
|
chengpindanjia_zip = MagazineHolder_6_Battery("成品弹夹")
|
||||||
jipian5 = ElectrodeSheet(name=f"zi_dan_jia5_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
self.assign_child_resource(chengpindanjia_zip, Coordinate(x=260.0, y=156.0, z=0))
|
||||||
zip_dan_jia5.children[i].assign_child_resource(jipian5, location=None)
|
|
||||||
for i in range(6):
|
|
||||||
jipian6 = ElectrodeSheet(name=f"zi_dan_jia6_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
|
||||||
zip_dan_jia6.children[i].assign_child_resource(jipian6, location=None)
|
|
||||||
for i in range(6):
|
|
||||||
jipian7 = ElectrodeSheet(name=f"zi_dan_jia7_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
|
||||||
zip_dan_jia7.children[i].assign_child_resource(jipian7, location=None)
|
|
||||||
for i in range(6):
|
|
||||||
jipian8 = ElectrodeSheet(name=f"zi_dan_jia8_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
|
||||||
zip_dan_jia8.children[i].assign_child_resource(jipian8, location=None)
|
|
||||||
|
|
||||||
# ====================================== 物料板 ============================================
|
# ====================================== 物料板 ============================================
|
||||||
# 创建6个4*4的物料板
|
# 创建物料板(料盘carrier)- 4x4布局
|
||||||
liaopan1 = MaterialPlate(name="liaopan1", size_x=120, size_y=100, size_z=10.0, fill=True)
|
# 负极料盘
|
||||||
self.assign_child_resource(liaopan1, Coordinate(x=1010, y=50, z=0))
|
fujiliaopan = MaterialPlate(name="负极料盘", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||||
for i in range(16):
|
self.assign_child_resource(fujiliaopan, Coordinate(x=708.0, y=794.0, z=0))
|
||||||
jipian_1 = ElectrodeSheet(name=f"{liaopan1.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
# for i in range(16):
|
||||||
liaopan1.children[i].assign_child_resource(jipian_1, location=None)
|
# fujipian = ElectrodeSheet(name=f"{fujiliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
# fujiliaopan.children[i].assign_child_resource(fujipian, location=None)
|
||||||
|
|
||||||
liaopan2 = MaterialPlate(name="liaopan2", size_x=120, size_y=100, size_z=10.0, fill=True)
|
# 隔膜料盘
|
||||||
self.assign_child_resource(liaopan2, Coordinate(x=1130, y=50, z=0))
|
gemoliaopan = MaterialPlate(name="隔膜料盘", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||||
|
self.assign_child_resource(gemoliaopan, Coordinate(x=718.0, y=918.0, z=0))
|
||||||
liaopan3 = MaterialPlate(name="liaopan3", size_x=120, size_y=100, size_z=10.0, fill=True)
|
# for i in range(16):
|
||||||
self.assign_child_resource(liaopan3, Coordinate(x=1250, y=50, z=0))
|
# gemopian = ElectrodeSheet(name=f"{gemoliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
# gemoliaopan.children[i].assign_child_resource(gemopian, location=None)
|
||||||
liaopan4 = MaterialPlate(name="liaopan4", size_x=120, size_y=100, size_z=10.0, fill=True)
|
|
||||||
self.assign_child_resource(liaopan4, Coordinate(x=1010, y=150, z=0))
|
|
||||||
for i in range(16):
|
|
||||||
jipian_4 = ElectrodeSheet(name=f"{liaopan4.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
|
||||||
liaopan4.children[i].assign_child_resource(jipian_4, location=None)
|
|
||||||
|
|
||||||
liaopan5 = MaterialPlate(name="liaopan5", size_x=120, size_y=100, size_z=10.0, fill=True)
|
|
||||||
self.assign_child_resource(liaopan5, Coordinate(x=1130, y=150, z=0))
|
|
||||||
|
|
||||||
liaopan6 = MaterialPlate(name="liaopan6", size_x=120, size_y=100, size_z=10.0, fill=True)
|
|
||||||
self.assign_child_resource(liaopan6, Coordinate(x=1250, y=150, z=0))
|
|
||||||
|
|
||||||
# ====================================== 瓶架、移液枪 ============================================
|
# ====================================== 瓶架、移液枪 ============================================
|
||||||
# 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒
|
# 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒
|
||||||
bottle_rack_3x4 = BottleRack(
|
# 奔耀上料5ml分液瓶小板 - 由奔曜跨站转运而来,不单独写,但是这里应该有一个堆栈用于摆放分液瓶小板
|
||||||
name="bottle_rack_3x4",
|
|
||||||
size_x=210.0,
|
|
||||||
size_y=140.0,
|
|
||||||
size_z=100.0,
|
|
||||||
num_items_x=3,
|
|
||||||
num_items_y=4,
|
|
||||||
position_spacing=35.0,
|
|
||||||
orientation="vertical",
|
|
||||||
)
|
|
||||||
self.assign_child_resource(bottle_rack_3x4, Coordinate(x=100, y=200, z=0))
|
|
||||||
|
|
||||||
bottle_rack_6x2 = BottleRack(
|
# bottle_rack_3x4 = BottleRack(
|
||||||
name="bottle_rack_6x2",
|
# name="bottle_rack_3x4",
|
||||||
size_x=120.0,
|
# size_x=210.0,
|
||||||
size_y=250.0,
|
# size_y=140.0,
|
||||||
size_z=100.0,
|
# size_z=100.0,
|
||||||
num_items_x=6,
|
# num_items_x=2,
|
||||||
num_items_y=2,
|
# num_items_y=4,
|
||||||
position_spacing=35.0,
|
# position_spacing=35.0,
|
||||||
orientation="vertical",
|
# orientation="vertical",
|
||||||
)
|
# )
|
||||||
self.assign_child_resource(bottle_rack_6x2, Coordinate(x=300, y=300, z=0))
|
# self.assign_child_resource(bottle_rack_3x4, Coordinate(x=1542.0, y=717.0, z=0))
|
||||||
|
|
||||||
bottle_rack_6x2_2 = BottleRack(
|
# 电解液缓存位 - 6x2布局
|
||||||
name="bottle_rack_6x2_2",
|
bottle_rack_6x2 = YIHUA_Electrolyte_12VialCarrier(name="bottle_rack_6x2")
|
||||||
size_x=120.0,
|
self.assign_child_resource(bottle_rack_6x2, Coordinate(x=1050.0, y=358.0, z=0))
|
||||||
size_y=250.0,
|
# 电解液回收位6x2
|
||||||
size_z=100.0,
|
bottle_rack_6x2_2 = YIHUA_Electrolyte_12VialCarrier(name="bottle_rack_6x2_2")
|
||||||
num_items_x=6,
|
self.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=914.0, y=358.0, z=0))
|
||||||
num_items_y=2,
|
|
||||||
position_spacing=35.0,
|
|
||||||
orientation="vertical",
|
|
||||||
)
|
|
||||||
self.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=430, y=300, z=0))
|
|
||||||
|
|
||||||
# 将 ElectrodeSheet 放满 3x4 与 6x2 的所有孔位
|
|
||||||
for idx in range(bottle_rack_3x4.num_items_x * bottle_rack_3x4.num_items_y):
|
|
||||||
sheet = ElectrodeSheet(name=f"sheet_3x4_{idx}", size_x=12, size_y=12, size_z=0.1)
|
|
||||||
bottle_rack_3x4.assign_child_resource(sheet, index=idx)
|
|
||||||
|
|
||||||
for idx in range(bottle_rack_6x2.num_items_x * bottle_rack_6x2.num_items_y):
|
|
||||||
sheet = ElectrodeSheet(name=f"sheet_6x2_{idx}", size_x=12, size_y=12, size_z=0.1)
|
|
||||||
bottle_rack_6x2.assign_child_resource(sheet, index=idx)
|
|
||||||
|
|
||||||
tip_box = TipBox64(name="tip_box_64")
|
tip_box = TipBox64(name="tip_box_64")
|
||||||
self.assign_child_resource(tip_box, Coordinate(x=300, y=100, z=0))
|
self.assign_child_resource(tip_box, Coordinate(x=782.0, y=514.0, z=0))
|
||||||
|
|
||||||
waste_tip_box = WasteTipBox(name="waste_tip_box")
|
waste_tip_box = WasteTipBox(name="waste_tip_box")
|
||||||
self.assign_child_resource(waste_tip_box, Coordinate(x=300, y=200, z=0))
|
self.assign_child_resource(waste_tip_box, Coordinate(x=778.0, y=622.0, z=0))
|
||||||
|
|
||||||
print(self)
|
|
||||||
|
|
||||||
|
|
||||||
def create_coin_cell_deck(name: str = "coin_cell_deck", size_x: float = 1000.0, size_y: float = 1000.0, size_z: float = 900.0) -> CoincellDeck:
|
|
||||||
"""创建并配置标准的纽扣电池组装工作站台面
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name: 台面名称
|
|
||||||
size_x: 长度 (mm)
|
|
||||||
size_y: 宽度 (mm)
|
|
||||||
size_z: 高度 (mm)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
已配置好的 CoincellDeck 对象
|
|
||||||
"""
|
|
||||||
# 创建 CoincellDeck 实例并自动执行 setup 配置
|
|
||||||
deck = CoincellDeck(name=name, size_x=size_x, size_y=size_y, size_z=size_z, setup=True)
|
|
||||||
return deck
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import csv
|
import csv
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
@@ -109,44 +108,22 @@ def _coerce_deck_input(deck: Any) -> Optional[Deck]:
|
|||||||
#构建物料系统
|
#构建物料系统
|
||||||
|
|
||||||
class CoinCellAssemblyWorkstation(WorkstationBase):
|
class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||||
def __init__(
|
def __init__(self,
|
||||||
self,
|
config: dict = None,
|
||||||
deck: Deck=None,
|
deck=None,
|
||||||
address: str = "172.16.28.102",
|
address: str = "172.16.28.102",
|
||||||
port: str = "502",
|
port: str = "502",
|
||||||
debug_mode: bool = False,
|
debug_mode: bool = False,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs):
|
||||||
):
|
|
||||||
if deck is None and "deck" in kwargs:
|
|
||||||
deck = kwargs.pop("deck")
|
|
||||||
else:
|
|
||||||
kwargs.pop("deck", None)
|
|
||||||
|
|
||||||
normalized_deck = _coerce_deck_input(deck)
|
if deck is None and config:
|
||||||
|
deck = config.get('deck')
|
||||||
if deck is None and isinstance(normalized_deck, Deck):
|
if deck is None:
|
||||||
deck = normalized_deck
|
logger.info("没有传入依华deck,检查启动json文件")
|
||||||
|
super().__init__(deck=deck, *args, **kwargs,)
|
||||||
super().__init__(
|
|
||||||
#桌子
|
|
||||||
deck=deck,
|
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
self.debug_mode = debug_mode
|
self.debug_mode = debug_mode
|
||||||
|
|
||||||
# 如果没有传入 deck,则创建标准配置的 deck
|
|
||||||
if self.deck is None:
|
|
||||||
self.deck = CoincellDeck(size_x=1000, size_y=1000, size_z=900, origin=Coordinate(-800, 0, 0),setup=True)
|
|
||||||
else:
|
|
||||||
# 如果传入了 deck 但还没有 setup,可以选择是否 setup
|
|
||||||
if self.deck is not None and len(self.deck.children) == 0:
|
|
||||||
# deck 为空,执行 setup
|
|
||||||
self.deck.setup()
|
|
||||||
# 否则使用传入的 deck(可能已经配置好了)
|
|
||||||
self.deck = self.deck
|
|
||||||
|
|
||||||
""" 连接初始化 """
|
""" 连接初始化 """
|
||||||
modbus_client = TCPClient(addr=address, port=port)
|
modbus_client = TCPClient(addr=address, port=port)
|
||||||
logger.debug(f"创建 Modbus 客户端: {modbus_client}")
|
logger.debug(f"创建 Modbus 客户端: {modbus_client}")
|
||||||
@@ -161,25 +138,19 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
if not modbus_client.client.is_socket_open():
|
if not modbus_client.client.is_socket_open():
|
||||||
raise ValueError('modbus tcp connection failed')
|
raise ValueError('modbus tcp connection failed')
|
||||||
else:
|
|
||||||
print("测试模式,跳过连接")
|
|
||||||
|
|
||||||
""" 工站的配置 """
|
|
||||||
self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_1105.csv'))
|
self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_1105.csv'))
|
||||||
self.client = modbus_client.register_node_list(self.nodes)
|
self.client = modbus_client.register_node_list(self.nodes)
|
||||||
|
else:
|
||||||
|
print("测试模式,跳过连接")
|
||||||
|
self.nodes, self.client = None, None
|
||||||
|
""" 工站的配置 """
|
||||||
|
|
||||||
self.success = False
|
self.success = False
|
||||||
self.allow_data_read = False #允许读取函数运行标志位
|
self.allow_data_read = False #允许读取函数运行标志位
|
||||||
self.csv_export_thread = None
|
self.csv_export_thread = None
|
||||||
self.csv_export_running = False
|
self.csv_export_running = False
|
||||||
self.csv_export_file = None
|
self.csv_export_file = None
|
||||||
self.coin_num_N = 0 #已组装电池数量
|
self.coin_num_N = 0 #已组装电池数量
|
||||||
#创建一个物料台面,包含两个极片板
|
|
||||||
#self._ros_node.update_resource(self.deck)
|
|
||||||
|
|
||||||
#ROS2DeviceNode.run_async_func(self._ros_node.update_resource, True, **{
|
|
||||||
# "resources": [self.deck]
|
|
||||||
#})
|
|
||||||
|
|
||||||
|
|
||||||
def post_init(self, ros_node: ROS2WorkstationNode):
|
def post_init(self, ros_node: ROS2WorkstationNode):
|
||||||
self._ros_node = ros_node
|
self._ros_node = ros_node
|
||||||
@@ -188,6 +159,27 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
"resources": [self.deck]
|
"resources": [self.deck]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def sync_transfer_resources(self) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
供跨工站转运完成后调用,强制将当前台面资源同步到云端/前端。
|
||||||
|
"""
|
||||||
|
if not hasattr(self, "_ros_node") or self._ros_node is None:
|
||||||
|
return {"status": "failed", "error": "ros_node_not_ready"}
|
||||||
|
if self.deck is None:
|
||||||
|
return {"status": "failed", "error": "deck_not_initialized"}
|
||||||
|
try:
|
||||||
|
future = ROS2DeviceNode.run_async_func(
|
||||||
|
self._ros_node.update_resource,
|
||||||
|
True,
|
||||||
|
resources=[self.deck],
|
||||||
|
)
|
||||||
|
if future:
|
||||||
|
future.result()
|
||||||
|
return {"status": "success"}
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"同步转运资源失败: {exc}", exc_info=True)
|
||||||
|
return {"status": "failed", "error": str(exc)}
|
||||||
|
|
||||||
# 批量操作在这里写
|
# 批量操作在这里写
|
||||||
async def change_hole_sheet_to_2(self, hole: MaterialHole):
|
async def change_hole_sheet_to_2(self, hole: MaterialHole):
|
||||||
hole._unilabos_state["max_sheets"] = 2
|
hole._unilabos_state["max_sheets"] = 2
|
||||||
@@ -818,7 +810,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
logger.debug(f"data_electrolyte_code: {data_electrolyte_code}")
|
logger.debug(f"data_electrolyte_code: {data_electrolyte_code}")
|
||||||
logger.debug(f"data_coin_cell_code: {data_coin_cell_code}")
|
logger.debug(f"data_coin_cell_code: {data_coin_cell_code}")
|
||||||
#接收完信息后,读取完毕标志位置True
|
#接收完信息后,读取完毕标志位置True
|
||||||
liaopan3 = self.deck.get_resource("\u7535\u6c60\u6599\u76d8")
|
liaopan3 = self.deck.get_resource("成品弹夹")
|
||||||
#把物料解绑后放到另一盘上
|
#把物料解绑后放到另一盘上
|
||||||
battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", size_x=14, size_y=14, size_z=2)
|
battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", size_x=14, size_y=14, size_z=2)
|
||||||
battery._unilabos_state = {
|
battery._unilabos_state = {
|
||||||
@@ -1013,6 +1005,31 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
#self.success = True
|
#self.success = True
|
||||||
#return self.success
|
#return self.success
|
||||||
|
|
||||||
|
def run_packaging_workflow(self, workflow_config: Dict[str, Any]) -> "CoinCellAssemblyWorkstation":
|
||||||
|
config = workflow_config or {}
|
||||||
|
|
||||||
|
qiming_params = config.get("qiming") or {}
|
||||||
|
if qiming_params:
|
||||||
|
self.qiming_coin_cell_code(**qiming_params)
|
||||||
|
|
||||||
|
if config.get("init", True):
|
||||||
|
self.func_pack_device_init()
|
||||||
|
if config.get("auto", True):
|
||||||
|
self.func_pack_device_auto()
|
||||||
|
if config.get("start", True):
|
||||||
|
self.func_pack_device_start()
|
||||||
|
|
||||||
|
packaging_config = config.get("packaging") or {}
|
||||||
|
bottle_num = packaging_config.get("bottle_num")
|
||||||
|
if bottle_num is not None:
|
||||||
|
self.func_pack_send_bottle_num(bottle_num)
|
||||||
|
|
||||||
|
allpack_params = packaging_config.get("command") or {}
|
||||||
|
if allpack_params:
|
||||||
|
self.func_allpack_cmd(**allpack_params)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
def fun_wuliao_test(self) -> bool:
|
def fun_wuliao_test(self) -> bool:
|
||||||
#找到data_init中构建的2个物料盘
|
#找到data_init中构建的2个物料盘
|
||||||
liaopan3 = self.deck.get_resource("\u7535\u6c60\u6599\u76d8")
|
liaopan3 = self.deck.get_resource("\u7535\u6c60\u6599\u76d8")
|
||||||
@@ -1035,7 +1052,7 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
# time.sleep(1)
|
# time.sleep(1)
|
||||||
# time.sleep(40)
|
# time.sleep(40)
|
||||||
# 数据读取与输出
|
# 数据读取与输出
|
||||||
def func_read_data_and_output(self, file_path: str="D:\\coin_cell_data"):
|
def func_read_data_and_output(self, file_path: str="/Users/sml/work"):
|
||||||
# 检查CSV导出是否正在运行,已运行则跳出,防止同时启动两个while循环
|
# 检查CSV导出是否正在运行,已运行则跳出,防止同时启动两个while循环
|
||||||
if self.csv_export_running:
|
if self.csv_export_running:
|
||||||
return False, "读取已在运行中"
|
return False, "读取已在运行中"
|
||||||
@@ -1229,11 +1246,92 @@ class CoinCellAssemblyWorkstation(WorkstationBase):
|
|||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
def run_coin_cell_assembly_workflow(
|
||||||
|
self,
|
||||||
|
workflow_config: Optional[Dict[str, Any]] = None,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
config: Dict[str, Any]
|
||||||
|
if workflow_config is None:
|
||||||
|
config = {}
|
||||||
|
elif isinstance(workflow_config, list):
|
||||||
|
config = {"materials": workflow_config}
|
||||||
|
else:
|
||||||
|
config = workflow_config
|
||||||
|
qiming_defaults = {
|
||||||
|
"fujipian_panshu": 1,
|
||||||
|
"fujipian_juzhendianwei": 0,
|
||||||
|
"gemopanshu": 1,
|
||||||
|
"gemo_juzhendianwei": 0,
|
||||||
|
"lvbodian": True,
|
||||||
|
"battery_pressure_mode": True,
|
||||||
|
"battery_pressure": 4200,
|
||||||
|
"battery_clean_ignore": False,
|
||||||
|
}
|
||||||
|
qiming_params = {**qiming_defaults, **(config.get("qiming") or {})}
|
||||||
|
qiming_success = self.qiming_coin_cell_code(**qiming_params)
|
||||||
|
|
||||||
|
step_results: Dict[str, Any] = {}
|
||||||
|
try:
|
||||||
|
self.func_pack_device_init()
|
||||||
|
step_results["init"] = True
|
||||||
|
except Exception as exc:
|
||||||
|
step_results["init"] = f"error: {exc}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.func_pack_device_auto()
|
||||||
|
step_results["auto"] = True
|
||||||
|
except Exception as exc:
|
||||||
|
step_results["auto"] = f"error: {exc}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.func_pack_device_start()
|
||||||
|
step_results["start"] = True
|
||||||
|
except Exception as exc:
|
||||||
|
step_results["start"] = f"error: {exc}"
|
||||||
|
|
||||||
|
packaging_cfg = config.get("packaging") or {}
|
||||||
|
bottle_num = packaging_cfg.get("bottle_num", 1)
|
||||||
|
try:
|
||||||
|
self.func_pack_send_bottle_num(bottle_num)
|
||||||
|
step_results["send_bottle_num"] = True
|
||||||
|
except Exception as exc:
|
||||||
|
step_results["send_bottle_num"] = f"error: {exc}"
|
||||||
|
|
||||||
|
command_defaults = {
|
||||||
|
"elec_num": 1,
|
||||||
|
"elec_use_num": 1,
|
||||||
|
"elec_vol": 50,
|
||||||
|
"assembly_type": 7,
|
||||||
|
"assembly_pressure": 4200,
|
||||||
|
"file_path": "/Users/sml/work",
|
||||||
|
}
|
||||||
|
command_params = {**command_defaults, **(packaging_cfg.get("command") or {})}
|
||||||
|
packaging_result = self.func_allpack_cmd(**command_params)
|
||||||
|
|
||||||
|
finished_result = self.func_pack_send_finished_cmd()
|
||||||
|
stop_result = self.func_pack_device_stop()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"qiming": {
|
||||||
|
"params": qiming_params,
|
||||||
|
"success": qiming_success,
|
||||||
|
},
|
||||||
|
"workflow_steps": step_results,
|
||||||
|
"packaging": {
|
||||||
|
"bottle_num": bottle_num,
|
||||||
|
"command": command_params,
|
||||||
|
"result": packaging_result,
|
||||||
|
},
|
||||||
|
"finish": {
|
||||||
|
"send_finished": finished_result,
|
||||||
|
"stop": stop_result,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# 简单测试
|
deck = CoincellDeck(setup=True, name="coin_cell_deck")
|
||||||
workstation = CoinCellAssemblyWorkstation()
|
w = CoinCellAssemblyWorkstation(deck=deck, address="172.16.28.102", port="502", debug_mode=False)
|
||||||
workstation.qiming_coin_cell_code(fujipian_panshu=1, fujipian_juzhendianwei=2, gemopanshu=3, gemo_juzhendianwei=4, lvbodian=False, battery_pressure_mode=False, battery_pressure=4200, battery_clean_ignore=False)
|
w.run_coin_cell_assembly_workflow()
|
||||||
print(f"工作站创建成功: {workstation.deck.name}")
|
|
||||||
print(f"料盘数量: {len(workstation.deck.children)}")
|
|
||||||
@@ -1,583 +0,0 @@
|
|||||||
"""
|
|
||||||
工作站物料管理基类
|
|
||||||
Workstation Material Management Base Class
|
|
||||||
|
|
||||||
基于PyLabRobot的物料管理系统
|
|
||||||
"""
|
|
||||||
from typing import Dict, Any, List, Optional, Union, Type
|
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
import json
|
|
||||||
|
|
||||||
from pylabrobot.resources import (
|
|
||||||
Resource as PLRResource,
|
|
||||||
Container,
|
|
||||||
Deck,
|
|
||||||
Coordinate as PLRCoordinate,
|
|
||||||
)
|
|
||||||
|
|
||||||
from unilabos.ros.nodes.resource_tracker import DeviceNodeResourceTracker
|
|
||||||
from unilabos.utils.log import logger
|
|
||||||
from unilabos.resources.graphio import resource_plr_to_ulab, resource_ulab_to_plr
|
|
||||||
|
|
||||||
|
|
||||||
class MaterialManagementBase(ABC):
|
|
||||||
"""物料管理基类
|
|
||||||
|
|
||||||
定义工作站物料管理的标准接口:
|
|
||||||
1. 物料初始化 - 根据配置创建物料资源
|
|
||||||
2. 物料追踪 - 实时跟踪物料位置和状态
|
|
||||||
3. 物料查找 - 按类型、位置、状态查找物料
|
|
||||||
4. 物料转换 - PyLabRobot与UniLab资源格式转换
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
device_id: str,
|
|
||||||
deck_config: Dict[str, Any],
|
|
||||||
resource_tracker: DeviceNodeResourceTracker,
|
|
||||||
children_config: Dict[str, Dict[str, Any]] = None
|
|
||||||
):
|
|
||||||
self.device_id = device_id
|
|
||||||
self.deck_config = deck_config
|
|
||||||
self.resource_tracker = resource_tracker
|
|
||||||
self.children_config = children_config or {}
|
|
||||||
|
|
||||||
# 创建主台面
|
|
||||||
self.plr_deck = self._create_deck()
|
|
||||||
|
|
||||||
# 扩展ResourceTracker
|
|
||||||
self._extend_resource_tracker()
|
|
||||||
|
|
||||||
# 注册deck到resource tracker
|
|
||||||
self.resource_tracker.add_resource(self.plr_deck)
|
|
||||||
|
|
||||||
# 初始化子资源
|
|
||||||
self.plr_resources = {}
|
|
||||||
self._initialize_materials()
|
|
||||||
|
|
||||||
def _create_deck(self) -> Deck:
|
|
||||||
"""创建主台面"""
|
|
||||||
return Deck(
|
|
||||||
name=f"{self.device_id}_deck",
|
|
||||||
size_x=self.deck_config.get("size_x", 1000.0),
|
|
||||||
size_y=self.deck_config.get("size_y", 1000.0),
|
|
||||||
size_z=self.deck_config.get("size_z", 500.0),
|
|
||||||
origin=PLRCoordinate(0, 0, 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _extend_resource_tracker(self):
|
|
||||||
"""扩展ResourceTracker以支持PyLabRobot特定功能"""
|
|
||||||
|
|
||||||
def find_by_type(resource_type):
|
|
||||||
"""按类型查找资源"""
|
|
||||||
return self._find_resources_by_type_recursive(self.plr_deck, resource_type)
|
|
||||||
|
|
||||||
def find_by_category(category: str):
|
|
||||||
"""按类别查找资源"""
|
|
||||||
found = []
|
|
||||||
for resource in self._get_all_resources():
|
|
||||||
if hasattr(resource, 'category') and resource.category == category:
|
|
||||||
found.append(resource)
|
|
||||||
return found
|
|
||||||
|
|
||||||
def find_by_name_pattern(pattern: str):
|
|
||||||
"""按名称模式查找资源"""
|
|
||||||
import re
|
|
||||||
found = []
|
|
||||||
for resource in self._get_all_resources():
|
|
||||||
if re.search(pattern, resource.name):
|
|
||||||
found.append(resource)
|
|
||||||
return found
|
|
||||||
|
|
||||||
# 动态添加方法到resource_tracker
|
|
||||||
self.resource_tracker.find_by_type = find_by_type
|
|
||||||
self.resource_tracker.find_by_category = find_by_category
|
|
||||||
self.resource_tracker.find_by_name_pattern = find_by_name_pattern
|
|
||||||
|
|
||||||
def _find_resources_by_type_recursive(self, resource, target_type):
|
|
||||||
"""递归查找指定类型的资源"""
|
|
||||||
found = []
|
|
||||||
if isinstance(resource, target_type):
|
|
||||||
found.append(resource)
|
|
||||||
|
|
||||||
# 递归查找子资源
|
|
||||||
children = getattr(resource, "children", [])
|
|
||||||
for child in children:
|
|
||||||
found.extend(self._find_resources_by_type_recursive(child, target_type))
|
|
||||||
|
|
||||||
return found
|
|
||||||
|
|
||||||
def _get_all_resources(self) -> List[PLRResource]:
|
|
||||||
"""获取所有资源"""
|
|
||||||
all_resources = []
|
|
||||||
|
|
||||||
def collect_resources(resource):
|
|
||||||
all_resources.append(resource)
|
|
||||||
children = getattr(resource, "children", [])
|
|
||||||
for child in children:
|
|
||||||
collect_resources(child)
|
|
||||||
|
|
||||||
collect_resources(self.plr_deck)
|
|
||||||
return all_resources
|
|
||||||
|
|
||||||
def _initialize_materials(self):
|
|
||||||
"""初始化物料"""
|
|
||||||
try:
|
|
||||||
# 确定创建顺序,确保父资源先于子资源创建
|
|
||||||
creation_order = self._determine_creation_order()
|
|
||||||
|
|
||||||
# 按顺序创建资源
|
|
||||||
for resource_id in creation_order:
|
|
||||||
config = self.children_config[resource_id]
|
|
||||||
self._create_plr_resource(resource_id, config)
|
|
||||||
|
|
||||||
logger.info(f"物料管理系统初始化完成,共创建 {len(self.plr_resources)} 个资源")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"物料初始化失败: {e}")
|
|
||||||
|
|
||||||
def _determine_creation_order(self) -> List[str]:
|
|
||||||
"""确定资源创建顺序"""
|
|
||||||
order = []
|
|
||||||
visited = set()
|
|
||||||
|
|
||||||
def visit(resource_id: str):
|
|
||||||
if resource_id in visited:
|
|
||||||
return
|
|
||||||
visited.add(resource_id)
|
|
||||||
|
|
||||||
config = self.children_config.get(resource_id, {})
|
|
||||||
parent_id = config.get("parent")
|
|
||||||
|
|
||||||
# 如果有父资源,先访问父资源
|
|
||||||
if parent_id and parent_id in self.children_config:
|
|
||||||
visit(parent_id)
|
|
||||||
|
|
||||||
order.append(resource_id)
|
|
||||||
|
|
||||||
for resource_id in self.children_config:
|
|
||||||
visit(resource_id)
|
|
||||||
|
|
||||||
return order
|
|
||||||
|
|
||||||
def _create_plr_resource(self, resource_id: str, config: Dict[str, Any]):
|
|
||||||
"""创建PyLabRobot资源"""
|
|
||||||
try:
|
|
||||||
resource_type = config.get("type", "unknown")
|
|
||||||
data = config.get("data", {})
|
|
||||||
location_config = config.get("location", {})
|
|
||||||
|
|
||||||
# 创建位置坐标
|
|
||||||
location = PLRCoordinate(
|
|
||||||
x=location_config.get("x", 0.0),
|
|
||||||
y=location_config.get("y", 0.0),
|
|
||||||
z=location_config.get("z", 0.0)
|
|
||||||
)
|
|
||||||
|
|
||||||
# 根据类型创建资源
|
|
||||||
resource = self._create_resource_by_type(resource_id, resource_type, config, data, location)
|
|
||||||
|
|
||||||
if resource:
|
|
||||||
# 设置父子关系
|
|
||||||
parent_id = config.get("parent")
|
|
||||||
if parent_id and parent_id in self.plr_resources:
|
|
||||||
parent_resource = self.plr_resources[parent_id]
|
|
||||||
parent_resource.assign_child_resource(resource, location)
|
|
||||||
else:
|
|
||||||
# 直接放在deck上
|
|
||||||
self.plr_deck.assign_child_resource(resource, location)
|
|
||||||
|
|
||||||
# 保存资源引用
|
|
||||||
self.plr_resources[resource_id] = resource
|
|
||||||
|
|
||||||
# 注册到resource tracker
|
|
||||||
self.resource_tracker.add_resource(resource)
|
|
||||||
|
|
||||||
logger.debug(f"创建资源成功: {resource_id} ({resource_type})")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"创建资源失败 {resource_id}: {e}")
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def _create_resource_by_type(
|
|
||||||
self,
|
|
||||||
resource_id: str,
|
|
||||||
resource_type: str,
|
|
||||||
config: Dict[str, Any],
|
|
||||||
data: Dict[str, Any],
|
|
||||||
location: PLRCoordinate
|
|
||||||
) -> Optional[PLRResource]:
|
|
||||||
"""根据类型创建资源 - 子类必须实现"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
# ============ 物料查找接口 ============
|
|
||||||
|
|
||||||
def find_materials_by_type(self, material_type: str) -> List[PLRResource]:
|
|
||||||
"""按材料类型查找物料"""
|
|
||||||
return self.resource_tracker.find_by_category(material_type)
|
|
||||||
|
|
||||||
def find_material_by_id(self, resource_id: str) -> Optional[PLRResource]:
|
|
||||||
"""按ID查找物料"""
|
|
||||||
return self.plr_resources.get(resource_id)
|
|
||||||
|
|
||||||
def find_available_positions(self, position_type: str) -> List[PLRResource]:
|
|
||||||
"""查找可用位置"""
|
|
||||||
positions = self.resource_tracker.find_by_category(position_type)
|
|
||||||
available = []
|
|
||||||
|
|
||||||
for pos in positions:
|
|
||||||
if hasattr(pos, 'is_available') and pos.is_available():
|
|
||||||
available.append(pos)
|
|
||||||
elif hasattr(pos, 'children') and len(pos.children) == 0:
|
|
||||||
available.append(pos)
|
|
||||||
|
|
||||||
return available
|
|
||||||
|
|
||||||
def get_material_inventory(self) -> Dict[str, int]:
|
|
||||||
"""获取物料库存统计"""
|
|
||||||
inventory = {}
|
|
||||||
|
|
||||||
for resource in self._get_all_resources():
|
|
||||||
if hasattr(resource, 'category'):
|
|
||||||
category = resource.category
|
|
||||||
inventory[category] = inventory.get(category, 0) + 1
|
|
||||||
|
|
||||||
return inventory
|
|
||||||
|
|
||||||
# ============ 物料状态更新接口 ============
|
|
||||||
|
|
||||||
def update_material_location(self, material_id: str, new_location: PLRCoordinate) -> bool:
|
|
||||||
"""更新物料位置"""
|
|
||||||
try:
|
|
||||||
material = self.find_material_by_id(material_id)
|
|
||||||
if material:
|
|
||||||
material.location = new_location
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"更新物料位置失败: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def move_material(self, material_id: str, target_container_id: str) -> bool:
|
|
||||||
"""移动物料到目标容器"""
|
|
||||||
try:
|
|
||||||
material = self.find_material_by_id(material_id)
|
|
||||||
target = self.find_material_by_id(target_container_id)
|
|
||||||
|
|
||||||
if material and target:
|
|
||||||
# 从原位置移除
|
|
||||||
if material.parent:
|
|
||||||
material.parent.unassign_child_resource(material)
|
|
||||||
|
|
||||||
# 添加到新位置
|
|
||||||
target.assign_child_resource(material)
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"移动物料失败: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# ============ 资源转换接口 ============
|
|
||||||
|
|
||||||
def convert_to_unilab_format(self, plr_resource: PLRResource) -> Dict[str, Any]:
|
|
||||||
"""将PyLabRobot资源转换为UniLab格式"""
|
|
||||||
return resource_plr_to_ulab(plr_resource)
|
|
||||||
|
|
||||||
def convert_from_unilab_format(self, unilab_resource: Dict[str, Any]) -> PLRResource:
|
|
||||||
"""将UniLab格式转换为PyLabRobot资源"""
|
|
||||||
return resource_ulab_to_plr(unilab_resource)
|
|
||||||
|
|
||||||
def get_deck_state(self) -> Dict[str, Any]:
|
|
||||||
"""获取Deck状态"""
|
|
||||||
try:
|
|
||||||
return {
|
|
||||||
"deck_info": {
|
|
||||||
"name": self.plr_deck.name,
|
|
||||||
"size": {
|
|
||||||
"x": self.plr_deck.size_x,
|
|
||||||
"y": self.plr_deck.size_y,
|
|
||||||
"z": self.plr_deck.size_z
|
|
||||||
},
|
|
||||||
"children_count": len(self.plr_deck.children)
|
|
||||||
},
|
|
||||||
"resources": {
|
|
||||||
resource_id: self.convert_to_unilab_format(resource)
|
|
||||||
for resource_id, resource in self.plr_resources.items()
|
|
||||||
},
|
|
||||||
"inventory": self.get_material_inventory()
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"获取Deck状态失败: {e}")
|
|
||||||
return {"error": str(e)}
|
|
||||||
|
|
||||||
# ============ 数据持久化接口 ============
|
|
||||||
|
|
||||||
def save_state_to_file(self, file_path: str) -> bool:
|
|
||||||
"""保存状态到文件"""
|
|
||||||
try:
|
|
||||||
state = self.get_deck_state()
|
|
||||||
with open(file_path, 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(state, f, indent=2, ensure_ascii=False)
|
|
||||||
logger.info(f"状态已保存到: {file_path}")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"保存状态失败: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def load_state_from_file(self, file_path: str) -> bool:
|
|
||||||
"""从文件加载状态"""
|
|
||||||
try:
|
|
||||||
with open(file_path, 'r', encoding='utf-8') as f:
|
|
||||||
state = json.load(f)
|
|
||||||
|
|
||||||
# 重新创建资源
|
|
||||||
self._recreate_resources_from_state(state)
|
|
||||||
logger.info(f"状态已从文件加载: {file_path}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"加载状态失败: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _recreate_resources_from_state(self, state: Dict[str, Any]):
|
|
||||||
"""从状态重新创建资源"""
|
|
||||||
# 清除现有资源
|
|
||||||
self.plr_resources.clear()
|
|
||||||
self.plr_deck.children.clear()
|
|
||||||
|
|
||||||
# 从状态重新创建
|
|
||||||
resources_data = state.get("resources", {})
|
|
||||||
for resource_id, resource_data in resources_data.items():
|
|
||||||
try:
|
|
||||||
plr_resource = self.convert_from_unilab_format(resource_data)
|
|
||||||
self.plr_resources[resource_id] = plr_resource
|
|
||||||
self.plr_deck.assign_child_resource(plr_resource)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"重新创建资源失败 {resource_id}: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
class CoinCellMaterialManagement(MaterialManagementBase):
|
|
||||||
"""纽扣电池物料管理类
|
|
||||||
|
|
||||||
从 button_battery_station 抽取的物料管理功能
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _create_resource_by_type(
|
|
||||||
self,
|
|
||||||
resource_id: str,
|
|
||||||
resource_type: str,
|
|
||||||
config: Dict[str, Any],
|
|
||||||
data: Dict[str, Any],
|
|
||||||
location: PLRCoordinate
|
|
||||||
) -> Optional[PLRResource]:
|
|
||||||
"""根据类型创建纽扣电池相关资源"""
|
|
||||||
|
|
||||||
# 导入纽扣电池资源类
|
|
||||||
from unilabos.device_comms.button_battery_station import (
|
|
||||||
MaterialPlate, PlateSlot, ClipMagazine, BatteryPressSlot,
|
|
||||||
TipBox64, WasteTipBox, BottleRack, Battery, ElectrodeSheet
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if resource_type == "material_plate":
|
|
||||||
return self._create_material_plate(resource_id, config, data, location)
|
|
||||||
|
|
||||||
elif resource_type == "plate_slot":
|
|
||||||
return self._create_plate_slot(resource_id, config, data, location)
|
|
||||||
|
|
||||||
elif resource_type == "clip_magazine":
|
|
||||||
return self._create_clip_magazine(resource_id, config, data, location)
|
|
||||||
|
|
||||||
elif resource_type == "battery_press_slot":
|
|
||||||
return self._create_battery_press_slot(resource_id, config, data, location)
|
|
||||||
|
|
||||||
elif resource_type == "tip_box":
|
|
||||||
return self._create_tip_box(resource_id, config, data, location)
|
|
||||||
|
|
||||||
elif resource_type == "waste_tip_box":
|
|
||||||
return self._create_waste_tip_box(resource_id, config, data, location)
|
|
||||||
|
|
||||||
elif resource_type == "bottle_rack":
|
|
||||||
return self._create_bottle_rack(resource_id, config, data, location)
|
|
||||||
|
|
||||||
elif resource_type == "battery":
|
|
||||||
return self._create_battery(resource_id, config, data, location)
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.warning(f"未知的资源类型: {resource_type}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"创建资源失败 {resource_id} ({resource_type}): {e}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _create_material_plate(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
|
|
||||||
"""创建料板"""
|
|
||||||
from unilabos.device_comms.button_battery_station import MaterialPlate, ElectrodeSheet
|
|
||||||
|
|
||||||
plate = MaterialPlate(
|
|
||||||
name=resource_id,
|
|
||||||
size_x=config.get("size_x", 80.0),
|
|
||||||
size_y=config.get("size_y", 80.0),
|
|
||||||
size_z=config.get("size_z", 10.0),
|
|
||||||
hole_diameter=config.get("hole_diameter", 15.0),
|
|
||||||
hole_depth=config.get("hole_depth", 8.0),
|
|
||||||
hole_spacing_x=config.get("hole_spacing_x", 20.0),
|
|
||||||
hole_spacing_y=config.get("hole_spacing_y", 20.0),
|
|
||||||
number=data.get("number", "")
|
|
||||||
)
|
|
||||||
plate.location = location
|
|
||||||
|
|
||||||
# 如果有预填充的极片数据,创建极片
|
|
||||||
electrode_sheets = data.get("electrode_sheets", [])
|
|
||||||
for i, sheet_data in enumerate(electrode_sheets):
|
|
||||||
if i < len(plate.children): # 确保不超过洞位数量
|
|
||||||
hole = plate.children[i]
|
|
||||||
sheet = ElectrodeSheet(
|
|
||||||
name=f"{resource_id}_sheet_{i}",
|
|
||||||
diameter=sheet_data.get("diameter", 14.0),
|
|
||||||
thickness=sheet_data.get("thickness", 0.1),
|
|
||||||
mass=sheet_data.get("mass", 0.01),
|
|
||||||
material_type=sheet_data.get("material_type", "cathode"),
|
|
||||||
info=sheet_data.get("info", "")
|
|
||||||
)
|
|
||||||
hole.place_electrode_sheet(sheet)
|
|
||||||
|
|
||||||
return plate
|
|
||||||
|
|
||||||
def _create_plate_slot(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
|
|
||||||
"""创建板槽位"""
|
|
||||||
from unilabos.device_comms.button_battery_station import PlateSlot
|
|
||||||
|
|
||||||
slot = PlateSlot(
|
|
||||||
name=resource_id,
|
|
||||||
max_plates=config.get("max_plates", 8)
|
|
||||||
)
|
|
||||||
slot.location = location
|
|
||||||
return slot
|
|
||||||
|
|
||||||
def _create_clip_magazine(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
|
|
||||||
"""创建子弹夹"""
|
|
||||||
from unilabos.device_comms.button_battery_station import ClipMagazine
|
|
||||||
|
|
||||||
magazine = ClipMagazine(
|
|
||||||
name=resource_id,
|
|
||||||
size_x=config.get("size_x", 150.0),
|
|
||||||
size_y=config.get("size_y", 100.0),
|
|
||||||
size_z=config.get("size_z", 50.0),
|
|
||||||
hole_diameter=config.get("hole_diameter", 15.0),
|
|
||||||
hole_depth=config.get("hole_depth", 40.0),
|
|
||||||
hole_spacing=config.get("hole_spacing", 25.0),
|
|
||||||
max_sheets_per_hole=config.get("max_sheets_per_hole", 100)
|
|
||||||
)
|
|
||||||
magazine.location = location
|
|
||||||
return magazine
|
|
||||||
|
|
||||||
def _create_battery_press_slot(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
|
|
||||||
"""创建电池压制槽"""
|
|
||||||
from unilabos.device_comms.button_battery_station import BatteryPressSlot
|
|
||||||
|
|
||||||
slot = BatteryPressSlot(
|
|
||||||
name=resource_id,
|
|
||||||
diameter=config.get("diameter", 20.0),
|
|
||||||
depth=config.get("depth", 15.0)
|
|
||||||
)
|
|
||||||
slot.location = location
|
|
||||||
return slot
|
|
||||||
|
|
||||||
def _create_tip_box(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
|
|
||||||
"""创建枪头盒"""
|
|
||||||
from unilabos.device_comms.button_battery_station import TipBox64
|
|
||||||
|
|
||||||
tip_box = TipBox64(
|
|
||||||
name=resource_id,
|
|
||||||
size_x=config.get("size_x", 127.8),
|
|
||||||
size_y=config.get("size_y", 85.5),
|
|
||||||
size_z=config.get("size_z", 60.0),
|
|
||||||
with_tips=data.get("with_tips", True)
|
|
||||||
)
|
|
||||||
tip_box.location = location
|
|
||||||
return tip_box
|
|
||||||
|
|
||||||
def _create_waste_tip_box(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
|
|
||||||
"""创建废枪头盒"""
|
|
||||||
from unilabos.device_comms.button_battery_station import WasteTipBox
|
|
||||||
|
|
||||||
waste_box = WasteTipBox(
|
|
||||||
name=resource_id,
|
|
||||||
size_x=config.get("size_x", 127.8),
|
|
||||||
size_y=config.get("size_y", 85.5),
|
|
||||||
size_z=config.get("size_z", 60.0),
|
|
||||||
max_tips=config.get("max_tips", 100)
|
|
||||||
)
|
|
||||||
waste_box.location = location
|
|
||||||
return waste_box
|
|
||||||
|
|
||||||
def _create_bottle_rack(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
|
|
||||||
"""创建瓶架"""
|
|
||||||
from unilabos.device_comms.button_battery_station import BottleRack
|
|
||||||
|
|
||||||
rack = BottleRack(
|
|
||||||
name=resource_id,
|
|
||||||
size_x=config.get("size_x", 210.0),
|
|
||||||
size_y=config.get("size_y", 140.0),
|
|
||||||
size_z=config.get("size_z", 100.0),
|
|
||||||
bottle_diameter=config.get("bottle_diameter", 30.0),
|
|
||||||
bottle_height=config.get("bottle_height", 100.0),
|
|
||||||
position_spacing=config.get("position_spacing", 35.0)
|
|
||||||
)
|
|
||||||
rack.location = location
|
|
||||||
return rack
|
|
||||||
|
|
||||||
def _create_battery(self, resource_id: str, config: Dict[str, Any], data: Dict[str, Any], location: PLRCoordinate):
|
|
||||||
"""创建电池"""
|
|
||||||
from unilabos.device_comms.button_battery_station import Battery
|
|
||||||
|
|
||||||
battery = Battery(
|
|
||||||
name=resource_id,
|
|
||||||
diameter=config.get("diameter", 20.0),
|
|
||||||
height=config.get("height", 3.2),
|
|
||||||
max_volume=config.get("max_volume", 100.0),
|
|
||||||
barcode=data.get("barcode", "")
|
|
||||||
)
|
|
||||||
battery.location = location
|
|
||||||
return battery
|
|
||||||
|
|
||||||
# ============ 纽扣电池特定查找方法 ============
|
|
||||||
|
|
||||||
def find_material_plates(self):
|
|
||||||
"""查找所有料板"""
|
|
||||||
from unilabos.device_comms.button_battery_station import MaterialPlate
|
|
||||||
return self.resource_tracker.find_by_type(MaterialPlate)
|
|
||||||
|
|
||||||
def find_batteries(self):
|
|
||||||
"""查找所有电池"""
|
|
||||||
from unilabos.device_comms.button_battery_station import Battery
|
|
||||||
return self.resource_tracker.find_by_type(Battery)
|
|
||||||
|
|
||||||
def find_electrode_sheets(self):
|
|
||||||
"""查找所有极片"""
|
|
||||||
found = []
|
|
||||||
plates = self.find_material_plates()
|
|
||||||
for plate in plates:
|
|
||||||
for hole in plate.children:
|
|
||||||
if hasattr(hole, 'has_electrode_sheet') and hole.has_electrode_sheet():
|
|
||||||
found.append(hole._electrode_sheet)
|
|
||||||
return found
|
|
||||||
|
|
||||||
def find_plate_slots(self):
|
|
||||||
"""查找所有板槽位"""
|
|
||||||
from unilabos.device_comms.button_battery_station import PlateSlot
|
|
||||||
return self.resource_tracker.find_by_type(PlateSlot)
|
|
||||||
|
|
||||||
def find_clip_magazines(self):
|
|
||||||
"""查找所有子弹夹"""
|
|
||||||
from unilabos.device_comms.button_battery_station import ClipMagazine
|
|
||||||
return self.resource_tracker.find_by_type(ClipMagazine)
|
|
||||||
|
|
||||||
def find_press_slots(self):
|
|
||||||
"""查找所有压制槽"""
|
|
||||||
from unilabos.device_comms.button_battery_station import BatteryPressSlot
|
|
||||||
return self.resource_tracker.find_by_type(BatteryPressSlot)
|
|
||||||
@@ -4,7 +4,6 @@ bioyond_cell:
|
|||||||
class:
|
class:
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
auto-auto_batch_outbound_from_xlsx:
|
auto-auto_batch_outbound_from_xlsx:
|
||||||
display_name: 批量导入上料
|
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
goal_default:
|
goal_default:
|
||||||
@@ -138,7 +137,7 @@ bioyond_cell:
|
|||||||
WH4_x5_y1_z1_5_quantity: 0.0
|
WH4_x5_y1_z1_5_quantity: 0.0
|
||||||
WH4_x5_y2_z1_10_materialName: ''
|
WH4_x5_y2_z1_10_materialName: ''
|
||||||
WH4_x5_y2_z1_10_quantity: 0.0
|
WH4_x5_y2_z1_10_quantity: 0.0
|
||||||
xlsx_path: C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
xlsx_path: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
placeholder_keys: {}
|
||||||
result: {}
|
result: {}
|
||||||
@@ -464,7 +463,7 @@ bioyond_cell:
|
|||||||
default: 0.0
|
default: 0.0
|
||||||
type: number
|
type: number
|
||||||
xlsx_path:
|
xlsx_path:
|
||||||
default: C:/ML/GitHub/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
default: /Users/sml/work/Unilab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
||||||
type: string
|
type: string
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
@@ -600,6 +599,7 @@ bioyond_cell:
|
|||||||
bottle_type: null
|
bottle_type: null
|
||||||
location_code: null
|
location_code: null
|
||||||
name: null
|
name: null
|
||||||
|
warehouse_name: 手动堆栈
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
placeholder_keys: {}
|
||||||
result: {}
|
result: {}
|
||||||
@@ -617,6 +617,9 @@ bioyond_cell:
|
|||||||
type: string
|
type: string
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
warehouse_name:
|
||||||
|
default: 手动堆栈
|
||||||
|
type: string
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
- board_type
|
- board_type
|
||||||
@@ -785,6 +788,187 @@ bioyond_cell:
|
|||||||
title: report_material_change参数
|
title: report_material_change参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-resource_tree_transfer:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
old_parent: null
|
||||||
|
parent_resource: null
|
||||||
|
plr_resource: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
old_parent:
|
||||||
|
type: object
|
||||||
|
parent_resource:
|
||||||
|
type: object
|
||||||
|
plr_resource:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- old_parent
|
||||||
|
- plr_resource
|
||||||
|
- parent_resource
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: resource_tree_transfer参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-run_feeding_stage:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles:
|
||||||
|
input: []
|
||||||
|
output:
|
||||||
|
- data_key: feeding_materials
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
|
handler_key: feeding_materials
|
||||||
|
label: Feeding Materials
|
||||||
|
placeholder_keys: {}
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
feeding_materials:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- feeding_materials
|
||||||
|
type: object
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: run_feeding_stage参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-run_liquid_preparation_stage:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles:
|
||||||
|
input:
|
||||||
|
- data_key: feeding_materials
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: feeding_materials
|
||||||
|
label: Feeding Materials
|
||||||
|
output:
|
||||||
|
- data_key: liquid_materials
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
|
handler_key: liquid_materials
|
||||||
|
label: Liquid Materials
|
||||||
|
placeholder_keys: {}
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
feeding_materials:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
liquid_materials:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- liquid_materials
|
||||||
|
type: object
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
feeding_materials:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: run_liquid_preparation_stage参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-run_transfer_stage:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles:
|
||||||
|
input:
|
||||||
|
- data_key: liquid_materials
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: liquid_materials
|
||||||
|
label: Liquid Materials
|
||||||
|
output:
|
||||||
|
- data_key: transfer_materials
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
|
handler_key: transfer_materials
|
||||||
|
label: Transfer Materials
|
||||||
|
placeholder_keys: {}
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
liquid_materials:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
transfer_materials:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
transfer_summary:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- transfer_materials
|
||||||
|
type: object
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
liquid_materials:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
liquid_materials:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
transfer_materials:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
transfer_summary:
|
||||||
|
type: object
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: run_transfer_stage参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-scheduler_continue:
|
auto-scheduler_continue:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -1072,12 +1256,13 @@ bioyond_cell:
|
|||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.workstation.bioyond_studio.bioyond_cell.bioyond_cell_workstation:BioyondCellWorkstation
|
module: unilabos.devices.workstation.bioyond_studio.bioyond_cell.bioyond_cell_workstation:BioyondCellWorkstation
|
||||||
status_types: {}
|
status_types:
|
||||||
|
device_id: String
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: ''
|
description: 配液工站
|
||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: benyao2.webp
|
||||||
init_param_schema:
|
init_param_schema:
|
||||||
config:
|
config:
|
||||||
properties:
|
properties:
|
||||||
@@ -1090,8 +1275,11 @@ bioyond_cell:
|
|||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
data:
|
data:
|
||||||
properties: {}
|
properties:
|
||||||
required: []
|
device_id:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- device_id
|
||||||
type: object
|
type: object
|
||||||
registry_type: device
|
registry_type: device
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ coincellassemblyworkstation_device:
|
|||||||
elec_num: null
|
elec_num: null
|
||||||
elec_use_num: null
|
elec_use_num: null
|
||||||
elec_vol: 50
|
elec_vol: 50
|
||||||
file_path: C:\Users\67484\Desktop
|
file_path: /Users/sml/work
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
placeholder_keys: {}
|
||||||
result: {}
|
result: {}
|
||||||
@@ -103,7 +103,7 @@ coincellassemblyworkstation_device:
|
|||||||
default: 50
|
default: 50
|
||||||
type: integer
|
type: integer
|
||||||
file_path:
|
file_path:
|
||||||
default: C:\Users\67484\Desktop
|
default: /Users/sml/work
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- elec_num
|
- elec_num
|
||||||
@@ -332,7 +332,7 @@ coincellassemblyworkstation_device:
|
|||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
goal_default:
|
goal_default:
|
||||||
file_path: D:\coin_cell_data
|
file_path: /Users/sml/work
|
||||||
handles: {}
|
handles: {}
|
||||||
placeholder_keys: {}
|
placeholder_keys: {}
|
||||||
result: {}
|
result: {}
|
||||||
@@ -343,7 +343,7 @@ coincellassemblyworkstation_device:
|
|||||||
goal:
|
goal:
|
||||||
properties:
|
properties:
|
||||||
file_path:
|
file_path:
|
||||||
default: D:\coin_cell_data
|
default: /Users/sml/work
|
||||||
type: string
|
type: string
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
@@ -477,6 +477,171 @@ coincellassemblyworkstation_device:
|
|||||||
title: qiming_coin_cell_code参数
|
title: qiming_coin_cell_code参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-run_coin_cell_assembly_workflow:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
workflow_config:
|
||||||
|
type: object
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
goal_default:
|
||||||
|
workflow_config: {}
|
||||||
|
handles:
|
||||||
|
input:
|
||||||
|
- data_key: workflow_config
|
||||||
|
data_source: handle
|
||||||
|
data_type: resource
|
||||||
|
handler_key: WorkflowConfig
|
||||||
|
label: Workflow Config
|
||||||
|
output:
|
||||||
|
- data_key: qiming
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
|
handler_key: QimingResult
|
||||||
|
label: Qiming Result
|
||||||
|
- data_key: workflow_steps
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
|
handler_key: WorkflowSteps
|
||||||
|
label: Workflow Steps
|
||||||
|
- data_key: packaging
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
|
handler_key: PackagingResult
|
||||||
|
label: Packaging Result
|
||||||
|
- data_key: finish
|
||||||
|
data_source: executor
|
||||||
|
data_type: resource
|
||||||
|
handler_key: FinishResult
|
||||||
|
label: Finish Result
|
||||||
|
placeholder_keys: {}
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
finish:
|
||||||
|
properties:
|
||||||
|
send_finished:
|
||||||
|
type: object
|
||||||
|
stop:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- send_finished
|
||||||
|
- stop
|
||||||
|
type: object
|
||||||
|
packaging:
|
||||||
|
properties:
|
||||||
|
bottle_num:
|
||||||
|
type: integer
|
||||||
|
command:
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- bottle_num
|
||||||
|
- command
|
||||||
|
- result
|
||||||
|
type: object
|
||||||
|
qiming:
|
||||||
|
properties:
|
||||||
|
params:
|
||||||
|
type: object
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- params
|
||||||
|
- success
|
||||||
|
type: object
|
||||||
|
workflow_steps:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- qiming
|
||||||
|
- workflow_steps
|
||||||
|
- packaging
|
||||||
|
- finish
|
||||||
|
type: object
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
workflow_config:
|
||||||
|
type: object
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
finish:
|
||||||
|
properties:
|
||||||
|
send_finished:
|
||||||
|
type: object
|
||||||
|
stop:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- send_finished
|
||||||
|
- stop
|
||||||
|
type: object
|
||||||
|
packaging:
|
||||||
|
properties:
|
||||||
|
bottle_num:
|
||||||
|
type: integer
|
||||||
|
command:
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- bottle_num
|
||||||
|
- command
|
||||||
|
- result
|
||||||
|
type: object
|
||||||
|
qiming:
|
||||||
|
properties:
|
||||||
|
params:
|
||||||
|
type: object
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- params
|
||||||
|
- success
|
||||||
|
type: object
|
||||||
|
workflow_steps:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- qiming
|
||||||
|
- workflow_steps
|
||||||
|
- packaging
|
||||||
|
- finish
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: run_coin_cell_assembly_workflow参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-run_packaging_workflow:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
workflow_config: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
workflow_config:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- workflow_config
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: run_packaging_workflow参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation
|
module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation
|
||||||
status_types:
|
status_types:
|
||||||
data_assembly_coin_cell_num: int
|
data_assembly_coin_cell_num: int
|
||||||
@@ -500,20 +665,22 @@ coincellassemblyworkstation_device:
|
|||||||
sys_status: str
|
sys_status: str
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: ''
|
description: 扣电工站
|
||||||
handles: []
|
handles: []
|
||||||
icon: coin_cell_assembly_picture.webp
|
icon: koudian.webp
|
||||||
init_param_schema:
|
init_param_schema:
|
||||||
config:
|
config:
|
||||||
properties:
|
properties:
|
||||||
address:
|
address:
|
||||||
default: 172.21.32.111
|
default: 172.16.28.102
|
||||||
type: string
|
type: string
|
||||||
|
config:
|
||||||
|
type: object
|
||||||
debug_mode:
|
debug_mode:
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
deck:
|
deck:
|
||||||
type: object
|
type: string
|
||||||
port:
|
port:
|
||||||
default: '502'
|
default: '502'
|
||||||
type: string
|
type: string
|
||||||
|
|||||||
@@ -654,6 +654,31 @@ liquid_handler:
|
|||||||
title: iter_tips参数
|
title: iter_tips参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-set_group:
|
auto-set_group:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -6170,6 +6195,31 @@ liquid_handler.prcxi:
|
|||||||
title: move_to参数
|
title: move_to参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommandAsync
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-run_protocol:
|
auto-run_protocol:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
neware_battery_test_system:
|
neware_battery_test_system:
|
||||||
category:
|
category:
|
||||||
- neware_battery_test_system
|
- neware_battery_test_system
|
||||||
|
- neware
|
||||||
|
- battery_test
|
||||||
class:
|
class:
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
auto-post_init:
|
auto-post_init:
|
||||||
@@ -70,6 +72,38 @@ neware_battery_test_system:
|
|||||||
title: test_connection参数
|
title: test_connection参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
debug_resource_names:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: {}
|
||||||
|
result:
|
||||||
|
return_info: return_info
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: 调试方法:显示所有资源的实际名称
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
description: 资源调试信息
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
description: 是否成功
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
export_status_json:
|
export_status_json:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -219,7 +253,9 @@ neware_battery_test_system:
|
|||||||
goal_default:
|
goal_default:
|
||||||
string: ''
|
string: ''
|
||||||
handles: {}
|
handles: {}
|
||||||
result: {}
|
result:
|
||||||
|
return_info: return_info
|
||||||
|
success: success
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
properties:
|
properties:
|
||||||
@@ -252,6 +288,56 @@ neware_battery_test_system:
|
|||||||
title: StrSingleInput
|
title: StrSingleInput
|
||||||
type: object
|
type: object
|
||||||
type: StrSingleInput
|
type: StrSingleInput
|
||||||
|
submit_from_csv:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
csv_path: string
|
||||||
|
output_dir: string
|
||||||
|
goal_default:
|
||||||
|
csv_path: ''
|
||||||
|
output_dir: .
|
||||||
|
handles: {}
|
||||||
|
result:
|
||||||
|
return_info: return_info
|
||||||
|
submitted_count: submitted_count
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: 从CSV文件批量提交Neware测试任务
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
csv_path:
|
||||||
|
description: 输入CSV文件的绝对路径
|
||||||
|
type: string
|
||||||
|
output_dir:
|
||||||
|
description: 输出目录(用于存储XML和备份文件),默认当前目录
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- csv_path
|
||||||
|
type: object
|
||||||
|
result:
|
||||||
|
properties:
|
||||||
|
return_info:
|
||||||
|
description: 执行结果详细信息
|
||||||
|
type: string
|
||||||
|
submitted_count:
|
||||||
|
description: 成功提交的任务数量
|
||||||
|
type: integer
|
||||||
|
success:
|
||||||
|
description: 是否成功
|
||||||
|
type: boolean
|
||||||
|
total_count:
|
||||||
|
description: CSV文件中的总行数
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- return_info
|
||||||
|
- success
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
test_connection_action:
|
test_connection_action:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -284,7 +370,7 @@ neware_battery_test_system:
|
|||||||
- goal
|
- goal
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.battery.neware_battery_test_system:NewareBatteryTestSystem
|
module: unilabos.devices.neware_battery_test_system.neware_battery_test_system:NewareBatteryTestSystem
|
||||||
status_types:
|
status_types:
|
||||||
channel_status: dict
|
channel_status: dict
|
||||||
connection_info: dict
|
connection_info: dict
|
||||||
@@ -294,7 +380,7 @@ neware_battery_test_system:
|
|||||||
total_channels: int
|
total_channels: int
|
||||||
type: python
|
type: python
|
||||||
config_info: []
|
config_info: []
|
||||||
description: 新威电池测试系统驱动,支持720个通道的电池测试状态监控和数据导出。通过TCP通信实现远程控制,包含完整的物料管理系统,支持2盘电池的状态映射和监控。
|
description: 新威电池测试系统驱动,提供720个通道的电池测试状态监控、物料管理和CSV批量提交功能。支持TCP通信实现远程控制,包含完整的物料管理系统(2盘电池状态映射),以及从CSV文件批量提交测试任务的能力。
|
||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema:
|
init_param_schema:
|
||||||
@@ -310,13 +396,13 @@ neware_battery_test_system:
|
|||||||
port:
|
port:
|
||||||
type: integer
|
type: integer
|
||||||
size_x:
|
size_x:
|
||||||
default: 500.0
|
default: 50
|
||||||
type: number
|
type: number
|
||||||
size_y:
|
size_y:
|
||||||
default: 500.0
|
default: 50
|
||||||
type: number
|
type: number
|
||||||
size_z:
|
size_z:
|
||||||
default: 2000.0
|
default: 20
|
||||||
type: number
|
type: number
|
||||||
timeout:
|
timeout:
|
||||||
type: integer
|
type: integer
|
||||||
|
|||||||
@@ -45,6 +45,31 @@ virtual_centrifuge:
|
|||||||
title: initialize参数
|
title: initialize参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommandAsync
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
centrifuge:
|
centrifuge:
|
||||||
feedback:
|
feedback:
|
||||||
current_speed: current_speed
|
current_speed: current_speed
|
||||||
@@ -335,6 +360,31 @@ virtual_column:
|
|||||||
title: initialize参数
|
title: initialize参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommandAsync
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
run_column:
|
run_column:
|
||||||
feedback:
|
feedback:
|
||||||
current_status: current_status
|
current_status: current_status
|
||||||
@@ -732,6 +782,31 @@ virtual_filter:
|
|||||||
title: initialize参数
|
title: initialize参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommandAsync
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
filter:
|
filter:
|
||||||
feedback:
|
feedback:
|
||||||
current_status: current_status
|
current_status: current_status
|
||||||
@@ -1358,6 +1433,31 @@ virtual_heatchill:
|
|||||||
title: initialize参数
|
title: initialize参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommandAsync
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
heat_chill:
|
heat_chill:
|
||||||
feedback:
|
feedback:
|
||||||
status: status
|
status: status
|
||||||
@@ -2358,6 +2458,31 @@ virtual_rotavap:
|
|||||||
title: initialize参数
|
title: initialize参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommandAsync
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
evaporate:
|
evaporate:
|
||||||
feedback:
|
feedback:
|
||||||
current_device: current_device
|
current_device: current_device
|
||||||
@@ -2690,6 +2815,31 @@ virtual_separator:
|
|||||||
title: initialize参数
|
title: initialize参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommandAsync
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
separate:
|
separate:
|
||||||
feedback:
|
feedback:
|
||||||
current_status: status
|
current_status: status
|
||||||
@@ -3600,6 +3750,31 @@ virtual_solenoid_valve:
|
|||||||
title: is_closed参数
|
title: is_closed参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-reset:
|
auto-reset:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -4177,6 +4352,31 @@ virtual_solid_dispenser:
|
|||||||
title: parse_mol_string参数
|
title: parse_mol_string参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
module: unilabos.devices.virtual.virtual_solid_dispenser:VirtualSolidDispenser
|
module: unilabos.devices.virtual.virtual_solid_dispenser:VirtualSolidDispenser
|
||||||
status_types:
|
status_types:
|
||||||
current_reagent: str
|
current_reagent: str
|
||||||
@@ -4278,6 +4478,31 @@ virtual_stirrer:
|
|||||||
title: initialize参数
|
title: initialize参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommandAsync
|
type: UniLabJsonCommandAsync
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
start_stir:
|
start_stir:
|
||||||
feedback:
|
feedback:
|
||||||
status: status
|
status: status
|
||||||
@@ -4995,6 +5220,31 @@ virtual_transfer_pump:
|
|||||||
title: is_full参数
|
title: is_full参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
|
auto-post_init:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ros_node: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ros_node:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- ros_node
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: post_init参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
auto-pull_plunger:
|
auto-pull_plunger:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
|
|||||||
@@ -1,16 +1,3 @@
|
|||||||
YB_qiang_tou:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottles:YB_qiang_tou
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_qiang_tou
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_20ml_fenyeping:
|
YB_20ml_fenyeping:
|
||||||
category:
|
category:
|
||||||
- yb3
|
- yb3
|
||||||
@@ -37,6 +24,19 @@ YB_5ml_fenyeping:
|
|||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
registry_type: resource
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
YB_jia_yang_tou_da:
|
||||||
|
category:
|
||||||
|
- yb3
|
||||||
|
- YB_bottle
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.YB_bottles:YB_jia_yang_tou_da
|
||||||
|
type: pylabrobot
|
||||||
|
description: YB_jia_yang_tou_da
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
YB_pei_ye_da_Bottle:
|
YB_pei_ye_da_Bottle:
|
||||||
category:
|
category:
|
||||||
- yb3
|
- yb3
|
||||||
@@ -63,3 +63,30 @@ YB_pei_ye_xiao_Bottle:
|
|||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
registry_type: resource
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
YB_qiang_tou:
|
||||||
|
category:
|
||||||
|
- yb3
|
||||||
|
- YB_bottle
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.YB_bottles:YB_qiang_tou
|
||||||
|
type: pylabrobot
|
||||||
|
description: YB_qiang_tou
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
|
YB_ye_Bottle:
|
||||||
|
category:
|
||||||
|
- yb3
|
||||||
|
- YB_bottle_carriers
|
||||||
|
- YB_bottle
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.YB_bottles:YB_ye_Bottle
|
||||||
|
type: pylabrobot
|
||||||
|
description: YB_ye_Bottle
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
|
|||||||
@@ -11,71 +11,6 @@ YB_100ml_yeti:
|
|||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
registry_type: resource
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_1BottleCarrier:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_1BottleCarrier
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_1BottleCarrier
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_gaonianye:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_gaonianye
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_gaonianye
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_peiyepingdaban:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingdaban
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_peiyepingdaban
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_6StockCarrier:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6StockCarrier
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_6StockCarrier
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_6VialCarrier:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6VialCarrier
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_6VialCarrier
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_20ml_fenyepingban:
|
YB_20ml_fenyepingban:
|
||||||
category:
|
category:
|
||||||
- yb3
|
- yb3
|
||||||
@@ -102,40 +37,27 @@ YB_5ml_fenyepingban:
|
|||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
registry_type: resource
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_peiyepingxiaoban:
|
YB_6StockCarrier:
|
||||||
category:
|
category:
|
||||||
- yb3
|
- yb3
|
||||||
- YB_bottle_carriers
|
- YB_bottle_carriers
|
||||||
class:
|
class:
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingxiaoban
|
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6StockCarrier
|
||||||
type: pylabrobot
|
type: pylabrobot
|
||||||
description: YB_peiyepingxiaoban
|
description: YB_6StockCarrier
|
||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
registry_type: resource
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_shi_pei_qi_kuai:
|
YB_6VialCarrier:
|
||||||
category:
|
category:
|
||||||
- yb3
|
- yb3
|
||||||
- YB_bottle_carriers
|
- YB_bottle_carriers
|
||||||
class:
|
class:
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_shi_pei_qi_kuai
|
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_6VialCarrier
|
||||||
type: pylabrobot
|
type: pylabrobot
|
||||||
description: YB_shi_pei_qi_kuai
|
description: YB_6VialCarrier
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
YB_qiang_tou_he:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_qiang_tou_he
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_qiang_tou_he
|
|
||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
@@ -154,14 +76,14 @@ YB_gao_nian_ye_Bottle:
|
|||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
registry_type: resource
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_jia_yang_tou_da:
|
YB_gaonianye:
|
||||||
category:
|
category:
|
||||||
- yb3
|
- yb3
|
||||||
- YB_bottle_carriers
|
- YB_bottle_carriers
|
||||||
class:
|
class:
|
||||||
module: unilabos.resources.bioyond.YB_bottles:YB_jia_yang_tou_da
|
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_gaonianye
|
||||||
type: pylabrobot
|
type: pylabrobot
|
||||||
description: YB_jia_yang_tou_da
|
description: YB_gaonianye
|
||||||
handles: []
|
handles: []
|
||||||
icon: ''
|
icon: ''
|
||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
@@ -180,6 +102,71 @@ YB_jia_yang_tou_da_Carrier:
|
|||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
registry_type: resource
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
YB_peiyepingdaban:
|
||||||
|
category:
|
||||||
|
- yb3
|
||||||
|
- YB_bottle_carriers
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingdaban
|
||||||
|
type: pylabrobot
|
||||||
|
description: YB_peiyepingdaban
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
|
YB_peiyepingxiaoban:
|
||||||
|
category:
|
||||||
|
- yb3
|
||||||
|
- YB_bottle_carriers
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingxiaoban
|
||||||
|
type: pylabrobot
|
||||||
|
description: YB_peiyepingxiaoban
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
|
YB_qiang_tou_he:
|
||||||
|
category:
|
||||||
|
- yb3
|
||||||
|
- YB_bottle_carriers
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_qiang_tou_he
|
||||||
|
type: pylabrobot
|
||||||
|
description: YB_qiang_tou_he
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
|
YB_shi_pei_qi_kuai:
|
||||||
|
category:
|
||||||
|
- yb3
|
||||||
|
- YB_bottle_carriers
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_shi_pei_qi_kuai
|
||||||
|
type: pylabrobot
|
||||||
|
description: YB_shi_pei_qi_kuai
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
|
YB_ye:
|
||||||
|
category:
|
||||||
|
- yb3
|
||||||
|
- YB_bottle_carriers
|
||||||
|
class:
|
||||||
|
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_ye
|
||||||
|
type: pylabrobot
|
||||||
|
description: YB_ye_Bottle_Carrier
|
||||||
|
handles: []
|
||||||
|
icon: ''
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
YB_ye_100ml_Bottle:
|
YB_ye_100ml_Bottle:
|
||||||
category:
|
category:
|
||||||
- yb3
|
- yb3
|
||||||
@@ -193,16 +180,3 @@ YB_ye_100ml_Bottle:
|
|||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
registry_type: resource
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
YB_ye_Bottle:
|
|
||||||
category:
|
|
||||||
- yb3
|
|
||||||
- YB_bottle_carriers
|
|
||||||
class:
|
|
||||||
module: unilabos.resources.bioyond.YB_bottles:YB_ye_Bottle
|
|
||||||
type: pylabrobot
|
|
||||||
description: YB_ye_Bottle
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema: {}
|
|
||||||
registry_type: resource
|
|
||||||
version: 1.0.0
|
|
||||||
|
|||||||
@@ -34,3 +34,15 @@ BIOYOND_YB_Deck:
|
|||||||
init_param_schema: {}
|
init_param_schema: {}
|
||||||
registry_type: resource
|
registry_type: resource
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
CoincellDeck:
|
||||||
|
category:
|
||||||
|
- deck
|
||||||
|
class:
|
||||||
|
module: unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:CoincellDeck
|
||||||
|
type: pylabrobot
|
||||||
|
description: CoincellDeck
|
||||||
|
handles: []
|
||||||
|
icon: yihua.webp
|
||||||
|
init_param_schema: {}
|
||||||
|
registry_type: resource
|
||||||
|
version: 1.0.0
|
||||||
|
|||||||
0
unilabos/resources/battery/__init__.py
Normal file
0
unilabos/resources/battery/__init__.py
Normal file
56
unilabos/resources/battery/bottle_carriers.py
Normal file
56
unilabos/resources/battery/bottle_carriers.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d
|
||||||
|
|
||||||
|
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
|
||||||
|
from unilabos.resources.bioyond.YB_bottles import (
|
||||||
|
YB_pei_ye_xiao_Bottle,
|
||||||
|
)
|
||||||
|
# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial
|
||||||
|
|
||||||
|
|
||||||
|
def YIHUA_Electrolyte_12VialCarrier(name: str) -> BottleCarrier:
|
||||||
|
"""12瓶载架 - 2x6布局"""
|
||||||
|
# 载架尺寸 (mm)
|
||||||
|
carrier_size_x = 120.0
|
||||||
|
carrier_size_y = 250.0
|
||||||
|
carrier_size_z = 50.0
|
||||||
|
|
||||||
|
# 瓶位尺寸
|
||||||
|
bottle_diameter = 35.0
|
||||||
|
bottle_spacing_x = 35.0 # X方向间距
|
||||||
|
bottle_spacing_y = 35.0 # Y方向间距
|
||||||
|
|
||||||
|
# 计算起始位置 (居中排列)
|
||||||
|
start_x = (carrier_size_x - (2 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||||
|
start_y = (carrier_size_y - (6 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||||
|
|
||||||
|
sites = create_ordered_items_2d(
|
||||||
|
klass=ResourceHolder,
|
||||||
|
num_items_x=2,
|
||||||
|
num_items_y=6,
|
||||||
|
dx=start_x,
|
||||||
|
dy=start_y,
|
||||||
|
dz=5.0,
|
||||||
|
item_dx=bottle_spacing_x,
|
||||||
|
item_dy=bottle_spacing_y,
|
||||||
|
|
||||||
|
size_x=bottle_diameter,
|
||||||
|
size_y=bottle_diameter,
|
||||||
|
size_z=carrier_size_z,
|
||||||
|
)
|
||||||
|
for k, v in sites.items():
|
||||||
|
v.name = f"{name}_{v.name}"
|
||||||
|
|
||||||
|
carrier = BottleCarrier(
|
||||||
|
name=name,
|
||||||
|
size_x=carrier_size_x,
|
||||||
|
size_y=carrier_size_y,
|
||||||
|
size_z=carrier_size_z,
|
||||||
|
sites=sites,
|
||||||
|
model="Electrolyte_12VialCarrier",
|
||||||
|
)
|
||||||
|
carrier.num_items_x = 2
|
||||||
|
carrier.num_items_y = 6
|
||||||
|
carrier.num_items_z = 1
|
||||||
|
for i in range(12):
|
||||||
|
carrier[i] = YB_pei_ye_xiao_Bottle(f"{name}_vial_{i+1}")
|
||||||
|
return carrier
|
||||||
179
unilabos/resources/battery/electrode_sheet.py
Normal file
179
unilabos/resources/battery/electrode_sheet.py
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
from typing import Any, Dict, Optional, TypedDict
|
||||||
|
|
||||||
|
from pylabrobot.resources import Resource as ResourcePLR
|
||||||
|
from pylabrobot.resources import Container
|
||||||
|
|
||||||
|
|
||||||
|
electrode_colors = {
|
||||||
|
"PositiveCan": "#ff0000",
|
||||||
|
"PositiveElectrode": "#cc3333",
|
||||||
|
"NegativeCan": "#000000",
|
||||||
|
"NegativeElectrode": "#666666",
|
||||||
|
"SpringWasher": "#8b7355",
|
||||||
|
"FlatWasher": "a9a9a9",
|
||||||
|
"AluminumFoil": "#ffcccc",
|
||||||
|
"Battery": "#00ff00",
|
||||||
|
}
|
||||||
|
|
||||||
|
class ElectrodeSheetState(TypedDict):
|
||||||
|
mass: float # 质量 (g)
|
||||||
|
material_type: str # 材料类型(铜、铝、不锈钢、弹簧钢等)
|
||||||
|
color: str # 材料类型对应的颜色
|
||||||
|
|
||||||
|
|
||||||
|
class ElectrodeSheet(ResourcePLR):
|
||||||
|
"""极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str = "极片",
|
||||||
|
size_x=10,
|
||||||
|
size_y=10,
|
||||||
|
size_z=10,
|
||||||
|
category: str = "electrode_sheet",
|
||||||
|
model: Optional[str] = None,
|
||||||
|
):
|
||||||
|
"""初始化极片
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 极片名称
|
||||||
|
category: 类别
|
||||||
|
model: 型号
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
size_x=size_x,
|
||||||
|
size_y=size_y,
|
||||||
|
size_z=size_z,
|
||||||
|
category=category,
|
||||||
|
model=model,
|
||||||
|
)
|
||||||
|
self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState(
|
||||||
|
diameter=14,
|
||||||
|
thickness=0.1,
|
||||||
|
mass=0.5,
|
||||||
|
material_type="copper",
|
||||||
|
info=None
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: 这个还要不要?给self._unilabos_state赋值的?
|
||||||
|
def load_state(self, state: Dict[str, Any]) -> None:
|
||||||
|
"""格式不变"""
|
||||||
|
super().load_state(state)
|
||||||
|
self._unilabos_state = state
|
||||||
|
#序列化
|
||||||
|
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""格式不变"""
|
||||||
|
data = super().serialize_state()
|
||||||
|
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def PositiveCan(name: str) -> ElectrodeSheet:
|
||||||
|
"""创建正极壳"""
|
||||||
|
sheet = ElectrodeSheet(name=name, size_x=12, size_y=12, size_z=3.0, model="PositiveCan")
|
||||||
|
sheet.load_state({"material_type": "aluminum", "color": electrode_colors["PositiveCan"]})
|
||||||
|
return sheet
|
||||||
|
|
||||||
|
|
||||||
|
def PositiveElectrode(name: str) -> ElectrodeSheet:
|
||||||
|
"""创建正极片"""
|
||||||
|
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.1, model="PositiveElectrode")
|
||||||
|
sheet.load_state({"material_type": "positive_electrode", "color": electrode_colors["PositiveElectrode"]})
|
||||||
|
return sheet
|
||||||
|
|
||||||
|
|
||||||
|
def NegativeCan(name: str) -> ElectrodeSheet:
|
||||||
|
"""创建负极壳"""
|
||||||
|
sheet = ElectrodeSheet(name=name, size_x=12, size_y=12, size_z=2.0, model="NegativeCan")
|
||||||
|
sheet.load_state({"material_type": "steel", "color": electrode_colors["NegativeCan"]})
|
||||||
|
return sheet
|
||||||
|
|
||||||
|
|
||||||
|
def NegativeElectrode(name: str) -> ElectrodeSheet:
|
||||||
|
"""创建负极片"""
|
||||||
|
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.1, model="NegativeElectrode")
|
||||||
|
sheet.load_state({"material_type": "negative_electrode", "color": electrode_colors["NegativeElectrode"]})
|
||||||
|
return sheet
|
||||||
|
|
||||||
|
|
||||||
|
def SpringWasher(name: str) -> ElectrodeSheet:
|
||||||
|
"""创建弹片"""
|
||||||
|
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.5, model="SpringWasher")
|
||||||
|
sheet.load_state({"material_type": "spring_steel", "color": electrode_colors["SpringWasher"]})
|
||||||
|
return sheet
|
||||||
|
|
||||||
|
|
||||||
|
def FlatWasher(name: str) -> ElectrodeSheet:
|
||||||
|
"""创建垫片"""
|
||||||
|
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.2, model="FlatWasher")
|
||||||
|
sheet.load_state({"material_type": "steel", "color": electrode_colors["FlatWasher"]})
|
||||||
|
return sheet
|
||||||
|
|
||||||
|
|
||||||
|
def AluminumFoil(name: str) -> ElectrodeSheet:
|
||||||
|
"""创建铝箔"""
|
||||||
|
sheet = ElectrodeSheet(name=name, size_x=10, size_y=10, size_z=0.05, model="AluminumFoil")
|
||||||
|
sheet.load_state({"material_type": "aluminum", "color": electrode_colors["AluminumFoil"]})
|
||||||
|
return sheet
|
||||||
|
|
||||||
|
|
||||||
|
class BatteryState(TypedDict):
|
||||||
|
color: str # 材料类型对应的颜色
|
||||||
|
electrolyte_name: str
|
||||||
|
data_electrolyte_code: str
|
||||||
|
open_circuit_voltage: float
|
||||||
|
assembly_pressure: float
|
||||||
|
electrolyte_volume: float
|
||||||
|
|
||||||
|
info: Optional[str] # 附加信息
|
||||||
|
|
||||||
|
|
||||||
|
class Battery(Container):
|
||||||
|
"""电池类 - 包含组装好的电池"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str = "电池",
|
||||||
|
size_x=12,
|
||||||
|
size_y=12,
|
||||||
|
size_z=6,
|
||||||
|
category: str = "battery",
|
||||||
|
model: Optional[str] = None,
|
||||||
|
):
|
||||||
|
"""初始化电池
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 电池名称
|
||||||
|
category: 类别
|
||||||
|
model: 型号
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
size_x=size_x,
|
||||||
|
size_y=size_y,
|
||||||
|
size_z=size_z,
|
||||||
|
category=category,
|
||||||
|
model=model,
|
||||||
|
)
|
||||||
|
self._unilabos_state: BatteryState = BatteryState(
|
||||||
|
color=electrode_colors["Battery"],
|
||||||
|
electrolyte_name="无",
|
||||||
|
data_electrolyte_code="",
|
||||||
|
open_circuit_voltage=0.0,
|
||||||
|
assembly_pressure=0.0,
|
||||||
|
electrolyte_volume=0.0,
|
||||||
|
info=None
|
||||||
|
)
|
||||||
|
|
||||||
|
def load_state(self, state: Dict[str, Any]) -> None:
|
||||||
|
"""格式不变"""
|
||||||
|
super().load_state(state)
|
||||||
|
self._unilabos_state = state
|
||||||
|
|
||||||
|
#序列化
|
||||||
|
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""格式不变"""
|
||||||
|
data = super().serialize_state()
|
||||||
|
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
||||||
|
return data
|
||||||
344
unilabos/resources/battery/magazine.py
Normal file
344
unilabos/resources/battery/magazine.py
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
from typing import Dict, List, Optional, OrderedDict, Union, Callable
|
||||||
|
import math
|
||||||
|
|
||||||
|
from pylabrobot.resources.coordinate import Coordinate
|
||||||
|
from pylabrobot.resources import Resource, ResourceStack, ItemizedResource
|
||||||
|
from pylabrobot.resources.carrier import create_homogeneous_resources
|
||||||
|
|
||||||
|
from unilabos.resources.battery.electrode_sheet import (
|
||||||
|
PositiveCan, PositiveElectrode,
|
||||||
|
NegativeCan, NegativeElectrode,
|
||||||
|
SpringWasher, FlatWasher,
|
||||||
|
AluminumFoil,
|
||||||
|
Battery
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Magazine(ResourceStack):
|
||||||
|
"""子弹夹洞位类"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
direction: str = 'z',
|
||||||
|
resources: Optional[List[Resource]] = None,
|
||||||
|
max_sheets: int = 100,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
"""初始化子弹夹洞位
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 洞位名称
|
||||||
|
direction: 堆叠方向
|
||||||
|
resources: 资源列表
|
||||||
|
max_sheets: 最大极片数量
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
direction=direction,
|
||||||
|
resources=resources,
|
||||||
|
)
|
||||||
|
self.max_sheets = max_sheets
|
||||||
|
|
||||||
|
@property
|
||||||
|
def size_x(self) -> float:
|
||||||
|
return self.get_size_x()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def size_y(self) -> float:
|
||||||
|
return self.get_size_y()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def size_z(self) -> float:
|
||||||
|
return self.get_size_z()
|
||||||
|
|
||||||
|
def serialize(self) -> dict:
|
||||||
|
return {
|
||||||
|
**super().serialize(),
|
||||||
|
"size_x": self.size_x or 10.0,
|
||||||
|
"size_y": self.size_y or 10.0,
|
||||||
|
"size_z": self.size_z or 10.0,
|
||||||
|
"max_sheets": self.max_sheets,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MagazineHolder(ItemizedResource):
|
||||||
|
"""子弹夹类 - 有多个洞位,每个洞位放多个极片"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
size_x: float,
|
||||||
|
size_y: float,
|
||||||
|
size_z: float,
|
||||||
|
ordered_items: Optional[Dict[str, Magazine]] = None,
|
||||||
|
ordering: Optional[OrderedDict[str, str]] = None,
|
||||||
|
hole_diameter: float = 14.0,
|
||||||
|
hole_depth: float = 10.0,
|
||||||
|
max_sheets_per_hole: int = 100,
|
||||||
|
cross_section_type: str = "circle",
|
||||||
|
category: str = "magazine_holder",
|
||||||
|
model: Optional[str] = None,
|
||||||
|
):
|
||||||
|
"""初始化子弹夹
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 子弹夹名称
|
||||||
|
size_x: 长度 (mm)
|
||||||
|
size_y: 宽度 (mm)
|
||||||
|
size_z: 高度 (mm)
|
||||||
|
hole_diameter: 洞直径 (mm)
|
||||||
|
hole_depth: 洞深度 (mm)
|
||||||
|
max_sheets_per_hole: 每个洞位最大极片数量
|
||||||
|
category: 类别
|
||||||
|
model: 型号
|
||||||
|
"""
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
size_x=size_x,
|
||||||
|
size_y=size_y,
|
||||||
|
size_z=size_z,
|
||||||
|
ordered_items=ordered_items,
|
||||||
|
ordering=ordering,
|
||||||
|
category=category,
|
||||||
|
model=model,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 保存洞位的直径和深度
|
||||||
|
self.hole_diameter = hole_diameter
|
||||||
|
self.hole_depth = hole_depth
|
||||||
|
self.max_sheets_per_hole = max_sheets_per_hole
|
||||||
|
self.cross_section_type = cross_section_type
|
||||||
|
|
||||||
|
def serialize(self) -> dict:
|
||||||
|
return {
|
||||||
|
**super().serialize(),
|
||||||
|
"hole_diameter": self.hole_diameter,
|
||||||
|
"hole_depth": self.hole_depth,
|
||||||
|
"max_sheets_per_hole": self.max_sheets_per_hole,
|
||||||
|
"cross_section_type": self.cross_section_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def magazine_factory(
|
||||||
|
name: str,
|
||||||
|
size_x: float,
|
||||||
|
size_y: float,
|
||||||
|
size_z: float,
|
||||||
|
locations: List[Coordinate],
|
||||||
|
klasses: Optional[List[Callable[[str], str]]] = None,
|
||||||
|
hole_diameter: float = 14.0,
|
||||||
|
hole_depth: float = 10.0,
|
||||||
|
max_sheets_per_hole: int = 100,
|
||||||
|
category: str = "magazine_holder",
|
||||||
|
model: Optional[str] = None,
|
||||||
|
) -> 'MagazineHolder':
|
||||||
|
"""工厂函数:创建子弹夹
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 子弹夹名称
|
||||||
|
size_x: 长度 (mm)
|
||||||
|
size_y: 宽度 (mm)
|
||||||
|
size_z: 高度 (mm)
|
||||||
|
locations: 洞位坐标列表
|
||||||
|
klasses: 每个洞位中极片的类列表
|
||||||
|
hole_diameter: 洞直径 (mm)
|
||||||
|
hole_depth: 洞深度 (mm)
|
||||||
|
max_sheets_per_hole: 每个洞位最大极片数量
|
||||||
|
category: 类别
|
||||||
|
model: 型号
|
||||||
|
"""
|
||||||
|
for loc in locations:
|
||||||
|
loc.x -= hole_diameter / 2
|
||||||
|
loc.y -= hole_diameter / 2
|
||||||
|
|
||||||
|
# 创建洞位
|
||||||
|
_sites = create_homogeneous_resources(
|
||||||
|
klass=Magazine,
|
||||||
|
locations=locations,
|
||||||
|
resource_size_x=hole_diameter,
|
||||||
|
resource_size_y=hole_diameter,
|
||||||
|
name_prefix=name,
|
||||||
|
max_sheets=max_sheets_per_hole,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 生成编号键
|
||||||
|
keys = [f"A{i+1}" for i in range(len(locations))]
|
||||||
|
sites = dict(zip(keys, _sites.values()))
|
||||||
|
|
||||||
|
holder = MagazineHolder(
|
||||||
|
name=name,
|
||||||
|
size_x=size_x,
|
||||||
|
size_y=size_y,
|
||||||
|
size_z=size_z,
|
||||||
|
ordered_items=sites,
|
||||||
|
hole_diameter=hole_diameter,
|
||||||
|
hole_depth=hole_depth,
|
||||||
|
max_sheets_per_hole=max_sheets_per_hole,
|
||||||
|
category=category,
|
||||||
|
model=model,
|
||||||
|
)
|
||||||
|
|
||||||
|
if klasses is not None:
|
||||||
|
for i, klass in enumerate(klasses):
|
||||||
|
hole_key = keys[i]
|
||||||
|
hole = holder.children[i]
|
||||||
|
for j in reversed(range(max_sheets_per_hole)):
|
||||||
|
item_name = f"{hole_key}_sheet{j+1}"
|
||||||
|
item = klass(name=item_name)
|
||||||
|
hole.assign_child_resource(item)
|
||||||
|
return holder
|
||||||
|
|
||||||
|
|
||||||
|
def MagazineHolder_6_Cathode(
|
||||||
|
name: str,
|
||||||
|
size_x: float = 80.0,
|
||||||
|
size_y: float = 80.0,
|
||||||
|
size_z: float = 40.0,
|
||||||
|
hole_diameter: float = 14.0,
|
||||||
|
hole_depth: float = 10.0,
|
||||||
|
hole_spacing: float = 20.0,
|
||||||
|
max_sheets_per_hole: int = 100,
|
||||||
|
) -> MagazineHolder:
|
||||||
|
"""创建6孔子弹夹 - 六边形排布"""
|
||||||
|
center_x = size_x / 2
|
||||||
|
center_y = size_y / 2
|
||||||
|
|
||||||
|
locations = []
|
||||||
|
|
||||||
|
# 周围6个孔,按六边形排布
|
||||||
|
for i in range(6):
|
||||||
|
angle = i * 60 * math.pi / 180 # 每60度一个孔
|
||||||
|
x = center_x + hole_spacing * math.cos(angle)
|
||||||
|
y = center_y + hole_spacing * math.sin(angle)
|
||||||
|
locations.append(Coordinate(x, y, size_z - hole_depth))
|
||||||
|
|
||||||
|
return magazine_factory(
|
||||||
|
name=name,
|
||||||
|
size_x=size_x,
|
||||||
|
size_y=size_y,
|
||||||
|
size_z=size_z,
|
||||||
|
locations=locations,
|
||||||
|
klasses=[FlatWasher, PositiveCan, PositiveCan, FlatWasher, PositiveCan, PositiveCan],
|
||||||
|
hole_diameter=hole_diameter,
|
||||||
|
hole_depth=hole_depth,
|
||||||
|
max_sheets_per_hole=max_sheets_per_hole,
|
||||||
|
category="magazine_holder",
|
||||||
|
model="MagazineHolder_6_Cathode",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def MagazineHolder_6_Anode(
|
||||||
|
name: str,
|
||||||
|
size_x: float = 80.0,
|
||||||
|
size_y: float = 80.0,
|
||||||
|
size_z: float = 40.0,
|
||||||
|
hole_diameter: float = 14.0,
|
||||||
|
hole_depth: float = 10.0,
|
||||||
|
hole_spacing: float = 20.0,
|
||||||
|
max_sheets_per_hole: int = 100,
|
||||||
|
) -> MagazineHolder:
|
||||||
|
"""创建6孔子弹夹 - 六边形排布"""
|
||||||
|
center_x = size_x / 2
|
||||||
|
center_y = size_y / 2
|
||||||
|
|
||||||
|
locations = []
|
||||||
|
|
||||||
|
# 周围6个孔,按六边形排布
|
||||||
|
for i in range(6):
|
||||||
|
angle = i * 60 * math.pi / 180 # 每60度一个孔
|
||||||
|
x = center_x + hole_spacing * math.cos(angle)
|
||||||
|
y = center_y + hole_spacing * math.sin(angle)
|
||||||
|
locations.append(Coordinate(x, y, size_z - hole_depth))
|
||||||
|
|
||||||
|
return magazine_factory(
|
||||||
|
name=name,
|
||||||
|
size_x=size_x,
|
||||||
|
size_y=size_y,
|
||||||
|
size_z=size_z,
|
||||||
|
locations=locations,
|
||||||
|
klasses=[SpringWasher, NegativeCan, NegativeCan, SpringWasher, NegativeCan, NegativeCan],
|
||||||
|
hole_diameter=hole_diameter,
|
||||||
|
hole_depth=hole_depth,
|
||||||
|
max_sheets_per_hole=max_sheets_per_hole,
|
||||||
|
category="magazine_holder",
|
||||||
|
model="MagazineHolder_6_Anode",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def MagazineHolder_6_Battery(
|
||||||
|
name: str,
|
||||||
|
size_x: float = 80.0,
|
||||||
|
size_y: float = 80.0,
|
||||||
|
size_z: float = 40.0,
|
||||||
|
hole_diameter: float = 14.0,
|
||||||
|
hole_depth: float = 10.0,
|
||||||
|
hole_spacing: float = 20.0,
|
||||||
|
max_sheets_per_hole: int = 100,
|
||||||
|
) -> MagazineHolder:
|
||||||
|
"""创建6孔子弹夹 - 六边形排布"""
|
||||||
|
center_x = size_x / 2
|
||||||
|
center_y = size_y / 2
|
||||||
|
|
||||||
|
locations = []
|
||||||
|
|
||||||
|
# 周围6个孔,按六边形排布
|
||||||
|
for i in range(6):
|
||||||
|
angle = i * 60 * math.pi / 180 # 每60度一个孔
|
||||||
|
x = center_x + hole_spacing * math.cos(angle)
|
||||||
|
y = center_y + hole_spacing * math.sin(angle)
|
||||||
|
locations.append(Coordinate(x, y, size_z - hole_depth))
|
||||||
|
|
||||||
|
return magazine_factory(
|
||||||
|
name=name,
|
||||||
|
size_x=size_x,
|
||||||
|
size_y=size_y,
|
||||||
|
size_z=size_z,
|
||||||
|
locations=locations,
|
||||||
|
klasses=None, # 初始化时,不放入装好的电池
|
||||||
|
hole_diameter=hole_diameter,
|
||||||
|
hole_depth=hole_depth,
|
||||||
|
max_sheets_per_hole=max_sheets_per_hole,
|
||||||
|
category="magazine_holder",
|
||||||
|
model="MagazineHolder_6_Battery",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def MagazineHolder_4_Cathode(
|
||||||
|
name: str,
|
||||||
|
) -> MagazineHolder:
|
||||||
|
"""创建4孔子弹夹 - 正方形四角排布"""
|
||||||
|
size_x: float = 80.0
|
||||||
|
size_y: float = 80.0
|
||||||
|
size_z: float = 10.0
|
||||||
|
hole_diameter: float = 14.0
|
||||||
|
hole_depth: float = 10.0
|
||||||
|
hole_spacing: float = 25.0
|
||||||
|
max_sheets_per_hole: int = 100
|
||||||
|
|
||||||
|
# 计算4个洞位的坐标(正方形四角排布)
|
||||||
|
center_x = size_x / 2
|
||||||
|
center_y = size_y / 2
|
||||||
|
offset = hole_spacing / 2
|
||||||
|
|
||||||
|
locations = [
|
||||||
|
Coordinate(center_x - offset, center_y - offset, size_z - hole_depth), # 左下
|
||||||
|
Coordinate(center_x + offset, center_y - offset, size_z - hole_depth), # 右下
|
||||||
|
Coordinate(center_x - offset, center_y + offset, size_z - hole_depth), # 左上
|
||||||
|
Coordinate(center_x + offset, center_y + offset, size_z - hole_depth), # 右上
|
||||||
|
]
|
||||||
|
|
||||||
|
return magazine_factory(
|
||||||
|
name=name,
|
||||||
|
size_x=size_x,
|
||||||
|
size_y=size_y,
|
||||||
|
size_z=size_z,
|
||||||
|
locations=locations,
|
||||||
|
klasses=[AluminumFoil, PositiveElectrode, PositiveElectrode, PositiveElectrode],
|
||||||
|
hole_diameter=hole_diameter,
|
||||||
|
hole_depth=hole_depth,
|
||||||
|
max_sheets_per_hole=max_sheets_per_hole,
|
||||||
|
category="magazine_holder",
|
||||||
|
model="MagazineHolder_4_Cathode",
|
||||||
|
)
|
||||||
@@ -206,7 +206,7 @@ def YB_6VialCarrier(name: str) -> BottleCarrier:
|
|||||||
return carrier
|
return carrier
|
||||||
|
|
||||||
# 1瓶载架 - 单个中央位置
|
# 1瓶载架 - 单个中央位置
|
||||||
def YB_1BottleCarrier(name: str) -> BottleCarrier:
|
def YB_ye(name: str) -> BottleCarrier:
|
||||||
|
|
||||||
# 载架尺寸 (mm)
|
# 载架尺寸 (mm)
|
||||||
carrier_size_x = 127.8
|
carrier_size_x = 127.8
|
||||||
@@ -233,7 +233,7 @@ def YB_1BottleCarrier(name: str) -> BottleCarrier:
|
|||||||
resource_size_y=beaker_diameter,
|
resource_size_y=beaker_diameter,
|
||||||
name_prefix=name,
|
name_prefix=name,
|
||||||
),
|
),
|
||||||
model="YB_1BottleCarrier",
|
model="YB_ye",
|
||||||
)
|
)
|
||||||
carrier.num_items_x = 1
|
carrier.num_items_x = 1
|
||||||
carrier.num_items_y = 1
|
carrier.num_items_y = 1
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ def YB_ye_Bottle(
|
|||||||
height=height,
|
height=height,
|
||||||
max_volume=max_volume,
|
max_volume=max_volume,
|
||||||
barcode=barcode,
|
barcode=barcode,
|
||||||
model="Liquid_Bottle",
|
model="YB_ye_Bottle",
|
||||||
)
|
)
|
||||||
|
|
||||||
"""100ml液体"""
|
"""100ml液体"""
|
||||||
|
|||||||
@@ -89,23 +89,19 @@ class BIOYOND_YB_Deck(Deck):
|
|||||||
"自动堆栈-右": bioyond_warehouse_2x2x1("自动堆栈-右"),
|
"自动堆栈-右": bioyond_warehouse_2x2x1("自动堆栈-右"),
|
||||||
"手动堆栈-左": bioyond_warehouse_3x5x1("手动堆栈-左"),
|
"手动堆栈-左": bioyond_warehouse_3x5x1("手动堆栈-左"),
|
||||||
"手动堆栈-右": bioyond_warehouse_3x5x1("手动堆栈-右"),
|
"手动堆栈-右": bioyond_warehouse_3x5x1("手动堆栈-右"),
|
||||||
"粉末加样头堆栈-左": bioyond_warehouse_10x1x1("粉末加样头堆栈-左"),
|
"粉末加样头堆栈": bioyond_warehouse_20x1x1("粉末加样头堆栈"),
|
||||||
"粉末加样头堆栈-右": bioyond_warehouse_10x1x1("粉末加样头堆栈-右"),
|
|
||||||
"配液站内试剂仓库": bioyond_warehouse_3x3x1("配液站内试剂仓库"),
|
"配液站内试剂仓库": bioyond_warehouse_3x3x1("配液站内试剂仓库"),
|
||||||
"试剂替换仓库-左": bioyond_warehouse_5x1x1("试剂替换仓库-左"),
|
"试剂替换仓库": bioyond_warehouse_10x1x1("试剂替换仓库"),
|
||||||
"试剂替换仓库-右": bioyond_warehouse_5x1x1("试剂替换仓库-右"),
|
|
||||||
}
|
}
|
||||||
# warehouse 的位置
|
# warehouse 的位置
|
||||||
self.warehouse_locations = {
|
self.warehouse_locations = {
|
||||||
"自动堆栈-左": Coordinate(-300.0, 158.0, 0.0),
|
"自动堆栈-左": Coordinate(-100.3, 171.5, 0.0),
|
||||||
"自动堆栈-右": Coordinate(4160.0, 158.0, 0.0),
|
"自动堆栈-右": Coordinate(3960.1, 155.9, 0.0),
|
||||||
"手动堆栈-左": Coordinate(-400.0, 877.0, 0.0),
|
"手动堆栈-左": Coordinate(-213.3, 804.4, 0.0),
|
||||||
"手动堆栈-右": Coordinate(4160.0, 877.0, 0.0),
|
"手动堆栈-右": Coordinate(3960.1, 807.6, 0.0),
|
||||||
"粉末加样头堆栈-左": Coordinate(415.0, 1301.0, 0.0),
|
"粉末加样头堆栈": Coordinate(415.0, 1301.0, 0.0),
|
||||||
"粉末加样头堆栈-右": Coordinate(2200.0, 1304.0, 0.0),
|
"配液站内试剂仓库": Coordinate(2162.0, 437.0, 0.0),
|
||||||
"配液站内试剂仓库": Coordinate(2162.0, 337.0, 0.0),
|
"试剂替换仓库": Coordinate(1173.0, 802.0, 0.0),
|
||||||
"试剂替换仓库-左": Coordinate(1173.0, 702.0, 0.0),
|
|
||||||
"试剂替换仓库-右": Coordinate(2721.0, 739.0, 0.0),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for warehouse_name, warehouse in self.warehouses.items():
|
for warehouse_name, warehouse in self.warehouses.items():
|
||||||
|
|||||||
@@ -674,10 +674,15 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: Dict[st
|
|||||||
for loc in material.get("locations", []):
|
for loc in material.get("locations", []):
|
||||||
if hasattr(deck, "warehouses") and loc.get("whName") in deck.warehouses:
|
if hasattr(deck, "warehouses") and loc.get("whName") in deck.warehouses:
|
||||||
warehouse = deck.warehouses[loc["whName"]]
|
warehouse = deck.warehouses[loc["whName"]]
|
||||||
|
num_x = getattr(warehouse, "num_items_x", 0) or 0
|
||||||
|
num_y = getattr(warehouse, "num_items_y", 0) or 0
|
||||||
|
num_z = getattr(warehouse, "num_items_z", 0) or 0
|
||||||
|
if num_x <= 0 or num_y <= 0 or num_z <= 0:
|
||||||
|
continue
|
||||||
idx = (
|
idx = (
|
||||||
(loc.get("y", 0) - 1) * warehouse.num_items_x * warehouse.num_items_y
|
(loc.get("z", 0) - 1) * num_x * num_y
|
||||||
+ (loc.get("x", 0) - 1) * warehouse.num_items_x
|
+ (loc.get("y", 0) - 1) * num_x
|
||||||
+ (loc.get("z", 0) - 1)
|
+ (loc.get("x", 0) - 1)
|
||||||
)
|
)
|
||||||
if 0 <= idx < warehouse.capacity:
|
if 0 <= idx < warehouse.capacity:
|
||||||
if warehouse[idx] is None or isinstance(warehouse[idx], ResourceHolder):
|
if warehouse[idx] is None or isinstance(warehouse[idx], ResourceHolder):
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class Bottle(Well):
|
|||||||
size_x: float = 0.0,
|
size_x: float = 0.0,
|
||||||
size_y: float = 0.0,
|
size_y: float = 0.0,
|
||||||
size_z: float = 0.0,
|
size_z: float = 0.0,
|
||||||
barcode: Optional[str] = "",
|
barcode: Optional[str] = None,
|
||||||
category: str = "container",
|
category: str = "container",
|
||||||
model: Optional[str] = None,
|
model: Optional[str] = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
|||||||
@@ -402,7 +402,6 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
|||||||
|
|
||||||
return result_future.result
|
return result_future.result
|
||||||
|
|
||||||
"""还没有改过的部分"""
|
|
||||||
|
|
||||||
def _setup_hardware_proxy(
|
def _setup_hardware_proxy(
|
||||||
self, device: ROS2DeviceNode, communication_device: ROS2DeviceNode, read_method, write_method
|
self, device: ROS2DeviceNode, communication_device: ROS2DeviceNode, read_method, write_method
|
||||||
|
|||||||
Reference in New Issue
Block a user