mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 13:25:13 +00:00
Compare commits
163 Commits
ead43b2bc1
...
workstatio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f355722281 | ||
|
|
936834f8c3 | ||
|
|
915a6a04c3 | ||
|
|
48b51c3a4a | ||
|
|
acef0b8ca2 | ||
|
|
d2a30fe33b | ||
|
|
096875e910 | ||
|
|
2e17dee121 | ||
|
|
c03abb341a | ||
|
|
ee4ed26846 | ||
|
|
b97be6a5d4 | ||
|
|
44f830cf00 | ||
|
|
04b578a68b | ||
|
|
19dffcb5db | ||
|
|
b441362cd2 | ||
|
|
ed53ef2f64 | ||
|
|
0c9f26e8fc | ||
|
|
39a799cabd | ||
|
|
0d64563fb6 | ||
|
|
fbb9e0963d | ||
|
|
af411ddfe6 | ||
|
|
f5dbcb1bfc | ||
|
|
1ecf89ea27 | ||
|
|
6efdf6e5a6 | ||
|
|
e32dc55db0 | ||
|
|
acc45b716d | ||
|
|
017eaefb8d | ||
|
|
9e8c692702 | ||
|
|
beb90f20d2 | ||
|
|
7a284069d2 | ||
|
|
4a2d862333 | ||
|
|
538891fcbe | ||
|
|
a0e92b8e9b | ||
|
|
1d77225912 | ||
|
|
06e6ab0b7f | ||
|
|
5399c6c1cf | ||
|
|
f872d3ef56 | ||
|
|
85c6f4e688 | ||
|
|
442b759397 | ||
|
|
47ecb154c8 | ||
|
|
be429147c0 | ||
|
|
123c69e97a | ||
|
|
04004c9b6f | ||
|
|
45a778b928 | ||
|
|
c44ae32070 | ||
|
|
7af32b379b | ||
|
|
48d429ae00 | ||
|
|
9bba4620b7 | ||
|
|
d7494ca458 | ||
|
|
85dc46cd38 | ||
|
|
5a0c2f9850 | ||
|
|
d897d70c3e | ||
|
|
d9dffc6bf8 | ||
|
|
30b202bea0 | ||
|
|
1b2c0dbcd7 | ||
|
|
0f341e9b4d | ||
|
|
4c3972820b | ||
|
|
a2a8ee9088 | ||
|
|
200105f647 | ||
|
|
8b5653d801 | ||
|
|
5f859917d4 | ||
|
|
af2fb7f34a | ||
|
|
baa107c230 | ||
|
|
83854a741d | ||
|
|
86c7880b5c | ||
|
|
6d934e354c | ||
|
|
bed453034f | ||
|
|
5331d7bfba | ||
|
|
38ab7d3e78 | ||
|
|
966b51042d | ||
|
|
d81638e20b | ||
|
|
3c583008aa | ||
|
|
9a85bfddcd | ||
|
|
d4e1286df7 | ||
|
|
765038a136 | ||
|
|
1d4e4c8377 | ||
|
|
54f749bcdb | ||
|
|
16ad4bbecc | ||
|
|
0ad2eaafea | ||
|
|
1477384c1a | ||
|
|
8149a175d9 | ||
|
|
bfd415279b | ||
|
|
0238a92e75 | ||
|
|
8009956326 | ||
|
|
68fc4dd61e | ||
|
|
cd12932788 | ||
|
|
f230028558 | ||
|
|
1c1a6b16c8 | ||
|
|
a2d6012080 | ||
|
|
10adc853a5 | ||
|
|
69ec034623 | ||
|
|
62d08aa954 | ||
|
|
4485907df8 | ||
|
|
b5b2358967 | ||
|
|
11f4f44bf9 | ||
|
|
f52fbd650e | ||
|
|
e561c818b8 | ||
|
|
5cbd880e5a | ||
|
|
41e7251f62 | ||
|
|
727d2c2595 | ||
|
|
202a2667fd | ||
|
|
03745c5d08 | ||
|
|
385a495e21 | ||
|
|
91513a5f4c | ||
|
|
a62896eda2 | ||
|
|
a82d1b7bdb | ||
|
|
6d7c39da9e | ||
|
|
d8e9ad4413 | ||
|
|
eb93b83415 | ||
|
|
6df93a5db7 | ||
|
|
2eb9986edb | ||
|
|
fe4e49e56d | ||
|
|
0fba4cf275 | ||
|
|
ef9359776a | ||
|
|
954f1ee7b2 | ||
|
|
f58921ef82 | ||
|
|
95bdd39bf8 | ||
|
|
b3e28196c6 | ||
|
|
9fe8f4f28f | ||
|
|
39bc317bfc | ||
|
|
a130c03ebd | ||
|
|
a97781c4eb | ||
|
|
c35edcece1 | ||
|
|
524e0f3053 | ||
|
|
66f483929d | ||
|
|
2d58576937 | ||
|
|
ff25e814de | ||
|
|
0163d16cbb | ||
|
|
3231d60646 | ||
|
|
d0279f63f0 | ||
|
|
ceef342860 | ||
|
|
42f7010134 | ||
|
|
190b2d2518 | ||
|
|
2901d72b4b | ||
|
|
6ad0157b50 | ||
|
|
55b678cd37 | ||
|
|
8101a22a0f | ||
|
|
667138baac | ||
|
|
01adf7ca92 | ||
|
|
f606062696 | ||
|
|
67d1c4acce | ||
|
|
7206e42bf1 | ||
|
|
e92d933968 | ||
|
|
f0ebcc60bb | ||
|
|
e2097f0b22 | ||
|
|
fd73731130 | ||
|
|
ab7f2081c9 | ||
|
|
9e850d8a81 | ||
|
|
1af6ffafc6 | ||
|
|
35fc2f5ea6 | ||
|
|
d3d8ba6500 | ||
|
|
5a7845d8ca | ||
|
|
9c4d0256cf | ||
|
|
de7c80c3c2 | ||
|
|
e70c545ec8 | ||
|
|
2c2d1e5569 | ||
|
|
4638611fe7 | ||
|
|
37641c4389 | ||
|
|
ab697ce973 | ||
|
|
d4724b8664 | ||
|
|
2f25063bf1 | ||
|
|
00b4b9cd87 | ||
|
|
d2352cc514 |
@@ -1,31 +1,32 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "BatteryStation",
|
||||
"name": "扣电工作站",
|
||||
"id": "bioyond_cell_workstation",
|
||||
"name": "配液分液工站",
|
||||
"children": [
|
||||
"coin_cell_deck"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "bettery_station_registry",
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
"class": "bioyond_cell",
|
||||
"config": {
|
||||
"protocol_type": [],
|
||||
"station_resource": {}
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "BatteryStation",
|
||||
"name": "扣电组装工作站",
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "bettery_station_registry",
|
||||
"config": {
|
||||
"debug_mode": false,
|
||||
"_comment": "protocol_type接外部工站固定写法字段,一般为空,deck写法也固定",
|
||||
|
||||
"protocol_type": [],
|
||||
"deck": {
|
||||
"data": {
|
||||
"_resource_child_name": "coin_cell_deck",
|
||||
"_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck"
|
||||
}
|
||||
},
|
||||
|
||||
"address": "192.168.1.20",
|
||||
"deck": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck",
|
||||
"address": "172.21.32.20",
|
||||
"port": 502
|
||||
},
|
||||
"data": {}
|
||||
@@ -98,7 +99,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazine_four",
|
||||
"type": "MagazineHolder_4",
|
||||
"size_x": 80,
|
||||
"size_y": 80,
|
||||
"size_z": 10,
|
||||
@@ -139,7 +140,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -234,7 +235,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -329,7 +330,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -424,7 +425,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -522,7 +523,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazine_four",
|
||||
"type": "MagazineHolder_4",
|
||||
"size_x": 80,
|
||||
"size_y": 80,
|
||||
"size_z": 10,
|
||||
@@ -563,7 +564,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -658,7 +659,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -753,7 +754,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -848,7 +849,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -948,7 +949,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazine",
|
||||
"type": "MagazineHolder_6",
|
||||
"size_x": 80,
|
||||
"size_y": 80,
|
||||
"size_z": 10,
|
||||
@@ -991,7 +992,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -1086,7 +1087,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -1181,7 +1182,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -1276,7 +1277,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -1371,7 +1372,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -1466,7 +1467,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -1566,7 +1567,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazine",
|
||||
"type": "MagazineHolder_6",
|
||||
"size_x": 80,
|
||||
"size_y": 80,
|
||||
"size_z": 10,
|
||||
@@ -1609,7 +1610,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -1704,7 +1705,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -1799,7 +1800,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -1894,7 +1895,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -1989,7 +1990,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -2084,7 +2085,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -2184,7 +2185,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazine",
|
||||
"type": "MagazineHolder_6",
|
||||
"size_x": 80,
|
||||
"size_y": 80,
|
||||
"size_z": 10,
|
||||
@@ -2227,7 +2228,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -2322,7 +2323,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -2417,7 +2418,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -2512,7 +2513,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -2607,7 +2608,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -2702,7 +2703,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -2802,7 +2803,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazine",
|
||||
"type": "MagazineHolder_6",
|
||||
"size_x": 80,
|
||||
"size_y": 80,
|
||||
"size_z": 10,
|
||||
@@ -2845,7 +2846,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -2940,7 +2941,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -3035,7 +3036,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -3130,7 +3131,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -3225,7 +3226,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -3320,7 +3321,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -3420,7 +3421,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazine",
|
||||
"type": "MagazineHolder_6",
|
||||
"size_x": 80,
|
||||
"size_y": 80,
|
||||
"size_z": 10,
|
||||
@@ -3463,7 +3464,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -3558,7 +3559,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -3653,7 +3654,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -3748,7 +3749,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -3843,7 +3844,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -3938,7 +3939,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -4038,7 +4039,7 @@
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazine",
|
||||
"type": "MagazineHolder_6",
|
||||
"size_x": 80,
|
||||
"size_y": 80,
|
||||
"size_z": 10,
|
||||
@@ -4081,7 +4082,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -4176,7 +4177,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -4271,7 +4272,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -4366,7 +4367,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -4461,7 +4462,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
@@ -4556,7 +4557,7 @@
|
||||
"z": 10
|
||||
},
|
||||
"config": {
|
||||
"type": "ClipMagazineHole",
|
||||
"type": "Magazine",
|
||||
"size_x": 14.0,
|
||||
"size_y": 14.0,
|
||||
"size_z": 10.0,
|
||||
2521
button_battery_station_resources_unilab.json
Normal file
2521
button_battery_station_resources_unilab.json
Normal file
File diff suppressed because it is too large
Load Diff
32
fix_datatype.py
Normal file
32
fix_datatype.py
Normal file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
|
||||
filepath = r'd:\UniLab\Uni-Lab-OS\unilabos\device_comms\modbus_plc\modbus.py'
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Replace the DataType placeholder with actual enum
|
||||
find_pattern = r'# DataType will be accessed via client instance.*?DataType = None # Placeholder.*?\n'
|
||||
replacement = '''# Define DataType enum for pymodbus 2.5.3 compatibility
|
||||
class DataType(Enum):
|
||||
INT16 = "int16"
|
||||
UINT16 = "uint16"
|
||||
INT32 = "int32"
|
||||
UINT32 = "uint32"
|
||||
INT64 = "int64"
|
||||
UINT64 = "uint64"
|
||||
FLOAT32 = "float32"
|
||||
FLOAT64 = "float64"
|
||||
STRING = "string"
|
||||
BOOL = "bool"
|
||||
|
||||
'''
|
||||
|
||||
new_content = re.sub(find_pattern, replacement, content, flags=re.DOTALL)
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
|
||||
print('File updated successfully!')
|
||||
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": []
|
||||
}
|
||||
98
new_cellconfig3c.json
Normal file
98
new_cellconfig3c.json
Normal file
@@ -0,0 +1,98 @@
|
||||
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "bioyond_cell_workstation",
|
||||
"name": "配液分液工站",
|
||||
"parent": null,
|
||||
"children": [
|
||||
"YB_Bioyond_Deck"
|
||||
],
|
||||
"type": "device",
|
||||
"class": "bioyond_cell",
|
||||
"config": {
|
||||
"deck": {
|
||||
"data": {
|
||||
"_resource_child_name": "YB_Bioyond_Deck",
|
||||
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_YB_Deck"
|
||||
}
|
||||
},
|
||||
"protocol_type": []
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "YB_Bioyond_Deck",
|
||||
"name": "YB_Bioyond_Deck",
|
||||
"children": [],
|
||||
"parent": "bioyond_cell_workstation",
|
||||
"type": "deck",
|
||||
"class": "BIOYOND_YB_Deck",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "BIOYOND_YB_Deck",
|
||||
"setup": true,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
}
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "BatteryStation",
|
||||
"name": "扣电工作站",
|
||||
"parent": null,
|
||||
"children": [
|
||||
"coin_cell_deck"
|
||||
],
|
||||
"type": "device",
|
||||
"class":"coincellassemblyworkstation_device",
|
||||
"config": {
|
||||
"deck": {
|
||||
"data": {
|
||||
"_resource_child_name": "YB_YH_Deck",
|
||||
"_resource_type": "unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:CoincellDeck"
|
||||
}
|
||||
},
|
||||
"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": {}
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
|
||||
|
||||
72
test/experiments/reaction_station_bioyond.json
Normal file
72
test/experiments/reaction_station_bioyond.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "reaction_station_bioyond",
|
||||
"name": "reaction_station_bioyond",
|
||||
"parent": null,
|
||||
"children": [
|
||||
"Bioyond_Deck"
|
||||
],
|
||||
"type": "device",
|
||||
"class": "reaction_station.bioyond",
|
||||
"config": {
|
||||
"config": {
|
||||
"api_key": "DE9BDDA0",
|
||||
"api_host": "http://192.168.1.200:44402",
|
||||
"workflow_mappings": {
|
||||
"reactor_taken_out": "3a16081e-4788-ca37-eff4-ceed8d7019d1",
|
||||
"reactor_taken_in": "3a160df6-76b3-0957-9eb0-cb496d5721c6",
|
||||
"Solid_feeding_vials": "3a160877-87e7-7699-7bc6-ec72b05eb5e6",
|
||||
"Liquid_feeding_vials(non-titration)": "3a167d99-6158-c6f0-15b5-eb030f7d8e47",
|
||||
"Liquid_feeding_solvents": "3a160824-0665-01ed-285a-51ef817a9046",
|
||||
"Liquid_feeding(titration)": "3a16082a-96ac-0449-446a-4ed39f3365b6",
|
||||
"liquid_feeding_beaker": "3a16087e-124f-8ddb-8ec1-c2dff09ca784",
|
||||
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
|
||||
},
|
||||
"material_type_mappings": {
|
||||
"烧杯": ["YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"],
|
||||
"试剂瓶": ["YB_1BottleCarrier", ""],
|
||||
"样品板": ["YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"],
|
||||
"分装板": ["YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"],
|
||||
"样品瓶": ["YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"],
|
||||
"90%分装小瓶": ["YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"],
|
||||
"10%分装小瓶": ["YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"]
|
||||
}
|
||||
},
|
||||
"deck": {
|
||||
"data": {
|
||||
"_resource_child_name": "Bioyond_Deck",
|
||||
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerReactionStation_Deck"
|
||||
}
|
||||
},
|
||||
"protocol_type": []
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "Bioyond_Deck",
|
||||
"name": "Bioyond_Deck",
|
||||
"children": [
|
||||
],
|
||||
"parent": "reaction_station_bioyond",
|
||||
"type": "deck",
|
||||
"class": "BIOYOND_PolymerReactionStation_Deck",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "BIOYOND_PolymerReactionStation_Deck",
|
||||
"setup": true,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
}
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
52
test/resources/YB_materials_info.json
Normal file
52
test/resources/YB_materials_info.json
Normal file
@@ -0,0 +1,52 @@
|
||||
[
|
||||
{
|
||||
"id": "3a1d377b-299d-d0f2-ced9-48257f60dfad",
|
||||
"typeName": "加样头(大)",
|
||||
"code": "0005-00145",
|
||||
"barCode": "",
|
||||
"name": "LiDFOB",
|
||||
"quantity": 9999.0,
|
||||
"lockQuantity": 0.0,
|
||||
"unit": "个",
|
||||
"status": 1,
|
||||
"isUse": false,
|
||||
"locations": [
|
||||
{
|
||||
"id": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
||||
"whid": "3a19da56-1378-613b-29f2-871e1a287aa5",
|
||||
"whName": "粉末加样头堆栈",
|
||||
"code": "0005-0001",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1,
|
||||
"quantity": 0
|
||||
}
|
||||
],
|
||||
"detail": []
|
||||
},
|
||||
{
|
||||
"id": "3a1d377b-6a81-6a7e-147c-f89f6463656d",
|
||||
"typeName": "液",
|
||||
"code": "0006-00141",
|
||||
"barCode": "",
|
||||
"name": "EMC",
|
||||
"quantity": 99999.0,
|
||||
"lockQuantity": 0.0,
|
||||
"unit": "g",
|
||||
"status": 1,
|
||||
"isUse": false,
|
||||
"locations": [
|
||||
{
|
||||
"id": "3a1baa20-a7b1-c665-8b9c-d8099d07d2f6",
|
||||
"whid": "3a1baa20-a7b0-5c19-8844-5de8924d4e78",
|
||||
"whName": "4号手套箱内部堆栈",
|
||||
"code": "0015-0001",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1,
|
||||
"quantity": 0
|
||||
}
|
||||
],
|
||||
"detail": []
|
||||
}
|
||||
]
|
||||
99
test/resources/test copy.json
Normal file
99
test/resources/test copy.json
Normal file
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"typeId": "3a190c8b-3284-af78-d29f-9a69463ad047",
|
||||
"code": "",
|
||||
"barCode": "",
|
||||
"name": "test",
|
||||
"unit": "",
|
||||
"parameters": "{}",
|
||||
"quantity": "",
|
||||
"details": [
|
||||
{
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
||||
"code": "",
|
||||
"name": "配液瓶(小)11",
|
||||
"quantity": "1",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1,
|
||||
"unit": "",
|
||||
"parameters": "{}"
|
||||
},
|
||||
{
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
||||
"code": "",
|
||||
"name": "配液瓶(小)21",
|
||||
"quantity": "1",
|
||||
"x": 2,
|
||||
"y": 1,
|
||||
"z": 1,
|
||||
"unit": "",
|
||||
"parameters": "{}"
|
||||
},
|
||||
{
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
||||
"code": "",
|
||||
"name": "配液瓶(小)12",
|
||||
"quantity": "1",
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"z": 1,
|
||||
"unit": "",
|
||||
"parameters": "{}"
|
||||
},
|
||||
{
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
||||
"code": "",
|
||||
"name": "配液瓶(小)22",
|
||||
"quantity": "1",
|
||||
"x": 2,
|
||||
"y": 2,
|
||||
"z": 1,
|
||||
"unit": "",
|
||||
"parameters": "{}"
|
||||
},
|
||||
{
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
||||
"code": "",
|
||||
"name": "配液瓶(小)13",
|
||||
"quantity": "1",
|
||||
"x": 1,
|
||||
"y": 3,
|
||||
"z": 1,
|
||||
"unit": "",
|
||||
"parameters": "{}"
|
||||
},
|
||||
{
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
||||
"code": "",
|
||||
"name": "配液瓶(小)23",
|
||||
"quantity": "1",
|
||||
"x": 2,
|
||||
"y": 3,
|
||||
"z": 1,
|
||||
"unit": "",
|
||||
"parameters": "{}"
|
||||
},
|
||||
{
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
||||
"code": "",
|
||||
"name": "配液瓶(小)14",
|
||||
"quantity": "1",
|
||||
"x": 1,
|
||||
"y": 4,
|
||||
"z": 1,
|
||||
"unit": "",
|
||||
"parameters": "{}"
|
||||
},
|
||||
{
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb",
|
||||
"code": "",
|
||||
"name": "配液瓶(小)24",
|
||||
"quantity": "1",
|
||||
"x": 2,
|
||||
"y": 4,
|
||||
"z": 1,
|
||||
"unit": "",
|
||||
"parameters": "{}"
|
||||
}
|
||||
]
|
||||
}
|
||||
148
test/resources/test.json
Normal file
148
test/resources/test.json
Normal file
@@ -0,0 +1,148 @@
|
||||
[
|
||||
{
|
||||
"id": "3a1d4c14-a9fb-d7dc-9e96-7a3ad6e50219",
|
||||
"typeName": "配液瓶(小)板",
|
||||
"code": "0001-00093",
|
||||
"barCode": "",
|
||||
"name": "test",
|
||||
"quantity": 2.0,
|
||||
"lockQuantity": 0.0,
|
||||
"unit": "块",
|
||||
"status": 1,
|
||||
"isUse": false,
|
||||
"locations": [
|
||||
{
|
||||
"id": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||
"whid": "3a19deae-2c79-05a3-9c76-8e6760424841",
|
||||
"whName": "手动堆栈",
|
||||
"code": "1",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1,
|
||||
"quantity": 0
|
||||
}
|
||||
],
|
||||
"detail": [
|
||||
{
|
||||
"id": "3a1d4c14-a9fc-1daa-71fa-146cb1ccb930",
|
||||
"detailMaterialId": "3a1d4c14-a9fc-4f38-4c48-68486c391c42",
|
||||
"code": "0001-00093 - 05",
|
||||
"name": "配液瓶(小)",
|
||||
"quantity": "1",
|
||||
"lockQuantity": "0",
|
||||
"unit": "个",
|
||||
"x": 1,
|
||||
"y": 3,
|
||||
"z": 1,
|
||||
"associateId": null,
|
||||
"typeName": "配液瓶(小)",
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
||||
},
|
||||
{
|
||||
"id": "3a1d4c14-a9fc-3659-ea61-cd587da9e131",
|
||||
"detailMaterialId": "3a1d4c14-a9fc-018f-93e5-c49343d37758",
|
||||
"code": "0001-00093 - 08",
|
||||
"name": "配液瓶(小)",
|
||||
"quantity": "1",
|
||||
"lockQuantity": "0",
|
||||
"unit": "个",
|
||||
"x": 2,
|
||||
"y": 4,
|
||||
"z": 1,
|
||||
"associateId": null,
|
||||
"typeName": "配液瓶(小)",
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
||||
},
|
||||
{
|
||||
"id": "3a1d4c14-a9fc-3f94-de83-979d2646e313",
|
||||
"detailMaterialId": "3a1d4c14-a9fc-9987-c0ef-4b7cbad49e6b",
|
||||
"code": "0001-00093 - 01",
|
||||
"name": "配液瓶(小)",
|
||||
"quantity": "1",
|
||||
"lockQuantity": "0",
|
||||
"unit": "个",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1,
|
||||
"associateId": null,
|
||||
"typeName": "配液瓶(小)",
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
||||
},
|
||||
{
|
||||
"id": "3a1d4c14-a9fc-8c35-6b25-913b11dbaf4e",
|
||||
"detailMaterialId": "3a1d4c14-a9fc-9a83-865b-0c26ea5e8cc4",
|
||||
"code": "0001-00093 - 03",
|
||||
"name": "配液瓶(小)",
|
||||
"quantity": "1",
|
||||
"lockQuantity": "0",
|
||||
"unit": "个",
|
||||
"x": 1,
|
||||
"y": 2,
|
||||
"z": 1,
|
||||
"associateId": null,
|
||||
"typeName": "配液瓶(小)",
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
||||
},
|
||||
{
|
||||
"id": "3a1d4c14-a9fc-b41f-e968-64953bfddccd",
|
||||
"detailMaterialId": "3a1d4c14-a9fc-daf7-9d64-e5ec8d3ae0e2",
|
||||
"code": "0001-00093 - 07",
|
||||
"name": "配液瓶(小)",
|
||||
"quantity": "1",
|
||||
"lockQuantity": "0",
|
||||
"unit": "个",
|
||||
"x": 1,
|
||||
"y": 4,
|
||||
"z": 1,
|
||||
"associateId": null,
|
||||
"typeName": "配液瓶(小)",
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
||||
},
|
||||
{
|
||||
"id": "3a1d4c14-a9fc-c20f-c26e-b1bb2cdc3bca",
|
||||
"detailMaterialId": "3a1d4c14-a9fc-673b-ac83-aaaf71287f1f",
|
||||
"code": "0001-00093 - 06",
|
||||
"name": "配液瓶(小)",
|
||||
"quantity": "1",
|
||||
"lockQuantity": "0",
|
||||
"unit": "个",
|
||||
"x": 2,
|
||||
"y": 3,
|
||||
"z": 1,
|
||||
"associateId": null,
|
||||
"typeName": "配液瓶(小)",
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
||||
},
|
||||
{
|
||||
"id": "3a1d4c14-a9fc-cf21-059c-fde361d82b6f",
|
||||
"detailMaterialId": "3a1d4c14-a9fc-25b1-e736-6b0d8dac0fae",
|
||||
"code": "0001-00093 - 02",
|
||||
"name": "配液瓶(小)",
|
||||
"quantity": "1",
|
||||
"lockQuantity": "0",
|
||||
"unit": "个",
|
||||
"x": 2,
|
||||
"y": 1,
|
||||
"z": 1,
|
||||
"associateId": null,
|
||||
"typeName": "配液瓶(小)",
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
||||
},
|
||||
{
|
||||
"id": "3a1d4c14-a9fc-d732-2b93-9b2bd2bf581b",
|
||||
"detailMaterialId": "3a1d4c14-a9fc-7f5d-b6b6-8bcb2e15f320",
|
||||
"code": "0001-00093 - 04",
|
||||
"name": "配液瓶(小)",
|
||||
"quantity": "1",
|
||||
"lockQuantity": "0",
|
||||
"unit": "个",
|
||||
"x": 2,
|
||||
"y": 2,
|
||||
"z": 1,
|
||||
"associateId": null,
|
||||
"typeName": "配液瓶(小)",
|
||||
"typeId": "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,7 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from unilabos.resources.bioyond.bottle_carriers import BIOYOND_Electrolyte_6VialCarrier, BIOYOND_Electrolyte_1BottleCarrier
|
||||
from unilabos.resources.bioyond.bottles import BIOYOND_PolymerStation_Solid_Vial, BIOYOND_PolymerStation_Solution_Beaker, BIOYOND_PolymerStation_Reagent_Bottle
|
||||
from unilabos.resources.bioyond.bottles import YB_Solid_Vial, YB_Solution_Beaker, YB_Reagent_Bottle
|
||||
|
||||
|
||||
def test_bottle_carrier() -> "BottleCarrier":
|
||||
@@ -16,9 +16,9 @@ def test_bottle_carrier() -> "BottleCarrier":
|
||||
print(f"1烧杯载架: {beaker_carrier.name}, 位置数: {len(beaker_carrier.sites)}")
|
||||
|
||||
# 创建瓶子和烧杯
|
||||
powder_bottle = BIOYOND_PolymerStation_Solid_Vial("powder_bottle_01")
|
||||
solution_beaker = BIOYOND_PolymerStation_Solution_Beaker("solution_beaker_01")
|
||||
reagent_bottle = BIOYOND_PolymerStation_Reagent_Bottle("reagent_bottle_01")
|
||||
powder_bottle = YB_Solid_Vial("powder_bottle_01")
|
||||
solution_beaker = YB_Solution_Beaker("solution_beaker_01")
|
||||
reagent_bottle = YB_Reagent_Bottle("reagent_bottle_01")
|
||||
|
||||
print(f"\n创建的物料:")
|
||||
print(f"粉末瓶: {powder_bottle.name} - {powder_bottle.diameter}mm x {powder_bottle.height}mm, {powder_bottle.max_volume}μL")
|
||||
|
||||
@@ -12,13 +12,13 @@ lab_registry.setup()
|
||||
|
||||
|
||||
type_mapping = {
|
||||
"烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
|
||||
"试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""),
|
||||
"样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
|
||||
"分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
|
||||
"样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
|
||||
"90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
|
||||
"10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
|
||||
"烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
|
||||
"试剂瓶": ("YB_1BottleCarrier", ""),
|
||||
"样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
|
||||
"分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
|
||||
"样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
|
||||
"90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
|
||||
"10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from ast import If
|
||||
import pytest
|
||||
import json
|
||||
import os
|
||||
@@ -8,18 +9,16 @@ from unilabos.ros.nodes.resource_tracker import ResourceTreeSet
|
||||
from unilabos.registry.registry import lab_registry
|
||||
|
||||
from unilabos.resources.bioyond.decks import BIOYOND_PolymerReactionStation_Deck
|
||||
from unilabos.resources.bioyond.decks import YB_Deck
|
||||
|
||||
lab_registry.setup()
|
||||
|
||||
|
||||
type_mapping = {
|
||||
"烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
|
||||
"试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""),
|
||||
"样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
|
||||
"分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
|
||||
"样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
|
||||
"90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
|
||||
"10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
|
||||
"加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
"液": ("YB_1BottleCarrier", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"),
|
||||
"配液瓶(小)板": ("YB_peiyepingxiaoban", "3a190c8b-3284-af78-d29f-9a69463ad047"),
|
||||
"配液瓶(小)": ("YB_pei_ye_xiao_Bottler", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"),
|
||||
}
|
||||
|
||||
|
||||
@@ -57,12 +56,20 @@ def bioyond_materials_liquidhandling_2() -> list[dict]:
|
||||
"bioyond_materials_reaction",
|
||||
"bioyond_materials_liquidhandling_1",
|
||||
])
|
||||
def test_resourcetreeset_from_plr(materials_fixture, request) -> list[dict]:
|
||||
materials = request.getfixturevalue(materials_fixture)
|
||||
deck = BIOYOND_PolymerReactionStation_Deck("test_deck")
|
||||
def test_resourcetreeset_from_plr() -> list[dict]:
|
||||
# 直接加载 bioyond_materials_reaction.json 文件
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
json_path = os.path.join(current_dir, "test.json")
|
||||
with open(json_path, "r", encoding="utf-8") as f:
|
||||
materials = json.load(f)
|
||||
deck = YB_Deck("test_deck")
|
||||
output = resource_bioyond_to_plr(materials, type_mapping=type_mapping, deck=deck)
|
||||
print(deck.summary())
|
||||
print(output)
|
||||
# print(deck.summary())
|
||||
|
||||
r = ResourceTreeSet.from_plr_resources([deck])
|
||||
print(r.dump())
|
||||
# json.dump(deck.serialize(), open("test.json", "w", encoding="utf-8"), indent=4)
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_resourcetreeset_from_plr()
|
||||
|
||||
@@ -421,7 +421,7 @@ class MessageProcessor:
|
||||
ssl_context = ssl_module.create_default_context()
|
||||
|
||||
ws_logger = logging.getLogger("websockets.client")
|
||||
ws_logger.setLevel(logging.INFO)
|
||||
# 日志级别已在 unilabos.utils.log 中统一配置为 WARNING
|
||||
|
||||
async with websockets.connect(
|
||||
self.websocket_url,
|
||||
@@ -1240,7 +1240,7 @@ class WebSocketClient(BaseCommunicationClient):
|
||||
},
|
||||
}
|
||||
self.message_processor.send_message(message)
|
||||
logger.debug(f"[WebSocketClient] Device status published: {device_id}.{property_name}")
|
||||
logger.trace(f"[WebSocketClient] Device status published: {device_id}.{property_name}")
|
||||
|
||||
def publish_job_status(
|
||||
self, feedback_data: dict, item: QueueItem, status: str, return_info: Optional[dict] = None
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import serial
|
||||
import time
|
||||
import csv
|
||||
import threading
|
||||
import os
|
||||
from collections import deque
|
||||
from typing import Dict, Any, Optional
|
||||
from pylabrobot.resources import Deck
|
||||
|
||||
from unilabos.devices.workstation.workstation_base import WorkstationBase
|
||||
|
||||
|
||||
class ElectrolysisWaterPlatform(WorkstationBase):
|
||||
"""
|
||||
电解水平台工作站
|
||||
基于 WorkstationBase 的电解水实验平台,支持串口通信和数据采集
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
deck: Deck,
|
||||
port: str = "COM10",
|
||||
baudrate: int = 115200,
|
||||
csv_path: Optional[str] = None,
|
||||
timeout: float = 0.2,
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(deck, **kwargs)
|
||||
|
||||
# ========== 配置 ==========
|
||||
self.port = port
|
||||
self.baudrate = baudrate
|
||||
# 如果没有指定路径,默认保存在代码文件所在目录
|
||||
if csv_path is None:
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
self.csv_path = os.path.join(current_dir, "stm32_data.csv")
|
||||
else:
|
||||
self.csv_path = csv_path
|
||||
self.ser_timeout = timeout
|
||||
self.chunk_read = 128
|
||||
|
||||
# 串口对象
|
||||
self.ser: Optional[serial.Serial] = None
|
||||
self.stop_flag = False
|
||||
|
||||
# 线程对象
|
||||
self.rx_thread: Optional[threading.Thread] = None
|
||||
self.tx_thread: Optional[threading.Thread] = None
|
||||
|
||||
# ==== 接收(下位机->上位机):固定 1+13+1 = 15 字节 ====
|
||||
self.RX_HEAD = 0x3E
|
||||
self.RX_TAIL = 0x3E
|
||||
self.RX_FRAME_LEN = 1 + 13 + 1 # 15
|
||||
|
||||
# ==== 发送(上位机->下位机):固定 1+9+1 = 11 字节 ====
|
||||
self.TX_HEAD = 0x3E
|
||||
self.TX_TAIL = 0xE3 # 协议图中标注 E3 作为帧尾
|
||||
self.TX_FRAME_LEN = 1 + 9 + 1 # 11
|
||||
|
||||
def open_serial(self, port: Optional[str] = None, baudrate: Optional[int] = None, timeout: Optional[float] = None) -> Optional[serial.Serial]:
|
||||
"""打开串口"""
|
||||
port = port or self.port
|
||||
baudrate = baudrate or self.baudrate
|
||||
timeout = timeout or self.ser_timeout
|
||||
try:
|
||||
ser = serial.Serial(port, baudrate, timeout=timeout)
|
||||
print(f"[OK] 串口 {port} 已打开,波特率 {baudrate}")
|
||||
ser.reset_input_buffer()
|
||||
ser.reset_output_buffer()
|
||||
self.ser = ser
|
||||
return ser
|
||||
except serial.SerialException as e:
|
||||
print(f"[ERR] 无法打开串口 {port}: {e}")
|
||||
return None
|
||||
|
||||
def close_serial(self):
|
||||
"""关闭串口"""
|
||||
if self.ser and self.ser.is_open:
|
||||
self.ser.close()
|
||||
print("[INFO] 串口已关闭")
|
||||
|
||||
@staticmethod
|
||||
def u16_be(h: int, l: int) -> int:
|
||||
"""将两个字节组合成16位无符号整数(大端序)"""
|
||||
return ((h & 0xFF) << 8) | (l & 0xFF)
|
||||
|
||||
@staticmethod
|
||||
def split_u16_be(val: int) -> tuple:
|
||||
"""返回 (高字节, 低字节),输入会夹到 0..65535"""
|
||||
v = int(max(0, min(65535, int(val))))
|
||||
return (v >> 8) & 0xFF, v & 0xFF
|
||||
|
||||
# ================== 接收:固定15字节 ==================
|
||||
def parse_rx_payload(self, dat13: bytes) -> Optional[Dict[str, Any]]:
|
||||
"""解析 13 字节数据区(下位机发送到上位机)"""
|
||||
if len(dat13) != 13:
|
||||
return None
|
||||
current_mA = self.u16_be(dat13[0], dat13[1])
|
||||
voltage_mV = self.u16_be(dat13[2], dat13[3])
|
||||
temperature_raw = self.u16_be(dat13[4], dat13[5])
|
||||
tds_ppm = self.u16_be(dat13[6], dat13[7])
|
||||
gas_sccm = self.u16_be(dat13[8], dat13[9])
|
||||
liquid_mL = self.u16_be(dat13[10], dat13[11])
|
||||
ph_raw = dat13[12] & 0xFF
|
||||
|
||||
return {
|
||||
"Current_mA": current_mA,
|
||||
"Voltage_mV": voltage_mV,
|
||||
"Temperature_C": round(temperature_raw / 100.0, 2),
|
||||
"TDS_ppm": tds_ppm,
|
||||
"GasFlow_sccm": gas_sccm,
|
||||
"LiquidFlow_mL": liquid_mL,
|
||||
"pH": round(ph_raw / 10.0, 2)
|
||||
}
|
||||
|
||||
def try_parse_rx_frame(self, frame15: bytes) -> Optional[Dict[str, Any]]:
|
||||
"""尝试解析接收帧"""
|
||||
if len(frame15) != self.RX_FRAME_LEN:
|
||||
return None
|
||||
if frame15[0] != self.RX_HEAD or frame15[-1] != self.RX_TAIL:
|
||||
return None
|
||||
return self.parse_rx_payload(frame15[1:-1])
|
||||
|
||||
def rx_thread_fn(self):
|
||||
"""接收线程函数"""
|
||||
headers = ["Timestamp", "Current_mA", "Voltage_mV",
|
||||
"Temperature_C", "TDS_ppm", "GasFlow_sccm", "LiquidFlow_mL", "pH"]
|
||||
|
||||
new_file = not os.path.exists(self.csv_path)
|
||||
f = open(self.csv_path, mode='a', newline='', encoding='utf-8')
|
||||
writer = csv.writer(f)
|
||||
if new_file:
|
||||
writer.writerow(headers)
|
||||
f.flush()
|
||||
|
||||
buf = deque(maxlen=8192)
|
||||
print(f"[RX] 开始接收(帧长 {self.RX_FRAME_LEN} 字节);写入:{self.csv_path}")
|
||||
|
||||
try:
|
||||
while not self.stop_flag and self.ser and self.ser.is_open:
|
||||
chunk = self.ser.read(self.chunk_read)
|
||||
if chunk:
|
||||
buf.extend(chunk)
|
||||
while True:
|
||||
# 找帧头
|
||||
try:
|
||||
start = next(i for i, b in enumerate(buf) if b == self.RX_HEAD)
|
||||
except StopIteration:
|
||||
buf.clear()
|
||||
break
|
||||
if start > 0:
|
||||
for _ in range(start):
|
||||
buf.popleft()
|
||||
if len(buf) < self.RX_FRAME_LEN:
|
||||
break
|
||||
candidate = bytes([buf[i] for i in range(self.RX_FRAME_LEN)])
|
||||
if candidate[-1] == self.RX_TAIL:
|
||||
parsed = self.try_parse_rx_frame(candidate)
|
||||
for _ in range(self.RX_FRAME_LEN):
|
||||
buf.popleft()
|
||||
if parsed:
|
||||
ts = time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
row = [ts,
|
||||
parsed["Current_mA"], parsed["Voltage_mV"],
|
||||
parsed["Temperature_C"], parsed["TDS_ppm"],
|
||||
parsed["GasFlow_sccm"], parsed["LiquidFlow_mL"],
|
||||
parsed["pH"]]
|
||||
writer.writerow(row)
|
||||
f.flush()
|
||||
# 若不想打印可注释下一行
|
||||
# print(f"[{ts}] I={parsed['Current_mA']} mA, V={parsed['Voltage_mV']} mV, "
|
||||
# f"T={parsed['Temperature_C']} °C, TDS={parsed['TDS_ppm']}, "
|
||||
# f"Gas={parsed['GasFlow_sccm']} sccm, Liq={parsed['LiquidFlow_mL']} mL, pH={parsed['pH']}")
|
||||
else:
|
||||
# 头不变,尾不对,丢1字节继续对齐
|
||||
buf.popleft()
|
||||
else:
|
||||
time.sleep(0.01)
|
||||
finally:
|
||||
f.close()
|
||||
print("[RX] 接收线程退出,CSV 已关闭")
|
||||
|
||||
# ================== 发送:固定11字节 ==================
|
||||
def build_tx_frame(self, mode: int, current_ma: int, voltage_mv: int, temp_c: float, ki: float, pump_percent: float) -> bytes:
|
||||
"""
|
||||
发送帧:HEAD + [mode, I_hi, I_lo, V_hi, V_lo, T_hi, T_lo, Ki_byte, Pump_byte] + TAIL
|
||||
- mode: 0=恒压, 1=恒流
|
||||
- current_ma: mA (0..65535)
|
||||
- voltage_mv: mV (0..65535)
|
||||
- temp_c: ℃,将 *100 后拆分为高/低字节
|
||||
- ki: 0.0..20.0 -> byte = round(ki * 10) 夹到 0..200
|
||||
- pump_percent: 0..100 -> byte = round(pump * 2) 夹到 0..200
|
||||
"""
|
||||
mode_b = 1 if int(mode) == 1 else 0
|
||||
|
||||
i_hi, i_lo = self.split_u16_be(current_ma)
|
||||
v_hi, v_lo = self.split_u16_be(voltage_mv)
|
||||
|
||||
t100 = int(round(float(temp_c) * 100.0))
|
||||
t_hi, t_lo = self.split_u16_be(t100)
|
||||
|
||||
ki_b = int(max(0, min(200, round(float(ki) * 10))))
|
||||
pump_b = int(max(0, min(200, round(float(pump_percent) * 2))))
|
||||
|
||||
return bytes((
|
||||
self.TX_HEAD,
|
||||
mode_b,
|
||||
i_hi, i_lo,
|
||||
v_hi, v_lo,
|
||||
t_hi, t_lo,
|
||||
ki_b,
|
||||
pump_b,
|
||||
self.TX_TAIL
|
||||
))
|
||||
|
||||
def tx_thread_fn(self):
|
||||
"""
|
||||
发送线程函数
|
||||
用户输入 6 个用逗号分隔的数值:
|
||||
mode,current_mA,voltage_mV,set_temp_C,Ki,pump_percent
|
||||
例如: 0,1000,500,0,0,50
|
||||
"""
|
||||
print("\n输入 6 个值(用英文逗号分隔),顺序为:")
|
||||
print("mode,current_mA,voltage_mV,set_temp_C,Ki,pump_percent")
|
||||
print("示例恒压:0,500,1000,25,0,100 (stop 结束)\n")
|
||||
print("示例恒流:1,1000,500,25,0,100 (stop 结束)\n")
|
||||
print("示例恒流:1,2000,500,25,0,100 (stop 结束)\n")
|
||||
# 1,2000,500,25,0,100
|
||||
|
||||
while not self.stop_flag and self.ser and self.ser.is_open:
|
||||
try:
|
||||
line = input(">>> ").strip()
|
||||
except EOFError:
|
||||
self.stop_flag = True
|
||||
break
|
||||
|
||||
if not line:
|
||||
continue
|
||||
if line.lower() == "stop":
|
||||
self.stop_flag = True
|
||||
print("[SYS] 停止程序")
|
||||
break
|
||||
|
||||
try:
|
||||
parts = [p.strip() for p in line.split(",")]
|
||||
if len(parts) != 6:
|
||||
raise ValueError("需要 6 个逗号分隔的数值")
|
||||
mode = int(parts[0])
|
||||
i_ma = int(float(parts[1]))
|
||||
v_mv = int(float(parts[2]))
|
||||
t_c = float(parts[3])
|
||||
ki = float(parts[4])
|
||||
pump = float(parts[5])
|
||||
|
||||
frame = self.build_tx_frame(mode, i_ma, v_mv, t_c, ki, pump)
|
||||
self.ser.write(frame)
|
||||
print("[TX]", " ".join(f"{b:02X}" for b in frame))
|
||||
except Exception as e:
|
||||
print("[TX] 输入/打包失败:", e)
|
||||
print("格式:mode,current_mA,voltage_mV,set_temp_C,Ki,pump_percent")
|
||||
continue
|
||||
|
||||
def start(self):
|
||||
"""启动电解水平台"""
|
||||
self.ser = self.open_serial()
|
||||
if self.ser:
|
||||
try:
|
||||
self.rx_thread = threading.Thread(target=self.rx_thread_fn, daemon=True)
|
||||
self.tx_thread = threading.Thread(target=self.tx_thread_fn, daemon=True)
|
||||
self.rx_thread.start()
|
||||
self.tx_thread.start()
|
||||
print("[INFO] 电解水平台已启动")
|
||||
self.tx_thread.join() # 等待用户输入线程结束(输入 stop)
|
||||
finally:
|
||||
self.close_serial()
|
||||
|
||||
def stop(self):
|
||||
"""停止电解水平台"""
|
||||
self.stop_flag = True
|
||||
if self.rx_thread and self.rx_thread.is_alive():
|
||||
self.rx_thread.join(timeout=2.0)
|
||||
if self.tx_thread and self.tx_thread.is_alive():
|
||||
self.tx_thread.join(timeout=2.0)
|
||||
self.close_serial()
|
||||
print("[INFO] 电解水平台已停止")
|
||||
|
||||
|
||||
# ================== 主入口 ==================
|
||||
if __name__ == "__main__":
|
||||
# 创建一个简单的 Deck 用于测试
|
||||
from pylabrobot.resources import Deck
|
||||
|
||||
deck = Deck()
|
||||
platform = ElectrolysisWaterPlatform(deck)
|
||||
platform.start()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,197 @@
|
||||
{
|
||||
"token": "",
|
||||
"request_time": "2025-12-24T15:32:09.2148671+08:00",
|
||||
"data": {
|
||||
"orderId": "3a1e614d-a082-c44a-60be-68647a35e6f1",
|
||||
"orderCode": "BSO2025122400024",
|
||||
"orderName": "DP20251224001",
|
||||
"startTime": "2025-12-24T14:51:50.549848",
|
||||
"endTime": "2025-12-24T15:32:09.000765",
|
||||
"status": "30",
|
||||
"workflowStatus": "completed",
|
||||
"completionTime": "2025-12-24T15:32:09.000765",
|
||||
"usedMaterials": [
|
||||
{
|
||||
"materialId": "3a1e614b-53a6-0ec4-10bd-956b240c0f04",
|
||||
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 2,
|
||||
"realQuantity": 2
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-4da7-cf62-3a40-7e5879255c0c",
|
||||
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-53a7-2850-42c8-a7a2de8ff4bf",
|
||||
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-4da6-ac9d-02be-4b0716796bd2",
|
||||
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 2,
|
||||
"realQuantity": 2
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614d-9c9a-fafa-4757-c7411b03bd9f",
|
||||
"locationId": "3a1abd46-18fe-1f56-6ced-a1f7fe08e36c",
|
||||
"typemode": "0",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-6917-b8f9-7987-7a33a3792829",
|
||||
"locationId": "3a19da43-57b5-294f-d663-154a1cc32270",
|
||||
"typemode": "2",
|
||||
"usedQuantity": 3.51,
|
||||
"realQuantity": 3.5155000000000000000000000000
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-6914-d92b-e348-f52e13817a5d",
|
||||
"locationId": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
||||
"typemode": "2",
|
||||
"usedQuantity": 0.33,
|
||||
"realQuantity": 0.3336000000000000000000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"token": "",
|
||||
"request_time": "2025-12-24T15:32:09.9999039+08:00",
|
||||
"data": {
|
||||
"orderId": "3a1e614d-a0a2-f7a9-9360-610021c9479d",
|
||||
"orderCode": "BSO2025122400025",
|
||||
"orderName": "DP20251224002",
|
||||
"startTime": "2025-12-24T14:53:03.44259",
|
||||
"endTime": "2025-12-24T15:32:09.828261",
|
||||
"status": "30",
|
||||
"workflowStatus": "completed",
|
||||
"completionTime": "2025-12-24T15:32:09.828261",
|
||||
"usedMaterials": [
|
||||
{
|
||||
"materialId": "3a1e614b-4da7-6527-9f1c-b39e3de8ff2b",
|
||||
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-53a6-0ec4-10bd-956b240c0f04",
|
||||
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 2,
|
||||
"realQuantity": 2
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-4da6-ac9d-02be-4b0716796bd2",
|
||||
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 2,
|
||||
"realQuantity": 2
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-53a8-8474-cac8-0fd7d349e4b2",
|
||||
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614d-9c9a-fafa-4757-c7411b03bd9f",
|
||||
"locationId": null,
|
||||
"typemode": "0",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-6917-b8f9-7987-7a33a3792829",
|
||||
"locationId": "3a19da43-57b5-294f-d663-154a1cc32270",
|
||||
"typemode": "2",
|
||||
"usedQuantity": 0.7,
|
||||
"realQuantity": 0
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-6914-d92b-e348-f52e13817a5d",
|
||||
"locationId": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
||||
"typemode": "2",
|
||||
"usedQuantity": 1.15,
|
||||
"realQuantity": 1.1627000000000000000000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"token": "",
|
||||
"request_time": "2025-12-24T15:34:00.4139986+08:00",
|
||||
"data": {
|
||||
"orderId": "3a1e614d-a0cd-81ca-9f7f-2f4e93af01cd",
|
||||
"orderCode": "BSO2025122400026",
|
||||
"orderName": "DP20251224003",
|
||||
"startTime": "2025-12-24T14:54:24.443344",
|
||||
"endTime": "2025-12-24T15:34:00.26321",
|
||||
"status": "30",
|
||||
"workflowStatus": "completed",
|
||||
"completionTime": "2025-12-24T15:34:00.26321",
|
||||
"usedMaterials": [
|
||||
{
|
||||
"materialId": "3a1e614b-4da6-ac9d-02be-4b0716796bd2",
|
||||
"locationId": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 2,
|
||||
"realQuantity": 2
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-4da8-b678-f204-207076f09c83",
|
||||
"locationId": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-53a6-0ec4-10bd-956b240c0f04",
|
||||
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 2,
|
||||
"realQuantity": 2
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-53a8-e3f2-dee0-fa97b600b652",
|
||||
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||
"typemode": "1",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614d-9c9a-fafa-4757-c7411b03bd9f",
|
||||
"locationId": null,
|
||||
"typemode": "0",
|
||||
"usedQuantity": 1,
|
||||
"realQuantity": 1
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-6917-b8f9-7987-7a33a3792829",
|
||||
"locationId": "3a19da43-57b5-294f-d663-154a1cc32270",
|
||||
"typemode": "2",
|
||||
"usedQuantity": 2.0,
|
||||
"realQuantity": 2.0075000000000000000000000000
|
||||
},
|
||||
{
|
||||
"materialId": "3a1e614b-6914-d92b-e348-f52e13817a5d",
|
||||
"locationId": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
||||
"typemode": "2",
|
||||
"usedQuantity": 1.2,
|
||||
"realQuantity": 1.2126000000000000000000000000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import pubchempy as pcp
|
||||
|
||||
cas = "21324-40-3" # 示例
|
||||
comps = pcp.get_compounds(cas, namespace="name")
|
||||
if not comps:
|
||||
raise ValueError("No hit")
|
||||
|
||||
c = comps[0]
|
||||
|
||||
print("Canonical SMILES:", c.canonical_smiles)
|
||||
print("Isomeric SMILES:", c.isomeric_smiles)
|
||||
print("MW:", c.molecular_weight)
|
||||
@@ -0,0 +1,7 @@
|
||||
material_name
|
||||
LiPF6
|
||||
LiDFOB
|
||||
DTD
|
||||
LiFSI
|
||||
LiPO2F2
|
||||
|
||||
|
@@ -0,0 +1,113 @@
|
||||
# Bioyond Cell 工作站 - 多订单返回示例
|
||||
|
||||
本文档说明了 `create_orders` 函数如何收集并返回所有订单的完成报文。
|
||||
|
||||
## 问题描述
|
||||
|
||||
之前的实现只会等待并返回第一个订单的完成报文,如果有多个订单(例如从 Excel 解析出 3 个订单),只能得到第一个订单的推送信息。
|
||||
|
||||
## 解决方案
|
||||
|
||||
修改后的 `create_orders` 函数现在会:
|
||||
|
||||
1. **提取所有 orderCode**:从 LIMS 接口返回的 `data` 列表中提取所有订单编号
|
||||
2. **逐个等待完成**:遍历所有 orderCode,调用 `wait_for_order_finish` 等待每个订单完成
|
||||
3. **收集所有报文**:将每个订单的完成报文存入 `all_reports` 列表
|
||||
4. **统一返回**:返回包含所有订单报文的 JSON 格式数据
|
||||
|
||||
## 返回格式
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "all_completed",
|
||||
"total_orders": 3,
|
||||
"reports": [
|
||||
{
|
||||
"token": "",
|
||||
"request_time": "2025-12-24T15:32:09.2148671+08:00",
|
||||
"data": {
|
||||
"orderId": "3a1e614d-a082-c44a-60be-68647a35e6f1",
|
||||
"orderCode": "BSO2025122400024",
|
||||
"orderName": "DP20251224001",
|
||||
"status": "30",
|
||||
"workflowStatus": "completed",
|
||||
"usedMaterials": [...]
|
||||
}
|
||||
},
|
||||
{
|
||||
"token": "",
|
||||
"request_time": "2025-12-24T15:32:09.9999039+08:00",
|
||||
"data": {
|
||||
"orderId": "3a1e614d-a0a2-f7a9-9360-610021c9479d",
|
||||
"orderCode": "BSO2025122400025",
|
||||
"orderName": "DP20251224002",
|
||||
"status": "30",
|
||||
"workflowStatus": "completed",
|
||||
"usedMaterials": [...]
|
||||
}
|
||||
},
|
||||
{
|
||||
"token": "",
|
||||
"request_time": "2025-12-24T15:34:00.4139986+08:00",
|
||||
"data": {
|
||||
"orderId": "3a1e614d-a0cd-81ca-9f7f-2f4e93af01cd",
|
||||
"orderCode": "BSO2025122400026",
|
||||
"orderName": "DP20251224003",
|
||||
"status": "30",
|
||||
"workflowStatus": "completed",
|
||||
"usedMaterials": [...]
|
||||
}
|
||||
}
|
||||
],
|
||||
"original_response": {...}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
# 调用 create_orders
|
||||
result = workstation.create_orders("20251224.xlsx")
|
||||
|
||||
# 访问返回数据
|
||||
print(f"总订单数: {result['total_orders']}")
|
||||
print(f"状态: {result['status']}")
|
||||
|
||||
# 遍历所有订单的报文
|
||||
for i, report in enumerate(result['reports'], 1):
|
||||
order_data = report.get('data', {})
|
||||
print(f"\n订单 {i}:")
|
||||
print(f" orderCode: {order_data.get('orderCode')}")
|
||||
print(f" orderName: {order_data.get('orderName')}")
|
||||
print(f" status: {order_data.get('status')}")
|
||||
print(f" 使用物料数: {len(order_data.get('usedMaterials', []))}")
|
||||
```
|
||||
|
||||
## 控制台输出示例
|
||||
|
||||
```
|
||||
[create_orders] 即将提交订单数量: 3
|
||||
[create_orders] 接口返回: {...}
|
||||
[create_orders] 等待 3 个订单完成: ['BSO2025122400024', 'BSO2025122400025', 'BSO2025122400026']
|
||||
[create_orders] 正在等待第 1/3 个订单: BSO2025122400024
|
||||
[create_orders] ✓ 订单 BSO2025122400024 完成
|
||||
[create_orders] 正在等待第 2/3 个订单: BSO2025122400025
|
||||
[create_orders] ✓ 订单 BSO2025122400025 完成
|
||||
[create_orders] 正在等待第 3/3 个订单: BSO2025122400026
|
||||
[create_orders] ✓ 订单 BSO2025122400026 完成
|
||||
[create_orders] 所有订单已完成,共收集 3 个报文
|
||||
实验记录本========================create_orders========================
|
||||
返回报文数量: 3
|
||||
报文 1: orderCode=BSO2025122400024, status=30
|
||||
报文 2: orderCode=BSO2025122400025, status=30
|
||||
报文 3: orderCode=BSO2025122400026, status=30
|
||||
========================
|
||||
```
|
||||
|
||||
## 关键改进
|
||||
|
||||
1. ✅ **等待所有订单**:不再只等待第一个订单,而是遍历所有 orderCode
|
||||
2. ✅ **收集完整报文**:每个订单的完整推送报文都被保存在 `reports` 数组中
|
||||
3. ✅ **详细日志**:清晰显示正在等待哪个订单,以及完成情况
|
||||
4. ✅ **错误处理**:即使某个订单失败,也会记录其状态信息
|
||||
5. ✅ **统一格式**:返回的 JSON 格式便于后续处理和分析
|
||||
@@ -47,8 +47,8 @@ class BioyondV1RPC(BaseRequest):
|
||||
super().__init__()
|
||||
print("开始初始化 BioyondV1RPC")
|
||||
self.config = config
|
||||
self.api_key = config["api_key"]
|
||||
self.host = config["api_host"]
|
||||
self.api_key = config.get("api_key", "")
|
||||
self.host = config.get("api_host", "") or config.get("base_url", "")
|
||||
self._logger = SimpleLogger()
|
||||
self.material_cache = {}
|
||||
self._load_material_cache()
|
||||
@@ -61,7 +61,7 @@ class BioyondV1RPC(BaseRequest):
|
||||
|
||||
:return: 当前时间的 ISO 8601 格式字符串
|
||||
"""
|
||||
current_time = datetime.now(timezone.utc).isoformat(
|
||||
current_time = datetime.now().isoformat(
|
||||
timespec='milliseconds'
|
||||
)
|
||||
# 替换时区部分为 'Z'
|
||||
@@ -192,23 +192,6 @@ class BioyondV1RPC(BaseRequest):
|
||||
return []
|
||||
return str(response.get("data", {}))
|
||||
|
||||
def material_type_list(self) -> list:
|
||||
"""查询物料类型列表
|
||||
|
||||
返回值:
|
||||
list: 物料类型数组,失败返回空列表
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/storage/material-type-list',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": {},
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return []
|
||||
return response.get("data", [])
|
||||
|
||||
def material_inbound(self, material_id: str, location_id: str) -> dict:
|
||||
"""
|
||||
描述:指定库位入库一个物料
|
||||
@@ -229,34 +212,8 @@ class BioyondV1RPC(BaseRequest):
|
||||
})
|
||||
|
||||
if not response or response['code'] != 1:
|
||||
if response:
|
||||
error_msg = response.get('message', '未知错误')
|
||||
print(f"[ERROR] 物料入库失败: code={response.get('code')}, message={error_msg}")
|
||||
else:
|
||||
print(f"[ERROR] 物料入库失败: API 无响应")
|
||||
return {}
|
||||
# 入库成功时,即使没有 data 字段,也返回成功标识
|
||||
return response.get("data") or {"success": True}
|
||||
|
||||
def batch_inbound(self, inbound_items: List[Dict[str, Any]]) -> int:
|
||||
"""批量入库物料
|
||||
|
||||
参数:
|
||||
inbound_items: 入库条目列表,每项包含 materialId/locationId/quantity 等
|
||||
|
||||
返回值:
|
||||
int: 成功返回1,失败返回0
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/storage/batch-inbound',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": inbound_items,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return 0
|
||||
return response.get("code", 0)
|
||||
return response.get("data", {})
|
||||
|
||||
def delete_material(self, material_id: str) -> dict:
|
||||
"""
|
||||
@@ -276,7 +233,7 @@ class BioyondV1RPC(BaseRequest):
|
||||
return response.get("data", {})
|
||||
|
||||
def material_outbound(self, material_id: str, location_name: str, quantity: int) -> dict:
|
||||
"""指定库位出库物料(通过库位名称)"""
|
||||
"""指定库位出库物料"""
|
||||
location_id = LOCATION_MAPPING.get(location_name, location_name)
|
||||
|
||||
params = {
|
||||
@@ -293,98 +250,9 @@ class BioyondV1RPC(BaseRequest):
|
||||
"data": params
|
||||
})
|
||||
|
||||
if not response or response['code'] != 1:
|
||||
return None
|
||||
return response
|
||||
|
||||
def material_outbound_by_id(self, material_id: str, location_id: str, quantity: int) -> dict:
|
||||
"""指定库位出库物料(直接使用location_id)
|
||||
|
||||
Args:
|
||||
material_id: 物料ID
|
||||
location_id: 库位ID(不是库位名称,是UUID)
|
||||
quantity: 数量
|
||||
|
||||
Returns:
|
||||
dict: API响应,失败返回None
|
||||
"""
|
||||
params = {
|
||||
"materialId": material_id,
|
||||
"locationId": location_id,
|
||||
"quantity": quantity
|
||||
}
|
||||
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/storage/outbound',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": params
|
||||
})
|
||||
|
||||
if not response or response['code'] != 1:
|
||||
return None
|
||||
return response
|
||||
|
||||
def batch_outbound(self, outbound_items: List[Dict[str, Any]]) -> int:
|
||||
"""批量出库物料
|
||||
|
||||
参数:
|
||||
outbound_items: 出库条目列表,每项包含 materialId/locationId/quantity 等
|
||||
|
||||
返回值:
|
||||
int: 成功返回1,失败返回0
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/storage/batch-outbound',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": outbound_items,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return 0
|
||||
return response.get("code", 0)
|
||||
|
||||
def material_info(self, material_id: str) -> dict:
|
||||
"""查询物料详情
|
||||
|
||||
参数:
|
||||
material_id: 物料ID
|
||||
|
||||
返回值:
|
||||
dict: 物料信息字典,失败返回空字典
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/storage/material-info',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": material_id,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return {}
|
||||
return response.get("data", {})
|
||||
|
||||
def reset_location(self, location_id: str) -> int:
|
||||
"""复位库位
|
||||
|
||||
参数:
|
||||
location_id: 库位ID
|
||||
|
||||
返回值:
|
||||
int: 成功返回1,失败返回0
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/storage/reset-location',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": location_id,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return 0
|
||||
return response.get("code", 0)
|
||||
return response
|
||||
|
||||
# ==================== 工作流查询相关接口 ====================
|
||||
|
||||
@@ -429,66 +297,6 @@ class BioyondV1RPC(BaseRequest):
|
||||
return {}
|
||||
return response.get("data", {})
|
||||
|
||||
def split_workflow_list(self, params: Dict[str, Any]) -> dict:
|
||||
"""查询可拆分工作流列表
|
||||
|
||||
参数:
|
||||
params: 查询条件参数
|
||||
|
||||
返回值:
|
||||
dict: 返回数据字典,失败返回空字典
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/workflow/split-workflow-list',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": params,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return {}
|
||||
return response.get("data", {})
|
||||
|
||||
def merge_workflow(self, data: Dict[str, Any]) -> dict:
|
||||
"""合并工作流(无参数版)
|
||||
|
||||
参数:
|
||||
data: 合并请求体,包含待合并的子工作流信息
|
||||
|
||||
返回值:
|
||||
dict: 合并结果,失败返回空字典
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/workflow/merge-workflow',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": data,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return {}
|
||||
return response.get("data", {})
|
||||
|
||||
def merge_workflow_with_parameters(self, data: Dict[str, Any]) -> dict:
|
||||
"""合并工作流(携带参数)
|
||||
|
||||
参数:
|
||||
data: 合并请求体,包含 name、workflows 以及 stepParameters 等
|
||||
|
||||
返回值:
|
||||
dict: 合并结果,失败返回空字典
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/workflow/merge-workflow-with-parameters',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": data,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return {}
|
||||
return response.get("data", {})
|
||||
|
||||
def validate_workflow_parameters(self, workflows: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""验证工作流参数格式"""
|
||||
try:
|
||||
@@ -651,15 +459,18 @@ class BioyondV1RPC(BaseRequest):
|
||||
return {}
|
||||
return response.get("data", {})
|
||||
|
||||
def order_report(self, order_id: str) -> dict:
|
||||
"""查询订单报告
|
||||
|
||||
参数:
|
||||
order_id: 订单ID
|
||||
|
||||
返回值:
|
||||
dict: 报告数据,失败返回空字典
|
||||
def order_report(self, json_str: str) -> dict:
|
||||
"""
|
||||
描述:查询某个任务明细
|
||||
json_str 格式为JSON字符串:
|
||||
'{"order_id": "order123"}'
|
||||
"""
|
||||
try:
|
||||
data = json.loads(json_str)
|
||||
order_id = data.get("order_id", "")
|
||||
except json.JSONDecodeError:
|
||||
return {}
|
||||
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/order/order-report',
|
||||
params={
|
||||
@@ -667,18 +478,16 @@ class BioyondV1RPC(BaseRequest):
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": order_id,
|
||||
})
|
||||
|
||||
if not response or response['code'] != 1:
|
||||
return {}
|
||||
return response.get("data", {})
|
||||
|
||||
def order_takeout(self, json_str: str) -> int:
|
||||
"""取出任务产物
|
||||
|
||||
参数:
|
||||
json_str: JSON字符串,包含 order_id 与 preintake_id
|
||||
|
||||
返回值:
|
||||
int: 成功返回1,失败返回0
|
||||
"""
|
||||
描述:取出任务产物
|
||||
json_str 格式为JSON字符串:
|
||||
'{"order_id": "order123", "preintake_id": "preintake123"}'
|
||||
"""
|
||||
try:
|
||||
data = json.loads(json_str)
|
||||
@@ -701,15 +510,14 @@ class BioyondV1RPC(BaseRequest):
|
||||
return 0
|
||||
return response.get("code", 0)
|
||||
|
||||
|
||||
def sample_waste_removal(self, order_id: str) -> dict:
|
||||
"""样品/废料取出
|
||||
"""
|
||||
样品/废料取出接口
|
||||
|
||||
参数:
|
||||
order_id: 订单ID
|
||||
- order_id: 订单ID
|
||||
|
||||
返回值:
|
||||
dict: 取出结果,失败返回空字典
|
||||
返回: 取出结果
|
||||
"""
|
||||
params = {"orderId": order_id}
|
||||
|
||||
@@ -731,13 +539,10 @@ class BioyondV1RPC(BaseRequest):
|
||||
return response.get("data", {})
|
||||
|
||||
def cancel_order(self, json_str: str) -> bool:
|
||||
"""取消指定任务
|
||||
|
||||
参数:
|
||||
json_str: JSON字符串,包含 order_id
|
||||
|
||||
返回值:
|
||||
bool: 成功返回 True,失败返回 False
|
||||
"""
|
||||
描述:取消指定任务
|
||||
json_str 格式为JSON字符串:
|
||||
'{"order_id": "order123"}'
|
||||
"""
|
||||
try:
|
||||
data = json.loads(json_str)
|
||||
@@ -757,126 +562,6 @@ class BioyondV1RPC(BaseRequest):
|
||||
return False
|
||||
return True
|
||||
|
||||
def cancel_experiment(self, order_id: str) -> int:
|
||||
"""取消指定实验
|
||||
|
||||
参数:
|
||||
order_id: 订单ID
|
||||
|
||||
返回值:
|
||||
int: 成功返回1,失败返回0
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/order/cancel-experiment',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": order_id,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return 0
|
||||
return response.get("code", 0)
|
||||
|
||||
def batch_cancel_experiment(self, order_ids: List[str]) -> int:
|
||||
"""批量取消实验
|
||||
|
||||
参数:
|
||||
order_ids: 订单ID列表
|
||||
|
||||
返回值:
|
||||
int: 成功返回1,失败返回0
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/order/batch-cancel-experiment',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": order_ids,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return 0
|
||||
return response.get("code", 0)
|
||||
|
||||
def gantts_by_order_id(self, order_id: str) -> dict:
|
||||
"""查询订单甘特图数据
|
||||
|
||||
参数:
|
||||
order_id: 订单ID
|
||||
|
||||
返回值:
|
||||
dict: 甘特数据,失败返回空字典
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/order/gantts-by-order-id',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": order_id,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return {}
|
||||
return response.get("data", {})
|
||||
|
||||
def simulation_gantt_by_order_id(self, order_id: str) -> dict:
|
||||
"""查询订单模拟甘特图数据
|
||||
|
||||
参数:
|
||||
order_id: 订单ID
|
||||
|
||||
返回值:
|
||||
dict: 模拟甘特数据,失败返回空字典
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/order/simulation-gantt-by-order-id',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": order_id,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return {}
|
||||
return response.get("data", {})
|
||||
|
||||
def reset_order_status(self, order_id: str) -> int:
|
||||
"""复位订单状态
|
||||
|
||||
参数:
|
||||
order_id: 订单ID
|
||||
|
||||
返回值:
|
||||
int: 成功返回1,失败返回0
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/order/reset-order-status',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": order_id,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return 0
|
||||
return response.get("code", 0)
|
||||
|
||||
def gantt_with_simulation_by_order_id(self, order_id: str) -> dict:
|
||||
"""查询订单甘特与模拟联合数据
|
||||
|
||||
参数:
|
||||
order_id: 订单ID
|
||||
|
||||
返回值:
|
||||
dict: 联合数据,失败返回空字典
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/order/gantt-with-simulation-by-order-id',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": order_id,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return {}
|
||||
return response.get("data", {})
|
||||
|
||||
# ==================== 设备管理相关接口 ====================
|
||||
|
||||
def device_list(self, json_str: str = "") -> list:
|
||||
@@ -908,13 +593,9 @@ class BioyondV1RPC(BaseRequest):
|
||||
return response.get("data", [])
|
||||
|
||||
def device_operation(self, json_str: str) -> int:
|
||||
"""设备操作
|
||||
|
||||
参数:
|
||||
json_str: JSON字符串,包含 device_no/operationType/operationParams
|
||||
|
||||
返回值:
|
||||
int: 成功返回1,失败返回0
|
||||
"""
|
||||
描述:操作设备
|
||||
json_str 格式为JSON字符串
|
||||
"""
|
||||
try:
|
||||
data = json.loads(json_str)
|
||||
@@ -927,7 +608,7 @@ class BioyondV1RPC(BaseRequest):
|
||||
return 0
|
||||
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/device/execute-operation',
|
||||
url=f'{self.host}/api/lims/device/device-operation',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
@@ -938,30 +619,9 @@ class BioyondV1RPC(BaseRequest):
|
||||
return 0
|
||||
return response.get("code", 0)
|
||||
|
||||
def reset_devices(self) -> int:
|
||||
"""复位设备集合
|
||||
|
||||
返回值:
|
||||
int: 成功返回1,失败返回0
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/device/reset-devices',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return 0
|
||||
return response.get("code", 0)
|
||||
|
||||
# ==================== 调度器相关接口 ====================
|
||||
|
||||
def scheduler_status(self) -> dict:
|
||||
"""查询调度器状态
|
||||
|
||||
返回值:
|
||||
dict: 包含 schedulerStatus/hasTask/creationTime 等
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/scheduler/scheduler-status',
|
||||
params={
|
||||
@@ -974,7 +634,7 @@ class BioyondV1RPC(BaseRequest):
|
||||
return response.get("data", {})
|
||||
|
||||
def scheduler_start(self) -> int:
|
||||
"""启动调度器"""
|
||||
"""描述:启动调度器"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/scheduler/start',
|
||||
params={
|
||||
@@ -987,22 +647,9 @@ class BioyondV1RPC(BaseRequest):
|
||||
return response.get("code", 0)
|
||||
|
||||
def scheduler_pause(self) -> int:
|
||||
"""暂停调度器"""
|
||||
"""描述:暂停调度器"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/scheduler/pause',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
})
|
||||
|
||||
if not response or response['code'] != 1:
|
||||
return 0
|
||||
return response.get("code", 0)
|
||||
|
||||
def scheduler_smart_pause(self) -> int:
|
||||
"""智能暂停调度器"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/scheduler/smart-pause',
|
||||
url=f'{self.host}/api/lims/scheduler/scheduler-pause',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
@@ -1013,9 +660,8 @@ class BioyondV1RPC(BaseRequest):
|
||||
return response.get("code", 0)
|
||||
|
||||
def scheduler_continue(self) -> int:
|
||||
"""继续调度器"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/scheduler/continue',
|
||||
url=f'{self.host}/api/lims/scheduler/scheduler-continue',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
@@ -1026,9 +672,9 @@ class BioyondV1RPC(BaseRequest):
|
||||
return response.get("code", 0)
|
||||
|
||||
def scheduler_stop(self) -> int:
|
||||
"""停止调度器"""
|
||||
"""描述:停止调度器"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/scheduler/stop',
|
||||
url=f'{self.host}/api/lims/scheduler/scheduler-stop',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
@@ -1039,9 +685,9 @@ class BioyondV1RPC(BaseRequest):
|
||||
return response.get("code", 0)
|
||||
|
||||
def scheduler_reset(self) -> int:
|
||||
"""复位调度器"""
|
||||
"""描述:重置调度器"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/scheduler/reset',
|
||||
url=f'{self.host}/api/lims/scheduler/scheduler-reset',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
@@ -1051,36 +697,16 @@ class BioyondV1RPC(BaseRequest):
|
||||
return 0
|
||||
return response.get("code", 0)
|
||||
|
||||
def scheduler_reply_error_handling(self, data: Dict[str, Any]) -> int:
|
||||
"""调度错误处理回复
|
||||
|
||||
参数:
|
||||
data: 错误处理参数
|
||||
|
||||
返回值:
|
||||
int: 成功返回1,失败返回0
|
||||
"""
|
||||
response = self.post(
|
||||
url=f'{self.host}/api/lims/scheduler/reply-error-handling',
|
||||
params={
|
||||
"apiKey": self.api_key,
|
||||
"requestTime": self.get_current_time_iso8601(),
|
||||
"data": data,
|
||||
})
|
||||
if not response or response['code'] != 1:
|
||||
return 0
|
||||
return response.get("code", 0)
|
||||
|
||||
# ==================== 辅助方法 ====================
|
||||
|
||||
def _load_material_cache(self):
|
||||
"""预加载材料列表到缓存中"""
|
||||
try:
|
||||
print("正在加载材料列表缓存...")
|
||||
|
||||
|
||||
# 加载所有类型的材料:耗材(0)、样品(1)、试剂(2)
|
||||
material_types = [0, 1, 2]
|
||||
|
||||
material_types = [1, 2]
|
||||
|
||||
for type_mode in material_types:
|
||||
print(f"正在加载类型 {type_mode} 的材料...")
|
||||
stock_query = f'{{"typeMode": {type_mode}, "includeDetail": true}}'
|
||||
@@ -1097,7 +723,7 @@ class BioyondV1RPC(BaseRequest):
|
||||
material_id = material.get("id")
|
||||
if material_name and material_id:
|
||||
self.material_cache[material_name] = material_id
|
||||
|
||||
|
||||
# 处理样品板等容器中的detail材料
|
||||
detail_materials = material.get("detail", [])
|
||||
for detail_material in detail_materials:
|
||||
@@ -1133,24 +759,4 @@ class BioyondV1RPC(BaseRequest):
|
||||
|
||||
def get_available_materials(self):
|
||||
"""获取所有可用的材料名称列表"""
|
||||
return list(self.material_cache.keys())
|
||||
|
||||
def get_scheduler_state(self) -> Optional[MachineState]:
|
||||
"""将调度状态字符串映射为枚举值
|
||||
|
||||
返回值:
|
||||
Optional[MachineState]: 映射后的枚举,失败返回 None
|
||||
"""
|
||||
data = self.scheduler_status()
|
||||
if not isinstance(data, dict):
|
||||
return None
|
||||
status = data.get("schedulerStatus")
|
||||
mapping = {
|
||||
"Init": MachineState.INITIAL,
|
||||
"Stop": MachineState.STOPPED,
|
||||
"Running": MachineState.RUNNING,
|
||||
"Pause": MachineState.PAUSED,
|
||||
"ErrorPause": MachineState.ERROR_PAUSED,
|
||||
"ErrorStop": MachineState.ERROR_STOPPED,
|
||||
}
|
||||
return mapping.get(status)
|
||||
return list(self.material_cache.keys())
|
||||
@@ -2,141 +2,372 @@
|
||||
"""
|
||||
配置文件 - 包含所有配置信息和映射关系
|
||||
"""
|
||||
import os
|
||||
|
||||
# API配置
|
||||
# ==================== API 基础配置 ====================
|
||||
# BioyondCellWorkstation 默认配置(包含所有必需参数)
|
||||
API_CONFIG = {
|
||||
"api_key": "",
|
||||
"api_host": ""
|
||||
}
|
||||
|
||||
# 工作流映射配置
|
||||
WORKFLOW_MAPPINGS = {
|
||||
"reactor_taken_out": "",
|
||||
"reactor_taken_in": "",
|
||||
"Solid_feeding_vials": "",
|
||||
"Liquid_feeding_vials(non-titration)": "",
|
||||
"Liquid_feeding_solvents": "",
|
||||
"Liquid_feeding(titration)": "",
|
||||
"liquid_feeding_beaker": "",
|
||||
"Drip_back": "",
|
||||
}
|
||||
|
||||
# 工作流名称到DisplaySectionName的映射
|
||||
WORKFLOW_TO_SECTION_MAP = {
|
||||
'reactor_taken_in': '反应器放入',
|
||||
'liquid_feeding_beaker': '液体投料-烧杯',
|
||||
'Liquid_feeding_vials(non-titration)': '液体投料-小瓶(非滴定)',
|
||||
'Liquid_feeding_solvents': '液体投料-溶剂',
|
||||
'Solid_feeding_vials': '固体投料-小瓶',
|
||||
'Liquid_feeding(titration)': '液体投料-滴定',
|
||||
'reactor_taken_out': '反应器取出'
|
||||
# API 连接配置
|
||||
# 实机
|
||||
#"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.118:44389"),
|
||||
# 仿真机
|
||||
"api_host": os.getenv("BIOYOND_API_HOST", "http://172.16.11.219:44388"),
|
||||
"api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"),
|
||||
"timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")),
|
||||
|
||||
# 报送配置
|
||||
"report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"),
|
||||
|
||||
# HTTP 服务配置
|
||||
"HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.11.206"), # HTTP服务监听地址,监听计算机飞连ip地址
|
||||
"HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")),
|
||||
"debug_mode": False,# 调试模式
|
||||
}
|
||||
|
||||
# 库位映射配置
|
||||
WAREHOUSE_MAPPING = {
|
||||
"粉末堆栈": {
|
||||
"粉末加样头堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
# 样品板
|
||||
"A1": "3a14198e-6929-31f0-8a22-0f98f72260df",
|
||||
"A2": "3a14198e-6929-4379-affa-9a2935c17f99",
|
||||
"A3": "3a14198e-6929-56da-9a1c-7f5fbd4ae8af",
|
||||
"A4": "3a14198e-6929-5e99-2b79-80720f7cfb54",
|
||||
"B1": "3a14198e-6929-f525-9a1b-1857552b28ee",
|
||||
"B2": "3a14198e-6929-bf98-0fd5-26e1d68bf62d",
|
||||
"B3": "3a14198e-6929-2d86-a468-602175a2b5aa",
|
||||
"B4": "3a14198e-6929-1a98-ae57-e97660c489ad",
|
||||
# 分装板
|
||||
"C1": "3a14198e-6929-46fe-841e-03dd753f1e4a",
|
||||
"C2": "3a14198e-6929-1bc9-a9bd-3b7ca66e7f95",
|
||||
"C3": "3a14198e-6929-72ac-32ce-9b50245682b8",
|
||||
"C4": "3a14198e-6929-3bd8-e6c7-4a9fd93be118",
|
||||
"D1": "3a14198e-6929-8a0b-b686-6f4a2955c4e2",
|
||||
"D2": "3a14198e-6929-dde1-fc78-34a84b71afdf",
|
||||
"D3": "3a14198e-6929-a0ec-5f15-c0f9f339f963",
|
||||
"D4": "3a14198e-6929-7ac8-915a-fea51cb2e884"
|
||||
"A01": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
||||
"B01": "3a19da56-1379-2424-d751-fe6e94cef938",
|
||||
"C01": "3a19da56-1379-271c-03e3-6bdb590e395e",
|
||||
"D01": "3a19da56-1379-277f-2b1b-0d11f7cf92c6",
|
||||
"E01": "3a19da56-1379-2f1c-a15b-e01db90eb39a",
|
||||
"F01": "3a19da56-1379-3fa1-846b-088158ac0b3d",
|
||||
"G01": "3a19da56-1379-5aeb-d0cd-d3b4609d66e1",
|
||||
"H01": "3a19da56-1379-6077-8258-bdc036870b78",
|
||||
"I01": "3a19da56-1379-863b-a120-f606baf04617",
|
||||
"J01": "3a19da56-1379-8a74-74e5-35a9b41d4fd5",
|
||||
"K01": "3a19da56-1379-b270-b7af-f18773918abe",
|
||||
"L01": "3a19da56-1379-ba54-6d78-fd770a671ffc",
|
||||
"M01": "3a19da56-1379-c22d-c96f-0ceb5eb54a04",
|
||||
"N01": "3a19da56-1379-d64e-c6c5-c72ea4829888",
|
||||
"O01": "3a19da56-1379-d887-1a3c-6f9cce90f90e",
|
||||
"P01": "3a19da56-1379-e77d-0e65-7463b238a3b9",
|
||||
"Q01": "3a19da56-1379-edf6-1472-802ddb628774",
|
||||
"R01": "3a19da56-1379-f281-0273-e0ef78f0fd97",
|
||||
"S01": "3a19da56-1379-f924-7f68-df1fa51489f4",
|
||||
"T01": "3a19da56-1379-ff7c-1745-07e200b44ce2"
|
||||
}
|
||||
},
|
||||
"溶液堆栈": {
|
||||
"配液站内试剂仓库": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A1": "3a14198e-d724-e036-afdc-2ae39a7f3383",
|
||||
"A2": "3a14198e-d724-afa4-fc82-0ac8a9016791",
|
||||
"A3": "3a14198e-d724-ca48-bb9e-7e85751e55b6",
|
||||
"A4": "3a14198e-d724-df6d-5e32-5483b3cab583",
|
||||
"B1": "3a14198e-d724-d818-6d4f-5725191a24b5",
|
||||
"B2": "3a14198e-d724-be8a-5e0b-012675e195c6",
|
||||
"B3": "3a14198e-d724-cc1e-5c2c-228a130f40a8",
|
||||
"B4": "3a14198e-d724-1e28-c885-574c3df468d0",
|
||||
"C1": "3a14198e-d724-b5bb-adf3-4c5a0da6fb31",
|
||||
"C2": "3a14198e-d724-ab4e-48cb-817c3c146707",
|
||||
"C3": "3a14198e-d724-7f18-1853-39d0c62e1d33",
|
||||
"C4": "3a14198e-d724-28a2-a760-baa896f46b66",
|
||||
"D1": "3a14198e-d724-d378-d266-2508a224a19f",
|
||||
"D2": "3a14198e-d724-f56e-468b-0110a8feb36a",
|
||||
"D3": "3a14198e-d724-0cf1-dea9-a1f40fe7e13c",
|
||||
"D4": "3a14198e-d724-0ddd-9654-f9352a421de9"
|
||||
"A01": "3a19da43-57b5-294f-d663-154a1cc32270",
|
||||
"B01": "3a19da43-57b5-7394-5f49-54efe2c9bef2",
|
||||
"C01": "3a19da43-57b5-5e75-552f-8dbd0ad1075f",
|
||||
"A02": "3a19da43-57b5-8441-db94-b4d3875a4b6c",
|
||||
"B02": "3a19da43-57b5-3e41-c181-5119dddaf50c",
|
||||
"C02": "3a19da43-57b5-269b-282d-fba61fe8ce96",
|
||||
"A03": "3a19da43-57b5-7c1e-d02e-c40e8c33f8a1",
|
||||
"B03": "3a19da43-57b5-659f-621f-1dcf3f640363",
|
||||
"C03": "3a19da43-57b5-855a-6e71-f398e376dee1",
|
||||
}
|
||||
},
|
||||
"试剂堆栈": {
|
||||
"试剂替换仓库": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A1": "3a14198c-c2cf-8b40-af28-b467808f1c36",
|
||||
"A2": "3a14198c-c2d0-f3e7-871a-e470d144296f",
|
||||
"A3": "3a14198c-c2d0-dc7d-b8d0-e1d88cee3094",
|
||||
"A4": "3a14198c-c2d0-2070-efc8-44e245f10c6f",
|
||||
"B1": "3a14198c-c2d0-354f-39ad-642e1a72fcb8",
|
||||
"B2": "3a14198c-c2d0-1559-105d-0ea30682cab4",
|
||||
"B3": "3a14198c-c2d0-725e-523d-34c037ac2440",
|
||||
"B4": "3a14198c-c2d0-efce-0939-69ca5a7dfd39"
|
||||
"A01": "3a19da51-8f4e-30f3-ea08-4f8498e9b097",
|
||||
"B01": "3a19da51-8f4e-1da7-beb0-80a4a01e67a8",
|
||||
"C01": "3a19da51-8f4e-337d-2675-bfac46880b06",
|
||||
"D01": "3a19da51-8f4e-e514-b92c-9c44dc5e489d",
|
||||
"E01": "3a19da51-8f4e-22d1-dd5b-9774ddc80402",
|
||||
"F01": "3a19da51-8f4e-273a-4871-dff41c29bfd9",
|
||||
"G01": "3a19da51-8f4e-b32f-454f-74bc1a665653",
|
||||
"H01": "3a19da51-8f4e-8c93-68c9-0b4382320f59",
|
||||
"I01": "3a19da51-8f4e-360c-0149-291b47c6089b",
|
||||
"J01": "3a19da51-8f4e-4152-9bca-8d64df8c1af0"
|
||||
}
|
||||
},
|
||||
"自动堆栈-左": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||
"A02": "3a19debc-84b5-033b-b31f-6b87f7c2bf52",
|
||||
"B01": "3a19debc-84b5-3924-172f-719ab01b125c",
|
||||
"B02": "3a19debc-84b5-aad8-70c6-b8c6bb2d8750"
|
||||
}
|
||||
},
|
||||
"自动堆栈-右": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19debe-5200-7df2-1dd9-7d202f158864",
|
||||
"A02": "3a19debe-5200-573b-6120-8b51f50e1e50",
|
||||
"B01": "3a19debe-5200-7cd8-7666-851b0a97e309",
|
||||
"B02": "3a19debe-5200-e6d3-96a3-baa6e3d5e484"
|
||||
}
|
||||
},
|
||||
"手动堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
|
||||
"A03": "3a19deae-2c7a-5876-c454-6b7e224ca927",
|
||||
"B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1",
|
||||
"B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3",
|
||||
"B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||
"C01": "3a19deae-2c7a-32bc-768e-556647e292f3",
|
||||
"C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4",
|
||||
"C03": "3a19deae-2c7a-3056-6504-10dc73fbc276",
|
||||
"D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440",
|
||||
"D02": "3a19deae-2c7a-61be-601c-b6fb5610499a",
|
||||
"D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560",
|
||||
"E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363",
|
||||
"E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910",
|
||||
"E03": "3a19deae-2c7a-b163-2219-23df15200311",
|
||||
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
|
||||
"F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679",
|
||||
"F03": "3a19deae-2c7a-f7c4-12bd-425799425698",
|
||||
"G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955",
|
||||
"G02": "3a19deae-2c7a-204e-95ed-1f1950f28343",
|
||||
"G03": "3a19deae-2c7a-392b-62f1-4907c66343f8",
|
||||
"H01": "3a19deae-2c7a-5602-e876-d27aca4e3201",
|
||||
"H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702",
|
||||
"H03": "3a19deae-2c7a-780b-8965-2e1345f7e834",
|
||||
"I01": "3a19deae-2c7a-8849-e172-07de14ede928",
|
||||
"I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0",
|
||||
"I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4",
|
||||
"J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6",
|
||||
"J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205",
|
||||
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
|
||||
}
|
||||
},
|
||||
"手动传递窗右": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
|
||||
"A03": "3a19deae-2c7a-5876-c454-6b7e224ca927",
|
||||
"B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1",
|
||||
"B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3",
|
||||
"B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||
"C01": "3a19deae-2c7a-32bc-768e-556647e292f3",
|
||||
"C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4",
|
||||
"C03": "3a19deae-2c7a-3056-6504-10dc73fbc276",
|
||||
"D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440",
|
||||
"D02": "3a19deae-2c7a-61be-601c-b6fb5610499a",
|
||||
"D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560",
|
||||
"E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363",
|
||||
"E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910",
|
||||
"E03": "3a19deae-2c7a-b163-2219-23df15200311",
|
||||
}
|
||||
},
|
||||
"手动传递窗左": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
|
||||
"F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679",
|
||||
"F03": "3a19deae-2c7a-f7c4-12bd-425799425698",
|
||||
"G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955",
|
||||
"G02": "3a19deae-2c7a-204e-95ed-1f1950f28343",
|
||||
"G03": "3a19deae-2c7a-392b-62f1-4907c66343f8",
|
||||
"H01": "3a19deae-2c7a-5602-e876-d27aca4e3201",
|
||||
"H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702",
|
||||
"H03": "3a19deae-2c7a-780b-8965-2e1345f7e834",
|
||||
"I01": "3a19deae-2c7a-8849-e172-07de14ede928",
|
||||
"I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0",
|
||||
"I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4",
|
||||
"J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6",
|
||||
"J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205",
|
||||
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
|
||||
}
|
||||
},
|
||||
"4号手套箱内部堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a1baa20-a7b1-c665-8b9c-d8099d07d2f6",
|
||||
"A02": "3a1baa20-a7b1-93a7-c988-f9c8ad6c58c9",
|
||||
"A03": "3a1baa20-a7b1-00ee-f751-da9b20b6c464",
|
||||
"A04": "3a1baa20-a7b1-4712-c37b-0b5b658ef7b9",
|
||||
"B01": "3a1baa20-a7b1-9847-fc9c-96d604cd1a8e",
|
||||
"B02": "3a1baa20-a7b1-4ae9-e604-0601db06249c",
|
||||
"B03": "3a1baa20-a7b1-8329-ea75-81ca559d9ce1",
|
||||
"B04": "3a1baa20-a7b1-89c5-d96f-36e98a8f7268",
|
||||
"C01": "3a1baa20-a7b1-32ec-39e6-8044733839d6",
|
||||
"C02": "3a1baa20-a7b1-b573-e426-4c86040348b2",
|
||||
"C03": "3a1baa20-a7b1-cca7-781e-0522b729bf5d",
|
||||
"C04": "3a1baa20-a7b1-7c98-5fd9-5855355ae4b3"
|
||||
}
|
||||
},
|
||||
"大分液瓶堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19da3d-4f3d-bcac-2932-7542041e10e0",
|
||||
"A02": "3a19da3d-4f3d-4d75-38ac-fb58ad0687c3",
|
||||
"A03": "3a19da3d-4f3d-b25e-f2b1-85342a5b7eae",
|
||||
"B01": "3a19da3d-4f3d-fd3e-058a-2733a0925767",
|
||||
"B02": "3a19da3d-4f3d-37bd-a944-c391ad56857f",
|
||||
"B03": "3a19da3d-4f3d-e353-7862-c6d1d4bc667f",
|
||||
"C01": "3a19da3d-4f3d-9519-5da7-76179c958e70",
|
||||
"C02": "3a19da3d-4f3d-b586-d7ed-9ec244f6f937",
|
||||
"C03": "3a19da3d-4f3d-5061-249b-35dfef732811"
|
||||
}
|
||||
},
|
||||
"小分液瓶堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"C03": "3a19da40-55bf-8943-d20d-a8b3ea0d16c0"
|
||||
}
|
||||
},
|
||||
"站内Tip头盒堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deab-d5cc-be1e-5c37-4e9e5a664388",
|
||||
"A02": "3a19deab-d5cc-b394-8141-27cb3853e8ea",
|
||||
"B01": "3a19deab-d5cc-4dca-596e-ca7414d5f501",
|
||||
"B02": "3a19deab-d5cc-9bc0-442b-12d9d59aa62a",
|
||||
"C01": "3a19deab-d5cc-2eaf-b6a4-f0d54e4f1246",
|
||||
"C02": "3a19deab-d5cc-d9f4-25df-b8018c372bc7"
|
||||
}
|
||||
},
|
||||
"配液站内配液大板仓库(无需提前上料)": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a1a21dc-06af-3915-9cb9-80a9dc42f386"
|
||||
}
|
||||
},
|
||||
"配液站内配液小板仓库(无需以前入料)": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a1a21de-8e8b-7938-2d06-858b36c10e31"
|
||||
}
|
||||
},
|
||||
"移液站内大瓶板仓库(无需提前如料)": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a1a224c-c727-fa62-1f2b-0037a84b9fca"
|
||||
}
|
||||
},
|
||||
"移液站内小瓶板仓库(无需提前入料)": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb"
|
||||
}
|
||||
},
|
||||
"适配器位仓库": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a1abd46-18fe-1f56-6ced-a1f7fe08e36c"
|
||||
}
|
||||
},
|
||||
"1号2号手套箱交接堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a1baa49-7f77-35aa-60b1-e55a45d065fa"
|
||||
}
|
||||
},
|
||||
"2号手套箱内部堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a1baa4b-393e-9f86-3921-7a18b0a8e371",
|
||||
"A02": "3a1baa4b-393e-9425-928b-ee0f6f679d44",
|
||||
"A03": "3a1baa4b-393e-0baf-632b-59dfdc931a3a",
|
||||
"B01": "3a1baa4b-393e-f8aa-c8a9-956f3132f05c",
|
||||
"B02": "3a1baa4b-393e-ef05-42f6-53f4c6e89d70",
|
||||
"B03": "3a1baa4b-393e-c07b-a924-a9f0dfda9711",
|
||||
"C01": "3a1baa4b-393e-4c2b-821a-16a7fe025c48",
|
||||
"C02": "3a1baa4b-393e-2eaf-61a1-9063c832d5a2",
|
||||
"C03": "3a1baa4b-393e-034e-8e28-8626d934a85f"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# 物料类型配置
|
||||
MATERIAL_TYPE_MAPPINGS = {
|
||||
"烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
|
||||
"试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""),
|
||||
"样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
|
||||
"分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
|
||||
"样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
|
||||
"90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
|
||||
"10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
|
||||
"100ml液体": ("YB_100ml_yeti", "d37166b3-ecaa-481e-bd84-3032b795ba07"),
|
||||
"液": ("YB_ye", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"),
|
||||
"高粘液": ("YB_gaonianye", "abe8df30-563d-43d2-85e0-cabec59ddc16"),
|
||||
"加样头(大)": ("YB_jia_yang_tou_da_Carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "加样头(大)板": ("YB_jia_yang_tou_da", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"),
|
||||
"5ml分液瓶板": ("YB_5ml_fenyepingban", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"),
|
||||
"5ml分液瓶": ("YB_5ml_fenyeping", "3a192c2a-ebb7-58a1-480d-8b3863bf74f4"),
|
||||
"20ml分液瓶板": ("YB_20ml_fenyepingban", "3a192fa4-47db-3449-162a-eaf8aba57e27"),
|
||||
"20ml分液瓶": ("YB_20ml_fenyeping", "3a192c2b-19e8-f0a3-035e-041ca8ca1035"),
|
||||
"配液瓶(小)板": ("YB_peiyepingxiaoban", "3a190c8b-3284-af78-d29f-9a69463ad047"),
|
||||
"配液瓶(小)": ("YB_pei_ye_xiao_Bottle", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"),
|
||||
"配液瓶(大)板": ("YB_peiyepingdaban", "53e50377-32dc-4781-b3c0-5ce45bc7dc27"),
|
||||
"配液瓶(大)": ("YB_pei_ye_da_Bottle", "19c52ad1-51c5-494f-8854-576f4ca9c6ca"),
|
||||
"适配器块": ("YB_shi_pei_qi_kuai", "efc3bb32-d504-4890-91c0-b64ed3ac80cf"),
|
||||
"枪头盒": ("YB_qiang_tou_he", "3a192c2e-20f3-a44a-0334-c8301839d0b3"),
|
||||
"枪头": ("YB_qiang_tou", "b6196971-1050-46da-9927-333e8dea062d"),
|
||||
}
|
||||
|
||||
# 步骤参数配置(各工作流的步骤UUID)
|
||||
WORKFLOW_STEP_IDS = {
|
||||
"reactor_taken_in": {
|
||||
"config": ""
|
||||
SOLID_LIQUID_MAPPINGS = {
|
||||
# 固体
|
||||
"LiDFOB": {
|
||||
"typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
||||
"code": "",
|
||||
"barCode": "",
|
||||
"name": "LiDFOB",
|
||||
"unit": "g",
|
||||
"parameters": "",
|
||||
"quantity": "2",
|
||||
"warningQuantity": "1",
|
||||
"details": []
|
||||
},
|
||||
"liquid_feeding_beaker": {
|
||||
"liquid": "",
|
||||
"observe": ""
|
||||
},
|
||||
"liquid_feeding_vials_non_titration": {
|
||||
"liquid": "",
|
||||
"observe": ""
|
||||
},
|
||||
"liquid_feeding_solvents": {
|
||||
"liquid": "",
|
||||
"observe": ""
|
||||
},
|
||||
"solid_feeding_vials": {
|
||||
"feeding": "",
|
||||
"observe": ""
|
||||
},
|
||||
"liquid_feeding_titration": {
|
||||
"liquid": "",
|
||||
"observe": ""
|
||||
},
|
||||
"drip_back": {
|
||||
"liquid": "",
|
||||
"observe": ""
|
||||
}
|
||||
# "LiPF6": {
|
||||
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
||||
# "code": "",
|
||||
# "barCode": "",
|
||||
# "name": "LiPF6",
|
||||
# "unit": "g",
|
||||
# "parameters": "",
|
||||
# "quantity": 2,
|
||||
# "warningQuantity": 1,
|
||||
# "details": []
|
||||
# },
|
||||
# "LiFSI": {
|
||||
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
||||
# "code": "",
|
||||
# "barCode": "",
|
||||
# "name": "LiFSI",
|
||||
# "unit": "g",
|
||||
# "parameters": "",
|
||||
# "quantity": 2,
|
||||
# "warningQuantity": 1,
|
||||
# "details": []
|
||||
# },
|
||||
# "DTC": {
|
||||
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
||||
# "code": "",
|
||||
# "barCode": "",
|
||||
# "name": "DTC",
|
||||
# "unit": "g",
|
||||
# "parameters": "",
|
||||
# "quantity": 2,
|
||||
# "warningQuantity": 1,
|
||||
# "details": []
|
||||
# },
|
||||
# "LiPO2F2": {
|
||||
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
||||
# "code": "",
|
||||
# "barCode": "",
|
||||
# "name": "LiPO2F2",
|
||||
# "unit": "g",
|
||||
# "parameters": "",
|
||||
# "quantity": 2,
|
||||
# "warningQuantity": 1,
|
||||
# "details": []
|
||||
# },
|
||||
# 液体
|
||||
# "SA": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "EC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "VC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "AND": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "HTCN": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "DENE": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "TMSP": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "TMSB": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "EP": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "DEC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "EMC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "SN": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "DMC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
# "FEC": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||
}
|
||||
|
||||
LOCATION_MAPPING = {}
|
||||
WORKFLOW_MAPPINGS = {}
|
||||
|
||||
ACTION_NAMES = {}
|
||||
|
||||
HTTP_SERVICE_CONFIG = {}
|
||||
LOCATION_MAPPING = {}
|
||||
@@ -1,25 +1,8 @@
|
||||
from datetime import datetime
|
||||
import json
|
||||
import time
|
||||
from typing import Optional, Dict, Any, List
|
||||
from typing_extensions import TypedDict
|
||||
import requests
|
||||
from unilabos.devices.workstation.bioyond_studio.config import API_CONFIG
|
||||
|
||||
from unilabos.devices.workstation.bioyond_studio.bioyond_rpc import BioyondException
|
||||
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation
|
||||
from unilabos.ros.nodes.base_device_node import ROS2DeviceNode, BaseROS2DeviceNode
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import importlib
|
||||
|
||||
class ComputeExperimentDesignReturn(TypedDict):
|
||||
solutions: list
|
||||
titration: dict
|
||||
solvents: dict
|
||||
feeding_order: list
|
||||
return_info: str
|
||||
|
||||
|
||||
class BioyondDispensingStation(BioyondWorkstation):
|
||||
@@ -40,111 +23,6 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
# self._logger = SimpleLogger()
|
||||
# self.is_running = False
|
||||
|
||||
# 用于跟踪任务完成状态的字典: {orderCode: {status, order_id, timestamp}}
|
||||
self.order_completion_status = {}
|
||||
|
||||
def _post_project_api(self, endpoint: str, data: Any) -> Dict[str, Any]:
|
||||
"""项目接口通用POST调用
|
||||
|
||||
参数:
|
||||
endpoint: 接口路径(例如 /api/lims/order/brief-step-paramerers)
|
||||
data: 请求体中的 data 字段内容
|
||||
|
||||
返回:
|
||||
dict: 服务端响应,失败时返回 {code:0,message,...}
|
||||
"""
|
||||
request_data = {
|
||||
"apiKey": API_CONFIG["api_key"],
|
||||
"requestTime": self.hardware_interface.get_current_time_iso8601(),
|
||||
"data": data
|
||||
}
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.hardware_interface.host}{endpoint}",
|
||||
json=request_data,
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=30
|
||||
)
|
||||
result = response.json()
|
||||
return result if isinstance(result, dict) else {"code": 0, "message": "非JSON响应"}
|
||||
except json.JSONDecodeError:
|
||||
return {"code": 0, "message": "非JSON响应"}
|
||||
except requests.exceptions.Timeout:
|
||||
return {"code": 0, "message": "请求超时"}
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {"code": 0, "message": str(e)}
|
||||
|
||||
def _delete_project_api(self, endpoint: str, data: Any) -> Dict[str, Any]:
|
||||
"""项目接口通用DELETE调用
|
||||
|
||||
参数:
|
||||
endpoint: 接口路径(例如 /api/lims/order/workflows)
|
||||
data: 请求体中的 data 字段内容
|
||||
|
||||
返回:
|
||||
dict: 服务端响应,失败时返回 {code:0,message,...}
|
||||
"""
|
||||
request_data = {
|
||||
"apiKey": API_CONFIG["api_key"],
|
||||
"requestTime": self.hardware_interface.get_current_time_iso8601(),
|
||||
"data": data
|
||||
}
|
||||
try:
|
||||
response = requests.delete(
|
||||
f"{self.hardware_interface.host}{endpoint}",
|
||||
json=request_data,
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=30
|
||||
)
|
||||
result = response.json()
|
||||
return result if isinstance(result, dict) else {"code": 0, "message": "非JSON响应"}
|
||||
except json.JSONDecodeError:
|
||||
return {"code": 0, "message": "非JSON响应"}
|
||||
except requests.exceptions.Timeout:
|
||||
return {"code": 0, "message": "请求超时"}
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {"code": 0, "message": str(e)}
|
||||
|
||||
def compute_experiment_design(
|
||||
self,
|
||||
ratio: dict,
|
||||
wt_percent: str = "0.25",
|
||||
m_tot: str = "70",
|
||||
titration_percent: str = "0.03",
|
||||
) -> ComputeExperimentDesignReturn:
|
||||
try:
|
||||
if isinstance(ratio, str):
|
||||
try:
|
||||
ratio = json.loads(ratio)
|
||||
except Exception:
|
||||
ratio = {}
|
||||
root = str(Path(__file__).resolve().parents[3])
|
||||
if root not in sys.path:
|
||||
sys.path.append(root)
|
||||
try:
|
||||
mod = importlib.import_module("tem.compute")
|
||||
except Exception as e:
|
||||
raise BioyondException(f"无法导入计算模块: {e}")
|
||||
try:
|
||||
wp = float(wt_percent) if isinstance(wt_percent, str) else wt_percent
|
||||
mt = float(m_tot) if isinstance(m_tot, str) else m_tot
|
||||
tp = float(titration_percent) if isinstance(titration_percent, str) else titration_percent
|
||||
except Exception as e:
|
||||
raise BioyondException(f"参数解析失败: {e}")
|
||||
res = mod.generate_experiment_design(ratio=ratio, wt_percent=wp, m_tot=mt, titration_percent=tp)
|
||||
out = {
|
||||
"solutions": res.get("solutions", []),
|
||||
"titration": res.get("titration", {}),
|
||||
"solvents": res.get("solvents", {}),
|
||||
"feeding_order": res.get("feeding_order", []),
|
||||
"return_info": json.dumps(res, ensure_ascii=False)
|
||||
}
|
||||
return out
|
||||
except BioyondException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise BioyondException(str(e))
|
||||
|
||||
# 90%10%小瓶投料任务创建方法
|
||||
def create_90_10_vial_feeding_task(self,
|
||||
order_name: str = None,
|
||||
@@ -392,45 +270,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
# 7. 调用create_order方法创建任务
|
||||
result = self.hardware_interface.create_order(json_str)
|
||||
self.hardware_interface._logger.info(f"创建90%10%小瓶投料任务结果: {result}")
|
||||
|
||||
# 8. 解析结果获取order_id
|
||||
order_id = None
|
||||
if isinstance(result, str):
|
||||
# result 格式: "{'3a1d895c-4d39-d504-1398-18f5a40bac1e': [{'id': '...', ...}]}"
|
||||
# 第一个键就是order_id (UUID)
|
||||
try:
|
||||
# 尝试解析字符串为字典
|
||||
import ast
|
||||
result_dict = ast.literal_eval(result)
|
||||
# 获取第一个键作为order_id
|
||||
if result_dict and isinstance(result_dict, dict):
|
||||
first_key = list(result_dict.keys())[0]
|
||||
order_id = first_key
|
||||
self.hardware_interface._logger.info(f"✓ 成功提取order_id: {order_id}")
|
||||
else:
|
||||
self.hardware_interface._logger.warning(f"result_dict格式异常: {result_dict}")
|
||||
except Exception as e:
|
||||
self.hardware_interface._logger.error(f"✗ 无法从结果中提取order_id: {e}, result类型={type(result)}")
|
||||
elif isinstance(result, dict):
|
||||
# 如果已经是字典
|
||||
if result:
|
||||
first_key = list(result.keys())[0]
|
||||
order_id = first_key
|
||||
self.hardware_interface._logger.info(f"✓ 成功提取order_id(dict): {order_id}")
|
||||
|
||||
if not order_id:
|
||||
self.hardware_interface._logger.warning(
|
||||
f"⚠ 未能提取order_id,result={result[:100] if isinstance(result, str) else result}"
|
||||
)
|
||||
|
||||
# 返回成功结果和构建的JSON数据
|
||||
return json.dumps({
|
||||
"suc": True,
|
||||
"order_code": order_code,
|
||||
"order_id": order_id,
|
||||
"result": result,
|
||||
"order_params": order_data
|
||||
})
|
||||
return json.dumps({"suc": True})
|
||||
|
||||
except BioyondException:
|
||||
# 重新抛出BioyondException
|
||||
@@ -558,37 +398,7 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
result = self.hardware_interface.create_order(json_str)
|
||||
self.hardware_interface._logger.info(f"创建二胺溶液配置任务结果: {result}")
|
||||
|
||||
# 8. 解析结果获取order_id
|
||||
order_id = None
|
||||
if isinstance(result, str):
|
||||
try:
|
||||
import ast
|
||||
result_dict = ast.literal_eval(result)
|
||||
if result_dict and isinstance(result_dict, dict):
|
||||
first_key = list(result_dict.keys())[0]
|
||||
order_id = first_key
|
||||
self.hardware_interface._logger.info(f"✓ 成功提取order_id: {order_id}")
|
||||
else:
|
||||
self.hardware_interface._logger.warning(f"result_dict格式异常: {result_dict}")
|
||||
except Exception as e:
|
||||
self.hardware_interface._logger.error(f"✗ 无法从结果中提取order_id: {e}")
|
||||
elif isinstance(result, dict):
|
||||
if result:
|
||||
first_key = list(result.keys())[0]
|
||||
order_id = first_key
|
||||
self.hardware_interface._logger.info(f"✓ 成功提取order_id(dict): {order_id}")
|
||||
|
||||
if not order_id:
|
||||
self.hardware_interface._logger.warning(f"⚠ 未能提取order_id")
|
||||
|
||||
# 返回成功结果和构建的JSON数据
|
||||
return json.dumps({
|
||||
"suc": True,
|
||||
"order_code": order_code,
|
||||
"order_id": order_id,
|
||||
"result": result,
|
||||
"order_params": order_data
|
||||
})
|
||||
return json.dumps({"suc": True})
|
||||
|
||||
except BioyondException:
|
||||
# 重新抛出BioyondException
|
||||
@@ -689,24 +499,15 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
hold_m_name=hold_m_name
|
||||
)
|
||||
|
||||
# 解析返回结果以获取order_code和order_id
|
||||
result_data = json.loads(result) if isinstance(result, str) else result
|
||||
order_code = result_data.get("order_code")
|
||||
order_id = result_data.get("order_id")
|
||||
order_params = result_data.get("order_params", {})
|
||||
|
||||
results.append({
|
||||
"index": idx + 1,
|
||||
"name": name,
|
||||
"success": True,
|
||||
"order_code": order_code,
|
||||
"order_id": order_id,
|
||||
"hold_m_name": hold_m_name,
|
||||
"order_params": order_params
|
||||
"hold_m_name": hold_m_name
|
||||
})
|
||||
success_count += 1
|
||||
self.hardware_interface._logger.info(
|
||||
f"成功创建二胺溶液配置任务: {name}, order_code={order_code}, order_id={order_id}"
|
||||
f"成功创建二胺溶液配置任务: {name}"
|
||||
)
|
||||
|
||||
except BioyondException as e:
|
||||
@@ -732,17 +533,11 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
f"创建第 {idx + 1} 个任务时发生未知错误: {str(e)}"
|
||||
)
|
||||
|
||||
# 提取所有成功任务的order_code和order_id
|
||||
order_codes = [r["order_code"] for r in results if r["success"]]
|
||||
order_ids = [r["order_id"] for r in results if r["success"]]
|
||||
|
||||
# 返回汇总结果
|
||||
summary = {
|
||||
"total": len(solutions),
|
||||
"success": success_count,
|
||||
"failed": failed_count,
|
||||
"order_codes": order_codes,
|
||||
"order_ids": order_ids,
|
||||
"details": results
|
||||
}
|
||||
|
||||
@@ -751,13 +546,8 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
f"成功={success_count}, 失败={failed_count}"
|
||||
)
|
||||
|
||||
# 构建返回结果
|
||||
summary["return_info"] = {
|
||||
"order_codes": order_codes,
|
||||
"order_ids": order_ids,
|
||||
}
|
||||
|
||||
return summary
|
||||
# 返回JSON字符串格式
|
||||
return json.dumps(summary, ensure_ascii=False)
|
||||
|
||||
except BioyondException:
|
||||
raise
|
||||
@@ -766,40 +556,6 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
self.hardware_interface._logger.error(error_msg)
|
||||
raise BioyondException(error_msg)
|
||||
|
||||
def brief_step_parameters(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""获取简要步骤参数(站点项目接口)
|
||||
|
||||
参数:
|
||||
data: 查询参数字典
|
||||
|
||||
返回值:
|
||||
dict: 接口返回数据
|
||||
"""
|
||||
return self._post_project_api("/api/lims/order/brief-step-paramerers", data)
|
||||
|
||||
def project_order_report(self, order_id: str) -> Dict[str, Any]:
|
||||
"""查询项目端订单报告(兼容旧路径)
|
||||
|
||||
参数:
|
||||
order_id: 订单ID
|
||||
|
||||
返回值:
|
||||
dict: 报告数据
|
||||
"""
|
||||
return self._post_project_api("/api/lims/order/project-order-report", order_id)
|
||||
|
||||
def workflow_sample_locations(self, workflow_id: str) -> Dict[str, Any]:
|
||||
"""查询工作流样品库位(站点项目接口)
|
||||
|
||||
参数:
|
||||
workflow_id: 工作流ID
|
||||
|
||||
返回值:
|
||||
dict: 位置信息数据
|
||||
"""
|
||||
return self._post_project_api("/api/lims/storage/workflow-sample-locations", workflow_id)
|
||||
|
||||
|
||||
# 批量创建90%10%小瓶投料任务
|
||||
def batch_create_90_10_vial_feeding_tasks(self,
|
||||
titration,
|
||||
@@ -857,15 +613,22 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
if not all([name, main_portion is not None, titration_portion is not None, titration_solvent is not None]):
|
||||
raise BioyondException("titration 数据缺少必要参数")
|
||||
|
||||
# 将main_portion平均分成3份作为90%物料(3个小瓶)
|
||||
portion_90 = main_portion / 3
|
||||
|
||||
# 调用单个任务创建方法
|
||||
result = self.create_90_10_vial_feeding_task(
|
||||
order_name=f"90%10%小瓶投料-{name}",
|
||||
speed=speed,
|
||||
temperature=temperature,
|
||||
delay_time=delay_time,
|
||||
# 90%物料 - 主称固体直接使用main_portion
|
||||
# 90%物料 - 主称固体平均分成3份
|
||||
percent_90_1_assign_material_name=name,
|
||||
percent_90_1_target_weigh=str(round(main_portion, 6)),
|
||||
percent_90_1_target_weigh=str(round(portion_90, 6)),
|
||||
percent_90_2_assign_material_name=name,
|
||||
percent_90_2_target_weigh=str(round(portion_90, 6)),
|
||||
percent_90_3_assign_material_name=name,
|
||||
percent_90_3_target_weigh=str(round(portion_90, 6)),
|
||||
# 10%物料 - 滴定固体 + 滴定溶剂(只使用第1个10%小瓶)
|
||||
percent_10_1_assign_material_name=name,
|
||||
percent_10_1_target_weigh=str(round(titration_portion, 6)),
|
||||
@@ -874,54 +637,29 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
hold_m_name=hold_m_name
|
||||
)
|
||||
|
||||
# 解析返回结果以获取order_code和order_id
|
||||
result_data = json.loads(result) if isinstance(result, str) else result
|
||||
order_code = result_data.get("order_code")
|
||||
order_id = result_data.get("order_id")
|
||||
order_params = result_data.get("order_params", {})
|
||||
|
||||
# 构建详细信息(保持原有结构)
|
||||
detail = {
|
||||
"index": 1,
|
||||
"name": name,
|
||||
summary = {
|
||||
"success": True,
|
||||
"order_code": order_code,
|
||||
"order_id": order_id,
|
||||
"hold_m_name": hold_m_name,
|
||||
"material_name": name,
|
||||
"90_vials": {
|
||||
"count": 1,
|
||||
"weight_per_vial": round(main_portion, 6),
|
||||
"count": 3,
|
||||
"weight_per_vial": round(portion_90, 6),
|
||||
"total_weight": round(main_portion, 6)
|
||||
},
|
||||
"10_vials": {
|
||||
"count": 1,
|
||||
"solid_weight": round(titration_portion, 6),
|
||||
"liquid_volume": round(titration_solvent, 6)
|
||||
},
|
||||
"order_params": order_params
|
||||
}
|
||||
|
||||
# 构建批量结果格式(与diamine_solution_tasks保持一致)
|
||||
summary = {
|
||||
"total": 1,
|
||||
"success": 1,
|
||||
"failed": 0,
|
||||
"order_codes": [order_code],
|
||||
"order_ids": [order_id],
|
||||
"details": [detail]
|
||||
}
|
||||
}
|
||||
|
||||
self.hardware_interface._logger.info(
|
||||
f"成功创建90%10%小瓶投料任务: {name}, order_code={order_code}, order_id={order_id}"
|
||||
f"成功创建90%10%小瓶投料任务: {hold_m_name}, "
|
||||
f"90%物料={portion_90:.6f}g×3, 10%物料={titration_portion:.6f}g+{titration_solvent:.6f}mL"
|
||||
)
|
||||
|
||||
# 构建返回结果
|
||||
summary["return_info"] = {
|
||||
"order_codes": [order_code],
|
||||
"order_ids": [order_id],
|
||||
}
|
||||
|
||||
return summary
|
||||
# 返回JSON字符串格式
|
||||
return json.dumps(summary, ensure_ascii=False)
|
||||
|
||||
except BioyondException:
|
||||
raise
|
||||
@@ -930,571 +668,6 @@ class BioyondDispensingStation(BioyondWorkstation):
|
||||
self.hardware_interface._logger.error(error_msg)
|
||||
raise BioyondException(error_msg)
|
||||
|
||||
def _extract_actuals_from_report(self, report) -> Dict[str, Any]:
|
||||
data = report.get('data') if isinstance(report, dict) else None
|
||||
actual_target_weigh = None
|
||||
actual_volume = None
|
||||
if data:
|
||||
extra = data.get('extraProperties') or {}
|
||||
if isinstance(extra, dict):
|
||||
for v in extra.values():
|
||||
obj = None
|
||||
try:
|
||||
obj = json.loads(v) if isinstance(v, str) else v
|
||||
except Exception:
|
||||
obj = None
|
||||
if isinstance(obj, dict):
|
||||
tw = obj.get('targetWeigh')
|
||||
vol = obj.get('volume')
|
||||
if tw is not None:
|
||||
try:
|
||||
actual_target_weigh = float(tw)
|
||||
except Exception:
|
||||
pass
|
||||
if vol is not None:
|
||||
try:
|
||||
actual_volume = float(vol)
|
||||
except Exception:
|
||||
pass
|
||||
return {
|
||||
'actualTargetWeigh': actual_target_weigh,
|
||||
'actualVolume': actual_volume
|
||||
}
|
||||
|
||||
# 等待多个任务完成并获取实验报告
|
||||
def wait_for_multiple_orders_and_get_reports(self,
|
||||
batch_create_result: str = None,
|
||||
timeout: int = 7200,
|
||||
check_interval: int = 10) -> Dict[str, Any]:
|
||||
"""
|
||||
同时等待多个任务完成并获取实验报告
|
||||
|
||||
参数说明:
|
||||
- batch_create_result: 批量创建任务的返回结果JSON字符串,包含order_codes和order_ids数组
|
||||
- timeout: 超时时间(秒),默认7200秒(2小时)
|
||||
- check_interval: 检查间隔(秒),默认10秒
|
||||
|
||||
返回: 包含所有任务状态和报告的字典
|
||||
{
|
||||
"total": 2,
|
||||
"completed": 2,
|
||||
"timeout": 0,
|
||||
"elapsed_time": 120.5,
|
||||
"reports": [
|
||||
{
|
||||
"order_code": "task_vial_1",
|
||||
"order_id": "uuid1",
|
||||
"status": "completed",
|
||||
"completion_status": 30,
|
||||
"report": {...}
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
异常:
|
||||
- BioyondException: 所有任务都超时或发生错误
|
||||
"""
|
||||
try:
|
||||
# 参数类型转换
|
||||
timeout = int(timeout) if timeout else 7200
|
||||
check_interval = int(check_interval) if check_interval else 10
|
||||
|
||||
# 验证batch_create_result参数
|
||||
if not batch_create_result or batch_create_result == "":
|
||||
raise BioyondException("batch_create_result参数为空,请确保从batch_create节点正确连接handle")
|
||||
|
||||
# 解析batch_create_result JSON对象
|
||||
try:
|
||||
# 清理可能存在的截断标记 [...]
|
||||
if isinstance(batch_create_result, str) and '[...]' in batch_create_result:
|
||||
batch_create_result = batch_create_result.replace('[...]', '[]')
|
||||
|
||||
result_obj = json.loads(batch_create_result) if isinstance(batch_create_result, str) else batch_create_result
|
||||
|
||||
# 兼容外层包装格式 {error, suc, return_value}
|
||||
if isinstance(result_obj, dict) and "return_value" in result_obj:
|
||||
inner = result_obj.get("return_value")
|
||||
if isinstance(inner, str):
|
||||
result_obj = json.loads(inner)
|
||||
elif isinstance(inner, dict):
|
||||
result_obj = inner
|
||||
|
||||
# 从summary对象中提取order_codes和order_ids
|
||||
order_codes = result_obj.get("order_codes", [])
|
||||
order_ids = result_obj.get("order_ids", [])
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
raise BioyondException(f"解析batch_create_result失败: {e}")
|
||||
except Exception as e:
|
||||
raise BioyondException(f"处理batch_create_result时出错: {e}")
|
||||
|
||||
# 验证提取的数据
|
||||
if not order_codes:
|
||||
raise BioyondException("batch_create_result中未找到order_codes字段或为空")
|
||||
if not order_ids:
|
||||
raise BioyondException("batch_create_result中未找到order_ids字段或为空")
|
||||
|
||||
# 确保order_codes和order_ids是列表类型
|
||||
if not isinstance(order_codes, list):
|
||||
order_codes = [order_codes] if order_codes else []
|
||||
if not isinstance(order_ids, list):
|
||||
order_ids = [order_ids] if order_ids else []
|
||||
|
||||
codes_list = order_codes
|
||||
ids_list = order_ids
|
||||
|
||||
if len(codes_list) != len(ids_list):
|
||||
raise BioyondException(
|
||||
f"order_codes数量({len(codes_list)})与order_ids数量({len(ids_list)})不匹配"
|
||||
)
|
||||
|
||||
if not codes_list or not ids_list:
|
||||
raise BioyondException("order_codes和order_ids不能为空")
|
||||
|
||||
# 初始化跟踪变量
|
||||
total = len(codes_list)
|
||||
pending_orders = {code: {"order_id": ids_list[i], "completed": False}
|
||||
for i, code in enumerate(codes_list)}
|
||||
reports = []
|
||||
|
||||
start_time = time.time()
|
||||
self.hardware_interface._logger.info(
|
||||
f"开始等待 {total} 个任务完成: {', '.join(codes_list)}"
|
||||
)
|
||||
|
||||
# 轮询检查任务状态
|
||||
while pending_orders:
|
||||
elapsed_time = time.time() - start_time
|
||||
|
||||
# 检查超时
|
||||
if elapsed_time > timeout:
|
||||
# 收集超时任务
|
||||
timeout_orders = list(pending_orders.keys())
|
||||
self.hardware_interface._logger.error(
|
||||
f"等待任务完成超时,剩余未完成任务: {', '.join(timeout_orders)}"
|
||||
)
|
||||
|
||||
# 为超时任务添加记录
|
||||
for order_code in timeout_orders:
|
||||
reports.append({
|
||||
"order_code": order_code,
|
||||
"order_id": pending_orders[order_code]["order_id"],
|
||||
"status": "timeout",
|
||||
"completion_status": None,
|
||||
"report": None,
|
||||
"extracted": None,
|
||||
"elapsed_time": elapsed_time
|
||||
})
|
||||
|
||||
break
|
||||
|
||||
# 检查每个待完成的任务
|
||||
completed_in_this_round = []
|
||||
for order_code in list(pending_orders.keys()):
|
||||
order_id = pending_orders[order_code]["order_id"]
|
||||
|
||||
# 检查任务是否完成
|
||||
if order_code in self.order_completion_status:
|
||||
completion_info = self.order_completion_status[order_code]
|
||||
self.hardware_interface._logger.info(
|
||||
f"检测到任务 {order_code} 已完成,状态: {completion_info.get('status')}"
|
||||
)
|
||||
|
||||
# 获取实验报告
|
||||
try:
|
||||
report = self.project_order_report(order_id)
|
||||
|
||||
if not report:
|
||||
self.hardware_interface._logger.warning(
|
||||
f"任务 {order_code} 已完成但无法获取报告"
|
||||
)
|
||||
report = {"error": "无法获取报告"}
|
||||
else:
|
||||
self.hardware_interface._logger.info(
|
||||
f"成功获取任务 {order_code} 的实验报告"
|
||||
)
|
||||
|
||||
reports.append({
|
||||
"order_code": order_code,
|
||||
"order_id": order_id,
|
||||
"status": "completed",
|
||||
"completion_status": completion_info.get('status'),
|
||||
"report": report,
|
||||
"extracted": self._extract_actuals_from_report(report),
|
||||
"elapsed_time": elapsed_time
|
||||
})
|
||||
|
||||
# 标记为已完成
|
||||
completed_in_this_round.append(order_code)
|
||||
|
||||
# 清理完成状态记录
|
||||
del self.order_completion_status[order_code]
|
||||
|
||||
except Exception as e:
|
||||
self.hardware_interface._logger.error(
|
||||
f"查询任务 {order_code} 报告失败: {str(e)}"
|
||||
)
|
||||
reports.append({
|
||||
"order_code": order_code,
|
||||
"order_id": order_id,
|
||||
"status": "error",
|
||||
"completion_status": completion_info.get('status'),
|
||||
"report": None,
|
||||
"extracted": None,
|
||||
"error": str(e),
|
||||
"elapsed_time": elapsed_time
|
||||
})
|
||||
completed_in_this_round.append(order_code)
|
||||
|
||||
# 从待完成列表中移除已完成的任务
|
||||
for order_code in completed_in_this_round:
|
||||
del pending_orders[order_code]
|
||||
|
||||
# 如果还有待完成的任务,等待后继续
|
||||
if pending_orders:
|
||||
time.sleep(check_interval)
|
||||
|
||||
# 每分钟记录一次等待状态
|
||||
new_elapsed_time = time.time() - start_time
|
||||
if int(new_elapsed_time) % 60 == 0 and new_elapsed_time > 0:
|
||||
self.hardware_interface._logger.info(
|
||||
f"批量等待任务中... 已完成 {len(reports)}/{total}, "
|
||||
f"待完成: {', '.join(pending_orders.keys())}, "
|
||||
f"已等待 {int(new_elapsed_time/60)} 分钟"
|
||||
)
|
||||
|
||||
# 统计结果
|
||||
completed_count = sum(1 for r in reports if r['status'] == 'completed')
|
||||
timeout_count = sum(1 for r in reports if r['status'] == 'timeout')
|
||||
error_count = sum(1 for r in reports if r['status'] == 'error')
|
||||
|
||||
final_elapsed_time = time.time() - start_time
|
||||
|
||||
summary = {
|
||||
"total": total,
|
||||
"completed": completed_count,
|
||||
"timeout": timeout_count,
|
||||
"error": error_count,
|
||||
"elapsed_time": round(final_elapsed_time, 2),
|
||||
"reports": reports
|
||||
}
|
||||
|
||||
self.hardware_interface._logger.info(
|
||||
f"批量等待任务完成: 总数={total}, 成功={completed_count}, "
|
||||
f"超时={timeout_count}, 错误={error_count}, 耗时={final_elapsed_time:.1f}秒"
|
||||
)
|
||||
|
||||
# 返回字典格式,在顶层包含统计信息
|
||||
return {
|
||||
"return_info": json.dumps(summary, ensure_ascii=False)
|
||||
}
|
||||
|
||||
except BioyondException:
|
||||
raise
|
||||
except Exception as e:
|
||||
error_msg = f"批量等待任务完成时发生未预期的错误: {str(e)}"
|
||||
self.hardware_interface._logger.error(error_msg)
|
||||
raise BioyondException(error_msg)
|
||||
|
||||
def process_order_finish_report(self, report_request, used_materials) -> Dict[str, Any]:
|
||||
"""
|
||||
重写父类方法,处理任务完成报送并记录到 order_completion_status
|
||||
|
||||
Args:
|
||||
report_request: WorkstationReportRequest 对象,包含任务完成信息
|
||||
used_materials: 物料使用记录列表
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: 处理结果
|
||||
"""
|
||||
try:
|
||||
# 调用父类方法
|
||||
result = super().process_order_finish_report(report_request, used_materials)
|
||||
|
||||
# 记录任务完成状态
|
||||
data = report_request.data
|
||||
order_code = data.get('orderCode')
|
||||
|
||||
if order_code:
|
||||
self.order_completion_status[order_code] = {
|
||||
'status': data.get('status'),
|
||||
'order_name': data.get('orderName'),
|
||||
'timestamp': datetime.now().isoformat(),
|
||||
'start_time': data.get('startTime'),
|
||||
'end_time': data.get('endTime')
|
||||
}
|
||||
|
||||
self.hardware_interface._logger.info(
|
||||
f"已记录任务完成状态: {order_code}, status={data.get('status')}"
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
self.hardware_interface._logger.error(f"处理任务完成报送失败: {e}")
|
||||
return {"processed": False, "error": str(e)}
|
||||
|
||||
def transfer_materials_to_reaction_station(
|
||||
self,
|
||||
target_device_id: str,
|
||||
transfer_groups: list
|
||||
) -> dict:
|
||||
"""
|
||||
将配液站完成的物料转移到指定反应站的堆栈库位
|
||||
支持多组转移任务,每组包含物料名称、目标堆栈和目标库位
|
||||
|
||||
Args:
|
||||
target_device_id: 目标反应站设备ID(所有转移组使用同一个设备)
|
||||
transfer_groups: 转移任务组列表,每组包含:
|
||||
- materials: 物料名称(字符串,将通过RPC查询)
|
||||
- target_stack: 目标堆栈名称(如"堆栈1左")
|
||||
- target_sites: 目标库位(如"A01")
|
||||
|
||||
Returns:
|
||||
dict: 转移结果
|
||||
{
|
||||
"success": bool,
|
||||
"total_groups": int,
|
||||
"successful_groups": int,
|
||||
"failed_groups": int,
|
||||
"target_device_id": str,
|
||||
"details": [...]
|
||||
}
|
||||
"""
|
||||
try:
|
||||
# 验证参数
|
||||
if not target_device_id:
|
||||
raise ValueError("目标设备ID不能为空")
|
||||
|
||||
if not transfer_groups:
|
||||
raise ValueError("转移任务组列表不能为空")
|
||||
|
||||
if not isinstance(transfer_groups, list):
|
||||
raise ValueError("transfer_groups必须是列表类型")
|
||||
|
||||
# 标准化设备ID格式: 确保以 /devices/ 开头
|
||||
if not target_device_id.startswith("/devices/"):
|
||||
if target_device_id.startswith("/"):
|
||||
target_device_id = f"/devices{target_device_id}"
|
||||
else:
|
||||
target_device_id = f"/devices/{target_device_id}"
|
||||
|
||||
self.hardware_interface._logger.info(
|
||||
f"目标设备ID标准化为: {target_device_id}"
|
||||
)
|
||||
|
||||
self.hardware_interface._logger.info(
|
||||
f"开始执行批量物料转移: {len(transfer_groups)}组任务 -> {target_device_id}"
|
||||
)
|
||||
|
||||
from .config import WAREHOUSE_MAPPING
|
||||
results = []
|
||||
successful_count = 0
|
||||
failed_count = 0
|
||||
|
||||
for idx, group in enumerate(transfer_groups, 1):
|
||||
try:
|
||||
# 提取参数
|
||||
material_name = group.get("materials", "")
|
||||
target_stack = group.get("target_stack", "")
|
||||
target_sites = group.get("target_sites", "")
|
||||
|
||||
# 验证必填参数
|
||||
if not material_name:
|
||||
raise ValueError(f"第{idx}组: 物料名称不能为空")
|
||||
if not target_stack:
|
||||
raise ValueError(f"第{idx}组: 目标堆栈不能为空")
|
||||
if not target_sites:
|
||||
raise ValueError(f"第{idx}组: 目标库位不能为空")
|
||||
|
||||
self.hardware_interface._logger.info(
|
||||
f"处理第{idx}组转移: {material_name} -> "
|
||||
f"{target_device_id}/{target_stack}/{target_sites}"
|
||||
)
|
||||
|
||||
# 通过物料名称从deck获取ResourcePLR对象
|
||||
try:
|
||||
material_resource = self.deck.get_resource(material_name)
|
||||
if not material_resource:
|
||||
raise ValueError(f"在deck中未找到物料: {material_name}")
|
||||
|
||||
self.hardware_interface._logger.info(
|
||||
f"从deck获取到物料 {material_name}: {material_resource}"
|
||||
)
|
||||
except Exception as e:
|
||||
raise ValueError(
|
||||
f"获取物料 {material_name} 失败: {str(e)},请确认物料已正确加载到deck中"
|
||||
)
|
||||
|
||||
# 验证目标堆栈是否存在
|
||||
if target_stack not in WAREHOUSE_MAPPING:
|
||||
raise ValueError(
|
||||
f"未知的堆栈名称: {target_stack},"
|
||||
f"可选值: {list(WAREHOUSE_MAPPING.keys())}"
|
||||
)
|
||||
|
||||
# 验证库位是否有效
|
||||
stack_sites = WAREHOUSE_MAPPING[target_stack].get("site_uuids", {})
|
||||
if target_sites not in stack_sites:
|
||||
raise ValueError(
|
||||
f"库位 {target_sites} 不存在于堆栈 {target_stack} 中,"
|
||||
f"可选库位: {list(stack_sites.keys())}"
|
||||
)
|
||||
|
||||
# 获取目标库位的UUID
|
||||
target_site_uuid = stack_sites[target_sites]
|
||||
if not target_site_uuid:
|
||||
raise ValueError(
|
||||
f"库位 {target_sites} 的 UUID 未配置,请在 WAREHOUSE_MAPPING 中完善"
|
||||
)
|
||||
|
||||
# 目标位点(包含UUID)
|
||||
future = ROS2DeviceNode.run_async_func(
|
||||
self._ros_node.get_resource_with_dir,
|
||||
True,
|
||||
**{
|
||||
"resource_id": f"/reaction_station_bioyond/Bioyond_Deck/{target_stack}",
|
||||
"with_children": True,
|
||||
},
|
||||
)
|
||||
# 等待异步完成后再获取结果
|
||||
if not future:
|
||||
raise ValueError(f"获取目标堆栈资源future无效: {target_stack}")
|
||||
while not future.done():
|
||||
time.sleep(0.1)
|
||||
target_site_resource = future.result()
|
||||
|
||||
# 调用父类的 transfer_resource_to_another 方法
|
||||
# 传入ResourcePLR对象和目标位点资源
|
||||
future = self.transfer_resource_to_another(
|
||||
resource=[material_resource],
|
||||
mount_resource=[target_site_resource],
|
||||
sites=[target_sites],
|
||||
mount_device_id=target_device_id
|
||||
)
|
||||
|
||||
# 等待异步任务完成(轮询直到完成,再取结果)
|
||||
if future:
|
||||
try:
|
||||
while not future.done():
|
||||
time.sleep(0.1)
|
||||
future.result()
|
||||
self.hardware_interface._logger.info(
|
||||
f"异步转移任务已完成: {material_name}"
|
||||
)
|
||||
except Exception as e:
|
||||
raise ValueError(f"转移任务执行失败: {str(e)}")
|
||||
|
||||
self.hardware_interface._logger.info(
|
||||
f"第{idx}组转移成功: {material_name} -> "
|
||||
f"{target_device_id}/{target_stack}/{target_sites}"
|
||||
)
|
||||
|
||||
successful_count += 1
|
||||
results.append({
|
||||
"group_index": idx,
|
||||
"success": True,
|
||||
"material_name": material_name,
|
||||
"target_stack": target_stack,
|
||||
"target_site": target_sites,
|
||||
"message": "转移成功"
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"第{idx}组转移失败: {str(e)}"
|
||||
self.hardware_interface._logger.error(error_msg)
|
||||
failed_count += 1
|
||||
results.append({
|
||||
"group_index": idx,
|
||||
"success": False,
|
||||
"material_name": group.get("materials", ""),
|
||||
"error": str(e)
|
||||
})
|
||||
|
||||
# 返回汇总结果
|
||||
return {
|
||||
"success": failed_count == 0,
|
||||
"total_groups": len(transfer_groups),
|
||||
"successful_groups": successful_count,
|
||||
"failed_groups": failed_count,
|
||||
"target_device_id": target_device_id,
|
||||
"details": results,
|
||||
"message": f"完成 {len(transfer_groups)} 组转移任务到 {target_device_id}: "
|
||||
f"{successful_count} 成功, {failed_count} 失败"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"批量转移物料失败: {str(e)}"
|
||||
self.hardware_interface._logger.error(error_msg)
|
||||
return {
|
||||
"success": False,
|
||||
"total_groups": len(transfer_groups) if transfer_groups else 0,
|
||||
"successful_groups": 0,
|
||||
"failed_groups": len(transfer_groups) if transfer_groups else 0,
|
||||
"target_device_id": target_device_id if target_device_id else "",
|
||||
"error": error_msg
|
||||
}
|
||||
|
||||
def query_resource_by_name(self, material_name: str):
|
||||
"""
|
||||
通过物料名称查询资源对象(适用于Bioyond系统)
|
||||
|
||||
Args:
|
||||
material_name: 物料名称
|
||||
|
||||
Returns:
|
||||
物料ID或None
|
||||
"""
|
||||
try:
|
||||
# Bioyond系统使用material_cache存储物料信息
|
||||
if not hasattr(self.hardware_interface, 'material_cache'):
|
||||
self.hardware_interface._logger.error(
|
||||
"hardware_interface没有material_cache属性"
|
||||
)
|
||||
return None
|
||||
|
||||
material_cache = self.hardware_interface.material_cache
|
||||
|
||||
self.hardware_interface._logger.info(
|
||||
f"查询物料 '{material_name}', 缓存中共有 {len(material_cache)} 个物料"
|
||||
)
|
||||
|
||||
# 调试: 打印前几个物料信息
|
||||
if material_cache:
|
||||
cache_items = list(material_cache.items())[:5]
|
||||
for name, material_id in cache_items:
|
||||
self.hardware_interface._logger.debug(
|
||||
f"缓存物料: name={name}, id={material_id}"
|
||||
)
|
||||
|
||||
# 直接从缓存中查找
|
||||
if material_name in material_cache:
|
||||
material_id = material_cache[material_name]
|
||||
self.hardware_interface._logger.info(
|
||||
f"找到物料: {material_name} -> ID: {material_id}"
|
||||
)
|
||||
return material_id
|
||||
|
||||
self.hardware_interface._logger.warning(
|
||||
f"未找到物料: {material_name} (缓存中无此物料)"
|
||||
)
|
||||
|
||||
# 打印所有可用物料名称供参考
|
||||
available_materials = list(material_cache.keys())
|
||||
if available_materials:
|
||||
self.hardware_interface._logger.info(
|
||||
f"可用物料列表(前10个): {available_materials[:10]}"
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
self.hardware_interface._logger.error(
|
||||
f"查询物料失败 {material_name}: {str(e)}"
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bioyond = BioyondDispensingStation(config={
|
||||
@@ -1916,3 +1089,4 @@ if __name__ == "__main__":
|
||||
|
||||
# id = "3a1bce3c-4f31-c8f3-5525-f3b273bc34dc"
|
||||
# bioyond.sample_waste_removal(id)
|
||||
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import json
|
||||
import time
|
||||
import requests
|
||||
from typing import List, Dict, Any
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from unilabos.devices.workstation.bioyond_studio.station import BioyondWorkstation
|
||||
from unilabos.devices.workstation.bioyond_studio.bioyond_rpc import MachineState
|
||||
from unilabos.ros.msgs.message_converter import convert_to_ros_msg, Float64, String
|
||||
from unilabos.devices.workstation.bioyond_studio.config import (
|
||||
WORKFLOW_STEP_IDS,
|
||||
WORKFLOW_TO_SECTION_MAP,
|
||||
@@ -15,37 +10,6 @@ from unilabos.devices.workstation.bioyond_studio.config import (
|
||||
from unilabos.devices.workstation.bioyond_studio.config import API_CONFIG
|
||||
|
||||
|
||||
class BioyondReactor:
|
||||
def __init__(self, config: dict = None, deck=None, protocol_type=None, **kwargs):
|
||||
self.in_temperature = 0.0
|
||||
self.out_temperature = 0.0
|
||||
self.pt100_temperature = 0.0
|
||||
self.sensor_average_temperature = 0.0
|
||||
self.target_temperature = 0.0
|
||||
self.setting_temperature = 0.0
|
||||
self.viscosity = 0.0
|
||||
self.average_viscosity = 0.0
|
||||
self.speed = 0.0
|
||||
self.force = 0.0
|
||||
|
||||
def update_metrics(self, payload: Dict[str, Any]):
|
||||
def _f(v):
|
||||
try:
|
||||
return float(v)
|
||||
except Exception:
|
||||
return 0.0
|
||||
self.target_temperature = _f(payload.get("targetTemperature"))
|
||||
self.setting_temperature = _f(payload.get("settingTemperature"))
|
||||
self.in_temperature = _f(payload.get("inTemperature"))
|
||||
self.out_temperature = _f(payload.get("outTemperature"))
|
||||
self.pt100_temperature = _f(payload.get("pt100Temperature"))
|
||||
self.sensor_average_temperature = _f(payload.get("sensorAverageTemperature"))
|
||||
self.speed = _f(payload.get("speed"))
|
||||
self.force = _f(payload.get("force"))
|
||||
self.viscosity = _f(payload.get("viscosity"))
|
||||
self.average_viscosity = _f(payload.get("averageViscosity"))
|
||||
|
||||
|
||||
class BioyondReactionStation(BioyondWorkstation):
|
||||
"""Bioyond反应站类
|
||||
|
||||
@@ -73,19 +37,6 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
print(f"BioyondReactionStation初始化完成 - workflow_mappings: {self.workflow_mappings}")
|
||||
print(f"workflow_mappings长度: {len(self.workflow_mappings)}")
|
||||
|
||||
self.in_temperature = 0.0
|
||||
self.out_temperature = 0.0
|
||||
self.pt100_temperature = 0.0
|
||||
self.sensor_average_temperature = 0.0
|
||||
self.target_temperature = 0.0
|
||||
self.setting_temperature = 0.0
|
||||
self.viscosity = 0.0
|
||||
self.average_viscosity = 0.0
|
||||
self.speed = 0.0
|
||||
self.force = 0.0
|
||||
|
||||
self._frame_to_reactor_id = {1: "reactor_1", 2: "reactor_2", 3: "reactor_3", 4: "reactor_4", 5: "reactor_5"}
|
||||
|
||||
# ==================== 工作流方法 ====================
|
||||
|
||||
def reactor_taken_out(self):
|
||||
@@ -281,7 +232,7 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
temperature: 温度设定(°C)
|
||||
"""
|
||||
# 处理 volume 参数:优先使用直接传入的 volume,否则从 solvents 中提取
|
||||
if not volume and solvents is not None:
|
||||
if volume is None and solvents is not None:
|
||||
# 参数类型转换:如果是字符串则解析为字典
|
||||
if isinstance(solvents, str):
|
||||
try:
|
||||
@@ -340,39 +291,22 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
|
||||
def liquid_feeding_titration(
|
||||
self,
|
||||
volume_formula: str,
|
||||
assign_material_name: str,
|
||||
volume_formula: str = None,
|
||||
x_value: str = None,
|
||||
feeding_order_data: str = None,
|
||||
extracted_actuals: str = None,
|
||||
titration_type: str = "2",
|
||||
titration_type: str = "1",
|
||||
time: str = "90",
|
||||
torque_variation: int = 2,
|
||||
temperature: float = 25.00
|
||||
):
|
||||
"""液体进料(滴定)
|
||||
|
||||
支持两种模式:
|
||||
1. 直接提供 volume_formula (传统方式)
|
||||
2. 自动计算公式: 提供 x_value, feeding_order_data, extracted_actuals (新方式)
|
||||
|
||||
Args:
|
||||
volume_formula: 分液公式(μL)
|
||||
assign_material_name: 物料名称
|
||||
volume_formula: 分液公式(μL),如果提供则直接使用,否则自动计算
|
||||
x_value: 手工输入的x值,格式如 "1-2-3"
|
||||
feeding_order_data: feeding_order JSON字符串或对象,用于获取m二酐值
|
||||
extracted_actuals: 从报告提取的实际加料量JSON字符串,包含actualTargetWeigh和actualVolume
|
||||
titration_type: 是否滴定(1=否, 2=是),默认2
|
||||
titration_type: 是否滴定(1=否, 2=是)
|
||||
time: 观察时间(分钟)
|
||||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
||||
temperature: 温度(°C)
|
||||
|
||||
自动公式模板: 1000*(m二酐-x)*V二酐滴定/m二酐滴定
|
||||
其中:
|
||||
- m二酐滴定 = actualTargetWeigh (从extracted_actuals获取)
|
||||
- V二酐滴定 = actualVolume (从extracted_actuals获取)
|
||||
- x = x_value (手工输入)
|
||||
- m二酐 = feeding_order中type为"main_anhydride"的amount值
|
||||
"""
|
||||
self.append_to_workflow_sequence('{"web_workflow_name": "Liquid_feeding(titration)"}')
|
||||
material_id = self.hardware_interface._get_material_id_by_name(assign_material_name)
|
||||
@@ -382,84 +316,6 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
if isinstance(temperature, str):
|
||||
temperature = float(temperature)
|
||||
|
||||
# 如果没有直接提供volume_formula,则自动计算
|
||||
if not volume_formula and x_value and feeding_order_data and extracted_actuals:
|
||||
# 1. 解析 feeding_order_data 获取 m二酐
|
||||
if isinstance(feeding_order_data, str):
|
||||
try:
|
||||
feeding_order_data = json.loads(feeding_order_data)
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"feeding_order_data JSON解析失败: {str(e)}")
|
||||
|
||||
# 支持两种格式:
|
||||
# 格式1: 直接是数组 [{...}, {...}]
|
||||
# 格式2: 对象包裹 {"feeding_order": [{...}, {...}]}
|
||||
if isinstance(feeding_order_data, list):
|
||||
feeding_order_list = feeding_order_data
|
||||
elif isinstance(feeding_order_data, dict):
|
||||
feeding_order_list = feeding_order_data.get("feeding_order", [])
|
||||
else:
|
||||
raise ValueError("feeding_order_data 必须是数组或包含feeding_order的字典")
|
||||
|
||||
# 从feeding_order中找到main_anhydride的amount
|
||||
m_anhydride = None
|
||||
for item in feeding_order_list:
|
||||
if item.get("type") == "main_anhydride":
|
||||
m_anhydride = item.get("amount")
|
||||
break
|
||||
|
||||
if m_anhydride is None:
|
||||
raise ValueError("在feeding_order中未找到type为'main_anhydride'的条目")
|
||||
|
||||
# 2. 解析 extracted_actuals 获取 actualTargetWeigh 和 actualVolume
|
||||
if isinstance(extracted_actuals, str):
|
||||
try:
|
||||
extracted_actuals_obj = json.loads(extracted_actuals)
|
||||
except json.JSONDecodeError as e:
|
||||
raise ValueError(f"extracted_actuals JSON解析失败: {str(e)}")
|
||||
else:
|
||||
extracted_actuals_obj = extracted_actuals
|
||||
|
||||
# 获取actuals数组
|
||||
actuals_list = extracted_actuals_obj.get("actuals", [])
|
||||
if not actuals_list:
|
||||
# actuals为空,无法自动生成公式,回退到手动模式
|
||||
print(f"警告: extracted_actuals中actuals数组为空,无法自动生成公式,请手动提供volume_formula")
|
||||
volume_formula = None # 清空,触发后续的错误检查
|
||||
else:
|
||||
# 根据assign_material_name匹配对应的actual数据
|
||||
# 假设order_code中包含物料名称
|
||||
matched_actual = None
|
||||
for actual in actuals_list:
|
||||
order_code = actual.get("order_code", "")
|
||||
# 简单匹配:如果order_code包含物料名称
|
||||
if assign_material_name in order_code:
|
||||
matched_actual = actual
|
||||
break
|
||||
|
||||
# 如果没有匹配到,使用第一个
|
||||
if not matched_actual and actuals_list:
|
||||
matched_actual = actuals_list[0]
|
||||
|
||||
if not matched_actual:
|
||||
raise ValueError("无法从extracted_actuals中获取实际加料量数据")
|
||||
|
||||
m_anhydride_titration = matched_actual.get("actualTargetWeigh") # m二酐滴定
|
||||
v_anhydride_titration = matched_actual.get("actualVolume") # V二酐滴定
|
||||
|
||||
if m_anhydride_titration is None or v_anhydride_titration is None:
|
||||
raise ValueError(f"实际加料量数据不完整: actualTargetWeigh={m_anhydride_titration}, actualVolume={v_anhydride_titration}")
|
||||
|
||||
# 3. 构建公式: 1000*(m二酐-x)*V二酐滴定/m二酐滴定
|
||||
# x_value 格式如 "{{1-2-3}}",保留完整格式(包括花括号)直接替换到公式中
|
||||
volume_formula = f"1000*({m_anhydride}-{x_value})*{v_anhydride_titration}/{m_anhydride_titration}"
|
||||
|
||||
print(f"自动生成滴定公式: {volume_formula}")
|
||||
print(f" m二酐={m_anhydride}, x={x_value}, V二酐滴定={v_anhydride_titration}, m二酐滴定={m_anhydride_titration}")
|
||||
|
||||
elif not volume_formula:
|
||||
raise ValueError("必须提供 volume_formula 或 (x_value + feeding_order_data + extracted_actuals)")
|
||||
|
||||
liquid_step_id = WORKFLOW_STEP_IDS["liquid_feeding_titration"]["liquid"]
|
||||
observe_step_id = WORKFLOW_STEP_IDS["liquid_feeding_titration"]["observe"]
|
||||
|
||||
@@ -487,288 +343,9 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
print(f"当前队列长度: {len(self.pending_task_params)}")
|
||||
return json.dumps({"suc": True})
|
||||
|
||||
def _extract_actuals_from_report(self, report) -> Dict[str, Any]:
|
||||
data = report.get('data') if isinstance(report, dict) else None
|
||||
actual_target_weigh = None
|
||||
actual_volume = None
|
||||
if data:
|
||||
extra = data.get('extraProperties') or {}
|
||||
if isinstance(extra, dict):
|
||||
for v in extra.values():
|
||||
obj = None
|
||||
try:
|
||||
obj = json.loads(v) if isinstance(v, str) else v
|
||||
except Exception:
|
||||
obj = None
|
||||
if isinstance(obj, dict):
|
||||
tw = obj.get('targetWeigh')
|
||||
vol = obj.get('volume')
|
||||
if tw is not None:
|
||||
try:
|
||||
actual_target_weigh = float(tw)
|
||||
except Exception:
|
||||
pass
|
||||
if vol is not None:
|
||||
try:
|
||||
actual_volume = float(vol)
|
||||
except Exception:
|
||||
pass
|
||||
return {
|
||||
'actualTargetWeigh': actual_target_weigh,
|
||||
'actualVolume': actual_volume
|
||||
}
|
||||
|
||||
def extract_actuals_from_batch_reports(self, batch_reports_result: str) -> dict:
|
||||
print(f"[DEBUG] extract_actuals 收到原始数据: {batch_reports_result[:500]}...") # 打印前500字符
|
||||
try:
|
||||
obj = json.loads(batch_reports_result) if isinstance(batch_reports_result, str) else batch_reports_result
|
||||
if isinstance(obj, dict) and "return_info" in obj:
|
||||
inner = obj["return_info"]
|
||||
obj = json.loads(inner) if isinstance(inner, str) else inner
|
||||
reports = obj.get("reports", []) if isinstance(obj, dict) else []
|
||||
print(f"[DEBUG] 解析后的 reports 数组长度: {len(reports)}")
|
||||
except Exception as e:
|
||||
print(f"[DEBUG] 解析异常: {e}")
|
||||
reports = []
|
||||
|
||||
actuals = []
|
||||
for i, r in enumerate(reports):
|
||||
print(f"[DEBUG] 处理 report[{i}]: order_code={r.get('order_code')}, has_extracted={r.get('extracted') is not None}, has_report={r.get('report') is not None}")
|
||||
order_code = r.get("order_code")
|
||||
order_id = r.get("order_id")
|
||||
ex = r.get("extracted")
|
||||
if isinstance(ex, dict) and (ex.get("actualTargetWeigh") is not None or ex.get("actualVolume") is not None):
|
||||
print(f"[DEBUG] 从 extracted 字段提取: actualTargetWeigh={ex.get('actualTargetWeigh')}, actualVolume={ex.get('actualVolume')}")
|
||||
actuals.append({
|
||||
"order_code": order_code,
|
||||
"order_id": order_id,
|
||||
"actualTargetWeigh": ex.get("actualTargetWeigh"),
|
||||
"actualVolume": ex.get("actualVolume")
|
||||
})
|
||||
continue
|
||||
report = r.get("report")
|
||||
vals = self._extract_actuals_from_report(report) if report else {"actualTargetWeigh": None, "actualVolume": None}
|
||||
print(f"[DEBUG] 从 report 字段提取: {vals}")
|
||||
actuals.append({
|
||||
"order_code": order_code,
|
||||
"order_id": order_id,
|
||||
**vals
|
||||
})
|
||||
|
||||
print(f"[DEBUG] 最终提取的 actuals 数组长度: {len(actuals)}")
|
||||
result = {
|
||||
"return_info": json.dumps({"actuals": actuals}, ensure_ascii=False)
|
||||
}
|
||||
print(f"[DEBUG] 返回结果: {result}")
|
||||
return result
|
||||
|
||||
def process_temperature_cutoff_report(self, report_request) -> Dict[str, Any]:
|
||||
try:
|
||||
data = report_request.data
|
||||
def _f(v):
|
||||
try:
|
||||
return float(v)
|
||||
except Exception:
|
||||
return 0.0
|
||||
self.target_temperature = _f(data.get("targetTemperature"))
|
||||
self.setting_temperature = _f(data.get("settingTemperature"))
|
||||
self.in_temperature = _f(data.get("inTemperature"))
|
||||
self.out_temperature = _f(data.get("outTemperature"))
|
||||
self.pt100_temperature = _f(data.get("pt100Temperature"))
|
||||
self.sensor_average_temperature = _f(data.get("sensorAverageTemperature"))
|
||||
self.speed = _f(data.get("speed"))
|
||||
self.force = _f(data.get("force"))
|
||||
self.viscosity = _f(data.get("viscosity"))
|
||||
self.average_viscosity = _f(data.get("averageViscosity"))
|
||||
|
||||
try:
|
||||
if hasattr(self, "_ros_node") and self._ros_node is not None:
|
||||
props = [
|
||||
"in_temperature","out_temperature","pt100_temperature","sensor_average_temperature",
|
||||
"target_temperature","setting_temperature","viscosity","average_viscosity",
|
||||
"speed","force"
|
||||
]
|
||||
for name in props:
|
||||
pub = self._ros_node._property_publishers.get(name)
|
||||
if pub:
|
||||
pub.publish_property()
|
||||
frame = data.get("frameCode")
|
||||
reactor_id = None
|
||||
try:
|
||||
reactor_id = self._frame_to_reactor_id.get(int(frame))
|
||||
except Exception:
|
||||
reactor_id = None
|
||||
if reactor_id and hasattr(self._ros_node, "sub_devices"):
|
||||
child = self._ros_node.sub_devices.get(reactor_id)
|
||||
if child and hasattr(child, "driver_instance"):
|
||||
child.driver_instance.update_metrics(data)
|
||||
pubs = getattr(child.ros_node_instance, "_property_publishers", {})
|
||||
for name in props:
|
||||
p = pubs.get(name)
|
||||
if p:
|
||||
p.publish_property()
|
||||
except Exception:
|
||||
pass
|
||||
event = {
|
||||
"frameCode": data.get("frameCode"),
|
||||
"generateTime": data.get("generateTime"),
|
||||
"targetTemperature": data.get("targetTemperature"),
|
||||
"settingTemperature": data.get("settingTemperature"),
|
||||
"inTemperature": data.get("inTemperature"),
|
||||
"outTemperature": data.get("outTemperature"),
|
||||
"pt100Temperature": data.get("pt100Temperature"),
|
||||
"sensorAverageTemperature": data.get("sensorAverageTemperature"),
|
||||
"speed": data.get("speed"),
|
||||
"force": data.get("force"),
|
||||
"viscosity": data.get("viscosity"),
|
||||
"averageViscosity": data.get("averageViscosity"),
|
||||
"request_time": report_request.request_time,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"reactor_id": self._frame_to_reactor_id.get(int(data.get("frameCode", 0))) if str(data.get("frameCode", "")).isdigit() else None,
|
||||
}
|
||||
|
||||
base_dir = Path(__file__).resolve().parents[3] / "unilabos_data"
|
||||
base_dir.mkdir(parents=True, exist_ok=True)
|
||||
out_file = base_dir / "temperature_cutoff_events.json"
|
||||
try:
|
||||
existing = json.loads(out_file.read_text(encoding="utf-8")) if out_file.exists() else []
|
||||
if not isinstance(existing, list):
|
||||
existing = []
|
||||
except Exception:
|
||||
existing = []
|
||||
existing.append(event)
|
||||
out_file.write_text(json.dumps(existing, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
if hasattr(self, "_ros_node") and self._ros_node is not None:
|
||||
ns = self._ros_node.namespace
|
||||
topics = {
|
||||
"targetTemperature": f"{ns}/metrics/temperature_cutoff/target_temperature",
|
||||
"settingTemperature": f"{ns}/metrics/temperature_cutoff/setting_temperature",
|
||||
"inTemperature": f"{ns}/metrics/temperature_cutoff/in_temperature",
|
||||
"outTemperature": f"{ns}/metrics/temperature_cutoff/out_temperature",
|
||||
"pt100Temperature": f"{ns}/metrics/temperature_cutoff/pt100_temperature",
|
||||
"sensorAverageTemperature": f"{ns}/metrics/temperature_cutoff/sensor_average_temperature",
|
||||
"speed": f"{ns}/metrics/temperature_cutoff/speed",
|
||||
"force": f"{ns}/metrics/temperature_cutoff/force",
|
||||
"viscosity": f"{ns}/metrics/temperature_cutoff/viscosity",
|
||||
"averageViscosity": f"{ns}/metrics/temperature_cutoff/average_viscosity",
|
||||
}
|
||||
for k, t in topics.items():
|
||||
v = data.get(k)
|
||||
if v is not None:
|
||||
pub = self._ros_node.create_publisher(Float64, t, 10)
|
||||
pub.publish(convert_to_ros_msg(Float64, float(v)))
|
||||
|
||||
evt_pub = self._ros_node.create_publisher(String, f"{ns}/events/temperature_cutoff", 10)
|
||||
evt_pub.publish(convert_to_ros_msg(String, json.dumps(event, ensure_ascii=False)))
|
||||
|
||||
return {"processed": True, "frame": data.get("frameCode")}
|
||||
except Exception as e:
|
||||
return {"processed": False, "error": str(e)}
|
||||
|
||||
def wait_for_multiple_orders_and_get_reports(self, batch_create_result: str = None, timeout: int = 7200, check_interval: int = 10) -> Dict[str, Any]:
|
||||
try:
|
||||
timeout = int(timeout) if timeout else 7200
|
||||
check_interval = int(check_interval) if check_interval else 10
|
||||
if not batch_create_result or batch_create_result == "":
|
||||
raise ValueError("batch_create_result为空")
|
||||
try:
|
||||
if isinstance(batch_create_result, str) and '[...]' in batch_create_result:
|
||||
batch_create_result = batch_create_result.replace('[...]', '[]')
|
||||
result_obj = json.loads(batch_create_result) if isinstance(batch_create_result, str) else batch_create_result
|
||||
if isinstance(result_obj, dict) and "return_value" in result_obj:
|
||||
inner = result_obj.get("return_value")
|
||||
if isinstance(inner, str):
|
||||
result_obj = json.loads(inner)
|
||||
elif isinstance(inner, dict):
|
||||
result_obj = inner
|
||||
order_codes = result_obj.get("order_codes", [])
|
||||
order_ids = result_obj.get("order_ids", [])
|
||||
except Exception as e:
|
||||
raise ValueError(f"解析batch_create_result失败: {e}")
|
||||
if not order_codes or not order_ids:
|
||||
raise ValueError("缺少order_codes或order_ids")
|
||||
if not isinstance(order_codes, list):
|
||||
order_codes = [order_codes]
|
||||
if not isinstance(order_ids, list):
|
||||
order_ids = [order_ids]
|
||||
if len(order_codes) != len(order_ids):
|
||||
raise ValueError("order_codes与order_ids数量不匹配")
|
||||
total = len(order_codes)
|
||||
pending = {c: {"order_id": order_ids[i], "completed": False} for i, c in enumerate(order_codes)}
|
||||
reports = []
|
||||
start_time = time.time()
|
||||
while pending:
|
||||
elapsed_time = time.time() - start_time
|
||||
if elapsed_time > timeout:
|
||||
for oc in list(pending.keys()):
|
||||
reports.append({
|
||||
"order_code": oc,
|
||||
"order_id": pending[oc]["order_id"],
|
||||
"status": "timeout",
|
||||
"completion_status": None,
|
||||
"report": None,
|
||||
"extracted": None,
|
||||
"elapsed_time": elapsed_time
|
||||
})
|
||||
break
|
||||
completed_round = []
|
||||
for oc in list(pending.keys()):
|
||||
oid = pending[oc]["order_id"]
|
||||
if oc in self.order_completion_status:
|
||||
info = self.order_completion_status[oc]
|
||||
try:
|
||||
rep = self.hardware_interface.order_report(oid)
|
||||
if not rep:
|
||||
rep = {"error": "无法获取报告"}
|
||||
reports.append({
|
||||
"order_code": oc,
|
||||
"order_id": oid,
|
||||
"status": "completed",
|
||||
"completion_status": info.get('status'),
|
||||
"report": rep,
|
||||
"extracted": self._extract_actuals_from_report(rep),
|
||||
"elapsed_time": elapsed_time
|
||||
})
|
||||
completed_round.append(oc)
|
||||
del self.order_completion_status[oc]
|
||||
except Exception as e:
|
||||
reports.append({
|
||||
"order_code": oc,
|
||||
"order_id": oid,
|
||||
"status": "error",
|
||||
"completion_status": info.get('status') if 'info' in locals() else None,
|
||||
"report": None,
|
||||
"extracted": None,
|
||||
"error": str(e),
|
||||
"elapsed_time": elapsed_time
|
||||
})
|
||||
completed_round.append(oc)
|
||||
for oc in completed_round:
|
||||
del pending[oc]
|
||||
if pending:
|
||||
time.sleep(check_interval)
|
||||
completed_count = sum(1 for r in reports if r['status'] == 'completed')
|
||||
timeout_count = sum(1 for r in reports if r['status'] == 'timeout')
|
||||
error_count = sum(1 for r in reports if r['status'] == 'error')
|
||||
final_elapsed_time = time.time() - start_time
|
||||
summary = {
|
||||
"total": total,
|
||||
"completed": completed_count,
|
||||
"timeout": timeout_count,
|
||||
"error": error_count,
|
||||
"elapsed_time": round(final_elapsed_time, 2),
|
||||
"reports": reports
|
||||
}
|
||||
return {
|
||||
"return_info": json.dumps(summary, ensure_ascii=False)
|
||||
}
|
||||
except Exception as e:
|
||||
raise
|
||||
|
||||
def liquid_feeding_beaker(
|
||||
self,
|
||||
volume: str = "350",
|
||||
volume: str = "35000",
|
||||
assign_material_name: str = "BAPP",
|
||||
time: str = "0",
|
||||
torque_variation: int = 1,
|
||||
@@ -778,7 +355,7 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
"""液体进料烧杯
|
||||
|
||||
Args:
|
||||
volume: 分液质量(g)
|
||||
volume: 分液量(μL)
|
||||
assign_material_name: 物料名称(试剂瓶位)
|
||||
time: 观察时间(分钟)
|
||||
torque_variation: 是否观察(int类型, 1=否, 2=是)
|
||||
@@ -912,106 +489,6 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
"""
|
||||
return self.hardware_interface.create_order(json_str)
|
||||
|
||||
def hard_delete_merged_workflows(self, workflow_ids: List[str]) -> Dict[str, Any]:
|
||||
"""
|
||||
调用新接口:硬删除合并后的工作流
|
||||
|
||||
Args:
|
||||
workflow_ids: 要删除的工作流ID数组
|
||||
|
||||
Returns:
|
||||
删除结果
|
||||
"""
|
||||
try:
|
||||
if not isinstance(workflow_ids, list):
|
||||
raise ValueError("workflow_ids必须是字符串数组")
|
||||
return self._delete_project_api("/api/lims/order/workflows", workflow_ids)
|
||||
except Exception as e:
|
||||
print(f"❌ 硬删除异常: {str(e)}")
|
||||
return {"code": 0, "message": str(e), "timestamp": int(time.time())}
|
||||
|
||||
# ==================== 项目接口通用方法 ====================
|
||||
|
||||
def _post_project_api(self, endpoint: str, data: Any) -> Dict[str, Any]:
|
||||
"""项目接口通用POST调用
|
||||
|
||||
参数:
|
||||
endpoint: 接口路径(例如 /api/lims/order/skip-titration-steps)
|
||||
data: 请求体中的 data 字段内容
|
||||
|
||||
返回:
|
||||
dict: 服务端响应,失败时返回 {code:0,message,...}
|
||||
"""
|
||||
request_data = {
|
||||
"apiKey": API_CONFIG["api_key"],
|
||||
"requestTime": self.hardware_interface.get_current_time_iso8601(),
|
||||
"data": data
|
||||
}
|
||||
print(f"\n📤 项目POST请求: {self.hardware_interface.host}{endpoint}")
|
||||
print(json.dumps(request_data, indent=4, ensure_ascii=False))
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.hardware_interface.host}{endpoint}",
|
||||
json=request_data,
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=30
|
||||
)
|
||||
result = response.json()
|
||||
if result.get("code") == 1:
|
||||
print("✅ 请求成功")
|
||||
else:
|
||||
print(f"❌ 请求失败: {result.get('message','未知错误')}")
|
||||
return result
|
||||
except json.JSONDecodeError:
|
||||
print("❌ 非JSON响应")
|
||||
return {"code": 0, "message": "非JSON响应", "timestamp": int(time.time())}
|
||||
except requests.exceptions.Timeout:
|
||||
print("❌ 请求超时")
|
||||
return {"code": 0, "message": "请求超时", "timestamp": int(time.time())}
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"❌ 网络异常: {str(e)}")
|
||||
return {"code": 0, "message": str(e), "timestamp": int(time.time())}
|
||||
|
||||
def _delete_project_api(self, endpoint: str, data: Any) -> Dict[str, Any]:
|
||||
"""项目接口通用DELETE调用
|
||||
|
||||
参数:
|
||||
endpoint: 接口路径(例如 /api/lims/order/workflows)
|
||||
data: 请求体中的 data 字段内容
|
||||
|
||||
返回:
|
||||
dict: 服务端响应,失败时返回 {code:0,message,...}
|
||||
"""
|
||||
request_data = {
|
||||
"apiKey": API_CONFIG["api_key"],
|
||||
"requestTime": self.hardware_interface.get_current_time_iso8601(),
|
||||
"data": data
|
||||
}
|
||||
print(f"\n📤 项目DELETE请求: {self.hardware_interface.host}{endpoint}")
|
||||
print(json.dumps(request_data, indent=4, ensure_ascii=False))
|
||||
try:
|
||||
response = requests.delete(
|
||||
f"{self.hardware_interface.host}{endpoint}",
|
||||
json=request_data,
|
||||
headers={"Content-Type": "application/json"},
|
||||
timeout=30
|
||||
)
|
||||
result = response.json()
|
||||
if result.get("code") == 1:
|
||||
print("✅ 请求成功")
|
||||
else:
|
||||
print(f"❌ 请求失败: {result.get('message','未知错误')}")
|
||||
return result
|
||||
except json.JSONDecodeError:
|
||||
print("❌ 非JSON响应")
|
||||
return {"code": 0, "message": "非JSON响应", "timestamp": int(time.time())}
|
||||
except requests.exceptions.Timeout:
|
||||
print("❌ 请求超时")
|
||||
return {"code": 0, "message": "请求超时", "timestamp": int(time.time())}
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"❌ 网络异常: {str(e)}")
|
||||
return {"code": 0, "message": str(e), "timestamp": int(time.time())}
|
||||
|
||||
# ==================== 工作流执行核心方法 ====================
|
||||
|
||||
def process_web_workflows(self, web_workflow_json: str) -> List[Dict[str, str]]:
|
||||
@@ -1042,6 +519,69 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
print(f"错误:处理工作流失败: {e}")
|
||||
return []
|
||||
|
||||
def process_and_execute_workflow(self, workflow_name: str, task_name: str) -> dict:
|
||||
"""
|
||||
一站式处理工作流程:解析网页工作流列表,合并工作流(带参数),然后发布任务
|
||||
|
||||
Args:
|
||||
workflow_name: 合并后的工作流名称
|
||||
task_name: 任务名称
|
||||
|
||||
Returns:
|
||||
任务创建结果
|
||||
"""
|
||||
web_workflow_list = self.get_workflow_sequence()
|
||||
print(f"\n{'='*60}")
|
||||
print(f"📋 处理网页工作流列表: {web_workflow_list}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
web_workflow_json = json.dumps({"web_workflow_list": web_workflow_list})
|
||||
workflows_result = self.process_web_workflows(web_workflow_json)
|
||||
|
||||
if not workflows_result:
|
||||
return self._create_error_result("处理网页工作流列表失败", "process_web_workflows")
|
||||
|
||||
print(f"workflows_result 类型: {type(workflows_result)}")
|
||||
print(f"workflows_result 内容: {workflows_result}")
|
||||
|
||||
workflows_with_params = self._build_workflows_with_parameters(workflows_result)
|
||||
|
||||
merge_data = {
|
||||
"name": workflow_name,
|
||||
"workflows": workflows_with_params
|
||||
}
|
||||
|
||||
# print(f"\n🔄 合并工作流(带参数),名称: {workflow_name}")
|
||||
merged_workflow = self.merge_workflow_with_parameters(json.dumps(merge_data))
|
||||
|
||||
if not merged_workflow:
|
||||
return self._create_error_result("合并工作流失败", "merge_workflow_with_parameters")
|
||||
|
||||
workflow_id = merged_workflow.get("subWorkflows", [{}])[0].get("id", "")
|
||||
# print(f"\n📤 使用工作流创建任务: {workflow_name} (ID: {workflow_id})")
|
||||
|
||||
order_params = [{
|
||||
"orderCode": f"task_{self.hardware_interface.get_current_time_iso8601()}",
|
||||
"orderName": task_name,
|
||||
"workFlowId": workflow_id,
|
||||
"borderNumber": 1,
|
||||
"paramValues": {}
|
||||
}]
|
||||
|
||||
result = self.create_order(json.dumps(order_params))
|
||||
|
||||
if not result:
|
||||
return self._create_error_result("创建任务失败", "create_order")
|
||||
|
||||
# 清空工作流序列和参数,防止下次执行时累积重复
|
||||
self.pending_task_params = []
|
||||
self.clear_workflows() # 清空工作流序列,避免重复累积
|
||||
|
||||
# print(f"\n✅ 任务创建成功: {result}")
|
||||
# print(f"\n✅ 任务创建成功")
|
||||
print(f"{'='*60}\n")
|
||||
return json.dumps({"success": True, "result": result})
|
||||
|
||||
def _build_workflows_with_parameters(self, workflows_result: list) -> list:
|
||||
"""
|
||||
构建带参数的工作流列表
|
||||
@@ -1240,91 +780,4 @@ class BioyondReactionStation(BioyondWorkstation):
|
||||
except Exception as e:
|
||||
print(f" ❌ 工作流ID验证失败: {e}")
|
||||
print(f" 💡 将重新合并工作流")
|
||||
return False
|
||||
|
||||
def process_and_execute_workflow(self, workflow_name: str, task_name: str) -> dict:
|
||||
"""
|
||||
一站式处理工作流程:解析网页工作流列表,合并工作流(带参数),然后发布任务
|
||||
|
||||
Args:
|
||||
workflow_name: 合并后的工作流名称
|
||||
task_name: 任务名称
|
||||
|
||||
Returns:
|
||||
任务创建结果
|
||||
"""
|
||||
web_workflow_list = self.get_workflow_sequence()
|
||||
print(f"\n{'='*60}")
|
||||
print(f"📋 处理网页工作流列表: {web_workflow_list}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
web_workflow_json = json.dumps({"web_workflow_list": web_workflow_list})
|
||||
workflows_result = self.process_web_workflows(web_workflow_json)
|
||||
|
||||
if not workflows_result:
|
||||
return self._create_error_result("处理网页工作流列表失败", "process_web_workflows")
|
||||
|
||||
print(f"workflows_result 类型: {type(workflows_result)}")
|
||||
print(f"workflows_result 内容: {workflows_result}")
|
||||
|
||||
workflows_with_params = self._build_workflows_with_parameters(workflows_result)
|
||||
|
||||
merge_data = {
|
||||
"name": workflow_name,
|
||||
"workflows": workflows_with_params
|
||||
}
|
||||
|
||||
# print(f"\n🔄 合并工作流(带参数),名称: {workflow_name}")
|
||||
merged_workflow = self.merge_workflow_with_parameters(json.dumps(merge_data))
|
||||
|
||||
if not merged_workflow:
|
||||
return self._create_error_result("合并工作流失败", "merge_workflow_with_parameters")
|
||||
|
||||
workflow_id = merged_workflow.get("subWorkflows", [{}])[0].get("id", "")
|
||||
# print(f"\n📤 使用工作流创建任务: {workflow_name} (ID: {workflow_id})")
|
||||
|
||||
order_params = [{
|
||||
"orderCode": f"task_{self.hardware_interface.get_current_time_iso8601()}",
|
||||
"orderName": task_name,
|
||||
"workFlowId": workflow_id,
|
||||
"borderNumber": 1,
|
||||
"paramValues": {}
|
||||
}]
|
||||
|
||||
result = self.create_order(json.dumps(order_params))
|
||||
|
||||
if not result:
|
||||
return self._create_error_result("创建任务失败", "create_order")
|
||||
|
||||
# 清空工作流序列和参数,防止下次执行时累积重复
|
||||
self.pending_task_params = []
|
||||
self.clear_workflows() # 清空工作流序列,避免重复累积
|
||||
|
||||
# print(f"\n✅ 任务创建成功: {result}")
|
||||
# print(f"\n✅ 任务创建成功")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
# 返回结果,包含合并后的工作流数据和订单参数
|
||||
return json.dumps({
|
||||
"success": True,
|
||||
"result": result,
|
||||
"merged_workflow": merged_workflow,
|
||||
"order_params": order_params
|
||||
})
|
||||
|
||||
# ==================== 反应器操作接口 ====================
|
||||
|
||||
def skip_titration_steps(self, preintake_id: str) -> Dict[str, Any]:
|
||||
"""跳过当前正在进行的滴定步骤
|
||||
|
||||
Args:
|
||||
preintake_id: 通量ID
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: 服务器响应,包含状态码、消息和时间戳
|
||||
"""
|
||||
try:
|
||||
return self._post_project_api("/api/lims/order/skip-titration-steps", preintake_id)
|
||||
except Exception as e:
|
||||
print(f"❌ 跳过滴定异常: {str(e)}")
|
||||
return {"code": 0, "message": str(e), "timestamp": int(time.time())}
|
||||
return False
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
# Modbus CSV 地址映射说明
|
||||
|
||||
本文档说明 `coin_cell_assembly_a.csv` 文件如何将命名节点映射到实际的 Modbus 地址,以及如何在代码中使用它们。
|
||||
|
||||
## 1. CSV 文件结构
|
||||
|
||||
地址表文件位于同级目录下:`coin_cell_assembly_a.csv`
|
||||
|
||||
每一行定义了一个 Modbus 节点,包含以下关键列:
|
||||
|
||||
| 列名 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **Name** | **节点名称** (代码中引用的 Key) | `COIL_ALUMINUM_FOIL` |
|
||||
| **DataType** | 数据类型 (BOOL, INT16, FLOAT32, STRING) | `BOOL` |
|
||||
| **Comment** | 注释说明 | `使用铝箔垫` |
|
||||
| **Attribute** | 属性 (通常留空或用于额外标记) | |
|
||||
| **DeviceType** | Modbus 寄存器类型 (`coil`, `hold_register`) | `coil` |
|
||||
| **Address** | **Modbus 地址** (十进制) | `8340` |
|
||||
|
||||
### 示例行 (铝箔垫片)
|
||||
|
||||
```csv
|
||||
COIL_ALUMINUM_FOIL,BOOL,,使用铝箔垫,,coil,8340,
|
||||
```
|
||||
|
||||
- **名称**: `COIL_ALUMINUM_FOIL`
|
||||
- **类型**: `coil` (线圈,读写单个位)
|
||||
- **地址**: `8340`
|
||||
|
||||
---
|
||||
|
||||
## 2. 加载与注册流程
|
||||
|
||||
在 `coin_cell_assembly.py` 的初始化代码中:
|
||||
|
||||
1. **加载 CSV**: `BaseClient.load_csv()` 读取 CSV 并解析每行定义。
|
||||
2. **注册节点**: `modbus_client.register_node_list()` 将解析后的节点注册到 Modbus 客户端实例中。
|
||||
|
||||
```python
|
||||
# 代码位置: coin_cell_assembly.py (L174-175)
|
||||
self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv'))
|
||||
self.client = modbus_client.register_node_list(self.nodes)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 代码中的使用方式
|
||||
|
||||
注册后,通过 `self.client.use_node('节点名称')` 即可获取该节点对象并进行读写操作,无需关心具体地址。
|
||||
|
||||
### 控制铝箔垫片 (COIL_ALUMINUM_FOIL)
|
||||
|
||||
```python
|
||||
# 代码位置: qiming_coin_cell_code 函数 (L1048)
|
||||
self.client.use_node('COIL_ALUMINUM_FOIL').write(not lvbodian)
|
||||
```
|
||||
|
||||
- **写入 True**: 对应 Modbus 功能码 05 (Write Single Coil),向地址 `8340` 写入 `1` (ON)。
|
||||
- **写入 False**: 向地址 `8340` 写入 `0` (OFF)。
|
||||
|
||||
> **注意**: 代码中使用了 `not lvbodian`,这意味着逻辑是反转的。如果 `lvbodian` 参数为 `True` (默认),写入的是 `False` (不使用铝箔垫)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 地址转换注意事项 (Modbus vs PLC)
|
||||
|
||||
CSV 中的 `Address` 列(如 `8340`)是 **Modbus 协议地址**。
|
||||
|
||||
如果使用 InoProShop (汇川 PLC 编程软件),看到的可能是 **PLC 内部地址** (如 `%QX...` 或 `%MW...`)。这两者之间通常需要转换。
|
||||
|
||||
### 常见的转换规则 (示例)
|
||||
|
||||
- **Coil (线圈) %QX**:
|
||||
- `Modbus地址 = 字节地址 * 8 + 位偏移`
|
||||
- *例子*: `%QX834.0` -> `834 * 8 + 0` = `6672`
|
||||
- *注意*: 如果 CSV 中配置的是 `8340`,这可能是一个自定义映射,或者是基于不同规则(如直接对应 Word 地址的某种映射,或者可能就是地址写错了/使用了非标准映射)。
|
||||
|
||||
- **Register (寄存器) %MW**:
|
||||
- 通常直接对应,或者有偏移量 (如 Modbus 40001 = PLC MW0)。
|
||||
|
||||
### 验证方法
|
||||
由于 `test_unilab_interact.py` 中发现 `8450` (CSV风格) 不工作,而 `6760` (%QX845.0 计算值) 工作正常,**建议对 CSV 中的其他地址也进行核实**,特别是像 `8340` 这样以 0 结尾看起来像是 "字节地址+0" 的数值,可能实际上应该是 `%QX834.0` 对应的 `6672`。
|
||||
|
||||
如果发现设备控制无反应,请尝试按照标准的 Modbus 计算方式转换 PLC 地址。
|
||||
@@ -0,0 +1,645 @@
|
||||
"""
|
||||
纽扣电池组装工作站物料类定义
|
||||
Button Battery Assembly Station Resource Classes
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import OrderedDict
|
||||
from typing import Any, Dict, List, Optional, TypedDict, Union, cast
|
||||
|
||||
from pylabrobot.resources.coordinate import Coordinate
|
||||
from pylabrobot.resources.container import Container
|
||||
from pylabrobot.resources.deck import Deck
|
||||
from pylabrobot.resources.itemized_resource import ItemizedResource
|
||||
from pylabrobot.resources.resource import Resource
|
||||
from pylabrobot.resources.resource_stack import ResourceStack
|
||||
from pylabrobot.resources.tip_rack import TipRack, TipSpot
|
||||
from pylabrobot.resources.trash import Trash
|
||||
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
|
||||
|
||||
|
||||
|
||||
# TODO: 这个应该只能放一个极片
|
||||
class MaterialHoleState(TypedDict):
|
||||
diameter: int
|
||||
depth: int
|
||||
max_sheets: int
|
||||
info: Optional[str] # 附加信息
|
||||
|
||||
class MaterialHole(Resource):
|
||||
"""料板洞位类"""
|
||||
children: List[ElectrodeSheet] = []
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
category: str = "material_hole",
|
||||
**kwargs
|
||||
):
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
category=category,
|
||||
)
|
||||
self._unilabos_state: MaterialHoleState = MaterialHoleState(
|
||||
diameter=20,
|
||||
depth=10,
|
||||
max_sheets=1,
|
||||
info=None
|
||||
)
|
||||
|
||||
def get_all_sheet_info(self):
|
||||
info_list = []
|
||||
for sheet in self.children:
|
||||
info_list.append(sheet._unilabos_state["info"])
|
||||
return info_list
|
||||
|
||||
#这个函数函数好像没用,一般不会集中赋值质量
|
||||
def set_all_sheet_mass(self):
|
||||
for sheet in self.children:
|
||||
sheet._unilabos_state["mass"] = 0.5 # 示例:设置质量为0.5g
|
||||
|
||||
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 get_sheet_with_name(self, name: str) -> Optional[ElectrodeSheet]:
|
||||
for sheet in self.children:
|
||||
if sheet.name == name:
|
||||
return sheet
|
||||
return None
|
||||
|
||||
def has_electrode_sheet(self) -> bool:
|
||||
"""检查洞位是否有极片"""
|
||||
return len(self.children) > 0
|
||||
|
||||
def assign_child_resource(
|
||||
self,
|
||||
resource: ElectrodeSheet,
|
||||
location: Optional[Coordinate],
|
||||
reassign: bool = True,
|
||||
):
|
||||
"""放置极片"""
|
||||
# TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题
|
||||
#if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]:
|
||||
# raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}")
|
||||
#if len(self.children) >= self._unilabos_state["max_sheets"]:
|
||||
# raise ValueError(f"洞位已满,无法放置更多极片")
|
||||
super().assign_child_resource(resource, location, reassign)
|
||||
|
||||
# 根据children的编号取物料对象。
|
||||
def get_electrode_sheet_info(self, index: int) -> ElectrodeSheet:
|
||||
return self.children[index]
|
||||
|
||||
|
||||
class MaterialPlateState(TypedDict):
|
||||
hole_spacing_x: float
|
||||
hole_spacing_y: float
|
||||
hole_diameter: float
|
||||
info: Optional[str] # 附加信息
|
||||
|
||||
class MaterialPlate(ItemizedResource[MaterialHole]):
|
||||
"""料板类 - 4x4个洞位,每个洞位放1个极片"""
|
||||
|
||||
children: List[MaterialHole]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
ordered_items: Optional[Dict[str, MaterialHole]] = None,
|
||||
ordering: Optional[OrderedDict[str, str]] = None,
|
||||
category: str = "material_plate",
|
||||
model: Optional[str] = None,
|
||||
fill: bool = False
|
||||
):
|
||||
"""初始化料板
|
||||
|
||||
Args:
|
||||
name: 料板名称
|
||||
size_x: 长度 (mm)
|
||||
size_y: 宽度 (mm)
|
||||
size_z: 高度 (mm)
|
||||
hole_diameter: 洞直径 (mm)
|
||||
hole_depth: 洞深度 (mm)
|
||||
hole_spacing_x: X方向洞位间距 (mm)
|
||||
hole_spacing_y: Y方向洞位间距 (mm)
|
||||
number: 编号
|
||||
category: 类别
|
||||
model: 型号
|
||||
"""
|
||||
self._unilabos_state: MaterialPlateState = MaterialPlateState(
|
||||
hole_spacing_x=24.0,
|
||||
hole_spacing_y=24.0,
|
||||
hole_diameter=20.0,
|
||||
info="",
|
||||
)
|
||||
# 创建4x4的洞位
|
||||
# TODO: 这里要改,对应不同形状
|
||||
holes = create_ordered_items_2d(
|
||||
klass=MaterialHole,
|
||||
num_items_x=4,
|
||||
num_items_y=4,
|
||||
dx=(size_x - 4 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中
|
||||
dy=(size_y - 4 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中
|
||||
dz=size_z,
|
||||
item_dx=self._unilabos_state["hole_spacing_x"],
|
||||
item_dy=self._unilabos_state["hole_spacing_y"],
|
||||
size_x = 16,
|
||||
size_y = 16,
|
||||
size_z = 16,
|
||||
)
|
||||
if fill:
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
ordered_items=holes,
|
||||
category=category,
|
||||
model=model,
|
||||
)
|
||||
else:
|
||||
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,
|
||||
)
|
||||
|
||||
def update_locations(self):
|
||||
# TODO:调多次相加
|
||||
holes = create_ordered_items_2d(
|
||||
klass=MaterialHole,
|
||||
num_items_x=4,
|
||||
num_items_y=4,
|
||||
dx=(self._size_x - 3 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中
|
||||
dy=(self._size_y - 3 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中
|
||||
dz=self._size_z,
|
||||
item_dx=self._unilabos_state["hole_spacing_x"],
|
||||
item_dy=self._unilabos_state["hole_spacing_y"],
|
||||
size_x = 1,
|
||||
size_y = 1,
|
||||
size_z = 1,
|
||||
)
|
||||
for item, original_item in zip(holes.items(), self.children):
|
||||
original_item.location = item[1].location
|
||||
|
||||
|
||||
class PlateSlot(ResourceStack):
|
||||
"""板槽位类 - 1个槽上能堆放8个板,移板只能操作最上方的板"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x: float,
|
||||
size_y: float,
|
||||
size_z: float,
|
||||
max_plates: int = 8,
|
||||
category: str = "plate_slot",
|
||||
model: Optional[str] = None
|
||||
):
|
||||
"""初始化板槽位
|
||||
|
||||
Args:
|
||||
name: 槽位名称
|
||||
max_plates: 最大板数量
|
||||
category: 类别
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
direction="z", # Z方向堆叠
|
||||
resources=[],
|
||||
)
|
||||
self.max_plates = max_plates
|
||||
self.category = category
|
||||
|
||||
def can_add_plate(self) -> bool:
|
||||
"""检查是否可以添加板"""
|
||||
return len(self.children) < self.max_plates
|
||||
|
||||
def add_plate(self, plate: MaterialPlate) -> None:
|
||||
"""添加料板"""
|
||||
if not self.can_add_plate():
|
||||
raise ValueError(f"槽位 {self.name} 已满,无法添加更多板")
|
||||
self.assign_child_resource(plate)
|
||||
|
||||
def get_top_plate(self) -> MaterialPlate:
|
||||
"""获取最上方的板"""
|
||||
if len(self.children) == 0:
|
||||
raise ValueError(f"槽位 {self.name} 为空")
|
||||
return cast(MaterialPlate, self.get_top_item())
|
||||
|
||||
def take_top_plate(self) -> MaterialPlate:
|
||||
"""取出最上方的板"""
|
||||
top_plate = self.get_top_plate()
|
||||
self.unassign_child_resource(top_plate)
|
||||
return top_plate
|
||||
|
||||
def can_access_for_picking(self) -> bool:
|
||||
"""检查是否可以进行取料操作(只有最上方的板能进行取料操作)"""
|
||||
return len(self.children) > 0
|
||||
|
||||
def serialize(self) -> dict:
|
||||
return {
|
||||
**super().serialize(),
|
||||
"max_plates": self.max_plates,
|
||||
}
|
||||
|
||||
|
||||
#是一种类型注解,不用self
|
||||
class BatteryState(TypedDict):
|
||||
"""电池状态字典"""
|
||||
diameter: float
|
||||
height: float
|
||||
assembly_pressure: float
|
||||
electrolyte_volume: float
|
||||
electrolyte_name: str
|
||||
|
||||
class Battery(Resource):
|
||||
"""电池类 - 可容纳极片"""
|
||||
children: List[ElectrodeSheet] = []
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x=1,
|
||||
size_y=1,
|
||||
size_z=1,
|
||||
category: str = "battery",
|
||||
):
|
||||
"""初始化电池
|
||||
|
||||
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: BatteryState = BatteryState(
|
||||
diameter = 1.0,
|
||||
height = 1.0,
|
||||
assembly_pressure = 1.0,
|
||||
electrolyte_volume = 1.0,
|
||||
electrolyte_name = "DP001"
|
||||
)
|
||||
|
||||
def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool:
|
||||
to_add_name = bottle._unilabos_state["electrolyte_name"]
|
||||
if bottle.aspirate_electrolyte(10):
|
||||
if self.add_electrolyte(to_add_name, 10):
|
||||
pass
|
||||
else:
|
||||
bottle._unilabos_state["electrolyte_volume"] += 10
|
||||
|
||||
def set_electrolyte(self, name: str, volume: float) -> None:
|
||||
"""设置电解液信息"""
|
||||
self._unilabos_state["electrolyte_name"] = name
|
||||
self._unilabos_state["electrolyte_volume"] = volume
|
||||
#这个应该没用,不会有加了后再加的事情
|
||||
def add_electrolyte(self, name: str, volume: float) -> bool:
|
||||
"""添加电解液信息"""
|
||||
if name != self._unilabos_state["electrolyte_name"]:
|
||||
return False
|
||||
self._unilabos_state["electrolyte_volume"] += volume
|
||||
|
||||
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 BatteryPressSlotState(TypedDict):
|
||||
"""电池状态字典"""
|
||||
diameter: float =20.0
|
||||
depth: float = 4.0
|
||||
|
||||
class BatteryPressSlot(Resource):
|
||||
"""电池压制槽类 - 设备,可容纳一个电池"""
|
||||
children: List[Battery] = []
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "BatteryPressSlot",
|
||||
category: str = "battery_press_slot",
|
||||
):
|
||||
"""初始化电池压制槽
|
||||
|
||||
Args:
|
||||
name: 压制槽名称
|
||||
diameter: 直径 (mm)
|
||||
depth: 深度 (mm)
|
||||
category: 类别
|
||||
model: 型号
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=10,
|
||||
size_y=12,
|
||||
size_z=13,
|
||||
category=category,
|
||||
)
|
||||
self._unilabos_state: BatteryPressSlotState = BatteryPressSlotState()
|
||||
|
||||
def has_battery(self) -> bool:
|
||||
"""检查是否有电池"""
|
||||
return len(self.children) > 0
|
||||
|
||||
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 assign_child_resource(
|
||||
self,
|
||||
resource: Battery,
|
||||
location: Optional[Coordinate],
|
||||
reassign: bool = True,
|
||||
):
|
||||
"""放置极片"""
|
||||
# TODO: 让高京看下槽位只有一个电池时是否这么写。
|
||||
if self.has_battery():
|
||||
raise ValueError(f"槽位已含有一个电池,无法再放置其他电池")
|
||||
super().assign_child_resource(resource, location, reassign)
|
||||
|
||||
# 根据children的编号取物料对象。
|
||||
def get_battery_info(self, index: int) -> Battery:
|
||||
return self.children[0]
|
||||
|
||||
|
||||
def TipBox64(
|
||||
name: str,
|
||||
size_x: float = 127.8,
|
||||
size_y: float = 85.5,
|
||||
size_z: float = 60.0,
|
||||
category: str = "tip_rack",
|
||||
model: Optional[str] = None,
|
||||
):
|
||||
"""64孔枪头盒类"""
|
||||
from pylabrobot.resources.tip import Tip
|
||||
|
||||
# 创建12x8=96个枪头位
|
||||
def make_tip():
|
||||
return Tip(
|
||||
has_filter=False,
|
||||
total_tip_length=20.0,
|
||||
maximal_volume=1000, # 1mL
|
||||
fitting_depth=8.0,
|
||||
)
|
||||
|
||||
tip_spots = create_ordered_items_2d(
|
||||
klass=TipSpot,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=8.0,
|
||||
dy=8.0,
|
||||
dz=0.0,
|
||||
item_dx=9.0,
|
||||
item_dy=9.0,
|
||||
size_x=10,
|
||||
size_y=10,
|
||||
size_z=0.0,
|
||||
make_tip=make_tip,
|
||||
)
|
||||
idx_available = list(range(0, 32)) + list(range(64, 96))
|
||||
tip_spots_available = {k: v for i, (k, v) in enumerate(tip_spots.items()) if i in idx_available}
|
||||
tip_rack = TipRack(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
# ordered_items=tip_spots_available,
|
||||
ordered_items=tip_spots,
|
||||
category=category,
|
||||
model=model,
|
||||
with_tips=False,
|
||||
)
|
||||
tip_rack.set_tip_state([True]*32 + [False]*32 + [True]*32) # 前32和后32个有枪头,中间32个无枪头
|
||||
return tip_rack
|
||||
|
||||
|
||||
class WasteTipBoxstate(TypedDict):
|
||||
""""废枪头盒状态字典"""
|
||||
max_tips: int = 100
|
||||
tip_count: int = 0
|
||||
|
||||
#枪头不是一次性的(同一溶液则反复使用),根据寄存器判断
|
||||
class WasteTipBox(Trash):
|
||||
"""废枪头盒类 - 100个枪头容量"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
size_x: float = 127.8,
|
||||
size_y: float = 85.5,
|
||||
size_z: float = 60.0,
|
||||
material_z_thickness=0,
|
||||
max_volume=float("inf"),
|
||||
category="trash",
|
||||
model=None,
|
||||
compute_volume_from_height=None,
|
||||
compute_height_from_volume=None,
|
||||
):
|
||||
"""初始化废枪头盒
|
||||
|
||||
Args:
|
||||
name: 废枪头盒名称
|
||||
size_x: 长度 (mm)
|
||||
size_y: 宽度 (mm)
|
||||
size_z: 高度 (mm)
|
||||
max_tips: 最大枪头容量
|
||||
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: WasteTipBoxstate = WasteTipBoxstate()
|
||||
|
||||
def add_tip(self) -> None:
|
||||
"""添加废枪头"""
|
||||
if self._unilabos_state["tip_count"] >= self._unilabos_state["max_tips"]:
|
||||
raise ValueError(f"废枪头盒 {self.name} 已满")
|
||||
self._unilabos_state["tip_count"] += 1
|
||||
|
||||
def get_tip_count(self) -> int:
|
||||
"""获取枪头数量"""
|
||||
return self._unilabos_state["tip_count"]
|
||||
|
||||
def empty(self) -> None:
|
||||
"""清空废枪头盒"""
|
||||
self._unilabos_state["tip_count"] = 0
|
||||
|
||||
|
||||
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 CoincellDeck(Deck):
|
||||
"""纽扣电池组装工作站台面类"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "coin_cell_deck",
|
||||
size_x: float = 1450.0, # 1m
|
||||
size_y: float = 1450.0, # 1m
|
||||
size_z: float = 100.0, # 0.9m
|
||||
origin: Coordinate = Coordinate(-2200, 0, 0),
|
||||
category: str = "coin_cell_deck",
|
||||
setup: bool = False, # 是否自动执行 setup
|
||||
):
|
||||
"""初始化纽扣电池组装工作站台面
|
||||
|
||||
Args:
|
||||
name: 台面名称
|
||||
size_x: 长度 (mm) - 1m
|
||||
size_y: 宽度 (mm) - 1m
|
||||
size_z: 高度 (mm) - 0.9m
|
||||
origin: 原点坐标
|
||||
category: 类别
|
||||
setup: 是否自动执行 setup 配置标准布局
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=1450.0,
|
||||
size_y=1450.0,
|
||||
size_z=100.0,
|
||||
origin=origin,
|
||||
)
|
||||
if setup:
|
||||
self.setup()
|
||||
|
||||
def setup(self) -> None:
|
||||
"""设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置"""
|
||||
# ====================================== 子弹夹 ============================================
|
||||
|
||||
# 正极片(4个洞位,2x2布局)
|
||||
zhengji_zip = MagazineHolder_4_Cathode("正极&铝箔弹夹")
|
||||
self.assign_child_resource(zhengji_zip, Coordinate(x=402.0, y=830.0, z=0))
|
||||
|
||||
# 正极壳、平垫片(6个洞位,2x2+2布局)
|
||||
zhengjike_zip = MagazineHolder_6_Cathode("正极壳&平垫片弹夹")
|
||||
self.assign_child_resource(zhengjike_zip, Coordinate(x=566.0, y=272.0, z=0))
|
||||
|
||||
# 负极壳、弹垫片(6个洞位,2x2+2布局)
|
||||
fujike_zip = MagazineHolder_6_Anode("负极壳&弹垫片弹夹")
|
||||
self.assign_child_resource(fujike_zip, Coordinate(x=474.0, y=276.0, z=0))
|
||||
|
||||
# 成品弹夹(6个洞位,3x2布局)
|
||||
chengpindanjia_zip = MagazineHolder_6_Battery("成品弹夹")
|
||||
self.assign_child_resource(chengpindanjia_zip, Coordinate(x=260.0, y=156.0, z=0))
|
||||
|
||||
# ====================================== 物料板 ============================================
|
||||
# 创建物料板(料盘carrier)- 4x4布局
|
||||
# 负极料盘
|
||||
fujiliaopan = MaterialPlate(name="负极料盘", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||
self.assign_child_resource(fujiliaopan, Coordinate(x=708.0, y=794.0, z=0))
|
||||
# for i in range(16):
|
||||
# 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)
|
||||
|
||||
# 隔膜料盘
|
||||
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))
|
||||
# for i in range(16):
|
||||
# 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)
|
||||
|
||||
# ====================================== 瓶架、移液枪 ============================================
|
||||
# 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒
|
||||
# 奔耀上料5ml分液瓶小板 - 由奔曜跨站转运而来,不单独写,但是这里应该有一个堆栈用于摆放分液瓶小板
|
||||
|
||||
# bottle_rack_3x4 = BottleRack(
|
||||
# name="bottle_rack_3x4",
|
||||
# size_x=210.0,
|
||||
# size_y=140.0,
|
||||
# size_z=100.0,
|
||||
# num_items_x=2,
|
||||
# num_items_y=4,
|
||||
# position_spacing=35.0,
|
||||
# orientation="vertical",
|
||||
# )
|
||||
# self.assign_child_resource(bottle_rack_3x4, Coordinate(x=1542.0, y=717.0, z=0))
|
||||
|
||||
# 电解液缓存位 - 6x2布局
|
||||
bottle_rack_6x2 = YIHUA_Electrolyte_12VialCarrier(name="bottle_rack_6x2")
|
||||
self.assign_child_resource(bottle_rack_6x2, Coordinate(x=1050.0, y=358.0, z=0))
|
||||
# 电解液回收位6x2
|
||||
bottle_rack_6x2_2 = YIHUA_Electrolyte_12VialCarrier(name="bottle_rack_6x2_2")
|
||||
self.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=914.0, y=358.0, z=0))
|
||||
|
||||
tip_box = TipBox64(name="tip_box_64")
|
||||
self.assign_child_resource(tip_box, Coordinate(x=782.0, y=514.0, z=0))
|
||||
|
||||
waste_tip_box = WasteTipBox(name="waste_tip_box")
|
||||
self.assign_child_resource(waste_tip_box, Coordinate(x=778.0, y=622.0, z=0))
|
||||
|
||||
|
||||
def YH_Deck(name=""):
|
||||
cd = CoincellDeck(name=name)
|
||||
cd.setup()
|
||||
return cd
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
deck = create_coin_cell_deck()
|
||||
print(deck)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,130 @@
|
||||
Name,DataType,InitValue,Comment,Attribute,DeviceType,Address,
|
||||
COIL_SYS_START_CMD,BOOL,,,,coil,8010,
|
||||
COIL_SYS_STOP_CMD,BOOL,,,,coil,8020,
|
||||
COIL_SYS_RESET_CMD,BOOL,,,,coil,8030,
|
||||
COIL_SYS_HAND_CMD,BOOL,,,,coil,8040,
|
||||
COIL_SYS_AUTO_CMD,BOOL,,,,coil,8050,
|
||||
COIL_SYS_INIT_CMD,BOOL,,,,coil,8060,
|
||||
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,8700,
|
||||
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,8710,unilab_rec_msg_succ_cmd
|
||||
COIL_SYS_START_STATUS,BOOL,,,,coil,8210,
|
||||
COIL_SYS_STOP_STATUS,BOOL,,,,coil,8220,
|
||||
COIL_SYS_RESET_STATUS,BOOL,,,,coil,8230,
|
||||
COIL_SYS_HAND_STATUS,BOOL,,,,coil,8240,
|
||||
COIL_SYS_AUTO_STATUS,BOOL,,,,coil,8250,
|
||||
COIL_SYS_INIT_STATUS,BOOL,,,,coil,8260,
|
||||
COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,8500,
|
||||
COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,8510,request_send_msg_status
|
||||
REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,11000,
|
||||
REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,11002,unilab_send_msg_electrolyte_num
|
||||
REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,11004,unilab_send_msg_electrolyte_vol
|
||||
REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,11006,unilab_send_msg_assembly_type
|
||||
REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,11008,unilab_send_msg_assembly_pressure
|
||||
REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,10000,data_assembly_coin_cell_num
|
||||
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,10002,data_open_circuit_voltage
|
||||
REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,10004,
|
||||
REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,10006,
|
||||
REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,10008,
|
||||
REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,10010,data_pole_weight
|
||||
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,10012,data_assembly_time
|
||||
REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,10014,data_assembly_pressure
|
||||
REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,10016,data_electrolyte_volume
|
||||
REG_DATA_COIN_NUM,INT16,,,,hold_register,10018,data_coin_num
|
||||
REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,10020,data_electrolyte_code()
|
||||
REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,10030,data_coin_cell_code()
|
||||
REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,12004,data_stack_vision_code()
|
||||
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,10050,data_glove_box_pressure
|
||||
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,10052,data_glove_box_water_content
|
||||
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,10054,data_glove_box_o2_content
|
||||
UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8720,
|
||||
UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8520,
|
||||
REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,496,
|
||||
REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000,
|
||||
UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730,
|
||||
UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530,
|
||||
REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8
|
||||
REG_UNILAB_INTERACT,BOOL,,,,coil,8450,
|
||||
,,,,,coil,8320,
|
||||
COIL_ALUMINUM_FOIL,BOOL,,,,coil,8340,
|
||||
REG_MSG_NE_PLATE_MATRIX,INT16,,,,hold_register,440,
|
||||
REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,,,hold_register,450,
|
||||
REG_MSG_TIP_BOX_MATRIX,INT16,,,,hold_register,480,
|
||||
REG_MSG_NE_PLATE_NUM,INT16,,,,hold_register,443,
|
||||
REG_MSG_SEPARATOR_PLATE_NUM,INT16,,,,hold_register,453,
|
||||
REG_MSG_PRESS_MODE,BOOL,,,,coil,8360,
|
||||
,BOOL,,,,coil,8300,
|
||||
,BOOL,,,,coil,8310,
|
||||
COIL_GB_L_IGNORE_CMD,BOOL,,,,coil,8320,
|
||||
COIL_GB_R_IGNORE_CMD,BOOL,,,,coil,8420,
|
||||
,BOOL,,,,coil,8350,
|
||||
COIL_ELECTROLYTE_DUAL_DROP_MODE,BOOL,,,,coil,8370,
|
||||
,BOOL,,,,coil,8380,
|
||||
,BOOL,,,,coil,8390,
|
||||
,BOOL,,,,coil,8400,
|
||||
,BOOL,,,,coil,8410,
|
||||
REG_MSG_DUAL_DROP_FIRST_VOLUME,INT16,,,,hold_register,4001,
|
||||
COIL_DUAL_DROP_SUCTION_TIMING,BOOL,,,,coil,8430,
|
||||
COIL_DUAL_DROP_START_TIMING,BOOL,,,,coil,8470,
|
||||
REG_MSG_BATTERY_CLEAN_IGNORE,BOOL,,,,coil,8460,
|
||||
COIL_ALARM_100_SYSTEM_ERROR,BOOL,,,,coil,1000,异常100-系统异常
|
||||
COIL_ALARM_101_EMERGENCY_STOP,BOOL,,,,coil,1010,异常101-急停
|
||||
COIL_ALARM_111_GLOVEBOX_EMERGENCY_STOP,BOOL,,,,coil,1110,异常111-手套箱急停
|
||||
COIL_ALARM_112_GLOVEBOX_GRATING_BLOCKED,BOOL,,,,coil,1120,异常112-手套箱内光栅遮挡
|
||||
COIL_ALARM_160_PIPETTE_TIP_SHORTAGE,BOOL,,,,coil,1600,异常160-移液枪头缺料
|
||||
COIL_ALARM_161_POSITIVE_SHELL_SHORTAGE,BOOL,,,,coil,1610,异常161-正极壳缺料
|
||||
COIL_ALARM_162_ALUMINUM_FOIL_SHORTAGE,BOOL,,,,coil,1620,异常162-铝箔垫缺料
|
||||
COIL_ALARM_163_POSITIVE_PLATE_SHORTAGE,BOOL,,,,coil,1630,异常163-正极片缺料
|
||||
COIL_ALARM_164_SEPARATOR_SHORTAGE,BOOL,,,,coil,1640,异常164-隔膜缺料
|
||||
COIL_ALARM_165_NEGATIVE_PLATE_SHORTAGE,BOOL,,,,coil,1650,异常165-负极片缺料
|
||||
COIL_ALARM_166_FLAT_WASHER_SHORTAGE,BOOL,,,,coil,1660,异常166-平垫缺料
|
||||
COIL_ALARM_167_SPRING_WASHER_SHORTAGE,BOOL,,,,coil,1670,异常167-弹垫缺料
|
||||
COIL_ALARM_168_NEGATIVE_SHELL_SHORTAGE,BOOL,,,,coil,1680,异常168-负极壳缺料
|
||||
COIL_ALARM_169_FINISHED_BATTERY_FULL,BOOL,,,,coil,1690,异常169-成品电池满料
|
||||
COIL_ALARM_201_SERVO_AXIS_01_ERROR,BOOL,,,,coil,2010,异常201-伺服轴01异常
|
||||
COIL_ALARM_202_SERVO_AXIS_02_ERROR,BOOL,,,,coil,2020,异常202-伺服轴02异常
|
||||
COIL_ALARM_203_SERVO_AXIS_03_ERROR,BOOL,,,,coil,2030,异常203-伺服轴03异常
|
||||
COIL_ALARM_204_SERVO_AXIS_04_ERROR,BOOL,,,,coil,2040,异常204-伺服轴04异常
|
||||
COIL_ALARM_205_SERVO_AXIS_05_ERROR,BOOL,,,,coil,2050,异常205-伺服轴05异常
|
||||
COIL_ALARM_206_SERVO_AXIS_06_ERROR,BOOL,,,,coil,2060,异常206-伺服轴06异常
|
||||
COIL_ALARM_207_SERVO_AXIS_07_ERROR,BOOL,,,,coil,2070,异常207-伺服轴07异常
|
||||
COIL_ALARM_208_SERVO_AXIS_08_ERROR,BOOL,,,,coil,2080,异常208-伺服轴08异常
|
||||
COIL_ALARM_209_SERVO_AXIS_09_ERROR,BOOL,,,,coil,2090,异常209-伺服轴09异常
|
||||
COIL_ALARM_210_SERVO_AXIS_10_ERROR,BOOL,,,,coil,2100,异常210-伺服轴10异常
|
||||
COIL_ALARM_211_SERVO_AXIS_11_ERROR,BOOL,,,,coil,2110,异常211-伺服轴11异常
|
||||
COIL_ALARM_212_SERVO_AXIS_12_ERROR,BOOL,,,,coil,2120,异常212-伺服轴12异常
|
||||
COIL_ALARM_213_SERVO_AXIS_13_ERROR,BOOL,,,,coil,2130,异常213-伺服轴13异常
|
||||
COIL_ALARM_214_SERVO_AXIS_14_ERROR,BOOL,,,,coil,2140,异常214-伺服轴14异常
|
||||
COIL_ALARM_250_OTHER_COMPONENT_ERROR,BOOL,,,,coil,2500,异常250-其他元件异常
|
||||
COIL_ALARM_251_PIPETTE_COMM_ERROR,BOOL,,,,coil,2510,异常251-移液枪通讯异常
|
||||
COIL_ALARM_252_PIPETTE_ALARM,BOOL,,,,coil,2520,异常252-移液枪报警
|
||||
COIL_ALARM_256_ELECTRIC_GRIPPER_ERROR,BOOL,,,,coil,2560,异常256-电爪异常
|
||||
COIL_ALARM_262_RB_UNKNOWN_POSITION_ERROR,BOOL,,,,coil,2620,异常262-RB报警:未知点位错误
|
||||
COIL_ALARM_263_RB_XYZ_PARAM_LIMIT_ERROR,BOOL,,,,coil,2630,异常263-RB报警:X、Y、Z参数超限制
|
||||
COIL_ALARM_264_RB_VISION_PARAM_ERROR,BOOL,,,,coil,2640,异常264-RB报警:视觉参数误差过大
|
||||
COIL_ALARM_265_RB_NOZZLE_1_PICK_FAIL,BOOL,,,,coil,2650,异常265-RB报警:1#吸嘴取料失败
|
||||
COIL_ALARM_266_RB_NOZZLE_2_PICK_FAIL,BOOL,,,,coil,2660,异常266-RB报警:2#吸嘴取料失败
|
||||
COIL_ALARM_267_RB_NOZZLE_3_PICK_FAIL,BOOL,,,,coil,2670,异常267-RB报警:3#吸嘴取料失败
|
||||
COIL_ALARM_268_RB_NOZZLE_4_PICK_FAIL,BOOL,,,,coil,2680,异常268-RB报警:4#吸嘴取料失败
|
||||
COIL_ALARM_269_RB_TRAY_PICK_FAIL,BOOL,,,,coil,2690,异常269-RB报警:取物料盘失败
|
||||
COIL_ALARM_280_RB_COLLISION_ERROR,BOOL,,,,coil,2800,异常280-RB碰撞异常
|
||||
COIL_ALARM_290_VISION_SYSTEM_COMM_ERROR,BOOL,,,,coil,2900,异常290-视觉系统通讯异常
|
||||
COIL_ALARM_291_VISION_ALIGNMENT_NG,BOOL,,,,coil,2910,异常291-视觉对位NG异常
|
||||
COIL_ALARM_292_BARCODE_SCANNER_COMM_ERROR,BOOL,,,,coil,2920,异常292-扫码枪通讯异常
|
||||
COIL_ALARM_310_OCV_TRANSFER_NOZZLE_SUCTION_ERROR,BOOL,,,,coil,3100,异常310-开电移载吸嘴吸真空异常
|
||||
COIL_ALARM_311_OCV_TRANSFER_NOZZLE_BREAK_ERROR,BOOL,,,,coil,3110,异常311-开电移载吸嘴破真空异常
|
||||
COIL_ALARM_312_WEIGHT_TRANSFER_NOZZLE_SUCTION_ERROR,BOOL,,,,coil,3120,异常312-称重移载吸嘴吸真空异常
|
||||
COIL_ALARM_313_WEIGHT_TRANSFER_NOZZLE_BREAK_ERROR,BOOL,,,,coil,3130,异常313-称重移载吸嘴破真空异常
|
||||
COIL_ALARM_340_OCV_NOZZLE_TRANSFER_CYLINDER_ERROR,BOOL,,,,coil,3400,异常340-开路电压吸嘴移载气缸异常
|
||||
COIL_ALARM_342_OCV_NOZZLE_LIFT_CYLINDER_ERROR,BOOL,,,,coil,3420,异常342-开路电压吸嘴升降气缸异常
|
||||
COIL_ALARM_344_OCV_CRIMPING_CYLINDER_ERROR,BOOL,,,,coil,3440,异常344-开路电压旋压气缸异常
|
||||
COIL_ALARM_350_WEIGHT_NOZZLE_TRANSFER_CYLINDER_ERROR,BOOL,,,,coil,3500,异常350-称重吸嘴移载气缸异常
|
||||
COIL_ALARM_352_WEIGHT_NOZZLE_LIFT_CYLINDER_ERROR,BOOL,,,,coil,3520,异常352-称重吸嘴升降气缸异常
|
||||
COIL_ALARM_354_CLEANING_CLOTH_TRANSFER_CYLINDER_ERROR,BOOL,,,,coil,3540,异常354-清洗无尘布移载气缸异常
|
||||
COIL_ALARM_356_CLEANING_CLOTH_PRESS_CYLINDER_ERROR,BOOL,,,,coil,3560,异常356-清洗无尘布压紧气缸异常
|
||||
COIL_ALARM_360_ELECTROLYTE_BOTTLE_POSITION_CYLINDER_ERROR,BOOL,,,,coil,3600,异常360-电解液瓶定位气缸异常
|
||||
COIL_ALARM_362_PIPETTE_TIP_BOX_POSITION_CYLINDER_ERROR,BOOL,,,,coil,3620,异常362-移液枪头盒定位气缸异常
|
||||
COIL_ALARM_364_REAGENT_BOTTLE_GRIPPER_LIFT_CYLINDER_ERROR,BOOL,,,,coil,3640,异常364-试剂瓶夹爪升降气缸异常
|
||||
COIL_ALARM_366_REAGENT_BOTTLE_GRIPPER_CYLINDER_ERROR,BOOL,,,,coil,3660,异常366-试剂瓶夹爪气缸异常
|
||||
COIL_ALARM_370_PRESS_MODULE_BLOW_CYLINDER_ERROR,BOOL,,,,coil,3700,异常370-压制模块吹气气缸异常
|
||||
COIL_ALARM_151_ELECTROLYTE_BOTTLE_POSITION_ERROR,BOOL,,,,coil,1510,异常151-电解液瓶定位在籍异常
|
||||
COIL_ALARM_152_ELECTROLYTE_BOTTLE_CAP_ERROR,BOOL,,,,coil,1520,异常152-电解液瓶盖在籍异常
|
||||
|
@@ -0,0 +1,6 @@
|
||||
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
|
||||
20260110_153356,0.0,23.889999389648438,17.0,3609,40,7,deaoR,
|
||||
20260110_153640,3.430999994277954,23.719999313354492,162.0,3560,40,7,deaoR,
|
||||
20260110_162905,0.0,23.920000076293945,772.0,3625,30,7,LG600001,YS103130
|
||||
20260110_163526,3.430999994277954,24.010000228881836,234.0,3690,30,7,LG600001,YS102964
|
||||
20260110_164530,0.0,23.589998245239258,219.0,3690,30,7,LG600001,YS102857
|
||||
|
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "bioyond_cell_workstation",
|
||||
"name": "配液分液工站",
|
||||
"children": [
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "bioyond_cell",
|
||||
"config": {
|
||||
"protocol_type": [],
|
||||
"station_resource": {}
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
|
||||
{
|
||||
"id": "BatteryStation",
|
||||
"name": "扣电工作站",
|
||||
"children": [
|
||||
"coin_cell_deck"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "coincellassemblyworkstation_device",
|
||||
"position": {
|
||||
"x": -600,
|
||||
"y": -400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"debug_mode": false,
|
||||
"protocol_type": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
107
unilabos/devices/workstation/coin_cell_assembly/电池资源冲突修复说明.md
Normal file
107
unilabos/devices/workstation/coin_cell_assembly/电池资源冲突修复说明.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# 电池组装资源冲突问题修复说明
|
||||
|
||||
## 问题描述
|
||||
|
||||
在运行 `func_allpack_cmd` 函数时,遇到以下错误:
|
||||
|
||||
```
|
||||
ValueError: Resource 'battery_0' already assigned to deck
|
||||
```
|
||||
|
||||
**错误位置**:`coin_cell_assembly.py` 第 849 行
|
||||
```python
|
||||
liaopan3.children[self.coin_num_N].assign_child_resource(battery, location=None)
|
||||
```
|
||||
|
||||
## 原因分析
|
||||
|
||||
1. **资源名称冲突**:
|
||||
- 每次创建电池资源使用固定格式 `battery_{coin_num_N}`
|
||||
- 如果程序重启或断点恢复,`coin_num_N` 可能重置为 0
|
||||
- Deck 上可能已存在 `battery_0` 等同名资源
|
||||
|
||||
2. **缺少冲突处理**:
|
||||
- 在分配资源前没有检查目标位置是否已有资源
|
||||
- 没有清理机制来移除旧资源
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 1. 使用时间戳确保资源名称唯一
|
||||
|
||||
```python
|
||||
# 之前
|
||||
battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", ...)
|
||||
|
||||
# 修复后
|
||||
timestamp_suffix = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
|
||||
battery_name = f"battery_{self.coin_num_N}_{timestamp_suffix}"
|
||||
battery = ElectrodeSheet(name=battery_name, ...)
|
||||
```
|
||||
|
||||
### 2. 添加资源冲突检查和清理
|
||||
|
||||
```python
|
||||
# 检查目标位置是否已有资源
|
||||
target_slot = liaopan3.children[self.coin_num_N]
|
||||
if target_slot.children:
|
||||
logger.warning(f"位置 {self.coin_num_N} 已有资源,将先卸载旧资源")
|
||||
try:
|
||||
# 卸载所有现有子资源
|
||||
for child in list(target_slot.children):
|
||||
target_slot.unassign_child_resource(child)
|
||||
logger.info(f"已卸载旧资源: {child.name}")
|
||||
except Exception as e:
|
||||
logger.error(f"卸载旧资源时出错: {e}")
|
||||
```
|
||||
|
||||
### 3. 增强错误处理
|
||||
|
||||
```python
|
||||
# 分配新资源到目标位置
|
||||
try:
|
||||
target_slot.assign_child_resource(battery, location=None)
|
||||
logger.info(f"成功分配电池 {battery_name} 到位置 {self.coin_num_N}")
|
||||
except Exception as e:
|
||||
logger.error(f"分配电池资源失败: {e}")
|
||||
raise
|
||||
```
|
||||
|
||||
## 修复效果
|
||||
|
||||
✅ **不再出现重复资源名称错误**
|
||||
- 每个电池资源都有唯一的时间戳后缀
|
||||
- 即使 `coin_num_N` 相同,资源名称也不会冲突
|
||||
|
||||
✅ **自动清理旧资源**
|
||||
- 在分配新资源前检查目标位置
|
||||
- 自动卸载已存在的旧资源
|
||||
|
||||
✅ **增强日志记录**
|
||||
- 记录资源卸载操作
|
||||
- 记录资源分配成功/失败
|
||||
- 便于调试和问题追踪
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **正常运行测试**:
|
||||
```python
|
||||
workstation.func_allpack_cmd(
|
||||
elec_num=1,
|
||||
elec_use_num=1,
|
||||
elec_vol=20,
|
||||
file_path="..."
|
||||
)
|
||||
```
|
||||
|
||||
2. **断点恢复测试**:
|
||||
- 运行一次后中断
|
||||
- 再次运行相同参数
|
||||
- 验证不会出现资源冲突错误
|
||||
|
||||
3. **连续运行测试**:
|
||||
- 连续多次运行
|
||||
- 验证每次都能正常分配资源
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `coin_cell_assembly.py` - 第 838-875 行(`func_pack_get_msg_cmd` 函数)
|
||||
@@ -147,7 +147,7 @@ class WorkstationBase(ABC):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
deck: Optional[Deck],
|
||||
deck: Deck,
|
||||
*args,
|
||||
**kwargs, # 必须有kwargs
|
||||
):
|
||||
@@ -349,5 +349,5 @@ class WorkstationBase(ABC):
|
||||
|
||||
|
||||
class ProtocolNode(WorkstationBase):
|
||||
def __init__(self, protocol_type: List[str], deck: Optional[PLRResource], *args, **kwargs):
|
||||
def __init__(self, deck: Optional[PLRResource], *args, **kwargs):
|
||||
super().__init__(deck, *args, **kwargs)
|
||||
|
||||
@@ -4,7 +4,7 @@ Workstation HTTP Service Module
|
||||
|
||||
统一的工作站报送接收服务,基于LIMS协议规范:
|
||||
1. 步骤完成报送 - POST /report/step_finish
|
||||
2. 通量完成报送 - POST /report/sample_finish
|
||||
2. 通量完成报送 - POST /report/sample_finish
|
||||
3. 任务完成报送 - POST /report/order_finish
|
||||
4. 批量更新报送 - POST /report/batch_update
|
||||
5. 物料变更报送 - POST /report/material_change
|
||||
@@ -22,7 +22,6 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from urllib.parse import urlparse
|
||||
from dataclasses import dataclass, asdict
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from unilabos.utils.log import logger
|
||||
|
||||
@@ -55,18 +54,18 @@ class HttpResponse:
|
||||
|
||||
class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
"""工作站HTTP请求处理器"""
|
||||
|
||||
|
||||
def __init__(self, workstation_instance, *args, **kwargs):
|
||||
self.workstation = workstation_instance
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def do_POST(self):
|
||||
"""处理POST请求 - 统一的工作站报送接口"""
|
||||
try:
|
||||
# 解析请求路径
|
||||
parsed_path = urlparse(self.path)
|
||||
endpoint = parsed_path.path
|
||||
|
||||
|
||||
# 读取请求体
|
||||
content_length = int(self.headers.get('Content-Length', 0))
|
||||
if content_length > 0:
|
||||
@@ -74,17 +73,9 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
request_data = json.loads(post_data.decode('utf-8'))
|
||||
else:
|
||||
request_data = {}
|
||||
|
||||
|
||||
logger.info(f"收到工作站报送: {endpoint} - {request_data.get('token', 'unknown')}")
|
||||
|
||||
try:
|
||||
payload_for_log = {"method": "POST", **request_data}
|
||||
self._save_raw_request(endpoint, payload_for_log)
|
||||
if hasattr(self.workstation, '_reports_received_count'):
|
||||
self.workstation._reports_received_count += 1
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# 统一的报送端点路由(基于LIMS协议规范)
|
||||
if endpoint == '/report/step_finish':
|
||||
response = self._handle_step_finish_report(request_data)
|
||||
@@ -99,8 +90,6 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
response = self._handle_material_change_report(request_data)
|
||||
elif endpoint == '/report/error_handling':
|
||||
response = self._handle_error_handling_report(request_data)
|
||||
elif endpoint == '/report/temperature-cutoff':
|
||||
response = self._handle_temperature_cutoff_report(request_data)
|
||||
# 保留LIMS协议端点以兼容现有系统
|
||||
elif endpoint == '/LIMS/step_finish':
|
||||
response = self._handle_step_finish_report(request_data)
|
||||
@@ -113,19 +102,18 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
success=False,
|
||||
message=f"不支持的报送端点: {endpoint}",
|
||||
data={"supported_endpoints": [
|
||||
"/report/step_finish",
|
||||
"/report/sample_finish",
|
||||
"/report/step_finish",
|
||||
"/report/sample_finish",
|
||||
"/report/order_finish",
|
||||
"/report/batch_update",
|
||||
"/report/material_change",
|
||||
"/report/error_handling",
|
||||
"/report/temperature-cutoff"
|
||||
"/report/error_handling"
|
||||
]}
|
||||
)
|
||||
|
||||
|
||||
# 发送响应
|
||||
self._send_response(response)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理工作站报送失败: {e}\\n{traceback.format_exc()}")
|
||||
error_response = HttpResponse(
|
||||
@@ -133,18 +121,13 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
message=f"请求处理失败: {str(e)}"
|
||||
)
|
||||
self._send_response(error_response)
|
||||
|
||||
|
||||
def do_GET(self):
|
||||
"""处理GET请求 - 健康检查和状态查询"""
|
||||
try:
|
||||
parsed_path = urlparse(self.path)
|
||||
endpoint = parsed_path.path
|
||||
|
||||
try:
|
||||
self._save_raw_request(endpoint, {"method": "GET"})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
if endpoint == '/status':
|
||||
response = self._handle_status_check()
|
||||
elif endpoint == '/health':
|
||||
@@ -155,9 +138,9 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
message=f"不支持的查询端点: {endpoint}",
|
||||
data={"supported_endpoints": ["/status", "/health"]}
|
||||
)
|
||||
|
||||
|
||||
self._send_response(response)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"GET请求处理失败: {e}")
|
||||
error_response = HttpResponse(
|
||||
@@ -165,7 +148,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
message=f"GET请求处理失败: {str(e)}"
|
||||
)
|
||||
self._send_response(error_response)
|
||||
|
||||
|
||||
def do_OPTIONS(self):
|
||||
"""处理OPTIONS请求 - CORS预检请求"""
|
||||
try:
|
||||
@@ -176,12 +159,12 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
||||
self.send_header('Access-Control-Max-Age', '86400')
|
||||
self.end_headers()
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"OPTIONS请求处理失败: {e}")
|
||||
self.send_response(500)
|
||||
self.end_headers()
|
||||
|
||||
|
||||
def _handle_step_finish_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
||||
"""处理步骤完成报送(统一LIMS协议规范)"""
|
||||
try:
|
||||
@@ -192,7 +175,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
success=False,
|
||||
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
||||
)
|
||||
|
||||
|
||||
# 验证data字段内容
|
||||
data = request_data['data']
|
||||
data_required_fields = ['orderCode', 'orderName', 'stepName', 'stepId', 'sampleId', 'startTime', 'endTime']
|
||||
@@ -201,31 +184,31 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
success=False,
|
||||
message=f"data字段缺少必要内容: {', '.join(data_missing_fields)}"
|
||||
)
|
||||
|
||||
|
||||
# 创建统一请求对象
|
||||
report_request = WorkstationReportRequest(
|
||||
token=request_data['token'],
|
||||
request_time=request_data['request_time'],
|
||||
data=data
|
||||
)
|
||||
|
||||
|
||||
# 调用工作站处理方法
|
||||
result = self.workstation.process_step_finish_report(report_request)
|
||||
|
||||
|
||||
return HttpResponse(
|
||||
success=True,
|
||||
message=f"步骤完成报送已处理: {data['stepName']} ({data['orderCode']})",
|
||||
acknowledgment_id=f"STEP_{int(time.time() * 1000)}_{data['stepId']}",
|
||||
data=result
|
||||
)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理步骤完成报送失败: {e}")
|
||||
return HttpResponse(
|
||||
success=False,
|
||||
message=f"步骤完成报送处理失败: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
def _handle_sample_finish_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
||||
"""处理通量完成报送(统一LIMS协议规范)"""
|
||||
try:
|
||||
@@ -236,7 +219,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
success=False,
|
||||
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
||||
)
|
||||
|
||||
|
||||
# 验证data字段内容
|
||||
data = request_data['data']
|
||||
data_required_fields = ['orderCode', 'orderName', 'sampleId', 'startTime', 'endTime', 'status']
|
||||
@@ -245,37 +228,37 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
success=False,
|
||||
message=f"data字段缺少必要内容: {', '.join(data_missing_fields)}"
|
||||
)
|
||||
|
||||
|
||||
# 创建统一请求对象
|
||||
report_request = WorkstationReportRequest(
|
||||
token=request_data['token'],
|
||||
request_time=request_data['request_time'],
|
||||
data=data
|
||||
)
|
||||
|
||||
|
||||
# 调用工作站处理方法
|
||||
result = self.workstation.process_sample_finish_report(report_request)
|
||||
|
||||
|
||||
status_names = {
|
||||
"0": "待生产", "2": "进样", "10": "开始",
|
||||
"0": "待生产", "2": "进样", "10": "开始",
|
||||
"20": "完成", "-2": "异常停止", "-3": "人工停止"
|
||||
}
|
||||
status_desc = status_names.get(str(data['status']), f"状态{data['status']}")
|
||||
|
||||
|
||||
return HttpResponse(
|
||||
success=True,
|
||||
message=f"通量完成报送已处理: {data['sampleId']} ({data['orderCode']}) - {status_desc}",
|
||||
acknowledgment_id=f"SAMPLE_{int(time.time() * 1000)}_{data['sampleId']}",
|
||||
data=result
|
||||
)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理通量完成报送失败: {e}")
|
||||
return HttpResponse(
|
||||
success=False,
|
||||
message=f"通量完成报送处理失败: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
def _handle_order_finish_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
||||
"""处理任务完成报送(统一LIMS协议规范)"""
|
||||
try:
|
||||
@@ -286,7 +269,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
success=False,
|
||||
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
||||
)
|
||||
|
||||
|
||||
# 验证data字段内容
|
||||
data = request_data['data']
|
||||
data_required_fields = ['orderCode', 'orderName', 'startTime', 'endTime', 'status']
|
||||
@@ -295,7 +278,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
success=False,
|
||||
message=f"data字段缺少必要内容: {', '.join(data_missing_fields)}"
|
||||
)
|
||||
|
||||
|
||||
# 处理物料使用记录
|
||||
used_materials = []
|
||||
if 'usedMaterials' in data:
|
||||
@@ -307,85 +290,41 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
usedQuantity=material_data.get('usedQuantity', 0.0)
|
||||
)
|
||||
used_materials.append(material)
|
||||
|
||||
|
||||
# 创建统一请求对象
|
||||
report_request = WorkstationReportRequest(
|
||||
token=request_data['token'],
|
||||
request_time=request_data['request_time'],
|
||||
data=data
|
||||
)
|
||||
|
||||
|
||||
# 调用工作站处理方法
|
||||
result = self.workstation.process_order_finish_report(report_request, used_materials)
|
||||
|
||||
|
||||
status_names = {"30": "完成", "-11": "异常停止", "-12": "人工停止"}
|
||||
status_desc = status_names.get(str(data['status']), f"状态{data['status']}")
|
||||
|
||||
|
||||
return HttpResponse(
|
||||
success=True,
|
||||
message=f"任务完成报送已处理: {data['orderName']} ({data['orderCode']}) - {status_desc}",
|
||||
acknowledgment_id=f"ORDER_{int(time.time() * 1000)}_{data['orderCode']}",
|
||||
data=result
|
||||
)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理任务完成报送失败: {e}")
|
||||
return HttpResponse(
|
||||
success=False,
|
||||
message=f"任务完成报送处理失败: {str(e)}"
|
||||
)
|
||||
|
||||
def _handle_temperature_cutoff_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
||||
try:
|
||||
required_fields = ['token', 'request_time', 'data']
|
||||
if missing := [f for f in required_fields if f not in request_data]:
|
||||
return HttpResponse(success=False, message=f"缺少必要字段: {', '.join(missing)}")
|
||||
|
||||
data = request_data['data']
|
||||
metrics = [
|
||||
'frameCode',
|
||||
'generateTime',
|
||||
'targetTemperature',
|
||||
'settingTemperature',
|
||||
'inTemperature',
|
||||
'outTemperature',
|
||||
'pt100Temperature',
|
||||
'sensorAverageTemperature',
|
||||
'speed',
|
||||
'force',
|
||||
'viscosity',
|
||||
'averageViscosity'
|
||||
]
|
||||
if miss := [f for f in metrics if f not in data]:
|
||||
return HttpResponse(success=False, message=f"data字段缺少必要内容: {', '.join(miss)}")
|
||||
|
||||
report_request = WorkstationReportRequest(
|
||||
token=request_data['token'],
|
||||
request_time=request_data['request_time'],
|
||||
data=data
|
||||
)
|
||||
|
||||
result = {}
|
||||
if hasattr(self.workstation, 'process_temperature_cutoff_report'):
|
||||
result = self.workstation.process_temperature_cutoff_report(report_request)
|
||||
|
||||
return HttpResponse(
|
||||
success=True,
|
||||
message=f"温度/粘度报送已处理: 帧{data['frameCode']}",
|
||||
acknowledgment_id=f"TEMP_CUTOFF_{int(time.time()*1000)}_{data['frameCode']}",
|
||||
data=result
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"处理温度/粘度报送失败: {e}\n{traceback.format_exc()}")
|
||||
return HttpResponse(success=False, message=f"温度/粘度报送处理失败: {str(e)}")
|
||||
|
||||
|
||||
def _handle_batch_update_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
||||
"""处理批量报送"""
|
||||
try:
|
||||
step_updates = request_data.get('step_updates', [])
|
||||
sample_updates = request_data.get('sample_updates', [])
|
||||
order_updates = request_data.get('order_updates', [])
|
||||
|
||||
|
||||
results = {
|
||||
'step_results': [],
|
||||
'sample_results': [],
|
||||
@@ -393,7 +332,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
'total_processed': 0,
|
||||
'total_failed': 0
|
||||
}
|
||||
|
||||
|
||||
# 处理批量步骤更新
|
||||
for step_data in step_updates:
|
||||
try:
|
||||
@@ -408,7 +347,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
except Exception as e:
|
||||
results['step_results'].append(HttpResponse(success=False, message=str(e)))
|
||||
results['total_failed'] += 1
|
||||
|
||||
|
||||
# 处理批量通量更新
|
||||
for sample_data in sample_updates:
|
||||
try:
|
||||
@@ -423,7 +362,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
except Exception as e:
|
||||
results['sample_results'].append(HttpResponse(success=False, message=str(e)))
|
||||
results['total_failed'] += 1
|
||||
|
||||
|
||||
# 处理批量任务更新
|
||||
for order_data in order_updates:
|
||||
try:
|
||||
@@ -438,21 +377,21 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
except Exception as e:
|
||||
results['order_results'].append(HttpResponse(success=False, message=str(e)))
|
||||
results['total_failed'] += 1
|
||||
|
||||
|
||||
return HttpResponse(
|
||||
success=results['total_failed'] == 0,
|
||||
message=f"批量报送处理完成: {results['total_processed']} 成功, {results['total_failed']} 失败",
|
||||
acknowledgment_id=f"BATCH_{int(time.time() * 1000)}",
|
||||
data=results
|
||||
)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理批量报送失败: {e}")
|
||||
return HttpResponse(
|
||||
success=False,
|
||||
message=f"批量报送处理失败: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
def _handle_material_change_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
||||
"""处理物料变更报送"""
|
||||
try:
|
||||
@@ -478,24 +417,24 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
success=False,
|
||||
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
||||
)
|
||||
|
||||
|
||||
# 调用工作站的处理方法
|
||||
result = self.workstation.process_material_change_report(request_data)
|
||||
|
||||
|
||||
return HttpResponse(
|
||||
success=True,
|
||||
message=f"物料变更报送已处理: {request_data['resource_id']} ({request_data['change_type']})",
|
||||
acknowledgment_id=f"MATERIAL_{int(time.time() * 1000)}_{request_data['resource_id']}",
|
||||
data=result
|
||||
)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理物料变更报送失败: {e}")
|
||||
return HttpResponse(
|
||||
success=False,
|
||||
message=f"物料变更报送处理失败: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
def _handle_error_handling_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
||||
"""处理错误处理报送"""
|
||||
try:
|
||||
@@ -507,13 +446,13 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
success=False,
|
||||
message="奔曜格式缺少text字段"
|
||||
)
|
||||
|
||||
|
||||
error_data = request_data["text"]
|
||||
logger.info(f"收到奔曜错误处理报送: {error_data}")
|
||||
|
||||
|
||||
# 调用工作站的处理方法
|
||||
result = self.workstation.handle_external_error(error_data)
|
||||
|
||||
|
||||
return HttpResponse(
|
||||
success=True,
|
||||
message=f"错误处理报送已收到: 任务{error_data.get('task', 'unknown')}, 错误代码{error_data.get('code', 'unknown')}",
|
||||
@@ -528,50 +467,42 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
success=False,
|
||||
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
||||
)
|
||||
|
||||
|
||||
# 调用工作站的处理方法
|
||||
result = self.workstation.handle_external_error(request_data)
|
||||
|
||||
|
||||
return HttpResponse(
|
||||
success=True,
|
||||
message=f"错误处理报送已处理: {request_data['error_type']} - {request_data['error_message']}",
|
||||
acknowledgment_id=f"ERROR_{int(time.time() * 1000)}_{request_data.get('action_id', 'unknown')}",
|
||||
data=result
|
||||
)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理错误处理报送失败: {e}")
|
||||
return HttpResponse(
|
||||
success=False,
|
||||
message=f"错误处理报送处理失败: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
def _handle_status_check(self) -> HttpResponse:
|
||||
"""处理状态查询"""
|
||||
try:
|
||||
# 安全地获取 device_id
|
||||
device_id = "unknown"
|
||||
if hasattr(self.workstation, 'device_id'):
|
||||
device_id = self.workstation.device_id
|
||||
elif hasattr(self.workstation, '_ros_node') and hasattr(self.workstation._ros_node, 'device_id'):
|
||||
device_id = self.workstation._ros_node.device_id
|
||||
|
||||
return HttpResponse(
|
||||
success=True,
|
||||
message="工作站报送服务正常运行",
|
||||
data={
|
||||
"workstation_id": device_id,
|
||||
"workstation_id": self.workstation.device_id,
|
||||
"service_type": "unified_reporting_service",
|
||||
"uptime": time.time() - getattr(self.workstation, '_start_time', time.time()),
|
||||
"reports_received": getattr(self.workstation, '_reports_received_count', 0),
|
||||
"supported_endpoints": [
|
||||
"POST /report/step_finish",
|
||||
"POST /report/sample_finish",
|
||||
"POST /report/sample_finish",
|
||||
"POST /report/order_finish",
|
||||
"POST /report/batch_update",
|
||||
"POST /report/material_change",
|
||||
"POST /report/error_handling",
|
||||
"POST /report/temperature-cutoff",
|
||||
"GET /status",
|
||||
"GET /health"
|
||||
]
|
||||
@@ -583,52 +514,36 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||
success=False,
|
||||
message=f"状态查询失败: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
def _send_response(self, response: HttpResponse):
|
||||
"""发送响应"""
|
||||
try:
|
||||
# 设置响应状态码
|
||||
status_code = 200 if response.success else 400
|
||||
self.send_response(status_code)
|
||||
|
||||
|
||||
# 设置响应头
|
||||
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
||||
self.end_headers()
|
||||
|
||||
|
||||
# 发送响应体
|
||||
response_json = json.dumps(asdict(response), ensure_ascii=False, indent=2)
|
||||
self.wfile.write(response_json.encode('utf-8'))
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"发送响应失败: {e}")
|
||||
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""重写日志方法"""
|
||||
logger.debug(f"HTTP请求: {format % args}")
|
||||
|
||||
def _save_raw_request(self, endpoint: str, request_data: Dict[str, Any]) -> None:
|
||||
try:
|
||||
base_dir = Path(__file__).resolve().parents[3] / "unilabos_data" / "http_reports"
|
||||
base_dir.mkdir(parents=True, exist_ok=True)
|
||||
log_path = getattr(self.workstation, "_http_log_path", None)
|
||||
log_file = Path(log_path) if log_path else (base_dir / f"http_{int(time.time()*1000)}.log")
|
||||
payload = {
|
||||
"endpoint": endpoint,
|
||||
"received_at": datetime.now().isoformat(),
|
||||
"body": request_data
|
||||
}
|
||||
with open(log_file, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class WorkstationHTTPService:
|
||||
"""工作站HTTP服务"""
|
||||
|
||||
|
||||
def __init__(self, workstation_instance, host: str = "127.0.0.1", port: int = 8080):
|
||||
self.workstation = workstation_instance
|
||||
self.host = host
|
||||
@@ -636,42 +551,31 @@ class WorkstationHTTPService:
|
||||
self.server = None
|
||||
self.server_thread = None
|
||||
self.running = False
|
||||
|
||||
|
||||
# 初始化统计信息
|
||||
self.workstation._start_time = time.time()
|
||||
self.workstation._reports_received_count = 0
|
||||
|
||||
|
||||
def start(self):
|
||||
"""启动HTTP服务"""
|
||||
try:
|
||||
# 创建处理器工厂函数
|
||||
def handler_factory(*args, **kwargs):
|
||||
return WorkstationHTTPHandler(self.workstation, *args, **kwargs)
|
||||
|
||||
|
||||
# 创建HTTP服务器
|
||||
self.server = HTTPServer((self.host, self.port), handler_factory)
|
||||
base_dir = Path(__file__).resolve().parents[3] / "unilabos_data" / "http_reports"
|
||||
base_dir.mkdir(parents=True, exist_ok=True)
|
||||
session_log = base_dir / f"http_{int(time.time()*1000)}.log"
|
||||
setattr(self.workstation, "_http_log_path", str(session_log))
|
||||
|
||||
# 安全地获取 device_id 用于线程命名
|
||||
device_id = "unknown"
|
||||
if hasattr(self.workstation, 'device_id'):
|
||||
device_id = self.workstation.device_id
|
||||
elif hasattr(self.workstation, '_ros_node') and hasattr(self.workstation._ros_node, 'device_id'):
|
||||
device_id = self.workstation._ros_node.device_id
|
||||
|
||||
|
||||
# 在单独线程中运行服务器
|
||||
self.server_thread = threading.Thread(
|
||||
target=self._run_server,
|
||||
daemon=True,
|
||||
name=f"WorkstationHTTP-{device_id}"
|
||||
name=f"WorkstationHTTP-{self.workstation.device_id}"
|
||||
)
|
||||
|
||||
|
||||
self.running = True
|
||||
self.server_thread.start()
|
||||
|
||||
|
||||
logger.info(f"工作站HTTP报送服务已启动: http://{self.host}:{self.port}")
|
||||
logger.info("统一的报送端点 (基于LIMS协议规范):")
|
||||
logger.info(" - POST /report/step_finish # 步骤完成报送")
|
||||
@@ -681,7 +585,6 @@ class WorkstationHTTPService:
|
||||
logger.info("扩展报送端点:")
|
||||
logger.info(" - POST /report/material_change # 物料变更报送")
|
||||
logger.info(" - POST /report/error_handling # 错误处理报送")
|
||||
logger.info(" - POST /report/temperature-cutoff # 温度/粘度报送")
|
||||
logger.info("兼容端点:")
|
||||
logger.info(" - POST /LIMS/step_finish # 兼容LIMS步骤完成")
|
||||
logger.info(" - POST /LIMS/preintake_finish # 兼容LIMS通量完成")
|
||||
@@ -689,33 +592,33 @@ class WorkstationHTTPService:
|
||||
logger.info("服务端点:")
|
||||
logger.info(" - GET /status # 服务状态查询")
|
||||
logger.info(" - GET /health # 健康检查")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"启动HTTP服务失败: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def stop(self):
|
||||
"""停止HTTP服务"""
|
||||
try:
|
||||
if self.running and self.server:
|
||||
logger.info("正在停止工作站HTTP报送服务...")
|
||||
self.running = False
|
||||
|
||||
|
||||
# 停止serve_forever循环
|
||||
self.server.shutdown()
|
||||
|
||||
|
||||
# 等待服务器线程结束
|
||||
if self.server_thread and self.server_thread.is_alive():
|
||||
self.server_thread.join(timeout=5.0)
|
||||
|
||||
|
||||
# 关闭服务器套接字
|
||||
self.server.server_close()
|
||||
|
||||
|
||||
logger.info("工作站HTTP报送服务已停止")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"停止HTTP服务失败: {e}")
|
||||
|
||||
|
||||
def _run_server(self):
|
||||
"""运行HTTP服务器"""
|
||||
try:
|
||||
@@ -726,12 +629,12 @@ class WorkstationHTTPService:
|
||||
logger.error(f"HTTP服务运行错误: {e}")
|
||||
finally:
|
||||
logger.info("HTTP服务器线程已退出")
|
||||
|
||||
|
||||
@property
|
||||
def is_running(self) -> bool:
|
||||
"""检查服务是否正在运行"""
|
||||
return self.running and self.server_thread and self.server_thread.is_alive()
|
||||
|
||||
|
||||
@property
|
||||
def service_url(self) -> str:
|
||||
"""获取服务URL"""
|
||||
@@ -745,7 +648,7 @@ class MaterialChangeReport:
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
@dataclass
|
||||
class TaskExecutionReport:
|
||||
"""已废弃:任务执行报送,请使用统一的WorkstationReportRequest"""
|
||||
pass
|
||||
@@ -765,43 +668,40 @@ __all__ = [
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 简单测试HTTP服务
|
||||
class BioyondWorkstation:
|
||||
class DummyWorkstation:
|
||||
device_id = "WS-001"
|
||||
|
||||
|
||||
def process_step_finish_report(self, report_request):
|
||||
return {"processed": True}
|
||||
|
||||
|
||||
def process_sample_finish_report(self, report_request):
|
||||
return {"processed": True}
|
||||
|
||||
|
||||
def process_order_finish_report(self, report_request, used_materials):
|
||||
return {"processed": True}
|
||||
|
||||
|
||||
def process_material_change_report(self, report_data):
|
||||
return {"processed": True}
|
||||
|
||||
|
||||
def handle_external_error(self, error_data):
|
||||
return {"handled": True}
|
||||
|
||||
def process_temperature_cutoff_report(self, report_request):
|
||||
return {"processed": True, "metrics": report_request.data}
|
||||
|
||||
workstation = BioyondWorkstation()
|
||||
|
||||
workstation = DummyWorkstation()
|
||||
http_service = WorkstationHTTPService(workstation)
|
||||
|
||||
|
||||
try:
|
||||
http_service.start()
|
||||
print(f"测试服务器已启动: {http_service.service_url}")
|
||||
print("按 Ctrl+C 停止服务器")
|
||||
print("服务将持续运行,等待接收HTTP请求...")
|
||||
|
||||
|
||||
# 保持服务器运行 - 使用更好的等待机制
|
||||
try:
|
||||
while http_service.is_running:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
print("\n接收到停止信号...")
|
||||
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n正在停止服务器...")
|
||||
http_service.stop()
|
||||
@@ -809,3 +709,4 @@ if __name__ == "__main__":
|
||||
except Exception as e:
|
||||
print(f"服务器运行错误: {e}")
|
||||
http_service.stop()
|
||||
|
||||
|
||||
@@ -1,589 +0,0 @@
|
||||
workstation.bioyond_dispensing_station:
|
||||
category:
|
||||
- workstation
|
||||
- bioyond
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-batch_create_90_10_vial_feeding_tasks:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
delay_time: null
|
||||
hold_m_name: null
|
||||
liquid_material_name: NMP
|
||||
speed: null
|
||||
temperature: null
|
||||
titration: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
delay_time:
|
||||
type: string
|
||||
hold_m_name:
|
||||
type: string
|
||||
liquid_material_name:
|
||||
default: NMP
|
||||
type: string
|
||||
speed:
|
||||
type: string
|
||||
temperature:
|
||||
type: string
|
||||
titration:
|
||||
type: string
|
||||
required:
|
||||
- titration
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: batch_create_90_10_vial_feeding_tasks参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-batch_create_diamine_solution_tasks:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
delay_time: null
|
||||
liquid_material_name: NMP
|
||||
solutions: null
|
||||
speed: null
|
||||
temperature: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
delay_time:
|
||||
type: string
|
||||
liquid_material_name:
|
||||
default: NMP
|
||||
type: string
|
||||
solutions:
|
||||
type: string
|
||||
speed:
|
||||
type: string
|
||||
temperature:
|
||||
type: string
|
||||
required:
|
||||
- solutions
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: batch_create_diamine_solution_tasks参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-brief_step_parameters:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
data: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
required:
|
||||
- data
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: brief_step_parameters参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-compute_experiment_design:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
m_tot: '70'
|
||||
ratio: null
|
||||
titration_percent: '0.03'
|
||||
wt_percent: '0.25'
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
m_tot:
|
||||
default: '70'
|
||||
type: string
|
||||
ratio:
|
||||
type: object
|
||||
titration_percent:
|
||||
default: '0.03'
|
||||
type: string
|
||||
wt_percent:
|
||||
default: '0.25'
|
||||
type: string
|
||||
required:
|
||||
- ratio
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
feeding_order:
|
||||
items: {}
|
||||
title: Feeding Order
|
||||
type: array
|
||||
return_info:
|
||||
title: Return Info
|
||||
type: string
|
||||
solutions:
|
||||
items: {}
|
||||
title: Solutions
|
||||
type: array
|
||||
solvents:
|
||||
additionalProperties: true
|
||||
title: Solvents
|
||||
type: object
|
||||
titration:
|
||||
additionalProperties: true
|
||||
title: Titration
|
||||
type: object
|
||||
required:
|
||||
- solutions
|
||||
- titration
|
||||
- solvents
|
||||
- feeding_order
|
||||
- return_info
|
||||
title: ComputeExperimentDesignReturn
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: compute_experiment_design参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-process_order_finish_report:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
report_request: null
|
||||
used_materials: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
report_request:
|
||||
type: string
|
||||
used_materials:
|
||||
type: string
|
||||
required:
|
||||
- report_request
|
||||
- used_materials
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: process_order_finish_report参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-project_order_report:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
order_id: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
order_id:
|
||||
type: string
|
||||
required:
|
||||
- order_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: project_order_report参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-query_resource_by_name:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
material_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
material_name:
|
||||
type: string
|
||||
required:
|
||||
- material_name
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: query_resource_by_name参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-transfer_materials_to_reaction_station:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
target_device_id: null
|
||||
transfer_groups: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
target_device_id:
|
||||
type: string
|
||||
transfer_groups:
|
||||
type: array
|
||||
required:
|
||||
- target_device_id
|
||||
- transfer_groups
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: transfer_materials_to_reaction_station参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-wait_for_multiple_orders_and_get_reports:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
batch_create_result: null
|
||||
check_interval: 10
|
||||
timeout: 7200
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
batch_create_result:
|
||||
type: string
|
||||
check_interval:
|
||||
default: 10
|
||||
type: integer
|
||||
timeout:
|
||||
default: 7200
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: wait_for_multiple_orders_and_get_reports参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-workflow_sample_locations:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
workflow_id: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
workflow_id:
|
||||
type: string
|
||||
required:
|
||||
- workflow_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: workflow_sample_locations参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
create_90_10_vial_feeding_task:
|
||||
feedback: {}
|
||||
goal:
|
||||
delay_time: delay_time
|
||||
hold_m_name: hold_m_name
|
||||
order_name: order_name
|
||||
percent_10_1_assign_material_name: percent_10_1_assign_material_name
|
||||
percent_10_1_liquid_material_name: percent_10_1_liquid_material_name
|
||||
percent_10_1_target_weigh: percent_10_1_target_weigh
|
||||
percent_10_1_volume: percent_10_1_volume
|
||||
percent_10_2_assign_material_name: percent_10_2_assign_material_name
|
||||
percent_10_2_liquid_material_name: percent_10_2_liquid_material_name
|
||||
percent_10_2_target_weigh: percent_10_2_target_weigh
|
||||
percent_10_2_volume: percent_10_2_volume
|
||||
percent_10_3_assign_material_name: percent_10_3_assign_material_name
|
||||
percent_10_3_liquid_material_name: percent_10_3_liquid_material_name
|
||||
percent_10_3_target_weigh: percent_10_3_target_weigh
|
||||
percent_10_3_volume: percent_10_3_volume
|
||||
percent_90_1_assign_material_name: percent_90_1_assign_material_name
|
||||
percent_90_1_target_weigh: percent_90_1_target_weigh
|
||||
percent_90_2_assign_material_name: percent_90_2_assign_material_name
|
||||
percent_90_2_target_weigh: percent_90_2_target_weigh
|
||||
percent_90_3_assign_material_name: percent_90_3_assign_material_name
|
||||
percent_90_3_target_weigh: percent_90_3_target_weigh
|
||||
speed: speed
|
||||
temperature: temperature
|
||||
goal_default:
|
||||
delay_time: ''
|
||||
hold_m_name: ''
|
||||
order_name: ''
|
||||
percent_10_1_assign_material_name: ''
|
||||
percent_10_1_liquid_material_name: ''
|
||||
percent_10_1_target_weigh: ''
|
||||
percent_10_1_volume: ''
|
||||
percent_10_2_assign_material_name: ''
|
||||
percent_10_2_liquid_material_name: ''
|
||||
percent_10_2_target_weigh: ''
|
||||
percent_10_2_volume: ''
|
||||
percent_10_3_assign_material_name: ''
|
||||
percent_10_3_liquid_material_name: ''
|
||||
percent_10_3_target_weigh: ''
|
||||
percent_10_3_volume: ''
|
||||
percent_90_1_assign_material_name: ''
|
||||
percent_90_1_target_weigh: ''
|
||||
percent_90_2_assign_material_name: ''
|
||||
percent_90_2_target_weigh: ''
|
||||
percent_90_3_assign_material_name: ''
|
||||
percent_90_3_target_weigh: ''
|
||||
speed: ''
|
||||
temperature: ''
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: DispenStationVialFeed_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
delay_time:
|
||||
type: string
|
||||
hold_m_name:
|
||||
type: string
|
||||
order_name:
|
||||
type: string
|
||||
percent_10_1_assign_material_name:
|
||||
type: string
|
||||
percent_10_1_liquid_material_name:
|
||||
type: string
|
||||
percent_10_1_target_weigh:
|
||||
type: string
|
||||
percent_10_1_volume:
|
||||
type: string
|
||||
percent_10_2_assign_material_name:
|
||||
type: string
|
||||
percent_10_2_liquid_material_name:
|
||||
type: string
|
||||
percent_10_2_target_weigh:
|
||||
type: string
|
||||
percent_10_2_volume:
|
||||
type: string
|
||||
percent_10_3_assign_material_name:
|
||||
type: string
|
||||
percent_10_3_liquid_material_name:
|
||||
type: string
|
||||
percent_10_3_target_weigh:
|
||||
type: string
|
||||
percent_10_3_volume:
|
||||
type: string
|
||||
percent_90_1_assign_material_name:
|
||||
type: string
|
||||
percent_90_1_target_weigh:
|
||||
type: string
|
||||
percent_90_2_assign_material_name:
|
||||
type: string
|
||||
percent_90_2_target_weigh:
|
||||
type: string
|
||||
percent_90_3_assign_material_name:
|
||||
type: string
|
||||
percent_90_3_target_weigh:
|
||||
type: string
|
||||
speed:
|
||||
type: string
|
||||
temperature:
|
||||
type: string
|
||||
required:
|
||||
- order_name
|
||||
- percent_90_1_assign_material_name
|
||||
- percent_90_1_target_weigh
|
||||
- percent_90_2_assign_material_name
|
||||
- percent_90_2_target_weigh
|
||||
- percent_90_3_assign_material_name
|
||||
- percent_90_3_target_weigh
|
||||
- percent_10_1_assign_material_name
|
||||
- percent_10_1_target_weigh
|
||||
- percent_10_1_volume
|
||||
- percent_10_1_liquid_material_name
|
||||
- percent_10_2_assign_material_name
|
||||
- percent_10_2_target_weigh
|
||||
- percent_10_2_volume
|
||||
- percent_10_2_liquid_material_name
|
||||
- percent_10_3_assign_material_name
|
||||
- percent_10_3_target_weigh
|
||||
- percent_10_3_volume
|
||||
- percent_10_3_liquid_material_name
|
||||
- speed
|
||||
- temperature
|
||||
- delay_time
|
||||
- hold_m_name
|
||||
title: DispenStationVialFeed_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: DispenStationVialFeed_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: DispenStationVialFeed
|
||||
type: object
|
||||
type: DispenStationVialFeed
|
||||
create_diamine_solution_task:
|
||||
feedback: {}
|
||||
goal:
|
||||
delay_time: delay_time
|
||||
hold_m_name: hold_m_name
|
||||
liquid_material_name: liquid_material_name
|
||||
material_name: material_name
|
||||
order_name: order_name
|
||||
speed: speed
|
||||
target_weigh: target_weigh
|
||||
temperature: temperature
|
||||
volume: volume
|
||||
goal_default:
|
||||
delay_time: ''
|
||||
hold_m_name: ''
|
||||
liquid_material_name: ''
|
||||
material_name: ''
|
||||
order_name: ''
|
||||
speed: ''
|
||||
target_weigh: ''
|
||||
temperature: ''
|
||||
volume: ''
|
||||
handles: {}
|
||||
result:
|
||||
return_info: return_info
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties: {}
|
||||
required: []
|
||||
title: DispenStationSolnPrep_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
delay_time:
|
||||
type: string
|
||||
hold_m_name:
|
||||
type: string
|
||||
liquid_material_name:
|
||||
type: string
|
||||
material_name:
|
||||
type: string
|
||||
order_name:
|
||||
type: string
|
||||
speed:
|
||||
type: string
|
||||
target_weigh:
|
||||
type: string
|
||||
temperature:
|
||||
type: string
|
||||
volume:
|
||||
type: string
|
||||
required:
|
||||
- order_name
|
||||
- material_name
|
||||
- target_weigh
|
||||
- volume
|
||||
- liquid_material_name
|
||||
- speed
|
||||
- temperature
|
||||
- delay_time
|
||||
- hold_m_name
|
||||
title: DispenStationSolnPrep_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
required:
|
||||
- return_info
|
||||
title: DispenStationSolnPrep_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: DispenStationSolnPrep
|
||||
type: object
|
||||
type: DispenStationSolnPrep
|
||||
module: unilabos.devices.workstation.bioyond_studio.dispensing_station:BioyondDispensingStation
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: ''
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
config:
|
||||
type: string
|
||||
deck:
|
||||
type: string
|
||||
required:
|
||||
- config
|
||||
- deck
|
||||
type: object
|
||||
data:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 1.0.0
|
||||
822
unilabos/registry/devices/bioyond_cell.yaml
Normal file
822
unilabos/registry/devices/bioyond_cell.yaml
Normal file
@@ -0,0 +1,822 @@
|
||||
bioyond_cell:
|
||||
category:
|
||||
- bioyond_cell
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-auto_batch_outbound_from_xlsx:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
xlsx_path: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
xlsx_path:
|
||||
type: string
|
||||
required:
|
||||
- xlsx_path
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: auto_batch_outbound_from_xlsx参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-auto_feeding4to3:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
xlsx_path: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
xlsx_path:
|
||||
default: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025122301.xlsx
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: auto_feeding4to3参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-create_and_inbound_materials:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
material_names: null
|
||||
type_id: 3a190ca0-b2f6-9aeb-8067-547e72c11469
|
||||
warehouse_name: 粉末加样头堆栈
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
material_names:
|
||||
type: string
|
||||
type_id:
|
||||
default: 3a190ca0-b2f6-9aeb-8067-547e72c11469
|
||||
type: string
|
||||
warehouse_name:
|
||||
default: 粉末加样头堆栈
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: create_and_inbound_materials参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-create_material:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
location_name_or_id: null
|
||||
material_name: null
|
||||
type_id: null
|
||||
warehouse_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
location_name_or_id:
|
||||
type: string
|
||||
material_name:
|
||||
type: string
|
||||
type_id:
|
||||
type: string
|
||||
warehouse_name:
|
||||
type: string
|
||||
required:
|
||||
- material_name
|
||||
- type_id
|
||||
- warehouse_name
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: create_material参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-create_materials:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
mappings: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
mappings:
|
||||
type: object
|
||||
required:
|
||||
- mappings
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: create_materials参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-create_orders:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
xlsx_path: null
|
||||
handles:
|
||||
output:
|
||||
- data_key: total_orders
|
||||
data_source: executor
|
||||
data_type: integer
|
||||
handler_key: bottle_count
|
||||
io_type: sink
|
||||
label: 配液瓶数
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
xlsx_path:
|
||||
type: string
|
||||
required:
|
||||
- xlsx_path
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: create_orders参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-create_orders_v2:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
xlsx_path: null
|
||||
handles:
|
||||
output:
|
||||
- data_key: total_orders
|
||||
data_source: executor
|
||||
data_type: integer
|
||||
handler_key: bottle_count
|
||||
io_type: sink
|
||||
label: 配液瓶数
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 从Excel解析并创建实验(V2版本)
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
xlsx_path:
|
||||
type: string
|
||||
required:
|
||||
- xlsx_path
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: create_orders_v2参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-create_sample:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
board_type: null
|
||||
bottle_type: null
|
||||
location_code: null
|
||||
name: null
|
||||
warehouse_name: 手动堆栈
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
board_type:
|
||||
type: string
|
||||
bottle_type:
|
||||
type: string
|
||||
location_code:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
warehouse_name:
|
||||
default: 手动堆栈
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- board_type
|
||||
- bottle_type
|
||||
- location_code
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: create_sample参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-order_list_v2:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
beginTime: ''
|
||||
endTime: ''
|
||||
filter: ''
|
||||
pageCount: 1
|
||||
skipCount: 0
|
||||
sorting: ''
|
||||
status: ''
|
||||
timeType: ''
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
beginTime:
|
||||
default: ''
|
||||
type: string
|
||||
endTime:
|
||||
default: ''
|
||||
type: string
|
||||
filter:
|
||||
default: ''
|
||||
type: string
|
||||
pageCount:
|
||||
default: 1
|
||||
type: integer
|
||||
skipCount:
|
||||
default: 0
|
||||
type: integer
|
||||
sorting:
|
||||
default: ''
|
||||
type: string
|
||||
status:
|
||||
default: ''
|
||||
type: string
|
||||
timeType:
|
||||
default: ''
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: order_list_v2参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-process_order_finish_report:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
report_request: null
|
||||
used_materials: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
report_request:
|
||||
type: string
|
||||
used_materials:
|
||||
type: string
|
||||
required:
|
||||
- report_request
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: process_order_finish_report参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-process_sample_finish_report:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
report_request: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
report_request:
|
||||
type: string
|
||||
required:
|
||||
- report_request
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: process_sample_finish_report参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-process_step_finish_report:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
report_request: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
report_request:
|
||||
type: string
|
||||
required:
|
||||
- report_request
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: process_step_finish_report参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-report_material_change:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
material_obj: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
material_obj:
|
||||
type: object
|
||||
required:
|
||||
- material_obj
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: report_material_change参数
|
||||
type: object
|
||||
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-scheduler_continue:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_continue参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-scheduler_reset:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_reset参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-scheduler_start:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_start参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-scheduler_start_and_auto_feeding:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
xlsx_path: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 组合函数:先启动调度,然后执行自动化上料
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
xlsx_path:
|
||||
default: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_start_and_auto_feeding参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-scheduler_stop:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: scheduler_stop参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-storage_batch_inbound:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
items: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- items
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: storage_batch_inbound参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-storage_inbound:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
location_id: null
|
||||
material_id: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
location_id:
|
||||
type: string
|
||||
material_id:
|
||||
type: string
|
||||
required:
|
||||
- material_id
|
||||
- location_id
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: storage_inbound参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-transfer_1_to_2:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: transfer_1_to_2参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-transfer_3_to_2:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
source_wh_id: 3a19debc-84b4-0359-e2d4-b3beea49348b
|
||||
source_x: 1
|
||||
source_y: 1
|
||||
source_z: 1
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 3-2 物料转运,从3号位置转运到2号位置
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
source_wh_id:
|
||||
default: 3a19debc-84b4-0359-e2d4-b3beea49348b
|
||||
description: 来源仓库ID
|
||||
type: string
|
||||
source_x:
|
||||
default: 1
|
||||
description: 来源位置X坐标
|
||||
type: integer
|
||||
source_y:
|
||||
default: 1
|
||||
description: 来源位置Y坐标
|
||||
type: integer
|
||||
source_z:
|
||||
default: 1
|
||||
description: 来源位置Z坐标
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: transfer_3_to_2参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-transfer_3_to_2_to_1:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
source_wh_id: 3a19debc-84b4-0359-e2d4-b3beea49348b
|
||||
source_x: 1
|
||||
source_y: 1
|
||||
source_z: 1
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
source_wh_id:
|
||||
default: 3a19debc-84b4-0359-e2d4-b3beea49348b
|
||||
type: string
|
||||
source_x:
|
||||
default: 1
|
||||
type: integer
|
||||
source_y:
|
||||
default: 1
|
||||
type: integer
|
||||
source_z:
|
||||
default: 1
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: transfer_3_to_2_to_1参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-update_push_ip:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
ip: null
|
||||
port: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
ip:
|
||||
type: string
|
||||
port:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: update_push_ip参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-wait_for_order_finish:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
order_code: null
|
||||
timeout: 36000
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
order_code:
|
||||
type: string
|
||||
timeout:
|
||||
default: 36000
|
||||
type: integer
|
||||
required:
|
||||
- order_code
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: wait_for_order_finish参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-wait_for_transfer_task:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
filter_text: null
|
||||
interval: 5
|
||||
timeout: 3000
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
filter_text:
|
||||
type: string
|
||||
interval:
|
||||
default: 5
|
||||
type: integer
|
||||
timeout:
|
||||
default: 3000
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: wait_for_transfer_task参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.workstation.bioyond_studio.bioyond_cell.bioyond_cell_workstation:BioyondCellWorkstation
|
||||
status_types:
|
||||
device_id: String
|
||||
type: python
|
||||
config_info: []
|
||||
description: ''
|
||||
handles: []
|
||||
icon: benyao2.webp
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
config:
|
||||
type: object
|
||||
deck:
|
||||
type: string
|
||||
protocol_type:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
properties:
|
||||
device_id:
|
||||
type: string
|
||||
required:
|
||||
- device_id
|
||||
type: object
|
||||
registry_type: device
|
||||
version: 1.0.0
|
||||
@@ -1,231 +1,3 @@
|
||||
hplc.agilent:
|
||||
category:
|
||||
- characterization_chromatic
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-check_status:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 检查安捷伦HPLC设备状态的函数。用于监控设备的运行状态、连接状态、错误信息等关键指标。该函数定期查询设备状态,确保系统稳定运行,及时发现和报告设备异常。适用于自动化流程中的设备监控、故障诊断、系统维护等场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: check_status参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-extract_data_from_txt:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
file_path: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 从文本文件中提取分析数据的函数。用于解析安捷伦HPLC生成的结果文件,提取峰面积、保留时间、浓度等关键分析数据。支持多种文件格式的自动识别和数据结构化处理,为后续数据分析和报告生成提供标准化的数据格式。适用于批量数据处理、结果验证、质量控制等分析工作流程。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
file_path:
|
||||
type: string
|
||||
required:
|
||||
- file_path
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: extract_data_from_txt参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-start_sequence:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
params: null
|
||||
resource: null
|
||||
wf_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 启动安捷伦HPLC分析序列的函数。用于执行预定义的分析方法序列,包括样品进样、色谱分离、检测等完整的分析流程。支持参数配置、资源分配、工作流程管理等功能,实现全自动的样品分析。适用于批量样品处理、标准化分析、质量检测等需要连续自动分析的应用场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
params:
|
||||
type: string
|
||||
resource:
|
||||
type: object
|
||||
wf_name:
|
||||
type: string
|
||||
required:
|
||||
- wf_name
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: start_sequence参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-try_close_sub_device:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
device_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 尝试关闭HPLC子设备的函数。用于安全地关闭泵、检测器、进样器等各个子模块,确保设备正常断开连接并保护硬件安全。该函数提供错误处理和状态确认机制,避免强制关闭可能造成的设备损坏。适用于设备维护、系统重启、紧急停机等需要安全关闭设备的场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
device_name:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: try_close_sub_device参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-try_open_sub_device:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
device_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 尝试打开HPLC子设备的函数。用于初始化和连接泵、检测器、进样器等各个子模块,建立设备通信并进行自检。该函数提供连接验证和错误恢复机制,确保子设备正常启动并准备就绪。适用于设备初始化、系统启动、设备重连等需要建立设备连接的场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
device_name:
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: try_open_sub_device参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
execute_command_from_outer:
|
||||
feedback: {}
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: SendCmd
|
||||
type: object
|
||||
type: SendCmd
|
||||
module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver
|
||||
status_types:
|
||||
could_run: bool
|
||||
data_file: String
|
||||
device_status: str
|
||||
driver_init_ok: bool
|
||||
finish_status: str
|
||||
is_running: bool
|
||||
status_text: str
|
||||
success: bool
|
||||
type: python
|
||||
config_info: []
|
||||
description: 安捷伦高效液相色谱(HPLC)分析设备,用于复杂化合物的分离、检测和定量分析。该设备通过UI自动化技术控制安捷伦ChemStation软件,实现全自动的样品分析流程。具备序列启动、设备状态监控、数据文件提取、结果处理等功能。支持多样品批量处理和实时状态反馈,适用于药物分析、环境检测、食品安全、化学研究等需要高精度色谱分析的实验室应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
driver_debug:
|
||||
default: false
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
properties:
|
||||
could_run:
|
||||
type: boolean
|
||||
data_file:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
device_status:
|
||||
type: string
|
||||
driver_init_ok:
|
||||
type: boolean
|
||||
finish_status:
|
||||
type: string
|
||||
is_running:
|
||||
type: boolean
|
||||
status_text:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- status_text
|
||||
- device_status
|
||||
- could_run
|
||||
- driver_init_ok
|
||||
- is_running
|
||||
- success
|
||||
- finish_status
|
||||
- data_file
|
||||
type: object
|
||||
version: 1.0.0
|
||||
hplc.agilent-zhida:
|
||||
category:
|
||||
- characterization_chromatic
|
||||
|
||||
@@ -1,194 +1 @@
|
||||
raman.home_made:
|
||||
category:
|
||||
- characterization_optic
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-ccd_time:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
int_time: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 设置CCD检测器积分时间的函数。用于配置拉曼光谱仪的信号采集时间,控制光谱数据的质量和信噪比。较长的积分时间可获得更高的信号强度和更好的光谱质量,但会增加测量时间。该函数允许根据样品特性和测量要求动态调整检测参数,优化测量效果。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
int_time:
|
||||
type: string
|
||||
required:
|
||||
- int_time
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: ccd_time参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-laser_on_power:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
output_voltage_laser: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 设置激光器输出功率的函数。用于控制拉曼光谱仪激光器的功率输出,调节激光强度以适应不同样品的测量需求。适当的激光功率能够获得良好的拉曼信号同时避免样品损伤。该函数支持精确的功率控制,确保测量结果的稳定性和重现性。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
output_voltage_laser:
|
||||
type: string
|
||||
required:
|
||||
- output_voltage_laser
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: laser_on_power参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-raman_without_background:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
int_time: null
|
||||
laser_power: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 执行无背景扣除的拉曼光谱测量函数。用于直接采集样品的拉曼光谱信号,不进行背景校正处理。该函数配置积分时间和激光功率参数,获取原始光谱数据用于后续的数据处理分析。适用于对光谱数据质量要求较高或需要自定义背景处理流程的测量场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
int_time:
|
||||
type: string
|
||||
laser_power:
|
||||
type: string
|
||||
required:
|
||||
- int_time
|
||||
- laser_power
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: raman_without_background参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-raman_without_background_average:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
average: null
|
||||
int_time: null
|
||||
laser_power: null
|
||||
sample_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 执行多次平均的无背景拉曼光谱测量函数。通过多次测量取平均值来提高光谱数据的信噪比和测量精度,减少随机噪声影响。该函数支持自定义平均次数、积分时间、激光功率等参数,并可为样品指定名称便于数据管理。适用于对测量精度要求较高的定量分析和研究应用。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
average:
|
||||
type: string
|
||||
int_time:
|
||||
type: string
|
||||
laser_power:
|
||||
type: string
|
||||
sample_name:
|
||||
type: string
|
||||
required:
|
||||
- sample_name
|
||||
- int_time
|
||||
- laser_power
|
||||
- average
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: raman_without_background_average参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
raman_cmd:
|
||||
feedback: {}
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: SendCmd
|
||||
type: object
|
||||
type: SendCmd
|
||||
module: unilabos.devices.raman_uv.home_made_raman:RamanObj
|
||||
status_types: {}
|
||||
type: python
|
||||
config_info: []
|
||||
description: 拉曼光谱分析设备,用于物质的分子结构和化学成分表征。该设备集成激光器和CCD检测器,通过串口通信控制激光功率和光谱采集。具备背景扣除、多次平均、自动数据处理等功能,支持高精度的拉曼光谱测量。适用于材料表征、化学分析、质量控制、研究开发等需要分子指纹识别和结构分析的实验应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
baudrate_ccd:
|
||||
default: 921600
|
||||
type: string
|
||||
baudrate_laser:
|
||||
default: 9600
|
||||
type: string
|
||||
port_ccd:
|
||||
type: string
|
||||
port_laser:
|
||||
type: string
|
||||
required:
|
||||
- port_laser
|
||||
- port_ccd
|
||||
type: object
|
||||
data:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 1.0.0
|
||||
{}
|
||||
|
||||
845
unilabos/registry/devices/coin_cell_workstation.yaml
Normal file
845
unilabos/registry/devices/coin_cell_workstation.yaml
Normal file
@@ -0,0 +1,845 @@
|
||||
coincellassemblyworkstation_device:
|
||||
category:
|
||||
- coin_cell_workstation
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-change_hole_sheet_to_2:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
hole: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
hole:
|
||||
type: object
|
||||
required:
|
||||
- hole
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: change_hole_sheet_to_2参数
|
||||
type: object
|
||||
type: UniLabJsonCommandAsync
|
||||
auto-fill_plate:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: fill_plate参数
|
||||
type: object
|
||||
type: UniLabJsonCommandAsync
|
||||
auto-fun_wuliao_test:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: fun_wuliao_test参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_allpack_cmd:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
assembly_pressure: 4200
|
||||
assembly_type: 7
|
||||
elec_num: null
|
||||
elec_use_num: null
|
||||
elec_vol: 50
|
||||
file_path: /Users/sml/work
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
assembly_pressure:
|
||||
default: 4200
|
||||
type: integer
|
||||
assembly_type:
|
||||
default: 7
|
||||
type: integer
|
||||
elec_num:
|
||||
type: string
|
||||
elec_use_num:
|
||||
type: string
|
||||
elec_vol:
|
||||
default: 50
|
||||
type: integer
|
||||
file_path:
|
||||
default: /Users/sml/work
|
||||
type: string
|
||||
required:
|
||||
- elec_num
|
||||
- elec_use_num
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_allpack_cmd参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_allpack_cmd_simp:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
assembly_pressure: 4200
|
||||
assembly_type: 7
|
||||
battery_clean_ignore: false
|
||||
battery_pressure_mode: true
|
||||
dual_drop_first_volume: 25
|
||||
dual_drop_mode: false
|
||||
dual_drop_start_timing: false
|
||||
dual_drop_suction_timing: false
|
||||
elec_num: null
|
||||
elec_use_num: null
|
||||
elec_vol: 50
|
||||
file_path: /Users/sml/work
|
||||
fujipian_juzhendianwei: 0
|
||||
fujipian_panshu: 0
|
||||
gemo_juzhendianwei: 0
|
||||
gemopanshu: 0
|
||||
lvbodian: true
|
||||
qiangtou_juzhendianwei: 0
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 简化版电池组装函数,整合了参数设置和双滴模式
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
assembly_pressure:
|
||||
default: 4200
|
||||
description: 电池压制力(N)
|
||||
type: integer
|
||||
assembly_type:
|
||||
default: 7
|
||||
description: 组装类型(7=不用铝箔垫, 8=使用铝箔垫)
|
||||
type: integer
|
||||
battery_clean_ignore:
|
||||
default: false
|
||||
description: 是否忽略电池清洁步骤
|
||||
type: boolean
|
||||
battery_pressure_mode:
|
||||
default: true
|
||||
description: 是否启用压力模式
|
||||
type: boolean
|
||||
dual_drop_first_volume:
|
||||
default: 25
|
||||
description: 二次滴液第一次排液体积(μL)
|
||||
type: integer
|
||||
dual_drop_mode:
|
||||
default: false
|
||||
description: 电解液添加模式(false=单次滴液, true=二次滴液)
|
||||
type: boolean
|
||||
dual_drop_start_timing:
|
||||
default: false
|
||||
description: 二次滴液开始滴液时机(false=正极片前, true=正极片后)
|
||||
type: boolean
|
||||
dual_drop_suction_timing:
|
||||
default: false
|
||||
description: 二次滴液吸液时机(false=正常吸液, true=先吸液)
|
||||
type: boolean
|
||||
elec_num:
|
||||
description: 电解液瓶数
|
||||
type: string
|
||||
elec_use_num:
|
||||
description: 每瓶电解液组装电池数
|
||||
type: string
|
||||
elec_vol:
|
||||
default: 50
|
||||
description: 电解液吸液量(μL)
|
||||
type: integer
|
||||
file_path:
|
||||
default: /Users/sml/work
|
||||
description: 实验记录保存路径
|
||||
type: string
|
||||
fujipian_juzhendianwei:
|
||||
default: 0
|
||||
description: 负极片矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
fujipian_panshu:
|
||||
default: 0
|
||||
description: 负极片盘数
|
||||
type: integer
|
||||
gemo_juzhendianwei:
|
||||
default: 0
|
||||
description: 隔膜矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
gemopanshu:
|
||||
default: 0
|
||||
description: 隔膜盘数
|
||||
type: integer
|
||||
lvbodian:
|
||||
default: true
|
||||
description: 是否使用铝箔垫片
|
||||
type: boolean
|
||||
qiangtou_juzhendianwei:
|
||||
default: 0
|
||||
description: 枪头盒矩阵点位。盘位置从1开始计数,有效范围:1-32, 64-96 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
required:
|
||||
- elec_num
|
||||
- elec_use_num
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_allpack_cmd_simp参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_get_csv_export_status:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_get_csv_export_status参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_pack_device_auto:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_device_auto参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_pack_device_init:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_device_init参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_pack_device_init_auto_start_combined:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 组合函数:设备初始化 + 切换自动模式 + 启动
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_device_init_auto_start_combined参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_pack_device_start:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_device_start参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_pack_device_stop:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_device_stop参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_pack_get_msg_cmd:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
file_path: D:\coin_cell_data
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
file_path:
|
||||
default: D:\coin_cell_data
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_get_msg_cmd参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_pack_send_bottle_num:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
bottle_num: null
|
||||
handles:
|
||||
input:
|
||||
- data_key: bottle_num
|
||||
data_source: workflow
|
||||
data_type: integer
|
||||
handler_key: bottle_count
|
||||
io_type: source
|
||||
label: 配液瓶数
|
||||
required: true
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
bottle_num:
|
||||
type: integer
|
||||
required:
|
||||
- bottle_num
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_send_bottle_num参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_pack_send_finished_cmd:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_send_finished_cmd参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_pack_send_msg_cmd:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
assembly_pressure: null
|
||||
assembly_type: null
|
||||
elec_use_num: null
|
||||
elec_vol: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
assembly_pressure:
|
||||
type: string
|
||||
assembly_type:
|
||||
type: string
|
||||
elec_use_num:
|
||||
type: string
|
||||
elec_vol:
|
||||
type: string
|
||||
required:
|
||||
- elec_use_num
|
||||
- elec_vol
|
||||
- assembly_type
|
||||
- assembly_pressure
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_pack_send_msg_cmd参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_read_data_and_output:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
file_path: /Users/sml/work
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
file_path:
|
||||
default: /Users/sml/work
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_read_data_and_output参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_sendbottle_allpack_multi:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
assembly_pressure: 4200
|
||||
assembly_type: 7
|
||||
battery_clean_ignore: false
|
||||
battery_pressure_mode: true
|
||||
dual_drop_first_volume: 25
|
||||
dual_drop_mode: false
|
||||
dual_drop_start_timing: false
|
||||
dual_drop_suction_timing: false
|
||||
elec_num: null
|
||||
elec_use_num: null
|
||||
elec_vol: 50
|
||||
file_path: /Users/sml/work
|
||||
fujipian_juzhendianwei: 0
|
||||
fujipian_panshu: 0
|
||||
gemo_juzhendianwei: 0
|
||||
gemopanshu: 0
|
||||
lvbodian: true
|
||||
qiangtou_juzhendianwei: 0
|
||||
handles:
|
||||
input:
|
||||
- data_key: elec_num
|
||||
data_source: workflow
|
||||
data_type: integer
|
||||
handler_key: bottle_count
|
||||
io_type: source
|
||||
label: 配液瓶数
|
||||
required: true
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 发送瓶数+简化组装函数(适用于第二批次及后续批次),合并了发送瓶数和简化组装流程
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
assembly_pressure:
|
||||
default: 4200
|
||||
description: 电池压制力(N)
|
||||
type: integer
|
||||
assembly_type:
|
||||
default: 7
|
||||
description: 组装类型(7=不用铝箔垫, 8=使用铝箔垫)
|
||||
type: integer
|
||||
battery_clean_ignore:
|
||||
default: false
|
||||
description: 是否忽略电池清洁步骤
|
||||
type: boolean
|
||||
battery_pressure_mode:
|
||||
default: true
|
||||
description: 是否启用压力模式
|
||||
type: boolean
|
||||
dual_drop_first_volume:
|
||||
default: 25
|
||||
description: 二次滴液第一次排液体积(μL)
|
||||
type: integer
|
||||
dual_drop_mode:
|
||||
default: false
|
||||
description: 电解液添加模式(false=单次滴液, true=二次滴液)
|
||||
type: boolean
|
||||
dual_drop_start_timing:
|
||||
default: false
|
||||
description: 二次滴液开始滴液时机(false=正极片前, true=正极片后)
|
||||
type: boolean
|
||||
dual_drop_suction_timing:
|
||||
default: false
|
||||
description: 二次滴液吸液时机(false=正常吸液, true=先吸液)
|
||||
type: boolean
|
||||
elec_num:
|
||||
description: 电解液瓶数,如果在workflow中已通过handles连接上游(create_orders的bottle_count输出),则此参数会自动从上游获取,无需手动填写;如果单独使用此函数(没有上游连接),则必须手动填写电解液瓶数
|
||||
type: string
|
||||
elec_use_num:
|
||||
description: 每瓶电解液组装电池数
|
||||
type: string
|
||||
elec_vol:
|
||||
default: 50
|
||||
description: 电解液吸液量(μL)
|
||||
type: integer
|
||||
file_path:
|
||||
default: /Users/sml/work
|
||||
description: 实验记录保存路径
|
||||
type: string
|
||||
fujipian_juzhendianwei:
|
||||
default: 0
|
||||
description: 负极片矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
fujipian_panshu:
|
||||
default: 0
|
||||
description: 负极片盘数
|
||||
type: integer
|
||||
gemo_juzhendianwei:
|
||||
default: 0
|
||||
description: 隔膜矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
gemopanshu:
|
||||
default: 0
|
||||
description: 隔膜盘数
|
||||
type: integer
|
||||
lvbodian:
|
||||
default: true
|
||||
description: 是否使用铝箔垫片
|
||||
type: boolean
|
||||
qiangtou_juzhendianwei:
|
||||
default: 0
|
||||
description: 枪头盒矩阵点位。盘位置从1开始计数,有效范围:1-32, 64-96 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
type: integer
|
||||
required:
|
||||
- elec_num
|
||||
- elec_use_num
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_sendbottle_allpack_multi参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-func_stop_read_data:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: func_stop_read_data参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-modify_deck_name:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
resource_name: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
resource_name:
|
||||
type: string
|
||||
required:
|
||||
- resource_name
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: modify_deck_name参数
|
||||
type: object
|
||||
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-qiming_coin_cell_code:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
battery_clean_ignore: false
|
||||
battery_pressure: 4000
|
||||
battery_pressure_mode: true
|
||||
fujipian_juzhendianwei: 0
|
||||
fujipian_panshu: null
|
||||
gemo_juzhendianwei: 0
|
||||
gemopanshu: 0
|
||||
lvbodian: true
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
battery_clean_ignore:
|
||||
default: false
|
||||
type: boolean
|
||||
battery_pressure:
|
||||
default: 4000
|
||||
type: integer
|
||||
battery_pressure_mode:
|
||||
default: true
|
||||
type: boolean
|
||||
fujipian_juzhendianwei:
|
||||
default: 0
|
||||
type: integer
|
||||
fujipian_panshu:
|
||||
type: integer
|
||||
gemo_juzhendianwei:
|
||||
default: 0
|
||||
type: integer
|
||||
gemopanshu:
|
||||
default: 0
|
||||
type: integer
|
||||
lvbodian:
|
||||
default: true
|
||||
type: boolean
|
||||
required:
|
||||
- fujipian_panshu
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: qiming_coin_cell_code参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation
|
||||
status_types:
|
||||
data_assembly_coin_cell_num: int
|
||||
data_assembly_pressure: int
|
||||
data_assembly_time: float
|
||||
data_axis_x_pos: float
|
||||
data_axis_y_pos: float
|
||||
data_axis_z_pos: float
|
||||
data_coin_cell_code: str
|
||||
data_coin_num: int
|
||||
data_electrolyte_code: str
|
||||
data_electrolyte_volume: int
|
||||
data_glove_box_o2_content: float
|
||||
data_glove_box_pressure: float
|
||||
data_glove_box_water_content: float
|
||||
data_open_circuit_voltage: float
|
||||
data_pole_weight: float
|
||||
request_rec_msg_status: bool
|
||||
request_send_msg_status: bool
|
||||
sys_mode: str
|
||||
sys_status: str
|
||||
type: python
|
||||
config_info: []
|
||||
description: ''
|
||||
handles: []
|
||||
icon: koudian.webp
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
address:
|
||||
default: 172.16.28.102
|
||||
type: string
|
||||
config:
|
||||
type: object
|
||||
debug_mode:
|
||||
default: false
|
||||
type: boolean
|
||||
deck:
|
||||
type: string
|
||||
port:
|
||||
default: '502'
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
properties:
|
||||
data_assembly_coin_cell_num:
|
||||
type: integer
|
||||
data_assembly_pressure:
|
||||
type: integer
|
||||
data_assembly_time:
|
||||
type: number
|
||||
data_axis_x_pos:
|
||||
type: number
|
||||
data_axis_y_pos:
|
||||
type: number
|
||||
data_axis_z_pos:
|
||||
type: number
|
||||
data_coin_cell_code:
|
||||
type: string
|
||||
data_coin_num:
|
||||
type: integer
|
||||
data_electrolyte_code:
|
||||
type: string
|
||||
data_electrolyte_volume:
|
||||
type: integer
|
||||
data_glove_box_o2_content:
|
||||
type: number
|
||||
data_glove_box_pressure:
|
||||
type: number
|
||||
data_glove_box_water_content:
|
||||
type: number
|
||||
data_open_circuit_voltage:
|
||||
type: number
|
||||
data_pole_weight:
|
||||
type: number
|
||||
request_rec_msg_status:
|
||||
type: boolean
|
||||
request_send_msg_status:
|
||||
type: boolean
|
||||
sys_mode:
|
||||
type: string
|
||||
sys_status:
|
||||
type: string
|
||||
required:
|
||||
- sys_status
|
||||
- sys_mode
|
||||
- request_rec_msg_status
|
||||
- request_send_msg_status
|
||||
- data_assembly_coin_cell_num
|
||||
- data_assembly_time
|
||||
- data_open_circuit_voltage
|
||||
- data_axis_x_pos
|
||||
- data_axis_y_pos
|
||||
- data_axis_z_pos
|
||||
- data_pole_weight
|
||||
- data_assembly_pressure
|
||||
- data_electrolyte_volume
|
||||
- data_coin_num
|
||||
- data_coin_cell_code
|
||||
- data_electrolyte_code
|
||||
- data_glove_box_pressure
|
||||
- data_glove_box_o2_content
|
||||
- data_glove_box_water_content
|
||||
type: object
|
||||
registry_type: device
|
||||
version: 1.0.0
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -834,174 +834,3 @@ linear_motion.toyo_xyz.sim:
|
||||
mesh: toyo_xyz
|
||||
type: device
|
||||
version: 1.0.0
|
||||
motor.iCL42:
|
||||
category:
|
||||
- robot_linear_motion
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-execute_run_motor:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
mode: null
|
||||
position: null
|
||||
velocity: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 步进电机执行运动函数。直接执行电机运动命令,包括位置设定、速度控制和路径规划。该函数处理底层的电机控制协议,消除警告信息,设置运动参数并启动电机运行。适用于需要直接控制电机运动的应用场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
mode:
|
||||
type: string
|
||||
position:
|
||||
type: number
|
||||
velocity:
|
||||
type: integer
|
||||
required:
|
||||
- mode
|
||||
- position
|
||||
- velocity
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: execute_run_motor参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-init_device:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: iCL42电机设备初始化函数。建立与iCL42步进电机驱动器的串口通信连接,配置通信参数包括波特率、数据位、校验位等。该函数是电机使用前的必要步骤,确保驱动器处于可控状态并准备接收运动指令。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: init_device参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-run_motor:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
mode: null
|
||||
position: null
|
||||
velocity: null
|
||||
handles: {}
|
||||
placeholder_keys: {}
|
||||
result: {}
|
||||
schema:
|
||||
description: 步进电机运动控制函数。根据指定的运动模式、目标位置和速度参数控制电机运动。支持多种运动模式和精确的位置控制,自动处理运动轨迹规划和执行。该函数提供异步执行和状态反馈,确保运动的准确性和可靠性。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
mode:
|
||||
type: string
|
||||
position:
|
||||
type: number
|
||||
velocity:
|
||||
type: integer
|
||||
required:
|
||||
- mode
|
||||
- position
|
||||
- velocity
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: run_motor参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
execute_command_from_outer:
|
||||
feedback: {}
|
||||
goal:
|
||||
command: command
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: {}
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- return_info
|
||||
- success
|
||||
title: SendCmd_Result
|
||||
type: object
|
||||
required:
|
||||
- goal
|
||||
title: SendCmd
|
||||
type: object
|
||||
type: SendCmd
|
||||
module: unilabos.devices.motor.iCL42:iCL42Driver
|
||||
status_types:
|
||||
is_executing_run: bool
|
||||
motor_position: int
|
||||
success: bool
|
||||
type: python
|
||||
config_info: []
|
||||
description: iCL42步进电机驱动器,用于实验室设备的精密线性运动控制。该设备通过串口通信控制iCL42型步进电机驱动器,支持多种运动模式和精确的位置、速度控制。具备位置反馈、运行状态监控和故障检测功能。适用于自动进样器、样品传送、精密定位平台等需要准确线性运动控制的实验室自动化设备。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
config:
|
||||
properties:
|
||||
device_address:
|
||||
default: 1
|
||||
type: integer
|
||||
device_com:
|
||||
default: COM9
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
data:
|
||||
properties:
|
||||
is_executing_run:
|
||||
type: boolean
|
||||
motor_position:
|
||||
type: integer
|
||||
success:
|
||||
type: boolean
|
||||
required:
|
||||
- motor_position
|
||||
- is_executing_run
|
||||
- success
|
||||
type: object
|
||||
version: 1.0.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
92
unilabos/registry/resources/bioyond/YB_bottle.yaml
Normal file
92
unilabos/registry/resources/bioyond/YB_bottle.yaml
Normal file
@@ -0,0 +1,92 @@
|
||||
YB_20ml_fenyeping:
|
||||
category:
|
||||
- yb3
|
||||
- YB_bottle
|
||||
class:
|
||||
module: unilabos.resources.bioyond.YB_bottles:YB_20ml_fenyeping
|
||||
type: pylabrobot
|
||||
description: YB_20ml_fenyeping
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_5ml_fenyeping:
|
||||
category:
|
||||
- yb3
|
||||
- YB_bottle
|
||||
class:
|
||||
module: unilabos.resources.bioyond.YB_bottles:YB_5ml_fenyeping
|
||||
type: pylabrobot
|
||||
description: YB_5ml_fenyeping
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
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:
|
||||
category:
|
||||
- yb3
|
||||
- YB_bottle
|
||||
class:
|
||||
module: unilabos.resources.bioyond.YB_bottles:YB_pei_ye_da_Bottle
|
||||
type: pylabrobot
|
||||
description: YB_pei_ye_da_Bottle
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_pei_ye_xiao_Bottle:
|
||||
category:
|
||||
- yb3
|
||||
- YB_bottle
|
||||
class:
|
||||
module: unilabos.resources.bioyond.YB_bottles:YB_pei_ye_xiao_Bottle
|
||||
type: pylabrobot
|
||||
description: YB_pei_ye_xiao_Bottle
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
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
|
||||
182
unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml
Normal file
182
unilabos/registry/resources/bioyond/YB_bottle_carriers.yaml
Normal file
@@ -0,0 +1,182 @@
|
||||
YB_100ml_yeti:
|
||||
category:
|
||||
- yb3
|
||||
- YB_bottle_carriers
|
||||
class:
|
||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_100ml_yeti
|
||||
type: pylabrobot
|
||||
description: YB_100ml_yeti
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_20ml_fenyepingban:
|
||||
category:
|
||||
- yb3
|
||||
- YB_bottle_carriers
|
||||
class:
|
||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_20ml_fenyepingban
|
||||
type: pylabrobot
|
||||
description: YB_20ml_fenyepingban
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_5ml_fenyepingban:
|
||||
category:
|
||||
- yb3
|
||||
- YB_bottle_carriers
|
||||
class:
|
||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_5ml_fenyepingban
|
||||
type: pylabrobot
|
||||
description: YB_5ml_fenyepingban
|
||||
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_gao_nian_ye_Bottle:
|
||||
category:
|
||||
- yb3
|
||||
- YB_bottle_carriers
|
||||
class:
|
||||
module: unilabos.resources.bioyond.YB_bottles:YB_gao_nian_ye_Bottle
|
||||
type: pylabrobot
|
||||
description: YB_gao_nian_ye_Bottle
|
||||
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_jia_yang_tou_da_Carrier:
|
||||
category:
|
||||
- yb3
|
||||
- YB_bottle_carriers
|
||||
class:
|
||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_jia_yang_tou_da_Carrier
|
||||
type: pylabrobot
|
||||
description: YB_jia_yang_tou_da_Carrier
|
||||
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_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:
|
||||
category:
|
||||
- yb3
|
||||
- YB_bottle_carriers
|
||||
class:
|
||||
module: unilabos.resources.bioyond.YB_bottles:YB_ye_100ml_Bottle
|
||||
type: pylabrobot
|
||||
description: YB_ye_100ml_Bottle
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
@@ -22,7 +22,7 @@ BIOYOND_PolymerReactionStation_Deck:
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
YB_Deck11:
|
||||
BIOYOND_YB_Deck:
|
||||
category:
|
||||
- deck
|
||||
class:
|
||||
@@ -34,3 +34,15 @@ YB_Deck11:
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
CoincellDeck:
|
||||
category:
|
||||
- deck
|
||||
class:
|
||||
module: unilabos.devices.workstation.coin_cell_assembly.YB_YH_materials:YH_Deck
|
||||
type: pylabrobot
|
||||
description: BIOYOND PolymerReactionStation Deck
|
||||
handles: []
|
||||
icon: koudian.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
|
||||
195
unilabos/resources/battery/electrode_sheet.py
Normal file
195
unilabos/resources/battery/electrode_sheet.py
Normal file
@@ -0,0 +1,195 @@
|
||||
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):
|
||||
diameter: float # 直径 (mm)
|
||||
thickness: float # 厚度 (mm)
|
||||
mass: float # 质量 (g)
|
||||
material_type: str # 材料类型(铜、铝、不锈钢、弹簧钢等)
|
||||
color: str # 材料类型对应的颜色
|
||||
info: Optional[str] # 附加信息
|
||||
|
||||
|
||||
class ElectrodeSheet(ResourcePLR):
|
||||
"""极片类 - 包含正负极片、隔膜、弹片、垫片、铝箔等所有片状材料"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "极片",
|
||||
size_x: float = 10,
|
||||
size_y: float = 10,
|
||||
size_z: float = 10,
|
||||
category: str = "electrode_sheet",
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""初始化极片
|
||||
|
||||
Args:
|
||||
name: 极片名称
|
||||
size_x: 长度 (mm)
|
||||
size_y: 宽度 (mm)
|
||||
size_z: 高度 (mm)
|
||||
category: 类别
|
||||
model: 型号
|
||||
**kwargs: 其他参数传递给父类
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
category=category,
|
||||
model=model,
|
||||
**kwargs
|
||||
)
|
||||
self._unilabos_state: ElectrodeSheetState = ElectrodeSheetState(
|
||||
diameter=14,
|
||||
thickness=0.1,
|
||||
mass=0.5,
|
||||
material_type="copper",
|
||||
color="#8b4513",
|
||||
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({"diameter": 20.0, "thickness": 0.5, "mass": 0.5, "material_type": "aluminum", "color": electrode_colors["PositiveCan"], "info": None})
|
||||
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: float = 12,
|
||||
size_y: float = 12,
|
||||
size_z: float = 6,
|
||||
category: str = "battery",
|
||||
model: Optional[str] = None,
|
||||
**kwargs
|
||||
):
|
||||
"""初始化电池
|
||||
|
||||
Args:
|
||||
name: 电池名称
|
||||
size_x: 长度 (mm)
|
||||
size_y: 宽度 (mm)
|
||||
size_z: 高度 (mm)
|
||||
category: 类别
|
||||
model: 型号
|
||||
**kwargs: 其他参数传递给父类
|
||||
"""
|
||||
super().__init__(
|
||||
name=name,
|
||||
size_x=size_x,
|
||||
size_y=size_y,
|
||||
size_z=size_z,
|
||||
category=category,
|
||||
model=model,
|
||||
**kwargs
|
||||
)
|
||||
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",
|
||||
)
|
||||
548
unilabos/resources/bioyond/README_WAREHOUSE.md
Normal file
548
unilabos/resources/bioyond/README_WAREHOUSE.md
Normal file
@@ -0,0 +1,548 @@
|
||||
# Bioyond 仓库系统开发指南
|
||||
|
||||
本文档详细说明 Bioyond 仓库(Warehouse)系统的架构、配置和使用方法,帮助开发者快速理解和维护仓库相关代码。
|
||||
|
||||
## 📚 目录
|
||||
|
||||
- [系统架构](#系统架构)
|
||||
- [核心概念](#核心概念)
|
||||
- [三层映射关系](#三层映射关系)
|
||||
- [warehouse_factory 详解](#warehouse_factory-详解)
|
||||
- [创建新仓库](#创建新仓库)
|
||||
- [常见问题](#常见问题)
|
||||
- [调试技巧](#调试技巧)
|
||||
|
||||
---
|
||||
|
||||
## 系统架构
|
||||
|
||||
Bioyond 仓库系统采用**三层架构**,实现从前端显示到后端 API 的完整映射:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 前端显示层 (YB_warehouses.py) │
|
||||
│ - warehouse_factory 自动生成库位网格 │
|
||||
│ - 生成库位名称:A01, B02, C03... │
|
||||
│ - 存储在 WareHouse.sites 字典中 │
|
||||
└────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Deck 布局层 (decks.py) │
|
||||
│ - 定义仓库在 Deck 上的物理位置 │
|
||||
│ - 组织多个仓库形成完整布局 │
|
||||
└────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ UUID 映射层 (config.py) │
|
||||
│ - 将库位名称映射到 Bioyond 系统 UUID │
|
||||
│ - 用于 API 调用时的物料入库操作 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心概念
|
||||
|
||||
### 仓库(Warehouse)
|
||||
|
||||
仓库是一个**三维网格**,用于存放物料。由以下参数定义:
|
||||
|
||||
- **num_items_x**: 列数(X 轴)
|
||||
- **num_items_y**: 行数(Y 轴)
|
||||
- **num_items_z**: 层数(Z 轴)
|
||||
|
||||
例如:`5行×3列×1层` = 5×3×1 = 15个库位
|
||||
|
||||
### 库位(Site)
|
||||
|
||||
库位是仓库中的单个存储位置,由**字母行+数字列**命名:
|
||||
|
||||
- **字母行**:A, B, C, D, E, F...(对应 Y 轴)
|
||||
- **数字列**:01, 02, 03, 04...(对应 X 轴或 Z 轴)
|
||||
|
||||
示例:`A01`, `B02`, `C03`
|
||||
|
||||
### 布局模式(Layout)
|
||||
|
||||
控制库位的排序和 Y 坐标计算:
|
||||
|
||||
| 模式 | 说明 | 生成顺序 | Y 坐标计算 | 显示效果 |
|
||||
|------|------|----------|-----------|---------|
|
||||
| `col-major` | 列优先(默认) | A01, B01, C01, A02... | `dy + (num_y - row - 1) * item_dy` | A 可能在下 |
|
||||
| `row-major` | 行优先 | A01, A02, A03, B01... | `dy + row * item_dy` | **A 在上** ✓ |
|
||||
|
||||
**重要:** 使用 `row-major` 可以避免上下颠倒问题!
|
||||
|
||||
---
|
||||
|
||||
## 三层映射关系
|
||||
|
||||
### 示例:手动传递窗右(A01-E03)
|
||||
|
||||
#### 1️⃣ 前端显示层 - [`YB_warehouses.py`](YB_warehouses.py)
|
||||
|
||||
```python
|
||||
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
|
||||
"""创建 5行×3列×1层 仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列
|
||||
num_items_y=5, # 5行
|
||||
num_items_z=1, # 1层
|
||||
row_offset=row_offset,
|
||||
layout="row-major",
|
||||
)
|
||||
```
|
||||
|
||||
**自动生成的库位:** A01, A02, A03, B01, B02, B03, ..., E01, E02, E03
|
||||
|
||||
#### 2️⃣ Deck 布局层 - [`decks.py`](decks.py)
|
||||
|
||||
```python
|
||||
self.warehouses = {
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1("手动传递窗右", row_offset=0),
|
||||
}
|
||||
self.warehouse_locations = {
|
||||
"手动传递窗右": Coordinate(4160.0, 877.0, 0.0),
|
||||
}
|
||||
```
|
||||
|
||||
**作用:**
|
||||
- 创建仓库实例
|
||||
- 设置在 Deck 上的物理坐标
|
||||
|
||||
#### 3️⃣ UUID 映射层 - [`config.py`](../../devices/workstation/bioyond_studio/config.py)
|
||||
|
||||
```python
|
||||
WAREHOUSE_MAPPING = {
|
||||
"手动传递窗右": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
|
||||
# ... 其他库位
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**作用:**
|
||||
- 用户拖拽物料到"手动传递窗右"的"A01"位置时
|
||||
- 系统查找 `WAREHOUSE_MAPPING["手动传递窗右"]["site_uuids"]["A01"]`
|
||||
- 获取 UUID `"3a19deae-2c7a-36f5-5e41-02c5b66feaea"`
|
||||
- 调用 Bioyond API 将物料入库到该 UUID 位置
|
||||
|
||||
---
|
||||
|
||||
## 实际配置案例
|
||||
|
||||
### 案例:手动传递窗左/右的完整配置
|
||||
|
||||
本案例展示如何为"手动传递窗右"和"手动传递窗左"建立完整的三层映射。
|
||||
|
||||
#### 背景需求
|
||||
- **手动传递窗右**: 需要 A01-E03(5行×3列=15个库位)
|
||||
- **手动传递窗左**: 需要 F01-J03(5行×3列=15个库位)
|
||||
- 这两个仓库共享同一个物理堆栈的 UUID("手动堆栈")
|
||||
|
||||
#### 实施步骤
|
||||
|
||||
**1️⃣ 修复前端布局** - [`YB_warehouses.py`](YB_warehouses.py)
|
||||
|
||||
```python
|
||||
# 创建新的 5×3×1 仓库函数(之前是错误的 1×3×3)
|
||||
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
|
||||
"""创建5行×3列×1层仓库,支持行偏移生成不同字母行"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列
|
||||
num_items_y=5, # 5行 ← 修正
|
||||
num_items_z=1, # 1层 ← 修正
|
||||
row_offset=row_offset, # ← 支持 F-J 行
|
||||
layout="row-major", # ← 避免上下颠倒
|
||||
)
|
||||
```
|
||||
|
||||
**2️⃣ 更新 Deck 配置** - [`decks.py`](decks.py)
|
||||
|
||||
```python
|
||||
from unilabos.resources.bioyond.YB_warehouses import (
|
||||
bioyond_warehouse_5x3x1, # 新增导入
|
||||
)
|
||||
|
||||
class BIOYOND_YB_Deck(Deck):
|
||||
def setup(self) -> None:
|
||||
self.warehouses = {
|
||||
# 修改前: bioyond_warehouse_1x3x3 (错误尺寸)
|
||||
# 修改后: bioyond_warehouse_5x3x1 (正确尺寸)
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1("手动传递窗右", row_offset=0), # A01-E03
|
||||
"手动传递窗左": bioyond_warehouse_5x3x1("手动传递窗左", row_offset=5), # F01-J03
|
||||
}
|
||||
```
|
||||
|
||||
**3️⃣ 添加 UUID 映射** - [`config.py`](../../devices/workstation/bioyond_studio/config.py)
|
||||
|
||||
```python
|
||||
WAREHOUSE_MAPPING = {
|
||||
# 保持原有的"手动堆栈"配置不变(A01-J03共30个库位)
|
||||
"手动堆栈": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||
# ... A02-E03 共15个
|
||||
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
|
||||
# ... F02-J03 共15个
|
||||
}
|
||||
},
|
||||
|
||||
# [新增] 手动传递窗右 - 复用"手动堆栈"的 A01-E03 UUID
|
||||
"手动传递窗右": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea", # ← 与手动堆栈A01相同
|
||||
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
|
||||
"A03": "3a19deae-2c7a-5876-c454-6b7e224ca927",
|
||||
"B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1",
|
||||
"B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3",
|
||||
"B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||
"C01": "3a19deae-2c7a-32bc-768e-556647e292f3",
|
||||
"C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4",
|
||||
"C03": "3a19deae-2c7a-3056-6504-10dc73fbc276",
|
||||
"D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440",
|
||||
"D02": "3a19deae-2c7a-61be-601c-b6fb5610499a",
|
||||
"D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560",
|
||||
"E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363",
|
||||
"E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910",
|
||||
"E03": "3a19deae-2c7a-b163-2219-23df15200311",
|
||||
}
|
||||
},
|
||||
|
||||
# [新增] 手动传递窗左 - 复用"手动堆栈"的 F01-J03 UUID
|
||||
"手动传递窗左": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a", # ← 与手动堆栈F01相同
|
||||
"F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679",
|
||||
"F03": "3a19deae-2c7a-f7c4-12bd-425799425698",
|
||||
"G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955",
|
||||
"G02": "3a19deae-2c7a-204e-95ed-1f1950f28343",
|
||||
"G03": "3a19deae-2c7a-392b-62f1-4907c66343f8",
|
||||
"H01": "3a19deae-2c7a-5602-e876-d27aca4e3201",
|
||||
"H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702",
|
||||
"H03": "3a19deae-2c7a-780b-8965-2e1345f7e834",
|
||||
"I01": "3a19deae-2c7a-8849-e172-07de14ede928",
|
||||
"I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0",
|
||||
"I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4",
|
||||
"J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6",
|
||||
"J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205",
|
||||
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### 关键要点
|
||||
|
||||
1. **UUID 可以复用**: 三个仓库(手动堆栈、手动传递窗右、手动传递窗左)可以共享相同的物理库位 UUID
|
||||
2. **库位名称必须匹配**: 前端生成的库位名称(如 F01)必须与 config.py 中的键名完全一致
|
||||
3. **row_offset 的妙用**:
|
||||
- `row_offset=0` → 生成 A-E 行
|
||||
- `row_offset=5` → 生成 F-J 行(跳过前5个字母)
|
||||
|
||||
#### 验证结果
|
||||
|
||||
配置完成后,拖拽测试:
|
||||
|
||||
| 拖拽位置 | 前端库位 | 查找路径 | UUID | 结果 |
|
||||
|---------|---------|---------|------|------|
|
||||
| 手动传递窗右/A01 | A01 | `WAREHOUSE_MAPPING["手动传递窗右"]["site_uuids"]["A01"]` | `3a19...eaea` | ✅ 正确入库 |
|
||||
| 手动传递窗左/F01 | F01 | `WAREHOUSE_MAPPING["手动传递窗左"]["site_uuids"]["F01"]` | `3a19...c4a` | ✅ 正确入库 |
|
||||
| 手动堆栈/A01 | A01 | `WAREHOUSE_MAPPING["手动堆栈"]["site_uuids"]["A01"]` | `3a19...eaea` | ✅ 仍然正常 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## warehouse_factory 详解
|
||||
|
||||
### 函数签名
|
||||
|
||||
```python
|
||||
def warehouse_factory(
|
||||
name: str,
|
||||
num_items_x: int = 1, # 列数
|
||||
num_items_y: int = 4, # 行数
|
||||
num_items_z: int = 4, # 层数
|
||||
dx: float = 137.0, # X 起始偏移
|
||||
dy: float = 96.0, # Y 起始偏移
|
||||
dz: float = 120.0, # Z 起始偏移
|
||||
item_dx: float = 10.0, # X 间距
|
||||
item_dy: float = 10.0, # Y 间距
|
||||
item_dz: float = 10.0, # Z 间距
|
||||
col_offset: int = 0, # 列偏移(影响数字)
|
||||
row_offset: int = 0, # 行偏移(影响字母)
|
||||
layout: str = "col-major", # 布局模式
|
||||
) -> WareHouse:
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
#### 尺寸参数
|
||||
- **num_items_x, y, z**: 定义仓库的网格尺寸
|
||||
- **注意**: 当 `num_items_z > 1` 时,Z 轴会被映射为数字列
|
||||
|
||||
#### 位置参数
|
||||
- **dx, dy, dz**: 第一个库位的起始坐标
|
||||
- **item_dx, dy, dz**: 库位之间的间距
|
||||
|
||||
#### 偏移参数
|
||||
- **col_offset**: 列起始偏移,用于生成 A05-D08 等命名
|
||||
```python
|
||||
col_offset=4 # 生成 A05, A06, A07, A08
|
||||
```
|
||||
|
||||
- **row_offset**: 行起始偏移,用于生成 F01-J03 等命名
|
||||
```python
|
||||
row_offset=5 # 生成 F01, F02, F03(跳过 A-E)
|
||||
```
|
||||
|
||||
#### 布局参数
|
||||
- **layout**:
|
||||
- `"col-major"`: 列优先(默认),可能导致上下颠倒
|
||||
- `"row-major"`: 行优先,**推荐使用**,A 显示在上
|
||||
|
||||
### 库位生成逻辑
|
||||
|
||||
```python
|
||||
# row-major 模式(推荐)
|
||||
keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}"
|
||||
for j in range(num_y)
|
||||
for i in range(num_x)]
|
||||
|
||||
# 示例:num_y=2, num_x=3, row_offset=0, col_offset=0
|
||||
# 生成:A01, A02, A03, B01, B02, B03
|
||||
```
|
||||
|
||||
### Y 坐标计算
|
||||
|
||||
```python
|
||||
if layout == "row-major":
|
||||
# A 在上(Y 较小)
|
||||
y = dy + row * item_dy
|
||||
else:
|
||||
# A 在下(Y 较大)- 不推荐
|
||||
y = dy + (num_items_y - row - 1) * item_dy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 创建新仓库
|
||||
|
||||
### 步骤 1: 在 YB_warehouses.py 中创建函数
|
||||
|
||||
```python
|
||||
def bioyond_warehouse_3x4x1(name: str) -> WareHouse:
|
||||
"""创建 3行×4列×1层 仓库
|
||||
|
||||
布局:
|
||||
A01 | A02 | A03 | A04
|
||||
B01 | B02 | B03 | B04
|
||||
C01 | C02 | C03 | C04
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=4, # 4列
|
||||
num_items_y=3, # 3行
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=120.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
layout="row-major", # ⭐ 推荐使用
|
||||
)
|
||||
```
|
||||
|
||||
### 步骤 2: 在 decks.py 中使用
|
||||
|
||||
```python
|
||||
# 1. 导入函数
|
||||
from unilabos.resources.bioyond.YB_warehouses import (
|
||||
bioyond_warehouse_3x4x1, # 新增
|
||||
)
|
||||
|
||||
# 2. 在 setup() 中添加
|
||||
self.warehouses = {
|
||||
"我的新仓库": bioyond_warehouse_3x4x1("我的新仓库"),
|
||||
}
|
||||
self.warehouse_locations = {
|
||||
"我的新仓库": Coordinate(100.0, 200.0, 0.0),
|
||||
}
|
||||
```
|
||||
|
||||
### 步骤 3: 在 config.py 中配置 UUID(可选)
|
||||
|
||||
```python
|
||||
WAREHOUSE_MAPPING = {
|
||||
"我的新仓库": {
|
||||
"uuid": "",
|
||||
"site_uuids": {
|
||||
"A01": "从 Bioyond 系统获取的 UUID",
|
||||
"A02": "从 Bioyond 系统获取的 UUID",
|
||||
# ... 其他 11 个库位
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**注意:** 如果不需要拖拽入库功能,可跳过此步骤。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 为什么库位显示上下颠倒(C 在上,A 在下)?
|
||||
|
||||
**原因:** 使用了默认的 `col-major` 布局。
|
||||
|
||||
**解决:** 在 `warehouse_factory` 中添加 `layout="row-major"`
|
||||
|
||||
```python
|
||||
return warehouse_factory(
|
||||
...
|
||||
layout="row-major", # ← 添加这行
|
||||
)
|
||||
```
|
||||
|
||||
### Q2: 我需要 1×3×3 还是 3×3×1?
|
||||
|
||||
**判断方法:**
|
||||
- **1×3×3**: 1列×3行×3**层**(垂直堆叠,有高度)
|
||||
- **3×3×1**: 3行×3列×1**层**(平面网格)
|
||||
|
||||
**推荐:** 大多数情况使用 `X×Y×1`(平面网格)更直观。
|
||||
|
||||
### Q3: 如何生成 F01-J03 而非 A01-E03?
|
||||
|
||||
**方法:** 使用 `row_offset` 参数
|
||||
|
||||
```python
|
||||
bioyond_warehouse_5x3x1("仓库名", row_offset=5)
|
||||
# row_offset=5 跳过 A-E,从 F 开始
|
||||
```
|
||||
|
||||
### Q4: 拖拽物料后找不到 UUID 怎么办?
|
||||
|
||||
**检查清单:**
|
||||
1. `config.py` 中是否有该仓库的配置?
|
||||
2. 仓库名称是否完全匹配?
|
||||
3. 库位名称(如 A01)是否在 `site_uuids` 中?
|
||||
|
||||
**示例错误:**
|
||||
```python
|
||||
# decks.py
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1(...)
|
||||
|
||||
# config.py - ❌ 名称不匹配
|
||||
"手动传递窗": { ... } # 缺少"右"字
|
||||
```
|
||||
|
||||
### Q5: 库位重叠怎么办?
|
||||
|
||||
**原因:** 间距(`item_dx/dy/dz`)太小。
|
||||
|
||||
**解决:** 增大间距参数
|
||||
|
||||
```python
|
||||
item_dx=150.0, # 增大 X 间距
|
||||
item_dy=130.0, # 增大 Y 间距
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 查看生成的库位
|
||||
|
||||
```python
|
||||
warehouse = bioyond_warehouse_5x3x1("测试仓库")
|
||||
print(list(warehouse.sites.keys()))
|
||||
# 输出:['A01', 'A02', 'A03', 'B01', 'B02', ...]
|
||||
```
|
||||
|
||||
### 2. 检查库位坐标
|
||||
|
||||
```python
|
||||
for name, site in warehouse.sites.items():
|
||||
print(f"{name}: {site.location}")
|
||||
# 输出:
|
||||
# A01: Coordinate(x=10.0, y=10.0, z=120.0)
|
||||
# A02: Coordinate(x=147.0, y=10.0, z=120.0)
|
||||
# ...
|
||||
```
|
||||
|
||||
### 3. 验证 UUID 映射
|
||||
|
||||
```python
|
||||
from unilabos.devices.workstation.bioyond_studio.config import WAREHOUSE_MAPPING
|
||||
|
||||
warehouse_name = "手动传递窗右"
|
||||
location_code = "A01"
|
||||
|
||||
if warehouse_name in WAREHOUSE_MAPPING:
|
||||
uuid = WAREHOUSE_MAPPING[warehouse_name]["site_uuids"].get(location_code)
|
||||
print(f"{warehouse_name}/{location_code} → {uuid}")
|
||||
else:
|
||||
print(f"❌ 未找到仓库: {warehouse_name}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 文件关系图
|
||||
|
||||
```
|
||||
unilabos/
|
||||
├── resources/
|
||||
│ ├── warehouse.py # warehouse_factory 核心实现
|
||||
│ └── bioyond/
|
||||
│ ├── YB_warehouses.py # ⭐ 仓库函数定义
|
||||
│ ├── decks.py # ⭐ Deck 布局配置
|
||||
│ └── README_WAREHOUSE.md # 📖 本文档
|
||||
└── devices/
|
||||
└── workstation/
|
||||
└── bioyond_studio/
|
||||
├── config.py # ⭐ UUID 映射配置
|
||||
└── bioyond_cell/
|
||||
└── bioyond_cell_workstation.py # 业务逻辑
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 版本历史
|
||||
|
||||
- **v1.1** (2026-01-08): 补充实际配置案例
|
||||
- 添加"手动传递窗右"和"手动传递窗左"的完整配置示例
|
||||
- 展示 UUID 复用的实际应用
|
||||
- 说明三个仓库共享物理堆栈的配置方法
|
||||
|
||||
- **v1.0** (2026-01-07): 初始版本
|
||||
- 新增 `row_offset` 参数支持
|
||||
- 创建 `bioyond_warehouse_5x3x1` 和 `bioyond_warehouse_2x2x1`
|
||||
- 修复多个仓库的上下颠倒问题
|
||||
|
||||
---
|
||||
|
||||
## 相关资源
|
||||
|
||||
- [warehouse.py](../warehouse.py) - 核心工厂函数实现
|
||||
- [YB_warehouses.py](YB_warehouses.py) - 所有仓库定义
|
||||
- [decks.py](decks.py) - Deck 布局配置
|
||||
- [config.py](../../devices/workstation/bioyond_studio/config.py) - UUID 映射
|
||||
|
||||
---
|
||||
|
||||
**维护者:** Uni-Lab-OS 开发团队
|
||||
**最后更新:** 2026-01-07
|
||||
653
unilabos/resources/bioyond/YB_bottle_carriers.py
Normal file
653
unilabos/resources/bioyond/YB_bottle_carriers.py
Normal file
@@ -0,0 +1,653 @@
|
||||
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_jia_yang_tou_da,
|
||||
YB_ye_Bottle,
|
||||
YB_ye_100ml_Bottle,
|
||||
YB_gao_nian_ye_Bottle,
|
||||
YB_5ml_fenyeping,
|
||||
YB_20ml_fenyeping,
|
||||
YB_pei_ye_xiao_Bottle,
|
||||
YB_pei_ye_da_Bottle,
|
||||
YB_qiang_tou,
|
||||
)
|
||||
# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial
|
||||
|
||||
|
||||
def BIOYOND_Electrolyte_6VialCarrier(name: str) -> BottleCarrier:
|
||||
"""6瓶载架 - 2x3布局"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 50.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 30.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=3,
|
||||
num_items_y=2,
|
||||
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_6VialCarrier",
|
||||
)
|
||||
carrier.num_items_x = 3
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
# for i in range(6):
|
||||
# carrier[i] = YB_Solid_Vial(f"{name}_vial_{i+1}")
|
||||
return carrier
|
||||
|
||||
|
||||
def BIOYOND_Electrolyte_1BottleCarrier(name: str) -> BottleCarrier:
|
||||
"""1瓶载架 - 单个中央位置"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 100.0
|
||||
|
||||
# 烧杯尺寸
|
||||
beaker_diameter = 80.0
|
||||
|
||||
# 计算中央位置
|
||||
center_x = (carrier_size_x - beaker_diameter) / 2
|
||||
center_y = (carrier_size_y - beaker_diameter) / 2
|
||||
center_z = 5.0
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=create_homogeneous_resources(
|
||||
klass=ResourceHolder,
|
||||
locations=[Coordinate(center_x, center_y, center_z)],
|
||||
resource_size_x=beaker_diameter,
|
||||
resource_size_y=beaker_diameter,
|
||||
name_prefix=name,
|
||||
),
|
||||
model="Electrolyte_1BottleCarrier",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
# carrier[0] = YB_Solution_Beaker(f"{name}_beaker_1")
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_6StockCarrier(name: str) -> BottleCarrier:
|
||||
"""6瓶载架 - 2x3布局"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 50.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 20.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=3,
|
||||
num_items_y=2,
|
||||
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="6StockCarrier",
|
||||
)
|
||||
carrier.num_items_x = 3
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序
|
||||
# for i in range(6):
|
||||
# carrier[i] = YB_Solid_Stock(f"{name}_vial_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_6VialCarrier(name: str) -> BottleCarrier:
|
||||
"""6瓶载架 - 2x3布局"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 50.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 30.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (3 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=3,
|
||||
num_items_y=2,
|
||||
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="6VialCarrier",
|
||||
)
|
||||
carrier.num_items_x = 3
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "A3", "B1", "B2", "B3"] # 自定义顺序
|
||||
# for i in range(3):
|
||||
# carrier[i] = YB_Solid_Vial(f"{name}_solidvial_{ordering[i]}")
|
||||
# for i in range(3, 6):
|
||||
# carrier[i] = YB_Liquid_Vial(f"{name}_liquidvial_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
# 1瓶载架 - 单个中央位置
|
||||
def YB_ye(name: str) -> BottleCarrier:
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 20.0
|
||||
|
||||
# 烧杯尺寸
|
||||
beaker_diameter = 60.0
|
||||
|
||||
# 计算中央位置
|
||||
center_x = (carrier_size_x - beaker_diameter) / 2
|
||||
center_y = (carrier_size_y - beaker_diameter) / 2
|
||||
center_z = 5.0
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=create_homogeneous_resources(
|
||||
klass=ResourceHolder,
|
||||
locations=[Coordinate(center_x, center_y, center_z)],
|
||||
resource_size_x=beaker_diameter,
|
||||
resource_size_y=beaker_diameter,
|
||||
name_prefix=name,
|
||||
),
|
||||
model="YB_ye",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
carrier[0] = YB_ye_Bottle(f"{name}_flask_1")
|
||||
return carrier
|
||||
|
||||
|
||||
# 高粘液瓶载架 - 单个中央位置
|
||||
def YB_gaonianye(name: str) -> BottleCarrier:
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 20.0
|
||||
|
||||
# 烧杯尺寸
|
||||
beaker_diameter = 60.0
|
||||
|
||||
# 计算中央位置
|
||||
center_x = (carrier_size_x - beaker_diameter) / 2
|
||||
center_y = (carrier_size_y - beaker_diameter) / 2
|
||||
center_z = 5.0
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=create_homogeneous_resources(
|
||||
klass=ResourceHolder,
|
||||
locations=[Coordinate(center_x, center_y, center_z)],
|
||||
resource_size_x=beaker_diameter,
|
||||
resource_size_y=beaker_diameter,
|
||||
name_prefix=name,
|
||||
),
|
||||
model="YB_gaonianye",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
carrier[0] = YB_gao_nian_ye_Bottle(f"{name}_flask_1")
|
||||
return carrier
|
||||
|
||||
|
||||
# 100ml液体瓶载架 - 单个中央位置
|
||||
def YB_100ml_yeti(name: str) -> BottleCarrier:
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 20.0
|
||||
|
||||
# 烧杯尺寸
|
||||
beaker_diameter = 60.0
|
||||
|
||||
# 计算中央位置
|
||||
center_x = (carrier_size_x - beaker_diameter) / 2
|
||||
center_y = (carrier_size_y - beaker_diameter) / 2
|
||||
center_z = 5.0
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=create_homogeneous_resources(
|
||||
klass=ResourceHolder,
|
||||
locations=[Coordinate(center_x, center_y, center_z)],
|
||||
resource_size_x=beaker_diameter,
|
||||
resource_size_y=beaker_diameter,
|
||||
name_prefix=name,
|
||||
),
|
||||
model="YB_100ml_yeti",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
carrier[0] = YB_ye_100ml_Bottle(f"{name}_flask_1")
|
||||
return carrier
|
||||
|
||||
# 5ml分液瓶板 - 4x2布局,8个位置
|
||||
def YB_5ml_fenyepingban(name: str) -> BottleCarrier:
|
||||
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 50.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 15.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=4,
|
||||
num_items_y=2,
|
||||
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="YB_5ml_fenyepingban",
|
||||
)
|
||||
carrier.num_items_x = 4
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
|
||||
for i in range(8):
|
||||
carrier[i] = YB_5ml_fenyeping(f"{name}_vial_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
# 20ml分液瓶板 - 4x2布局,8个位置
|
||||
def YB_20ml_fenyepingban(name: str) -> BottleCarrier:
|
||||
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 70.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 20.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=4,
|
||||
num_items_y=2,
|
||||
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="YB_20ml_fenyepingban",
|
||||
)
|
||||
carrier.num_items_x = 4
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
|
||||
for i in range(8):
|
||||
carrier[i] = YB_20ml_fenyeping(f"{name}_vial_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
# 配液瓶(小)板 - 4x2布局,8个位置
|
||||
def YB_peiyepingxiaoban(name: str) -> BottleCarrier:
|
||||
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 65.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 35.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=4,
|
||||
num_items_y=2,
|
||||
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="YB_peiyepingxiaoban",
|
||||
)
|
||||
carrier.num_items_x = 4
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
|
||||
for i in range(8):
|
||||
carrier[i] = YB_pei_ye_xiao_Bottle(f"{name}_bottle_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
|
||||
# 配液瓶(大)板 - 2x2布局,4个位置
|
||||
def YB_peiyepingdaban(name: str) -> BottleCarrier:
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 95.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 55.0
|
||||
bottle_spacing_x = 60.0 # X方向间距
|
||||
bottle_spacing_y = 60.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (2 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=2,
|
||||
num_items_y=2,
|
||||
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="YB_peiyepingdaban",
|
||||
)
|
||||
carrier.num_items_x = 2
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
ordering = ["A1", "A2", "B1", "B2"]
|
||||
for i in range(4):
|
||||
carrier[i] = YB_pei_ye_da_Bottle(f"{name}_bottle_{ordering[i]}")
|
||||
return carrier
|
||||
|
||||
# 加样头(大)板 - 1x1布局,1个位置
|
||||
def YB_jia_yang_tou_da_Carrier(name: str) -> BottleCarrier:
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 95.0
|
||||
|
||||
# 瓶位尺寸
|
||||
bottle_diameter = 35.0
|
||||
bottle_spacing_x = 42.0 # X方向间距
|
||||
bottle_spacing_y = 35.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (1 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (1 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=1,
|
||||
num_items_y=1,
|
||||
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="YB_jia_yang_tou_da_Carrier",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
carrier[0] = YB_jia_yang_tou_da(f"{name}_head_1")
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_shi_pei_qi_kuai(name: str) -> BottleCarrier:
|
||||
"""适配器块 - 单个中央位置"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 30.0
|
||||
|
||||
# 适配器尺寸
|
||||
adapter_diameter = 80.0
|
||||
|
||||
# 计算中央位置
|
||||
center_x = (carrier_size_x - adapter_diameter) / 2
|
||||
center_y = (carrier_size_y - adapter_diameter) / 2
|
||||
center_z = 0.0
|
||||
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=create_homogeneous_resources(
|
||||
klass=ResourceHolder,
|
||||
locations=[Coordinate(center_x, center_y, center_z)],
|
||||
resource_size_x=adapter_diameter,
|
||||
resource_size_y=adapter_diameter,
|
||||
name_prefix=name,
|
||||
),
|
||||
model="YB_shi_pei_qi_kuai",
|
||||
)
|
||||
carrier.num_items_x = 1
|
||||
carrier.num_items_y = 1
|
||||
carrier.num_items_z = 1
|
||||
# 适配器块本身不包含瓶子,只是一个支撑结构
|
||||
return carrier
|
||||
|
||||
|
||||
def YB_qiang_tou_he(name: str) -> BottleCarrier:
|
||||
"""枪头盒 - 8x12布局,96个位置"""
|
||||
|
||||
# 载架尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 55.0
|
||||
|
||||
# 枪头尺寸
|
||||
tip_diameter = 10.0
|
||||
tip_spacing_x = 9.0 # X方向间距
|
||||
tip_spacing_y = 9.0 # Y方向间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (12 - 1) * tip_spacing_x - tip_diameter) / 2
|
||||
start_y = (carrier_size_y - (8 - 1) * tip_spacing_y - tip_diameter) / 2
|
||||
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=12,
|
||||
num_items_y=8,
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0,
|
||||
item_dx=tip_spacing_x,
|
||||
item_dy=tip_spacing_y,
|
||||
size_x=tip_diameter,
|
||||
size_y=tip_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="YB_qiang_tou_he",
|
||||
)
|
||||
carrier.num_items_x = 12
|
||||
carrier.num_items_y = 8
|
||||
carrier.num_items_z = 1
|
||||
# 创建96个枪头
|
||||
for i in range(96):
|
||||
row = chr(65 + i // 12) # A-H
|
||||
col = (i % 12) + 1 # 1-12
|
||||
carrier[i] = YB_qiang_tou(f"{name}_tip_{row}{col}")
|
||||
return carrier
|
||||
|
||||
163
unilabos/resources/bioyond/YB_bottles.py
Normal file
163
unilabos/resources/bioyond/YB_bottles.py
Normal file
@@ -0,0 +1,163 @@
|
||||
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
|
||||
# 工厂函数
|
||||
"""加样头(大)"""
|
||||
def YB_jia_yang_tou_da(
|
||||
name: str,
|
||||
diameter: float = 20.0,
|
||||
height: float = 100.0,
|
||||
max_volume: float = 30000.0, # 30mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建粉末瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,# 未知
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_jia_yang_tou_da",
|
||||
)
|
||||
|
||||
"""液1x1"""
|
||||
def YB_ye_Bottle(
|
||||
name: str,
|
||||
diameter: float = 40.0,
|
||||
height: float = 70.0,
|
||||
max_volume: float = 50000.0, # 50mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建液体瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_ye_Bottle",
|
||||
)
|
||||
|
||||
"""100ml液体"""
|
||||
def YB_ye_100ml_Bottle(
|
||||
name: str,
|
||||
diameter: float = 50.0,
|
||||
height: float = 90.0,
|
||||
max_volume: float = 100000.0, # 100mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建100ml液体瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_100ml_yeti",
|
||||
)
|
||||
|
||||
"""高粘液"""
|
||||
def YB_gao_nian_ye_Bottle(
|
||||
name: str,
|
||||
diameter: float = 40.0,
|
||||
height: float = 70.0,
|
||||
max_volume: float = 50000.0, # 50mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建高粘液瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="High_Viscosity_Liquid",
|
||||
)
|
||||
|
||||
"""5ml分液瓶"""
|
||||
def YB_5ml_fenyeping(
|
||||
name: str,
|
||||
diameter: float = 20.0,
|
||||
height: float = 50.0,
|
||||
max_volume: float = 5000.0, # 5mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建5ml分液瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_5ml_fenyeping",
|
||||
)
|
||||
|
||||
"""20ml分液瓶"""
|
||||
def YB_20ml_fenyeping(
|
||||
name: str,
|
||||
diameter: float = 30.0,
|
||||
height: float = 65.0,
|
||||
max_volume: float = 20000.0, # 20mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建20ml分液瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_20ml_fenyeping",
|
||||
)
|
||||
|
||||
"""配液瓶(小)"""
|
||||
def YB_pei_ye_xiao_Bottle(
|
||||
name: str,
|
||||
diameter: float = 35.0,
|
||||
height: float = 60.0,
|
||||
max_volume: float = 30000.0, # 30mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建配液瓶(小)"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_pei_ye_xiao_Bottle",
|
||||
)
|
||||
|
||||
"""配液瓶(大)"""
|
||||
def YB_pei_ye_da_Bottle(
|
||||
name: str,
|
||||
diameter: float = 55.0,
|
||||
height: float = 100.0,
|
||||
max_volume: float = 150000.0, # 150mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建配液瓶(大)"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_pei_ye_da_Bottle",
|
||||
)
|
||||
|
||||
"""枪头"""
|
||||
def YB_qiang_tou(
|
||||
name: str,
|
||||
diameter: float = 10.0,
|
||||
height: float = 50.0,
|
||||
max_volume: float = 1000.0, # 1mL
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建枪头"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_qiang_tou",
|
||||
)
|
||||
@@ -166,7 +166,14 @@ def bioyond_warehouse_1x4x2(name: str) -> WareHouse:
|
||||
)
|
||||
|
||||
def bioyond_warehouse_1x2x2(name: str) -> WareHouse:
|
||||
"""创建BioYond 1x2x2仓库"""
|
||||
"""创建BioYond 1x2x2仓库(1列×2行×2层)- 旧版本,已弃用
|
||||
|
||||
布局(2层):
|
||||
层1: A01
|
||||
B01
|
||||
层2: A02
|
||||
B02
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=1,
|
||||
@@ -179,8 +186,32 @@ def bioyond_warehouse_1x2x2(name: str) -> WareHouse:
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
layout="row-major", # 使用行优先避免上下颠倒
|
||||
)
|
||||
|
||||
def bioyond_warehouse_2x2x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 2x2x1仓库(2行×2列×1层)
|
||||
|
||||
布局:
|
||||
A01 | A02
|
||||
B01 | B02
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=2, # 2列
|
||||
num_items_y=2, # 2行
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
layout="row-major", # 使用行优先避免上下颠倒
|
||||
)
|
||||
|
||||
|
||||
def bioyond_warehouse_10x1x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 10x1x1仓库"""
|
||||
return warehouse_factory(
|
||||
@@ -208,11 +239,61 @@ def bioyond_warehouse_1x3x3(name: str) -> WareHouse:
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dy=120.0, # 增大Y方向间距以避免重叠
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_5x3x1(name: str, row_offset: int = 0) -> WareHouse:
|
||||
"""创建BioYond 5x3x1仓库(5行×3列×1层)
|
||||
|
||||
标准布局(row_offset=0):
|
||||
A01 | A02 | A03
|
||||
B01 | B02 | B03
|
||||
C01 | C02 | C03
|
||||
D01 | D02 | D03
|
||||
E01 | E02 | E03
|
||||
|
||||
带偏移布局(row_offset=5):
|
||||
F01 | F02 | F03
|
||||
G01 | G02 | G03
|
||||
H01 | H02 | H03
|
||||
I01 | I02 | I03
|
||||
J01 | J02 | J03
|
||||
"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3, # 3列
|
||||
num_items_y=5, # 5行
|
||||
num_items_z=1, # 1层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=120.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
col_offset=0,
|
||||
row_offset=row_offset, # 支持行偏移
|
||||
layout="row-major", # 使用行优先避免颠倒
|
||||
)
|
||||
|
||||
|
||||
def bioyond_warehouse_3x3x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 3x3x1仓库"""
|
||||
return warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=3,
|
||||
num_items_y=3,
|
||||
num_items_z=1,
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="warehouse",
|
||||
)
|
||||
def bioyond_warehouse_2x1x3(name: str) -> WareHouse:
|
||||
"""创建BioYond 2x1x3仓库"""
|
||||
return warehouse_factory(
|
||||
@@ -1,14 +1,16 @@
|
||||
from os import name
|
||||
from pylabrobot.resources import Deck, Coordinate, Rotation
|
||||
|
||||
from unilabos.resources.bioyond.warehouses import (
|
||||
from unilabos.resources.bioyond.YB_warehouses import (
|
||||
bioyond_warehouse_1x4x4,
|
||||
bioyond_warehouse_1x4x4_right, # 新增:右侧仓库 (A05~D08)
|
||||
bioyond_warehouse_1x4x2,
|
||||
bioyond_warehouse_reagent_stack, # 新增:试剂堆栈 (A1-B4)
|
||||
bioyond_warehouse_liquid_and_lid_handling,
|
||||
bioyond_warehouse_1x2x2,
|
||||
bioyond_warehouse_2x2x1, # 新增:321和43窗口 (2行×2列)
|
||||
bioyond_warehouse_1x3x3,
|
||||
bioyond_warehouse_5x3x1, # 新增:手动传递窗仓库 (5行×3列)
|
||||
bioyond_warehouse_10x1x1,
|
||||
bioyond_warehouse_3x3x1,
|
||||
bioyond_warehouse_3x3x1_2,
|
||||
@@ -115,10 +117,10 @@ class BIOYOND_YB_Deck(Deck):
|
||||
def setup(self) -> None:
|
||||
# 添加仓库
|
||||
self.warehouses = {
|
||||
"321窗口": bioyond_warehouse_1x2x2("321窗口"),
|
||||
"43窗口": bioyond_warehouse_1x2x2("43窗口"),
|
||||
"手动传递窗左": bioyond_warehouse_1x3x3("手动传递窗左"),
|
||||
"手动传递窗右": bioyond_warehouse_1x3x3("手动传递窗右"),
|
||||
"321窗口": bioyond_warehouse_2x2x1("321窗口"), # 2行×2列
|
||||
"43窗口": bioyond_warehouse_2x2x1("43窗口"), # 2行×2列
|
||||
"手动传递窗右": bioyond_warehouse_5x3x1("手动传递窗右", row_offset=0), # A01-E03
|
||||
"手动传递窗左": bioyond_warehouse_5x3x1("手动传递窗左", row_offset=5), # F01-J03
|
||||
"加样头堆栈左": bioyond_warehouse_10x1x1("加样头堆栈左"),
|
||||
"加样头堆栈右": bioyond_warehouse_10x1x1("加样头堆栈右"),
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class Bottle(Well):
|
||||
size_x: float = 0.0,
|
||||
size_y: float = 0.0,
|
||||
size_z: float = 0.0,
|
||||
barcode: Optional[str] = "",
|
||||
barcode: Optional[str] = None,
|
||||
category: str = "container",
|
||||
model: Optional[str] = None,
|
||||
**kwargs,
|
||||
|
||||
@@ -27,6 +27,7 @@ def warehouse_factory(
|
||||
category: str = "warehouse",
|
||||
model: Optional[str] = None,
|
||||
col_offset: int = 0, # 列起始偏移量,用于生成A05-D08等命名
|
||||
row_offset: int = 0, # 行起始偏移量,用于生成F01-J03等命名
|
||||
layout: str = "col-major", # 新增:排序方式,"col-major"=列优先,"row-major"=行优先
|
||||
):
|
||||
# 创建位置坐标
|
||||
@@ -65,10 +66,10 @@ def warehouse_factory(
|
||||
if layout == "row-major":
|
||||
# 行优先顺序: A01,A02,A03,A04, B01,B02,B03,B04
|
||||
# locations[0] 对应 row=0, y最大(前端顶部)→ 应该是 A01
|
||||
keys = [f"{LETTERS[j]}{i + 1 + col_offset:02d}" for j in range(len_y) for i in range(len_x)]
|
||||
keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}" for j in range(len_y) for i in range(len_x)]
|
||||
else:
|
||||
# 列优先顺序: A01,B01,C01,D01, A02,B02,C02,D02
|
||||
keys = [f"{LETTERS[j]}{i + 1 + col_offset:02d}" for i in range(len_x) for j in range(len_y)]
|
||||
keys = [f"{LETTERS[j + row_offset]}{i + 1 + col_offset:02d}" for i in range(len_x) for j in range(len_y)]
|
||||
|
||||
sites = {i: site for i, site in zip(keys, _sites.values())}
|
||||
|
||||
|
||||
@@ -664,7 +664,7 @@ class HostNode(BaseROS2DeviceNode):
|
||||
if bCreate:
|
||||
self.lab_logger().trace(f"Status created: {device_id}.{property_name} = {msg.data}")
|
||||
else:
|
||||
self.lab_logger().debug(f"Status updated: {device_id}.{property_name} = {msg.data}")
|
||||
self.lab_logger().trace(f"Status updated: {device_id}.{property_name} = {msg.data}")
|
||||
|
||||
def send_goal(
|
||||
self,
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
|
||||
},
|
||||
"material_type_mappings": {
|
||||
"烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
|
||||
"试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
|
||||
"样品板": "BIOYOND_PolymerStation_6VialCarrier"
|
||||
"烧杯": "YB_1FlaskCarrier",
|
||||
"试剂瓶": "YB_1BottleCarrier",
|
||||
"样品板": "YB_6VialCarrier"
|
||||
}
|
||||
},
|
||||
"deck": {
|
||||
|
||||
@@ -191,6 +191,18 @@ def configure_logger(loglevel=None, working_dir=None):
|
||||
|
||||
# 添加处理器到根日志记录器
|
||||
root_logger.addHandler(console_handler)
|
||||
|
||||
# 降低第三方库的日志级别,避免过多输出
|
||||
# pymodbus 库的日志太详细,设置为 WARNING
|
||||
logging.getLogger('pymodbus').setLevel(logging.WARNING)
|
||||
logging.getLogger('pymodbus.logging').setLevel(logging.WARNING)
|
||||
logging.getLogger('pymodbus.logging.base').setLevel(logging.WARNING)
|
||||
logging.getLogger('pymodbus.logging.decoders').setLevel(logging.WARNING)
|
||||
|
||||
# websockets 库的日志输出较多,设置为 WARNING
|
||||
logging.getLogger('websockets').setLevel(logging.WARNING)
|
||||
logging.getLogger('websockets.client').setLevel(logging.WARNING)
|
||||
logging.getLogger('websockets.server').setLevel(logging.WARNING)
|
||||
|
||||
# 如果指定了工作目录,添加文件处理器
|
||||
if working_dir is not None:
|
||||
|
||||
Reference in New Issue
Block a user