mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 13:25:13 +00:00
Compare commits
162 Commits
f9a9e35269
...
workstatio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||
|
||||
@@ -4,8 +4,7 @@ import traceback
|
||||
from typing import Any, Union, List, Dict, Callable, Optional, Tuple
|
||||
from pydantic import BaseModel
|
||||
|
||||
from pymodbus.client import ModbusSerialClient, ModbusTcpClient
|
||||
from pymodbus.framer import FramerType
|
||||
from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient
|
||||
from typing import TypedDict
|
||||
|
||||
from unilabos.device_comms.modbus_plc.modbus import DeviceType, HoldRegister, Coil, InputRegister, DiscreteInputs, DataType, WorderOrder
|
||||
@@ -403,7 +402,7 @@ class TCPClient(BaseClient):
|
||||
class RTUClient(BaseClient):
|
||||
def __init__(self, port: str, baudrate: int, timeout: int):
|
||||
super().__init__()
|
||||
self._set_client(ModbusSerialClient(framer=FramerType.RTU, port=port, baudrate=baudrate, timeout=timeout))
|
||||
self._set_client(ModbusSerialClient(method='rtu', port=port, baudrate=baudrate, timeout=timeout))
|
||||
self._connect()
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
# coding=utf-8
|
||||
from enum import Enum
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Tuple, Union, Optional, TYPE_CHECKING
|
||||
from pymodbus.payload import BinaryPayloadDecoder, BinaryPayloadBuilder
|
||||
from pymodbus.constants import Endian
|
||||
|
||||
from pymodbus.client import ModbusBaseSyncClient
|
||||
from pymodbus.client.mixin import ModbusClientMixin
|
||||
from typing import Tuple, Union, Optional
|
||||
if TYPE_CHECKING:
|
||||
from pymodbus.client.sync import ModbusSerialClient, ModbusTcpClient
|
||||
|
||||
# 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"
|
||||
|
||||
DataType = ModbusClientMixin.DATATYPE
|
||||
|
||||
class WorderOrder(Enum):
|
||||
BIG = "big"
|
||||
@@ -19,8 +33,96 @@ class DeviceType(Enum):
|
||||
INPUT_REGISTER = 'input_register'
|
||||
|
||||
|
||||
def _convert_from_registers(registers, data_type: DataType, word_order: str = 'big'):
|
||||
"""Convert registers to a value using BinaryPayloadDecoder.
|
||||
|
||||
Args:
|
||||
registers: List of register values
|
||||
data_type: DataType enum specifying the target data type
|
||||
word_order: 'big' or 'little' endian
|
||||
|
||||
Returns:
|
||||
Converted value
|
||||
"""
|
||||
# Determine byte and word order based on word_order parameter
|
||||
if word_order == 'little':
|
||||
byte_order = Endian.Little
|
||||
word_order_enum = Endian.Little
|
||||
else:
|
||||
byte_order = Endian.Big
|
||||
word_order_enum = Endian.Big
|
||||
|
||||
decoder = BinaryPayloadDecoder.fromRegisters(registers, byteorder=byte_order, wordorder=word_order_enum)
|
||||
|
||||
if data_type == DataType.INT16:
|
||||
return decoder.decode_16bit_int()
|
||||
elif data_type == DataType.UINT16:
|
||||
return decoder.decode_16bit_uint()
|
||||
elif data_type == DataType.INT32:
|
||||
return decoder.decode_32bit_int()
|
||||
elif data_type == DataType.UINT32:
|
||||
return decoder.decode_32bit_uint()
|
||||
elif data_type == DataType.INT64:
|
||||
return decoder.decode_64bit_int()
|
||||
elif data_type == DataType.UINT64:
|
||||
return decoder.decode_64bit_uint()
|
||||
elif data_type == DataType.FLOAT32:
|
||||
return decoder.decode_32bit_float()
|
||||
elif data_type == DataType.FLOAT64:
|
||||
return decoder.decode_64bit_float()
|
||||
elif data_type == DataType.STRING:
|
||||
return decoder.decode_string(len(registers) * 2)
|
||||
else:
|
||||
raise ValueError(f"Unsupported data type: {data_type}")
|
||||
|
||||
|
||||
def _convert_to_registers(value, data_type: DataType, word_order: str = 'little'):
|
||||
"""Convert a value to registers using BinaryPayloadBuilder.
|
||||
|
||||
Args:
|
||||
value: Value to convert
|
||||
data_type: DataType enum specifying the source data type
|
||||
word_order: 'big' or 'little' endian
|
||||
|
||||
Returns:
|
||||
List of register values
|
||||
"""
|
||||
# Determine byte and word order based on word_order parameter
|
||||
if word_order == 'little':
|
||||
byte_order = Endian.Little
|
||||
word_order_enum = Endian.Little
|
||||
else:
|
||||
byte_order = Endian.Big
|
||||
word_order_enum = Endian.Big
|
||||
|
||||
builder = BinaryPayloadBuilder(byteorder=byte_order, wordorder=word_order_enum)
|
||||
|
||||
if data_type == DataType.INT16:
|
||||
builder.add_16bit_int(value)
|
||||
elif data_type == DataType.UINT16:
|
||||
builder.add_16bit_uint(value)
|
||||
elif data_type == DataType.INT32:
|
||||
builder.add_32bit_int(value)
|
||||
elif data_type == DataType.UINT32:
|
||||
builder.add_32bit_uint(value)
|
||||
elif data_type == DataType.INT64:
|
||||
builder.add_64bit_int(value)
|
||||
elif data_type == DataType.UINT64:
|
||||
builder.add_64bit_uint(value)
|
||||
elif data_type == DataType.FLOAT32:
|
||||
builder.add_32bit_float(value)
|
||||
elif data_type == DataType.FLOAT64:
|
||||
builder.add_64bit_float(value)
|
||||
elif data_type == DataType.STRING:
|
||||
builder.add_string(value)
|
||||
else:
|
||||
raise ValueError(f"Unsupported data type: {data_type}")
|
||||
|
||||
return builder.to_registers()
|
||||
|
||||
|
||||
class Base(ABC):
|
||||
def __init__(self, client: ModbusBaseSyncClient, name: str, address: int, typ: DeviceType, data_type: DataType):
|
||||
def __init__(self, client, name: str, address: int, typ: DeviceType, data_type):
|
||||
self._address: int = address
|
||||
self._client = client
|
||||
self._name = name
|
||||
@@ -58,7 +160,11 @@ class Coil(Base):
|
||||
count = value,
|
||||
slave = slave)
|
||||
|
||||
return resp.bits, resp.isError()
|
||||
# 检查是否读取出错
|
||||
if resp.isError():
|
||||
return [], True
|
||||
|
||||
return resp.bits, False
|
||||
|
||||
def write(self,value: Union[int, float, bool, str, list[bool], list[int], list[float]], data_type: Optional[DataType ]= None, word_order: WorderOrder = WorderOrder.LITTLE, slave = 1) -> bool:
|
||||
if isinstance(value, list):
|
||||
@@ -91,8 +197,18 @@ class DiscreteInputs(Base):
|
||||
count = value,
|
||||
slave = slave)
|
||||
|
||||
# 检查是否读取出错
|
||||
if resp.isError():
|
||||
# 根据数据类型返回默认值
|
||||
if data_type in [DataType.FLOAT32, DataType.FLOAT64]:
|
||||
return 0.0, True
|
||||
elif data_type == DataType.STRING:
|
||||
return "", True
|
||||
else:
|
||||
return 0, True
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
return self._client.convert_from_registers(resp.registers, data_type, word_order=word_order.value), resp.isError()
|
||||
return _convert_from_registers(resp.registers, data_type, word_order=word_order.value), False
|
||||
|
||||
def write(self,value: Union[int, float, bool, str, list[bool], list[int], list[float]], data_type: Optional[DataType ]= None, word_order: WorderOrder = WorderOrder.LITTLE, slave = 1) -> bool:
|
||||
raise ValueError('discrete inputs only support read')
|
||||
@@ -112,8 +228,19 @@ class HoldRegister(Base):
|
||||
address = self.address,
|
||||
count = value,
|
||||
slave = slave)
|
||||
|
||||
# 检查是否读取出错
|
||||
if resp.isError():
|
||||
# 根据数据类型返回默认值
|
||||
if data_type in [DataType.FLOAT32, DataType.FLOAT64]:
|
||||
return 0.0, True
|
||||
elif data_type == DataType.STRING:
|
||||
return "", True
|
||||
else:
|
||||
return 0, True
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
return self._client.convert_from_registers(resp.registers, data_type, word_order=word_order.value), resp.isError()
|
||||
return _convert_from_registers(resp.registers, data_type, word_order=word_order.value), False
|
||||
|
||||
|
||||
def write(self,value: Union[int, float, bool, str, list[bool], list[int], list[float]], data_type: Optional[DataType ]= None, word_order: WorderOrder = WorderOrder.LITTLE, slave = 1) -> bool:
|
||||
@@ -132,7 +259,7 @@ class HoldRegister(Base):
|
||||
return self._client.write_register(self.address, value, slave= slave).isError()
|
||||
else:
|
||||
# noinspection PyTypeChecker
|
||||
encoder_resp = self._client.convert_to_registers(value, data_type=data_type, word_order=word_order.value)
|
||||
encoder_resp = _convert_to_registers(value, data_type=data_type, word_order=word_order.value)
|
||||
return self._client.write_registers(self.address, encoder_resp, slave=slave).isError()
|
||||
|
||||
|
||||
@@ -153,8 +280,19 @@ class InputRegister(Base):
|
||||
address = self.address,
|
||||
count = value,
|
||||
slave = slave)
|
||||
|
||||
# 检查是否读取出错
|
||||
if resp.isError():
|
||||
# 根据数据类型返回默认值
|
||||
if data_type in [DataType.FLOAT32, DataType.FLOAT64]:
|
||||
return 0.0, True
|
||||
elif data_type == DataType.STRING:
|
||||
return "", True
|
||||
else:
|
||||
return 0, True
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
return self._client.convert_from_registers(resp.registers, data_type, word_order=word_order.value), resp.isError()
|
||||
return _convert_from_registers(resp.registers, data_type, word_order=word_order.value), False
|
||||
|
||||
def write(self,value: Union[int, float, bool, str, list[bool], list[int], list[float]], data_type: Optional[DataType ]= None, word_order: WorderOrder = WorderOrder.LITTLE, slave = 1) -> bool:
|
||||
raise ValueError('input register only support read')
|
||||
|
||||
@@ -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.
File diff suppressed because it is too large
Load Diff
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,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,332 @@
|
||||
"""
|
||||
配置文件 - 包含所有配置信息和映射关系
|
||||
"""
|
||||
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"
|
||||
}
|
||||
},
|
||||
"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
@@ -0,0 +1,64 @@
|
||||
Name,DataType,InitValue,Comment,Attribute,DeviceType,Address,
|
||||
COIL_SYS_START_CMD,BOOL,,,,coil,9010,
|
||||
COIL_SYS_STOP_CMD,BOOL,,,,coil,9020,
|
||||
COIL_SYS_RESET_CMD,BOOL,,,,coil,9030,
|
||||
COIL_SYS_HAND_CMD,BOOL,,,,coil,9040,
|
||||
COIL_SYS_AUTO_CMD,BOOL,,,,coil,9050,
|
||||
COIL_SYS_INIT_CMD,BOOL,,,,coil,9060,
|
||||
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,9700,
|
||||
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,9710,unilab_rec_msg_succ_cmd
|
||||
COIL_SYS_START_STATUS,BOOL,,,,coil,9210,
|
||||
COIL_SYS_STOP_STATUS,BOOL,,,,coil,9220,
|
||||
COIL_SYS_RESET_STATUS,BOOL,,,,coil,9230,
|
||||
COIL_SYS_HAND_STATUS,BOOL,,,,coil,9240,
|
||||
COIL_SYS_AUTO_STATUS,BOOL,,,,coil,9250,
|
||||
COIL_SYS_INIT_STATUS,BOOL,,,,coil,9260,
|
||||
COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,9500,
|
||||
COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,9510,request_send_msg_status
|
||||
REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,17000,
|
||||
REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,17002,unilab_send_msg_electrolyte_num
|
||||
REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,17004,unilab_send_msg_electrolyte_vol
|
||||
REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,17006,unilab_send_msg_assembly_type
|
||||
REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,17008,unilab_send_msg_assembly_pressure
|
||||
REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,16000,data_assembly_coin_cell_num
|
||||
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,16002,data_open_circuit_voltage
|
||||
REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,16004,
|
||||
REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,16006,
|
||||
REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,16008,
|
||||
REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,16010,data_pole_weight
|
||||
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,16012,data_assembly_time
|
||||
REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,16014,data_assembly_pressure
|
||||
REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,16016,data_electrolyte_volume
|
||||
REG_DATA_COIN_NUM,INT16,,,,hold_register,16018,data_coin_num
|
||||
REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,16020,data_electrolyte_code()
|
||||
REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,16030,data_coin_cell_code()
|
||||
REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,18004,data_stack_vision_code()
|
||||
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,16050,data_glove_box_pressure
|
||||
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,16052,data_glove_box_water_content
|
||||
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,16054,data_glove_box_o2_content
|
||||
UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,9720,
|
||||
UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,9520,
|
||||
REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,17496,
|
||||
REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,16000,
|
||||
UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,9730,
|
||||
UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,9530,
|
||||
REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,16018,ASSEMBLY_TYPE7or8
|
||||
COIL_ALUMINUM_FOIL,BOOL,,使用铝箔垫,,coil,9340,
|
||||
REG_MSG_NE_PLATE_MATRIX,INT16,,负极片矩阵点位,,hold_register,17440,
|
||||
REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,隔膜矩阵点位,,hold_register,17450,
|
||||
REG_MSG_TIP_BOX_MATRIX,INT16,,移液枪头矩阵点位,,hold_register,17480,
|
||||
REG_MSG_NE_PLATE_NUM,INT16,,负极片盘数,,hold_register,17443,
|
||||
REG_MSG_SEPARATOR_PLATE_NUM,INT16,,隔膜盘数,,hold_register,17453,
|
||||
REG_MSG_PRESS_MODE,BOOL,,压制模式(false:压力检测模式,True:距离模式),,coil,9360,电池压制模式
|
||||
,,,,,,,
|
||||
,BOOL,,视觉对位(false:使用,true:忽略),,coil,9300,视觉对位
|
||||
,BOOL,,复检(false:使用,true:忽略),,coil,9310,视觉复检
|
||||
,BOOL,,手套箱_左仓(false:使用,true:忽略),,coil,9320,手套箱左仓
|
||||
,BOOL,,手套箱_右仓(false:使用,true:忽略),,coil,9420,手套箱右仓
|
||||
,BOOL,,真空检知(false:使用,true:忽略),,coil,9350,真空检知
|
||||
,BOOL,,电解液添加模式(false:单次滴液,true:二次滴液),,coil,9370,滴液模式
|
||||
,BOOL,,正极片称重(false:使用,true:忽略),,coil,9380,正极片称重
|
||||
,BOOL,,正负极片组装方式(false:正装,true:倒装),,coil,9390,正负极反装
|
||||
,BOOL,,压制清洁(false:使用,true:忽略),,coil,9400,压制清洁
|
||||
,BOOL,,物料盘摆盘方式(false:水平摆盘,true:堆叠摆盘),,coil,9410,负极片摆盘方式
|
||||
REG_MSG_BATTERY_CLEAN_IGNORE,BOOL,,忽略电池清洁(false:使用,true:忽略),,coil,9460,
|
||||
|
@@ -0,0 +1,159 @@
|
||||
Name,DataType,Comment,DeviceType,Address,,
|
||||
COIL_SYS_START_CMD,BOOL,<EFBFBD>豸<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8010,,
|
||||
COIL_SYS_STOP_CMD,BOOL,<EFBFBD>豸ֹͣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8020,,
|
||||
COIL_SYS_RESET_CMD,BOOL,<EFBFBD>豸<EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8030,,
|
||||
COIL_SYS_HAND_CMD,BOOL,<EFBFBD>豸<EFBFBD>ֶ<EFBFBD>ģʽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8040,,
|
||||
COIL_SYS_AUTO_CMD,BOOL,<EFBFBD>豸<EFBFBD>Զ<EFBFBD>ģʽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8050,,
|
||||
COIL_SYS_INIT_CMD,BOOL,<EFBFBD>豸<EFBFBD><EFBFBD>ʼ<EFBFBD><EFBFBD>ģʽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8060,,
|
||||
COIL_SYS_STOP_STATUS,BOOL,<EFBFBD>豸<EFBFBD><EFBFBD>ͣ<EFBFBD><EFBFBD>,coil,8220,,
|
||||
,,,,,,
|
||||
,BOOL,UNILAB<EFBFBD><EFBFBD><EFBFBD>͵<EFBFBD><EFBFBD><EFBFBD>Һƿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8720,,
|
||||
,BOOL,<EFBFBD>豸<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܵ<EFBFBD><EFBFBD><EFBFBD>Һƿ<EFBFBD><EFBFBD>,coil,8520,,
|
||||
REG_MSG_ELECTROLYTE_NUM,WORD,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һʹ<EFBFBD><EFBFBD>ƿ<EFBFBD><EFBFBD>,hold_register,496,,
|
||||
,WORD,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD>̾<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼλ0<EFBFBD><EFBFBD>,hold_register,440,,
|
||||
,WORD,<EFBFBD><EFBFBD>Ĥ<EFBFBD>̾<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼλ0<EFBFBD><EFBFBD>,hold_register,450,,
|
||||
,WORD,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һƿ<EFBFBD><EFBFBD>_<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ͼ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼλ0<EFBFBD><EFBFBD>,hold_register,460,,
|
||||
,WORD,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һƿ<EFBFBD><EFBFBD>_<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>վ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼλ0<EFBFBD><EFBFBD>,hold_register,430,,
|
||||
,WORD,g_<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һƿ<EFBFBD><EFBFBD>_<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˸<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼλ0<EFBFBD><EFBFBD>,hold_register,470,,
|
||||
,WORD,<EFBFBD><EFBFBD>Һǹͷ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʼλ0<EFBFBD><EFBFBD>,hold_register,480,,
|
||||
,WORD,<EFBFBD><EFBFBD><EFBFBD>ø<EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,443,,
|
||||
,WORD,<EFBFBD><EFBFBD><EFBFBD>ø<EFBFBD>Ĥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,453,,
|
||||
,,,,,,
|
||||
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,UNILAB<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>䷽<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8700,,
|
||||
COIL_REQUEST_REC_MSG_STATUS,BOOL,<EFBFBD>豸<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>䷽,coil,8500,,
|
||||
REG_MSG_ELECTROLYTE_USE_NUM,INT16,<EFBFBD><EFBFBD>ƿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һʹ<EFBFBD>ô<EFBFBD><EFBFBD><EFBFBD>,hold_register,11000,,
|
||||
REG_MSG_ELECTROLYTE_VOLUME,INT16,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һ<EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD>,hold_register,11004,,
|
||||
REG_MSG_ASSEMBLY_PRESSURE,INT16,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,11008,,
|
||||
REG_DATA_ELECTROLYTE_CODE,STRING,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һ<EFBFBD><EFBFBD>ά<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>к<EFBFBD>,hold_register,10020,,
|
||||
,BOOL,<EFBFBD>Ӿ<EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8300,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD>죨false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8310,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><EFBFBD><EFBFBD>֣<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8320,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD>Ҳ֣<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8420,,
|
||||
,BOOL,<EFBFBD><EFBFBD>е<EFBFBD>ְ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̣<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8330,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ϣ<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8340,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD>ռ<EFBFBD>֪<EFBFBD><EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8350,,
|
||||
,BOOL,ѹ<EFBFBD><EFBFBD>ģʽ<EFBFBD><EFBFBD>false:ѹ<><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<C4A3><CABD>True:<3A><><EFBFBD><EFBFBD>ģʽ<C4A3><CABD>,coil,8360,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<EFBFBD><EFBFBD>false:<3A><><EFBFBD>ε<EFBFBD>Һ<EFBFBD><D2BA>true:<3A><><EFBFBD>ε<EFBFBD>Һ<EFBFBD><D2BA>,coil,8370,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD>أ<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8380,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD>װ<EFBFBD><EFBFBD>ʽ<EFBFBD><EFBFBD>false:<3A><>װ<EFBFBD><D7B0>true:<3A><>װ<EFBFBD><D7B0>,coil,8390,,
|
||||
,BOOL,ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ࣨfalse:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8400,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̰<EFBFBD><EFBFBD>̷<EFBFBD>ʽ<EFBFBD><EFBFBD>false:ˮƽ<CBAE><C6BD><EFBFBD>̣<EFBFBD>true:<3A>ѵ<EFBFBD><D1B5><EFBFBD><EFBFBD>̣<EFBFBD>,coil,8410,,
|
||||
COIL_SYS_UNILAB_INTERACT ,BOOL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Unilab<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,coil,8450,,
|
||||
,BOOL,<EFBFBD><EFBFBD><EFBFBD>Ե<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ࣨfalse:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,colil,8460,,
|
||||
,,,,,,
|
||||
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,UNILAB<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>䷽<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8510,,
|
||||
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,UNILAB<EFBFBD><EFBFBD><EFBFBD>ܲ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,8710,,
|
||||
REG_DATA_POLE_WEIGHT,FLOAT32,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,10010,,
|
||||
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD>ŵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װʱ<EFBFBD><EFBFBD>,hold_register,10012,,
|
||||
REG_DATA_ASSEMBLY_PRESSURE,INT16,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,10014,,
|
||||
REG_DATA_ELECTROLYTE_VOLUME,INT16,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һ<EFBFBD><EFBFBD>ע<EFBFBD><EFBFBD>,hold_register,10016,,
|
||||
REG_DATA_ASSEMBLY_TYPE,INT16,<EFBFBD><EFBFBD>װ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD>ѵ<EFBFBD><EFBFBD><EFBFBD>ʽ(7/8),hold_register,10018,,
|
||||
REG_DATA_ELECTROLYTE_CODE,STRING,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һ<EFBFBD><EFBFBD>ά<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>к<EFBFBD>,hold_register,10020,,
|
||||
REG_DATA_COIN_CELL_CODE,STRING,<EFBFBD><EFBFBD><EFBFBD>ض<EFBFBD>ά<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>к<EFBFBD>,hold_register,10030,,
|
||||
REG_DATA_STACK_VISON_CODE,STRING,<EFBFBD><EFBFBD><EFBFBD>϶ѵ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ͼƬ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,10040,,
|
||||
REG_DATA_ELECTROLYTE_USE_NUM,INT16,<EFBFBD><EFBFBD>ǰ<EFBFBD>缫Һ<EFBFBD><EFBFBD>װ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,10000,,
|
||||
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD>ص<EFBFBD>ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,10002,,
|
||||
,INT,<EFBFBD><EFBFBD>е<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD><EFBFBD><EFBFBD>ϼĴ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>1-<2D><><EFBFBD><EFBFBD><EFBFBD>ǡ<EFBFBD>2-<2D><><EFBFBD>桢3-<2D><><EFBFBD><EFBFBD>Ƭ<EFBFBD><C6AC>4-<2D><>Ĥ<EFBFBD><C4A4>5-<2D><><EFBFBD><EFBFBD>Ƭ<EFBFBD><C6AC>6-ƽ<>桢7-<2D><><EFBFBD>桢8-<2D><><EFBFBD><EFBFBD><EFBFBD>ǣ<EFBFBD>,hold_register,10060,,
|
||||
,,,,,,
|
||||
,INT,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,10062,PLC<EFBFBD><EFBFBD>ַ,1223-<2D><><EFBFBD><EFBFBD>
|
||||
,INT,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD>Ĥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,10064,,
|
||||
,INT,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һ״̬<EFBFBD>루R<EFBFBD><EFBFBD>,hold_register,10066,,
|
||||
,REAL,<EFBFBD><EFBFBD>·<EFBFBD><EFBFBD>ѹOK<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,10068,,
|
||||
,REAL,<EFBFBD><EFBFBD>·<EFBFBD><EFBFBD>ѹOK<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ֵ<EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,10070,,
|
||||
,INT,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,10072,,
|
||||
,INT,<EFBFBD><EFBFBD>ǰ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,10074,,
|
||||
,REAL,10mm<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,520,HMI<EFBFBD><EFBFBD>ַ,
|
||||
,REAL,12mm<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,522,,
|
||||
,REAL,16mm<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,524,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,526,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,528,,
|
||||
,REAL,ƽ<EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,530,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,532,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,534,,
|
||||
,REAL,<EFBFBD><EFBFBD>Ʒ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,536,,
|
||||
,REAL,<EFBFBD><EFBFBD>Ʒ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>NG<EFBFBD><EFBFBD>ʣ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>R<EFBFBD><EFBFBD>,hold_register,538,,
|
||||
,,,,,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>10mm<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,540,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>12mm<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,542,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>16mm<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,544,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,546,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ǻ<EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,548,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƽ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,550,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD>ø<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ǻ<EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,552,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD>õ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,554,,
|
||||
,REAL,<EFBFBD><EFBFBD><EFBFBD>ó<EFBFBD>Ʒ<EFBFBD><EFBFBD><EFBFBD>غ<EFBFBD><EFBFBD>ȣ<EFBFBD>W<EFBFBD><EFBFBD>,hold_register,556,,
|
||||
,,,,,,
|
||||
,,,,,,
|
||||
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD>,hold_register,10050,,
|
||||
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ˮ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,10052,,
|
||||
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,hold_register,10054,,
|
||||
,,,,,,
|
||||
,BOOL,<EFBFBD>쳣100-ϵͳ<CFB5>쳣,coil,1000,,
|
||||
,BOOL,<EFBFBD>쳣101-<2D><>ͣ,coil,1010,,
|
||||
,BOOL,<EFBFBD>쳣111-<2D><><EFBFBD><EFBFBD><EFBFBD>伱ͣ,coil,1110,,
|
||||
,BOOL,<EFBFBD>쳣112-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ڹ<EFBFBD>դ<EFBFBD>ڵ<EFBFBD>,coil,1120,,
|
||||
,BOOL,<EFBFBD>쳣160-<2D><>Һǹͷȱ<CDB7><C8B1>,coil,1600,,
|
||||
,BOOL,<EFBFBD>쳣161-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȱ<EFBFBD><C8B1>,coil,1610,,
|
||||
,BOOL,<EFBFBD>쳣162-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȱ<EFBFBD><C8B1>,coil,1620,,
|
||||
,BOOL,<EFBFBD>쳣163-<2D><><EFBFBD><EFBFBD>Ƭȱ<C6AC><C8B1>,coil,1630,,
|
||||
,BOOL,<EFBFBD>쳣164-<2D><>Ĥȱ<C4A4><C8B1>,coil,1640,,
|
||||
,BOOL,<EFBFBD>쳣165-<2D><><EFBFBD><EFBFBD>Ƭȱ<C6AC><C8B1>,coil,1650,,
|
||||
,BOOL,<EFBFBD>쳣166-ƽ<><C6BD>ȱ<EFBFBD><C8B1>,coil,1660,,
|
||||
,BOOL,<EFBFBD>쳣167-<2D><><EFBFBD><EFBFBD>ȱ<EFBFBD><C8B1>,coil,1670,,
|
||||
,BOOL,<EFBFBD>쳣168-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȱ<EFBFBD><C8B1>,coil,1680,,
|
||||
,BOOL,<EFBFBD>쳣169-<2D><>Ʒ<EFBFBD><C6B7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,1690,,
|
||||
,BOOL,<EFBFBD>쳣201-<2D>ŷ<EFBFBD><C5B7><EFBFBD>01<30>쳣,coil,2010,,
|
||||
,BOOL,<EFBFBD>쳣202-<2D>ŷ<EFBFBD><C5B7><EFBFBD>02<30>쳣,coil,2020,,
|
||||
,BOOL,<EFBFBD>쳣203-<2D>ŷ<EFBFBD><C5B7><EFBFBD>03<30>쳣,coil,2030,,
|
||||
,BOOL,<EFBFBD>쳣204-<2D>ŷ<EFBFBD><C5B7><EFBFBD>04<30>쳣,coil,2040,,
|
||||
,BOOL,<EFBFBD>쳣205-<2D>ŷ<EFBFBD><C5B7><EFBFBD>05<30>쳣,coil,2050,,
|
||||
,BOOL,<EFBFBD>쳣206-<2D>ŷ<EFBFBD><C5B7><EFBFBD>06<30>쳣,coil,2060,,
|
||||
,BOOL,<EFBFBD>쳣207-<2D>ŷ<EFBFBD><C5B7><EFBFBD>07<30>쳣,coil,2070,,
|
||||
,BOOL,<EFBFBD>쳣208-<2D>ŷ<EFBFBD><C5B7><EFBFBD>08<30>쳣,coil,2080,,
|
||||
,BOOL,<EFBFBD>쳣209-<2D>ŷ<EFBFBD><C5B7><EFBFBD>09<30>쳣,coil,2090,,
|
||||
,BOOL,<EFBFBD>쳣210-<2D>ŷ<EFBFBD><C5B7><EFBFBD>10<31>쳣,coil,2100,,
|
||||
,BOOL,<EFBFBD>쳣211-<2D>ŷ<EFBFBD><C5B7><EFBFBD>11<31>쳣,coil,2110,,
|
||||
,BOOL,<EFBFBD>쳣212-<2D>ŷ<EFBFBD><C5B7><EFBFBD>12<31>쳣,coil,2120,,
|
||||
,BOOL,<EFBFBD>쳣213-<2D>ŷ<EFBFBD><C5B7><EFBFBD>13<31>쳣,coil,2130,,
|
||||
,BOOL,<EFBFBD>쳣214-<2D>ŷ<EFBFBD><C5B7><EFBFBD>14<31>쳣,coil,2140,,
|
||||
,BOOL,<EFBFBD>쳣250-<2D><><EFBFBD><EFBFBD>Ԫ<EFBFBD><D4AA><EFBFBD>쳣,coil,2500,,
|
||||
,BOOL,<EFBFBD>쳣251-<2D><>ҺǹͨѶ<CDA8>쳣,coil,2510,,
|
||||
,BOOL,<EFBFBD>쳣252-<2D><>Һǹ<D2BA><C7B9><EFBFBD><EFBFBD>,coil,2520,,
|
||||
,BOOL,<EFBFBD>쳣256-<2D><>צ<EFBFBD>쳣,coil,2560,,
|
||||
,BOOL,<EFBFBD>쳣262-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD>δ֪<CEB4><D6AA>λ<EFBFBD><CEBB><EFBFBD><EFBFBD>,coil,2620,,
|
||||
,BOOL,<EFBFBD>쳣263-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD>X<EFBFBD><58>Y<EFBFBD><59>Z<EFBFBD><5A><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,2630,,
|
||||
,BOOL,<EFBFBD>쳣264-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ӿ<EFBFBD><D3BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,coil,2640,,
|
||||
,BOOL,<EFBFBD>쳣265-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD>1#<23><><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1>ʧ<EFBFBD><CAA7>,coil,2650,,
|
||||
,BOOL,<EFBFBD>쳣266-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD>2#<23><><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1>ʧ<EFBFBD><CAA7>,coil,2660,,
|
||||
,BOOL,<EFBFBD>쳣267-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD>3#<23><><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1>ʧ<EFBFBD><CAA7>,coil,2670,,
|
||||
,BOOL,<EFBFBD>쳣268-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD>4#<23><><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1>ʧ<EFBFBD><CAA7>,coil,2680,,
|
||||
,BOOL,<EFBFBD>쳣269-RB<52><42><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʧ<EFBFBD><CAA7>,coil,2690,,
|
||||
,BOOL,<EFBFBD>쳣280-RB<52><42>ײ<EFBFBD>쳣,coil,2800,,
|
||||
,BOOL,<EFBFBD>쳣290-<2D>Ӿ<EFBFBD>ϵͳͨѶ<CDA8>쳣,coil,2900,,
|
||||
,BOOL,<EFBFBD>쳣291-<2D>Ӿ<EFBFBD><D3BE><EFBFBD>λNG<4E>쳣,coil,2910,,
|
||||
,BOOL,<EFBFBD>쳣292-ɨ<><C9A8>ǹͨѶ<CDA8>쳣,coil,2920,,
|
||||
,BOOL,<EFBFBD>쳣310-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3100,,
|
||||
,BOOL,<EFBFBD>쳣311-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3110,,
|
||||
,BOOL,<EFBFBD>쳣312-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3120,,
|
||||
,BOOL,<EFBFBD>쳣313-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3130,,
|
||||
,BOOL,<EFBFBD>쳣340-<2D><>·<EFBFBD><C2B7>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3400,,
|
||||
,BOOL,<EFBFBD>쳣342-<2D><>·<EFBFBD><C2B7>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3420,,
|
||||
,BOOL,<EFBFBD>쳣344-<2D><>·<EFBFBD><C2B7>ѹ<EFBFBD><D1B9>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3440,,
|
||||
,BOOL,<EFBFBD>쳣350-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3500,,
|
||||
,BOOL,<EFBFBD>쳣352-<2D><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3520,,
|
||||
,BOOL,<EFBFBD>쳣354-<2D><>ϴ<EFBFBD><EFBFBD><DEB3><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3540,,
|
||||
,BOOL,<EFBFBD>쳣356-<2D><>ϴ<EFBFBD><EFBFBD><DEB3><EFBFBD>ѹ<EFBFBD><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3560,,
|
||||
,BOOL,<EFBFBD>쳣360-<2D><><EFBFBD><EFBFBD>Һƿ<D2BA><C6BF>λ<EFBFBD><CEBB><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3600,,
|
||||
,BOOL,<EFBFBD>쳣362-<2D><>Һǹͷ<C7B9>ж<EFBFBD>λ<EFBFBD><CEBB><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3620,,
|
||||
COIL ALARM_364_SERVO_DRIVE_ERROR,BOOL,<EFBFBD>쳣364-<2D>Լ<EFBFBD>ƿ<EFBFBD><C6BF>צ<EFBFBD><D7A6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3640,,
|
||||
COIL ALARM_367_SERVO_DRIVER_ERROR,BOOL,<EFBFBD>쳣366-<2D>Լ<EFBFBD>ƿ<EFBFBD><C6BF>צ<EFBFBD><D7A6><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3660,,
|
||||
COIL ALARM_370_SERVO_MODULE_ERROR,BOOL,<EFBFBD>쳣370-ѹ<><D1B9>ģ<EFBFBD>鴵<EFBFBD><E9B4B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>쳣,coil,3700,,
|
||||
,,,,,,
|
||||
,,,,,,
|
||||
,,,,,,
|
||||
,,,,,,
|
||||
,,,,,,
|
||||
,,,,,,
|
||||
,,,,,,
|
||||
COIL + <20><><EFBFBD><EFBFBD>ģ<EFBFBD><C4A3>/<2F><><EFBFBD><EFBFBD> + <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>д+<2B>»<EFBFBD><C2BB>߷ָ<DFB7><D6B8><EFBFBD>--<2D><><EFBFBD><EFBFBD>boolֵ,,,,,,
|
||||
REG + <20><><EFBFBD><EFBFBD>ģ<EFBFBD><C4A3>/<2F><><EFBFBD><EFBFBD> + <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>д+<2B>»<EFBFBD><C2BB>߷ָ<DFB7><D6B8><EFBFBD>--<2D><><EFBFBD>ԼĴ<D4BC><C4B4><EFBFBD>,,,,,,
|
||||
|
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,2 @@
|
||||
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
|
||||
20251224_172304,-5.537573695435827e-37,-48.45097351074219,1.372190511464448e+16,3820,30,7,b'\x00\x00d\x00eaoR',b'\x00\x00\x01\x00\x00\x00\r\n'
|
||||
|
@@ -0,0 +1,2 @@
|
||||
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
|
||||
20251225_105600,5.566961054206384e-37,-53149746331648.0,3271557120.0,3658,10,7,b'\x00\x00d\x00eaoR',b'\x00\x00\x01\x00\x00\x00\r\n'
|
||||
|
@@ -0,0 +1,2 @@
|
||||
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
|
||||
20251229_161836,-5.537573695435827e-37,8.919000478163591e+20,-3.806253867691382e-29,3544,20,7,b'\x00\x00d\x00eaoR',b'\x00\x00\x01\x00\x00\x00\r\n'
|
||||
|
@@ -0,0 +1,9 @@
|
||||
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
|
||||
20251230_182319,0.01600000075995922,13.899999618530273,175.0,3836,20,7,b'\x00\x00d\x00eaoR',b'\x00\x00\x01\x00\x00\x00\r\n'
|
||||
20251230_185306,0.01600000075995922,13.639999389648438,625.0,3819,20,7,deaoR,
|
||||
20251230_192124,0.0,8.949999809265137,414.0,3803,20,8,deaoR,
|
||||
20251230_195621,3.8359999656677246,10.069999694824219,205.0,3350,20,8,LG600001,19311909
|
||||
20251230_200830,0.7929999828338623,9.34999942779541,18.0,3318,20,8,LG600001,19533419
|
||||
20251230_201123,0.0,9.169999122619629,17.0,3269,20,8,LG600001,20054389
|
||||
20251230_201410,0.0,9.569999694824219,18.0,3237,20,8,LG600001,YS102704
|
||||
20251230_201659,0.0,9.699999809265137,169.0,3318,20,8,LG600001,20112754
|
||||
|
@@ -0,0 +1,3 @@
|
||||
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
|
||||
20260106_221708,0.03200000151991844,26.26999855041504,18.0,3803,30,7,NoRead88,22000063
|
||||
20260106_221957,0.11299999803304672,26.26999855041504,170.0,3787,30,7,LG600001,22124813
|
||||
|
@@ -0,0 +1,536 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""扣式电池组装系统 - 交互式CSV导出演示脚本(增强版)
|
||||
|
||||
此脚本专为交互式使用优化,提供清洁的命令行界面,
|
||||
禁用了所有调试信息输出,确保用户可以顺畅地输入命令。
|
||||
|
||||
主要功能:
|
||||
1. 手动导出设备数据到CSV文件(包含6个关键数据字段)
|
||||
2. 查看CSV文件内容和导出状态
|
||||
3. 兼容原有的电池组装完成状态自动导出功能
|
||||
4. 实时查看设备数据和电池数量
|
||||
|
||||
数据字段:
|
||||
- timestamp: 时间戳
|
||||
- assembly_time: 单颗电池组装时间(秒)
|
||||
- open_circuit_voltage: 开路电压值(V)
|
||||
- pole_weight: 正极片称重数据(g)
|
||||
- battery_qr_code: 电池二维码序列号
|
||||
- electrolyte_qr_code: 电解液二维码序列号
|
||||
|
||||
使用方法:
|
||||
1. 确保设备已连接并可正常通信
|
||||
2. 运行此脚本: python interactive_battery_export_demo.py
|
||||
3. 使用交互式命令控制导出功能
|
||||
"""
|
||||
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import csv
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
# 完全禁用所有调试和信息级别的日志输出
|
||||
logging.getLogger().setLevel(logging.CRITICAL)
|
||||
logging.getLogger('pymodbus').setLevel(logging.CRITICAL)
|
||||
logging.getLogger('unilabos').setLevel(logging.CRITICAL)
|
||||
logging.getLogger('pymodbus.logging').setLevel(logging.CRITICAL)
|
||||
logging.getLogger('pymodbus.logging.tcp').setLevel(logging.CRITICAL)
|
||||
logging.getLogger('pymodbus.logging.base').setLevel(logging.CRITICAL)
|
||||
logging.getLogger('pymodbus.logging.decoders').setLevel(logging.CRITICAL)
|
||||
|
||||
# 添加当前目录到Python路径,以便正确导入模块
|
||||
current_dir = Path(__file__).parent
|
||||
sys.path.insert(0, str(current_dir.parent.parent.parent)) # 添加unilabos根目录
|
||||
sys.path.insert(0, str(current_dir)) # 添加当前目录
|
||||
|
||||
# 导入扣式电池组装系统
|
||||
try:
|
||||
from unilabos.devices.coin_cell_assembly.coin_cell_assembly_system import Coin_Cell_Assembly
|
||||
except ImportError:
|
||||
# 如果上述导入失败,尝试直接导入
|
||||
try:
|
||||
from coin_cell_assembly_system import Coin_Cell_Assembly
|
||||
except ImportError as e:
|
||||
print(f"导入错误: {e}")
|
||||
print("请确保在正确的目录下运行此脚本,或者将unilabos添加到Python路径中")
|
||||
sys.exit(1)
|
||||
|
||||
def clear_screen():
|
||||
"""清屏函数"""
|
||||
os.system('cls' if os.name == 'nt' else 'clear')
|
||||
|
||||
def print_header():
|
||||
"""打印程序头部信息"""
|
||||
print("="*60)
|
||||
print(" 扣式电池组装系统 - 交互式CSV导出控制台")
|
||||
print("="*60)
|
||||
print()
|
||||
|
||||
def print_commands():
|
||||
"""打印可用命令"""
|
||||
print("可用命令:")
|
||||
print(" start - 启动电池组装完成状态导出")
|
||||
print(" stop - 停止导出")
|
||||
print(" status - 查看导出状态")
|
||||
print(" data - 查看当前设备数据")
|
||||
print(" count - 查看当前电池数量")
|
||||
print(" export - 手动导出当前数据到CSV")
|
||||
print(" setpath - 设置自定义CSV文件路径")
|
||||
print(" view - 查看CSV文件内容")
|
||||
print(" force - 强制继续CSV导出(即使设备停止)")
|
||||
print(" detail - 显示详细设备状态")
|
||||
print(" clear - 清屏")
|
||||
print(" help - 显示帮助信息")
|
||||
print(" quit - 退出程序")
|
||||
print("-"*60)
|
||||
|
||||
def print_status_info(device, csv_file_path):
|
||||
"""打印状态信息"""
|
||||
try:
|
||||
status = device.get_csv_export_status()
|
||||
is_running = status.get('running', False)
|
||||
export_file = status.get('file_path', None)
|
||||
thread_alive = status.get('thread_alive', False)
|
||||
device_status = status.get('device_status', 'N/A')
|
||||
battery_count = status.get('battery_count', 'N/A')
|
||||
|
||||
print(f"导出状态: {'运行中' if is_running else '已停止'}")
|
||||
print(f"导出文件: {export_file if export_file else 'N/A'}")
|
||||
print(f"线程状态: {'活跃' if thread_alive else '非活跃'}")
|
||||
print(f"设备状态: {device_status}")
|
||||
print(f"电池计数: {battery_count}")
|
||||
|
||||
# 检查手动导出的CSV文件
|
||||
if os.path.exists(csv_file_path):
|
||||
file_size = os.path.getsize(csv_file_path)
|
||||
print(f"手动导出文件: {csv_file_path} ({file_size} 字节)")
|
||||
else:
|
||||
print(f"手动导出文件: {csv_file_path} (不存在)")
|
||||
|
||||
# 显示设备运行状态
|
||||
try:
|
||||
print("\n=== 设备运行状态 ===")
|
||||
print(f"系统启动状态: {device.sys_start_status}")
|
||||
print(f"系统停止状态: {device.sys_stop_status}")
|
||||
print(f"自动模式状态: {device.sys_auto_status}")
|
||||
print(f"手动模式状态: {device.sys_hand_status}")
|
||||
except Exception as e:
|
||||
print(f"获取设备运行状态失败: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取状态失败: {e}")
|
||||
|
||||
def collect_device_data(device):
|
||||
"""收集设备的六个关键数据"""
|
||||
try:
|
||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 读取各项数据,添加错误处理和重试机制
|
||||
try:
|
||||
assembly_time = device.data_assembly_time # 单颗电池组装时间(秒)
|
||||
# 确保返回的是数值类型
|
||||
if isinstance(assembly_time, (list, tuple)) and len(assembly_time) > 0:
|
||||
assembly_time = float(assembly_time[0])
|
||||
else:
|
||||
assembly_time = float(assembly_time)
|
||||
except Exception as e:
|
||||
print(f"读取组装时间失败: {e}")
|
||||
assembly_time = 0.0
|
||||
|
||||
try:
|
||||
open_circuit_voltage = device.data_open_circuit_voltage # 开路电压值(V)
|
||||
# 确保返回的是数值类型
|
||||
if isinstance(open_circuit_voltage, (list, tuple)) and len(open_circuit_voltage) > 0:
|
||||
open_circuit_voltage = float(open_circuit_voltage[0])
|
||||
else:
|
||||
open_circuit_voltage = float(open_circuit_voltage)
|
||||
except Exception as e:
|
||||
print(f"读取开路电压失败: {e}")
|
||||
open_circuit_voltage = 0.0
|
||||
|
||||
try:
|
||||
pole_weight = device.data_pole_weight # 正极片称重数据(g)
|
||||
# 确保返回的是数值类型
|
||||
if isinstance(pole_weight, (list, tuple)) and len(pole_weight) > 0:
|
||||
pole_weight = float(pole_weight[0])
|
||||
else:
|
||||
pole_weight = float(pole_weight)
|
||||
except Exception as e:
|
||||
print(f"读取正极片重量失败: {e}")
|
||||
pole_weight = 0.0
|
||||
|
||||
try:
|
||||
assembly_pressure = device.data_assembly_pressure # 电池压制力(N)
|
||||
# 确保返回的是数值类型
|
||||
if isinstance(assembly_pressure, (list, tuple)) and len(assembly_pressure) > 0:
|
||||
assembly_pressure = int(assembly_pressure[0])
|
||||
else:
|
||||
assembly_pressure = int(assembly_pressure)
|
||||
except Exception as e:
|
||||
print(f"读取压制力失败: {e}")
|
||||
assembly_pressure = 0
|
||||
|
||||
try:
|
||||
battery_qr_code = device.data_coin_cell_code # 电池二维码序列号
|
||||
# 处理字符串类型数据
|
||||
if isinstance(battery_qr_code, str):
|
||||
battery_qr_code = battery_qr_code.strip()
|
||||
else:
|
||||
battery_qr_code = str(battery_qr_code)
|
||||
except Exception as e:
|
||||
print(f"读取电池二维码失败: {e}")
|
||||
battery_qr_code = "N/A"
|
||||
|
||||
try:
|
||||
electrolyte_qr_code = device.data_electrolyte_code # 电解液二维码序列号
|
||||
# 处理字符串类型数据
|
||||
if isinstance(electrolyte_qr_code, str):
|
||||
electrolyte_qr_code = electrolyte_qr_code.strip()
|
||||
else:
|
||||
electrolyte_qr_code = str(electrolyte_qr_code)
|
||||
except Exception as e:
|
||||
print(f"读取电解液二维码失败: {e}")
|
||||
electrolyte_qr_code = "N/A"
|
||||
|
||||
# 获取电池数量
|
||||
try:
|
||||
battery_count = device.data_assembly_coin_cell_num
|
||||
# 确保返回的是数值类型
|
||||
if isinstance(battery_count, (list, tuple)) and len(battery_count) > 0:
|
||||
battery_count = int(battery_count[0])
|
||||
else:
|
||||
battery_count = int(battery_count)
|
||||
except Exception as e:
|
||||
print(f"读取电池数量失败: {e}")
|
||||
battery_count = 0
|
||||
|
||||
return {
|
||||
'Timestamp': timestamp,
|
||||
'Battery_Count': battery_count,
|
||||
'Assembly_Time': assembly_time,
|
||||
'Open_Circuit_Voltage': open_circuit_voltage,
|
||||
'Pole_Weight': pole_weight,
|
||||
'Assembly_Pressure': assembly_pressure,
|
||||
'Battery_Code': battery_qr_code,
|
||||
'Electrolyte_Code': electrolyte_qr_code
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"收集数据时出错: {e}")
|
||||
return None
|
||||
|
||||
def export_to_csv(data, csv_file_path):
|
||||
"""将数据导出到CSV文件"""
|
||||
try:
|
||||
# 检查文件是否存在,如果不存在则创建并写入表头
|
||||
file_exists = os.path.exists(csv_file_path)
|
||||
|
||||
# 确保目录存在
|
||||
csv_dir = os.path.dirname(csv_file_path)
|
||||
if csv_dir:
|
||||
os.makedirs(csv_dir, exist_ok=True)
|
||||
|
||||
# 确保数值字段为正确的数值类型,避免前导单引号问题
|
||||
processed_data = data.copy()
|
||||
|
||||
# 处理数值字段,确保它们是数值类型而不是字符串,增强错误处理
|
||||
numeric_fields = ['Battery_Count', 'Assembly_Time', 'Open_Circuit_Voltage', 'Pole_Weight', 'Assembly_Pressure']
|
||||
for field in numeric_fields:
|
||||
if field in processed_data:
|
||||
try:
|
||||
value = processed_data[field]
|
||||
# 处理可能的列表或元组类型
|
||||
if isinstance(value, (list, tuple)) and len(value) > 0:
|
||||
value = value[0]
|
||||
|
||||
if field == 'Battery_Count' or field == 'Assembly_Pressure':
|
||||
processed_data[field] = int(float(value)) # 先转float再转int,处理字符串数字
|
||||
else:
|
||||
processed_data[field] = float(value)
|
||||
except (ValueError, TypeError, IndexError) as e:
|
||||
print(f"字段 {field} 类型转换失败: {e}, 使用默认值")
|
||||
processed_data[field] = 0 if field == 'Battery_Count' else 0.0
|
||||
|
||||
# 处理字符串字段
|
||||
for field in ['Battery_Code', 'Electrolyte_Code']:
|
||||
if field in processed_data:
|
||||
try:
|
||||
value = processed_data[field]
|
||||
if isinstance(value, (list, tuple)) and len(value) > 0:
|
||||
value = value[0]
|
||||
processed_data[field] = str(value).strip()
|
||||
except Exception as e:
|
||||
print(f"字段 {field} 处理失败: {e}, 使用默认值")
|
||||
processed_data[field] = "N/A"
|
||||
|
||||
with open(csv_file_path, 'a', newline='', encoding='utf-8') as csvfile:
|
||||
fieldnames = ['Timestamp', 'Battery_Count', 'Assembly_Time', 'Open_Circuit_Voltage',
|
||||
'Pole_Weight', 'Assembly_Pressure', 'Battery_QR_Code', 'Electrolyte_QR_Code']
|
||||
writer = csv.DictWriter(csvfile, fieldnames=fieldnames, quoting=csv.QUOTE_MINIMAL)
|
||||
|
||||
# 如果文件不存在,写入表头
|
||||
if not file_exists:
|
||||
writer.writeheader()
|
||||
print(f"创建新的CSV文件: {csv_file_path}")
|
||||
|
||||
# 写入数据
|
||||
writer.writerow(processed_data)
|
||||
print(f"数据已导出到: {csv_file_path}")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"导出CSV时出错: {e}")
|
||||
return False
|
||||
|
||||
def view_csv_content(csv_file_path, lines=10):
|
||||
"""查看CSV文件内容"""
|
||||
try:
|
||||
if not os.path.exists(csv_file_path):
|
||||
print("CSV文件不存在")
|
||||
return
|
||||
|
||||
with open(csv_file_path, 'r', encoding='utf-8') as csvfile:
|
||||
content = csvfile.readlines()
|
||||
|
||||
if not content:
|
||||
print("CSV文件为空")
|
||||
return
|
||||
|
||||
print(f"CSV文件内容 (显示最后{min(lines, len(content))}行):")
|
||||
print("-" * 80)
|
||||
|
||||
# 显示表头
|
||||
if len(content) > 0:
|
||||
print(content[0].strip())
|
||||
print("-" * 80)
|
||||
|
||||
# 显示最后几行数据
|
||||
start_line = max(1, len(content) - lines + 1)
|
||||
for i in range(start_line, len(content)):
|
||||
print(content[i].strip())
|
||||
|
||||
print("-" * 80)
|
||||
print(f"总共 {len(content)-1} 条数据记录")
|
||||
|
||||
except Exception as e:
|
||||
print(f"读取CSV文件时出错: {e}")
|
||||
|
||||
def interactive_demo():
|
||||
"""
|
||||
交互式演示模式(优化版)
|
||||
"""
|
||||
clear_screen()
|
||||
print_header()
|
||||
|
||||
print("正在初始化设备连接...")
|
||||
print("设备地址: 192.168.1.20:502")
|
||||
print("正在尝试连接...")
|
||||
|
||||
try:
|
||||
device = Coin_Cell_Assembly(address="192.168.1.20", port="502")
|
||||
print("✓ 设备连接成功")
|
||||
|
||||
# 测试设备数据读取
|
||||
print("正在测试设备数据读取...")
|
||||
try:
|
||||
test_count = device.data_assembly_coin_cell_num
|
||||
print(f"✓ 当前电池数量: {test_count}")
|
||||
except Exception as e:
|
||||
print(f"⚠ 数据读取测试失败: {e}")
|
||||
print("设备连接正常,但数据读取可能存在问题")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 设备连接失败: {e}")
|
||||
print("请检查以下项目:")
|
||||
print("1. 设备是否已开机并正常运行")
|
||||
print("2. 网络连接是否正常")
|
||||
print("3. 设备IP地址是否为192.168.1.20")
|
||||
print("4. Modbus服务是否在端口502上运行")
|
||||
input("按回车键退出...")
|
||||
return
|
||||
|
||||
csv_file_path = "battery_data_export.csv"
|
||||
print(f"CSV文件路径: {os.path.abspath(csv_file_path)}")
|
||||
print()
|
||||
print("功能说明:")
|
||||
print("- 支持手动导出当前设备数据到CSV文件")
|
||||
print("- 包含六个关键数据: 组装时间、开路电压、正极片重量、电池码、电解液码")
|
||||
print("- 电池码和电解液码可能显示为N/A(当二维码读取失败时)")
|
||||
print("- 支持查看CSV文件内容和导出状态")
|
||||
print("- 兼容原有的电池组装完成状态自动导出功能")
|
||||
print()
|
||||
|
||||
print_commands()
|
||||
|
||||
while True:
|
||||
try:
|
||||
command = input("\n请输入命令 > ").strip().lower()
|
||||
|
||||
if command == "start":
|
||||
print("启动电池组装完成状态导出...")
|
||||
try:
|
||||
success, message = device.start_battery_completion_export(csv_file_path)
|
||||
if success:
|
||||
print(f"✓ {message}")
|
||||
print("系统正在监控电池组装完成状态...")
|
||||
else:
|
||||
print(f"✗ {message}")
|
||||
except Exception as e:
|
||||
print(f"启动导出时出错: {e}")
|
||||
|
||||
elif command == "stop":
|
||||
print("停止导出...")
|
||||
try:
|
||||
success, message = device.stop_csv_export()
|
||||
if success:
|
||||
print(f"✓ {message}")
|
||||
else:
|
||||
print(f"✗ {message}")
|
||||
except Exception as e:
|
||||
print(f"停止导出时出错: {e}")
|
||||
|
||||
elif command == "force":
|
||||
print("强制继续CSV导出...")
|
||||
try:
|
||||
success, message = device.force_continue_csv_export()
|
||||
if success:
|
||||
print(f"✓ {message}")
|
||||
print("注意: CSV导出将继续监控数据变化,即使设备处于停止状态")
|
||||
else:
|
||||
print(f"✗ {message}")
|
||||
except AttributeError:
|
||||
print("✗ 当前版本不支持强制继续功能")
|
||||
except Exception as e:
|
||||
print(f"✗ 强制继续失败: {e}")
|
||||
|
||||
elif command == "detail":
|
||||
print("=== 详细设备状态 ===")
|
||||
print_status_info(device, csv_file_path)
|
||||
|
||||
elif command == "status":
|
||||
print_status_info(device, csv_file_path)
|
||||
|
||||
elif command == "data":
|
||||
print("读取当前设备数据...")
|
||||
try:
|
||||
data = collect_device_data(device)
|
||||
if data:
|
||||
print("\n=== 当前设备数据 ===")
|
||||
print(f"时间戳: {data['Timestamp']}")
|
||||
print(f"电池数量: {data['Battery_Count']}")
|
||||
print(f"单颗电池组装时间: {data['Assembly_Time']:.2f} 秒")
|
||||
print(f"开路电压值: {data['Open_Circuit_Voltage']:.4f} V")
|
||||
print(f"正极片称重数据: {data['Pole_Weight']:.4f} g")
|
||||
print(f"电池压制力: {data['Assembly_Pressure']} N")
|
||||
print(f"电池二维码序列号: {data['Battery_Code']}")
|
||||
print(f"电解液二维码序列号: {data['Electrolyte_Code']}")
|
||||
print("===================")
|
||||
else:
|
||||
print("无法获取设备数据")
|
||||
except Exception as e:
|
||||
print(f"读取数据时出错: {e}")
|
||||
|
||||
elif command == "count":
|
||||
print("读取当前电池数量...")
|
||||
try:
|
||||
count = device.data_assembly_coin_cell_num
|
||||
print(f"当前已完成电池数量: {count}")
|
||||
except Exception as e:
|
||||
print(f"读取电池数量时出错: {e}")
|
||||
|
||||
elif command == "export":
|
||||
print("正在收集设备数据并导出到CSV...")
|
||||
data = collect_device_data(device)
|
||||
if data:
|
||||
print(f"收集到数据: 电池数量={data.get('Battery_Count', 'N/A')}, 组装时间={data.get('Assembly_Time', 'N/A')}s")
|
||||
if export_to_csv(data, csv_file_path):
|
||||
print("✓ 数据已成功导出到CSV文件")
|
||||
print(f"导出数据: 时间={data['Timestamp']}, 电池数量={data['Battery_Count']}, 组装时间={data['Assembly_Time']}秒, "
|
||||
f"电压={data['Open_Circuit_Voltage']}V, 重量={data['Pole_Weight']}g, 压制力={data['Assembly_Pressure']}N")
|
||||
print(f"电池码={data['Battery_Code']}, 电解液码={data['Electrolyte_Code']}")
|
||||
else:
|
||||
print("✗ 导出失败")
|
||||
else:
|
||||
print("✗ 数据收集失败,无法导出!请检查设备连接状态。")
|
||||
# 尝试重新连接设备
|
||||
try:
|
||||
if hasattr(device, 'connect'):
|
||||
device.connect()
|
||||
print("尝试重新连接设备...")
|
||||
except Exception as e:
|
||||
print(f"重新连接失败: {e}")
|
||||
|
||||
elif command == "setpath":
|
||||
print("设置自定义CSV文件路径")
|
||||
print(f"当前CSV文件路径: {csv_file_path}")
|
||||
new_path = input("请输入新的CSV文件路径(包含文件名,如: D:/data/my_battery_data.csv): ").strip()
|
||||
if new_path:
|
||||
try:
|
||||
# 确保目录存在
|
||||
new_dir = os.path.dirname(new_path)
|
||||
if new_dir and not os.path.exists(new_dir):
|
||||
os.makedirs(new_dir, exist_ok=True)
|
||||
print(f"✓ 已创建目录: {new_dir}")
|
||||
|
||||
csv_file_path = new_path
|
||||
print(f"✓ CSV文件路径已更新为: {os.path.abspath(csv_file_path)}")
|
||||
|
||||
# 检查文件是否存在
|
||||
if os.path.exists(csv_file_path):
|
||||
file_size = os.path.getsize(csv_file_path)
|
||||
print(f"文件已存在,大小: {file_size} 字节")
|
||||
else:
|
||||
print("文件不存在,将在首次导出时创建")
|
||||
except Exception as e:
|
||||
print(f"✗ 设置路径失败: {e}")
|
||||
else:
|
||||
print("路径不能为空")
|
||||
|
||||
elif command == "view":
|
||||
print("查看CSV文件内容...")
|
||||
view_csv_content(csv_file_path)
|
||||
|
||||
elif command == "clear":
|
||||
clear_screen()
|
||||
print_header()
|
||||
print_commands()
|
||||
|
||||
elif command == "help":
|
||||
print_commands()
|
||||
|
||||
elif command == "quit" or command == "exit":
|
||||
print("正在退出...")
|
||||
# 停止导出
|
||||
try:
|
||||
device.stop_csv_export()
|
||||
print("✓ 导出已停止")
|
||||
except:
|
||||
pass
|
||||
print("程序已退出")
|
||||
break
|
||||
|
||||
elif command == "":
|
||||
# 空命令,不做任何操作
|
||||
continue
|
||||
|
||||
else:
|
||||
print(f"未知命令: {command}")
|
||||
print("输入 'help' 查看可用命令")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n检测到 Ctrl+C,正在退出...")
|
||||
try:
|
||||
device.stop_csv_export()
|
||||
print("✓ 导出已停止")
|
||||
except:
|
||||
pass
|
||||
print("程序已退出")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"执行命令时出错: {e}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
interactive_demo()
|
||||
@@ -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
|
||||
749
unilabos/registry/devices/bioyond_cell.yaml
Normal file
749
unilabos/registry/devices/bioyond_cell.yaml
Normal file
@@ -0,0 +1,749 @@
|
||||
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_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_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
|
||||
{}
|
||||
|
||||
726
unilabos/registry/devices/coin_cell_workstation.yaml
Normal file
726
unilabos/registry/devices/coin_cell_workstation.yaml
Normal file
@@ -0,0 +1,726 @@
|
||||
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: 负极片矩阵点位
|
||||
type: integer
|
||||
fujipian_panshu:
|
||||
default: 0
|
||||
description: 负极片盘数
|
||||
type: integer
|
||||
gemo_juzhendianwei:
|
||||
default: 0
|
||||
description: 隔膜矩阵点位
|
||||
type: integer
|
||||
gemopanshu:
|
||||
default: 0
|
||||
description: 隔膜盘数
|
||||
type: integer
|
||||
lvbodian:
|
||||
default: true
|
||||
description: 是否使用铝箔垫片
|
||||
type: boolean
|
||||
qiangtou_juzhendianwei:
|
||||
default: 0
|
||||
description: 枪头盒矩阵点位
|
||||
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_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
|
||||
|
||||
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",
|
||||
)
|
||||
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",
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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