mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-11 10:15:14 +00:00
Compare commits
163 Commits
a25e8f6853
...
workstatio
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f355722281 | ||
|
|
936834f8c3 | ||
|
|
915a6a04c3 | ||
|
|
48b51c3a4a | ||
|
|
acef0b8ca2 | ||
|
|
d2a30fe33b | ||
|
|
096875e910 | ||
|
|
2e17dee121 | ||
|
|
c03abb341a | ||
|
|
ee4ed26846 | ||
|
|
b97be6a5d4 | ||
|
|
44f830cf00 | ||
|
|
04b578a68b | ||
|
|
19dffcb5db | ||
|
|
b441362cd2 | ||
|
|
ed53ef2f64 | ||
|
|
0c9f26e8fc | ||
|
|
39a799cabd | ||
|
|
0d64563fb6 | ||
|
|
fbb9e0963d | ||
|
|
af411ddfe6 | ||
|
|
f5dbcb1bfc | ||
|
|
1ecf89ea27 | ||
|
|
6efdf6e5a6 | ||
|
|
e32dc55db0 | ||
|
|
acc45b716d | ||
|
|
017eaefb8d | ||
|
|
9e8c692702 | ||
|
|
beb90f20d2 | ||
|
|
7a284069d2 | ||
|
|
4a2d862333 | ||
|
|
538891fcbe | ||
|
|
a0e92b8e9b | ||
|
|
1d77225912 | ||
|
|
06e6ab0b7f | ||
|
|
5399c6c1cf | ||
|
|
f872d3ef56 | ||
|
|
85c6f4e688 | ||
|
|
442b759397 | ||
|
|
47ecb154c8 | ||
|
|
be429147c0 | ||
|
|
123c69e97a | ||
|
|
04004c9b6f | ||
|
|
45a778b928 | ||
|
|
c44ae32070 | ||
|
|
7af32b379b | ||
|
|
48d429ae00 | ||
|
|
9bba4620b7 | ||
|
|
d7494ca458 | ||
|
|
85dc46cd38 | ||
|
|
5a0c2f9850 | ||
|
|
d897d70c3e | ||
|
|
d9dffc6bf8 | ||
|
|
30b202bea0 | ||
|
|
1b2c0dbcd7 | ||
|
|
0f341e9b4d | ||
|
|
4c3972820b | ||
|
|
a2a8ee9088 | ||
|
|
200105f647 | ||
|
|
8b5653d801 | ||
|
|
5f859917d4 | ||
|
|
af2fb7f34a | ||
|
|
baa107c230 | ||
|
|
83854a741d | ||
|
|
86c7880b5c | ||
|
|
6d934e354c | ||
|
|
bed453034f | ||
|
|
5331d7bfba | ||
|
|
38ab7d3e78 | ||
|
|
966b51042d | ||
|
|
d81638e20b | ||
|
|
3c583008aa | ||
|
|
9a85bfddcd | ||
|
|
d4e1286df7 | ||
|
|
765038a136 | ||
|
|
1d4e4c8377 | ||
|
|
54f749bcdb | ||
|
|
16ad4bbecc | ||
|
|
0ad2eaafea | ||
|
|
1477384c1a | ||
|
|
8149a175d9 | ||
|
|
bfd415279b | ||
|
|
0238a92e75 | ||
|
|
8009956326 | ||
|
|
68fc4dd61e | ||
|
|
cd12932788 | ||
|
|
f230028558 | ||
|
|
1c1a6b16c8 | ||
|
|
a2d6012080 | ||
|
|
10adc853a5 | ||
|
|
69ec034623 | ||
|
|
62d08aa954 | ||
|
|
4485907df8 | ||
|
|
b5b2358967 | ||
|
|
11f4f44bf9 | ||
|
|
f52fbd650e | ||
|
|
e561c818b8 | ||
|
|
5cbd880e5a | ||
|
|
41e7251f62 | ||
|
|
727d2c2595 | ||
|
|
202a2667fd | ||
|
|
03745c5d08 | ||
|
|
385a495e21 | ||
|
|
91513a5f4c | ||
|
|
a62896eda2 | ||
|
|
a82d1b7bdb | ||
|
|
6d7c39da9e | ||
|
|
d8e9ad4413 | ||
|
|
eb93b83415 | ||
|
|
6df93a5db7 | ||
|
|
2eb9986edb | ||
|
|
fe4e49e56d | ||
|
|
0fba4cf275 | ||
|
|
ef9359776a | ||
|
|
954f1ee7b2 | ||
|
|
f58921ef82 | ||
|
|
95bdd39bf8 | ||
|
|
b3e28196c6 | ||
|
|
9fe8f4f28f | ||
|
|
39bc317bfc | ||
|
|
a130c03ebd | ||
|
|
a97781c4eb | ||
|
|
c35edcece1 | ||
|
|
524e0f3053 | ||
|
|
66f483929d | ||
|
|
2d58576937 | ||
|
|
ff25e814de | ||
|
|
0163d16cbb | ||
|
|
3231d60646 | ||
|
|
d0279f63f0 | ||
|
|
ceef342860 | ||
|
|
42f7010134 | ||
|
|
190b2d2518 | ||
|
|
2901d72b4b | ||
|
|
6ad0157b50 | ||
|
|
55b678cd37 | ||
|
|
8101a22a0f | ||
|
|
667138baac | ||
|
|
01adf7ca92 | ||
|
|
f606062696 | ||
|
|
67d1c4acce | ||
|
|
7206e42bf1 | ||
|
|
e92d933968 | ||
|
|
f0ebcc60bb | ||
|
|
e2097f0b22 | ||
|
|
fd73731130 | ||
|
|
ab7f2081c9 | ||
|
|
9e850d8a81 | ||
|
|
1af6ffafc6 | ||
|
|
35fc2f5ea6 | ||
|
|
d3d8ba6500 | ||
|
|
5a7845d8ca | ||
|
|
9c4d0256cf | ||
|
|
de7c80c3c2 | ||
|
|
e70c545ec8 | ||
|
|
2c2d1e5569 | ||
|
|
4638611fe7 | ||
|
|
37641c4389 | ||
|
|
ab697ce973 | ||
|
|
d4724b8664 | ||
|
|
2f25063bf1 | ||
|
|
00b4b9cd87 | ||
|
|
d2352cc514 |
@@ -1,31 +1,32 @@
|
|||||||
{
|
{
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"id": "BatteryStation",
|
"id": "bioyond_cell_workstation",
|
||||||
"name": "扣电工作站",
|
"name": "配液分液工站",
|
||||||
"children": [
|
"children": [
|
||||||
"coin_cell_deck"
|
|
||||||
],
|
],
|
||||||
"parent": null,
|
"parent": null,
|
||||||
"type": "device",
|
"type": "device",
|
||||||
"class": "bettery_station_registry",
|
"class": "bioyond_cell",
|
||||||
"position": {
|
"config": {
|
||||||
"x": 600,
|
"protocol_type": [],
|
||||||
"y": 400,
|
"station_resource": {}
|
||||||
"z": 0
|
|
||||||
},
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "BatteryStation",
|
||||||
|
"name": "扣电组装工作站",
|
||||||
|
"children": [],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "bettery_station_registry",
|
||||||
"config": {
|
"config": {
|
||||||
"debug_mode": false,
|
"debug_mode": false,
|
||||||
"_comment": "protocol_type接外部工站固定写法字段,一般为空,deck写法也固定",
|
|
||||||
"protocol_type": [],
|
"protocol_type": [],
|
||||||
"deck": {
|
"deck": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck",
|
||||||
"data": {
|
"address": "172.21.32.20",
|
||||||
"_resource_child_name": "coin_cell_deck",
|
|
||||||
"_resource_type": "unilabos.devices.workstation.coin_cell_assembly.button_battery_station:CoincellDeck"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"address": "192.168.1.20",
|
|
||||||
"port": 502
|
"port": 502
|
||||||
},
|
},
|
||||||
"data": {}
|
"data": {}
|
||||||
@@ -98,7 +99,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine_four",
|
"type": "MagazineHolder_4",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -139,7 +140,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -234,7 +235,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -329,7 +330,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -424,7 +425,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -522,7 +523,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine_four",
|
"type": "MagazineHolder_4",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -563,7 +564,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -658,7 +659,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -753,7 +754,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -848,7 +849,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -948,7 +949,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -991,7 +992,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1086,7 +1087,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1181,7 +1182,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1276,7 +1277,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1371,7 +1372,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1466,7 +1467,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1566,7 +1567,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -1609,7 +1610,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1704,7 +1705,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1799,7 +1800,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1894,7 +1895,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -1989,7 +1990,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2084,7 +2085,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2184,7 +2185,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -2227,7 +2228,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2322,7 +2323,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2417,7 +2418,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2512,7 +2513,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2607,7 +2608,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2702,7 +2703,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2802,7 +2803,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -2845,7 +2846,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -2940,7 +2941,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3035,7 +3036,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3130,7 +3131,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3225,7 +3226,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3320,7 +3321,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3420,7 +3421,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -3463,7 +3464,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3558,7 +3559,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3653,7 +3654,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3748,7 +3749,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3843,7 +3844,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -3938,7 +3939,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4038,7 +4039,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazine",
|
"type": "MagazineHolder_6",
|
||||||
"size_x": 80,
|
"size_x": 80,
|
||||||
"size_y": 80,
|
"size_y": 80,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -4081,7 +4082,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4176,7 +4177,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4271,7 +4272,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4366,7 +4367,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4461,7 +4462,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
@@ -4556,7 +4557,7 @@
|
|||||||
"z": 10
|
"z": 10
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "ClipMagazineHole",
|
"type": "Magazine",
|
||||||
"size_x": 14.0,
|
"size_x": 14.0,
|
||||||
"size_y": 14.0,
|
"size_y": 14.0,
|
||||||
"size_z": 10.0,
|
"size_z": 10.0,
|
||||||
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
|
import pytest
|
||||||
|
|
||||||
from unilabos.resources.bioyond.bottle_carriers import BIOYOND_Electrolyte_6VialCarrier, BIOYOND_Electrolyte_1BottleCarrier
|
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":
|
def test_bottle_carrier() -> "BottleCarrier":
|
||||||
@@ -16,9 +16,9 @@ def test_bottle_carrier() -> "BottleCarrier":
|
|||||||
print(f"1烧杯载架: {beaker_carrier.name}, 位置数: {len(beaker_carrier.sites)}")
|
print(f"1烧杯载架: {beaker_carrier.name}, 位置数: {len(beaker_carrier.sites)}")
|
||||||
|
|
||||||
# 创建瓶子和烧杯
|
# 创建瓶子和烧杯
|
||||||
powder_bottle = BIOYOND_PolymerStation_Solid_Vial("powder_bottle_01")
|
powder_bottle = YB_Solid_Vial("powder_bottle_01")
|
||||||
solution_beaker = BIOYOND_PolymerStation_Solution_Beaker("solution_beaker_01")
|
solution_beaker = YB_Solution_Beaker("solution_beaker_01")
|
||||||
reagent_bottle = BIOYOND_PolymerStation_Reagent_Bottle("reagent_bottle_01")
|
reagent_bottle = YB_Reagent_Bottle("reagent_bottle_01")
|
||||||
|
|
||||||
print(f"\n创建的物料:")
|
print(f"\n创建的物料:")
|
||||||
print(f"粉末瓶: {powder_bottle.name} - {powder_bottle.diameter}mm x {powder_bottle.height}mm, {powder_bottle.max_volume}μL")
|
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 = {
|
type_mapping = {
|
||||||
"烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
|
"烧杯": ("YB_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
|
||||||
"试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""),
|
"试剂瓶": ("YB_1BottleCarrier", ""),
|
||||||
"样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
|
"样品板": ("YB_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
|
||||||
"分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
|
"分装板": ("YB_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
|
||||||
"样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
|
"样品瓶": ("YB_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
|
||||||
"90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
|
"90%分装小瓶": ("YB_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
|
||||||
"10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
|
"10%分装小瓶": ("YB_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from ast import If
|
||||||
import pytest
|
import pytest
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@@ -8,18 +9,16 @@ from unilabos.ros.nodes.resource_tracker import ResourceTreeSet
|
|||||||
from unilabos.registry.registry import lab_registry
|
from unilabos.registry.registry import lab_registry
|
||||||
|
|
||||||
from unilabos.resources.bioyond.decks import BIOYOND_PolymerReactionStation_Deck
|
from unilabos.resources.bioyond.decks import BIOYOND_PolymerReactionStation_Deck
|
||||||
|
from unilabos.resources.bioyond.decks import YB_Deck
|
||||||
|
|
||||||
lab_registry.setup()
|
lab_registry.setup()
|
||||||
|
|
||||||
|
|
||||||
type_mapping = {
|
type_mapping = {
|
||||||
"烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
|
"加样头(大)": ("YB_jia_yang_tou_da", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||||
"试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""),
|
"液": ("YB_1BottleCarrier", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"),
|
||||||
"样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
|
"配液瓶(小)板": ("YB_peiyepingxiaoban", "3a190c8b-3284-af78-d29f-9a69463ad047"),
|
||||||
"分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
|
"配液瓶(小)": ("YB_pei_ye_xiao_Bottler", "3a190c8c-fe8f-bf48-0dc3-97afc7f508eb"),
|
||||||
"样品瓶": ("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"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -57,12 +56,20 @@ def bioyond_materials_liquidhandling_2() -> list[dict]:
|
|||||||
"bioyond_materials_reaction",
|
"bioyond_materials_reaction",
|
||||||
"bioyond_materials_liquidhandling_1",
|
"bioyond_materials_liquidhandling_1",
|
||||||
])
|
])
|
||||||
def test_resourcetreeset_from_plr(materials_fixture, request) -> list[dict]:
|
def test_resourcetreeset_from_plr() -> list[dict]:
|
||||||
materials = request.getfixturevalue(materials_fixture)
|
# 直接加载 bioyond_materials_reaction.json 文件
|
||||||
deck = BIOYOND_PolymerReactionStation_Deck("test_deck")
|
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)
|
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])
|
r = ResourceTreeSet.from_plr_resources([deck])
|
||||||
print(r.dump())
|
print(r.dump())
|
||||||
# json.dump(deck.serialize(), open("test.json", "w", encoding="utf-8"), indent=4)
|
# json.dump(deck.serialize(), open("test.json", "w", encoding="utf-8"), indent=4)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_resourcetreeset_from_plr()
|
||||||
|
|||||||
@@ -388,10 +388,6 @@ def main():
|
|||||||
for ind, i in enumerate(resource_edge_info[::-1]):
|
for ind, i in enumerate(resource_edge_info[::-1]):
|
||||||
source_node: ResourceDict = nodes[i["source"]]
|
source_node: ResourceDict = nodes[i["source"]]
|
||||||
target_node: ResourceDict = nodes[i["target"]]
|
target_node: ResourceDict = nodes[i["target"]]
|
||||||
if "sourceHandle" not in source_node:
|
|
||||||
continue
|
|
||||||
if "targetHandle" not in target_node:
|
|
||||||
continue
|
|
||||||
source_handle = i["sourceHandle"]
|
source_handle = i["sourceHandle"]
|
||||||
target_handle = i["targetHandle"]
|
target_handle = i["targetHandle"]
|
||||||
source_handler_keys = [
|
source_handler_keys = [
|
||||||
|
|||||||
@@ -300,10 +300,6 @@ class HTTPClient:
|
|||||||
)
|
)
|
||||||
if response.status_code not in [200, 201]:
|
if response.status_code not in [200, 201]:
|
||||||
logger.error(f"注册资源失败: {response.status_code}, {response.text}")
|
logger.error(f"注册资源失败: {response.status_code}, {response.text}")
|
||||||
if response.status_code == 200:
|
|
||||||
res = response.json()
|
|
||||||
if "code" in res and res["code"] != 0:
|
|
||||||
logger.error(f"注册资源失败: {response.text}")
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def request_startup_json(self) -> Optional[Dict[str, Any]]:
|
def request_startup_json(self) -> Optional[Dict[str, Any]]:
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ class MessageProcessor:
|
|||||||
ssl_context = ssl_module.create_default_context()
|
ssl_context = ssl_module.create_default_context()
|
||||||
|
|
||||||
ws_logger = logging.getLogger("websockets.client")
|
ws_logger = logging.getLogger("websockets.client")
|
||||||
ws_logger.setLevel(logging.INFO)
|
# 日志级别已在 unilabos.utils.log 中统一配置为 WARNING
|
||||||
|
|
||||||
async with websockets.connect(
|
async with websockets.connect(
|
||||||
self.websocket_url,
|
self.websocket_url,
|
||||||
@@ -1240,7 +1240,7 @@ class WebSocketClient(BaseCommunicationClient):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
self.message_processor.send_message(message)
|
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(
|
def publish_job_status(
|
||||||
self, feedback_data: dict, item: QueueItem, status: str, return_info: Optional[dict] = None
|
self, feedback_data: dict, item: QueueItem, status: str, return_info: Optional[dict] = None
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ from enum import Enum
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Tuple, Union, Optional, Any, List
|
from typing import Tuple, Union, Optional, Any, List
|
||||||
|
|
||||||
from opcua import Client, Node, ua
|
from opcua import Client, Node
|
||||||
from opcua.ua import NodeId, NodeClass, VariantType
|
from opcua.ua import NodeId, NodeClass, VariantType
|
||||||
|
|
||||||
|
|
||||||
@@ -43,72 +43,27 @@ class Base(ABC):
|
|||||||
self._type = typ
|
self._type = typ
|
||||||
self._data_type = data_type
|
self._data_type = data_type
|
||||||
self._node: Optional[Node] = None
|
self._node: Optional[Node] = None
|
||||||
|
|
||||||
def _get_node(self) -> Node:
|
def _get_node(self) -> Node:
|
||||||
if self._node is None:
|
if self._node is None:
|
||||||
try:
|
try:
|
||||||
# 尝试多种 NodeId 字符串格式解析,兼容不同服务器/库的输出
|
# 检查是否是NumericNodeId(ns=X;i=Y)格式
|
||||||
# 可能的格式示例: 'ns=2;i=1234', 'ns=2;s=SomeString',
|
if "NumericNodeId" in self._node_id:
|
||||||
# 'StringNodeId(ns=4;s=OPC|变量名)', 'NumericNodeId(ns=2;i=1234)' 等
|
# 从字符串中提取命名空间和标识符
|
||||||
import re
|
import re
|
||||||
|
match = re.search(r'ns=(\d+);i=(\d+)', self._node_id)
|
||||||
nid = self._node_id
|
if match:
|
||||||
# 如果已经是 NodeId/Node 对象(库用户可能传入),直接使用
|
ns = int(match.group(1))
|
||||||
try:
|
identifier = int(match.group(2))
|
||||||
from opcua.ua import NodeId as UaNodeId
|
node_id = NodeId(identifier, ns)
|
||||||
if isinstance(nid, UaNodeId):
|
self._node = self._client.get_node(node_id)
|
||||||
self._node = self._client.get_node(nid)
|
|
||||||
return self._node
|
|
||||||
except Exception:
|
|
||||||
# 若导入或类型判断失败,则继续下一步
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 直接以字符串形式处理
|
|
||||||
if isinstance(nid, str):
|
|
||||||
nid = nid.strip()
|
|
||||||
|
|
||||||
# 处理包含类名的格式,如 'StringNodeId(ns=4;s=...)' 或 'NumericNodeId(ns=2;i=...)'
|
|
||||||
# 提取括号内的内容
|
|
||||||
match_wrapped = re.match(r'(String|Numeric|Byte|Guid|TwoByteNode|FourByteNode)NodeId\((.*)\)', nid)
|
|
||||||
if match_wrapped:
|
|
||||||
# 提取括号内的实际 node_id 字符串
|
|
||||||
nid = match_wrapped.group(2).strip()
|
|
||||||
|
|
||||||
# 常见短格式 'ns=2;i=1234' 或 'ns=2;s=SomeString'
|
|
||||||
if re.match(r'^ns=\d+;[is]=', nid):
|
|
||||||
self._node = self._client.get_node(nid)
|
|
||||||
else:
|
else:
|
||||||
# 尝试提取 ns 和 i 或 s
|
raise ValueError(f"无法解析节点ID: {self._node_id}")
|
||||||
# 对于字符串标识符,可能包含特殊字符,使用非贪婪匹配
|
|
||||||
m_num = re.search(r'ns=(\d+);i=(\d+)', nid)
|
|
||||||
m_str = re.search(r'ns=(\d+);s=(.+?)(?:\)|$)', nid)
|
|
||||||
if m_num:
|
|
||||||
ns = int(m_num.group(1))
|
|
||||||
identifier = int(m_num.group(2))
|
|
||||||
node_id = NodeId(identifier, ns)
|
|
||||||
self._node = self._client.get_node(node_id)
|
|
||||||
elif m_str:
|
|
||||||
ns = int(m_str.group(1))
|
|
||||||
identifier = m_str.group(2).strip()
|
|
||||||
# 对于字符串标识符,直接使用字符串格式
|
|
||||||
node_id_str = f"ns={ns};s={identifier}"
|
|
||||||
self._node = self._client.get_node(node_id_str)
|
|
||||||
else:
|
|
||||||
# 回退:尝试直接传入字符串(有些实现接受其它格式)
|
|
||||||
try:
|
|
||||||
self._node = self._client.get_node(self._node_id)
|
|
||||||
except Exception as e:
|
|
||||||
# 输出更详细的错误信息供调试
|
|
||||||
print(f"获取节点失败(尝试直接字符串): {self._node_id}, 错误: {e}")
|
|
||||||
raise
|
|
||||||
else:
|
else:
|
||||||
# 非字符串,尝试直接使用
|
# 直接使用节点ID字符串
|
||||||
self._node = self._client.get_node(self._node_id)
|
self._node = self._client.get_node(self._node_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"获取节点失败: {self._node_id}, 错误: {e}")
|
print(f"获取节点失败: {self._node_id}, 错误: {e}")
|
||||||
# 添加额外提示,帮助定位 BadNodeIdUnknown 问题
|
|
||||||
print("提示: 请确认该 node_id 是否来自当前连接的服务器地址空间," \
|
|
||||||
"以及 CSV/配置中名称与服务器 BrowseName 是否匹配。")
|
|
||||||
raise
|
raise
|
||||||
return self._node
|
return self._node
|
||||||
|
|
||||||
@@ -116,16 +71,16 @@ class Base(ABC):
|
|||||||
def read(self) -> Tuple[Any, bool]:
|
def read(self) -> Tuple[Any, bool]:
|
||||||
"""读取节点值,返回(值, 是否出错)"""
|
"""读取节点值,返回(值, 是否出错)"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def write(self, value: Any) -> bool:
|
def write(self, value: Any) -> bool:
|
||||||
"""写入节点值,返回是否出错"""
|
"""写入节点值,返回是否出错"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self) -> NodeType:
|
def type(self) -> NodeType:
|
||||||
return self._type
|
return self._type
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def node_id(self) -> str:
|
def node_id(self) -> str:
|
||||||
return self._node_id
|
return self._node_id
|
||||||
@@ -149,56 +104,7 @@ class Variable(Base):
|
|||||||
|
|
||||||
def write(self, value: Any) -> bool:
|
def write(self, value: Any) -> bool:
|
||||||
try:
|
try:
|
||||||
# 如果声明了数据类型,则尝试转换并使用对应的 Variant 写入
|
self._get_node().set_value(value)
|
||||||
coerced = value
|
|
||||||
try:
|
|
||||||
if self._data_type is not None:
|
|
||||||
# 基于声明的数据类型做简单类型转换
|
|
||||||
dt = self._data_type
|
|
||||||
if dt in (DataType.SBYTE, DataType.BYTE, DataType.INT16, DataType.UINT16,
|
|
||||||
DataType.INT32, DataType.UINT32, DataType.INT64, DataType.UINT64):
|
|
||||||
# 数值类型 -> int
|
|
||||||
if isinstance(value, str):
|
|
||||||
coerced = int(value)
|
|
||||||
else:
|
|
||||||
coerced = int(value)
|
|
||||||
elif dt in (DataType.FLOAT, DataType.DOUBLE):
|
|
||||||
if isinstance(value, str):
|
|
||||||
coerced = float(value)
|
|
||||||
else:
|
|
||||||
coerced = float(value)
|
|
||||||
elif dt == DataType.BOOLEAN:
|
|
||||||
if isinstance(value, str):
|
|
||||||
v = value.strip().lower()
|
|
||||||
if v in ("true", "1", "yes", "on"):
|
|
||||||
coerced = True
|
|
||||||
elif v in ("false", "0", "no", "off"):
|
|
||||||
coerced = False
|
|
||||||
else:
|
|
||||||
coerced = bool(value)
|
|
||||||
else:
|
|
||||||
coerced = bool(value)
|
|
||||||
elif dt == DataType.STRING or dt == DataType.BYTESTRING or dt == DataType.DATETIME:
|
|
||||||
coerced = str(value)
|
|
||||||
|
|
||||||
# 使用 ua.Variant 明确指定 VariantType
|
|
||||||
try:
|
|
||||||
variant = ua.Variant(coerced, dt.value)
|
|
||||||
self._get_node().set_value(variant)
|
|
||||||
except Exception:
|
|
||||||
# 回退:有些 set_value 实现接受 (value, variant_type)
|
|
||||||
try:
|
|
||||||
self._get_node().set_value(coerced, dt.value)
|
|
||||||
except Exception:
|
|
||||||
# 最后回退到直接写入(保持兼容性)
|
|
||||||
self._get_node().set_value(coerced)
|
|
||||||
else:
|
|
||||||
# 未声明数据类型,直接写入
|
|
||||||
self._get_node().set_value(value)
|
|
||||||
except Exception:
|
|
||||||
# 若在转换或按数据类型写入失败,尝试直接写入原始值并让上层捕获错误
|
|
||||||
self._get_node().set_value(value)
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"写入变量 {self._name} 失败: {e}")
|
print(f"写入变量 {self._name} 失败: {e}")
|
||||||
@@ -210,54 +116,24 @@ class Method(Base):
|
|||||||
super().__init__(client, name, node_id, NodeType.METHOD, data_type)
|
super().__init__(client, name, node_id, NodeType.METHOD, data_type)
|
||||||
self._parent_node_id = parent_node_id
|
self._parent_node_id = parent_node_id
|
||||||
self._parent_node = None
|
self._parent_node = None
|
||||||
|
|
||||||
def _get_parent_node(self) -> Node:
|
def _get_parent_node(self) -> Node:
|
||||||
if self._parent_node is None:
|
if self._parent_node is None:
|
||||||
try:
|
try:
|
||||||
# 处理父节点ID,使用与_get_node相同的解析逻辑
|
# 检查是否是NumericNodeId(ns=X;i=Y)格式
|
||||||
import re
|
if "NumericNodeId" in self._parent_node_id:
|
||||||
|
# 从字符串中提取命名空间和标识符
|
||||||
nid = self._parent_node_id
|
import re
|
||||||
|
match = re.search(r'ns=(\d+);i=(\d+)', self._parent_node_id)
|
||||||
# 如果已经是 NodeId 对象,直接使用
|
if match:
|
||||||
try:
|
ns = int(match.group(1))
|
||||||
from opcua.ua import NodeId as UaNodeId
|
identifier = int(match.group(2))
|
||||||
if isinstance(nid, UaNodeId):
|
node_id = NodeId(identifier, ns)
|
||||||
self._parent_node = self._client.get_node(nid)
|
self._parent_node = self._client.get_node(node_id)
|
||||||
return self._parent_node
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 字符串处理
|
|
||||||
if isinstance(nid, str):
|
|
||||||
nid = nid.strip()
|
|
||||||
|
|
||||||
# 处理包含类名的格式
|
|
||||||
match_wrapped = re.match(r'(String|Numeric|Byte|Guid|TwoByteNode|FourByteNode)NodeId\((.*)\)', nid)
|
|
||||||
if match_wrapped:
|
|
||||||
nid = match_wrapped.group(2).strip()
|
|
||||||
|
|
||||||
# 常见短格式
|
|
||||||
if re.match(r'^ns=\d+;[is]=', nid):
|
|
||||||
self._parent_node = self._client.get_node(nid)
|
|
||||||
else:
|
else:
|
||||||
# 提取 ns 和 i 或 s
|
raise ValueError(f"无法解析父节点ID: {self._parent_node_id}")
|
||||||
m_num = re.search(r'ns=(\d+);i=(\d+)', nid)
|
|
||||||
m_str = re.search(r'ns=(\d+);s=(.+?)(?:\)|$)', nid)
|
|
||||||
if m_num:
|
|
||||||
ns = int(m_num.group(1))
|
|
||||||
identifier = int(m_num.group(2))
|
|
||||||
node_id = NodeId(identifier, ns)
|
|
||||||
self._parent_node = self._client.get_node(node_id)
|
|
||||||
elif m_str:
|
|
||||||
ns = int(m_str.group(1))
|
|
||||||
identifier = m_str.group(2).strip()
|
|
||||||
node_id_str = f"ns={ns};s={identifier}"
|
|
||||||
self._parent_node = self._client.get_node(node_id_str)
|
|
||||||
else:
|
|
||||||
# 回退
|
|
||||||
self._parent_node = self._client.get_node(self._parent_node_id)
|
|
||||||
else:
|
else:
|
||||||
|
# 直接使用节点ID字符串
|
||||||
self._parent_node = self._client.get_node(self._parent_node_id)
|
self._parent_node = self._client.get_node(self._parent_node_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"获取父节点失败: {self._parent_node_id}, 错误: {e}")
|
print(f"获取父节点失败: {self._parent_node_id}, 错误: {e}")
|
||||||
@@ -271,7 +147,7 @@ class Method(Base):
|
|||||||
def write(self, value: Any) -> bool:
|
def write(self, value: Any) -> bool:
|
||||||
"""方法节点不支持写入操作"""
|
"""方法节点不支持写入操作"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def call(self, *args) -> Tuple[Any, bool]:
|
def call(self, *args) -> Tuple[Any, bool]:
|
||||||
"""调用方法,返回(返回值, 是否出错)"""
|
"""调用方法,返回(返回值, 是否出错)"""
|
||||||
try:
|
try:
|
||||||
@@ -285,7 +161,7 @@ class Method(Base):
|
|||||||
class Object(Base):
|
class Object(Base):
|
||||||
def __init__(self, client: Client, name: str, node_id: str):
|
def __init__(self, client: Client, name: str, node_id: str):
|
||||||
super().__init__(client, name, node_id, NodeType.OBJECT, None)
|
super().__init__(client, name, node_id, NodeType.OBJECT, None)
|
||||||
|
|
||||||
def read(self) -> Tuple[Any, bool]:
|
def read(self) -> Tuple[Any, bool]:
|
||||||
"""对象节点不支持直接读取操作"""
|
"""对象节点不支持直接读取操作"""
|
||||||
return None, True
|
return None, True
|
||||||
@@ -293,7 +169,7 @@ class Object(Base):
|
|||||||
def write(self, value: Any) -> bool:
|
def write(self, value: Any) -> bool:
|
||||||
"""对象节点不支持直接写入操作"""
|
"""对象节点不支持直接写入操作"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_children(self) -> Tuple[List[Node], bool]:
|
def get_children(self) -> Tuple[List[Node], bool]:
|
||||||
"""获取子节点列表,返回(子节点列表, 是否出错)"""
|
"""获取子节点列表,返回(子节点列表, 是否出错)"""
|
||||||
try:
|
try:
|
||||||
@@ -301,4 +177,4 @@ class Object(Base):
|
|||||||
return children, False
|
return children, False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"获取对象 {self._name} 的子节点失败: {e}")
|
print(f"获取对象 {self._name} 的子节点失败: {e}")
|
||||||
return [], True
|
return [], True
|
||||||
@@ -1,712 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import asyncio
|
|
||||||
import json
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
from typing import Optional, Dict, Any
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import requests
|
|
||||||
import websockets
|
|
||||||
|
|
||||||
logging.getLogger("zeep").setLevel(logging.WARNING)
|
|
||||||
logging.getLogger("zeep.xsd.schema").setLevel(logging.WARNING)
|
|
||||||
logging.getLogger("zeep.xsd.schema.schema").setLevel(logging.WARNING)
|
|
||||||
from onvif import ONVIFCamera # 新增:ONVIF PTZ 控制
|
|
||||||
|
|
||||||
|
|
||||||
# ======================= 独立的 PTZController =======================
|
|
||||||
class PTZController:
|
|
||||||
def __init__(self, host: str, port: int, user: str, password: str):
|
|
||||||
"""
|
|
||||||
:param host: 摄像机 IP 或域名(和 RTSP 的一样即可)
|
|
||||||
:param port: ONVIF 端口(多数为 80,看你的设备)
|
|
||||||
:param user: 摄像机用户名
|
|
||||||
:param password: 摄像机密码
|
|
||||||
"""
|
|
||||||
self.host = host
|
|
||||||
self.port = port
|
|
||||||
self.user = user
|
|
||||||
self.password = password
|
|
||||||
|
|
||||||
self.cam: Optional[ONVIFCamera] = None
|
|
||||||
self.media_service = None
|
|
||||||
self.ptz_service = None
|
|
||||||
self.profile = None
|
|
||||||
|
|
||||||
def connect(self) -> bool:
|
|
||||||
"""
|
|
||||||
建立 ONVIF 连接并初始化 PTZ 能力,失败返回 False(不抛异常)
|
|
||||||
Note: 首先 pip install onvif-zeep
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
self.cam = ONVIFCamera(self.host, self.port, self.user, self.password)
|
|
||||||
self.media_service = self.cam.create_media_service()
|
|
||||||
self.ptz_service = self.cam.create_ptz_service()
|
|
||||||
profiles = self.media_service.GetProfiles()
|
|
||||||
if not profiles:
|
|
||||||
print("[PTZ] No media profiles found on camera.", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
self.profile = profiles[0]
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[PTZ] Failed to init ONVIF PTZ: {e}", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _continuous_move(self, pan: float, tilt: float, zoom: float, duration: float) -> bool:
|
|
||||||
"""
|
|
||||||
连续移动一段时间(秒),之后自动停止。
|
|
||||||
此函数为阻塞模式:只有在 Stop 调用结束后,才返回 True/False。
|
|
||||||
"""
|
|
||||||
if not self.ptz_service or not self.profile:
|
|
||||||
print("[PTZ] _continuous_move: ptz_service or profile not ready", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 进入前先强行停一下,避免前一次残留动作
|
|
||||||
self._force_stop()
|
|
||||||
|
|
||||||
req = self.ptz_service.create_type("ContinuousMove")
|
|
||||||
req.ProfileToken = self.profile.token
|
|
||||||
|
|
||||||
req.Velocity = {
|
|
||||||
"PanTilt": {"x": pan, "y": tilt},
|
|
||||||
"Zoom": {"x": zoom},
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
print(f"[PTZ] ContinuousMove start: pan={pan}, tilt={tilt}, zoom={zoom}, duration={duration}", file=sys.stderr)
|
|
||||||
self.ptz_service.ContinuousMove(req)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[PTZ] ContinuousMove failed: {e}", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 阻塞等待:这里决定“运动时间”
|
|
||||||
import time
|
|
||||||
wait_seconds = max(2 * duration, 0.0)
|
|
||||||
time.sleep(wait_seconds)
|
|
||||||
|
|
||||||
# 运动完成后强制停止
|
|
||||||
return self._force_stop()
|
|
||||||
|
|
||||||
def stop(self) -> bool:
|
|
||||||
"""
|
|
||||||
阻塞调用 Stop(带重试),成功 True,失败 False。
|
|
||||||
"""
|
|
||||||
return self._force_stop()
|
|
||||||
|
|
||||||
# ------- 对外动作接口(给 CameraController 调用) -------
|
|
||||||
# 所有接口都为“阻塞模式”:只有在运动 + Stop 完成后才返回 True/False
|
|
||||||
|
|
||||||
def move_up(self, speed: float = 0.5, duration: float = 1.0) -> bool:
|
|
||||||
print(f"[PTZ] move_up called, speed={speed}, duration={duration}", file=sys.stderr)
|
|
||||||
return self._continuous_move(pan=0.0, tilt=+speed, zoom=0.0, duration=duration)
|
|
||||||
|
|
||||||
def move_down(self, speed: float = 0.5, duration: float = 1.0) -> bool:
|
|
||||||
print(f"[PTZ] move_down called, speed={speed}, duration={duration}", file=sys.stderr)
|
|
||||||
return self._continuous_move(pan=0.0, tilt=-speed, zoom=0.0, duration=duration)
|
|
||||||
|
|
||||||
def move_left(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
|
||||||
print(f"[PTZ] move_left called, speed={speed}, duration={duration}", file=sys.stderr)
|
|
||||||
return self._continuous_move(pan=-speed, tilt=0.0, zoom=0.0, duration=duration)
|
|
||||||
|
|
||||||
def move_right(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
|
||||||
print(f"[PTZ] move_right called, speed={speed}, duration={duration}", file=sys.stderr)
|
|
||||||
return self._continuous_move(pan=+speed, tilt=0.0, zoom=0.0, duration=duration)
|
|
||||||
|
|
||||||
# ------- 占位的变倍接口(当前设备不支持) -------
|
|
||||||
def zoom_in(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
|
||||||
"""
|
|
||||||
当前设备不支持变倍;保留方法只是避免上层调用时报错。
|
|
||||||
"""
|
|
||||||
print("[PTZ] zoom_in is disabled for this device.", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def zoom_out(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
|
||||||
"""
|
|
||||||
当前设备不支持变倍;保留方法只是避免上层调用时报错。
|
|
||||||
"""
|
|
||||||
print("[PTZ] zoom_out is disabled for this device.", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _force_stop(self, retries: int = 3, delay: float = 0.1) -> bool:
|
|
||||||
"""
|
|
||||||
尝试多次调用 Stop,作为“强制停止”手段。
|
|
||||||
:param retries: 重试次数
|
|
||||||
:param delay: 每次重试间隔(秒)
|
|
||||||
"""
|
|
||||||
if not self.ptz_service or not self.profile:
|
|
||||||
print("[PTZ] _force_stop: ptz_service or profile not ready", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
import time
|
|
||||||
last_error = None
|
|
||||||
for i in range(retries):
|
|
||||||
try:
|
|
||||||
print(f"[PTZ] _force_stop: calling Stop(), attempt={i+1}", file=sys.stderr)
|
|
||||||
self.ptz_service.Stop({"ProfileToken": self.profile.token})
|
|
||||||
print("[PTZ] _force_stop: Stop() returned OK", file=sys.stderr)
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
last_error = e
|
|
||||||
print(f"[PTZ] _force_stop: Stop() failed at attempt {i+1}: {e}", file=sys.stderr)
|
|
||||||
time.sleep(delay)
|
|
||||||
|
|
||||||
print(f"[PTZ] _force_stop: all {retries} attempts failed, last error: {last_error}", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# ======================= CameraController(加入 PTZ) =======================
|
|
||||||
|
|
||||||
class CameraController:
|
|
||||||
"""
|
|
||||||
Uni-Lab-OS 摄像头驱动(driver 形式)
|
|
||||||
启动 Uni-Lab-OS 后,立即开始推流
|
|
||||||
|
|
||||||
- WebSocket 信令:通过 signal_backend_url 连接到后端
|
|
||||||
例如: wss://sciol.ac.cn/api/realtime/signal/host/<host_id>
|
|
||||||
- 媒体服务器:通过 rtmp_url / webrtc_api / webrtc_stream_url
|
|
||||||
当前配置为 SRS,与独立 HostSimulator 独立运行脚本保持一致。
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
host_id: str = "demo-host",
|
|
||||||
|
|
||||||
# (1)信令后端(WebSocket)
|
|
||||||
signal_backend_url: str = "wss://sciol.ac.cn/api/realtime/signal/host",
|
|
||||||
|
|
||||||
# (2)媒体后端(RTMP + WebRTC API)
|
|
||||||
rtmp_url: str = "rtmp://srs.sciol.ac.cn:4499/live/camera-01",
|
|
||||||
webrtc_api: str = "https://srs.sciol.ac.cn/rtc/v1/play/",
|
|
||||||
webrtc_stream_url: str = "webrtc://srs.sciol.ac.cn:4500/live/camera-01",
|
|
||||||
camera_rtsp_url: str = "",
|
|
||||||
|
|
||||||
# (3)PTZ 控制相关(ONVIF)
|
|
||||||
ptz_host: str = "", # 一般就是摄像头 IP,比如 "192.168.31.164"
|
|
||||||
ptz_port: int = 80, # ONVIF 端口,不一定是 80,按实际情况改
|
|
||||||
ptz_user: str = "", # admin
|
|
||||||
ptz_password: str = "", # admin123
|
|
||||||
):
|
|
||||||
self.host_id = host_id
|
|
||||||
self.camera_rtsp_url = camera_rtsp_url
|
|
||||||
|
|
||||||
# 拼接最终的 WebSocket URL:.../host/<host_id>
|
|
||||||
signal_backend_url = signal_backend_url.rstrip("/")
|
|
||||||
if not signal_backend_url.endswith("/host"):
|
|
||||||
signal_backend_url = signal_backend_url + "/host"
|
|
||||||
self.signal_backend_url = f"{signal_backend_url}/{host_id}"
|
|
||||||
|
|
||||||
# 媒体服务器配置
|
|
||||||
self.rtmp_url = rtmp_url
|
|
||||||
self.webrtc_api = webrtc_api
|
|
||||||
self.webrtc_stream_url = webrtc_stream_url
|
|
||||||
|
|
||||||
# PTZ 控制
|
|
||||||
self.ptz_host = ptz_host
|
|
||||||
self.ptz_port = ptz_port
|
|
||||||
self.ptz_user = ptz_user
|
|
||||||
self.ptz_password = ptz_password
|
|
||||||
self._ptz: Optional[PTZController] = None
|
|
||||||
self._init_ptz_if_possible()
|
|
||||||
|
|
||||||
# 运行时状态
|
|
||||||
self._ws: Optional[object] = None
|
|
||||||
self._ffmpeg_process: Optional[subprocess.Popen] = None
|
|
||||||
self._running = False
|
|
||||||
self._loop_task: Optional[asyncio.Future] = None
|
|
||||||
|
|
||||||
# 事件循环 & 线程
|
|
||||||
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
|
||||||
self._loop_thread: Optional[threading.Thread] = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.start()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[CameraController] __init__ auto start failed: {e}", file=sys.stderr)
|
|
||||||
|
|
||||||
# ------------------------ PTZ 初始化 ------------------------
|
|
||||||
|
|
||||||
# ------------------------ PTZ 公开动作方法(一个动作一个函数) ------------------------
|
|
||||||
|
|
||||||
def ptz_move_up(self, speed: float = 0.5, duration: float = 1.0) -> bool:
|
|
||||||
print(f"[CameraController] ptz_move_up called, speed={speed}, duration={duration}")
|
|
||||||
return self._ptz.move_up(speed=speed, duration=duration)
|
|
||||||
|
|
||||||
def ptz_move_down(self, speed: float = 0.5, duration: float = 1.0) -> bool:
|
|
||||||
print(f"[CameraController] ptz_move_down called, speed={speed}, duration={duration}")
|
|
||||||
return self._ptz.move_down(speed=speed, duration=duration)
|
|
||||||
|
|
||||||
def ptz_move_left(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
|
||||||
print(f"[CameraController] ptz_move_left called, speed={speed}, duration={duration}")
|
|
||||||
return self._ptz.move_left(speed=speed, duration=duration)
|
|
||||||
|
|
||||||
def ptz_move_right(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
|
||||||
print(f"[CameraController] ptz_move_right called, speed={speed}, duration={duration}")
|
|
||||||
return self._ptz.move_right(speed=speed, duration=duration)
|
|
||||||
|
|
||||||
def zoom_in(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
|
||||||
"""
|
|
||||||
当前设备不支持变倍;保留方法只是避免上层调用时报错。
|
|
||||||
"""
|
|
||||||
print("[PTZ] zoom_in is disabled for this device.", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def zoom_out(self, speed: float = 0.2, duration: float = 1.0) -> bool:
|
|
||||||
"""
|
|
||||||
当前设备不支持变倍;保留方法只是避免上层调用时报错。
|
|
||||||
"""
|
|
||||||
print("[PTZ] zoom_out is disabled for this device.", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def ptz_stop(self):
|
|
||||||
if self._ptz is None:
|
|
||||||
print("[CameraController] PTZ not initialized.", file=sys.stderr)
|
|
||||||
return
|
|
||||||
self._ptz.stop()
|
|
||||||
|
|
||||||
def _init_ptz_if_possible(self):
|
|
||||||
"""
|
|
||||||
根据 ptz_host / user / password 初始化 PTZ;
|
|
||||||
如果配置信息不全则不启用 PTZ(静默)。
|
|
||||||
"""
|
|
||||||
if not (self.ptz_host and self.ptz_user and self.ptz_password):
|
|
||||||
return
|
|
||||||
ctrl = PTZController(
|
|
||||||
host=self.ptz_host,
|
|
||||||
port=self.ptz_port,
|
|
||||||
user=self.ptz_user,
|
|
||||||
password=self.ptz_password,
|
|
||||||
)
|
|
||||||
if ctrl.connect():
|
|
||||||
self._ptz = ctrl
|
|
||||||
else:
|
|
||||||
self._ptz = None
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# 对外暴露的方法:供 Uni-Lab-OS 调用
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
def start(self, config: Optional[Dict[str, Any]] = None):
|
|
||||||
"""
|
|
||||||
启动 Camera 连接 & 消息循环,并在启动时就开启 FFmpeg 推流,
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self._running:
|
|
||||||
return {"status": "already_running", "host_id": self.host_id}
|
|
||||||
|
|
||||||
# 应用 config 覆盖(如果有)
|
|
||||||
if config:
|
|
||||||
self.camera_rtsp_url = config.get("camera_rtsp_url", self.camera_rtsp_url)
|
|
||||||
cfg_host_id = config.get("host_id")
|
|
||||||
if cfg_host_id:
|
|
||||||
self.host_id = cfg_host_id
|
|
||||||
|
|
||||||
signal_backend_url = config.get("signal_backend_url")
|
|
||||||
if signal_backend_url:
|
|
||||||
signal_backend_url = signal_backend_url.rstrip("/")
|
|
||||||
if not signal_backend_url.endswith("/host"):
|
|
||||||
signal_backend_url = signal_backend_url + "/host"
|
|
||||||
self.signal_backend_url = f"{signal_backend_url}/{self.host_id}"
|
|
||||||
|
|
||||||
self.rtmp_url = config.get("rtmp_url", self.rtmp_url)
|
|
||||||
self.webrtc_api = config.get("webrtc_api", self.webrtc_api)
|
|
||||||
self.webrtc_stream_url = config.get(
|
|
||||||
"webrtc_stream_url", self.webrtc_stream_url
|
|
||||||
)
|
|
||||||
|
|
||||||
# PTZ 相关配置也允许通过 config 注入
|
|
||||||
self.ptz_host = config.get("ptz_host", self.ptz_host)
|
|
||||||
self.ptz_port = int(config.get("ptz_port", self.ptz_port))
|
|
||||||
self.ptz_user = config.get("ptz_user", self.ptz_user)
|
|
||||||
self.ptz_password = config.get("ptz_password", self.ptz_password)
|
|
||||||
self._init_ptz_if_possible()
|
|
||||||
|
|
||||||
self._running = True
|
|
||||||
|
|
||||||
# === start 时启动 FFmpeg 推流 ===
|
|
||||||
self._start_ffmpeg()
|
|
||||||
|
|
||||||
# 创建新的事件循环和线程(用于 WebSocket 信令)
|
|
||||||
self._loop = asyncio.new_event_loop()
|
|
||||||
|
|
||||||
def loop_runner(loop: asyncio.AbstractEventLoop):
|
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
try:
|
|
||||||
loop.run_forever()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[CameraController] event loop error: {e}", file=sys.stderr)
|
|
||||||
|
|
||||||
self._loop_thread = threading.Thread(
|
|
||||||
target=loop_runner, args=(self._loop,), daemon=True
|
|
||||||
)
|
|
||||||
self._loop_thread.start()
|
|
||||||
|
|
||||||
self._loop_task = asyncio.run_coroutine_threadsafe(
|
|
||||||
self._run_main_loop(), self._loop
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"status": "started",
|
|
||||||
"host_id": self.host_id,
|
|
||||||
"signal_backend_url": self.signal_backend_url,
|
|
||||||
"rtmp_url": self.rtmp_url,
|
|
||||||
"webrtc_api": self.webrtc_api,
|
|
||||||
"webrtc_stream_url": self.webrtc_stream_url,
|
|
||||||
}
|
|
||||||
|
|
||||||
def stop(self) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
停止推流 & 断开 WebSocket,并关闭事件循环线程。
|
|
||||||
"""
|
|
||||||
self._running = False
|
|
||||||
|
|
||||||
self._stop_ffmpeg()
|
|
||||||
|
|
||||||
if self._ws and self._loop is not None:
|
|
||||||
async def close_ws():
|
|
||||||
try:
|
|
||||||
await self._ws.close()
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"[CameraController] error when closing WebSocket: {e}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
asyncio.run_coroutine_threadsafe(close_ws(), self._loop)
|
|
||||||
|
|
||||||
if self._loop_task is not None:
|
|
||||||
if not self._loop_task.done():
|
|
||||||
self._loop_task.cancel()
|
|
||||||
try:
|
|
||||||
self._loop_task.result()
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
pass
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"[CameraController] main loop task error in stop(): {e}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
self._loop_task = None
|
|
||||||
|
|
||||||
if self._loop is not None:
|
|
||||||
try:
|
|
||||||
self._loop.call_soon_threadsafe(self._loop.stop)
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"[CameraController] error when stopping event loop: {e}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
if self._loop_thread is not None:
|
|
||||||
try:
|
|
||||||
self._loop_thread.join(timeout=5)
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"[CameraController] error when joining loop thread: {e}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
finally:
|
|
||||||
self._loop_thread = None
|
|
||||||
|
|
||||||
self._ws = None
|
|
||||||
self._loop = None
|
|
||||||
|
|
||||||
return {"status": "stopped", "host_id": self.host_id}
|
|
||||||
|
|
||||||
def get_status(self) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
查询当前状态,方便在 Uni-Lab-OS 中做监控。
|
|
||||||
"""
|
|
||||||
ws_closed = None
|
|
||||||
if self._ws is not None:
|
|
||||||
ws_closed = getattr(self._ws, "closed", None)
|
|
||||||
|
|
||||||
if ws_closed is None:
|
|
||||||
websocket_connected = self._ws is not None
|
|
||||||
else:
|
|
||||||
websocket_connected = (self._ws is not None) and (not ws_closed)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"host_id": self.host_id,
|
|
||||||
"running": self._running,
|
|
||||||
"websocket_connected": websocket_connected,
|
|
||||||
"ffmpeg_running": bool(
|
|
||||||
self._ffmpeg_process and self._ffmpeg_process.poll() is None
|
|
||||||
),
|
|
||||||
"signal_backend_url": self.signal_backend_url,
|
|
||||||
"rtmp_url": self.rtmp_url,
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# 内部实现逻辑:WebSocket 循环 / FFmpeg / WebRTC Offer 处理
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
async def _run_main_loop(self):
|
|
||||||
try:
|
|
||||||
while self._running:
|
|
||||||
try:
|
|
||||||
async with websockets.connect(self.signal_backend_url) as ws:
|
|
||||||
self._ws = ws
|
|
||||||
await self._recv_loop()
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
raise
|
|
||||||
except Exception as e:
|
|
||||||
if self._running:
|
|
||||||
print(
|
|
||||||
f"[CameraController] WebSocket connection error: {e}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
await asyncio.sleep(3)
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _recv_loop(self):
|
|
||||||
assert self._ws is not None
|
|
||||||
ws = self._ws
|
|
||||||
|
|
||||||
async for message in ws:
|
|
||||||
try:
|
|
||||||
data = json.loads(message)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
print(
|
|
||||||
f"[CameraController] received non-JSON message: {message}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
await self._handle_message(data)
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"[CameraController] error while handling message {data}: {e}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _handle_message(self, data: Dict[str, Any]):
|
|
||||||
"""
|
|
||||||
处理来自信令后端的消息:
|
|
||||||
- command: start_stream / stop_stream / ptz_xxx
|
|
||||||
- type: offer (WebRTC)
|
|
||||||
"""
|
|
||||||
cmd = data.get("command")
|
|
||||||
|
|
||||||
# ---------- 推流控制 ----------
|
|
||||||
if cmd == "start_stream":
|
|
||||||
try:
|
|
||||||
self._start_ffmpeg()
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"[CameraController] error when starting FFmpeg on start_stream: {e}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if cmd == "stop_stream":
|
|
||||||
try:
|
|
||||||
self._stop_ffmpeg()
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"[CameraController] error when stopping FFmpeg on stop_stream: {e}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
# # ---------- PTZ 控制 ----------
|
|
||||||
# # 例如信令可以发:
|
|
||||||
# # {"command": "ptz_move", "direction": "down", "speed": 0.5, "duration": 0.5}
|
|
||||||
# if cmd == "ptz_move":
|
|
||||||
# if self._ptz is None:
|
|
||||||
# # 没有初始化 PTZ,静默忽略或打印一条
|
|
||||||
# print("[CameraController] PTZ not initialized.", file=sys.stderr)
|
|
||||||
# return
|
|
||||||
|
|
||||||
# direction = data.get("direction", "")
|
|
||||||
# speed = float(data.get("speed", 0.5))
|
|
||||||
# duration = float(data.get("duration", 0.5))
|
|
||||||
|
|
||||||
# try:
|
|
||||||
# if direction == "up":
|
|
||||||
# self._ptz.move_up(speed=speed, duration=duration)
|
|
||||||
# elif direction == "down":
|
|
||||||
# self._ptz.move_down(speed=speed, duration=duration)
|
|
||||||
# elif direction == "left":
|
|
||||||
# self._ptz.move_left(speed=speed, duration=duration)
|
|
||||||
# elif direction == "right":
|
|
||||||
# self._ptz.move_right(speed=speed, duration=duration)
|
|
||||||
# elif direction == "zoom_in":
|
|
||||||
# self._ptz.zoom_in(speed=speed, duration=duration)
|
|
||||||
# elif direction == "zoom_out":
|
|
||||||
# self._ptz.zoom_out(speed=speed, duration=duration)
|
|
||||||
# elif direction == "stop":
|
|
||||||
# self._ptz.stop()
|
|
||||||
# else:
|
|
||||||
# # 未知方向,忽略
|
|
||||||
# pass
|
|
||||||
# except Exception as e:
|
|
||||||
# print(
|
|
||||||
# f"[CameraController] error when handling PTZ move: {e}",
|
|
||||||
# file=sys.stderr,
|
|
||||||
# )
|
|
||||||
# return
|
|
||||||
|
|
||||||
# ---------- WebRTC Offer ----------
|
|
||||||
if data.get("type") == "offer":
|
|
||||||
offer_sdp = data.get("sdp", "")
|
|
||||||
camera_id = data.get("cameraId", "camera-01")
|
|
||||||
try:
|
|
||||||
answer_sdp = await self._handle_webrtc_offer(offer_sdp)
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"[CameraController] error when handling WebRTC offer: {e}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if self._ws:
|
|
||||||
answer_payload = {
|
|
||||||
"type": "answer",
|
|
||||||
"sdp": answer_sdp,
|
|
||||||
"cameraId": camera_id,
|
|
||||||
"hostId": self.host_id,
|
|
||||||
}
|
|
||||||
try:
|
|
||||||
await self._ws.send(json.dumps(answer_payload))
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"[CameraController] error when sending WebRTC answer: {e}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
# ------------------------ FFmpeg 相关 ------------------------
|
|
||||||
|
|
||||||
def _start_ffmpeg(self):
|
|
||||||
if self._ffmpeg_process and self._ffmpeg_process.poll() is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
cmd = [
|
|
||||||
"ffmpeg",
|
|
||||||
"-rtsp_transport", "tcp",
|
|
||||||
"-i", self.camera_rtsp_url,
|
|
||||||
|
|
||||||
"-c:v", "libx264",
|
|
||||||
"-preset", "ultrafast",
|
|
||||||
"-tune", "zerolatency",
|
|
||||||
"-profile:v", "baseline",
|
|
||||||
"-b:v", "1M",
|
|
||||||
"-maxrate", "1M",
|
|
||||||
"-bufsize", "2M",
|
|
||||||
"-g", "10",
|
|
||||||
"-keyint_min", "10",
|
|
||||||
"-sc_threshold", "0",
|
|
||||||
"-pix_fmt", "yuv420p",
|
|
||||||
"-x264-params", "bframes=0",
|
|
||||||
|
|
||||||
"-c:a", "aac",
|
|
||||||
"-ar", "44100",
|
|
||||||
"-ac", "1",
|
|
||||||
"-b:a", "64k",
|
|
||||||
|
|
||||||
"-f", "flv",
|
|
||||||
self.rtmp_url,
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._ffmpeg_process = subprocess.Popen(
|
|
||||||
cmd,
|
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
stderr=subprocess.STDOUT,
|
|
||||||
shell=False,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[CameraController] failed to start FFmpeg: {e}", file=sys.stderr)
|
|
||||||
self._ffmpeg_process = None
|
|
||||||
raise
|
|
||||||
|
|
||||||
def _stop_ffmpeg(self):
|
|
||||||
proc = self._ffmpeg_process
|
|
||||||
|
|
||||||
if proc and proc.poll() is None:
|
|
||||||
try:
|
|
||||||
proc.terminate()
|
|
||||||
try:
|
|
||||||
proc.wait(timeout=5)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
try:
|
|
||||||
proc.kill()
|
|
||||||
try:
|
|
||||||
proc.wait(timeout=2)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
print(
|
|
||||||
f"[CameraController] FFmpeg process did not exit even after kill (pid={proc.pid})",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"[CameraController] failed to kill FFmpeg process: {e}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"[CameraController] error when stopping FFmpeg: {e}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
|
|
||||||
self._ffmpeg_process = None
|
|
||||||
|
|
||||||
# ------------------------ WebRTC Offer 相关 ------------------------
|
|
||||||
|
|
||||||
async def _handle_webrtc_offer(self, offer_sdp: str) -> str:
|
|
||||||
payload = {
|
|
||||||
"api": self.webrtc_api,
|
|
||||||
"streamurl": self.webrtc_stream_url,
|
|
||||||
"sdp": offer_sdp,
|
|
||||||
}
|
|
||||||
|
|
||||||
headers = {"Content-Type": "application/json"}
|
|
||||||
|
|
||||||
def _do_request():
|
|
||||||
return requests.post(
|
|
||||||
self.webrtc_api,
|
|
||||||
json=payload,
|
|
||||||
headers=headers,
|
|
||||||
timeout=10,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
resp = await loop.run_in_executor(None, _do_request)
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"[CameraController] failed to send offer to media server: {e}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
|
|
||||||
try:
|
|
||||||
resp.raise_for_status()
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"[CameraController] media server HTTP error: {e}, "
|
|
||||||
f"status={resp.status_code}, body={resp.text[:200]}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = resp.json()
|
|
||||||
except Exception as e:
|
|
||||||
print(
|
|
||||||
f"[CameraController] failed to parse media server JSON: {e}, "
|
|
||||||
f"raw={resp.text[:200]}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
|
|
||||||
answer_sdp = data.get("sdp", "")
|
|
||||||
if not answer_sdp:
|
|
||||||
msg = f"empty SDP from media server: {data}"
|
|
||||||
print(f"[CameraController] {msg}", file=sys.stderr)
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
return answer_sdp
|
|
||||||
@@ -1,401 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import asyncio
|
|
||||||
import json
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import threading
|
|
||||||
from typing import Optional, Dict, Any
|
|
||||||
|
|
||||||
import requests
|
|
||||||
import websockets
|
|
||||||
|
|
||||||
|
|
||||||
class CameraController:
|
|
||||||
"""
|
|
||||||
Uni-Lab-OS 摄像头驱动(Linux USB 摄像头版,无 PTZ)
|
|
||||||
|
|
||||||
- WebSocket 信令:signal_backend_url 连接到后端
|
|
||||||
例如: wss://sciol.ac.cn/api/realtime/signal/host/<host_id>
|
|
||||||
- 媒体服务器:RTMP 推流到 rtmp_url;WebRTC offer 转发到 SRS 的 webrtc_api
|
|
||||||
- 视频源:本地 USB 摄像头(V4L2,默认 /dev/video0)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
host_id: str = "demo-host",
|
|
||||||
signal_backend_url: str = "wss://sciol.ac.cn/api/realtime/signal/host",
|
|
||||||
rtmp_url: str = "rtmp://srs.sciol.ac.cn:4499/live/camera-01",
|
|
||||||
webrtc_api: str = "https://srs.sciol.ac.cn/rtc/v1/play/",
|
|
||||||
webrtc_stream_url: str = "webrtc://srs.sciol.ac.cn:4500/live/camera-01",
|
|
||||||
video_device: str = "/dev/video0",
|
|
||||||
width: int = 1280,
|
|
||||||
height: int = 720,
|
|
||||||
fps: int = 30,
|
|
||||||
video_bitrate: str = "1500k",
|
|
||||||
audio_device: Optional[str] = None, # 比如 "hw:1,0",没有音频就保持 None
|
|
||||||
audio_bitrate: str = "64k",
|
|
||||||
):
|
|
||||||
self.host_id = host_id
|
|
||||||
|
|
||||||
# 拼接最终 WebSocket URL:.../host/<host_id>
|
|
||||||
signal_backend_url = signal_backend_url.rstrip("/")
|
|
||||||
if not signal_backend_url.endswith("/host"):
|
|
||||||
signal_backend_url = signal_backend_url + "/host"
|
|
||||||
self.signal_backend_url = f"{signal_backend_url}/{host_id}"
|
|
||||||
|
|
||||||
# 媒体服务器配置
|
|
||||||
self.rtmp_url = rtmp_url
|
|
||||||
self.webrtc_api = webrtc_api
|
|
||||||
self.webrtc_stream_url = webrtc_stream_url
|
|
||||||
|
|
||||||
# 本地采集配置
|
|
||||||
self.video_device = video_device
|
|
||||||
self.width = int(width)
|
|
||||||
self.height = int(height)
|
|
||||||
self.fps = int(fps)
|
|
||||||
self.video_bitrate = video_bitrate
|
|
||||||
self.audio_device = audio_device
|
|
||||||
self.audio_bitrate = audio_bitrate
|
|
||||||
|
|
||||||
# 运行时状态
|
|
||||||
self._ws: Optional[object] = None
|
|
||||||
self._ffmpeg_process: Optional[subprocess.Popen] = None
|
|
||||||
self._running = False
|
|
||||||
self._loop_task: Optional[asyncio.Future] = None
|
|
||||||
|
|
||||||
# 事件循环 & 线程
|
|
||||||
self._loop: Optional[asyncio.AbstractEventLoop] = None
|
|
||||||
self._loop_thread: Optional[threading.Thread] = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.start()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[CameraController] __init__ auto start failed: {e}", file=sys.stderr)
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# 对外方法
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
def start(self, config: Optional[Dict[str, Any]] = None):
|
|
||||||
if self._running:
|
|
||||||
return {"status": "already_running", "host_id": self.host_id}
|
|
||||||
|
|
||||||
# 应用 config 覆盖(如果有)
|
|
||||||
if config:
|
|
||||||
cfg_host_id = config.get("host_id")
|
|
||||||
if cfg_host_id:
|
|
||||||
self.host_id = cfg_host_id
|
|
||||||
|
|
||||||
signal_backend_url = config.get("signal_backend_url")
|
|
||||||
if signal_backend_url:
|
|
||||||
signal_backend_url = signal_backend_url.rstrip("/")
|
|
||||||
if not signal_backend_url.endswith("/host"):
|
|
||||||
signal_backend_url = signal_backend_url + "/host"
|
|
||||||
self.signal_backend_url = f"{signal_backend_url}/{self.host_id}"
|
|
||||||
|
|
||||||
self.rtmp_url = config.get("rtmp_url", self.rtmp_url)
|
|
||||||
self.webrtc_api = config.get("webrtc_api", self.webrtc_api)
|
|
||||||
self.webrtc_stream_url = config.get("webrtc_stream_url", self.webrtc_stream_url)
|
|
||||||
|
|
||||||
self.video_device = config.get("video_device", self.video_device)
|
|
||||||
self.width = int(config.get("width", self.width))
|
|
||||||
self.height = int(config.get("height", self.height))
|
|
||||||
self.fps = int(config.get("fps", self.fps))
|
|
||||||
self.video_bitrate = config.get("video_bitrate", self.video_bitrate)
|
|
||||||
self.audio_device = config.get("audio_device", self.audio_device)
|
|
||||||
self.audio_bitrate = config.get("audio_bitrate", self.audio_bitrate)
|
|
||||||
|
|
||||||
self._running = True
|
|
||||||
|
|
||||||
print("[CameraController] start(): starting FFmpeg streaming...", file=sys.stderr)
|
|
||||||
self._start_ffmpeg()
|
|
||||||
|
|
||||||
self._loop = asyncio.new_event_loop()
|
|
||||||
|
|
||||||
def loop_runner(loop: asyncio.AbstractEventLoop):
|
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
try:
|
|
||||||
loop.run_forever()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[CameraController] event loop error: {e}", file=sys.stderr)
|
|
||||||
|
|
||||||
self._loop_thread = threading.Thread(target=loop_runner, args=(self._loop,), daemon=True)
|
|
||||||
self._loop_thread.start()
|
|
||||||
|
|
||||||
self._loop_task = asyncio.run_coroutine_threadsafe(self._run_main_loop(), self._loop)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"status": "started",
|
|
||||||
"host_id": self.host_id,
|
|
||||||
"signal_backend_url": self.signal_backend_url,
|
|
||||||
"rtmp_url": self.rtmp_url,
|
|
||||||
"webrtc_api": self.webrtc_api,
|
|
||||||
"webrtc_stream_url": self.webrtc_stream_url,
|
|
||||||
"video_device": self.video_device,
|
|
||||||
"width": self.width,
|
|
||||||
"height": self.height,
|
|
||||||
"fps": self.fps,
|
|
||||||
"video_bitrate": self.video_bitrate,
|
|
||||||
"audio_device": self.audio_device,
|
|
||||||
}
|
|
||||||
|
|
||||||
def stop(self) -> Dict[str, Any]:
|
|
||||||
self._running = False
|
|
||||||
|
|
||||||
# 先取消主任务(让 ws connect/sleep 尽快退出)
|
|
||||||
if self._loop_task is not None and not self._loop_task.done():
|
|
||||||
self._loop_task.cancel()
|
|
||||||
|
|
||||||
# 停止推流
|
|
||||||
self._stop_ffmpeg()
|
|
||||||
|
|
||||||
# 关闭 WebSocket(在 loop 中执行)
|
|
||||||
if self._ws and self._loop is not None:
|
|
||||||
|
|
||||||
async def close_ws():
|
|
||||||
try:
|
|
||||||
await self._ws.close()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[CameraController] error closing WebSocket: {e}", file=sys.stderr)
|
|
||||||
|
|
||||||
try:
|
|
||||||
asyncio.run_coroutine_threadsafe(close_ws(), self._loop)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# 停止事件循环
|
|
||||||
if self._loop is not None:
|
|
||||||
try:
|
|
||||||
self._loop.call_soon_threadsafe(self._loop.stop)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[CameraController] error stopping loop: {e}", file=sys.stderr)
|
|
||||||
|
|
||||||
# 等待线程退出
|
|
||||||
if self._loop_thread is not None:
|
|
||||||
try:
|
|
||||||
self._loop_thread.join(timeout=5)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[CameraController] error joining loop thread: {e}", file=sys.stderr)
|
|
||||||
|
|
||||||
self._ws = None
|
|
||||||
self._loop_task = None
|
|
||||||
self._loop = None
|
|
||||||
self._loop_thread = None
|
|
||||||
|
|
||||||
return {"status": "stopped", "host_id": self.host_id}
|
|
||||||
|
|
||||||
def get_status(self) -> Dict[str, Any]:
|
|
||||||
ws_closed = None
|
|
||||||
if self._ws is not None:
|
|
||||||
ws_closed = getattr(self._ws, "closed", None)
|
|
||||||
|
|
||||||
if ws_closed is None:
|
|
||||||
websocket_connected = self._ws is not None
|
|
||||||
else:
|
|
||||||
websocket_connected = (self._ws is not None) and (not ws_closed)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"host_id": self.host_id,
|
|
||||||
"running": self._running,
|
|
||||||
"websocket_connected": websocket_connected,
|
|
||||||
"ffmpeg_running": bool(self._ffmpeg_process and self._ffmpeg_process.poll() is None),
|
|
||||||
"signal_backend_url": self.signal_backend_url,
|
|
||||||
"rtmp_url": self.rtmp_url,
|
|
||||||
"video_device": self.video_device,
|
|
||||||
"width": self.width,
|
|
||||||
"height": self.height,
|
|
||||||
"fps": self.fps,
|
|
||||||
"video_bitrate": self.video_bitrate,
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# WebSocket / 信令
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
async def _run_main_loop(self):
|
|
||||||
print("[CameraController] main loop started", file=sys.stderr)
|
|
||||||
try:
|
|
||||||
while self._running:
|
|
||||||
try:
|
|
||||||
async with websockets.connect(self.signal_backend_url) as ws:
|
|
||||||
self._ws = ws
|
|
||||||
print(f"[CameraController] WebSocket connected: {self.signal_backend_url}", file=sys.stderr)
|
|
||||||
await self._recv_loop()
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
raise
|
|
||||||
except Exception as e:
|
|
||||||
if self._running:
|
|
||||||
print(f"[CameraController] WebSocket connection error: {e}", file=sys.stderr)
|
|
||||||
await asyncio.sleep(3)
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
print("[CameraController] main loop exited", file=sys.stderr)
|
|
||||||
|
|
||||||
async def _recv_loop(self):
|
|
||||||
assert self._ws is not None
|
|
||||||
ws = self._ws
|
|
||||||
|
|
||||||
async for message in ws:
|
|
||||||
try:
|
|
||||||
data = json.loads(message)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
print(f"[CameraController] non-JSON message: {message}", file=sys.stderr)
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
await self._handle_message(data)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[CameraController] error handling message {data}: {e}", file=sys.stderr)
|
|
||||||
|
|
||||||
async def _handle_message(self, data: Dict[str, Any]):
|
|
||||||
cmd = data.get("command")
|
|
||||||
|
|
||||||
if cmd == "start_stream":
|
|
||||||
self._start_ffmpeg()
|
|
||||||
return
|
|
||||||
|
|
||||||
if cmd == "stop_stream":
|
|
||||||
self._stop_ffmpeg()
|
|
||||||
return
|
|
||||||
|
|
||||||
if data.get("type") == "offer":
|
|
||||||
offer_sdp = data.get("sdp", "")
|
|
||||||
camera_id = data.get("cameraId", "camera-01")
|
|
||||||
|
|
||||||
answer_sdp = await self._handle_webrtc_offer(offer_sdp)
|
|
||||||
|
|
||||||
if self._ws:
|
|
||||||
answer_payload = {
|
|
||||||
"type": "answer",
|
|
||||||
"sdp": answer_sdp,
|
|
||||||
"cameraId": camera_id,
|
|
||||||
"hostId": self.host_id,
|
|
||||||
}
|
|
||||||
await self._ws.send(json.dumps(answer_payload))
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# FFmpeg 推流(V4L2 USB 摄像头)
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _start_ffmpeg(self):
|
|
||||||
if self._ffmpeg_process and self._ffmpeg_process.poll() is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
# 兼容性优先:不强制输入像素格式;失败再通过外部调整 width/height/fps
|
|
||||||
video_size = f"{self.width}x{self.height}"
|
|
||||||
|
|
||||||
cmd = [
|
|
||||||
"ffmpeg",
|
|
||||||
"-hide_banner",
|
|
||||||
"-loglevel",
|
|
||||||
"warning",
|
|
||||||
|
|
||||||
# video input
|
|
||||||
"-f", "v4l2",
|
|
||||||
"-framerate", str(self.fps),
|
|
||||||
"-video_size", video_size,
|
|
||||||
"-i", self.video_device,
|
|
||||||
]
|
|
||||||
|
|
||||||
# optional audio input
|
|
||||||
if self.audio_device:
|
|
||||||
cmd += [
|
|
||||||
"-f", "alsa",
|
|
||||||
"-i", self.audio_device,
|
|
||||||
"-c:a", "aac",
|
|
||||||
"-b:a", self.audio_bitrate,
|
|
||||||
"-ar", "44100",
|
|
||||||
"-ac", "1",
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
cmd += ["-an"]
|
|
||||||
|
|
||||||
# video encode + rtmp out
|
|
||||||
cmd += [
|
|
||||||
"-c:v", "libx264",
|
|
||||||
"-preset", "ultrafast",
|
|
||||||
"-tune", "zerolatency",
|
|
||||||
"-profile:v", "baseline",
|
|
||||||
"-pix_fmt", "yuv420p",
|
|
||||||
"-b:v", self.video_bitrate,
|
|
||||||
"-maxrate", self.video_bitrate,
|
|
||||||
"-bufsize", "2M",
|
|
||||||
"-g", str(max(self.fps, 10)),
|
|
||||||
"-keyint_min", str(max(self.fps, 10)),
|
|
||||||
"-sc_threshold", "0",
|
|
||||||
"-x264-params", "bframes=0",
|
|
||||||
|
|
||||||
"-f", "flv",
|
|
||||||
self.rtmp_url,
|
|
||||||
]
|
|
||||||
|
|
||||||
print(f"[CameraController] starting FFmpeg: {' '.join(cmd)}", file=sys.stderr)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 不再丢弃日志,至少能看到 ffmpeg 报错(调试很关键)
|
|
||||||
self._ffmpeg_process = subprocess.Popen(
|
|
||||||
cmd,
|
|
||||||
stdout=subprocess.DEVNULL,
|
|
||||||
stderr=sys.stderr,
|
|
||||||
shell=False,
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
self._ffmpeg_process = None
|
|
||||||
print(f"[CameraController] failed to start FFmpeg: {e}", file=sys.stderr)
|
|
||||||
|
|
||||||
def _stop_ffmpeg(self):
|
|
||||||
proc = self._ffmpeg_process
|
|
||||||
if proc and proc.poll() is None:
|
|
||||||
try:
|
|
||||||
proc.terminate()
|
|
||||||
try:
|
|
||||||
proc.wait(timeout=5)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
proc.kill()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[CameraController] error stopping FFmpeg: {e}", file=sys.stderr)
|
|
||||||
self._ffmpeg_process = None
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
# WebRTC offer -> SRS
|
|
||||||
# ---------------------------------------------------------------------
|
|
||||||
|
|
||||||
async def _handle_webrtc_offer(self, offer_sdp: str) -> str:
|
|
||||||
payload = {
|
|
||||||
"api": self.webrtc_api,
|
|
||||||
"streamurl": self.webrtc_stream_url,
|
|
||||||
"sdp": offer_sdp,
|
|
||||||
}
|
|
||||||
headers = {"Content-Type": "application/json"}
|
|
||||||
|
|
||||||
def _do_post():
|
|
||||||
return requests.post(self.webrtc_api, json=payload, headers=headers, timeout=10)
|
|
||||||
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
resp = await loop.run_in_executor(None, _do_post)
|
|
||||||
|
|
||||||
resp.raise_for_status()
|
|
||||||
data = resp.json()
|
|
||||||
answer_sdp = data.get("sdp", "")
|
|
||||||
if not answer_sdp:
|
|
||||||
raise RuntimeError(f"empty SDP from media server: {data}")
|
|
||||||
return answer_sdp
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# 直接运行用于手动测试
|
|
||||||
c = CameraController(
|
|
||||||
host_id="demo-host",
|
|
||||||
video_device="/dev/video0",
|
|
||||||
width=1280,
|
|
||||||
height=720,
|
|
||||||
fps=30,
|
|
||||||
video_bitrate="1500k",
|
|
||||||
audio_device=None,
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
asyncio.sleep(1)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
c.stop()
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import time
|
|
||||||
import json
|
|
||||||
|
|
||||||
from cameraUSB import CameraController
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# 按你的实际情况改
|
|
||||||
cfg = dict(
|
|
||||||
host_id="demo-host",
|
|
||||||
signal_backend_url="wss://sciol.ac.cn/api/realtime/signal/host",
|
|
||||||
rtmp_url="rtmp://srs.sciol.ac.cn:4499/live/camera-01",
|
|
||||||
webrtc_api="https://srs.sciol.ac.cn/rtc/v1/play/",
|
|
||||||
webrtc_stream_url="webrtc://srs.sciol.ac.cn:4500/live/camera-01",
|
|
||||||
video_device="/dev/video7",
|
|
||||||
width=1280,
|
|
||||||
height=720,
|
|
||||||
fps=30,
|
|
||||||
video_bitrate="1500k",
|
|
||||||
audio_device=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
c = CameraController(**cfg)
|
|
||||||
|
|
||||||
# 可选:如果你不想依赖 __init__ 自动 start,可以这样显式调用:
|
|
||||||
# c = CameraController(host_id=cfg["host_id"])
|
|
||||||
# c.start(cfg)
|
|
||||||
|
|
||||||
run_seconds = 30 # 测试运行时长
|
|
||||||
t0 = time.time()
|
|
||||||
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
st = c.get_status()
|
|
||||||
print(json.dumps(st, ensure_ascii=False, indent=2))
|
|
||||||
|
|
||||||
if time.time() - t0 >= run_seconds:
|
|
||||||
break
|
|
||||||
|
|
||||||
time.sleep(2)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("Interrupted, stopping...")
|
|
||||||
finally:
|
|
||||||
print("Stopping controller...")
|
|
||||||
c.stop()
|
|
||||||
print("Done.")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import cv2
|
|
||||||
|
|
||||||
# 推荐把 @ 进行 URL 编码:@ -> %40
|
|
||||||
RTSP_URL = "rtsp://admin:admin123@192.168.31.164:554/stream1"
|
|
||||||
OUTPUT_IMAGE = "rtsp_test_frame.jpg"
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print(f"尝试连接 RTSP 流: {RTSP_URL}")
|
|
||||||
cap = cv2.VideoCapture(RTSP_URL)
|
|
||||||
|
|
||||||
if not cap.isOpened():
|
|
||||||
print("错误:无法打开 RTSP 流,请检查:")
|
|
||||||
print(" 1. IP/端口是否正确")
|
|
||||||
print(" 2. 账号密码(尤其是 @ 是否已转成 %40)是否正确")
|
|
||||||
print(" 3. 摄像头是否允许当前主机访问(同一网段、防火墙等)")
|
|
||||||
return
|
|
||||||
|
|
||||||
print("连接成功,开始读取一帧...")
|
|
||||||
ret, frame = cap.read()
|
|
||||||
|
|
||||||
if not ret or frame is None:
|
|
||||||
print("错误:已连接但未能读取到帧数据(可能是码流未开启或网络抖动)")
|
|
||||||
cap.release()
|
|
||||||
return
|
|
||||||
|
|
||||||
# 保存当前帧
|
|
||||||
success = cv2.imwrite(OUTPUT_IMAGE, frame)
|
|
||||||
cap.release()
|
|
||||||
|
|
||||||
if success:
|
|
||||||
print(f"成功截取一帧并保存为: {OUTPUT_IMAGE}")
|
|
||||||
else:
|
|
||||||
print("错误:写入图片失败,请检查磁盘权限/路径")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# run_camera_push.py
|
|
||||||
import time
|
|
||||||
from cameraDriver import CameraController # 这里根据你的文件名调整
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
controller = CameraController(
|
|
||||||
host_id="demo-host",
|
|
||||||
signal_backend_url="wss://sciol.ac.cn/api/realtime/signal/host",
|
|
||||||
rtmp_url="rtmp://srs.sciol.ac.cn:4499/live/camera-01",
|
|
||||||
webrtc_api="https://srs.sciol.ac.cn/rtc/v1/play/",
|
|
||||||
webrtc_stream_url="webrtc://srs.sciol.ac.cn:4500/live/camera-01",
|
|
||||||
camera_rtsp_url="rtsp://admin:admin123@192.168.31.164:554/stream1",
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
status = controller.get_status()
|
|
||||||
print(status)
|
|
||||||
time.sleep(5)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
controller.stop()
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
使用 CameraController 来测试 PTZ:
|
|
||||||
让摄像头按顺序向下、向上、向左、向右运动几次。
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# 根据你的工程结构修改导入路径:
|
|
||||||
# 假设 CameraController 定义在 cameraController.py 里
|
|
||||||
from cameraDriver import CameraController
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# === 根据你的实际情况填 IP、端口、账号密码 ===
|
|
||||||
ptz_host = "192.168.31.164"
|
|
||||||
ptz_port = 2020 # 注意要和你单独测试 PTZController 时保持一致
|
|
||||||
ptz_user = "admin"
|
|
||||||
ptz_password = "admin123"
|
|
||||||
|
|
||||||
# 1. 创建 CameraController 实例
|
|
||||||
cam = CameraController(
|
|
||||||
# 其他摄像机相关参数按你类的 __init__ 来补充
|
|
||||||
ptz_host=ptz_host,
|
|
||||||
ptz_port=ptz_port,
|
|
||||||
ptz_user=ptz_user,
|
|
||||||
ptz_password=ptz_password,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 2. 启动 / 初始化(如果你的 CameraController 有 start(config) 之类的接口)
|
|
||||||
# 这里给一个最小的 config,重点是 PTZ 相关字段
|
|
||||||
config = {
|
|
||||||
"ptz_host": ptz_host,
|
|
||||||
"ptz_port": ptz_port,
|
|
||||||
"ptz_user": ptz_user,
|
|
||||||
"ptz_password": ptz_password,
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
cam.start(config)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"[TEST] CameraController start() 失败: {e}", file=sys.stderr)
|
|
||||||
return
|
|
||||||
|
|
||||||
# 这里可以判断一下内部 _ptz 是否初始化成功(如果你对 CameraController 做了封装)
|
|
||||||
if getattr(cam, "_ptz", None) is None:
|
|
||||||
print("[TEST] CameraController 内部 PTZ 未初始化成功,请检查 ptz_host/port/user/password 配置。", file=sys.stderr)
|
|
||||||
return
|
|
||||||
|
|
||||||
# 3. 依次调用 CameraController 的 PTZ 方法
|
|
||||||
# 这里假设你在 CameraController 中提供了这几个对外方法:
|
|
||||||
# ptz_move_down / ptz_move_up / ptz_move_left / ptz_move_right
|
|
||||||
# 如果你命名不一样,把下面调用名改成你的即可。
|
|
||||||
|
|
||||||
print("向下移动(通过 CameraController)...")
|
|
||||||
cam.ptz_move_down(speed=0.5, duration=1.0)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
print("向上移动(通过 CameraController)...")
|
|
||||||
cam.ptz_move_up(speed=0.5, duration=1.0)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
print("向左移动(通过 CameraController)...")
|
|
||||||
cam.ptz_move_left(speed=0.5, duration=1.0)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
print("向右移动(通过 CameraController)...")
|
|
||||||
cam.ptz_move_right(speed=0.5, duration=1.0)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
print("测试结束。")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
|
||||||
测试 cameraDriver.py中的 PTZController 类,让摄像头按顺序运动几次
|
|
||||||
"""
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
from cameraDriver import PTZController
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# 根据你的实际情况填 IP、端口、账号密码
|
|
||||||
host = "192.168.31.164"
|
|
||||||
port = 80
|
|
||||||
user = "admin"
|
|
||||||
password = "admin123"
|
|
||||||
|
|
||||||
ptz = PTZController(host=host, port=port, user=user, password=password)
|
|
||||||
|
|
||||||
# 1. 连接摄像头
|
|
||||||
if not ptz.connect():
|
|
||||||
print("连接 PTZ 失败,检查 IP/用户名/密码/端口。")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 2. 依次测试几个动作
|
|
||||||
# 每个动作之间 sleep 一下方便观察
|
|
||||||
|
|
||||||
print("向下移动...")
|
|
||||||
ptz.move_down(speed=0.5, duration=1.0)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
print("向上移动...")
|
|
||||||
ptz.move_up(speed=0.5, duration=1.0)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
print("向左移动...")
|
|
||||||
ptz.move_left(speed=0.5, duration=1.0)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
print("向右移动...")
|
|
||||||
ptz.move_right(speed=0.5, duration=1.0)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
print("测试结束。")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -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()
|
||||||
@@ -1,954 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"uuid": "3b6f33ffbf734014bcc20e3c63e124d4",
|
|
||||||
"Code": "ZX-58-1250",
|
|
||||||
"Name": "Tip头适配器 1250uL",
|
|
||||||
"SummaryName": "Tip头适配器 1250uL",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 128,
|
|
||||||
"WidthNum": 85,
|
|
||||||
"HeightNum": 20,
|
|
||||||
"DepthNum": 4,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 1,
|
|
||||||
"HoleRow": 1,
|
|
||||||
"HoleDiameter": 0,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": "/images/20220624015044.jpg",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 10,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 16:03:52.6583727",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-06-24 13:50:44.8123474",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": null,
|
|
||||||
"YSpacing": null,
|
|
||||||
"materialEnum": null,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "7c822592b360451fb59690e49ac6b181",
|
|
||||||
"Code": "ZX-58-300",
|
|
||||||
"Name": "ZHONGXI 适配器 300uL",
|
|
||||||
"SummaryName": "ZHONGXI 适配器 300uL",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 127,
|
|
||||||
"WidthNum": 85,
|
|
||||||
"HeightNum": 81,
|
|
||||||
"DepthNum": 4,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 1,
|
|
||||||
"HoleRow": 1,
|
|
||||||
"HoleDiameter": 0,
|
|
||||||
"Volume": 300,
|
|
||||||
"ImagePath": "/images/20220623102838.jpg",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 10,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 16:07:53.7453351",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-06-23 10:28:38.6190575",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": null,
|
|
||||||
"YSpacing": null,
|
|
||||||
"materialEnum": null,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "8cc3dce884ac41c09f4570d0bcbfb01c",
|
|
||||||
"Code": "ZX-58-10",
|
|
||||||
"Name": "吸头10ul 适配器",
|
|
||||||
"SummaryName": "吸头10ul 适配器",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 128,
|
|
||||||
"WidthNum": 85,
|
|
||||||
"HeightNum": 72.3,
|
|
||||||
"DepthNum": 0,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": 0,
|
|
||||||
"HoleColum": 1,
|
|
||||||
"HoleRow": 1,
|
|
||||||
"HoleDiameter": 127,
|
|
||||||
"Volume": 1000,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 10,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 16:37:40.7073733",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-05-30 15:17:01.8231737",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 0,
|
|
||||||
"YSpacing": 0,
|
|
||||||
"materialEnum": 0,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "7960f49ddfe9448abadda89bd1556936",
|
|
||||||
"Code": "ZX-001-1250",
|
|
||||||
"Name": "1250μL Tip头",
|
|
||||||
"SummaryName": "1250μL Tip头",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 118.09,
|
|
||||||
"WidthNum": 80.7,
|
|
||||||
"HeightNum": 107.67,
|
|
||||||
"DepthNum": 100,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 7.95,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": "/images/20220623102536.jpg",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 96,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 20:53:27.8591195",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-06-23 10:25:36.2592442",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "45f2ed3ad925484d96463d675a0ebf66",
|
|
||||||
"Code": "ZX-001-10",
|
|
||||||
"Name": "10μL Tip头",
|
|
||||||
"SummaryName": "10μL Tip头",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 120.98,
|
|
||||||
"WidthNum": 82.12,
|
|
||||||
"HeightNum": 67,
|
|
||||||
"DepthNum": 39.1,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 5,
|
|
||||||
"Volume": 10,
|
|
||||||
"ImagePath": "/images/20221119041031.jpg",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": -21,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 20:56:53.462015",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-11-19 16:10:31.126801",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "068b3815e36b4a72a59bae017011b29f",
|
|
||||||
"Code": "ZX-001-10+",
|
|
||||||
"Name": "10μL加长 Tip头",
|
|
||||||
"SummaryName": "10μL加长 Tip头",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 122.11,
|
|
||||||
"WidthNum": 80.05,
|
|
||||||
"HeightNum": 58.23,
|
|
||||||
"DepthNum": 45.1,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": 60,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 7,
|
|
||||||
"Volume": 10,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 42,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 20:57:57.331211",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-09-17 17:02:51.2070383",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": 1,
|
|
||||||
"Margins_X": 7.97,
|
|
||||||
"Margins_Y": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "80652665f6a54402b2408d50b40398df",
|
|
||||||
"Code": "ZX-001-1000",
|
|
||||||
"Name": "1000μL Tip头",
|
|
||||||
"SummaryName": "1000μL Tip头",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 128.09,
|
|
||||||
"WidthNum": 85.8,
|
|
||||||
"HeightNum": 98,
|
|
||||||
"DepthNum": 88,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": 100,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 7.95,
|
|
||||||
"Volume": 1000,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 47,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 20:59:20.5534915",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-05-30 14:49:53.639727",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": 1,
|
|
||||||
"Margins_X": 14.5,
|
|
||||||
"Margins_Y": 11.4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "076250742950465b9d6ea29a225dfb00",
|
|
||||||
"Code": "ZX-001-300",
|
|
||||||
"Name": "300μL Tip头",
|
|
||||||
"SummaryName": "300μL Tip头",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 122.11,
|
|
||||||
"WidthNum": 80.05,
|
|
||||||
"HeightNum": 58.23,
|
|
||||||
"DepthNum": 45.1,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": 60,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 7,
|
|
||||||
"Volume": 300,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 11,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 21:00:24.7266192",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-09-17 17:02:40.6676947",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": 1,
|
|
||||||
"Margins_X": 7.97,
|
|
||||||
"Margins_Y": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "7a73bb9e5c264515a8fcbe88aed0e6f7",
|
|
||||||
"Code": "ZX-001-200",
|
|
||||||
"Name": "200μL Tip头",
|
|
||||||
"SummaryName": "200μL Tip头",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 120.98,
|
|
||||||
"WidthNum": 82.12,
|
|
||||||
"HeightNum": 66.9,
|
|
||||||
"DepthNum": 52,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": 30,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 5.5,
|
|
||||||
"Volume": 200,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 19,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 21:01:17.626704",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-05-27 11:42:24.6021522",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": 0,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "73bb9b10bc394978b70e027bf45ce2d3",
|
|
||||||
"Code": "ZX-023-0.2",
|
|
||||||
"Name": "0.2ml PCR板",
|
|
||||||
"SummaryName": "0.2ml PCR板",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 126,
|
|
||||||
"WidthNum": 86,
|
|
||||||
"HeightNum": 21.2,
|
|
||||||
"DepthNum": 15.17,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 6,
|
|
||||||
"Volume": 1000,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": -12,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 21:06:02.7746392",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2024-02-20 16:17:16.7921748",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "ca877b8b114a4310b429d1de4aae96ee",
|
|
||||||
"Code": "ZX-019-2.2",
|
|
||||||
"Name": "2.2ml 深孔板",
|
|
||||||
"SummaryName": "2.2ml 深孔板",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 127.3,
|
|
||||||
"WidthNum": 85.35,
|
|
||||||
"HeightNum": 44,
|
|
||||||
"DepthNum": 42,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 8.2,
|
|
||||||
"Volume": 2200,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 34,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 21:07:16.4538022",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-08-12 13:11:26.3993472",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "04211a2dc93547fe9bf6121eac533650",
|
|
||||||
"Code": "ZX-58-10000",
|
|
||||||
"Name": "储液槽",
|
|
||||||
"SummaryName": "储液槽",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 125.02,
|
|
||||||
"WidthNum": 82.97,
|
|
||||||
"HeightNum": 31.2,
|
|
||||||
"DepthNum": 24,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": 0,
|
|
||||||
"HoleColum": 1,
|
|
||||||
"HoleRow": 1,
|
|
||||||
"HoleDiameter": 99.33,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": -172,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-31 18:37:56.7949909",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-09-17 17:22:22.8543991",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": 0,
|
|
||||||
"Margins_X": 8.5,
|
|
||||||
"Margins_Y": 5.5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "4a043a07c65a4f9bb97745e1f129b165",
|
|
||||||
"Code": "ZX-58-0001",
|
|
||||||
"Name": "全裙边 PCR适配器",
|
|
||||||
"SummaryName": "全裙边 PCR适配器",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 125.42,
|
|
||||||
"WidthNum": 83.13,
|
|
||||||
"HeightNum": 15.69,
|
|
||||||
"DepthNum": 13.41,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": 0,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 5.1,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 100,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2022-01-02 19:21:35.8664843",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-09-17 17:14:36.1210193",
|
|
||||||
"IsStright": 1,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": 3,
|
|
||||||
"Margins_X": 9.78,
|
|
||||||
"Margins_Y": 7.72
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "6bdfdd7069df453896b0806df50f2f4d",
|
|
||||||
"Code": "ZX-ADP-001",
|
|
||||||
"Name": "储液槽 适配器",
|
|
||||||
"SummaryName": "储液槽 适配器",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 133,
|
|
||||||
"WidthNum": 91.8,
|
|
||||||
"HeightNum": 70,
|
|
||||||
"DepthNum": 4,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 1,
|
|
||||||
"HoleRow": 1,
|
|
||||||
"HoleDiameter": 1,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2022-02-16 17:31:26.413594",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-08-12 13:10:58.786996",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 0,
|
|
||||||
"YSpacing": 0,
|
|
||||||
"materialEnum": null,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "9a439bed8f3344549643d6b3bc5a5eb4",
|
|
||||||
"Code": "ZX-002-300",
|
|
||||||
"Name": "300ul深孔板适配器",
|
|
||||||
"SummaryName": "300ul深孔板适配器",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 136.4,
|
|
||||||
"WidthNum": 93.8,
|
|
||||||
"HeightNum": 96,
|
|
||||||
"DepthNum": 7,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 8.1,
|
|
||||||
"Volume": 300,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2022-06-18 15:17:42.7917763",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-08-12 13:10:46.1526635",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "4dc8d6ecfd0449549683b8ef815a861b",
|
|
||||||
"Code": "ZX-002-10",
|
|
||||||
"Name": "10ul专用深孔板适配器",
|
|
||||||
"SummaryName": "10ul专用深孔板适配器",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 136.5,
|
|
||||||
"WidthNum": 93.8,
|
|
||||||
"HeightNum": 121.5,
|
|
||||||
"DepthNum": 7,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 8.1,
|
|
||||||
"Volume": 10,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2022-06-30 09:37:31.0451435",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-08-12 13:10:38.5409878",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "b01627718d3341aba649baa81c2c083c",
|
|
||||||
"Code": "Sd155",
|
|
||||||
"Name": "爱津",
|
|
||||||
"SummaryName": "爱津",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 125,
|
|
||||||
"WidthNum": 85,
|
|
||||||
"HeightNum": 64,
|
|
||||||
"DepthNum": 45.5,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 4,
|
|
||||||
"Volume": 20,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2022-11-07 08:56:30.1794274",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-11-07 09:00:29.5496845",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": null,
|
|
||||||
"YSpacing": null,
|
|
||||||
"materialEnum": null,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "adfabfffa8f24af5abfbba67b8d0f973",
|
|
||||||
"Code": "Fhh478",
|
|
||||||
"Name": "适配器",
|
|
||||||
"SummaryName": "适配器",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 120,
|
|
||||||
"WidthNum": 90,
|
|
||||||
"HeightNum": 86,
|
|
||||||
"DepthNum": 4,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 1,
|
|
||||||
"HoleRow": 1,
|
|
||||||
"HoleDiameter": 4,
|
|
||||||
"Volume": 1000,
|
|
||||||
"ImagePath": null,
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2022-11-07 09:00:10.7579131",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-11-07 09:00:10.7579134",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": null,
|
|
||||||
"YSpacing": null,
|
|
||||||
"materialEnum": null,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "730067cf07ae43849ddf4034299030e9",
|
|
||||||
"Code": "q1",
|
|
||||||
"Name": "废弃槽",
|
|
||||||
"SummaryName": "废弃槽",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 126.59,
|
|
||||||
"WidthNum": 84.87,
|
|
||||||
"HeightNum": 103.17,
|
|
||||||
"DepthNum": 80,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": 0,
|
|
||||||
"HoleColum": 1,
|
|
||||||
"HoleRow": 1,
|
|
||||||
"HoleDiameter": 1,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2023-10-14 13:15:45.8172852",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-09-17 17:06:18.3331101",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 1,
|
|
||||||
"YSpacing": 1,
|
|
||||||
"materialEnum": 0,
|
|
||||||
"Margins_X": 2.29,
|
|
||||||
"Margins_Y": 2.64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
|
||||||
"Code": "q2",
|
|
||||||
"Name": "96深孔板",
|
|
||||||
"SummaryName": "96深孔板",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 127.3,
|
|
||||||
"WidthNum": 85.35,
|
|
||||||
"HeightNum": 44,
|
|
||||||
"DepthNum": 42,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": 1,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 8.2,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2023-10-14 13:19:55.7225524",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-07-03 17:28:59.0082394",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": 0,
|
|
||||||
"Margins_X": 15,
|
|
||||||
"Margins_Y": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "853dcfb6226f476e8b23c250217dc7da",
|
|
||||||
"Code": "q3",
|
|
||||||
"Name": "384板",
|
|
||||||
"SummaryName": "384板",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 126.6,
|
|
||||||
"WidthNum": 84,
|
|
||||||
"HeightNum": 9.4,
|
|
||||||
"DepthNum": 8,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 24,
|
|
||||||
"HoleRow": 16,
|
|
||||||
"HoleDiameter": 3,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": null,
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2023-10-14 13:22:34.779818",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-10-14 13:22:34.7798181",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 4.5,
|
|
||||||
"YSpacing": 4.5,
|
|
||||||
"materialEnum": null,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "01953864f6f140ccaa8ddffd4f3e46f5",
|
|
||||||
"Code": "sdfrth654",
|
|
||||||
"Name": "4道储液槽",
|
|
||||||
"SummaryName": "4道储液槽",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 100,
|
|
||||||
"WidthNum": 40,
|
|
||||||
"HeightNum": 30,
|
|
||||||
"DepthNum": 10,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 4,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 4,
|
|
||||||
"Volume": 1000,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2024-02-20 14:44:25.0021372",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-03-31 15:09:30.7392062",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 27,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": 0,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "026c5d5cf3d94e56b4e16b7fb53a995b",
|
|
||||||
"Code": "22",
|
|
||||||
"Name": "48孔深孔板",
|
|
||||||
"SummaryName": "48孔深孔板",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "",
|
|
||||||
"LengthNum": null,
|
|
||||||
"WidthNum": null,
|
|
||||||
"HeightNum": null,
|
|
||||||
"DepthNum": null,
|
|
||||||
"StandardHeight": null,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 6,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": null,
|
|
||||||
"Volume": 23,
|
|
||||||
"ImagePath": null,
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2025-03-19 09:38:09.8535874",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-03-19 09:38:09.8536386",
|
|
||||||
"IsStright": null,
|
|
||||||
"IsGeneral": null,
|
|
||||||
"IsControl": null,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 18.5,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": 2,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "0f1639987b154e1fac78f4fb29a1f7c1",
|
|
||||||
"Code": "12道储液槽",
|
|
||||||
"Name": "12道储液槽",
|
|
||||||
"SummaryName": "12道储液槽",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "",
|
|
||||||
"LengthNum": 129.5,
|
|
||||||
"WidthNum": 83.047,
|
|
||||||
"HeightNum": 30.6,
|
|
||||||
"DepthNum": 26.7,
|
|
||||||
"StandardHeight": null,
|
|
||||||
"PipetteHeight": 0,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 8.04,
|
|
||||||
"Volume": 12,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2025-05-21 13:10:53.2735971",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-09-17 17:20:40.4460256",
|
|
||||||
"IsStright": null,
|
|
||||||
"IsGeneral": null,
|
|
||||||
"IsControl": null,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": 0,
|
|
||||||
"Margins_X": 8.7,
|
|
||||||
"Margins_Y": 5.35
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "548bbc3df0d4447586f2c19d2c0c0c55",
|
|
||||||
"Code": "HPLC01",
|
|
||||||
"Name": "HPLC料盘",
|
|
||||||
"SummaryName": "HPLC料盘",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "",
|
|
||||||
"LengthNum": 0,
|
|
||||||
"WidthNum": 0,
|
|
||||||
"HeightNum": 0,
|
|
||||||
"DepthNum": 0,
|
|
||||||
"StandardHeight": null,
|
|
||||||
"PipetteHeight": 0,
|
|
||||||
"HoleColum": 7,
|
|
||||||
"HoleRow": 15,
|
|
||||||
"HoleDiameter": 0,
|
|
||||||
"Volume": 1,
|
|
||||||
"ImagePath": null,
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2025-07-12 17:10:43.2660127",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-07-12 17:10:43.2660131",
|
|
||||||
"IsStright": null,
|
|
||||||
"IsGeneral": null,
|
|
||||||
"IsControl": null,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 12.5,
|
|
||||||
"YSpacing": 16.5,
|
|
||||||
"materialEnum": 0,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "e146697c395e4eabb3d6b74f0dd6aaf7",
|
|
||||||
"Code": "1",
|
|
||||||
"Name": "ep适配器",
|
|
||||||
"SummaryName": "ep适配器",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "",
|
|
||||||
"LengthNum": 128.04,
|
|
||||||
"WidthNum": 85.8,
|
|
||||||
"HeightNum": 42.66,
|
|
||||||
"DepthNum": 38.08,
|
|
||||||
"StandardHeight": null,
|
|
||||||
"PipetteHeight": 0,
|
|
||||||
"HoleColum": 6,
|
|
||||||
"HoleRow": 4,
|
|
||||||
"HoleDiameter": 10.6,
|
|
||||||
"Volume": 1,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2025-09-03 13:31:54.1541015",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-09-17 17:18:03.8051993",
|
|
||||||
"IsStright": null,
|
|
||||||
"IsGeneral": null,
|
|
||||||
"IsControl": null,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 21,
|
|
||||||
"YSpacing": 18,
|
|
||||||
"materialEnum": 0,
|
|
||||||
"Margins_X": 3.54,
|
|
||||||
"Margins_Y": 10.5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "a0757a90d8e44e81a68f306a608694f2",
|
|
||||||
"Code": "ZX-58-30",
|
|
||||||
"Name": "30mm适配器",
|
|
||||||
"SummaryName": "30mm适配器",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "",
|
|
||||||
"LengthNum": 132,
|
|
||||||
"WidthNum": 93.5,
|
|
||||||
"HeightNum": 30,
|
|
||||||
"DepthNum": 7,
|
|
||||||
"StandardHeight": null,
|
|
||||||
"PipetteHeight": 0,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 8.1,
|
|
||||||
"Volume": 30,
|
|
||||||
"ImagePath": null,
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2025-09-15 14:02:30.8094658",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-09-15 14:02:30.8098183",
|
|
||||||
"IsStright": null,
|
|
||||||
"IsGeneral": null,
|
|
||||||
"IsControl": null,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": 0,
|
|
||||||
"Margins_X": 0,
|
|
||||||
"Margins_Y": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "b05b3b2aafd94ec38ea0cd3215ecea8f",
|
|
||||||
"Code": "ZX-78-096",
|
|
||||||
"Name": "细菌培养皿",
|
|
||||||
"SummaryName": "细菌培养皿",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "",
|
|
||||||
"LengthNum": 124.09,
|
|
||||||
"WidthNum": 81.89,
|
|
||||||
"HeightNum": 13.67,
|
|
||||||
"DepthNum": 11.2,
|
|
||||||
"StandardHeight": null,
|
|
||||||
"PipetteHeight": 0,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"HoleDiameter": 6.58,
|
|
||||||
"Volume": 78,
|
|
||||||
"ImagePath": null,
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2025-09-17 17:10:54.1859566",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2025-09-17 17:10:54.1859568",
|
|
||||||
"IsStright": null,
|
|
||||||
"IsGeneral": null,
|
|
||||||
"IsControl": null,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": 4,
|
|
||||||
"Margins_X": 9.28,
|
|
||||||
"Margins_Y": 6.19
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300TipRack",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -4323,7 +4323,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300Plate",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -8297,7 +8297,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300Plate",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -8425,7 +8425,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300Plate",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -12496,7 +12496,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300TipRack",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -16664,7 +16664,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300Plate",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -20640,7 +20640,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300Plate",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -20671,7 +20671,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300Plate",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -20799,7 +20799,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300Plate",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -24872,7 +24872,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300Plate",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -28848,7 +28848,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300Plate",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -28879,7 +28879,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300Plate",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -29007,7 +29007,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300Plate",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -33080,7 +33080,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300Plate",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -37153,7 +37153,7 @@
|
|||||||
"z": 0
|
"z": 0
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"type": "PRCXI9300Plate",
|
"type": "PRCXI9300Container",
|
||||||
"size_x": 50,
|
"size_x": 50,
|
||||||
"size_y": 50,
|
"size_y": 50,
|
||||||
"size_z": 10,
|
"size_z": 10,
|
||||||
@@ -41151,5 +41151,6 @@
|
|||||||
"uuid": "730067cf07ae43849ddf4034299030e9"
|
"uuid": "730067cf07ae43849ddf4034299030e9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
]
|
"links": []
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
[]
|
|
||||||
@@ -1,607 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"Id": "1853794d-8cc1-4268-94b8-fc83e8be3ecc",
|
|
||||||
"StartDosage": 1.0,
|
|
||||||
"EndDosage": 55.0,
|
|
||||||
"Aspiration": 0.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2126.89990234375,
|
|
||||||
"B": 2085.300048828125,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "37a31398-499c-4df3-9bfe-ff92e6bc1427",
|
|
||||||
"StartDosage": 1.0,
|
|
||||||
"EndDosage": 303.0,
|
|
||||||
"Aspiration": -1.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2229.6,
|
|
||||||
"B": 3082.7,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "e602c693-e51c-4485-8788-beb3560e0599",
|
|
||||||
"StartDosage": 303.0,
|
|
||||||
"EndDosage": 400.0,
|
|
||||||
"Aspiration": -0.8,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2156.6,
|
|
||||||
"B": 9582.1,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "d7cdf777-ae58-46ab-b1ec-a5e59496bb8a",
|
|
||||||
"StartDosage": 400.0,
|
|
||||||
"EndDosage": 501.0,
|
|
||||||
"Aspiration": -1.5,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2087.9,
|
|
||||||
"B": 37256.0,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "6149a3a7-98fb-4270-83b4-4f21b5c4e8d8",
|
|
||||||
"StartDosage": 501.0,
|
|
||||||
"EndDosage": 600.0,
|
|
||||||
"Aspiration": -1.5,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2185.0,
|
|
||||||
"B": -12375.0,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "039f5735-a598-482d-b21d-b265d5e7436a",
|
|
||||||
"StartDosage": 600.0,
|
|
||||||
"EndDosage": 700.0,
|
|
||||||
"Aspiration": -6.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2222.0,
|
|
||||||
"B": -30370.0,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "80875977-ee0f-49f4-b10d-de429e57c5b8",
|
|
||||||
"StartDosage": 700.0,
|
|
||||||
"EndDosage": 800.0,
|
|
||||||
"Aspiration": -6.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 1705.0,
|
|
||||||
"B": 324436.0,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "a38afc7c-9c86-4014-a669-a7d159fb0c70",
|
|
||||||
"StartDosage": 800.0,
|
|
||||||
"EndDosage": 900.0,
|
|
||||||
"Aspiration": 0.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2068.0,
|
|
||||||
"B": 61331.0,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "a5ce0671-8767-4752-a04c-fdbdc3c7dc91",
|
|
||||||
"StartDosage": 900.0,
|
|
||||||
"EndDosage": 1001.0,
|
|
||||||
"Aspiration": 3.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2047.2,
|
|
||||||
"B": 78417.0,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "14daba17-0a35-474f-9f8a-e9ea6c355eb0",
|
|
||||||
"StartDosage": 1.0,
|
|
||||||
"EndDosage": 303.0,
|
|
||||||
"Aspiration": -1.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2229.6,
|
|
||||||
"B": 3082.7,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "82c2439c-79f6-4f61-9518-1b1205e44027",
|
|
||||||
"StartDosage": 303.0,
|
|
||||||
"EndDosage": 400.0,
|
|
||||||
"Aspiration": -0.8,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2156.6,
|
|
||||||
"B": 9582.1,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "7981db10-4005-4c62-a22d-fac90875e91c",
|
|
||||||
"StartDosage": 400.0,
|
|
||||||
"EndDosage": 501.0,
|
|
||||||
"Aspiration": -1.5,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2087.9,
|
|
||||||
"B": 37256.0,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "ae7606fd-98fa-4236-bec4-a4d60018dbea",
|
|
||||||
"StartDosage": 501.0,
|
|
||||||
"EndDosage": 600.0,
|
|
||||||
"Aspiration": -1.5,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2185.0,
|
|
||||||
"B": -12375.0,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "ed2a2db0-77b6-4a0a-ac36-7184f0b2c2c8",
|
|
||||||
"StartDosage": 600.0,
|
|
||||||
"EndDosage": 700.0,
|
|
||||||
"Aspiration": -6.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2222.0,
|
|
||||||
"B": -30370.0,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "ed639da4-b02f-4d2a-825d-b47cebdfbf1b",
|
|
||||||
"StartDosage": 700.0,
|
|
||||||
"EndDosage": 800.0,
|
|
||||||
"Aspiration": -6.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 1705.0,
|
|
||||||
"B": 324436.0,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "7e740c8a-1043-4db1-820f-2e6e77386d7f",
|
|
||||||
"StartDosage": 800.0,
|
|
||||||
"EndDosage": 900.0,
|
|
||||||
"Aspiration": 0.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2068.0,
|
|
||||||
"B": 61331.0,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "49b6c4fe-e11a-4056-8de7-fd9a2b81bc90",
|
|
||||||
"StartDosage": 900.0,
|
|
||||||
"EndDosage": 1001.0,
|
|
||||||
"Aspiration": 3.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2047.2,
|
|
||||||
"B": 78417.0,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "67dee69d-a2a9-4598-8d8d-98b211a58821",
|
|
||||||
"StartDosage": 1.0,
|
|
||||||
"EndDosage": 6.0,
|
|
||||||
"Aspiration": 0.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 20211.0,
|
|
||||||
"B": 10779.0,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 50
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "d5c1b2b0-f897-4873-86bf-0ce5f443dfd3",
|
|
||||||
"StartDosage": 6.0,
|
|
||||||
"EndDosage": 25.0,
|
|
||||||
"Aspiration": 0.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 20211.0,
|
|
||||||
"B": 10779.0,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 50
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "b2789b53-6e0e-4b83-9932-f41c83d10da8",
|
|
||||||
"StartDosage": 25.0,
|
|
||||||
"EndDosage": 50.0,
|
|
||||||
"Aspiration": 0.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 20015.0,
|
|
||||||
"B": 17507.0,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 50
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "1f0d0bbb-6ea2-4d19-8452-6824fa1f474c",
|
|
||||||
"StartDosage": 0.1,
|
|
||||||
"EndDosage": 5.0,
|
|
||||||
"Aspiration": -1.1,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 1981.1,
|
|
||||||
"B": 3498.1,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "c58111db-dadc-43bd-97b3-a596f441d704",
|
|
||||||
"StartDosage": 5.0,
|
|
||||||
"EndDosage": 10.0,
|
|
||||||
"Aspiration": -1.1,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2113.3,
|
|
||||||
"B": 2810.8,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "a15fd33d-28cd-4bca-bd6c-018e3bafcb65",
|
|
||||||
"StartDosage": 10.0,
|
|
||||||
"EndDosage": 50.0,
|
|
||||||
"Aspiration": -0.8,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2113.3,
|
|
||||||
"B": 2810.8,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "ab957383-d83d-4fcc-8373-9d8f415c3023",
|
|
||||||
"StartDosage": 50.0,
|
|
||||||
"EndDosage": 100.0,
|
|
||||||
"Aspiration": -0.1,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2093.7,
|
|
||||||
"B": 2969.2,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "be6b6f79-222f-4f6f-ae73-e537f397a11e",
|
|
||||||
"StartDosage": 100.0,
|
|
||||||
"EndDosage": 150.0,
|
|
||||||
"Aspiration": 1.7,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2093.7,
|
|
||||||
"B": 2969.2,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "0ab3fc05-8f9f-4dc0-a2ce-918ade17810c",
|
|
||||||
"StartDosage": 150.0,
|
|
||||||
"EndDosage": 200.0,
|
|
||||||
"Aspiration": 0.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2085.0,
|
|
||||||
"B": 3548.3,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "43b82710-37df-4039-9513-aa49bc5bc607",
|
|
||||||
"StartDosage": 200.0,
|
|
||||||
"EndDosage": 250.0,
|
|
||||||
"Aspiration": 4.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2085.0,
|
|
||||||
"B": 3548.3,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "2f208ffc-808f-4bf9-b443-14dbf0338d83",
|
|
||||||
"StartDosage": 250.0,
|
|
||||||
"EndDosage": 310.0,
|
|
||||||
"Aspiration": 5.3,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2085.0,
|
|
||||||
"B": 3548.3,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "84bb5356-481d-41b9-a563-917e64b5e20c",
|
|
||||||
"StartDosage": 1.0,
|
|
||||||
"EndDosage": 10.0,
|
|
||||||
"Aspiration": 0.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 964.19,
|
|
||||||
"B": 1207.7,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "67463c2c-a520-4d33-831f-e0c3cdcdec60",
|
|
||||||
"StartDosage": 10.0,
|
|
||||||
"EndDosage": 50.0,
|
|
||||||
"Aspiration": 0.5,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 964.19,
|
|
||||||
"B": 1207.7,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "a752d77e-7c5d-450a-8b54-e87513facda0",
|
|
||||||
"StartDosage": 50.0,
|
|
||||||
"EndDosage": 100.0,
|
|
||||||
"Aspiration": 0.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 964.19,
|
|
||||||
"B": 1207.7,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "d30f522a-5992-4be4-984d-0c27b9e8f410",
|
|
||||||
"StartDosage": 100.0,
|
|
||||||
"EndDosage": 300.0,
|
|
||||||
"Aspiration": 1.8,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 937.8,
|
|
||||||
"B": 3550.1,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "29914cbe-ad35-4712-80b1-8c4e54f9fc15",
|
|
||||||
"StartDosage": 300.0,
|
|
||||||
"EndDosage": 500.0,
|
|
||||||
"Aspiration": 2.5,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 937.8,
|
|
||||||
"B": 3550.1,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "b75b1d6d-9b53-4b5c-b6ab-640cb23491d8",
|
|
||||||
"StartDosage": 500.0,
|
|
||||||
"EndDosage": 800.0,
|
|
||||||
"Aspiration": 50.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 928.69,
|
|
||||||
"B": 8253.7,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "1658a9de-bb62-4dd6-9715-0e8e71b27f97",
|
|
||||||
"StartDosage": 800.0,
|
|
||||||
"EndDosage": 900.0,
|
|
||||||
"Aspiration": 4.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 928.69,
|
|
||||||
"B": 8253.7,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "4d0fec65-983d-47f6-82fe-723bb9efd42a",
|
|
||||||
"StartDosage": 900.0,
|
|
||||||
"EndDosage": 1050.0,
|
|
||||||
"Aspiration": 5.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 928.69,
|
|
||||||
"B": 8253.7,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 1000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "f194ad17-3be3-4684-bf21-d458693e640c",
|
|
||||||
"StartDosage": 1.0,
|
|
||||||
"EndDosage": 2.0,
|
|
||||||
"Aspiration": 0.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 62616.0,
|
|
||||||
"B": 106.49,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "fa43155c-8220-4ead-bc8f-6984a25711bf",
|
|
||||||
"StartDosage": 2.0,
|
|
||||||
"EndDosage": 7.0,
|
|
||||||
"Aspiration": -0.1,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 52421.0,
|
|
||||||
"B": 20977.0,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "9b05eebb-ba5d-427c-bd4f-1b6745bab932",
|
|
||||||
"StartDosage": 7.0,
|
|
||||||
"EndDosage": 11.0,
|
|
||||||
"Aspiration": 0.1,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 51942.0,
|
|
||||||
"B": 21434.0,
|
|
||||||
"compensateEnum": 5,
|
|
||||||
"materialVolume": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "d4715f09-e24a-4ed2-b784-09256640bcf7",
|
|
||||||
"StartDosage": 0.5,
|
|
||||||
"EndDosage": 5.0,
|
|
||||||
"Aspiration": -1.1,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 1981.1,
|
|
||||||
"B": 3498.1,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "e37e2fad-954d-4a17-8312-e08bbde00902",
|
|
||||||
"StartDosage": 5.0,
|
|
||||||
"EndDosage": 10.0,
|
|
||||||
"Aspiration": -1.1,
|
|
||||||
"Dispensing": -0.8,
|
|
||||||
"K": 2113.3,
|
|
||||||
"B": 2810.8,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "642714bd-22c6-46b5-9a48-2f0bcd91d555",
|
|
||||||
"StartDosage": 10.0,
|
|
||||||
"EndDosage": 50.0,
|
|
||||||
"Aspiration": -0.8,
|
|
||||||
"Dispensing": -2.0,
|
|
||||||
"K": 2113.3,
|
|
||||||
"B": 2810.8,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "2fccf79f-52e5-4b6c-be6e-bdac167dd40c",
|
|
||||||
"StartDosage": 50.0,
|
|
||||||
"EndDosage": 100.0,
|
|
||||||
"Aspiration": -0.1,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2093.7,
|
|
||||||
"B": 2969.2,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "34555f2c-2e11-4c45-b733-83a8185727da",
|
|
||||||
"StartDosage": 100.0,
|
|
||||||
"EndDosage": 150.0,
|
|
||||||
"Aspiration": 1.7,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2093.7,
|
|
||||||
"B": 2969.2,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "9353ac79-b710-49da-a423-4bfe651ac16a",
|
|
||||||
"StartDosage": 150.0,
|
|
||||||
"EndDosage": 200.0,
|
|
||||||
"Aspiration": 0.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2085.0,
|
|
||||||
"B": 3548.3,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "1628da53-8c86-4eff-b119-07cb7a859bb6",
|
|
||||||
"StartDosage": 200.0,
|
|
||||||
"EndDosage": 250.0,
|
|
||||||
"Aspiration": 4.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2085.0,
|
|
||||||
"B": 3548.3,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "658913c3-2c3e-4e14-9eb3-0489b5fdee7f",
|
|
||||||
"StartDosage": 250.0,
|
|
||||||
"EndDosage": 310.0,
|
|
||||||
"Aspiration": -11.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2085.0,
|
|
||||||
"B": 3548.3,
|
|
||||||
"compensateEnum": 7,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "f736e716-ec13-432c-ac2e-4905753ac6f9",
|
|
||||||
"StartDosage": 0.1,
|
|
||||||
"EndDosage": 5.0,
|
|
||||||
"Aspiration": -1.1,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 1981.1,
|
|
||||||
"B": 3498.1,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "7595eda8-f2d8-491f-bdac-69d169308ab5",
|
|
||||||
"StartDosage": 5.0,
|
|
||||||
"EndDosage": 10.0,
|
|
||||||
"Aspiration": -1.1,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2113.3,
|
|
||||||
"B": 2810.8,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "42eddd0a-8394-4245-8ad3-49573b25286e",
|
|
||||||
"StartDosage": 10.0,
|
|
||||||
"EndDosage": 50.0,
|
|
||||||
"Aspiration": -0.8,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2113.3,
|
|
||||||
"B": 2810.8,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "713eadfe-25c0-4ec0-acfd-900df9e12396",
|
|
||||||
"StartDosage": 50.0,
|
|
||||||
"EndDosage": 100.0,
|
|
||||||
"Aspiration": -0.1,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2093.7,
|
|
||||||
"B": 2969.2,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "f602c7bd-bdcf-4be0-9d77-a16d409bc64b",
|
|
||||||
"StartDosage": 100.0,
|
|
||||||
"EndDosage": 150.0,
|
|
||||||
"Aspiration": 1.7,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2093.7,
|
|
||||||
"B": 2969.2,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "b91867e5-f0a2-4bbe-b37e-aec9837b019e",
|
|
||||||
"StartDosage": 150.0,
|
|
||||||
"EndDosage": 200.0,
|
|
||||||
"Aspiration": 0.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2085.0,
|
|
||||||
"B": 3548.3,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "bd2e39d7-eb93-4d40-b0b4-2aac6b5678f3",
|
|
||||||
"StartDosage": 200.0,
|
|
||||||
"EndDosage": 250.0,
|
|
||||||
"Aspiration": 4.0,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2085.0,
|
|
||||||
"B": 3548.3,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 300
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Id": "52e20b7f-f519-434f-86bb-a48238c290d1",
|
|
||||||
"StartDosage": 250.0,
|
|
||||||
"EndDosage": 310.0,
|
|
||||||
"Aspiration": 5.3,
|
|
||||||
"Dispensing": 0.0,
|
|
||||||
"K": 2085.0,
|
|
||||||
"B": 3548.3,
|
|
||||||
"compensateEnum": 6,
|
|
||||||
"materialVolume": 300
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,794 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"uuid": "3b6f33ffbf734014bcc20e3c63e124d4",
|
|
||||||
"Code": "ZX-58-1250",
|
|
||||||
"Name": "Tip头适配器 1250uL",
|
|
||||||
"SummaryName": "Tip头适配器 1250uL",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 128,
|
|
||||||
"WidthNum": 85,
|
|
||||||
"HeightNum": 20,
|
|
||||||
"DepthNum": 4,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 1,
|
|
||||||
"HoleRow": 1,
|
|
||||||
"ChannelNum": 1,
|
|
||||||
"HoleDiameter": 0,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": "/images/20220624015044.jpg",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 10,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 16:03:52.6583727",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-06-24 13:50:44.8123474",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": null,
|
|
||||||
"YSpacing": null,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "7c822592b360451fb59690e49ac6b181",
|
|
||||||
"Code": "ZX-58-300",
|
|
||||||
"Name": "ZHONGXI 适配器 300uL",
|
|
||||||
"SummaryName": "ZHONGXI 适配器 300uL",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 127,
|
|
||||||
"WidthNum": 85,
|
|
||||||
"HeightNum": 81,
|
|
||||||
"DepthNum": 4,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 1,
|
|
||||||
"HoleRow": 1,
|
|
||||||
"ChannelNum": 1,
|
|
||||||
"HoleDiameter": 0,
|
|
||||||
"Volume": 300,
|
|
||||||
"ImagePath": "/images/20220623102838.jpg",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 10,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 16:07:53.7453351",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-06-23 10:28:38.6190575",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": null,
|
|
||||||
"YSpacing": null,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "8cc3dce884ac41c09f4570d0bcbfb01c",
|
|
||||||
"Code": "ZX-58-10",
|
|
||||||
"Name": "吸头10ul 适配器",
|
|
||||||
"SummaryName": "吸头10ul 适配器",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 128,
|
|
||||||
"WidthNum": 85,
|
|
||||||
"HeightNum": 81,
|
|
||||||
"DepthNum": 4,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 1,
|
|
||||||
"HoleRow": 1,
|
|
||||||
"ChannelNum": 1,
|
|
||||||
"HoleDiameter": 127,
|
|
||||||
"Volume": 1000,
|
|
||||||
"ImagePath": "/images/20221115010348.jpg",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 10,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 16:37:40.7073733",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-11-15 13:03:48.1679642",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": null,
|
|
||||||
"YSpacing": null,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "7960f49ddfe9448abadda89bd1556936",
|
|
||||||
"Code": "ZX-001-1250",
|
|
||||||
"Name": "1250μL Tip头",
|
|
||||||
"SummaryName": "1250μL Tip头",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 118.09,
|
|
||||||
"WidthNum": 80.7,
|
|
||||||
"HeightNum": 107.67,
|
|
||||||
"DepthNum": 100,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"ChannelNum": 8,
|
|
||||||
"HoleDiameter": 7.95,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": "/images/20220623102536.jpg",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 96,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 20:53:27.8591195",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-06-23 10:25:36.2592442",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": null,
|
|
||||||
"YSpacing": null,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "45f2ed3ad925484d96463d675a0ebf66",
|
|
||||||
"Code": "ZX-001-10",
|
|
||||||
"Name": "10μL Tip头",
|
|
||||||
"SummaryName": "10μL Tip头",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 120.98,
|
|
||||||
"WidthNum": 82.12,
|
|
||||||
"HeightNum": 67,
|
|
||||||
"DepthNum": 39.1,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"ChannelNum": 8,
|
|
||||||
"HoleDiameter": 5,
|
|
||||||
"Volume": 1000,
|
|
||||||
"ImagePath": "/images/20221119041031.jpg",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": -21,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 20:56:53.462015",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-11-19 16:10:31.126801",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": null,
|
|
||||||
"YSpacing": null,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "068b3815e36b4a72a59bae017011b29f",
|
|
||||||
"Code": "ZX-001-10+",
|
|
||||||
"Name": "10μL加长 Tip头",
|
|
||||||
"SummaryName": "10μL加长 Tip头",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 120.98,
|
|
||||||
"WidthNum": 82.12,
|
|
||||||
"HeightNum": 50.3,
|
|
||||||
"DepthNum": 45.8,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"ChannelNum": 8,
|
|
||||||
"HoleDiameter": 5,
|
|
||||||
"Volume": 20,
|
|
||||||
"ImagePath": "/images/20220718120113.jpg",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 42,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 20:57:57.331211",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-07-18 12:01:13.2131453",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": null,
|
|
||||||
"YSpacing": null,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "80652665f6a54402b2408d50b40398df",
|
|
||||||
"Code": "ZX-001-1000",
|
|
||||||
"Name": "1000μL Tip头",
|
|
||||||
"SummaryName": "1000μL Tip头",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 118.09,
|
|
||||||
"WidthNum": 80.7,
|
|
||||||
"HeightNum": 107.67,
|
|
||||||
"DepthNum": 88,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"ChannelNum": 8,
|
|
||||||
"HoleDiameter": 7.95,
|
|
||||||
"Volume": 1000,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 47,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 20:59:20.5534915",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-08-12 13:11:44.8670189",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "076250742950465b9d6ea29a225dfb00",
|
|
||||||
"Code": "ZX-001-300",
|
|
||||||
"Name": "300μL Tip头",
|
|
||||||
"SummaryName": "300μL Tip头",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 120.98,
|
|
||||||
"WidthNum": 82.12,
|
|
||||||
"HeightNum": 40,
|
|
||||||
"DepthNum": 59.3,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"ChannelNum": 8,
|
|
||||||
"HoleDiameter": 5.5,
|
|
||||||
"Volume": 300,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 11,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 21:00:24.7266192",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2024-02-01 15:48:02.1562734",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "7a73bb9e5c264515a8fcbe88aed0e6f7",
|
|
||||||
"Code": "ZX-001-200",
|
|
||||||
"Name": "200μL Tip头",
|
|
||||||
"SummaryName": "200μL Tip头",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 120.98,
|
|
||||||
"WidthNum": 82.12,
|
|
||||||
"HeightNum": 66.9,
|
|
||||||
"DepthNum": 52,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"ChannelNum": 8,
|
|
||||||
"HoleDiameter": 5.5,
|
|
||||||
"Volume": 200,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 19,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 21:01:17.626704",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-10-14 13:44:41.5428946",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "73bb9b10bc394978b70e027bf45ce2d3",
|
|
||||||
"Code": "ZX-023-0.2",
|
|
||||||
"Name": "0.2ml PCR板",
|
|
||||||
"SummaryName": "0.2ml PCR板",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 126,
|
|
||||||
"WidthNum": 86,
|
|
||||||
"HeightNum": 21.2,
|
|
||||||
"DepthNum": 15.17,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"ChannelNum": 96,
|
|
||||||
"HoleDiameter": 6,
|
|
||||||
"Volume": 1000,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": -12,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 21:06:02.7746392",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2024-02-20 16:17:16.7921748",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "ca877b8b114a4310b429d1de4aae96ee",
|
|
||||||
"Code": "ZX-019-2.2",
|
|
||||||
"Name": "2.2ml 深孔板",
|
|
||||||
"SummaryName": "2.2ml 深孔板",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 127.3,
|
|
||||||
"WidthNum": 85.35,
|
|
||||||
"HeightNum": 44,
|
|
||||||
"DepthNum": 42,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"ChannelNum": 8,
|
|
||||||
"HoleDiameter": 8.2,
|
|
||||||
"Volume": 2200,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 34,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-30 21:07:16.4538022",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-08-12 13:11:26.3993472",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "04211a2dc93547fe9bf6121eac533650",
|
|
||||||
"Code": "ZX-58-10000",
|
|
||||||
"Name": "储液槽",
|
|
||||||
"SummaryName": "储液槽",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 127,
|
|
||||||
"WidthNum": 85,
|
|
||||||
"HeightNum": 31.2,
|
|
||||||
"DepthNum": 24,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 1,
|
|
||||||
"HoleRow": 1,
|
|
||||||
"ChannelNum": 1,
|
|
||||||
"HoleDiameter": 127,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": "/images/20220623103134.jpg",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": -172,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2021-12-31 18:37:56.7949909",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-06-23 10:31:34.4261358",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": null,
|
|
||||||
"YSpacing": null,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "4a043a07c65a4f9bb97745e1f129b165",
|
|
||||||
"Code": "ZX-58-0001",
|
|
||||||
"Name": "半裙边 PCR适配器",
|
|
||||||
"SummaryName": "半裙边 PCR适配器",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 127,
|
|
||||||
"WidthNum": 85,
|
|
||||||
"HeightNum": 88,
|
|
||||||
"DepthNum": 5,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"ChannelNum": 96,
|
|
||||||
"HoleDiameter": 9,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": "/images/20221123051800.jpg",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": 100,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2022-01-02 19:21:35.8664843",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-11-23 17:18:00.8826719",
|
|
||||||
"IsStright": 1,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 1,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": null,
|
|
||||||
"YSpacing": null,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "6bdfdd7069df453896b0806df50f2f4d",
|
|
||||||
"Code": "ZX-ADP-001",
|
|
||||||
"Name": "储液槽 适配器",
|
|
||||||
"SummaryName": "储液槽 适配器",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 133,
|
|
||||||
"WidthNum": 91.8,
|
|
||||||
"HeightNum": 70,
|
|
||||||
"DepthNum": 4,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 1,
|
|
||||||
"HoleRow": 1,
|
|
||||||
"ChannelNum": 8,
|
|
||||||
"HoleDiameter": 1,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2022-02-16 17:31:26.413594",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-08-12 13:10:58.786996",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 0,
|
|
||||||
"YSpacing": 0,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "9a439bed8f3344549643d6b3bc5a5eb4",
|
|
||||||
"Code": "ZX-002-300",
|
|
||||||
"Name": "300ul深孔板适配器",
|
|
||||||
"SummaryName": "300ul深孔板适配器",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 136.4,
|
|
||||||
"WidthNum": 93.8,
|
|
||||||
"HeightNum": 96,
|
|
||||||
"DepthNum": 7,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"ChannelNum": 96,
|
|
||||||
"HoleDiameter": 8.1,
|
|
||||||
"Volume": 300,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2022-06-18 15:17:42.7917763",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-08-12 13:10:46.1526635",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "4dc8d6ecfd0449549683b8ef815a861b",
|
|
||||||
"Code": "ZX-002-10",
|
|
||||||
"Name": "10ul专用深孔板适配器",
|
|
||||||
"SummaryName": "10ul专用深孔板适配器",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "宁静致远",
|
|
||||||
"LengthNum": 136.5,
|
|
||||||
"WidthNum": 93.8,
|
|
||||||
"HeightNum": 121.5,
|
|
||||||
"DepthNum": 7,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"ChannelNum": 96,
|
|
||||||
"HoleDiameter": 8.1,
|
|
||||||
"Volume": 10,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2022-06-30 09:37:31.0451435",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-08-12 13:10:38.5409878",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "b01627718d3341aba649baa81c2c083c",
|
|
||||||
"Code": "Sd155",
|
|
||||||
"Name": "爱津",
|
|
||||||
"SummaryName": "爱津",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 125,
|
|
||||||
"WidthNum": 85,
|
|
||||||
"HeightNum": 64,
|
|
||||||
"DepthNum": 45.5,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"ChannelNum": 1,
|
|
||||||
"HoleDiameter": 4,
|
|
||||||
"Volume": 20,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2022-11-07 08:56:30.1794274",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-11-07 09:00:29.5496845",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": null,
|
|
||||||
"YSpacing": null,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "adfabfffa8f24af5abfbba67b8d0f973",
|
|
||||||
"Code": "Fhh478",
|
|
||||||
"Name": "适配器",
|
|
||||||
"SummaryName": "适配器",
|
|
||||||
"SupplyType": 2,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 120,
|
|
||||||
"WidthNum": 90,
|
|
||||||
"HeightNum": 86,
|
|
||||||
"DepthNum": 4,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 1,
|
|
||||||
"HoleRow": 1,
|
|
||||||
"ChannelNum": 1,
|
|
||||||
"HoleDiameter": 4,
|
|
||||||
"Volume": 1000,
|
|
||||||
"ImagePath": null,
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2022-11-07 09:00:10.7579131",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2022-11-07 09:00:10.7579134",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": null,
|
|
||||||
"YSpacing": null,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "1592e84a07f74668af155588867f2da7",
|
|
||||||
"Code": "12",
|
|
||||||
"Name": "12",
|
|
||||||
"SummaryName": "12",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "12",
|
|
||||||
"LengthNum": 1,
|
|
||||||
"WidthNum": 1,
|
|
||||||
"HeightNum": 1,
|
|
||||||
"DepthNum": 100,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 8,
|
|
||||||
"HoleRow": 12,
|
|
||||||
"ChannelNum": 12,
|
|
||||||
"HoleDiameter": 7,
|
|
||||||
"Volume": 12,
|
|
||||||
"ImagePath": null,
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2023-10-08 09:35:19.281766",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-10-08 09:35:19.2817667",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 0,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "730067cf07ae43849ddf4034299030e9",
|
|
||||||
"Code": "q1",
|
|
||||||
"Name": "废弃槽",
|
|
||||||
"SummaryName": "废弃槽",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 190,
|
|
||||||
"WidthNum": 135,
|
|
||||||
"HeightNum": 75,
|
|
||||||
"DepthNum": 1,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 1,
|
|
||||||
"HoleRow": 1,
|
|
||||||
"ChannelNum": 1,
|
|
||||||
"HoleDiameter": 1,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": null,
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2023-10-14 13:15:45.8172852",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-10-14 13:15:45.8172869",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 1,
|
|
||||||
"YSpacing": 1,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f",
|
|
||||||
"Code": "q2",
|
|
||||||
"Name": "96深孔板",
|
|
||||||
"SummaryName": "96深孔板",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 126.5,
|
|
||||||
"WidthNum": 84.5,
|
|
||||||
"HeightNum": 41.4,
|
|
||||||
"DepthNum": 38.4,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 12,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"ChannelNum": 96,
|
|
||||||
"HoleDiameter": 8.3,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": null,
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2023-10-14 13:19:55.7225524",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-10-14 13:19:55.7225525",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 9,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "853dcfb6226f476e8b23c250217dc7da",
|
|
||||||
"Code": "q3",
|
|
||||||
"Name": "384板",
|
|
||||||
"SummaryName": "384板",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 126.6,
|
|
||||||
"WidthNum": 84,
|
|
||||||
"HeightNum": 9.4,
|
|
||||||
"DepthNum": 8,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 24,
|
|
||||||
"HoleRow": 16,
|
|
||||||
"ChannelNum": 384,
|
|
||||||
"HoleDiameter": 3,
|
|
||||||
"Volume": 1250,
|
|
||||||
"ImagePath": null,
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2023-10-14 13:22:34.779818",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2023-10-14 13:22:34.7798181",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 4.5,
|
|
||||||
"YSpacing": 4.5,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "e201e206fcfc4e8ab51946a22e8cd1bc",
|
|
||||||
"Code": "1",
|
|
||||||
"Name": "ep",
|
|
||||||
"SummaryName": "ep",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 504,
|
|
||||||
"WidthNum": 337,
|
|
||||||
"HeightNum": 160,
|
|
||||||
"DepthNum": 163,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 6,
|
|
||||||
"HoleRow": 4,
|
|
||||||
"ChannelNum": 24,
|
|
||||||
"HoleDiameter": 41.2,
|
|
||||||
"Volume": 1,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2024-01-20 13:14:38.0308919",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2024-02-05 16:27:07.2582693",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 21,
|
|
||||||
"YSpacing": 18,
|
|
||||||
"materialEnum": null
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "01953864f6f140ccaa8ddffd4f3e46f5",
|
|
||||||
"Code": "sdfrth654",
|
|
||||||
"Name": "4道储液槽",
|
|
||||||
"SummaryName": "4道储液槽",
|
|
||||||
"SupplyType": 1,
|
|
||||||
"Factory": "中析",
|
|
||||||
"LengthNum": 100,
|
|
||||||
"WidthNum": 40,
|
|
||||||
"HeightNum": 30,
|
|
||||||
"DepthNum": 10,
|
|
||||||
"StandardHeight": 0,
|
|
||||||
"PipetteHeight": null,
|
|
||||||
"HoleColum": 4,
|
|
||||||
"HoleRow": 8,
|
|
||||||
"ChannelNum": 4,
|
|
||||||
"HoleDiameter": 4,
|
|
||||||
"Volume": 1000,
|
|
||||||
"ImagePath": "",
|
|
||||||
"QRCode": null,
|
|
||||||
"Qty": null,
|
|
||||||
"CreateName": null,
|
|
||||||
"CreateTime": "2024-02-20 14:44:25.0021372",
|
|
||||||
"UpdateName": null,
|
|
||||||
"UpdateTime": "2024-02-20 15:28:21.3881302",
|
|
||||||
"IsStright": 0,
|
|
||||||
"IsGeneral": 1,
|
|
||||||
"IsControl": 0,
|
|
||||||
"ArmCode": null,
|
|
||||||
"XSpacing": 27,
|
|
||||||
"YSpacing": 9,
|
|
||||||
"materialEnum": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,602 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"uuid": "87ea11eeb24b43648ce294654b561fe7",
|
|
||||||
"PlanName": "2341",
|
|
||||||
"PlanCode": "2980eb",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-05-15 18:24:00.8445073",
|
|
||||||
"MatrixId": "34ba3f02-6fcd-48e6-bb8e-3b0ce1d54ed5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "0a977d6ebc4244739793b0b6f8b3f815",
|
|
||||||
"PlanName": "384测试方案(300模块)",
|
|
||||||
"PlanCode": "9336ee",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-06-13 10:34:52.5310959",
|
|
||||||
"MatrixId": "74ed84ea-0b5d-4307-a966-ceb83fcaefe7"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "aff2cd213ad34072b370f44acb5ab658",
|
|
||||||
"PlanName": "96孔吸300方案(单放)",
|
|
||||||
"PlanCode": "9932fc",
|
|
||||||
"PlanTarget": "测试用",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-06-13 09:57:38.422353",
|
|
||||||
"MatrixId": "bacd78be-b86d-49d6-973a-dd522834e4c4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "97816d94f99a48409379013d19f0ab66",
|
|
||||||
"PlanName": "384测试方案(50模块)",
|
|
||||||
"PlanCode": "3964de",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-06-13 10:32:22.8918817",
|
|
||||||
"MatrixId": "74ed84ea-0b5d-4307-a966-ceb83fcaefe7"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "c3d86e9d7eed4ddb8c32e9234da659de",
|
|
||||||
"PlanName": "96吸50方案(单放)",
|
|
||||||
"PlanCode": "6994aa",
|
|
||||||
"PlanTarget": "测试用",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-08-08 11:50:14.6850189",
|
|
||||||
"MatrixId": "bacd78be-b86d-49d6-973a-dd522834e4c4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "59a97f77718d4bbba6bed1ddbf959772",
|
|
||||||
"PlanName": "test12",
|
|
||||||
"PlanCode": "8630fa",
|
|
||||||
"PlanTarget": "12通道",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-10-08 09:36:14.2536629",
|
|
||||||
"MatrixId": "517c836e-56c6-4c06-a897-7074886061bd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "84d50e4cf3034aa6a3de505a92b30812",
|
|
||||||
"PlanName": "test001",
|
|
||||||
"PlanCode": "9013fe",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-10-08 16:37:57.2302499",
|
|
||||||
"MatrixId": "ed9b1ceb-b879-4b8c-a246-2d4f54fbe970"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "d052b893c6324ae38d301a58614a5663",
|
|
||||||
"PlanName": "test01",
|
|
||||||
"PlanCode": "8524cf",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-10-09 11:00:21.4973895",
|
|
||||||
"MatrixId": "bacd78be-b86d-49d6-973a-dd522834e4c4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "875a6eaa00e548b99318fd0be310e879",
|
|
||||||
"PlanName": "test002",
|
|
||||||
"PlanCode": "2477fe",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-10-09 11:02:01.2027308",
|
|
||||||
"MatrixId": "7374dc89-d425-42aa-b252-1b1338d3c2f2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "ecb3cb37f603495d95a93522a6b611e3",
|
|
||||||
"PlanName": "test02",
|
|
||||||
"PlanCode": "5126cb",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-10-09 11:02:14.7987877",
|
|
||||||
"MatrixId": "7374dc89-d425-42aa-b252-1b1338d3c2f2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "705edabbcbd645d0925e4e581643247c",
|
|
||||||
"PlanName": "test003",
|
|
||||||
"PlanCode": "4994cc",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-10-09 11:41:04.1715458",
|
|
||||||
"MatrixId": "4c126841-5c37-49c7-b4e8-539983bc9cc4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "6c58136d7de54a6abb7b51e6327eacac",
|
|
||||||
"PlanName": "test04",
|
|
||||||
"PlanCode": "9704dd",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-10-09 11:51:59.1752071",
|
|
||||||
"MatrixId": "4c126841-5c37-49c7-b4e8-539983bc9cc4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "208f00a911b846d9922b2e72bdda978c",
|
|
||||||
"PlanName": "96版位 50ul量程",
|
|
||||||
"PlanCode": "7595be",
|
|
||||||
"PlanTarget": "213213",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-10-18 19:12:17.4641981",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "40bd0ca25ffb4be6b246353db6ebefc9",
|
|
||||||
"PlanName": "96版位 300ul量程",
|
|
||||||
"PlanCode": "7421fc",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-10-14 14:47:03.8105699",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "30b838bb7d124ec885b506df29ee7860",
|
|
||||||
"PlanName": "300版位 50ul量程",
|
|
||||||
"PlanCode": "6364cc",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-10-14 14:48:05.2235254",
|
|
||||||
"MatrixId": "f8c70333-b717-4ca0-9306-c40fd5f156fb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "e53c591c86334c6f92d3b1afa107bcf8",
|
|
||||||
"PlanName": "384版位 300ul量程",
|
|
||||||
"PlanCode": "4029be",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-10-14 14:47:48.9478679",
|
|
||||||
"MatrixId": "f8c70333-b717-4ca0-9306-c40fd5f156fb"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "1d26d1ab45c6431990ba0e00cc1f78d2",
|
|
||||||
"PlanName": "96版位梯度稀释 50ul量程",
|
|
||||||
"PlanCode": "3502cf",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-10-14 14:48:12.8676989",
|
|
||||||
"MatrixId": "916bbd00-e66c-4237-9843-e049b70b740a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "7a0383b4fbb543339723513228365451",
|
|
||||||
"PlanName": "96版位梯度稀释 300ul量程",
|
|
||||||
"PlanCode": "9345fe",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-10-14 14:50:02.0250566",
|
|
||||||
"MatrixId": "916bbd00-e66c-4237-9843-e049b70b740a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "69d4882f0f024fb5a3b91010f149ff89",
|
|
||||||
"PlanName": "测试",
|
|
||||||
"PlanCode": "3941bf",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2023-12-11 15:24:30.1371824",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "3603f89f4e0945f68353a33e8017ba6e",
|
|
||||||
"PlanName": "测试111",
|
|
||||||
"PlanCode": "8056eb",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-16 09:29:12.1441631",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "b44be8260740460598816c40f13fd6b4",
|
|
||||||
"PlanName": "测试12",
|
|
||||||
"PlanCode": "8272fb",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-16 10:40:54.2543702",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "f189a50122d54a568f3d39dc1f996167",
|
|
||||||
"PlanName": "0.5",
|
|
||||||
"PlanCode": "2093ec",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-16 13:06:37.8280696",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "b48218c8f2274b108e278d019c9b5126",
|
|
||||||
"PlanName": "3",
|
|
||||||
"PlanCode": "9493bb",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-16 14:20:42.4761092",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "41d2ebc5ab5b4b2da3e203937c5cbe70",
|
|
||||||
"PlanName": "6",
|
|
||||||
"PlanCode": "5586de",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-16 15:21:03.4440875",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "49ec03499aa646b9b8069a783dbeca1c",
|
|
||||||
"PlanName": "7",
|
|
||||||
"PlanCode": "1162bc",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-16 15:31:33.7359724",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "a9c6d149cdf04636ac43cfb7623e4e7f",
|
|
||||||
"PlanName": "8",
|
|
||||||
"PlanCode": "7354eb",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-16 15:39:32.2399414",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "0e3a36cabefa4f5497e35193db48b559",
|
|
||||||
"PlanName": "9",
|
|
||||||
"PlanCode": "4453ba",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-16 15:49:31.5830134",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "d0a0d926e2034abc94b4d883951a78f7",
|
|
||||||
"PlanName": "10",
|
|
||||||
"PlanCode": "5797ab",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-16 16:00:25.4439315",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "22ac523a47e7421e80f401baf1526daf",
|
|
||||||
"PlanName": "50",
|
|
||||||
"PlanCode": "2507ca",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-16 16:23:13.8022807",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "fdea60f535ee4bc39c02c602a64f46bd",
|
|
||||||
"PlanName": "11",
|
|
||||||
"PlanCode": "1574ae",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-18 09:14:59.8230591",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "6650f7df6b8944f98476da92ce81d688",
|
|
||||||
"PlanName": "12",
|
|
||||||
"PlanCode": "2145bd",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-18 09:45:34.137906",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "9415a69280c042a09d6836f5eeddf40f",
|
|
||||||
"PlanName": "100",
|
|
||||||
"PlanCode": "2073fd",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-18 10:12:29.9998926",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "d9740fea94a04c2db44b1364a336b338",
|
|
||||||
"PlanName": "250",
|
|
||||||
"PlanCode": "2601ea",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-18 11:15:54.2583401",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "1d80c1fff5af442595c21963e6ca9fee",
|
|
||||||
"PlanName": "160",
|
|
||||||
"PlanCode": "6612ea",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-18 11:18:59.0457638",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "36889fb926aa480cb42de97700522bbf",
|
|
||||||
"PlanName": "200",
|
|
||||||
"PlanCode": "3174dc",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-18 11:20:15.7676326",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "bd90ae2846c14e708854938158fd3443",
|
|
||||||
"PlanName": "300",
|
|
||||||
"PlanCode": "2665df",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-18 13:00:16.9242256",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "9df4857d2bef45bcad14cc13055e9f7b",
|
|
||||||
"PlanName": "500",
|
|
||||||
"PlanCode": "4771ab",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-18 13:26:32.3910805",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "d2f6e63cf1ff41a4a8d03f4444a2aeac",
|
|
||||||
"PlanName": "800",
|
|
||||||
"PlanCode": "4560bc",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-18 13:42:35.5153947",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "f40a6f4326a346d39d5a82f6262aba47",
|
|
||||||
"PlanName": "测试12345",
|
|
||||||
"PlanCode": "3402ab",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-18 14:37:29.8890777",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "4248035f01e943faa6d71697ed386e19",
|
|
||||||
"PlanName": "995",
|
|
||||||
"PlanCode": "2688dc",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-18 14:39:23.5292196",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "a73bc780e4d04099bf54c2b90fa7b974",
|
|
||||||
"PlanName": "1000",
|
|
||||||
"PlanCode": "2889bf",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 09:16:37.7818522",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "4d97363a0a334094a1ff24494a902d02",
|
|
||||||
"PlanName": "2.。",
|
|
||||||
"PlanCode": "6527ff",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 11:38:00.0672017",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "6eec360c74464769967ebefa43b7aec1",
|
|
||||||
"PlanName": "2222222",
|
|
||||||
"PlanCode": "8763ce",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 11:40:42.7038484",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "986049c83b054171a1b34dd49b3ca9cf",
|
|
||||||
"PlanName": "9ul",
|
|
||||||
"PlanCode": "1945fd",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 13:33:06.6556398",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "462eed73962142c2bd3b8fe717caceb6",
|
|
||||||
"PlanName": "8ul",
|
|
||||||
"PlanCode": "6912fc",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 15:16:17.4254316",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "b2f0c7ab462f4cf1bae56ee59a49a253",
|
|
||||||
"PlanName": "11.",
|
|
||||||
"PlanCode": "6190ba",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 15:21:57.6729366",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "b9768a1d91444d4a86b7a013467bee95",
|
|
||||||
"PlanName": "8ulll",
|
|
||||||
"PlanCode": "6899be",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 15:29:03.2029069",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "98621898cd514bc9a1ac0c92362284f4",
|
|
||||||
"PlanName": "7u",
|
|
||||||
"PlanCode": "7651fe",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 15:57:16.4898686",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "4d03142fd86844db8e23c19061b3d505",
|
|
||||||
"PlanName": "55555",
|
|
||||||
"PlanCode": "7963fe",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 16:23:37.7271107",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "c78c3f38a59748c3aef949405e434b05",
|
|
||||||
"PlanName": "44443",
|
|
||||||
"PlanCode": "4564dd",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 16:29:26.6765074",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "0fc4ffd86091451db26162af4f7b235e",
|
|
||||||
"PlanName": "u",
|
|
||||||
"PlanCode": "9246de",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 16:34:15.4217796",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "a08748982b934daab8752f55796e1b0c",
|
|
||||||
"PlanName": "666y",
|
|
||||||
"PlanCode": "5492ce",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 16:38:55.6092122",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "2317611bdb614e45b61a5118e58e3a2a",
|
|
||||||
"PlanName": "8ull、",
|
|
||||||
"PlanCode": "4641de",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 16:46:26.6184295",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "62cb45ac3af64a46aa6d450ba56963e7",
|
|
||||||
"PlanName": "33333",
|
|
||||||
"PlanCode": "1270aa",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 16:49:19.6115492",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "321f717a3a2640a3bfc9515aee7d1052",
|
|
||||||
"PlanName": "999",
|
|
||||||
"PlanCode": "7597ed",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-01-19 16:58:22.6149002",
|
|
||||||
"MatrixId": "b3da2b21-875b-4ae6-8077-ec951730201b"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "6c3246ac0f974a6abc24c83bf45e1cf4",
|
|
||||||
"PlanName": "QPCR",
|
|
||||||
"PlanCode": "7297ad",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-02-19 13:03:44.3456134",
|
|
||||||
"MatrixId": "f02830f3-ed67-49fb-9865-c31828ba3a48"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "1d307a2c095b461abeec6e8521565ad3",
|
|
||||||
"PlanName": "绝对定量",
|
|
||||||
"PlanCode": "8540af",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-02-19 13:35:14.2243691",
|
|
||||||
"MatrixId": "739ddf78-e04c-4d43-9293-c35d31f36f51"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "bbd6dc765867466ca2a415525f5bdbdd",
|
|
||||||
"PlanName": "血凝",
|
|
||||||
"PlanCode": "6513ee",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-02-20 16:14:25.0364174",
|
|
||||||
"MatrixId": "20e70dcb-63f6-4bac-82e3-29e88eb6a7ab"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "f7282ecbfee44e91b05cefbc1beac1ae",
|
|
||||||
"PlanName": "血凝抑制",
|
|
||||||
"PlanCode": "1431ba",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-02-21 10:00:05.8661038",
|
|
||||||
"MatrixId": "1c948beb-4c32-494f-b226-14bb84b3e144"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "196e0d757c574020932b64b69e88fac9",
|
|
||||||
"PlanName": "测试杀杀杀",
|
|
||||||
"PlanCode": "9833df",
|
|
||||||
"PlanTarget": "",
|
|
||||||
"Annotate": "",
|
|
||||||
"CreateName": "",
|
|
||||||
"CreateDate": "2024-02-21 10:54:19.3136491",
|
|
||||||
"MatrixId": "3667ead7-9044-46ad-b73e-655b57c8c6b9"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,302 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": "630a9ca9-dfbf-40f9-b90b-6df73e6a1d7f",
|
|
||||||
"number": 1,
|
|
||||||
"name": "T1",
|
|
||||||
"row": 0,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "db955443-1397-4a7a-a0cc-185eb6422c27",
|
|
||||||
"number": 2,
|
|
||||||
"name": "T2",
|
|
||||||
"row": 0,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "635e8265-e2b9-430e-8a4e-ddf94256266f",
|
|
||||||
"number": 3,
|
|
||||||
"name": "T3",
|
|
||||||
"row": 0,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 2,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "6de1521d-a249-4a7e-800f-1d49b5c7b56f",
|
|
||||||
"number": 4,
|
|
||||||
"name": "T4",
|
|
||||||
"row": 1,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4f9f2527-0f71-4ec4-a0ac-e546407e2960",
|
|
||||||
"number": 5,
|
|
||||||
"name": "T5",
|
|
||||||
"row": 1,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "55ecff40-453f-4a5f-9ed3-1267b0a03cae",
|
|
||||||
"number": 1,
|
|
||||||
"name": "T1",
|
|
||||||
"row": 0,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7dcd9c87-6702-4659-b28a-f6565b27f8e3",
|
|
||||||
"number": 2,
|
|
||||||
"name": "T2",
|
|
||||||
"row": 0,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "67e51bd6-6eee-46e4-931c-73d9e07397eb",
|
|
||||||
"number": 3,
|
|
||||||
"name": "T3",
|
|
||||||
"row": 0,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "e1289406-4f5e-4966-a1e6-fb29be6cd4bd",
|
|
||||||
"number": 4,
|
|
||||||
"name": "T4",
|
|
||||||
"row": 0,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4ecb9ef7-cbd4-44bc-a6a9-fdbbefdc01d6",
|
|
||||||
"number": 5,
|
|
||||||
"name": "T5",
|
|
||||||
"row": 1,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "c7bcaeeb-7ce7-479d-8dae-e82f4023a2b6",
|
|
||||||
"number": 6,
|
|
||||||
"name": "T6",
|
|
||||||
"row": 1,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "e502d5ee-3197-4f60-8ac4-3bc005349dfd",
|
|
||||||
"number": 7,
|
|
||||||
"name": "T7",
|
|
||||||
"row": 1,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "829c78b0-9e05-448f-9531-6d19c094c83f",
|
|
||||||
"number": 8,
|
|
||||||
"name": "T8",
|
|
||||||
"row": 1,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "d0fd64d6-360d-4f5e-9451-21a332e247f5",
|
|
||||||
"number": 9,
|
|
||||||
"name": "T9",
|
|
||||||
"row": 2,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7f3da25d-0be0-4e07-885f-fbbbfa952f9f",
|
|
||||||
"number": 10,
|
|
||||||
"name": "T10",
|
|
||||||
"row": 2,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "491d396d-7264-43d6-9ad4-60bffbe66c26",
|
|
||||||
"number": 11,
|
|
||||||
"name": "T11",
|
|
||||||
"row": 2,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "a8853b6d-639d-46f9-a4bf-9153c0c22461",
|
|
||||||
"number": 12,
|
|
||||||
"name": "T12",
|
|
||||||
"row": 2,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "b7beb8d0-0003-471d-bd8d-a9c0e09b07d5",
|
|
||||||
"number": 1,
|
|
||||||
"name": "T1",
|
|
||||||
"row": 0,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "306e3f96-a6d7-484a-83ef-722e3710d5c4",
|
|
||||||
"number": 2,
|
|
||||||
"name": "T2",
|
|
||||||
"row": 0,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4e7bb617-ac1a-4360-b379-7ac4197089c4",
|
|
||||||
"number": 3,
|
|
||||||
"name": "T3",
|
|
||||||
"row": 0,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "af583180-c29d-418e-9061-9e030f77cf57",
|
|
||||||
"number": 4,
|
|
||||||
"name": "T4",
|
|
||||||
"row": 0,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 2,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "24a85ce8-e9e3-44f5-9d08-25116173ba75",
|
|
||||||
"number": 5,
|
|
||||||
"name": "T5",
|
|
||||||
"row": 1,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7bf61a40-f65a-4d2f-bb19-d42bfd80e2e9",
|
|
||||||
"number": 6,
|
|
||||||
"name": "T6",
|
|
||||||
"row": 1,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "a3177806-3c02-4c4f-86d6-604a38c2ba2a",
|
|
||||||
"number": 7,
|
|
||||||
"name": "T7",
|
|
||||||
"row": 1,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "8ccaad5a-8588-4ff3-b0d7-17e7fd5ac6cc",
|
|
||||||
"number": 1,
|
|
||||||
"name": "T1",
|
|
||||||
"row": 0,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "93ae7707-b6b8-4bc4-8700-c500c3d7b165",
|
|
||||||
"number": 2,
|
|
||||||
"name": "T2",
|
|
||||||
"row": 0,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3591a07b-4922-4882-996f-7bebee843be1",
|
|
||||||
"number": 3,
|
|
||||||
"name": "T3",
|
|
||||||
"row": 0,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "669fdba9-b20c-4bd2-8352-8fe5682e3e0c",
|
|
||||||
"number": 4,
|
|
||||||
"name": "T4",
|
|
||||||
"row": 1,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "8bf3333e-4a73-4e4c-959a-8ae44e1038a2",
|
|
||||||
"number": 5,
|
|
||||||
"name": "T5",
|
|
||||||
"row": 1,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2837bf69-273a-4cbb-a74c-0af1b362f609",
|
|
||||||
"number": 6,
|
|
||||||
"name": "T6",
|
|
||||||
"row": 1,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"uuid": "9a3007baa748457b8d5162f5c5918553",
|
|
||||||
"ArmCode": "SC10",
|
|
||||||
"ArmName": "单道-10uL",
|
|
||||||
"CmdCode": "SC10",
|
|
||||||
"ChannelNum": 1,
|
|
||||||
"Dosage": 10,
|
|
||||||
"CreateName": "admin",
|
|
||||||
"CreateTime": "2021-11-13 14:04:02.000",
|
|
||||||
"UpdateName": "admin",
|
|
||||||
"UpdateTime": "2021-11-13 14:04:12.000"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "8f57a4cc859d4c02bffbeeadcfb2b661",
|
|
||||||
"ArmCode": "SC300",
|
|
||||||
"ArmName": "单道-300uL",
|
|
||||||
"CmdCode": "SC300",
|
|
||||||
"ChannelNum": 1,
|
|
||||||
"Dosage": 300,
|
|
||||||
"CreateName": "admin",
|
|
||||||
"CreateTime": "2021-11-11 11:11:11.000",
|
|
||||||
"UpdateName": "admin",
|
|
||||||
"UpdateTime": "2021-11-11 11:11:11.000"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "8fe0320823de49a99bfa5060ce1aaa28",
|
|
||||||
"ArmCode": "SC1250",
|
|
||||||
"ArmName": "单道-1250",
|
|
||||||
"CmdCode": "SC1250",
|
|
||||||
"ChannelNum": 1,
|
|
||||||
"Dosage": 1250,
|
|
||||||
"CreateName": "admin",
|
|
||||||
"CreateTime": "2021-11-12 10:10:10.000",
|
|
||||||
"UpdateName": "admin",
|
|
||||||
"UpdateTime": "2021-11-12 11:11:11.000"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "88f22c5384e94dbbad60961d4d2b5e91",
|
|
||||||
"ArmCode": "MC10",
|
|
||||||
"ArmName": "八道-10uL",
|
|
||||||
"CmdCode": "MC10",
|
|
||||||
"ChannelNum": 8,
|
|
||||||
"Dosage": 10,
|
|
||||||
"CreateName": "admin",
|
|
||||||
"CreateTime": "2021-11-12 10:10:10.000",
|
|
||||||
"UpdateName": "admin",
|
|
||||||
"UpdateTime": "2021-11-13 12:12:12.000"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "09206ff90e64466f90ce6a785a24bad8",
|
|
||||||
"ArmCode": "MC300",
|
|
||||||
"ArmName": "八道-300uL",
|
|
||||||
"CmdCode": "MC300",
|
|
||||||
"ChannelNum": 8,
|
|
||||||
"Dosage": 300,
|
|
||||||
"CreateName": "admin",
|
|
||||||
"CreateTime": "2021-11-12 12:12:12.000",
|
|
||||||
"UpdateName": "admin",
|
|
||||||
"UpdateTime": "2021-11-12 10:10:10.000"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "5afcbd7d1d6749079d1c94f8c2e68f06",
|
|
||||||
"ArmCode": "MC1250",
|
|
||||||
"ArmName": "八道-1250uL",
|
|
||||||
"CmdCode": "MC1250",
|
|
||||||
"ChannelNum": 8,
|
|
||||||
"Dosage": 1250,
|
|
||||||
"CreateName": "admin",
|
|
||||||
"CreateTime": "2021-11-12 12:12:10.000",
|
|
||||||
"UpdateName": "admin",
|
|
||||||
"UpdateTime": "2021-11-12 12:11:11.000"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"uuid": "bd52d6566534441ea523265814dc06e8",
|
|
||||||
"uuidMaterial": "01bdeb95a1314dc78b8f25667b08d531",
|
|
||||||
"ChannelNum": 8,
|
|
||||||
"HoleNo": 96,
|
|
||||||
"HoleCenterXYZ": "300",
|
|
||||||
"uuidLayoutMaster": "4f35adc958c540fcb40d6f9dd51e40fa"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"uuid": "4f35adc958c540fcb40d6f9dd51e40fa",
|
|
||||||
"BoardCode": 34,
|
|
||||||
"BoardNum": 1,
|
|
||||||
"BoardLength": 500,
|
|
||||||
"BoardWidth": 400,
|
|
||||||
"BoardColum": 4,
|
|
||||||
"BoardRow": 3,
|
|
||||||
"TotalColum": 4,
|
|
||||||
"TotalRow": 3,
|
|
||||||
"BoardCenterXY": "300",
|
|
||||||
"HoleQty": 96,
|
|
||||||
"Version": 1,
|
|
||||||
"CreateTime": "2021-11-15",
|
|
||||||
"CreateName": "admin",
|
|
||||||
"UpdateTime": "2021-11-15",
|
|
||||||
"UpdateName": "admin"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,98 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": "ef121889-2724-4b3d-a786-bbf0bd213c3d",
|
|
||||||
"name": "9300_V02",
|
|
||||||
"row": 2,
|
|
||||||
"col": 3,
|
|
||||||
"create_name": "",
|
|
||||||
"create_time": "2023-08-12 16:02:20.994",
|
|
||||||
"update_name": null,
|
|
||||||
"update_time": null,
|
|
||||||
"remark": "9300_V02",
|
|
||||||
"isUse": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "9af15efc-29d2-4c44-8533-bbaf24913be6",
|
|
||||||
"name": "9310",
|
|
||||||
"row": 3,
|
|
||||||
"col": 4,
|
|
||||||
"create_name": "",
|
|
||||||
"create_time": "2023-08-12 16:23:07.472",
|
|
||||||
"update_name": null,
|
|
||||||
"update_time": null,
|
|
||||||
"remark": "9310",
|
|
||||||
"isUse": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546",
|
|
||||||
"name": "6版位",
|
|
||||||
"row": 2,
|
|
||||||
"col": 4,
|
|
||||||
"create_name": "",
|
|
||||||
"create_time": "2023-10-09 11:05:57.244",
|
|
||||||
"update_name": null,
|
|
||||||
"update_time": null,
|
|
||||||
"remark": "6版位",
|
|
||||||
"isUse": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "77673540-92c4-4404-b659-4257034a9c5e",
|
|
||||||
"name": "9300_V03",
|
|
||||||
"row": 2,
|
|
||||||
"col": 3,
|
|
||||||
"create_name": "",
|
|
||||||
"create_time": "2024-01-20 08:49:09.620",
|
|
||||||
"update_name": null,
|
|
||||||
"update_time": null,
|
|
||||||
"remark": "9300_V03",
|
|
||||||
"isUse": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e",
|
|
||||||
"name": "9320",
|
|
||||||
"row": 4,
|
|
||||||
"col": 7,
|
|
||||||
"create_name": "",
|
|
||||||
"create_time": "2025-03-10 13:44:17.994",
|
|
||||||
"update_name": null,
|
|
||||||
"update_time": null,
|
|
||||||
"remark": "9320",
|
|
||||||
"isUse": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "54092457-a8b8-4457-bccd-e8c251e83ebd",
|
|
||||||
"name": "7.17演示",
|
|
||||||
"row": 4,
|
|
||||||
"col": 4,
|
|
||||||
"create_name": "",
|
|
||||||
"create_time": "2025-07-12 17:08:38.336",
|
|
||||||
"update_name": null,
|
|
||||||
"update_time": null,
|
|
||||||
"remark": "7.17演示",
|
|
||||||
"isUse": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc",
|
|
||||||
"name": "北京大学 16版位",
|
|
||||||
"row": 4,
|
|
||||||
"col": 4,
|
|
||||||
"create_name": "",
|
|
||||||
"create_time": "2025-09-03 13:23:51.781",
|
|
||||||
"update_name": null,
|
|
||||||
"update_time": null,
|
|
||||||
"remark": "北京大学 16版位",
|
|
||||||
"isUse": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a",
|
|
||||||
"name": "TEST",
|
|
||||||
"row": 4,
|
|
||||||
"col": 4,
|
|
||||||
"create_name": "",
|
|
||||||
"create_time": "2025-10-27 14:36:03.266",
|
|
||||||
"update_name": null,
|
|
||||||
"update_time": null,
|
|
||||||
"remark": "TEST",
|
|
||||||
"isUse": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,872 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": "630a9ca9-dfbf-40f9-b90b-6df73e6a1d7f",
|
|
||||||
"number": 1,
|
|
||||||
"name": "T1",
|
|
||||||
"row": 0,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "db955443-1397-4a7a-a0cc-185eb6422c27",
|
|
||||||
"number": 2,
|
|
||||||
"name": "T2",
|
|
||||||
"row": 0,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "635e8265-e2b9-430e-8a4e-ddf94256266f",
|
|
||||||
"number": 3,
|
|
||||||
"name": "T3",
|
|
||||||
"row": 0,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 2,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "6de1521d-a249-4a7e-800f-1d49b5c7b56f",
|
|
||||||
"number": 4,
|
|
||||||
"name": "T4",
|
|
||||||
"row": 1,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4f9f2527-0f71-4ec4-a0ac-e546407e2960",
|
|
||||||
"number": 5,
|
|
||||||
"name": "T5",
|
|
||||||
"row": 1,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "ef121889-2724-4b3d-a786-bbf0bd213c3d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "55ecff40-453f-4a5f-9ed3-1267b0a03cae",
|
|
||||||
"number": 1,
|
|
||||||
"name": "T1",
|
|
||||||
"row": 0,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7dcd9c87-6702-4659-b28a-f6565b27f8e3",
|
|
||||||
"number": 2,
|
|
||||||
"name": "T2",
|
|
||||||
"row": 0,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "67e51bd6-6eee-46e4-931c-73d9e07397eb",
|
|
||||||
"number": 3,
|
|
||||||
"name": "T3",
|
|
||||||
"row": 0,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "e1289406-4f5e-4966-a1e6-fb29be6cd4bd",
|
|
||||||
"number": 4,
|
|
||||||
"name": "T4",
|
|
||||||
"row": 0,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4ecb9ef7-cbd4-44bc-a6a9-fdbbefdc01d6",
|
|
||||||
"number": 5,
|
|
||||||
"name": "T5",
|
|
||||||
"row": 1,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "c7bcaeeb-7ce7-479d-8dae-e82f4023a2b6",
|
|
||||||
"number": 6,
|
|
||||||
"name": "T6",
|
|
||||||
"row": 1,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "e502d5ee-3197-4f60-8ac4-3bc005349dfd",
|
|
||||||
"number": 7,
|
|
||||||
"name": "T7",
|
|
||||||
"row": 1,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "829c78b0-9e05-448f-9531-6d19c094c83f",
|
|
||||||
"number": 8,
|
|
||||||
"name": "T8",
|
|
||||||
"row": 1,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "d0fd64d6-360d-4f5e-9451-21a332e247f5",
|
|
||||||
"number": 9,
|
|
||||||
"name": "T9",
|
|
||||||
"row": 2,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7f3da25d-0be0-4e07-885f-fbbbfa952f9f",
|
|
||||||
"number": 10,
|
|
||||||
"name": "T10",
|
|
||||||
"row": 2,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "491d396d-7264-43d6-9ad4-60bffbe66c26",
|
|
||||||
"number": 11,
|
|
||||||
"name": "T11",
|
|
||||||
"row": 2,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "a8853b6d-639d-46f9-a4bf-9153c0c22461",
|
|
||||||
"number": 12,
|
|
||||||
"name": "T12",
|
|
||||||
"row": 2,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "9af15efc-29d2-4c44-8533-bbaf24913be6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "b7beb8d0-0003-471d-bd8d-a9c0e09b07d5",
|
|
||||||
"number": 1,
|
|
||||||
"name": "T1",
|
|
||||||
"row": 0,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "306e3f96-a6d7-484a-83ef-722e3710d5c4",
|
|
||||||
"number": 2,
|
|
||||||
"name": "T2",
|
|
||||||
"row": 0,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4e7bb617-ac1a-4360-b379-7ac4197089c4",
|
|
||||||
"number": 3,
|
|
||||||
"name": "T3",
|
|
||||||
"row": 0,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "af583180-c29d-418e-9061-9e030f77cf57",
|
|
||||||
"number": 4,
|
|
||||||
"name": "T4",
|
|
||||||
"row": 0,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 2,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "24a85ce8-e9e3-44f5-9d08-25116173ba75",
|
|
||||||
"number": 5,
|
|
||||||
"name": "T5",
|
|
||||||
"row": 1,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7bf61a40-f65a-4d2f-bb19-d42bfd80e2e9",
|
|
||||||
"number": 6,
|
|
||||||
"name": "T6",
|
|
||||||
"row": 1,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "a3177806-3c02-4c4f-86d6-604a38c2ba2a",
|
|
||||||
"number": 7,
|
|
||||||
"name": "T7",
|
|
||||||
"row": 1,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "6ed12532-eeae-4c16-a9ae-18f0b0cfc546"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "8ccaad5a-8588-4ff3-b0d7-17e7fd5ac6cc",
|
|
||||||
"number": 1,
|
|
||||||
"name": "T1",
|
|
||||||
"row": 0,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "93ae7707-b6b8-4bc4-8700-c500c3d7b165",
|
|
||||||
"number": 2,
|
|
||||||
"name": "T2",
|
|
||||||
"row": 0,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3591a07b-4922-4882-996f-7bebee843be1",
|
|
||||||
"number": 3,
|
|
||||||
"name": "T3",
|
|
||||||
"row": 0,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "669fdba9-b20c-4bd2-8352-8fe5682e3e0c",
|
|
||||||
"number": 4,
|
|
||||||
"name": "T4",
|
|
||||||
"row": 1,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "8bf3333e-4a73-4e4c-959a-8ae44e1038a2",
|
|
||||||
"number": 5,
|
|
||||||
"name": "T5",
|
|
||||||
"row": 1,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2837bf69-273a-4cbb-a74c-0af1b362f609",
|
|
||||||
"number": 6,
|
|
||||||
"name": "T6",
|
|
||||||
"row": 1,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "77673540-92c4-4404-b659-4257034a9c5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "e9d352fa-816a-4c01-a9e2-f52bce8771f1",
|
|
||||||
"number": 1,
|
|
||||||
"name": "T1",
|
|
||||||
"row": 0,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 4,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "713f1d85-b671-49f1-a2f9-11a64e5bb545",
|
|
||||||
"number": 2,
|
|
||||||
"name": "T2",
|
|
||||||
"row": 0,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 4,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ba2d8fd6-e2fa-4dd3-8afc-13472ca12afb",
|
|
||||||
"number": 3,
|
|
||||||
"name": "T3",
|
|
||||||
"row": 0,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 4,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "68137a87-ae26-4e27-8953-4b1335ed957c",
|
|
||||||
"number": 4,
|
|
||||||
"name": "T4",
|
|
||||||
"row": 0,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "182b2814-9c89-4a75-8456-9a82e774f876",
|
|
||||||
"number": 5,
|
|
||||||
"name": "T5",
|
|
||||||
"row": 0,
|
|
||||||
"col": 4,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "bc149d3c-9d54-45f0-8c33-23a5d4b70aff",
|
|
||||||
"number": 6,
|
|
||||||
"name": "T6",
|
|
||||||
"row": 0,
|
|
||||||
"col": 5,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7d9ce812-c39c-42fe-9b73-f35364a7b01f",
|
|
||||||
"number": 7,
|
|
||||||
"name": "T7",
|
|
||||||
"row": 0,
|
|
||||||
"col": 6,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4907b17d-c3f8-40a6-a8a2-e874f66195b1",
|
|
||||||
"number": 8,
|
|
||||||
"name": "T8",
|
|
||||||
"row": 1,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "f858fdb5-649f-4cb2-8e95-06a1b2d97113",
|
|
||||||
"number": 9,
|
|
||||||
"name": "T9",
|
|
||||||
"row": 1,
|
|
||||||
"col": 4,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cc5f91d2-494a-4991-9dda-3b82ae61556b",
|
|
||||||
"number": 10,
|
|
||||||
"name": "T10",
|
|
||||||
"row": 1,
|
|
||||||
"col": 5,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "afed9a1f-2f48-4ca9-ae14-eb1ae4e80181",
|
|
||||||
"number": 11,
|
|
||||||
"name": "T11",
|
|
||||||
"row": 1,
|
|
||||||
"col": 6,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "1d39cacd-7828-4318-9d4f-5bf8fc21d77d",
|
|
||||||
"number": 12,
|
|
||||||
"name": "T12",
|
|
||||||
"row": 2,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "086912ac-4f33-4214-a2c8-22acb5291bfe",
|
|
||||||
"number": 13,
|
|
||||||
"name": "T13",
|
|
||||||
"row": 2,
|
|
||||||
"col": 4,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "89d43ea4-93f6-4cbf-aba4-564b0067295f",
|
|
||||||
"number": 14,
|
|
||||||
"name": "T14",
|
|
||||||
"row": 2,
|
|
||||||
"col": 5,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "866b12a8-5ef6-426d-a65b-b0583a3d8f16",
|
|
||||||
"number": 15,
|
|
||||||
"name": "T15",
|
|
||||||
"row": 2,
|
|
||||||
"col": 6,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "6c5969a9-e763-48f4-97f4-a9027e3ea7ef",
|
|
||||||
"number": 16,
|
|
||||||
"name": "T16",
|
|
||||||
"row": 3,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "af8370be-076d-455d-b0b3-dd246f76d930",
|
|
||||||
"number": 17,
|
|
||||||
"name": "T17",
|
|
||||||
"row": 3,
|
|
||||||
"col": 4,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "abf2b8c7-79ef-4fd1-9f9b-14e7e6a128c7",
|
|
||||||
"number": 18,
|
|
||||||
"name": "T18",
|
|
||||||
"row": 3,
|
|
||||||
"col": 5,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "ca92a1e9-eb7d-4f9a-a42c-9bae461da797",
|
|
||||||
"number": 19,
|
|
||||||
"name": "T19",
|
|
||||||
"row": 3,
|
|
||||||
"col": 6,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "c08591fe-bc7e-42a8-bfa1-a27a4967058e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4a4df4fd-ea0b-461c-aad4-032bfda5abab",
|
|
||||||
"number": 1,
|
|
||||||
"name": "T1",
|
|
||||||
"row": 0,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 4,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "dba90870-4b7a-4fbd-b33f-948bbb594703",
|
|
||||||
"number": 2,
|
|
||||||
"name": "T2",
|
|
||||||
"row": 0,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "fddc5c2b-157f-4554-8b39-2c9e338f4d3a",
|
|
||||||
"number": 3,
|
|
||||||
"name": "T3",
|
|
||||||
"row": 0,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2569a396-2cd8-4cac-8b78-a8af1313c993",
|
|
||||||
"number": 4,
|
|
||||||
"name": "T4",
|
|
||||||
"row": 0,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "f0f693c7-a45f-4dd3-b629-621461ca9992",
|
|
||||||
"number": 5,
|
|
||||||
"name": "T5",
|
|
||||||
"row": 1,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "9dcba2bf-8a48-4bc6-a9b1-88f51ffaa8af",
|
|
||||||
"number": 6,
|
|
||||||
"name": "T6",
|
|
||||||
"row": 1,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "08449a38-0dca-48c4-a156-6f1055cf74c4",
|
|
||||||
"number": 7,
|
|
||||||
"name": "T7",
|
|
||||||
"row": 1,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "6ec7343f-12b9-42ae-86d1-3894758e69b4",
|
|
||||||
"number": 8,
|
|
||||||
"name": "T8",
|
|
||||||
"row": 2,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "b5f02dbc-ffc6-452a-ad9f-2d1ff3db2064",
|
|
||||||
"number": 9,
|
|
||||||
"name": "T9",
|
|
||||||
"row": 2,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7635380a-4f96-4894-9a54-37c2bd27f148",
|
|
||||||
"number": 10,
|
|
||||||
"name": "T10",
|
|
||||||
"row": 2,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "b4b6b063-5a0b-45a2-aa47-f427d4cd06f6",
|
|
||||||
"number": 11,
|
|
||||||
"name": "T11",
|
|
||||||
"row": 3,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "af02c689-7bca-476b-bd05-ce21d3e83f27",
|
|
||||||
"number": 12,
|
|
||||||
"name": "T12",
|
|
||||||
"row": 3,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "52a42e58-c0d6-420c-bc0b-575f749c7e3b",
|
|
||||||
"number": 13,
|
|
||||||
"name": "T13",
|
|
||||||
"row": 3,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "54092457-a8b8-4457-bccd-e8c251e83ebd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "169c12fe-e2f4-465e-9fd3-e58eac83a502",
|
|
||||||
"number": 1,
|
|
||||||
"name": "T1",
|
|
||||||
"row": 0,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "b6072651-1df5-4946-a5b4-fbff3fa54e6a",
|
|
||||||
"number": 2,
|
|
||||||
"name": "T2",
|
|
||||||
"row": 0,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "d0b8ea7c-f06e-4d94-98a8-70ffcba73c47",
|
|
||||||
"number": 3,
|
|
||||||
"name": "T3",
|
|
||||||
"row": 0,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "a7a8eb69-63f6-494e-a441-b7aef0f7c8a4",
|
|
||||||
"number": 4,
|
|
||||||
"name": "T4",
|
|
||||||
"row": 0,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "21966669-6761-4e37-947c-12fec82173fb",
|
|
||||||
"number": 5,
|
|
||||||
"name": "T5",
|
|
||||||
"row": 1,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2227b825-fe1d-4fa3-bcb2-6e4b3c10ea53",
|
|
||||||
"number": 6,
|
|
||||||
"name": "T6",
|
|
||||||
"row": 1,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "b799da88-c2d9-4ec4-81ec-bc0991a50fe5",
|
|
||||||
"number": 7,
|
|
||||||
"name": "T7",
|
|
||||||
"row": 1,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "adaaa00a-ff6b-4bd8-b8f1-bb100488f306",
|
|
||||||
"number": 8,
|
|
||||||
"name": "T8",
|
|
||||||
"row": 1,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3bc98311-b548-46d3-a0e0-4f1edcf10e24",
|
|
||||||
"number": 9,
|
|
||||||
"name": "T9",
|
|
||||||
"row": 2,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "81befc70-d249-49af-93dd-2efbe88c0211",
|
|
||||||
"number": 10,
|
|
||||||
"name": "T10",
|
|
||||||
"row": 2,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "45dd5535-0293-4d27-beab-1e486657b148",
|
|
||||||
"number": 11,
|
|
||||||
"name": "T11",
|
|
||||||
"row": 2,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "12ccf33a-6fe7-44a4-8643-b0b0ac6dd181",
|
|
||||||
"number": 12,
|
|
||||||
"name": "T12",
|
|
||||||
"row": 2,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "900272dd-23fd-41a4-a366-254999a30487",
|
|
||||||
"number": 13,
|
|
||||||
"name": "T13",
|
|
||||||
"row": 3,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "c366710d-2b81-4cee-8667-2b86e77e5c34",
|
|
||||||
"number": 14,
|
|
||||||
"name": "T14",
|
|
||||||
"row": 3,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "e18a9271-bc66-4c2b-8bc1-0fb129b5cc2f",
|
|
||||||
"number": 15,
|
|
||||||
"name": "T15",
|
|
||||||
"row": 3,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "6737cba0-de84-4c1f-992d-645e7f159b0c",
|
|
||||||
"number": 16,
|
|
||||||
"name": "T16",
|
|
||||||
"row": 3,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "e3855307-d91f-4ddc-9caf-565c0fd8adfc"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "8ace38ab-dbc7-48a1-8226-0fe92d176e07",
|
|
||||||
"number": 1,
|
|
||||||
"name": "T1",
|
|
||||||
"row": 0,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "033fec53-c52d-4b59-aec6-2135ae0e18b9",
|
|
||||||
"number": 2,
|
|
||||||
"name": "T2",
|
|
||||||
"row": 0,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "fa730930-8709-4250-928f-f757fce57b60",
|
|
||||||
"number": 3,
|
|
||||||
"name": "T3",
|
|
||||||
"row": 0,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "e279d6f1-5243-4224-8953-1033dbea25ac",
|
|
||||||
"number": 4,
|
|
||||||
"name": "T4",
|
|
||||||
"row": 0,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "76bd9426-6324-4af2-b12f-6ec0ff8c416e",
|
|
||||||
"number": 5,
|
|
||||||
"name": "T5",
|
|
||||||
"row": 1,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3f4ff652-3d87-4254-a235-bafde3359dae",
|
|
||||||
"number": 6,
|
|
||||||
"name": "T6",
|
|
||||||
"row": 1,
|
|
||||||
"col": 1,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "a38e94af-e91e-4e7a-b49d-8668001bb356",
|
|
||||||
"number": 7,
|
|
||||||
"name": "T7",
|
|
||||||
"row": 1,
|
|
||||||
"col": 2,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "9e45da24-1346-4886-a303-932880a79954",
|
|
||||||
"number": 8,
|
|
||||||
"name": "T8",
|
|
||||||
"row": 1,
|
|
||||||
"col": 3,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "1ac46e58-86ae-42d9-b230-d476b984507a",
|
|
||||||
"number": 9,
|
|
||||||
"name": "T9",
|
|
||||||
"row": 2,
|
|
||||||
"col": 0,
|
|
||||||
"row_span": 1,
|
|
||||||
"col_span": 1,
|
|
||||||
"plate_position_id": "a25563ec-8a2a-4de8-9ca2-a59c1c71427a"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[]
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
[]
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"uuid": "4034fa042e7f418db42ab80b0044a8cd",
|
|
||||||
"Code": "MDHC-001-10",
|
|
||||||
"Key": "c28ae2cb",
|
|
||||||
"Value": "MDHC-001-1000522001001612db9dc",
|
|
||||||
"CreateTime": "2022-01-22 17:07:00.8651386"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "8fb6d7589fdd42df93c1e1989ff13a62",
|
|
||||||
"Code": "MDHC-001-10",
|
|
||||||
"Key": "52980979",
|
|
||||||
"Value": "MDHC-001-100052200100119bb6731",
|
|
||||||
"CreateTime": "2022-01-22 20:19:20.9444209"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "efc4c92b40a94de6b0662c64486c18d1",
|
|
||||||
"Code": "MDHC-001-10",
|
|
||||||
"Key": "79da8402",
|
|
||||||
"Value": "MDHC-001-1000522001001e24ea780",
|
|
||||||
"CreateTime": "2022-01-22 20:19:26.8107506"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "3b81b1a9eabc4449b4dcbbbde47cb17f",
|
|
||||||
"Code": "MDHC-001-10",
|
|
||||||
"Key": "daa51755",
|
|
||||||
"Value": "MDHC-001-100052200100185dd22e2",
|
|
||||||
"CreateTime": "2022-01-22 20:19:36.1581374"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "d005a70801544e42ab9d216ad68dbf50",
|
|
||||||
"Code": "MDHC-023-0.2",
|
|
||||||
"Key": "992bbdab",
|
|
||||||
"Value": "MDHC-023-0.2005220010014871a385",
|
|
||||||
"CreateTime": "2022-02-16 15:49:53.760377"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "222315afb8e04320b0fcff10e3ddb8ae",
|
|
||||||
"Code": "MDHC-023-0.2",
|
|
||||||
"Key": "76d23270",
|
|
||||||
"Value": "MDHC-023-0.200522001001e61547ee",
|
|
||||||
"CreateTime": "2022-02-16 15:50:05.1932055"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "31e2a5d4f884419aa9ba96cef98b7385",
|
|
||||||
"Code": "MDHC-023-0.2",
|
|
||||||
"Key": "ba2b8a46",
|
|
||||||
"Value": "MDHC-023-0.2005220010013bfed6cf",
|
|
||||||
"CreateTime": "2022-02-16 17:26:20.0024235"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "9ccb8e0c5ca64ef09b8aced680395335",
|
|
||||||
"Code": "MDHC-023-0.2",
|
|
||||||
"Key": "1d1276d0",
|
|
||||||
"Value": "MDHC-023-0.2005220010015c039a9c",
|
|
||||||
"CreateTime": "2022-02-16 17:26:31.8479966"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"uuid": "f3932aeae93533f19c0519c4c14702aa",
|
|
||||||
"RoleCode": "admin",
|
|
||||||
"RoleName": "管理员",
|
|
||||||
"RoleMenu": "all",
|
|
||||||
"CreateTime": "2022-02-26 00:00:00.000",
|
|
||||||
"CreateName": "admin",
|
|
||||||
"UpdateTime": "2022-02-26 14:50:10.000",
|
|
||||||
"UpdateName": "admin"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "8c822592b360345fb59690e49ac6b181",
|
|
||||||
"RoleCode": "user",
|
|
||||||
"RoleName": "实验员",
|
|
||||||
"RoleMenu": "nosetting",
|
|
||||||
"CreateTime": "2022-02-26 14:54:16.000",
|
|
||||||
"CreateName": "admin",
|
|
||||||
"UpdateTime": "2022-02-26 14:54:19.000",
|
|
||||||
"UpdateName": "admin"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"uuid": "f3932aeae93533f19c0519c4c14702dd",
|
|
||||||
"UserName": "admin",
|
|
||||||
"Password": "NuGlByx4NZBm7XcV9f89qA==",
|
|
||||||
"RealName": "管理员",
|
|
||||||
"IsEnable": 1,
|
|
||||||
"uuidRole": "f3932aeae93533f19c0519c4c14702aa",
|
|
||||||
"IsDel": 0,
|
|
||||||
"CreateTime": "2022-02-26 14:51:41.000",
|
|
||||||
"CreateName": "admin",
|
|
||||||
"UpdateTime": "2022-02-26 14:51:49.000",
|
|
||||||
"UpdateName": "admin"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "5c522592b366645fb55690e49ac6b166",
|
|
||||||
"UserName": "user",
|
|
||||||
"Password": "4QrcOUm6Wau+VuBX8g+IPg==",
|
|
||||||
"RealName": "实验员",
|
|
||||||
"IsEnable": 1,
|
|
||||||
"uuidRole": "8c822592b360345fb59690e49ac6b181",
|
|
||||||
"IsDel": 0,
|
|
||||||
"CreateTime": "2022-02-26 14:56:57.000",
|
|
||||||
"CreateName": "admin",
|
|
||||||
"UpdateTime": "2022-02-26 14:58:39.000",
|
|
||||||
"UpdateName": "admin"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "ju0514zjhi9267mz8s0buspq8b9s0bgb",
|
|
||||||
"UserName": "Administrator",
|
|
||||||
"Password": "3J17Il4KOR+wKPszf/0cHQ==",
|
|
||||||
"RealName": "超级管理员",
|
|
||||||
"IsEnable": 1,
|
|
||||||
"uuidRole": "f3932aeae93533f19c0519c4c14702aa",
|
|
||||||
"IsDel": 0,
|
|
||||||
"CreateTime": "2023-08-12 00:00:00.000",
|
|
||||||
"CreateName": "admin",
|
|
||||||
"UpdateTime": "2023-08-12 00:00:00.000",
|
|
||||||
"UpdateName": "admin"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uuid": "2",
|
|
||||||
"UserName": "shortcut",
|
|
||||||
"Password": "4QrcOUm6Wau+VuBX8g+IPg==",
|
|
||||||
"RealName": "实验员",
|
|
||||||
"IsEnable": 1,
|
|
||||||
"uuidRole": "8c822592b360345fb59690e49ac6b181",
|
|
||||||
"IsDel": 0,
|
|
||||||
"CreateTime": null,
|
|
||||||
"CreateName": "admin",
|
|
||||||
"UpdateTime": "2023-10-23 00:00:00.000",
|
|
||||||
"UpdateName": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -6,7 +6,7 @@ import os
|
|||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Any, List, Dict, Optional, OrderedDict, Tuple, TypedDict, Union, Sequence, Iterator, Literal
|
from typing import Any, List, Dict, Optional, Tuple, TypedDict, Union, Sequence, Iterator, Literal
|
||||||
|
|
||||||
from pylabrobot.liquid_handling import (
|
from pylabrobot.liquid_handling import (
|
||||||
LiquidHandlerBackend,
|
LiquidHandlerBackend,
|
||||||
@@ -28,7 +28,7 @@ from pylabrobot.liquid_handling.standard import (
|
|||||||
ResourceMove,
|
ResourceMove,
|
||||||
ResourceDrop,
|
ResourceDrop,
|
||||||
)
|
)
|
||||||
from pylabrobot.resources import Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate, TipSpot, Trash, TubeRack, PlateAdapter
|
from pylabrobot.resources import Tip, Deck, Plate, Well, TipRack, Resource, Container, Coordinate, TipSpot, Trash
|
||||||
|
|
||||||
from unilabos.devices.liquid_handling.liquid_handler_abstract import LiquidHandlerAbstract
|
from unilabos.devices.liquid_handling.liquid_handler_abstract import LiquidHandlerAbstract
|
||||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode
|
||||||
@@ -70,129 +70,50 @@ class PRCXI9300Deck(Deck):
|
|||||||
super().__init__(name, size_x, size_y, size_z)
|
super().__init__(name, size_x, size_y, size_z)
|
||||||
self.slots = [None] * 6 # PRCXI 9300 有 6 个槽位
|
self.slots = [None] * 6 # PRCXI 9300 有 6 个槽位
|
||||||
|
|
||||||
class PRCXI9300Plate(Plate):
|
|
||||||
"""
|
class PRCXI9300Container(Plate, TipRack):
|
||||||
专用孔板类:
|
"""PRCXI 9300 的专用 Container 类,继承自 Plate和TipRack。
|
||||||
1. 继承自 PLR 原生 Plate,保留所有物理特性。
|
|
||||||
2. 增加 material_info 参数,用于在初始化时直接绑定 Unilab UUID。
|
该类定义了 PRCXI 9300 的工作台布局和槽位信息。
|
||||||
"""
|
"""
|
||||||
def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
|
|
||||||
category: str = "plate",
|
def __init__(
|
||||||
ordered_items: collections.OrderedDict = None,
|
self,
|
||||||
ordering: Optional[collections.OrderedDict] = None,
|
name: str,
|
||||||
model: Optional[str] = None,
|
size_x: float,
|
||||||
material_info: Optional[Dict[str, Any]] = None,
|
size_y: float,
|
||||||
**kwargs):
|
size_z: float,
|
||||||
items = ordered_items if ordered_items is not None else ordering
|
category: str,
|
||||||
super().__init__(name, size_x, size_y, size_z,
|
ordering: collections.OrderedDict,
|
||||||
ordered_items=items,
|
model: Optional[str] = None,
|
||||||
category=category,
|
**kwargs,
|
||||||
model=model, **kwargs)
|
):
|
||||||
|
super().__init__(name, size_x, size_y, size_z, category=category, ordering=ordering, model=model)
|
||||||
self._unilabos_state = {}
|
self._unilabos_state = {}
|
||||||
if material_info:
|
|
||||||
self._unilabos_state["Material"] = material_info
|
|
||||||
|
|
||||||
|
|
||||||
def load_state(self, state: Dict[str, Any]) -> 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]]:
|
|
||||||
try:
|
|
||||||
data = super().serialize_state()
|
|
||||||
except AttributeError:
|
|
||||||
data = {}
|
|
||||||
if hasattr(self, '_unilabos_state') and self._unilabos_state:
|
|
||||||
safe_state = {}
|
|
||||||
for k, v in self._unilabos_state.items():
|
|
||||||
# 如果是 Material 字典,深入检查
|
|
||||||
if k == "Material" and isinstance(v, dict):
|
|
||||||
safe_material = {}
|
|
||||||
for mk, mv in v.items():
|
|
||||||
# 只保留基本数据类型 (字符串, 数字, 布尔值, 列表, 字典)
|
|
||||||
if isinstance(mv, (str, int, float, bool, list, dict, type(None))):
|
|
||||||
safe_material[mk] = mv
|
|
||||||
else:
|
|
||||||
# 打印日志提醒(可选)
|
|
||||||
# print(f"Warning: Removing non-serializable key {mk} from {self.name}")
|
|
||||||
pass
|
|
||||||
safe_state[k] = safe_material
|
|
||||||
# 其他顶层属性也进行类型检查
|
|
||||||
elif isinstance(v, (str, int, float, bool, list, dict, type(None))):
|
|
||||||
safe_state[k] = v
|
|
||||||
|
|
||||||
data.update(safe_state)
|
|
||||||
return data
|
|
||||||
|
|
||||||
class PRCXI9300TipRack(TipRack):
|
|
||||||
""" 专用吸头盒类 """
|
|
||||||
def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
|
|
||||||
category: str = "tip_rack",
|
|
||||||
ordered_items: collections.OrderedDict = None,
|
|
||||||
ordering: Optional[collections.OrderedDict] = None,
|
|
||||||
model: Optional[str] = None,
|
|
||||||
material_info: Optional[Dict[str, Any]] = None,
|
|
||||||
**kwargs):
|
|
||||||
items = ordered_items if ordered_items is not None else ordering
|
|
||||||
super().__init__(name, size_x, size_y, size_z,
|
|
||||||
ordered_items=items,
|
|
||||||
category=category,
|
|
||||||
model=model, **kwargs)
|
|
||||||
self._unilabos_state = {}
|
|
||||||
if material_info:
|
|
||||||
self._unilabos_state["Material"] = material_info
|
|
||||||
|
|
||||||
def load_state(self, state: Dict[str, Any]) -> None:
|
|
||||||
super().load_state(state)
|
super().load_state(state)
|
||||||
self._unilabos_state = state
|
self._unilabos_state = state
|
||||||
|
|
||||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||||
try:
|
data = super().serialize_state()
|
||||||
data = super().serialize_state()
|
data.update(self._unilabos_state)
|
||||||
except AttributeError:
|
|
||||||
data = {}
|
|
||||||
if hasattr(self, '_unilabos_state') and self._unilabos_state:
|
|
||||||
safe_state = {}
|
|
||||||
for k, v in self._unilabos_state.items():
|
|
||||||
# 如果是 Material 字典,深入检查
|
|
||||||
if k == "Material" and isinstance(v, dict):
|
|
||||||
safe_material = {}
|
|
||||||
for mk, mv in v.items():
|
|
||||||
# 只保留基本数据类型 (字符串, 数字, 布尔值, 列表, 字典)
|
|
||||||
if isinstance(mv, (str, int, float, bool, list, dict, type(None))):
|
|
||||||
safe_material[mk] = mv
|
|
||||||
else:
|
|
||||||
# 打印日志提醒(可选)
|
|
||||||
# print(f"Warning: Removing non-serializable key {mk} from {self.name}")
|
|
||||||
pass
|
|
||||||
safe_state[k] = safe_material
|
|
||||||
# 其他顶层属性也进行类型检查
|
|
||||||
elif isinstance(v, (str, int, float, bool, list, dict, type(None))):
|
|
||||||
safe_state[k] = v
|
|
||||||
|
|
||||||
data.update(safe_state)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class PRCXI9300Trash(Trash):
|
class PRCXI9300Trash(Trash):
|
||||||
"""PRCXI 9300 的专用 Trash 类,继承自 Trash。
|
"""PRCXI 9300 的专用 Trash 类,继承自 Trash。
|
||||||
|
|
||||||
该类定义了 PRCXI 9300 的工作台布局和槽位信息。
|
该类定义了 PRCXI 9300 的工作台布局和槽位信息。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
|
def __init__(self, name: str, size_x: float, size_y: float, size_z: float, category: str, **kwargs):
|
||||||
category: str = "trash",
|
|
||||||
material_info: Optional[Dict[str, Any]] = None,
|
|
||||||
**kwargs):
|
|
||||||
|
|
||||||
if name != "trash":
|
if name != "trash":
|
||||||
print(f"Warning: PRCXI9300Trash usually expects name='trash' for backend logic, but got '{name}'.")
|
name = "trash"
|
||||||
super().__init__(name, size_x, size_y, size_z, **kwargs)
|
print("PRCXI9300Trash name must be 'trash', using 'trash' instead.")
|
||||||
|
super().__init__(name, size_x, size_y, size_z, category=category, **kwargs)
|
||||||
self._unilabos_state = {}
|
self._unilabos_state = {}
|
||||||
# 初始化时注入 UUID
|
|
||||||
if material_info:
|
|
||||||
self._unilabos_state["Material"] = material_info
|
|
||||||
|
|
||||||
def load_state(self, state: Dict[str, Any]) -> None:
|
def load_state(self, state: Dict[str, Any]) -> None:
|
||||||
"""从给定的状态加载工作台信息。"""
|
"""从给定的状态加载工作台信息。"""
|
||||||
@@ -200,152 +121,10 @@ class PRCXI9300Trash(Trash):
|
|||||||
self._unilabos_state = state
|
self._unilabos_state = state
|
||||||
|
|
||||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||||
try:
|
data = super().serialize_state()
|
||||||
data = super().serialize_state()
|
data.update(self._unilabos_state)
|
||||||
except AttributeError:
|
|
||||||
data = {}
|
|
||||||
if hasattr(self, '_unilabos_state') and self._unilabos_state:
|
|
||||||
safe_state = {}
|
|
||||||
for k, v in self._unilabos_state.items():
|
|
||||||
# 如果是 Material 字典,深入检查
|
|
||||||
if k == "Material" and isinstance(v, dict):
|
|
||||||
safe_material = {}
|
|
||||||
for mk, mv in v.items():
|
|
||||||
# 只保留基本数据类型 (字符串, 数字, 布尔值, 列表, 字典)
|
|
||||||
if isinstance(mv, (str, int, float, bool, list, dict, type(None))):
|
|
||||||
safe_material[mk] = mv
|
|
||||||
else:
|
|
||||||
# 打印日志提醒(可选)
|
|
||||||
# print(f"Warning: Removing non-serializable key {mk} from {self.name}")
|
|
||||||
pass
|
|
||||||
safe_state[k] = safe_material
|
|
||||||
# 其他顶层属性也进行类型检查
|
|
||||||
elif isinstance(v, (str, int, float, bool, list, dict, type(None))):
|
|
||||||
safe_state[k] = v
|
|
||||||
|
|
||||||
data.update(safe_state)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
class PRCXI9300TubeRack(TubeRack):
|
|
||||||
"""
|
|
||||||
专用管架类:用于 EP 管架、试管架等。
|
|
||||||
继承自 PLR 的 TubeRack,并支持注入 material_info (UUID)。
|
|
||||||
"""
|
|
||||||
def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
|
|
||||||
category: str = "tube_rack",
|
|
||||||
items: Optional[Dict[str, Any]] = None,
|
|
||||||
ordered_items: Optional[OrderedDict] = None,
|
|
||||||
model: Optional[str] = None,
|
|
||||||
material_info: Optional[Dict[str, Any]] = None,
|
|
||||||
**kwargs):
|
|
||||||
|
|
||||||
# 兼容处理:PLR 的 TubeRack 构造函数可能接受 items 或 ordered_items
|
|
||||||
items_to_pass = items if items is not None else ordered_items
|
|
||||||
super().__init__(name, size_x, size_y, size_z,
|
|
||||||
ordered_items=ordered_items,
|
|
||||||
model=model,
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
self._unilabos_state = {}
|
|
||||||
if material_info:
|
|
||||||
self._unilabos_state["Material"] = material_info
|
|
||||||
|
|
||||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
|
||||||
try:
|
|
||||||
data = super().serialize_state()
|
|
||||||
except AttributeError:
|
|
||||||
data = {}
|
|
||||||
if hasattr(self, '_unilabos_state') and self._unilabos_state:
|
|
||||||
safe_state = {}
|
|
||||||
for k, v in self._unilabos_state.items():
|
|
||||||
# 如果是 Material 字典,深入检查
|
|
||||||
if k == "Material" and isinstance(v, dict):
|
|
||||||
safe_material = {}
|
|
||||||
for mk, mv in v.items():
|
|
||||||
# 只保留基本数据类型 (字符串, 数字, 布尔值, 列表, 字典)
|
|
||||||
if isinstance(mv, (str, int, float, bool, list, dict, type(None))):
|
|
||||||
safe_material[mk] = mv
|
|
||||||
else:
|
|
||||||
# 打印日志提醒(可选)
|
|
||||||
# print(f"Warning: Removing non-serializable key {mk} from {self.name}")
|
|
||||||
pass
|
|
||||||
safe_state[k] = safe_material
|
|
||||||
# 其他顶层属性也进行类型检查
|
|
||||||
elif isinstance(v, (str, int, float, bool, list, dict, type(None))):
|
|
||||||
safe_state[k] = v
|
|
||||||
|
|
||||||
data.update(safe_state)
|
|
||||||
return data
|
|
||||||
|
|
||||||
class PRCXI9300PlateAdapter(PlateAdapter):
|
|
||||||
"""
|
|
||||||
专用板式适配器类:用于承载 Plate 的底座(如 PCR 适配器、磁吸架等)。
|
|
||||||
支持注入 material_info (UUID)。
|
|
||||||
"""
|
|
||||||
def __init__(self, name: str, size_x: float, size_y: float, size_z: float,
|
|
||||||
category: str = "plate_adapter",
|
|
||||||
model: Optional[str] = None,
|
|
||||||
material_info: Optional[Dict[str, Any]] = None,
|
|
||||||
# 参数给予默认值 (标准96孔板尺寸)
|
|
||||||
adapter_hole_size_x: float = 127.76,
|
|
||||||
adapter_hole_size_y: float = 85.48,
|
|
||||||
adapter_hole_size_z: float = 10.0, # 假设凹槽深度或板子放置高度
|
|
||||||
dx: Optional[float] = None,
|
|
||||||
dy: Optional[float] = None,
|
|
||||||
dz: float = 0.0, # 默认Z轴偏移
|
|
||||||
**kwargs):
|
|
||||||
|
|
||||||
# 自动居中计算:如果未指定 dx/dy,则根据适配器尺寸和孔尺寸计算居中位置
|
|
||||||
if dx is None:
|
|
||||||
dx = (size_x - adapter_hole_size_x) / 2
|
|
||||||
if dy is None:
|
|
||||||
dy = (size_y - adapter_hole_size_y) / 2
|
|
||||||
|
|
||||||
super().__init__(
|
|
||||||
name=name,
|
|
||||||
size_x=size_x,
|
|
||||||
size_y=size_y,
|
|
||||||
size_z=size_z,
|
|
||||||
dx=dx,
|
|
||||||
dy=dy,
|
|
||||||
dz=dz,
|
|
||||||
adapter_hole_size_x=adapter_hole_size_x,
|
|
||||||
adapter_hole_size_y=adapter_hole_size_y,
|
|
||||||
adapter_hole_size_z=adapter_hole_size_z,
|
|
||||||
model=model,
|
|
||||||
**kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
self._unilabos_state = {}
|
|
||||||
if material_info:
|
|
||||||
self._unilabos_state["Material"] = material_info
|
|
||||||
|
|
||||||
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
|
||||||
try:
|
|
||||||
data = super().serialize_state()
|
|
||||||
except AttributeError:
|
|
||||||
data = {}
|
|
||||||
if hasattr(self, '_unilabos_state') and self._unilabos_state:
|
|
||||||
safe_state = {}
|
|
||||||
for k, v in self._unilabos_state.items():
|
|
||||||
# 如果是 Material 字典,深入检查
|
|
||||||
if k == "Material" and isinstance(v, dict):
|
|
||||||
safe_material = {}
|
|
||||||
for mk, mv in v.items():
|
|
||||||
# 只保留基本数据类型 (字符串, 数字, 布尔值, 列表, 字典)
|
|
||||||
if isinstance(mv, (str, int, float, bool, list, dict, type(None))):
|
|
||||||
safe_material[mk] = mv
|
|
||||||
else:
|
|
||||||
# 打印日志提醒(可选)
|
|
||||||
# print(f"Warning: Removing non-serializable key {mk} from {self.name}")
|
|
||||||
pass
|
|
||||||
safe_state[k] = safe_material
|
|
||||||
# 其他顶层属性也进行类型检查
|
|
||||||
elif isinstance(v, (str, int, float, bool, list, dict, type(None))):
|
|
||||||
safe_state[k] = v
|
|
||||||
|
|
||||||
data.update(safe_state)
|
|
||||||
return data
|
|
||||||
|
|
||||||
class PRCXI9300Handler(LiquidHandlerAbstract):
|
class PRCXI9300Handler(LiquidHandlerAbstract):
|
||||||
support_touch_tip = False
|
support_touch_tip = False
|
||||||
@@ -375,15 +154,10 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
tablets_info = []
|
tablets_info = []
|
||||||
count = 0
|
count = 0
|
||||||
for child in deck.children:
|
for child in deck.children:
|
||||||
child_state = getattr(child, "_unilabos_state", {})
|
if "Material" in child._unilabos_state:
|
||||||
if "Material" in child_state:
|
|
||||||
count += 1
|
count += 1
|
||||||
tablets_info.append(
|
tablets_info.append(
|
||||||
WorkTablets(
|
WorkTablets(Number=count, Code=f"T{count}", Material=child._unilabos_state["Material"])
|
||||||
Number=count,
|
|
||||||
Code=f"T{count}",
|
|
||||||
Material=child_state["Material"]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if is_9320:
|
if is_9320:
|
||||||
print("当前设备是9320")
|
print("当前设备是9320")
|
||||||
@@ -660,6 +434,7 @@ class PRCXI9300Handler(LiquidHandlerAbstract):
|
|||||||
async def move_to(self, well: Well, dis_to_top: float = 0, channel: int = 0):
|
async def move_to(self, well: Well, dis_to_top: float = 0, channel: int = 0):
|
||||||
return await super().move_to(well, dis_to_top, channel)
|
return await super().move_to(well, dis_to_top, channel)
|
||||||
|
|
||||||
|
|
||||||
class PRCXI9300Backend(LiquidHandlerBackend):
|
class PRCXI9300Backend(LiquidHandlerBackend):
|
||||||
"""PRCXI 9300 的后端实现,继承自 LiquidHandlerBackend。
|
"""PRCXI 9300 的后端实现,继承自 LiquidHandlerBackend。
|
||||||
|
|
||||||
@@ -1758,31 +1533,31 @@ if __name__ == "__main__":
|
|||||||
from pylabrobot.resources.opentrons.tip_racks import tipone_96_tiprack_200ul, opentrons_96_tiprack_10ul
|
from pylabrobot.resources.opentrons.tip_racks import tipone_96_tiprack_200ul, opentrons_96_tiprack_10ul
|
||||||
from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
|
from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
|
||||||
|
|
||||||
def get_well_container(name: str) -> PRCXI9300Plate:
|
def get_well_container(name: str) -> PRCXI9300Container:
|
||||||
well_containers = corning_96_wellplate_360ul_flat(name).serialize()
|
well_containers = corning_96_wellplate_360ul_flat(name).serialize()
|
||||||
plate = PRCXI9300Plate(
|
plate = PRCXI9300Container(
|
||||||
name=name, size_x=50, size_y=50, size_z=10, category="plate", ordered_items=well_containers["ordering"]
|
name=name, size_x=50, size_y=50, size_z=10, category="plate", ordering=well_containers["ordering"]
|
||||||
)
|
)
|
||||||
plate_serialized = plate.serialize()
|
plate_serialized = plate.serialize()
|
||||||
plate_serialized["parent_name"] = deck.name
|
plate_serialized["parent_name"] = deck.name
|
||||||
well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
|
well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
|
||||||
new_plate: PRCXI9300Plate = PRCXI9300Plate.deserialize(well_containers)
|
new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
|
||||||
return new_plate
|
return new_plate
|
||||||
|
|
||||||
def get_tip_rack(name: str, child_prefix: str = "tip") -> PRCXI9300TipRack:
|
def get_tip_rack(name: str, child_prefix: str = "tip") -> PRCXI9300Container:
|
||||||
tip_racks = opentrons_96_tiprack_10ul(name).serialize()
|
tip_racks = opentrons_96_tiprack_10ul(name).serialize()
|
||||||
tip_rack = PRCXI9300TipRack(
|
tip_rack = PRCXI9300Container(
|
||||||
name=name,
|
name=name,
|
||||||
size_x=50,
|
size_x=50,
|
||||||
size_y=50,
|
size_y=50,
|
||||||
size_z=10,
|
size_z=10,
|
||||||
category="tip_rack",
|
category="tip_rack",
|
||||||
ordered_items=collections.OrderedDict({k: f"{child_prefix}_{k}" for k, v in tip_racks["ordering"].items()}),
|
ordering=collections.OrderedDict({k: f"{child_prefix}_{k}" for k, v in tip_racks["ordering"].items()}),
|
||||||
)
|
)
|
||||||
tip_rack_serialized = tip_rack.serialize()
|
tip_rack_serialized = tip_rack.serialize()
|
||||||
tip_rack_serialized["parent_name"] = deck.name
|
tip_rack_serialized["parent_name"] = deck.name
|
||||||
tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
|
tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
|
||||||
new_tip_rack: PRCXI9300TipRack = PRCXI9300TipRack.deserialize(tip_racks)
|
new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
|
||||||
return new_tip_rack
|
return new_tip_rack
|
||||||
|
|
||||||
plate1 = get_tip_rack("RackT1")
|
plate1 = get_tip_rack("RackT1")
|
||||||
@@ -1829,8 +1604,8 @@ if __name__ == "__main__":
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
plate7 = PRCXI9300Plate(
|
plate7 = PRCXI9300Container(
|
||||||
name="plateT7", size_x=50, size_y=50, size_z=10, category="plate", ordered_items=collections.OrderedDict()
|
name="plateT7", size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()
|
||||||
)
|
)
|
||||||
plate7.load_state({"Material": {"uuid": "04211a2dc93547fe9bf6121eac533650"}})
|
plate7.load_state({"Material": {"uuid": "04211a2dc93547fe9bf6121eac533650"}})
|
||||||
plate8 = get_tip_rack("PlateT8")
|
plate8 = get_tip_rack("PlateT8")
|
||||||
@@ -1904,13 +1679,13 @@ if __name__ == "__main__":
|
|||||||
deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
|
deck.assign_child_resource(plate1, location=Coordinate(0, 0, 0))
|
||||||
deck.assign_child_resource(plate2, location=Coordinate(0, 0, 0))
|
deck.assign_child_resource(plate2, location=Coordinate(0, 0, 0))
|
||||||
deck.assign_child_resource(
|
deck.assign_child_resource(
|
||||||
PRCXI9300Plate(
|
PRCXI9300Container(
|
||||||
name="container_for_nothin3",
|
name="container_for_nothin3",
|
||||||
size_x=50,
|
size_x=50,
|
||||||
size_y=50,
|
size_y=50,
|
||||||
size_z=10,
|
size_z=10,
|
||||||
category="plate",
|
category="plate",
|
||||||
ordered_items=collections.OrderedDict(),
|
ordering=collections.OrderedDict(),
|
||||||
),
|
),
|
||||||
location=Coordinate(0, 0, 0),
|
location=Coordinate(0, 0, 0),
|
||||||
)
|
)
|
||||||
@@ -1918,48 +1693,48 @@ if __name__ == "__main__":
|
|||||||
deck.assign_child_resource(plate5, location=Coordinate(0, 0, 0))
|
deck.assign_child_resource(plate5, location=Coordinate(0, 0, 0))
|
||||||
deck.assign_child_resource(plate6, location=Coordinate(0, 0, 0))
|
deck.assign_child_resource(plate6, location=Coordinate(0, 0, 0))
|
||||||
deck.assign_child_resource(
|
deck.assign_child_resource(
|
||||||
PRCXI9300Plate(
|
PRCXI9300Container(
|
||||||
name="container_for_nothing7",
|
name="container_for_nothing7",
|
||||||
size_x=50,
|
size_x=50,
|
||||||
size_y=50,
|
size_y=50,
|
||||||
size_z=10,
|
size_z=10,
|
||||||
category="plate",
|
category="plate",
|
||||||
ordered_items=collections.OrderedDict(),
|
ordering=collections.OrderedDict(),
|
||||||
),
|
),
|
||||||
location=Coordinate(0, 0, 0),
|
location=Coordinate(0, 0, 0),
|
||||||
)
|
)
|
||||||
deck.assign_child_resource(
|
deck.assign_child_resource(
|
||||||
PRCXI9300Plate(
|
PRCXI9300Container(
|
||||||
name="container_for_nothing8",
|
name="container_for_nothing8",
|
||||||
size_x=50,
|
size_x=50,
|
||||||
size_y=50,
|
size_y=50,
|
||||||
size_z=10,
|
size_z=10,
|
||||||
category="plate",
|
category="plate",
|
||||||
ordered_items=collections.OrderedDict(),
|
ordering=collections.OrderedDict(),
|
||||||
),
|
),
|
||||||
location=Coordinate(0, 0, 0),
|
location=Coordinate(0, 0, 0),
|
||||||
)
|
)
|
||||||
deck.assign_child_resource(plate9, location=Coordinate(0, 0, 0))
|
deck.assign_child_resource(plate9, location=Coordinate(0, 0, 0))
|
||||||
deck.assign_child_resource(plate10, location=Coordinate(0, 0, 0))
|
deck.assign_child_resource(plate10, location=Coordinate(0, 0, 0))
|
||||||
deck.assign_child_resource(
|
deck.assign_child_resource(
|
||||||
PRCXI9300Plate(
|
PRCXI9300Container(
|
||||||
name="container_for_nothing11",
|
name="container_for_nothing11",
|
||||||
size_x=50,
|
size_x=50,
|
||||||
size_y=50,
|
size_y=50,
|
||||||
size_z=10,
|
size_z=10,
|
||||||
category="plate",
|
category="plate",
|
||||||
ordered_items=collections.OrderedDict(),
|
ordering=collections.OrderedDict(),
|
||||||
),
|
),
|
||||||
location=Coordinate(0, 0, 0),
|
location=Coordinate(0, 0, 0),
|
||||||
)
|
)
|
||||||
deck.assign_child_resource(
|
deck.assign_child_resource(
|
||||||
PRCXI9300Plate(
|
PRCXI9300Container(
|
||||||
name="container_for_nothing12",
|
name="container_for_nothing12",
|
||||||
size_x=50,
|
size_x=50,
|
||||||
size_y=50,
|
size_y=50,
|
||||||
size_z=10,
|
size_z=10,
|
||||||
category="plate",
|
category="plate",
|
||||||
ordered_items=collections.OrderedDict(),
|
ordering=collections.OrderedDict(),
|
||||||
),
|
),
|
||||||
location=Coordinate(0, 0, 0),
|
location=Coordinate(0, 0, 0),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,841 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
from pylabrobot.resources import Tube, Coordinate
|
|
||||||
from pylabrobot.resources.well import Well, WellBottomType, CrossSectionType
|
|
||||||
from pylabrobot.resources.tip import Tip, TipCreator
|
|
||||||
from pylabrobot.resources.tip_rack import TipRack, TipSpot
|
|
||||||
from pylabrobot.resources.utils import create_ordered_items_2d
|
|
||||||
from pylabrobot.resources.height_volume_functions import (
|
|
||||||
compute_height_from_volume_rectangle,
|
|
||||||
compute_volume_from_height_rectangle,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .prcxi import PRCXI9300Plate, PRCXI9300TipRack, PRCXI9300Trash, PRCXI9300TubeRack, PRCXI9300PlateAdapter
|
|
||||||
|
|
||||||
def _make_tip_helper(volume: float, length: float, depth: float) -> Tip:
|
|
||||||
"""
|
|
||||||
PLR 的 Tip 类参数名为: maximal_volume, total_tip_length, fitting_depth
|
|
||||||
"""
|
|
||||||
return Tip(
|
|
||||||
has_filter=False, # 默认无滤芯
|
|
||||||
maximal_volume=volume,
|
|
||||||
total_tip_length=length,
|
|
||||||
fitting_depth=depth
|
|
||||||
)
|
|
||||||
|
|
||||||
# =========================================================================
|
|
||||||
# 标准品 参照 PLR 标准库的参数,但是用 PRCXI9300Plate 实例化,并注入 UUID
|
|
||||||
# =========================================================================
|
|
||||||
def PRCXI_BioER_96_wellplate(name: str) -> PRCXI9300Plate:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: ZX-019-2.2 (2.2ml 深孔板)
|
|
||||||
原型: pylabrobot.resources.bioer.BioER_96_wellplate_Vb_2200uL
|
|
||||||
"""
|
|
||||||
return PRCXI9300Plate(
|
|
||||||
name=name,
|
|
||||||
size_x=127.1,
|
|
||||||
size_y=85.0,
|
|
||||||
size_z=44.2,
|
|
||||||
lid=None,
|
|
||||||
model="PRCXI_BioER_96_wellplate",
|
|
||||||
category="plate",
|
|
||||||
material_info={
|
|
||||||
"uuid": "ca877b8b114a4310b429d1de4aae96ee",
|
|
||||||
"Code": "ZX-019-2.2",
|
|
||||||
"Name": "2.2ml 深孔板",
|
|
||||||
"materialEnum": 0,
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
Well,
|
|
||||||
size_x=8.25,
|
|
||||||
size_y=8.25,
|
|
||||||
size_z=39.3, # 修改过
|
|
||||||
dx=9.5,
|
|
||||||
dy=7.5,
|
|
||||||
dz=6,
|
|
||||||
material_z_thickness=0.8,
|
|
||||||
item_dx=9.0,
|
|
||||||
item_dy=9.0,
|
|
||||||
num_items_x=12,
|
|
||||||
num_items_y=8,
|
|
||||||
cross_section_type=CrossSectionType.RECTANGLE,
|
|
||||||
bottom_type=WellBottomType.V, # 是否需要修改?
|
|
||||||
max_volume=2200,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def PRCXI_nest_1_troughplate(name: str) -> PRCXI9300Plate:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: ZX-58-10000 (储液槽)
|
|
||||||
原型: pylabrobot.resources.nest.nest_1_troughplate_195000uL_Vb
|
|
||||||
"""
|
|
||||||
well_size_x = 127.76 - (14.38 - 9 / 2) * 2
|
|
||||||
well_size_y = 85.48 - (11.24 - 9 / 2) * 2
|
|
||||||
well_kwargs = {
|
|
||||||
"size_x": well_size_x,
|
|
||||||
"size_y": well_size_y,
|
|
||||||
"size_z": 26.85,
|
|
||||||
"bottom_type": WellBottomType.V,
|
|
||||||
"compute_height_from_volume": lambda liquid_volume: compute_height_from_volume_rectangle(
|
|
||||||
liquid_volume=liquid_volume, well_length=well_size_x, well_width=well_size_y
|
|
||||||
),
|
|
||||||
"compute_volume_from_height": lambda liquid_height: compute_volume_from_height_rectangle(
|
|
||||||
liquid_height=liquid_height, well_length=well_size_x, well_width=well_size_y
|
|
||||||
),
|
|
||||||
"material_z_thickness": 31.4 - 26.85 - 3.55,
|
|
||||||
}
|
|
||||||
|
|
||||||
return PRCXI9300Plate(
|
|
||||||
name=name,
|
|
||||||
size_x=127.76,
|
|
||||||
size_y=85.48,
|
|
||||||
size_z=31.4,
|
|
||||||
lid=None,
|
|
||||||
model="PRCXI_Nest_1_troughplate",
|
|
||||||
category="plate",
|
|
||||||
material_info={
|
|
||||||
"uuid": "04211a2dc93547fe9bf6121eac533650",
|
|
||||||
"Code": "ZX-58-10000",
|
|
||||||
"Name": "储液槽",
|
|
||||||
"materialEnum": 0,
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
Well,
|
|
||||||
num_items_x=1,
|
|
||||||
num_items_y=1,
|
|
||||||
dx=14.38 - 9 / 2,
|
|
||||||
dy=11.24 - 9 / 2,
|
|
||||||
dz=3.55,
|
|
||||||
item_dx=9.0,
|
|
||||||
item_dy=9.0,
|
|
||||||
**well_kwargs, # 传入上面计算好的孔参数
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def PRCXI_BioRad_384_wellplate(name: str) -> PRCXI9300Plate:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: q3 (384板)
|
|
||||||
原型: pylabrobot.resources.biorad.BioRad_384_wellplate_50uL_Vb
|
|
||||||
"""
|
|
||||||
return PRCXI9300Plate(
|
|
||||||
name=name,
|
|
||||||
# 直接抄录 PLR 标准品的物理尺寸
|
|
||||||
size_x=127.76,
|
|
||||||
size_y=85.48,
|
|
||||||
size_z=10.40,
|
|
||||||
model="BioRad_384_wellplate_50uL_Vb",
|
|
||||||
category="plate",
|
|
||||||
# 2. 注入 Unilab 必须的 UUID 信息
|
|
||||||
material_info={
|
|
||||||
"uuid": "853dcfb6226f476e8b23c250217dc7da",
|
|
||||||
"Code": "q3",
|
|
||||||
"Name": "384板",
|
|
||||||
"SupplyType": 1,
|
|
||||||
},
|
|
||||||
# 3. 定义孔的排列 (抄录标准参数)
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
Well,
|
|
||||||
num_items_x=24,
|
|
||||||
num_items_y=16,
|
|
||||||
dx=10.58, # A1 左边缘距离板子左边缘 需要进一步测量
|
|
||||||
dy=7.44, # P1 下边缘距离板子下边缘 需要进一步测量
|
|
||||||
dz=1.05,
|
|
||||||
item_dx=4.5,
|
|
||||||
item_dy=4.5,
|
|
||||||
size_x=3.10,
|
|
||||||
size_y=3.10,
|
|
||||||
size_z=9.35,
|
|
||||||
max_volume=50,
|
|
||||||
material_z_thickness=1,
|
|
||||||
bottom_type=WellBottomType.V,
|
|
||||||
cross_section_type=CrossSectionType.CIRCLE,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
def PRCXI_AGenBio_4_troughplate(name: str) -> PRCXI9300Plate:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: sdfrth654 (4道储液槽)
|
|
||||||
原型: pylabrobot.resources.agenbio.AGenBio_4_troughplate_75000uL_Vb
|
|
||||||
"""
|
|
||||||
INNER_WELL_WIDTH = 26.1
|
|
||||||
INNER_WELL_LENGTH = 71.2
|
|
||||||
well_kwargs = {
|
|
||||||
"size_x": 26,
|
|
||||||
"size_y": 71.2,
|
|
||||||
"size_z": 42.55,
|
|
||||||
"bottom_type": WellBottomType.FLAT,
|
|
||||||
"cross_section_type": CrossSectionType.RECTANGLE,
|
|
||||||
"compute_height_from_volume": lambda liquid_volume: compute_height_from_volume_rectangle(
|
|
||||||
liquid_volume,
|
|
||||||
INNER_WELL_LENGTH,
|
|
||||||
INNER_WELL_WIDTH,
|
|
||||||
),
|
|
||||||
"compute_volume_from_height": lambda liquid_height: compute_volume_from_height_rectangle(
|
|
||||||
liquid_height,
|
|
||||||
INNER_WELL_LENGTH,
|
|
||||||
INNER_WELL_WIDTH,
|
|
||||||
),
|
|
||||||
"material_z_thickness": 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
return PRCXI9300Plate(
|
|
||||||
name=name,
|
|
||||||
size_x=127.76,
|
|
||||||
size_y=85.48,
|
|
||||||
size_z=43.80,
|
|
||||||
model="PRCXI_AGenBio_4_troughplate",
|
|
||||||
category="plate",
|
|
||||||
material_info={
|
|
||||||
"uuid": "01953864f6f140ccaa8ddffd4f3e46f5",
|
|
||||||
"Code": "sdfrth654",
|
|
||||||
"Name": "4道储液槽",
|
|
||||||
"materialEnum": 0,
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
Well,
|
|
||||||
num_items_x=4,
|
|
||||||
num_items_y=1,
|
|
||||||
dx=9.8,
|
|
||||||
dy=7.2,
|
|
||||||
dz=0.9,
|
|
||||||
item_dx=INNER_WELL_WIDTH + 1, # 1 mm wall thickness
|
|
||||||
item_dy=INNER_WELL_LENGTH,
|
|
||||||
**well_kwargs,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def PRCXI_nest_12_troughplate(name: str) -> PRCXI9300Plate:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: 12道储液槽 (12道储液槽)
|
|
||||||
原型: pylabrobot.resources.nest.nest_12_troughplate_15000uL_Vb
|
|
||||||
"""
|
|
||||||
well_size_x = 8.2
|
|
||||||
well_size_y = 71.2
|
|
||||||
well_kwargs = {
|
|
||||||
"size_x": well_size_x,
|
|
||||||
"size_y": well_size_y,
|
|
||||||
"size_z": 26.85,
|
|
||||||
"bottom_type": WellBottomType.V,
|
|
||||||
"compute_height_from_volume": lambda liquid_volume: compute_height_from_volume_rectangle(
|
|
||||||
liquid_volume=liquid_volume, well_length=well_size_x, well_width=well_size_y
|
|
||||||
),
|
|
||||||
"compute_volume_from_height": lambda liquid_height: compute_volume_from_height_rectangle(
|
|
||||||
liquid_height=liquid_height, well_length=well_size_x, well_width=well_size_y
|
|
||||||
),
|
|
||||||
"material_z_thickness": 31.4 - 26.85 - 3.55,
|
|
||||||
}
|
|
||||||
|
|
||||||
return PRCXI9300Plate(
|
|
||||||
name=name,
|
|
||||||
size_x=127.76,
|
|
||||||
size_y=85.48,
|
|
||||||
size_z=31.4,
|
|
||||||
lid=None,
|
|
||||||
model="PRCXI_nest_12_troughplate",
|
|
||||||
category="plate",
|
|
||||||
material_info={
|
|
||||||
"uuid": "0f1639987b154e1fac78f4fb29a1f7c1",
|
|
||||||
"Code": "12道储液槽",
|
|
||||||
"Name": "12道储液槽",
|
|
||||||
"materialEnum": 0,
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
Well,
|
|
||||||
num_items_x=12,
|
|
||||||
num_items_y=1,
|
|
||||||
dx=14.38 - 8.2 / 2,
|
|
||||||
dy=(85.48 - 71.2) / 2,
|
|
||||||
dz=3.55,
|
|
||||||
item_dx=9.0,
|
|
||||||
item_dy=9.0,
|
|
||||||
**well_kwargs,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def PRCXI_CellTreat_96_wellplate(name: str) -> PRCXI9300Plate:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: ZX-78-096 (细菌培养皿)
|
|
||||||
原型: pylabrobot.resources.celltreat.CellTreat_96_wellplate_350ul_Fb
|
|
||||||
"""
|
|
||||||
well_kwargs = {
|
|
||||||
"size_x": 6.96,
|
|
||||||
"size_y": 6.96,
|
|
||||||
"size_z": 10.04,
|
|
||||||
"bottom_type": WellBottomType.FLAT,
|
|
||||||
"material_z_thickness": 1.75,
|
|
||||||
"cross_section_type": CrossSectionType.CIRCLE,
|
|
||||||
"max_volume": 300,
|
|
||||||
}
|
|
||||||
|
|
||||||
return PRCXI9300Plate(
|
|
||||||
name=name,
|
|
||||||
size_x=127.61,
|
|
||||||
size_y=85.24,
|
|
||||||
size_z=14.30,
|
|
||||||
lid=None,
|
|
||||||
model="PRCXI_CellTreat_96_wellplate",
|
|
||||||
category="plate",
|
|
||||||
material_info={
|
|
||||||
"uuid": "b05b3b2aafd94ec38ea0cd3215ecea8f",
|
|
||||||
"Code": "ZX-78-096",
|
|
||||||
"Name": "细菌培养皿",
|
|
||||||
"materialEnum": 4,
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
Well,
|
|
||||||
num_items_x=12,
|
|
||||||
num_items_y=8,
|
|
||||||
dx=10.83,
|
|
||||||
dy=7.67,
|
|
||||||
dz=4.05,
|
|
||||||
item_dx=9,
|
|
||||||
item_dy=9,
|
|
||||||
**well_kwargs,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
# =========================================================================
|
|
||||||
# 自定义/需测量品 (Custom Measurement)
|
|
||||||
# =========================================================================
|
|
||||||
def PRCXI_10ul_eTips(name: str) -> PRCXI9300TipRack:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: ZX-001-10+
|
|
||||||
"""
|
|
||||||
return PRCXI9300TipRack(
|
|
||||||
name=name,
|
|
||||||
size_x=122.11,
|
|
||||||
size_y=85.48, #修改
|
|
||||||
size_z=58.23,
|
|
||||||
model="PRCXI_10ul_eTips",
|
|
||||||
material_info={
|
|
||||||
"uuid": "068b3815e36b4a72a59bae017011b29f",
|
|
||||||
"Code": "ZX-001-10+",
|
|
||||||
"Name": "10μL加长 Tip头",
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
TipSpot,
|
|
||||||
num_items_x=12,
|
|
||||||
num_items_y=8,
|
|
||||||
dx=7.97, #需要修改
|
|
||||||
dy=5.0, #需修改
|
|
||||||
dz=2.0, #需修改
|
|
||||||
item_dx=9.0,
|
|
||||||
item_dy=9.0,
|
|
||||||
size_x=7.0,
|
|
||||||
size_y=7.0,
|
|
||||||
size_z=0,
|
|
||||||
make_tip=lambda: _make_tip_helper(volume=10, length=52.0, depth=45.1)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
def PRCXI_300ul_Tips(name: str) -> PRCXI9300TipRack:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: ZX-001-300
|
|
||||||
吸头盒通常比较特殊,需要定义 Tip 对象
|
|
||||||
"""
|
|
||||||
return PRCXI9300TipRack(
|
|
||||||
name=name,
|
|
||||||
size_x=122.11,
|
|
||||||
size_y=85.48, #修改
|
|
||||||
size_z=58.23,
|
|
||||||
model="PRCXI_300ul_Tips",
|
|
||||||
material_info={
|
|
||||||
"uuid": "076250742950465b9d6ea29a225dfb00",
|
|
||||||
"Code": "ZX-001-300",
|
|
||||||
"Name": "300μL Tip头",
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
TipSpot,
|
|
||||||
num_items_x=12,
|
|
||||||
num_items_y=8,
|
|
||||||
dx=7.97, #需要修改
|
|
||||||
dy=5.0, #需修改
|
|
||||||
dz=2.0, #需修改
|
|
||||||
item_dx=9.0,
|
|
||||||
item_dy=9.0,
|
|
||||||
size_x=7.0,
|
|
||||||
size_y=7.0,
|
|
||||||
size_z=0,
|
|
||||||
make_tip=lambda: _make_tip_helper(volume=300, length=60.0, depth=51.0)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
def PRCXI_PCR_Plate_200uL_nonskirted(name: str) -> PRCXI9300Plate:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: ZX-023-0.2 (0.2ml PCR 板)
|
|
||||||
"""
|
|
||||||
return PRCXI9300Plate(
|
|
||||||
name=name,
|
|
||||||
size_x=119.5,
|
|
||||||
size_y=80.0,
|
|
||||||
size_z=26.0,
|
|
||||||
model="PRCXI_PCR_Plate_200uL_nonskirted",
|
|
||||||
plate_type="non-skirted",
|
|
||||||
category="plate",
|
|
||||||
material_info={
|
|
||||||
"uuid": "73bb9b10bc394978b70e027bf45ce2d3",
|
|
||||||
"Code": "ZX-023-0.2",
|
|
||||||
"Name": "0.2ml PCR 板",
|
|
||||||
"materialEnum": 0,
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
Well,
|
|
||||||
num_items_x=12,
|
|
||||||
num_items_y=8,
|
|
||||||
dx=7,
|
|
||||||
dy=5,
|
|
||||||
dz=0.0,
|
|
||||||
item_dx=9,
|
|
||||||
item_dy=9,
|
|
||||||
size_x=6,
|
|
||||||
size_y=6,
|
|
||||||
size_z=15.17,
|
|
||||||
bottom_type=WellBottomType.V,
|
|
||||||
cross_section_type=CrossSectionType.CIRCLE,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def PRCXI_PCR_Plate_200uL_semiskirted(name: str) -> PRCXI9300Plate:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: ZX-023-0.2 (0.2ml PCR 板)
|
|
||||||
"""
|
|
||||||
return PRCXI9300Plate(
|
|
||||||
name=name,
|
|
||||||
size_x=126,
|
|
||||||
size_y=86,
|
|
||||||
size_z=21.2,
|
|
||||||
model="PRCXI_PCR_Plate_200uL_semiskirted",
|
|
||||||
plate_type="semi-skirted",
|
|
||||||
category="plate",
|
|
||||||
material_info={
|
|
||||||
"uuid": "73bb9b10bc394978b70e027bf45ce2d3",
|
|
||||||
"Code": "ZX-023-0.2",
|
|
||||||
"Name": "0.2ml PCR 板",
|
|
||||||
"materialEnum": 0,
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
Well,
|
|
||||||
num_items_x=12,
|
|
||||||
num_items_y=8,
|
|
||||||
dx=11,
|
|
||||||
dy=8,
|
|
||||||
dz=0.0,
|
|
||||||
item_dx=9,
|
|
||||||
item_dy=9,
|
|
||||||
size_x=6,
|
|
||||||
size_y=6,
|
|
||||||
size_z=15.17,
|
|
||||||
bottom_type=WellBottomType.V,
|
|
||||||
cross_section_type=CrossSectionType.CIRCLE,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def PRCXI_PCR_Plate_200uL_skirted(name: str) -> PRCXI9300Plate:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: ZX-023-0.2 (0.2ml PCR 板)
|
|
||||||
"""
|
|
||||||
return PRCXI9300Plate(
|
|
||||||
name=name,
|
|
||||||
size_x=127.76,
|
|
||||||
size_y=86,
|
|
||||||
size_z=16.1,
|
|
||||||
model="PRCXI_PCR_Plate_200uL_skirted",
|
|
||||||
plate_type="skirted",
|
|
||||||
category="plate",
|
|
||||||
material_info={
|
|
||||||
"uuid": "73bb9b10bc394978b70e027bf45ce2d3",
|
|
||||||
"Code": "ZX-023-0.2",
|
|
||||||
"Name": "0.2ml PCR 板",
|
|
||||||
"materialEnum": 0,
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
Well,
|
|
||||||
num_items_x=12,
|
|
||||||
num_items_y=8,
|
|
||||||
dx=11,
|
|
||||||
dy=8.49,
|
|
||||||
dz=0.8,
|
|
||||||
item_dx=9,
|
|
||||||
item_dy=9,
|
|
||||||
size_x=6,
|
|
||||||
size_y=6,
|
|
||||||
size_z=15.1,
|
|
||||||
bottom_type=WellBottomType.V,
|
|
||||||
cross_section_type=CrossSectionType.CIRCLE,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
def PRCXI_trash(name: str = "trash") -> PRCXI9300Trash:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: q1 (废弃槽)
|
|
||||||
"""
|
|
||||||
return PRCXI9300Trash(
|
|
||||||
name="trash",
|
|
||||||
size_x=126.59,
|
|
||||||
size_y=84.87,
|
|
||||||
size_z=89.5, # 修改
|
|
||||||
category="trash",
|
|
||||||
model="PRCXI_trash",
|
|
||||||
material_info={
|
|
||||||
"uuid": "730067cf07ae43849ddf4034299030e9",
|
|
||||||
"Code": "q1",
|
|
||||||
"Name": "废弃槽",
|
|
||||||
"materialEnum": 0,
|
|
||||||
"SupplyType": 1
|
|
||||||
}
|
|
||||||
)
|
|
||||||
def PRCXI_96_DeepWell(name: str) -> PRCXI9300Plate:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: q2 (96深孔板)
|
|
||||||
"""
|
|
||||||
return PRCXI9300Plate(
|
|
||||||
name=name,
|
|
||||||
size_x=127.3,
|
|
||||||
size_y=85.35,
|
|
||||||
size_z=45.0, #修改
|
|
||||||
model="PRCXI_96_DeepWell",
|
|
||||||
material_info={
|
|
||||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f", # 对应 q2 uuid
|
|
||||||
"Code": "q2",
|
|
||||||
"Name": "96深孔板",
|
|
||||||
"materialEnum": 0
|
|
||||||
},
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
Well,
|
|
||||||
num_items_x=12,
|
|
||||||
num_items_y=8,
|
|
||||||
dx=10.9,
|
|
||||||
dy=8.25,
|
|
||||||
dz=2.0,
|
|
||||||
item_dx=9.0,
|
|
||||||
item_dy=9.0,
|
|
||||||
size_x=8.2,
|
|
||||||
size_y=8.2,
|
|
||||||
size_z=42.0,
|
|
||||||
max_volume=2200
|
|
||||||
)
|
|
||||||
)
|
|
||||||
def PRCXI_EP_Adapter(name: str) -> PRCXI9300TubeRack:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: 1 (ep适配器)
|
|
||||||
这是一个 4x6 的 EP 管架,适配 1.5mL/2.0mL 离心管
|
|
||||||
"""
|
|
||||||
ep_tube_prototype = Tube(
|
|
||||||
name="EP_Tube_1.5mL",
|
|
||||||
size_x=10.6,
|
|
||||||
size_y=10.6,
|
|
||||||
size_z=40.0, # 管子本身的高度,通常比架子孔略高或持平
|
|
||||||
max_volume=1500,
|
|
||||||
model="EP_Tube_1.5mL"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 计算 PRCXI9300TubeRack 中孔的起始位置 dx, dy
|
|
||||||
dy_calc = 85.8 - 10.5 - (3 * 18) - 10.6
|
|
||||||
dx_calc = 3.54
|
|
||||||
return PRCXI9300TubeRack(
|
|
||||||
name=name,
|
|
||||||
size_x=128.04,
|
|
||||||
size_y=85.8,
|
|
||||||
size_z=42.66,
|
|
||||||
model="PRCXI_EP_Adapter",
|
|
||||||
category="tube_rack",
|
|
||||||
material_info={
|
|
||||||
"uuid": "e146697c395e4eabb3d6b74f0dd6aaf7",
|
|
||||||
"Code": "1",
|
|
||||||
"Name": "ep适配器",
|
|
||||||
"materialEnum": 0,
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
Tube,
|
|
||||||
num_items_x=6,
|
|
||||||
num_items_y=4,
|
|
||||||
dx=dx_calc,
|
|
||||||
dy=dy_calc,
|
|
||||||
dz=42.66 - 38.08, # 架高 - 孔深
|
|
||||||
item_dx=21.0,
|
|
||||||
item_dy=18.0,
|
|
||||||
size_x=10.6,
|
|
||||||
size_y=10.6,
|
|
||||||
size_z=40.0,
|
|
||||||
max_volume=1500
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# =========================================================================
|
|
||||||
# 无实物,需要测量
|
|
||||||
# =========================================================================
|
|
||||||
def PRCXI_Tip1250_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
|
||||||
""" Code: ZX-58-1250 """
|
|
||||||
return PRCXI9300PlateAdapter(
|
|
||||||
name=name,
|
|
||||||
size_x=128,
|
|
||||||
size_y=85,
|
|
||||||
size_z=20,
|
|
||||||
material_info={
|
|
||||||
"uuid": "3b6f33ffbf734014bcc20e3c63e124d4",
|
|
||||||
"Code": "ZX-58-1250",
|
|
||||||
"Name": "Tip头适配器 1250uL",
|
|
||||||
"SupplyType": 2
|
|
||||||
}
|
|
||||||
)
|
|
||||||
def PRCXI_Tip300_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
|
||||||
""" Code: ZX-58-300 """
|
|
||||||
return PRCXI9300PlateAdapter(
|
|
||||||
name=name,
|
|
||||||
size_x=127,
|
|
||||||
size_y=85,
|
|
||||||
size_z=81,
|
|
||||||
material_info={
|
|
||||||
"uuid": "7c822592b360451fb59690e49ac6b181",
|
|
||||||
"Code": "ZX-58-300",
|
|
||||||
"Name": "ZHONGXI 适配器 300uL",
|
|
||||||
"SupplyType": 2
|
|
||||||
}
|
|
||||||
)
|
|
||||||
def PRCXI_Tip10_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
|
||||||
""" Code: ZX-58-10 """
|
|
||||||
return PRCXI9300PlateAdapter(
|
|
||||||
name=name,
|
|
||||||
size_x=128,
|
|
||||||
size_y=85,
|
|
||||||
size_z=72.3,
|
|
||||||
material_info={
|
|
||||||
"uuid": "8cc3dce884ac41c09f4570d0bcbfb01c",
|
|
||||||
"Code": "ZX-58-10",
|
|
||||||
"Name": "吸头10ul 适配器",
|
|
||||||
"SupplyType": 2
|
|
||||||
}
|
|
||||||
)
|
|
||||||
def PRCXI_1250uL_Tips(name: str) -> PRCXI9300TipRack:
|
|
||||||
""" Code: ZX-001-1250 """
|
|
||||||
return PRCXI9300TipRack(
|
|
||||||
name=name,
|
|
||||||
size_x=118.09,
|
|
||||||
size_y=80.7,
|
|
||||||
size_z=107.67,
|
|
||||||
model="PRCXI_1250uL_Tips",
|
|
||||||
material_info={
|
|
||||||
"uuid": "7960f49ddfe9448abadda89bd1556936",
|
|
||||||
"Code": "ZX-001-1250",
|
|
||||||
"Name": "1250μL Tip头",
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
TipSpot,
|
|
||||||
num_items_x=12,
|
|
||||||
num_items_y=8,
|
|
||||||
dx=9.545 - 7.95/2,
|
|
||||||
dy=8.85 - 7.95/2,
|
|
||||||
dz=2.0,
|
|
||||||
item_dx=9,
|
|
||||||
item_dy=9,
|
|
||||||
size_x=7.0,
|
|
||||||
size_y=7.0,
|
|
||||||
size_z=0,
|
|
||||||
make_tip=lambda: _make_tip_helper(volume=1250, length=107.67, depth=8)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
def PRCXI_10uL_Tips(name: str) -> PRCXI9300TipRack:
|
|
||||||
""" Code: ZX-001-10 """
|
|
||||||
return PRCXI9300TipRack(
|
|
||||||
name=name,
|
|
||||||
size_x=120.98,
|
|
||||||
size_y=82.12,
|
|
||||||
size_z=67,
|
|
||||||
model="PRCXI_10uL_Tips",
|
|
||||||
material_info={
|
|
||||||
"uuid": "45f2ed3ad925484d96463d675a0ebf66",
|
|
||||||
"Code": "ZX-001-10",
|
|
||||||
"Name": "10μL Tip头",
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
TipSpot,
|
|
||||||
num_items_x=12,
|
|
||||||
num_items_y=8,
|
|
||||||
dx=10.99 - 5/2,
|
|
||||||
dy=9.56 - 5/2,
|
|
||||||
dz=2.0,
|
|
||||||
item_dx=9,
|
|
||||||
item_dy=9,
|
|
||||||
size_x=7.0,
|
|
||||||
size_y=7.0,
|
|
||||||
size_z=0,
|
|
||||||
make_tip=lambda: _make_tip_helper(volume=1250, length=52.0, depth=5)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
def PRCXI_1000uL_Tips(name: str) -> PRCXI9300TipRack:
|
|
||||||
""" Code: ZX-001-1000 """
|
|
||||||
return PRCXI9300TipRack(
|
|
||||||
name=name,
|
|
||||||
size_x=128.09,
|
|
||||||
size_y=85.8,
|
|
||||||
size_z=98,
|
|
||||||
model="PRCXI_1000uL_Tips",
|
|
||||||
material_info={
|
|
||||||
"uuid": "80652665f6a54402b2408d50b40398df",
|
|
||||||
"Code": "ZX-001-1000",
|
|
||||||
"Name": "1000μL Tip头",
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
TipSpot,
|
|
||||||
num_items_x=12,
|
|
||||||
num_items_y=8,
|
|
||||||
dx=14.5 - 7.95/2,
|
|
||||||
dy=7.425,
|
|
||||||
dz=2.0,
|
|
||||||
item_dx=9,
|
|
||||||
item_dy=9,
|
|
||||||
size_x=7.0,
|
|
||||||
size_y=7.0,
|
|
||||||
size_z=0,
|
|
||||||
make_tip=lambda: _make_tip_helper(volume=1000, length=55.0, depth=8)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
def PRCXI_200uL_Tips(name: str) -> PRCXI9300TipRack:
|
|
||||||
""" Code: ZX-001-200 """
|
|
||||||
return PRCXI9300TipRack(
|
|
||||||
name=name,
|
|
||||||
size_x=120.98,
|
|
||||||
size_y=82.12,
|
|
||||||
size_z=66.9,
|
|
||||||
model="PRCXI_200uL_Tips",
|
|
||||||
material_info={
|
|
||||||
"uuid": "7a73bb9e5c264515a8fcbe88aed0e6f7",
|
|
||||||
"Code": "ZX-001-200",
|
|
||||||
"Name": "200μL Tip头",
|
|
||||||
"SupplyType": 1},
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
TipSpot,
|
|
||||||
num_items_x=12,
|
|
||||||
num_items_y=8,
|
|
||||||
dx=10.99 - 5.5/2,
|
|
||||||
dy=9.56 - 5.5/2,
|
|
||||||
dz=2.0,
|
|
||||||
item_dx=9,
|
|
||||||
item_dy=9,
|
|
||||||
size_x=7.0,
|
|
||||||
size_z=0,
|
|
||||||
size_y=7.0,
|
|
||||||
make_tip=lambda: _make_tip_helper(volume=200, length=52.0, depth=5)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
def PRCXI_PCR_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
|
||||||
"""
|
|
||||||
对应 JSON Code: ZX-58-0001 (全裙边 PCR适配器)
|
|
||||||
"""
|
|
||||||
return PRCXI9300PlateAdapter(
|
|
||||||
name=name,
|
|
||||||
size_x=127.76,
|
|
||||||
size_y=85.48,
|
|
||||||
size_z=21.69,
|
|
||||||
model="PRCXI_PCR_Adapter",
|
|
||||||
material_info={
|
|
||||||
"uuid": "4a043a07c65a4f9bb97745e1f129b165",
|
|
||||||
"Code": "ZX-58-0001",
|
|
||||||
"Name": "全裙边 PCR适配器",
|
|
||||||
"materialEnum": 3,
|
|
||||||
"SupplyType": 2
|
|
||||||
}
|
|
||||||
)
|
|
||||||
def PRCXI_Reservoir_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
|
||||||
""" Code: ZX-ADP-001 """
|
|
||||||
return PRCXI9300PlateAdapter(
|
|
||||||
name=name,
|
|
||||||
size_x=133,
|
|
||||||
size_y=91.8,
|
|
||||||
size_z=70,
|
|
||||||
material_info={
|
|
||||||
"uuid": "6bdfdd7069df453896b0806df50f2f4d",
|
|
||||||
"Code": "ZX-ADP-001",
|
|
||||||
"Name": "储液槽 适配器",
|
|
||||||
"SupplyType": 2
|
|
||||||
}
|
|
||||||
)
|
|
||||||
def PRCXI_Deep300_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
|
||||||
""" Code: ZX-002-300 """
|
|
||||||
return PRCXI9300PlateAdapter(
|
|
||||||
name=name,
|
|
||||||
size_x=136.4,
|
|
||||||
size_y=93.8,
|
|
||||||
size_z=96,
|
|
||||||
material_info={
|
|
||||||
"uuid": "9a439bed8f3344549643d6b3bc5a5eb4",
|
|
||||||
"Code": "ZX-002-300",
|
|
||||||
"Name": "300ul深孔板适配器",
|
|
||||||
"SupplyType": 2
|
|
||||||
}
|
|
||||||
)
|
|
||||||
def PRCXI_Deep10_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
|
||||||
""" Code: ZX-002-10 """
|
|
||||||
return PRCXI9300PlateAdapter(
|
|
||||||
name=name,
|
|
||||||
size_x=136.5,
|
|
||||||
size_y=93.8,
|
|
||||||
size_z=121.5,
|
|
||||||
material_info={
|
|
||||||
"uuid": "4dc8d6ecfd0449549683b8ef815a861b",
|
|
||||||
"Code": "ZX-002-10",
|
|
||||||
"Name": "10ul专用深孔板适配器",
|
|
||||||
"SupplyType": 2
|
|
||||||
}
|
|
||||||
)
|
|
||||||
def PRCXI_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
|
||||||
""" Code: Fhh478 """
|
|
||||||
return PRCXI9300PlateAdapter(
|
|
||||||
name=name,
|
|
||||||
size_x=120,
|
|
||||||
size_y=90,
|
|
||||||
size_z=86,
|
|
||||||
material_info={
|
|
||||||
"uuid": "adfabfffa8f24af5abfbba67b8d0f973",
|
|
||||||
"Code": "Fhh478",
|
|
||||||
"Name": "适配器",
|
|
||||||
"SupplyType": 2
|
|
||||||
}
|
|
||||||
)
|
|
||||||
def PRCXI_48_DeepWell(name: str) -> PRCXI9300Plate:
|
|
||||||
""" Code: 22 (48孔深孔板) """
|
|
||||||
print("Warning: Code '22' (48孔深孔板) dimensions are null in JSON.")
|
|
||||||
return PRCXI9300Plate(
|
|
||||||
name=name,
|
|
||||||
size_x=127,
|
|
||||||
size_y=85,
|
|
||||||
size_z=44,
|
|
||||||
model="PRCXI_48_DeepWell",
|
|
||||||
material_info={
|
|
||||||
"uuid": "026c5d5cf3d94e56b4e16b7fb53a995b",
|
|
||||||
"Code": "22",
|
|
||||||
"Name": "48孔深孔板",
|
|
||||||
"SupplyType": 1
|
|
||||||
},
|
|
||||||
ordered_items=create_ordered_items_2d(
|
|
||||||
Well,
|
|
||||||
num_items_x=6,
|
|
||||||
num_items_y=8,
|
|
||||||
dx=10,
|
|
||||||
dy=10,
|
|
||||||
dz=1,
|
|
||||||
item_dx=18.5,
|
|
||||||
item_dy=9,
|
|
||||||
size_x=8,
|
|
||||||
size_y=8,
|
|
||||||
size_z=40
|
|
||||||
)
|
|
||||||
)
|
|
||||||
def PRCXI_30mm_Adapter(name: str) -> PRCXI9300PlateAdapter:
|
|
||||||
""" Code: ZX-58-30 """
|
|
||||||
return PRCXI9300PlateAdapter(
|
|
||||||
name=name,
|
|
||||||
size_x=132,
|
|
||||||
size_y=93.5,
|
|
||||||
size_z=30,
|
|
||||||
material_info={
|
|
||||||
"uuid": "a0757a90d8e44e81a68f306a608694f2",
|
|
||||||
"Code": "ZX-58-30",
|
|
||||||
"Name": "30mm适配器",
|
|
||||||
"SupplyType": 2
|
|
||||||
}
|
|
||||||
)
|
|
||||||
31
unilabos/devices/liquid_handling/prcxi/prcxi_material.json
Normal file
31
unilabos/devices/liquid_handling/prcxi/prcxi_material.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"Tip头适配器 1250uL": {"uuid": "3b6f33ffbf734014bcc20e3c63e124d4", "materialEnum": "0"},
|
||||||
|
"ZHONGXI 适配器 300uL": {"uuid": "7c822592b360451fb59690e49ac6b181", "materialEnum": "0"},
|
||||||
|
"吸头10ul 适配器": {"uuid": "8cc3dce884ac41c09f4570d0bcbfb01c", "materialEnum": "0"},
|
||||||
|
"1250μL Tip头": {"uuid": "7960f49ddfe9448abadda89bd1556936", "materialEnum": "0"},
|
||||||
|
"10μL Tip头": {"uuid": "45f2ed3ad925484d96463d675a0ebf66", "materialEnum": "0"},
|
||||||
|
"10μL加长 Tip头": {"uuid": "068b3815e36b4a72a59bae017011b29f", "materialEnum": "1"},
|
||||||
|
"1000μL Tip头": {"uuid": "80652665f6a54402b2408d50b40398df", "materialEnum": "1"},
|
||||||
|
"300μL Tip头": {"uuid": "076250742950465b9d6ea29a225dfb00", "materialEnum": "1"},
|
||||||
|
"200μL Tip头": {"uuid": "7a73bb9e5c264515a8fcbe88aed0e6f7", "materialEnum": "0"},
|
||||||
|
"0.2ml PCR板": {"uuid": "73bb9b10bc394978b70e027bf45ce2d3", "materialEnum": "0"},
|
||||||
|
"2.2ml 深孔板": {"uuid": "ca877b8b114a4310b429d1de4aae96ee", "materialEnum": "0"},
|
||||||
|
"储液槽": {"uuid": "04211a2dc93547fe9bf6121eac533650", "materialEnum": "0"},
|
||||||
|
"全裙边 PCR适配器": {"uuid": "4a043a07c65a4f9bb97745e1f129b165", "materialEnum": "3"},
|
||||||
|
"储液槽 适配器": {"uuid": "6bdfdd7069df453896b0806df50f2f4d", "materialEnum": "0"},
|
||||||
|
"300ul深孔板适配器": {"uuid": "9a439bed8f3344549643d6b3bc5a5eb4", "materialEnum": "0"},
|
||||||
|
"10ul专用深孔板适配器": {"uuid": "4dc8d6ecfd0449549683b8ef815a861b", "materialEnum": "0"},
|
||||||
|
"爱津": {"uuid": "b01627718d3341aba649baa81c2c083c", "materialEnum": "0"},
|
||||||
|
"适配器": {"uuid": "adfabfffa8f24af5abfbba67b8d0f973", "materialEnum": "0"},
|
||||||
|
"废弃槽": {"uuid": "730067cf07ae43849ddf4034299030e9", "materialEnum": "0"},
|
||||||
|
"96深孔板": {"uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": "0"},
|
||||||
|
"384板": {"uuid": "853dcfb6226f476e8b23c250217dc7da", "materialEnum": "0"},
|
||||||
|
"4道储液槽": {"uuid": "01953864f6f140ccaa8ddffd4f3e46f5", "materialEnum": "0"},
|
||||||
|
"48孔深孔板": {"uuid": "026c5d5cf3d94e56b4e16b7fb53a995b", "materialEnum": "2"},
|
||||||
|
"12道储液槽": {"uuid": "0f1639987b154e1fac78f4fb29a1f7c1", "materialEnum": "0"},
|
||||||
|
"HPLC料盘": {"uuid": "548bbc3df0d4447586f2c19d2c0c0c55", "materialEnum": "0"},
|
||||||
|
"ep适配器": {"uuid": "e146697c395e4eabb3d6b74f0dd6aaf7", "materialEnum": "0"},
|
||||||
|
"30mm适配器": {"uuid": "a0757a90d8e44e81a68f306a608694f2", "materialEnum": "0"},
|
||||||
|
"细菌培养皿": {"uuid": "b05b3b2aafd94ec38ea0cd3215ecea8f", "materialEnum": "4"},
|
||||||
|
"96 细胞培养皿":{ "uuid": "57b1e4711e9e4a32b529f3132fc5931f", "materialEnum": "0"}
|
||||||
|
}
|
||||||
21
unilabos/devices/liquid_handling/prcxi/prcxi_materials.py
Normal file
21
unilabos/devices/liquid_handling/prcxi/prcxi_materials.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import collections
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Container
|
||||||
|
|
||||||
|
|
||||||
|
prcxi_materials_path = str(Path(__file__).parent / "prcxi_material.json")
|
||||||
|
with open(prcxi_materials_path, mode="r", encoding="utf-8") as f:
|
||||||
|
prcxi_materials = json.loads(f.read())
|
||||||
|
|
||||||
|
|
||||||
|
def tip_adaptor_1250ul(name="Tip头适配器 1250uL") -> PRCXI9300Container: # 必须传入一个name参数,是plr的规范要求
|
||||||
|
# tip_rack = PRCXI9300Container(name, prcxi_materials["name"]["Height"])
|
||||||
|
tip_rack = PRCXI9300Container(name, 1000,400,800, "tip_rack", collections.OrderedDict())
|
||||||
|
tip_rack.load_state({
|
||||||
|
"Materials": {"uuid": "7960f49ddfe9448abadda89bd1556936", "materialEnum": "0"}
|
||||||
|
})
|
||||||
|
return tip_rack
|
||||||
|
|
||||||
|
|
||||||
44
unilabos/devices/liquid_handling/prcxi/prcxi_res.py
Normal file
44
unilabos/devices/liquid_handling/prcxi/prcxi_res.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import collections
|
||||||
|
|
||||||
|
from pylabrobot.resources import opentrons_96_tiprack_10ul
|
||||||
|
from pylabrobot.resources.opentrons.plates import corning_96_wellplate_360ul_flat, nest_96_wellplate_2ml_deep
|
||||||
|
|
||||||
|
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Container, PRCXI9300Trash
|
||||||
|
|
||||||
|
|
||||||
|
def get_well_container(name: str) -> PRCXI9300Container:
|
||||||
|
well_containers = corning_96_wellplate_360ul_flat(name).serialize()
|
||||||
|
plate = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="plate",
|
||||||
|
ordering=collections.OrderedDict())
|
||||||
|
plate_serialized = plate.serialize()
|
||||||
|
well_containers.update({k: v for k, v in plate_serialized.items() if k not in ["children"]})
|
||||||
|
new_plate: PRCXI9300Container = PRCXI9300Container.deserialize(well_containers)
|
||||||
|
return new_plate
|
||||||
|
|
||||||
|
def get_tip_rack(name: str) -> PRCXI9300Container:
|
||||||
|
tip_racks = opentrons_96_tiprack_10ul("name").serialize()
|
||||||
|
tip_rack = PRCXI9300Container(name=name, size_x=50, size_y=50, size_z=10, category="tip_rack",
|
||||||
|
ordering=collections.OrderedDict())
|
||||||
|
tip_rack_serialized = tip_rack.serialize()
|
||||||
|
tip_racks.update({k: v for k, v in tip_rack_serialized.items() if k not in ["children"]})
|
||||||
|
new_tip_rack: PRCXI9300Container = PRCXI9300Container.deserialize(tip_racks)
|
||||||
|
return new_tip_rack
|
||||||
|
|
||||||
|
def prcxi_96_wellplate_360ul_flat(name: str):
|
||||||
|
return get_well_container(name)
|
||||||
|
|
||||||
|
def prcxi_opentrons_96_tiprack_10ul(name: str):
|
||||||
|
return get_tip_rack(name)
|
||||||
|
|
||||||
|
def prcxi_trash(name: str = None):
|
||||||
|
return PRCXI9300Trash(name="trash", size_x=50, size_y=50, size_z=10, category="trash")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Example usage
|
||||||
|
test_plate = prcxi_96_wellplate_360ul_flat("test_plate")
|
||||||
|
test_rack = prcxi_opentrons_96_tiprack_10ul("test_rack")
|
||||||
|
tash = prcxi_trash("trash")
|
||||||
|
print(test_plate)
|
||||||
|
print(test_rack)
|
||||||
|
print(tash)
|
||||||
|
# Output will be a dictionary representation of the PRCXI9300Container with well details
|
||||||
@@ -1,560 +0,0 @@
|
|||||||
# 新威电池测试系统 - OSS 上传功能说明
|
|
||||||
|
|
||||||
## 功能概述
|
|
||||||
|
|
||||||
本次更新为新威电池测试系统添加了**阿里云 OSS 文件上传功能**,采用统一的 API 方式,允许将测试数据备份文件上传到云端存储。
|
|
||||||
|
|
||||||
## 版本更新说明
|
|
||||||
|
|
||||||
### ⚠️ 重大变更(2025-12-17)
|
|
||||||
|
|
||||||
本次更新将 OSS 上传方式从 **`oss2` 库** 改为 **统一 API 方式**,实现与团队其他系统的统一。
|
|
||||||
|
|
||||||
**主要变化**:
|
|
||||||
- ✅ 用 `requests` 库
|
|
||||||
- ✅ 通过统一 API 获取预签名 URL 进行上传
|
|
||||||
- ✅ 简化环境变量配置(仅需要 JWT Token)
|
|
||||||
- ✅ 返回文件访问 URL
|
|
||||||
|
|
||||||
## 主要改动
|
|
||||||
|
|
||||||
### 1. OSS 上传工具函数重构(第30-200行)
|
|
||||||
|
|
||||||
#### 新增函数
|
|
||||||
|
|
||||||
- **`get_upload_token(base_url, auth_token, scene, filename)`**
|
|
||||||
从统一 API 获取文件上传的预签名 URL
|
|
||||||
|
|
||||||
- **`upload_file_with_presigned_url(upload_info, file_path)`**
|
|
||||||
使用预签名 URL 上传文件到 OSS
|
|
||||||
|
|
||||||
#### 更新的函数
|
|
||||||
|
|
||||||
- **`upload_file_to_oss(local_file_path, oss_object_name)`**
|
|
||||||
上传单个文件到阿里云 OSS(使用统一 API 方式)
|
|
||||||
- 返回值变更:成功时返回文件访问 URL,失败时返回 `False`
|
|
||||||
|
|
||||||
- **`upload_files_to_oss(file_paths, oss_prefix)`**
|
|
||||||
批量上传文件列表
|
|
||||||
- `oss_prefix` 参数保留但暂不使用(接口兼容性)
|
|
||||||
|
|
||||||
- **`upload_directory_to_oss(local_dir, oss_prefix)`**
|
|
||||||
上传整个目录
|
|
||||||
- 简化实现,直接使用文件名上传
|
|
||||||
|
|
||||||
### 2. 环境变量配置简化
|
|
||||||
|
|
||||||
#### 新方式(推荐)
|
|
||||||
```bash
|
|
||||||
# ✅ 必需
|
|
||||||
UNI_LAB_AUTH_TOKEN # API Key 格式: "Api xxxxxx"
|
|
||||||
|
|
||||||
# ✅ 可选(有默认值)
|
|
||||||
UNI_LAB_BASE_URL (默认: https://uni-lab.test.bohrium.com)
|
|
||||||
UNI_LAB_UPLOAD_SCENE (默认: job,其他值会被改成 default)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 初始化方法(保持不变)
|
|
||||||
|
|
||||||
`__init__` 方法中的 OSS 相关配置参数:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# OSS 上传配置
|
|
||||||
self.oss_upload_enabled = False # 默认不启用 OSS 上传
|
|
||||||
self.oss_prefix = "neware_backup" # OSS 对象路径前缀
|
|
||||||
self._last_backup_dir = None # 记录最近一次的 backup_dir
|
|
||||||
```
|
|
||||||
|
|
||||||
**默认行为**:OSS 上传功能默认关闭(`oss_upload_enabled=False`),不影响现有系统。
|
|
||||||
|
|
||||||
### 4. upload_backup_to_oss 方法(保持不变)
|
|
||||||
|
|
||||||
```python
|
|
||||||
def upload_backup_to_oss(
|
|
||||||
self,
|
|
||||||
backup_dir: str = None,
|
|
||||||
file_pattern: str = "*",
|
|
||||||
oss_prefix: str = None
|
|
||||||
) -> dict
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用说明
|
|
||||||
|
|
||||||
### 前置条件
|
|
||||||
|
|
||||||
#### 1. 安装依赖
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# requests 库(通常已安装)
|
|
||||||
pip install requests
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. 配置环境变量
|
|
||||||
|
|
||||||
根据您使用的终端类型配置环境变量:
|
|
||||||
|
|
||||||
##### PowerShell(推荐)
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# 必需:设置认证 Token(API Key 格式)
|
|
||||||
$env:UNI_LAB_AUTH_TOKEN = "Api xxxx"
|
|
||||||
|
|
||||||
# 可选:自定义服务器地址(默认为 test 环境)
|
|
||||||
$env:UNI_LAB_BASE_URL = "https://uni-lab.test.bohrium.com"
|
|
||||||
|
|
||||||
# 可选:自定义上传场景(默认为 job)
|
|
||||||
$env:UNI_LAB_UPLOAD_SCENE = "job"
|
|
||||||
|
|
||||||
# 验证是否设置成功
|
|
||||||
echo $env:UNI_LAB_AUTH_TOKEN
|
|
||||||
```
|
|
||||||
|
|
||||||
##### CMD / 命令提示符
|
|
||||||
|
|
||||||
```cmd
|
|
||||||
REM 必需:设置认证 Token(API Key 格式)
|
|
||||||
set UNI_LAB_AUTH_TOKEN=Api xxxx
|
|
||||||
|
|
||||||
REM 可选:自定义配置
|
|
||||||
set UNI_LAB_BASE_URL=https://uni-lab.test.bohrium.com
|
|
||||||
set UNI_LAB_UPLOAD_SCENE=job
|
|
||||||
|
|
||||||
REM 验证是否设置成功
|
|
||||||
echo %UNI_LAB_AUTH_TOKEN%
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Linux/Mac
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 必需:设置认证 Token(API Key 格式)
|
|
||||||
export UNI_LAB_AUTH_TOKEN="Api xxxx"
|
|
||||||
|
|
||||||
# 可选:自定义配置
|
|
||||||
export UNI_LAB_BASE_URL="https://uni-lab.test.bohrium.com"
|
|
||||||
export UNI_LAB_UPLOAD_SCENE="job"
|
|
||||||
|
|
||||||
# 验证是否设置成功
|
|
||||||
echo $UNI_LAB_AUTH_TOKEN
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. 获取认证 Token
|
|
||||||
|
|
||||||
> **重要**:从 Uni-Lab 主页 → 账号安全 中获取 API Key。
|
|
||||||
|
|
||||||
**获取步骤**:
|
|
||||||
1. 登录 Uni-Lab 系统
|
|
||||||
2. 进入主页 → 账号安全
|
|
||||||
3. 复制 API Key
|
|
||||||
|
|
||||||
Token 格式示例:
|
|
||||||
```
|
|
||||||
Api 48ccxx336fba44f39e1e37db93xxxxx
|
|
||||||
```
|
|
||||||
|
|
||||||
> **提示**:
|
|
||||||
> - 如果 Token 已经包含 `Api ` 前缀,直接使用
|
|
||||||
> - 如果没有前缀,代码会自动添加 `Api ` 前缀
|
|
||||||
> - 旧版 `Bearer` JWT Token 格式仍然兼容
|
|
||||||
|
|
||||||
#### 4. 持久化配置(可选)
|
|
||||||
|
|
||||||
**临时配置**:上述命令设置的环境变量只在当前终端会话中有效。
|
|
||||||
|
|
||||||
**持久化方式 1:PowerShell 配置文件**
|
|
||||||
```powershell
|
|
||||||
# 编辑 PowerShell 配置文件
|
|
||||||
notepad $PROFILE
|
|
||||||
|
|
||||||
# 在打开的文件中添加:
|
|
||||||
$env:UNI_LAB_AUTH_TOKEN = "Api 你的API_Key"
|
|
||||||
```
|
|
||||||
|
|
||||||
**持久化方式 2:Windows 系统环境变量**
|
|
||||||
- 右键"此电脑" → "属性" → "高级系统设置" → "环境变量"
|
|
||||||
- 添加用户变量或系统变量:
|
|
||||||
- 变量名:`UNI_LAB_AUTH_TOKEN`
|
|
||||||
- 变量值:`Api 你的API_Key`
|
|
||||||
|
|
||||||
### 使用流程
|
|
||||||
|
|
||||||
#### 步骤 1:启用 OSS 上传功能
|
|
||||||
|
|
||||||
**推荐方式:在 `device.json` 中配置**
|
|
||||||
|
|
||||||
编辑设备配置文件 `unilabos/devices/neware_battery_test_system/device.json`,在 `config` 中添加:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "NEWARE_BATTERY_TEST_SYSTEM",
|
|
||||||
"config": {
|
|
||||||
"ip": "127.0.0.1",
|
|
||||||
"port": 502,
|
|
||||||
"machine_id": 1,
|
|
||||||
"oss_upload_enabled": true,
|
|
||||||
"oss_prefix": "neware_backup/2025-12"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**参数说明**:
|
|
||||||
- `oss_upload_enabled`: 设置为 `true` 启用 OSS 上传
|
|
||||||
- `oss_prefix`: OSS 文件路径前缀,建议按日期或项目组织(暂时未使用,保留接口兼容性)
|
|
||||||
|
|
||||||
**其他方式:通过初始化参数**
|
|
||||||
|
|
||||||
```python
|
|
||||||
device = NewareBatteryTestSystem(
|
|
||||||
ip="127.0.0.1",
|
|
||||||
port=502,
|
|
||||||
oss_upload_enabled=True, # 启用 OSS 上传
|
|
||||||
oss_prefix="neware_backup/2025-12" # 可选:自定义路径前缀
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**配置完成后,重启 ROS 节点使配置生效。**
|
|
||||||
|
|
||||||
#### 步骤 2:提交测试任务
|
|
||||||
|
|
||||||
使用 `submit_from_csv` 提交测试任务:
|
|
||||||
|
|
||||||
```python
|
|
||||||
result = device.submit_from_csv(
|
|
||||||
csv_path="test_data.csv",
|
|
||||||
output_dir="D:/neware_output"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
此时会创建以下目录结构:
|
|
||||||
```
|
|
||||||
D:/neware_output/
|
|
||||||
├── xml_dir/ # XML 配置文件
|
|
||||||
└── backup_dir/ # 测试数据备份(由新威设备生成)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 步骤 3:等待测试完成
|
|
||||||
|
|
||||||
等待新威设备完成测试,备份文件会生成到 `backup_dir` 中。
|
|
||||||
|
|
||||||
#### 步骤 4:上传备份文件到 OSS
|
|
||||||
|
|
||||||
**方法 A:使用默认设置(推荐)**
|
|
||||||
```python
|
|
||||||
# 自动使用最近一次的 backup_dir,上传所有文件
|
|
||||||
result = device.upload_backup_to_oss()
|
|
||||||
```
|
|
||||||
|
|
||||||
**方法 B:指定备份目录**
|
|
||||||
```python
|
|
||||||
# 手动指定备份目录
|
|
||||||
result = device.upload_backup_to_oss(
|
|
||||||
backup_dir="D:/neware_output/backup_dir"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**方法 C:筛选特定文件**
|
|
||||||
```python
|
|
||||||
# 仅上传 CSV 文件
|
|
||||||
result = device.upload_backup_to_oss(
|
|
||||||
backup_dir="D:/neware_output/backup_dir",
|
|
||||||
file_pattern="*.csv"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 仅上传特定电池编号的文件
|
|
||||||
result = device.upload_backup_to_oss(
|
|
||||||
file_pattern="Battery_A001_*.nda"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 返回结果示例
|
|
||||||
|
|
||||||
**成功上传所有文件**:
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"return_info": "全部上传成功: 15/15 个文件",
|
|
||||||
"success": True,
|
|
||||||
"uploaded_count": 15,
|
|
||||||
"total_count": 15,
|
|
||||||
"failed_files": [],
|
|
||||||
"uploaded_files": [
|
|
||||||
{
|
|
||||||
"filename": "Battery_A001.ndax",
|
|
||||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A001.ndax"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename": "Battery_A002.ndax",
|
|
||||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A002.ndax"
|
|
||||||
}
|
|
||||||
# ... 其他 13 个文件
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**部分上传成功**:
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"return_info": "部分上传成功: 12/15 个文件,失败 3 个",
|
|
||||||
"success": True,
|
|
||||||
"uploaded_count": 12,
|
|
||||||
"total_count": 15,
|
|
||||||
"failed_files": ["Battery_A003.csv", "Battery_A007.csv", "test.log"],
|
|
||||||
"uploaded_files": [
|
|
||||||
{
|
|
||||||
"filename": "Battery_A001.ndax",
|
|
||||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A001.ndax"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename": "Battery_A002.ndax",
|
|
||||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A002.ndax"
|
|
||||||
}
|
|
||||||
# ... 其他 10 个成功上传的文件
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **说明**:`uploaded_files` 字段包含所有成功上传文件的详细信息:
|
|
||||||
> - `filename`: 文件名(不含路径)
|
|
||||||
> - `url`: 文件在 OSS 上的完整访问 URL
|
|
||||||
|
|
||||||
## 错误处理
|
|
||||||
|
|
||||||
### OSS 上传未启用
|
|
||||||
|
|
||||||
如果 `oss_upload_enabled=False`,调用 `upload_backup_to_oss` 会返回:
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"return_info": "OSS 上传未启用 (oss_upload_enabled=False),跳过上传。备份目录: ...",
|
|
||||||
"success": False,
|
|
||||||
"uploaded_count": 0,
|
|
||||||
"total_count": 0,
|
|
||||||
"failed_files": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**解决方法**:设置 `device.oss_upload_enabled = True`
|
|
||||||
|
|
||||||
### 环境变量未配置
|
|
||||||
|
|
||||||
如果缺少 `UNI_LAB_AUTH_TOKEN`,会返回:
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"return_info": "OSS 环境变量配置错误: 请设置环境变量: UNI_LAB_AUTH_TOKEN",
|
|
||||||
"success": False,
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**解决方法**:按照前置条件配置环境变量
|
|
||||||
|
|
||||||
### 备份目录不存在
|
|
||||||
|
|
||||||
如果指定的备份目录不存在,会返回:
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"return_info": "备份目录不存在: D:/neware_output/backup_dir",
|
|
||||||
"success": False,
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**解决方法**:检查目录路径是否正确,或等待测试生成备份文件
|
|
||||||
|
|
||||||
### API 认证失败
|
|
||||||
|
|
||||||
如果 Token 无效或过期,会返回:
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"return_info": "获取凭证失败: 认证失败",
|
|
||||||
"success": False,
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**解决方法**:检查 Token 是否正确,或联系开发团队获取新 Token
|
|
||||||
|
|
||||||
## 技术细节
|
|
||||||
|
|
||||||
### OSS 上传流程(新方式)
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
A[开始上传] --> B[验证配置和环境变量]
|
|
||||||
B --> C[扫描备份目录]
|
|
||||||
C --> D[筛选符合 pattern 的文件]
|
|
||||||
D --> E[遍历每个文件]
|
|
||||||
E --> F[调用 API 获取预签名 URL]
|
|
||||||
F --> G{获取成功?}
|
|
||||||
G -->|是| H[使用预签名 URL 上传文件]
|
|
||||||
G -->|否| I[记录失败]
|
|
||||||
H --> J{上传成功?}
|
|
||||||
J -->|是| K[记录成功 + 文件 URL]
|
|
||||||
J -->|否| I
|
|
||||||
I --> L{还有文件?}
|
|
||||||
K --> L
|
|
||||||
L -->|是| E
|
|
||||||
L -->|否| M[返回统计结果]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 上传 API 流程
|
|
||||||
|
|
||||||
1. **获取预签名 URL**
|
|
||||||
- 请求:`GET /api/v1/applications/token?scene={scene}&filename={filename}`
|
|
||||||
- 认证:`Authorization: Bearer {token}`
|
|
||||||
- 响应:`{code: 0, data: {url: "预签名URL", path: "文件路径"}}`
|
|
||||||
|
|
||||||
2. **上传文件**
|
|
||||||
- 请求:`PUT {预签名URL}`
|
|
||||||
- 内容:文件二进制数据
|
|
||||||
- 响应:HTTP 200 表示成功
|
|
||||||
|
|
||||||
3. **生成访问 URL**
|
|
||||||
- 格式:`https://{OSS_PUBLIC_HOST}/{path}`
|
|
||||||
- 示例:`https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/20251217/battery_data.csv`
|
|
||||||
|
|
||||||
### 日志记录
|
|
||||||
|
|
||||||
所有上传操作都会通过 ROS 日志系统记录:
|
|
||||||
- `INFO` 级别:上传进度和成功信息
|
|
||||||
- `WARNING` 级别:空目录、未启用等警告
|
|
||||||
- `ERROR` 级别:上传失败、配置错误
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **上传时机**:`backup_dir` 中的文件是在新威设备执行测试过程中实时生成的,请确保测试已完成再上传。
|
|
||||||
|
|
||||||
2. **文件命名**:上传到 OSS 的文件会保留原始文件名,路径由统一 API 分配。
|
|
||||||
|
|
||||||
3. **网络要求**:上传需要稳定的网络连接到阿里云 OSS 服务。
|
|
||||||
|
|
||||||
4. **Token 有效期**:JWT Token 有过期时间,过期后需要重新获取。
|
|
||||||
|
|
||||||
5. **成本考虑**:OSS 存储和流量会产生费用,请根据需要合理设置文件筛选规则。
|
|
||||||
|
|
||||||
6. **并发上传**:当前实现为串行上传,大量文件上传可能需要较长时间。
|
|
||||||
|
|
||||||
7. **文件大小限制**:请注意单个文件大小是否有上传限制(由统一 API 控制)。
|
|
||||||
|
|
||||||
## 兼容性
|
|
||||||
|
|
||||||
- ✅ **向后兼容**:默认 `oss_upload_enabled=False`,不影响现有系统
|
|
||||||
- ✅ **可选功能**:仅在需要时启用
|
|
||||||
- ✅ **独立操作**:上传失败不会影响测试任务的提交和执行
|
|
||||||
- ⚠️ **环境变量变更**:需要更新环境变量配置(从 OSS AK/SK 改为 JWT Token)
|
|
||||||
|
|
||||||
## 迁移指南
|
|
||||||
|
|
||||||
如果您之前使用 `oss2` 库方式,请按以下步骤迁移:
|
|
||||||
|
|
||||||
### 1. 卸载旧依赖(可选)
|
|
||||||
```bash
|
|
||||||
pip uninstall oss2
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 删除旧环境变量
|
|
||||||
```powershell
|
|
||||||
# PowerShell
|
|
||||||
Remove-Item Env:\OSS_ACCESS_KEY_ID
|
|
||||||
Remove-Item Env:\OSS_ACCESS_KEY_SECRET
|
|
||||||
Remove-Item Env:\OSS_BUCKET_NAME
|
|
||||||
Remove-Item Env:\OSS_ENDPOINT
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 设置新环境变量
|
|
||||||
```powershell
|
|
||||||
# PowerShell
|
|
||||||
$env:UNI_LAB_AUTH_TOKEN = "Bearer 你的token..."
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 测试上传功能
|
|
||||||
```python
|
|
||||||
# 验证上传是否正常工作
|
|
||||||
result = device.upload_backup_to_oss(backup_dir="测试目录")
|
|
||||||
print(result)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
**Q: 为什么要从 `oss2` 改为统一 API?**
|
|
||||||
A: 为了与团队其他系统保持一致,简化配置,并统一认证方式。
|
|
||||||
|
|
||||||
**Q: Token 在哪里获取?**
|
|
||||||
A: 请联系开发团队获取有效的 JWT Token。
|
|
||||||
|
|
||||||
**Q: Token 过期了怎么办?**
|
|
||||||
A: 重新获取新的 Token 并更新环境变量 `UNI_LAB_AUTH_TOKEN`。
|
|
||||||
|
|
||||||
**Q: 可以自定义上传路径吗?**
|
|
||||||
A: 当前版本路径由统一 API 自动分配,`oss_prefix` 参数暂不使用(保留接口兼容性)。
|
|
||||||
|
|
||||||
**Q: 为什么不在 `submit_from_csv` 中自动上传?**
|
|
||||||
A: 因为备份文件在测试进行中逐步生成,方法返回时可能文件尚未完全生成,因此提供独立的上传方法更灵活。
|
|
||||||
|
|
||||||
**Q: 上传后如何访问文件?**
|
|
||||||
A: 上传成功后会返回文件访问 URL,格式为 `https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/{path}`
|
|
||||||
|
|
||||||
**Q: 如何删除已上传的文件?**
|
|
||||||
A: 需要通过 OSS 控制台或 API 操作,本功能仅负责上传。
|
|
||||||
|
|
||||||
## 验证上传结果
|
|
||||||
|
|
||||||
### 方法1:通过阿里云控制台查看
|
|
||||||
|
|
||||||
1. 登录 [阿里云 OSS 控制台](https://oss.console.aliyun.com/)
|
|
||||||
2. 点击左侧 **Bucket列表**
|
|
||||||
3. 选择 `uni-lab-test` Bucket
|
|
||||||
4. 点击 **文件管理**
|
|
||||||
5. 查看上传的文件列表
|
|
||||||
|
|
||||||
### 方法2:使用返回的文件 URL
|
|
||||||
|
|
||||||
上传成功后,`upload_file_to_oss()` 会返回文件访问 URL:
|
|
||||||
```python
|
|
||||||
url = upload_file_to_oss("local_file.csv")
|
|
||||||
print(f"文件访问 URL: {url}")
|
|
||||||
# 输出示例:https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/20251217/local_file.csv
|
|
||||||
```
|
|
||||||
|
|
||||||
> **注意**:OSS 文件默认为私有访问,直接访问 URL 可能需要签名认证。
|
|
||||||
|
|
||||||
### 方法3:使用 ossutil 命令行工具
|
|
||||||
|
|
||||||
安装 [ossutil](https://help.aliyun.com/document_detail/120075.html) 后:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 列出文件
|
|
||||||
ossutil ls oss://uni-lab-test/job/
|
|
||||||
|
|
||||||
# 下载文件到本地
|
|
||||||
ossutil cp oss://uni-lab-test/job/20251217/文件名 ./本地路径
|
|
||||||
|
|
||||||
# 生成签名URL(有效期1小时)
|
|
||||||
ossutil sign oss://uni-lab-test/job/20251217/文件名 --timeout 3600
|
|
||||||
```
|
|
||||||
|
|
||||||
## 更新日志
|
|
||||||
|
|
||||||
- **2025-12-17**: v2.0(重大更新)
|
|
||||||
- ⚠️ 从 `oss2` 库改为统一 API 方式
|
|
||||||
- 简化环境变量配置(仅需 JWT Token)
|
|
||||||
- 新增 `get_upload_token()` 和 `upload_file_with_presigned_url()` 函数
|
|
||||||
- `upload_file_to_oss()` 返回值改为文件访问 URL
|
|
||||||
- 更新文档和迁移指南
|
|
||||||
|
|
||||||
- **2025-12-15**: v1.1
|
|
||||||
- 添加初始化参数 `oss_upload_enabled` 和 `oss_prefix`
|
|
||||||
- 支持在 `device.json` 中配置 OSS 上传
|
|
||||||
- 更新使用说明,添加验证方法
|
|
||||||
|
|
||||||
- **2025-12-13**: v1.0 初始版本
|
|
||||||
- 添加 OSS 上传工具函数(基于 `oss2` 库)
|
|
||||||
- 创建 `upload_backup_to_oss` 动作方法
|
|
||||||
- 支持文件筛选和自定义 OSS 路径
|
|
||||||
|
|
||||||
## 参考资料
|
|
||||||
|
|
||||||
- [Uni-Lab 统一文件上传 API 文档](https://uni-lab.test.bohrium.com/api/docs)(如有)
|
|
||||||
- [阿里云 OSS 控制台](https://oss.console.aliyun.com/)
|
|
||||||
- [ossutil 工具文档](https://help.aliyun.com/document_detail/120075.html)
|
|
||||||
@@ -1,574 +0,0 @@
|
|||||||
# Neware Battery Test System - OSS Upload Feature
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This update adds **Aliyun OSS file upload functionality** to the Neware Battery Test System using a unified API approach, allowing test data backup files to be uploaded to cloud storage.
|
|
||||||
|
|
||||||
## Version Updates
|
|
||||||
|
|
||||||
### ⚠️ Breaking Changes (2025-12-17)
|
|
||||||
|
|
||||||
This update changes the OSS upload method from **`oss2` library** to **unified API approach** to align with other team systems.
|
|
||||||
|
|
||||||
**Main Changes**:
|
|
||||||
- ✅ Use `requests` library
|
|
||||||
- ✅ Upload via presigned URLs obtained through unified API
|
|
||||||
- ✅ Simplified environment variable configuration (only API Key required)
|
|
||||||
- ✅ Returns file access URLs
|
|
||||||
|
|
||||||
## Main Changes
|
|
||||||
|
|
||||||
### 1. OSS Upload Functions Refactored (Lines 30-200)
|
|
||||||
|
|
||||||
#### New Functions
|
|
||||||
|
|
||||||
- **`get_upload_token(base_url, auth_token, scene, filename)`**
|
|
||||||
Obtain presigned URL for file upload from unified API
|
|
||||||
|
|
||||||
- **`upload_file_with_presigned_url(upload_info, file_path)`**
|
|
||||||
Upload file to OSS using presigned URL
|
|
||||||
|
|
||||||
#### Updated Functions
|
|
||||||
|
|
||||||
- **`upload_file_to_oss(local_file_path, oss_object_name)`**
|
|
||||||
Upload single file to Aliyun OSS (using unified API approach)
|
|
||||||
- Return value changed: returns file access URL on success, `False` on failure
|
|
||||||
|
|
||||||
- **`upload_files_to_oss(file_paths, oss_prefix)`**
|
|
||||||
Batch upload file list
|
|
||||||
- `oss_prefix` parameter retained but not used (interface compatibility)
|
|
||||||
|
|
||||||
- **`upload_directory_to_oss(local_dir, oss_prefix)`**
|
|
||||||
Upload entire directory
|
|
||||||
- Simplified implementation, uploads using filenames directly
|
|
||||||
|
|
||||||
### 2. Simplified Environment Variable Configuration
|
|
||||||
|
|
||||||
#### Old Method (Deprecated)
|
|
||||||
```bash
|
|
||||||
# ❌ No longer used
|
|
||||||
OSS_ACCESS_KEY_ID
|
|
||||||
OSS_ACCESS_KEY_SECRET
|
|
||||||
OSS_BUCKET_NAME
|
|
||||||
OSS_ENDPOINT
|
|
||||||
```
|
|
||||||
|
|
||||||
#### New Method (Recommended)
|
|
||||||
```bash
|
|
||||||
# ✅ Required
|
|
||||||
UNI_LAB_AUTH_TOKEN # API Key format: "Api xxxxxx"
|
|
||||||
|
|
||||||
# ✅ Optional (with defaults)
|
|
||||||
UNI_LAB_BASE_URL (default: https://uni-lab.test.bohrium.com)
|
|
||||||
UNI_LAB_UPLOAD_SCENE (default: job, other values will be changed to default)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Initialization Method (Unchanged)
|
|
||||||
|
|
||||||
OSS-related configuration parameters in `__init__` method:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# OSS upload configuration
|
|
||||||
self.oss_upload_enabled = False # OSS upload disabled by default
|
|
||||||
self.oss_prefix = "neware_backup" # OSS object path prefix
|
|
||||||
self._last_backup_dir = None # Record last backup_dir
|
|
||||||
```
|
|
||||||
|
|
||||||
**Default Behavior**: OSS upload is disabled by default (`oss_upload_enabled=False`), does not affect existing systems.
|
|
||||||
|
|
||||||
### 4. upload_backup_to_oss Method (Unchanged)
|
|
||||||
|
|
||||||
```python
|
|
||||||
def upload_backup_to_oss(
|
|
||||||
self,
|
|
||||||
backup_dir: str = None,
|
|
||||||
file_pattern: str = "*",
|
|
||||||
oss_prefix: str = None
|
|
||||||
) -> dict
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage Guide
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
#### 1. Install Dependencies
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# requests library (usually pre-installed)
|
|
||||||
pip install requests
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note**: No longer need to install `oss2` library
|
|
||||||
|
|
||||||
#### 2. Configure Environment Variables
|
|
||||||
|
|
||||||
Configure environment variables based on your terminal type:
|
|
||||||
|
|
||||||
##### PowerShell (Recommended)
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# Required: Set authentication Token (API Key format)
|
|
||||||
$env:UNI_LAB_AUTH_TOKEN = "Api xxxx"
|
|
||||||
|
|
||||||
# Optional: Custom server URL (defaults to test environment)
|
|
||||||
$env:UNI_LAB_BASE_URL = "https://uni-lab.test.bohrium.com"
|
|
||||||
|
|
||||||
# Optional: Custom upload scene (defaults to job)
|
|
||||||
$env:UNI_LAB_UPLOAD_SCENE = "job"
|
|
||||||
|
|
||||||
# Verify if set successfully
|
|
||||||
echo $env:UNI_LAB_AUTH_TOKEN
|
|
||||||
```
|
|
||||||
|
|
||||||
##### CMD / Command Prompt
|
|
||||||
|
|
||||||
```cmd
|
|
||||||
REM Required: Set authentication Token (API Key format)
|
|
||||||
set UNI_LAB_AUTH_TOKEN=Api xxxx
|
|
||||||
|
|
||||||
REM Optional: Custom configuration
|
|
||||||
set UNI_LAB_BASE_URL=https://uni-lab.test.bohrium.com
|
|
||||||
set UNI_LAB_UPLOAD_SCENE=job
|
|
||||||
|
|
||||||
REM Verify if set successfully
|
|
||||||
echo %UNI_LAB_AUTH_TOKEN%
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Linux/Mac
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Required: Set authentication Token (API Key format)
|
|
||||||
export UNI_LAB_AUTH_TOKEN="Api xxxx"
|
|
||||||
|
|
||||||
# Optional: Custom configuration
|
|
||||||
export UNI_LAB_BASE_URL="https://uni-lab.test.bohrium.com"
|
|
||||||
export UNI_LAB_UPLOAD_SCENE="job"
|
|
||||||
|
|
||||||
# Verify if set successfully
|
|
||||||
echo $UNI_LAB_AUTH_TOKEN
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. Obtain Authentication Token
|
|
||||||
|
|
||||||
> **Important**: Obtain API Key from Uni-Lab Homepage → Account Security.
|
|
||||||
|
|
||||||
**Steps to Obtain**:
|
|
||||||
1. Login to Uni-Lab system
|
|
||||||
2. Go to Homepage → Account Security
|
|
||||||
3. Copy your API Key
|
|
||||||
|
|
||||||
Token format example:
|
|
||||||
```
|
|
||||||
Api 48ccxx336fba44f39e1e37db93xxxxx
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Tips**:
|
|
||||||
> - If Token already includes `Api ` prefix, use directly
|
|
||||||
> - If no prefix, code will automatically add `Api ` prefix
|
|
||||||
> - Old `Bearer` JWT Token format is still compatible
|
|
||||||
|
|
||||||
#### 4. Persistent Configuration (Optional)
|
|
||||||
|
|
||||||
**Temporary Configuration**: Environment variables set with the above commands are only valid for the current terminal session.
|
|
||||||
|
|
||||||
**Persistence Method 1: PowerShell Profile**
|
|
||||||
```powershell
|
|
||||||
# Edit PowerShell profile
|
|
||||||
notepad $PROFILE
|
|
||||||
|
|
||||||
# Add to the opened file:
|
|
||||||
$env:UNI_LAB_AUTH_TOKEN = "Api your_API_Key"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Persistence Method 2: Windows System Environment Variables**
|
|
||||||
- Right-click "This PC" → "Properties" → "Advanced system settings" → "Environment Variables"
|
|
||||||
- Add user or system variable:
|
|
||||||
- Variable name: `UNI_LAB_AUTH_TOKEN`
|
|
||||||
- Variable value: `Api your_API_Key`
|
|
||||||
|
|
||||||
### Usage Workflow
|
|
||||||
|
|
||||||
#### Step 1: Enable OSS Upload Feature
|
|
||||||
|
|
||||||
**Recommended: Configure in `device.json`**
|
|
||||||
|
|
||||||
Edit device configuration file `unilabos/devices/neware_battery_test_system/device.json`, add to `config`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "NEWARE_BATTERY_TEST_SYSTEM",
|
|
||||||
"config": {
|
|
||||||
"ip": "127.0.0.1",
|
|
||||||
"port": 502,
|
|
||||||
"machine_id": 1,
|
|
||||||
"oss_upload_enabled": true,
|
|
||||||
"oss_prefix": "neware_backup/2025-12"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Parameter Description**:
|
|
||||||
- `oss_upload_enabled`: Set to `true` to enable OSS upload
|
|
||||||
- `oss_prefix`: OSS file path prefix, recommended to organize by date or project (currently unused, retained for interface compatibility)
|
|
||||||
|
|
||||||
**Alternative: Via Initialization Parameters**
|
|
||||||
|
|
||||||
```python
|
|
||||||
device = NewareBatteryTestSystem(
|
|
||||||
ip="127.0.0.1",
|
|
||||||
port=502,
|
|
||||||
oss_upload_enabled=True, # Enable OSS upload
|
|
||||||
oss_prefix="neware_backup/2025-12" # Optional: custom path prefix
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**After configuration, restart the ROS node for changes to take effect.**
|
|
||||||
|
|
||||||
#### Step 2: Submit Test Tasks
|
|
||||||
|
|
||||||
Use `submit_from_csv` to submit test tasks:
|
|
||||||
|
|
||||||
```python
|
|
||||||
result = device.submit_from_csv(
|
|
||||||
csv_path="test_data.csv",
|
|
||||||
output_dir="D:/neware_output"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates the following directory structure:
|
|
||||||
```
|
|
||||||
D:/neware_output/
|
|
||||||
├── xml_dir/ # XML configuration files
|
|
||||||
└── backup_dir/ # Test data backup (generated by Neware device)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 3: Wait for Test Completion
|
|
||||||
|
|
||||||
Wait for the Neware device to complete testing. Backup files will be generated in the `backup_dir`.
|
|
||||||
|
|
||||||
#### Step 4: Upload Backup Files to OSS
|
|
||||||
|
|
||||||
**Method A: Use Default Settings (Recommended)**
|
|
||||||
```python
|
|
||||||
# Automatically uses the last backup_dir, uploads all files
|
|
||||||
result = device.upload_backup_to_oss()
|
|
||||||
```
|
|
||||||
|
|
||||||
**Method B: Specify Backup Directory**
|
|
||||||
```python
|
|
||||||
# Manually specify backup directory
|
|
||||||
result = device.upload_backup_to_oss(
|
|
||||||
backup_dir="D:/neware_output/backup_dir"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Method C: Filter Specific Files**
|
|
||||||
```python
|
|
||||||
# Upload only CSV files
|
|
||||||
result = device.upload_backup_to_oss(
|
|
||||||
backup_dir="D:/neware_output/backup_dir",
|
|
||||||
file_pattern="*.csv"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Upload files for specific battery IDs
|
|
||||||
result = device.upload_backup_to_oss(
|
|
||||||
file_pattern="Battery_A001_*.nda"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Return Result Examples
|
|
||||||
|
|
||||||
**All Files Uploaded Successfully**:
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"return_info": "All uploads successful: 15/15 files",
|
|
||||||
"success": True,
|
|
||||||
"uploaded_count": 15,
|
|
||||||
"total_count": 15,
|
|
||||||
"failed_files": [],
|
|
||||||
"uploaded_files": [
|
|
||||||
{
|
|
||||||
"filename": "Battery_A001.ndax",
|
|
||||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A001.ndax"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename": "Battery_A002.ndax",
|
|
||||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A002.ndax"
|
|
||||||
}
|
|
||||||
# ... other 13 files
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Partial Upload Success**:
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"return_info": "Partial upload success: 12/15 files, 3 failed",
|
|
||||||
"success": True,
|
|
||||||
"uploaded_count": 12,
|
|
||||||
"total_count": 15,
|
|
||||||
"failed_files": ["Battery_A003.csv", "Battery_A007.csv", "test.log"],
|
|
||||||
"uploaded_files": [
|
|
||||||
{
|
|
||||||
"filename": "Battery_A001.ndax",
|
|
||||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A001.ndax"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"filename": "Battery_A002.ndax",
|
|
||||||
"url": "https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/abc123.../Battery_A002.ndax"
|
|
||||||
}
|
|
||||||
# ... other 10 successfully uploaded files
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note**: The `uploaded_files` field contains detailed information for all successfully uploaded files:
|
|
||||||
> - `filename`: Filename (without path)
|
|
||||||
> - `url`: Complete OSS access URL for the file
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
### OSS Upload Not Enabled
|
|
||||||
|
|
||||||
If `oss_upload_enabled=False`, calling `upload_backup_to_oss` returns:
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"return_info": "OSS upload not enabled (oss_upload_enabled=False), skipping upload. Backup directory: ...",
|
|
||||||
"success": False,
|
|
||||||
"uploaded_count": 0,
|
|
||||||
"total_count": 0,
|
|
||||||
"failed_files": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution**: Set `device.oss_upload_enabled = True`
|
|
||||||
|
|
||||||
### Environment Variables Not Configured
|
|
||||||
|
|
||||||
If `UNI_LAB_AUTH_TOKEN` is missing, returns:
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"return_info": "OSS environment variable configuration error: Please set environment variable: UNI_LAB_AUTH_TOKEN",
|
|
||||||
"success": False,
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution**: Configure environment variables as per prerequisites
|
|
||||||
|
|
||||||
### Backup Directory Does Not Exist
|
|
||||||
|
|
||||||
If specified backup directory doesn't exist, returns:
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"return_info": "Backup directory does not exist: D:/neware_output/backup_dir",
|
|
||||||
"success": False,
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution**: Check if directory path is correct, or wait for test to generate backup files
|
|
||||||
|
|
||||||
### API Authentication Failed
|
|
||||||
|
|
||||||
If Token is invalid or expired, returns:
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"return_info": "Failed to get credentials: Authentication failed",
|
|
||||||
"success": False,
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution**: Check if Token is correct, or contact development team for new Token
|
|
||||||
|
|
||||||
## Technical Details
|
|
||||||
|
|
||||||
### OSS Upload Process (New Method)
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
A[Start Upload] --> B[Verify Configuration and Environment Variables]
|
|
||||||
B --> C[Scan Backup Directory]
|
|
||||||
C --> D[Filter Files Matching Pattern]
|
|
||||||
D --> E[Iterate Each File]
|
|
||||||
E --> F[Call API to Get Presigned URL]
|
|
||||||
F --> G{Success?}
|
|
||||||
G -->|Yes| H[Upload File Using Presigned URL]
|
|
||||||
G -->|No| I[Record Failure]
|
|
||||||
H --> J{Upload Success?}
|
|
||||||
J -->|Yes| K[Record Success + File URL]
|
|
||||||
J -->|No| I
|
|
||||||
I --> L{More Files?}
|
|
||||||
K --> L
|
|
||||||
L -->|Yes| E
|
|
||||||
L -->|No| M[Return Statistics]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Upload API Flow
|
|
||||||
|
|
||||||
1. **Get Presigned URL**
|
|
||||||
- Request: `GET /api/v1/lab/storage/token?scene={scene}&filename={filename}&path={path}`
|
|
||||||
- Authentication: `Authorization: Api {api_key}` or `Authorization: Bearer {token}`
|
|
||||||
- Response: `{code: 0, data: {url: "presigned_url", path: "file_path"}}`
|
|
||||||
|
|
||||||
2. **Upload File**
|
|
||||||
- Request: `PUT {presigned_url}`
|
|
||||||
- Content: File binary data
|
|
||||||
- Response: HTTP 200 indicates success
|
|
||||||
|
|
||||||
3. **Generate Access URL**
|
|
||||||
- Format: `https://{OSS_PUBLIC_HOST}/{path}`
|
|
||||||
- Example: `https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/20251217/battery_data.csv`
|
|
||||||
|
|
||||||
### Logging
|
|
||||||
|
|
||||||
All upload operations are logged through ROS logging system:
|
|
||||||
- `INFO` level: Upload progress and success information
|
|
||||||
- `WARNING` level: Empty directory, not enabled warnings
|
|
||||||
- `ERROR` level: Upload failures, configuration errors
|
|
||||||
|
|
||||||
## Important Notes
|
|
||||||
|
|
||||||
1. **Upload Timing**: Files in `backup_dir` are generated in real-time during test execution. Ensure testing is complete before uploading.
|
|
||||||
|
|
||||||
2. **File Naming**: Files uploaded to OSS retain original filenames. Paths are assigned by unified API.
|
|
||||||
|
|
||||||
3. **Network Requirements**: Upload requires stable network connection to Aliyun OSS service.
|
|
||||||
|
|
||||||
4. **Token Expiration**: JWT Tokens have expiration time. Need to obtain new token after expiration.
|
|
||||||
|
|
||||||
5. **Cost Considerations**: OSS storage and traffic incur costs. Set file filtering rules appropriately.
|
|
||||||
|
|
||||||
6. **Concurrent Upload**: Current implementation uses serial upload. Large number of files may take considerable time.
|
|
||||||
|
|
||||||
7. **File Size Limits**: Note single file size upload limits (controlled by unified API).
|
|
||||||
|
|
||||||
## Compatibility
|
|
||||||
|
|
||||||
- ✅ **Backward Compatible**: Default `oss_upload_enabled=False`, does not affect existing systems
|
|
||||||
- ✅ **Optional Feature**: Enable only when needed
|
|
||||||
- ✅ **Independent Operation**: Upload failures do not affect test task submission and execution
|
|
||||||
- ⚠️ **Environment Variable Changes**: Need to update environment variable configuration (from OSS AK/SK to API Key)
|
|
||||||
|
|
||||||
## Migration Guide
|
|
||||||
|
|
||||||
If you previously used the `oss2` library method, follow these steps to migrate:
|
|
||||||
|
|
||||||
### 1. Uninstall Old Dependencies (Optional)
|
|
||||||
```bash
|
|
||||||
pip uninstall oss2
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Remove Old Environment Variables
|
|
||||||
```powershell
|
|
||||||
# PowerShell
|
|
||||||
Remove-Item Env:\OSS_ACCESS_KEY_ID
|
|
||||||
Remove-Item Env:\OSS_ACCESS_KEY_SECRET
|
|
||||||
Remove-Item Env:\OSS_BUCKET_NAME
|
|
||||||
Remove-Item Env:\OSS_ENDPOINT
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Set New Environment Variables
|
|
||||||
```powershell
|
|
||||||
# PowerShell
|
|
||||||
$env:UNI_LAB_AUTH_TOKEN = "Api your_API_Key"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Test Upload Functionality
|
|
||||||
```python
|
|
||||||
# Verify upload works correctly
|
|
||||||
result = device.upload_backup_to_oss(backup_dir="test_directory")
|
|
||||||
print(result)
|
|
||||||
```
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
**Q: Why change from `oss2` to unified API?**
|
|
||||||
A: To maintain consistency with other team systems, simplify configuration, and unify authentication methods.
|
|
||||||
|
|
||||||
**Q: Where to get the Token?**
|
|
||||||
A: Obtain API Key from Uni-Lab Homepage → Account Security.
|
|
||||||
|
|
||||||
**Q: What if Token expires?**
|
|
||||||
A: Obtain a new API Key and update the `UNI_LAB_AUTH_TOKEN` environment variable.
|
|
||||||
|
|
||||||
**Q: Can I customize upload paths?**
|
|
||||||
A: Current version has paths automatically assigned by unified API. `oss_prefix` parameter is currently unused (retained for interface compatibility).
|
|
||||||
|
|
||||||
**Q: Why not auto-upload in `submit_from_csv`?**
|
|
||||||
A: Because backup files are generated progressively during testing, they may not be fully generated when the method returns. A separate upload method provides more flexibility.
|
|
||||||
|
|
||||||
**Q: How to access files after upload?**
|
|
||||||
A: Upload success returns file access URL in format `https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/{path}`
|
|
||||||
|
|
||||||
**Q: How to delete uploaded files?**
|
|
||||||
A: Need to operate through OSS console or API. This feature only handles uploads.
|
|
||||||
|
|
||||||
## Verifying Upload Results
|
|
||||||
|
|
||||||
### Method 1: Via Aliyun Console
|
|
||||||
|
|
||||||
1. Login to [Aliyun OSS Console](https://oss.console.aliyun.com/)
|
|
||||||
2. Click **Bucket List** on the left
|
|
||||||
3. Select the `uni-lab-test` Bucket
|
|
||||||
4. Click **File Management**
|
|
||||||
5. View uploaded file list
|
|
||||||
|
|
||||||
### Method 2: Using Returned File URL
|
|
||||||
|
|
||||||
After successful upload, `upload_file_to_oss()` returns file access URL:
|
|
||||||
```python
|
|
||||||
url = upload_file_to_oss("local_file.csv")
|
|
||||||
print(f"File access URL: {url}")
|
|
||||||
# Example output: https://uni-lab-test.oss-cn-zhangjiakou.aliyuncs.com/job/20251217/local_file.csv
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note**: OSS files are private by default, direct URL access may require signature authentication.
|
|
||||||
|
|
||||||
### Method 3: Using ossutil CLI Tool
|
|
||||||
|
|
||||||
After installing [ossutil](https://help.aliyun.com/document_detail/120075.html):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# List files
|
|
||||||
ossutil ls oss://uni-lab-test/job/
|
|
||||||
|
|
||||||
# Download file to local
|
|
||||||
ossutil cp oss://uni-lab-test/job/20251217/filename ./local_path
|
|
||||||
|
|
||||||
# Generate signed URL (valid for 1 hour)
|
|
||||||
ossutil sign oss://uni-lab-test/job/20251217/filename --timeout 3600
|
|
||||||
```
|
|
||||||
|
|
||||||
## Changelog
|
|
||||||
|
|
||||||
- **2025-12-17**: v2.0 (Major Update)
|
|
||||||
- ⚠️ Changed from `oss2` library to unified API approach
|
|
||||||
- Simplified environment variable configuration (only API Key required)
|
|
||||||
- Added `get_upload_token()` and `upload_file_with_presigned_url()` functions
|
|
||||||
- `upload_file_to_oss()` return value changed to file access URL
|
|
||||||
- Updated documentation and migration guide
|
|
||||||
- Token format: Support both `Api Key` and `Bearer JWT`
|
|
||||||
- API endpoint: `/api/v1/lab/storage/token`
|
|
||||||
- Scene parameter: Fixed to `job` (other values changed to `default`)
|
|
||||||
|
|
||||||
- **2025-12-15**: v1.1
|
|
||||||
- Added initialization parameters `oss_upload_enabled` and `oss_prefix`
|
|
||||||
- Support OSS upload configuration in `device.json`
|
|
||||||
- Updated usage guide, added verification methods
|
|
||||||
|
|
||||||
- **2025-12-13**: v1.0 Initial Version
|
|
||||||
- Added OSS upload utility functions (based on `oss2` library)
|
|
||||||
- Created `upload_backup_to_oss` action method
|
|
||||||
- Support file filtering and custom OSS paths
|
|
||||||
|
|
||||||
## References
|
|
||||||
|
|
||||||
- [Uni-Lab Unified File Upload API Documentation](https://uni-lab.test.bohrium.com/api/docs) (if available)
|
|
||||||
- [Aliyun OSS Console](https://oss.console.aliyun.com/)
|
|
||||||
- [ossutil Tool Documentation](https://help.aliyun.com/document_detail/120075.html)
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "NEWARE_BATTERY_TEST_SYSTEM",
|
|
||||||
"name": "Neware Battery Test System",
|
|
||||||
"parent": null,
|
|
||||||
"type": "device",
|
|
||||||
"class": "neware_battery_test_system",
|
|
||||||
"position": {
|
|
||||||
"x": 620.0,
|
|
||||||
"y": 200.0,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"ip": "127.0.0.1",
|
|
||||||
"port": 502,
|
|
||||||
"machine_id": 1,
|
|
||||||
"devtype": "27",
|
|
||||||
"timeout": 20,
|
|
||||||
"size_x": 500.0,
|
|
||||||
"size_y": 500.0,
|
|
||||||
"size_z": 2000.0,
|
|
||||||
"oss_upload_enabled": true,
|
|
||||||
"oss_prefix": "neware_backup/2025-12"
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"功能说明": "新威电池测试系统,提供720通道监控和CSV批量提交功能",
|
|
||||||
"监控功能": "支持720个通道的实时状态监控、2盘电池物料管理、状态导出等",
|
|
||||||
"提交功能": "通过submit_from_csv action从CSV文件批量提交测试任务。CSV必须包含: Battery_Code, Pole_Weight, 集流体质量, 活性物质含量, 克容量mah/g, 电池体系, 设备号, 排号, 通道号"
|
|
||||||
},
|
|
||||||
"children": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"links": []
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,197 @@
|
|||||||
|
{
|
||||||
|
"token": "",
|
||||||
|
"request_time": "2025-12-24T15:32:09.2148671+08:00",
|
||||||
|
"data": {
|
||||||
|
"orderId": "3a1e614d-a082-c44a-60be-68647a35e6f1",
|
||||||
|
"orderCode": "BSO2025122400024",
|
||||||
|
"orderName": "DP20251224001",
|
||||||
|
"startTime": "2025-12-24T14:51:50.549848",
|
||||||
|
"endTime": "2025-12-24T15:32:09.000765",
|
||||||
|
"status": "30",
|
||||||
|
"workflowStatus": "completed",
|
||||||
|
"completionTime": "2025-12-24T15:32:09.000765",
|
||||||
|
"usedMaterials": [
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-53a6-0ec4-10bd-956b240c0f04",
|
||||||
|
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||||
|
"typemode": "1",
|
||||||
|
"usedQuantity": 2,
|
||||||
|
"realQuantity": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-4da7-cf62-3a40-7e5879255c0c",
|
||||||
|
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
|
||||||
|
"typemode": "1",
|
||||||
|
"usedQuantity": 1,
|
||||||
|
"realQuantity": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-53a7-2850-42c8-a7a2de8ff4bf",
|
||||||
|
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||||
|
"typemode": "1",
|
||||||
|
"usedQuantity": 1,
|
||||||
|
"realQuantity": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-4da6-ac9d-02be-4b0716796bd2",
|
||||||
|
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
|
||||||
|
"typemode": "1",
|
||||||
|
"usedQuantity": 2,
|
||||||
|
"realQuantity": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614d-9c9a-fafa-4757-c7411b03bd9f",
|
||||||
|
"locationId": "3a1abd46-18fe-1f56-6ced-a1f7fe08e36c",
|
||||||
|
"typemode": "0",
|
||||||
|
"usedQuantity": 1,
|
||||||
|
"realQuantity": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-6917-b8f9-7987-7a33a3792829",
|
||||||
|
"locationId": "3a19da43-57b5-294f-d663-154a1cc32270",
|
||||||
|
"typemode": "2",
|
||||||
|
"usedQuantity": 3.51,
|
||||||
|
"realQuantity": 3.5155000000000000000000000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-6914-d92b-e348-f52e13817a5d",
|
||||||
|
"locationId": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
||||||
|
"typemode": "2",
|
||||||
|
"usedQuantity": 0.33,
|
||||||
|
"realQuantity": 0.3336000000000000000000000000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"token": "",
|
||||||
|
"request_time": "2025-12-24T15:32:09.9999039+08:00",
|
||||||
|
"data": {
|
||||||
|
"orderId": "3a1e614d-a0a2-f7a9-9360-610021c9479d",
|
||||||
|
"orderCode": "BSO2025122400025",
|
||||||
|
"orderName": "DP20251224002",
|
||||||
|
"startTime": "2025-12-24T14:53:03.44259",
|
||||||
|
"endTime": "2025-12-24T15:32:09.828261",
|
||||||
|
"status": "30",
|
||||||
|
"workflowStatus": "completed",
|
||||||
|
"completionTime": "2025-12-24T15:32:09.828261",
|
||||||
|
"usedMaterials": [
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-4da7-6527-9f1c-b39e3de8ff2b",
|
||||||
|
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
|
||||||
|
"typemode": "1",
|
||||||
|
"usedQuantity": 1,
|
||||||
|
"realQuantity": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-53a6-0ec4-10bd-956b240c0f04",
|
||||||
|
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||||
|
"typemode": "1",
|
||||||
|
"usedQuantity": 2,
|
||||||
|
"realQuantity": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-4da6-ac9d-02be-4b0716796bd2",
|
||||||
|
"locationId": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb",
|
||||||
|
"typemode": "1",
|
||||||
|
"usedQuantity": 2,
|
||||||
|
"realQuantity": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-53a8-8474-cac8-0fd7d349e4b2",
|
||||||
|
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||||
|
"typemode": "1",
|
||||||
|
"usedQuantity": 1,
|
||||||
|
"realQuantity": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614d-9c9a-fafa-4757-c7411b03bd9f",
|
||||||
|
"locationId": null,
|
||||||
|
"typemode": "0",
|
||||||
|
"usedQuantity": 1,
|
||||||
|
"realQuantity": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-6917-b8f9-7987-7a33a3792829",
|
||||||
|
"locationId": "3a19da43-57b5-294f-d663-154a1cc32270",
|
||||||
|
"typemode": "2",
|
||||||
|
"usedQuantity": 0.7,
|
||||||
|
"realQuantity": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-6914-d92b-e348-f52e13817a5d",
|
||||||
|
"locationId": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
||||||
|
"typemode": "2",
|
||||||
|
"usedQuantity": 1.15,
|
||||||
|
"realQuantity": 1.1627000000000000000000000000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"token": "",
|
||||||
|
"request_time": "2025-12-24T15:34:00.4139986+08:00",
|
||||||
|
"data": {
|
||||||
|
"orderId": "3a1e614d-a0cd-81ca-9f7f-2f4e93af01cd",
|
||||||
|
"orderCode": "BSO2025122400026",
|
||||||
|
"orderName": "DP20251224003",
|
||||||
|
"startTime": "2025-12-24T14:54:24.443344",
|
||||||
|
"endTime": "2025-12-24T15:34:00.26321",
|
||||||
|
"status": "30",
|
||||||
|
"workflowStatus": "completed",
|
||||||
|
"completionTime": "2025-12-24T15:34:00.26321",
|
||||||
|
"usedMaterials": [
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-4da6-ac9d-02be-4b0716796bd2",
|
||||||
|
"locationId": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||||
|
"typemode": "1",
|
||||||
|
"usedQuantity": 2,
|
||||||
|
"realQuantity": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-4da8-b678-f204-207076f09c83",
|
||||||
|
"locationId": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||||
|
"typemode": "1",
|
||||||
|
"usedQuantity": 1,
|
||||||
|
"realQuantity": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-53a6-0ec4-10bd-956b240c0f04",
|
||||||
|
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||||
|
"typemode": "1",
|
||||||
|
"usedQuantity": 2,
|
||||||
|
"realQuantity": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-53a8-e3f2-dee0-fa97b600b652",
|
||||||
|
"locationId": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||||
|
"typemode": "1",
|
||||||
|
"usedQuantity": 1,
|
||||||
|
"realQuantity": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614d-9c9a-fafa-4757-c7411b03bd9f",
|
||||||
|
"locationId": null,
|
||||||
|
"typemode": "0",
|
||||||
|
"usedQuantity": 1,
|
||||||
|
"realQuantity": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-6917-b8f9-7987-7a33a3792829",
|
||||||
|
"locationId": "3a19da43-57b5-294f-d663-154a1cc32270",
|
||||||
|
"typemode": "2",
|
||||||
|
"usedQuantity": 2.0,
|
||||||
|
"realQuantity": 2.0075000000000000000000000000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"materialId": "3a1e614b-6914-d92b-e348-f52e13817a5d",
|
||||||
|
"locationId": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
||||||
|
"typemode": "2",
|
||||||
|
"usedQuantity": 1.2,
|
||||||
|
"realQuantity": 1.2126000000000000000000000000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import pubchempy as pcp
|
||||||
|
|
||||||
|
cas = "21324-40-3" # 示例
|
||||||
|
comps = pcp.get_compounds(cas, namespace="name")
|
||||||
|
if not comps:
|
||||||
|
raise ValueError("No hit")
|
||||||
|
|
||||||
|
c = comps[0]
|
||||||
|
|
||||||
|
print("Canonical SMILES:", c.canonical_smiles)
|
||||||
|
print("Isomeric SMILES:", c.isomeric_smiles)
|
||||||
|
print("MW:", c.molecular_weight)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
material_name
|
||||||
|
LiPF6
|
||||||
|
LiDFOB
|
||||||
|
DTD
|
||||||
|
LiFSI
|
||||||
|
LiPO2F2
|
||||||
|
|
||||||
|
@@ -0,0 +1,113 @@
|
|||||||
|
# Bioyond Cell 工作站 - 多订单返回示例
|
||||||
|
|
||||||
|
本文档说明了 `create_orders` 函数如何收集并返回所有订单的完成报文。
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
|
||||||
|
之前的实现只会等待并返回第一个订单的完成报文,如果有多个订单(例如从 Excel 解析出 3 个订单),只能得到第一个订单的推送信息。
|
||||||
|
|
||||||
|
## 解决方案
|
||||||
|
|
||||||
|
修改后的 `create_orders` 函数现在会:
|
||||||
|
|
||||||
|
1. **提取所有 orderCode**:从 LIMS 接口返回的 `data` 列表中提取所有订单编号
|
||||||
|
2. **逐个等待完成**:遍历所有 orderCode,调用 `wait_for_order_finish` 等待每个订单完成
|
||||||
|
3. **收集所有报文**:将每个订单的完成报文存入 `all_reports` 列表
|
||||||
|
4. **统一返回**:返回包含所有订单报文的 JSON 格式数据
|
||||||
|
|
||||||
|
## 返回格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "all_completed",
|
||||||
|
"total_orders": 3,
|
||||||
|
"reports": [
|
||||||
|
{
|
||||||
|
"token": "",
|
||||||
|
"request_time": "2025-12-24T15:32:09.2148671+08:00",
|
||||||
|
"data": {
|
||||||
|
"orderId": "3a1e614d-a082-c44a-60be-68647a35e6f1",
|
||||||
|
"orderCode": "BSO2025122400024",
|
||||||
|
"orderName": "DP20251224001",
|
||||||
|
"status": "30",
|
||||||
|
"workflowStatus": "completed",
|
||||||
|
"usedMaterials": [...]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "",
|
||||||
|
"request_time": "2025-12-24T15:32:09.9999039+08:00",
|
||||||
|
"data": {
|
||||||
|
"orderId": "3a1e614d-a0a2-f7a9-9360-610021c9479d",
|
||||||
|
"orderCode": "BSO2025122400025",
|
||||||
|
"orderName": "DP20251224002",
|
||||||
|
"status": "30",
|
||||||
|
"workflowStatus": "completed",
|
||||||
|
"usedMaterials": [...]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "",
|
||||||
|
"request_time": "2025-12-24T15:34:00.4139986+08:00",
|
||||||
|
"data": {
|
||||||
|
"orderId": "3a1e614d-a0cd-81ca-9f7f-2f4e93af01cd",
|
||||||
|
"orderCode": "BSO2025122400026",
|
||||||
|
"orderName": "DP20251224003",
|
||||||
|
"status": "30",
|
||||||
|
"workflowStatus": "completed",
|
||||||
|
"usedMaterials": [...]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"original_response": {...}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用示例
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 调用 create_orders
|
||||||
|
result = workstation.create_orders("20251224.xlsx")
|
||||||
|
|
||||||
|
# 访问返回数据
|
||||||
|
print(f"总订单数: {result['total_orders']}")
|
||||||
|
print(f"状态: {result['status']}")
|
||||||
|
|
||||||
|
# 遍历所有订单的报文
|
||||||
|
for i, report in enumerate(result['reports'], 1):
|
||||||
|
order_data = report.get('data', {})
|
||||||
|
print(f"\n订单 {i}:")
|
||||||
|
print(f" orderCode: {order_data.get('orderCode')}")
|
||||||
|
print(f" orderName: {order_data.get('orderName')}")
|
||||||
|
print(f" status: {order_data.get('status')}")
|
||||||
|
print(f" 使用物料数: {len(order_data.get('usedMaterials', []))}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 控制台输出示例
|
||||||
|
|
||||||
|
```
|
||||||
|
[create_orders] 即将提交订单数量: 3
|
||||||
|
[create_orders] 接口返回: {...}
|
||||||
|
[create_orders] 等待 3 个订单完成: ['BSO2025122400024', 'BSO2025122400025', 'BSO2025122400026']
|
||||||
|
[create_orders] 正在等待第 1/3 个订单: BSO2025122400024
|
||||||
|
[create_orders] ✓ 订单 BSO2025122400024 完成
|
||||||
|
[create_orders] 正在等待第 2/3 个订单: BSO2025122400025
|
||||||
|
[create_orders] ✓ 订单 BSO2025122400025 完成
|
||||||
|
[create_orders] 正在等待第 3/3 个订单: BSO2025122400026
|
||||||
|
[create_orders] ✓ 订单 BSO2025122400026 完成
|
||||||
|
[create_orders] 所有订单已完成,共收集 3 个报文
|
||||||
|
实验记录本========================create_orders========================
|
||||||
|
返回报文数量: 3
|
||||||
|
报文 1: orderCode=BSO2025122400024, status=30
|
||||||
|
报文 2: orderCode=BSO2025122400025, status=30
|
||||||
|
报文 3: orderCode=BSO2025122400026, status=30
|
||||||
|
========================
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键改进
|
||||||
|
|
||||||
|
1. ✅ **等待所有订单**:不再只等待第一个订单,而是遍历所有 orderCode
|
||||||
|
2. ✅ **收集完整报文**:每个订单的完整推送报文都被保存在 `reports` 数组中
|
||||||
|
3. ✅ **详细日志**:清晰显示正在等待哪个订单,以及完成情况
|
||||||
|
4. ✅ **错误处理**:即使某个订单失败,也会记录其状态信息
|
||||||
|
5. ✅ **统一格式**:返回的 JSON 格式便于后续处理和分析
|
||||||
@@ -47,8 +47,8 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
print("开始初始化 BioyondV1RPC")
|
print("开始初始化 BioyondV1RPC")
|
||||||
self.config = config
|
self.config = config
|
||||||
self.api_key = config["api_key"]
|
self.api_key = config.get("api_key", "")
|
||||||
self.host = config["api_host"]
|
self.host = config.get("api_host", "") or config.get("base_url", "")
|
||||||
self._logger = SimpleLogger()
|
self._logger = SimpleLogger()
|
||||||
self.material_cache = {}
|
self.material_cache = {}
|
||||||
self._load_material_cache()
|
self._load_material_cache()
|
||||||
@@ -61,7 +61,7 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
|
|
||||||
:return: 当前时间的 ISO 8601 格式字符串
|
:return: 当前时间的 ISO 8601 格式字符串
|
||||||
"""
|
"""
|
||||||
current_time = datetime.now(timezone.utc).isoformat(
|
current_time = datetime.now().isoformat(
|
||||||
timespec='milliseconds'
|
timespec='milliseconds'
|
||||||
)
|
)
|
||||||
# 替换时区部分为 'Z'
|
# 替换时区部分为 'Z'
|
||||||
@@ -176,24 +176,7 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
print(f"add material data: {response['data']}")
|
print(f"add material data: {response['data']}")
|
||||||
|
return response.get("data", {})
|
||||||
# 自动更新缓存
|
|
||||||
data = response.get("data", {})
|
|
||||||
if data:
|
|
||||||
if isinstance(data, str):
|
|
||||||
# 如果返回的是字符串,通常是ID
|
|
||||||
mat_id = data
|
|
||||||
name = params.get("name")
|
|
||||||
else:
|
|
||||||
# 如果返回的是字典,尝试获取name和id
|
|
||||||
name = data.get("name") or params.get("name")
|
|
||||||
mat_id = data.get("id")
|
|
||||||
|
|
||||||
if name and mat_id:
|
|
||||||
self.material_cache[name] = mat_id
|
|
||||||
print(f"已自动更新缓存: {name} -> {mat_id}")
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def query_matial_type_id(self, data) -> list:
|
def query_matial_type_id(self, data) -> list:
|
||||||
"""查找物料typeid"""
|
"""查找物料typeid"""
|
||||||
@@ -209,23 +192,6 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return []
|
return []
|
||||||
return str(response.get("data", {}))
|
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": 0,
|
|
||||||
})
|
|
||||||
if not response or response['code'] != 1:
|
|
||||||
return []
|
|
||||||
return response.get("data", [])
|
|
||||||
|
|
||||||
def material_inbound(self, material_id: str, location_id: str) -> dict:
|
def material_inbound(self, material_id: str, location_id: str) -> dict:
|
||||||
"""
|
"""
|
||||||
描述:指定库位入库一个物料
|
描述:指定库位入库一个物料
|
||||||
@@ -246,34 +212,8 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
})
|
})
|
||||||
|
|
||||||
if not response or response['code'] != 1:
|
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 {}
|
return {}
|
||||||
# 入库成功时,即使没有 data 字段,也返回成功标识
|
return response.get("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)
|
|
||||||
|
|
||||||
def delete_material(self, material_id: str) -> dict:
|
def delete_material(self, material_id: str) -> dict:
|
||||||
"""
|
"""
|
||||||
@@ -290,18 +230,10 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
|
|
||||||
if not response or response['code'] != 1:
|
if not response or response['code'] != 1:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# 自动更新缓存 - 移除被删除的物料
|
|
||||||
for name, mid in list(self.material_cache.items()):
|
|
||||||
if mid == material_id:
|
|
||||||
del self.material_cache[name]
|
|
||||||
print(f"已从缓存移除物料: {name}")
|
|
||||||
break
|
|
||||||
|
|
||||||
return response.get("data", {})
|
return response.get("data", {})
|
||||||
|
|
||||||
def material_outbound(self, material_id: str, location_name: str, quantity: int) -> dict:
|
def material_outbound(self, material_id: str, location_name: str, quantity: int) -> dict:
|
||||||
"""指定库位出库物料(通过库位名称)"""
|
"""指定库位出库物料"""
|
||||||
location_id = LOCATION_MAPPING.get(location_name, location_name)
|
location_id = LOCATION_MAPPING.get(location_name, location_name)
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
@@ -318,98 +250,9 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
"data": params
|
"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:
|
if not response or response['code'] != 1:
|
||||||
return {}
|
return {}
|
||||||
return response.get("data", {})
|
return response
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# ==================== 工作流查询相关接口 ====================
|
# ==================== 工作流查询相关接口 ====================
|
||||||
|
|
||||||
@@ -454,66 +297,6 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return {}
|
return {}
|
||||||
return response.get("data", {})
|
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]:
|
def validate_workflow_parameters(self, workflows: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||||
"""验证工作流参数格式"""
|
"""验证工作流参数格式"""
|
||||||
try:
|
try:
|
||||||
@@ -676,15 +459,18 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return {}
|
return {}
|
||||||
return response.get("data", {})
|
return response.get("data", {})
|
||||||
|
|
||||||
def order_report(self, order_id: str) -> dict:
|
def order_report(self, json_str: str) -> dict:
|
||||||
"""查询订单报告
|
|
||||||
|
|
||||||
参数:
|
|
||||||
order_id: 订单ID
|
|
||||||
|
|
||||||
返回值:
|
|
||||||
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(
|
response = self.post(
|
||||||
url=f'{self.host}/api/lims/order/order-report',
|
url=f'{self.host}/api/lims/order/order-report',
|
||||||
params={
|
params={
|
||||||
@@ -692,18 +478,16 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
"requestTime": self.get_current_time_iso8601(),
|
"requestTime": self.get_current_time_iso8601(),
|
||||||
"data": order_id,
|
"data": order_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
if not response or response['code'] != 1:
|
if not response or response['code'] != 1:
|
||||||
return {}
|
return {}
|
||||||
return response.get("data", {})
|
return response.get("data", {})
|
||||||
|
|
||||||
def order_takeout(self, json_str: str) -> int:
|
def order_takeout(self, json_str: str) -> int:
|
||||||
"""取出任务产物
|
"""
|
||||||
|
描述:取出任务产物
|
||||||
参数:
|
json_str 格式为JSON字符串:
|
||||||
json_str: JSON字符串,包含 order_id 与 preintake_id
|
'{"order_id": "order123", "preintake_id": "preintake123"}'
|
||||||
|
|
||||||
返回值:
|
|
||||||
int: 成功返回1,失败返回0
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
data = json.loads(json_str)
|
data = json.loads(json_str)
|
||||||
@@ -726,15 +510,14 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return 0
|
return 0
|
||||||
return response.get("code", 0)
|
return response.get("code", 0)
|
||||||
|
|
||||||
|
|
||||||
def sample_waste_removal(self, order_id: str) -> dict:
|
def sample_waste_removal(self, order_id: str) -> dict:
|
||||||
"""样品/废料取出
|
"""
|
||||||
|
样品/废料取出接口
|
||||||
|
|
||||||
参数:
|
参数:
|
||||||
order_id: 订单ID
|
- order_id: 订单ID
|
||||||
|
|
||||||
返回值:
|
返回: 取出结果
|
||||||
dict: 取出结果,失败返回空字典
|
|
||||||
"""
|
"""
|
||||||
params = {"orderId": order_id}
|
params = {"orderId": order_id}
|
||||||
|
|
||||||
@@ -756,13 +539,10 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return response.get("data", {})
|
return response.get("data", {})
|
||||||
|
|
||||||
def cancel_order(self, json_str: str) -> bool:
|
def cancel_order(self, json_str: str) -> bool:
|
||||||
"""取消指定任务
|
"""
|
||||||
|
描述:取消指定任务
|
||||||
参数:
|
json_str 格式为JSON字符串:
|
||||||
json_str: JSON字符串,包含 order_id
|
'{"order_id": "order123"}'
|
||||||
|
|
||||||
返回值:
|
|
||||||
bool: 成功返回 True,失败返回 False
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
data = json.loads(json_str)
|
data = json.loads(json_str)
|
||||||
@@ -782,126 +562,6 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return False
|
return False
|
||||||
return True
|
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:
|
def device_list(self, json_str: str = "") -> list:
|
||||||
@@ -933,13 +593,9 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return response.get("data", [])
|
return response.get("data", [])
|
||||||
|
|
||||||
def device_operation(self, json_str: str) -> int:
|
def device_operation(self, json_str: str) -> int:
|
||||||
"""设备操作
|
"""
|
||||||
|
描述:操作设备
|
||||||
参数:
|
json_str 格式为JSON字符串
|
||||||
json_str: JSON字符串,包含 device_no/operationType/operationParams
|
|
||||||
|
|
||||||
返回值:
|
|
||||||
int: 成功返回1,失败返回0
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
data = json.loads(json_str)
|
data = json.loads(json_str)
|
||||||
@@ -952,7 +608,7 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
response = self.post(
|
response = self.post(
|
||||||
url=f'{self.host}/api/lims/device/execute-operation',
|
url=f'{self.host}/api/lims/device/device-operation',
|
||||||
params={
|
params={
|
||||||
"apiKey": self.api_key,
|
"apiKey": self.api_key,
|
||||||
"requestTime": self.get_current_time_iso8601(),
|
"requestTime": self.get_current_time_iso8601(),
|
||||||
@@ -963,30 +619,9 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return 0
|
return 0
|
||||||
return response.get("code", 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:
|
def scheduler_status(self) -> dict:
|
||||||
"""查询调度器状态
|
|
||||||
|
|
||||||
返回值:
|
|
||||||
dict: 包含 schedulerStatus/hasTask/creationTime 等
|
|
||||||
"""
|
|
||||||
response = self.post(
|
response = self.post(
|
||||||
url=f'{self.host}/api/lims/scheduler/scheduler-status',
|
url=f'{self.host}/api/lims/scheduler/scheduler-status',
|
||||||
params={
|
params={
|
||||||
@@ -999,7 +634,7 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return response.get("data", {})
|
return response.get("data", {})
|
||||||
|
|
||||||
def scheduler_start(self) -> int:
|
def scheduler_start(self) -> int:
|
||||||
"""启动调度器"""
|
"""描述:启动调度器"""
|
||||||
response = self.post(
|
response = self.post(
|
||||||
url=f'{self.host}/api/lims/scheduler/start',
|
url=f'{self.host}/api/lims/scheduler/start',
|
||||||
params={
|
params={
|
||||||
@@ -1012,22 +647,9 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return response.get("code", 0)
|
return response.get("code", 0)
|
||||||
|
|
||||||
def scheduler_pause(self) -> int:
|
def scheduler_pause(self) -> int:
|
||||||
"""暂停调度器"""
|
"""描述:暂停调度器"""
|
||||||
response = self.post(
|
response = self.post(
|
||||||
url=f'{self.host}/api/lims/scheduler/pause',
|
url=f'{self.host}/api/lims/scheduler/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',
|
|
||||||
params={
|
params={
|
||||||
"apiKey": self.api_key,
|
"apiKey": self.api_key,
|
||||||
"requestTime": self.get_current_time_iso8601(),
|
"requestTime": self.get_current_time_iso8601(),
|
||||||
@@ -1038,9 +660,8 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return response.get("code", 0)
|
return response.get("code", 0)
|
||||||
|
|
||||||
def scheduler_continue(self) -> int:
|
def scheduler_continue(self) -> int:
|
||||||
"""继续调度器"""
|
|
||||||
response = self.post(
|
response = self.post(
|
||||||
url=f'{self.host}/api/lims/scheduler/continue',
|
url=f'{self.host}/api/lims/scheduler/scheduler-continue',
|
||||||
params={
|
params={
|
||||||
"apiKey": self.api_key,
|
"apiKey": self.api_key,
|
||||||
"requestTime": self.get_current_time_iso8601(),
|
"requestTime": self.get_current_time_iso8601(),
|
||||||
@@ -1051,9 +672,9 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return response.get("code", 0)
|
return response.get("code", 0)
|
||||||
|
|
||||||
def scheduler_stop(self) -> int:
|
def scheduler_stop(self) -> int:
|
||||||
"""停止调度器"""
|
"""描述:停止调度器"""
|
||||||
response = self.post(
|
response = self.post(
|
||||||
url=f'{self.host}/api/lims/scheduler/stop',
|
url=f'{self.host}/api/lims/scheduler/scheduler-stop',
|
||||||
params={
|
params={
|
||||||
"apiKey": self.api_key,
|
"apiKey": self.api_key,
|
||||||
"requestTime": self.get_current_time_iso8601(),
|
"requestTime": self.get_current_time_iso8601(),
|
||||||
@@ -1064,9 +685,9 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return response.get("code", 0)
|
return response.get("code", 0)
|
||||||
|
|
||||||
def scheduler_reset(self) -> int:
|
def scheduler_reset(self) -> int:
|
||||||
"""复位调度器"""
|
"""描述:重置调度器"""
|
||||||
response = self.post(
|
response = self.post(
|
||||||
url=f'{self.host}/api/lims/scheduler/reset',
|
url=f'{self.host}/api/lims/scheduler/scheduler-reset',
|
||||||
params={
|
params={
|
||||||
"apiKey": self.api_key,
|
"apiKey": self.api_key,
|
||||||
"requestTime": self.get_current_time_iso8601(),
|
"requestTime": self.get_current_time_iso8601(),
|
||||||
@@ -1076,36 +697,16 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
return 0
|
return 0
|
||||||
return response.get("code", 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):
|
def _load_material_cache(self):
|
||||||
"""预加载材料列表到缓存中"""
|
"""预加载材料列表到缓存中"""
|
||||||
try:
|
try:
|
||||||
print("正在加载材料列表缓存...")
|
print("正在加载材料列表缓存...")
|
||||||
|
|
||||||
# 加载所有类型的材料:耗材(0)、样品(1)、试剂(2)
|
# 加载所有类型的材料:耗材(0)、样品(1)、试剂(2)
|
||||||
material_types = [0, 1, 2]
|
material_types = [1, 2]
|
||||||
|
|
||||||
for type_mode in material_types:
|
for type_mode in material_types:
|
||||||
print(f"正在加载类型 {type_mode} 的材料...")
|
print(f"正在加载类型 {type_mode} 的材料...")
|
||||||
stock_query = f'{{"typeMode": {type_mode}, "includeDetail": true}}'
|
stock_query = f'{{"typeMode": {type_mode}, "includeDetail": true}}'
|
||||||
@@ -1122,7 +723,7 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
material_id = material.get("id")
|
material_id = material.get("id")
|
||||||
if material_name and material_id:
|
if material_name and material_id:
|
||||||
self.material_cache[material_name] = material_id
|
self.material_cache[material_name] = material_id
|
||||||
|
|
||||||
# 处理样品板等容器中的detail材料
|
# 处理样品板等容器中的detail材料
|
||||||
detail_materials = material.get("detail", [])
|
detail_materials = material.get("detail", [])
|
||||||
for detail_material in detail_materials:
|
for detail_material in detail_materials:
|
||||||
@@ -1148,14 +749,6 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
print(f"从缓存找到材料: {material_name_or_id} -> ID: {material_id}")
|
print(f"从缓存找到材料: {material_name_or_id} -> ID: {material_id}")
|
||||||
return material_id
|
return material_id
|
||||||
|
|
||||||
# 如果缓存中没有,尝试刷新缓存
|
|
||||||
print(f"缓存中未找到材料 '{material_name_or_id}',尝试刷新缓存...")
|
|
||||||
self.refresh_material_cache()
|
|
||||||
if material_name_or_id in self.material_cache:
|
|
||||||
material_id = self.material_cache[material_name_or_id]
|
|
||||||
print(f"刷新缓存后找到材料: {material_name_or_id} -> ID: {material_id}")
|
|
||||||
return material_id
|
|
||||||
|
|
||||||
print(f"警告: 未在缓存中找到材料名称 '{material_name_or_id}',将使用原值")
|
print(f"警告: 未在缓存中找到材料名称 '{material_name_or_id}',将使用原值")
|
||||||
return material_name_or_id
|
return material_name_or_id
|
||||||
|
|
||||||
@@ -1166,24 +759,4 @@ class BioyondV1RPC(BaseRequest):
|
|||||||
|
|
||||||
def get_available_materials(self):
|
def get_available_materials(self):
|
||||||
"""获取所有可用的材料名称列表"""
|
"""获取所有可用的材料名称列表"""
|
||||||
return list(self.material_cache.keys())
|
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)
|
|
||||||
@@ -2,141 +2,372 @@
|
|||||||
"""
|
"""
|
||||||
配置文件 - 包含所有配置信息和映射关系
|
配置文件 - 包含所有配置信息和映射关系
|
||||||
"""
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
# API配置
|
# ==================== API 基础配置 ====================
|
||||||
|
# BioyondCellWorkstation 默认配置(包含所有必需参数)
|
||||||
API_CONFIG = {
|
API_CONFIG = {
|
||||||
"api_key": "",
|
# API 连接配置
|
||||||
"api_host": ""
|
# 实机
|
||||||
}
|
#"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"),
|
||||||
WORKFLOW_MAPPINGS = {
|
"api_key": os.getenv("BIOYOND_API_KEY", "8A819E5C"),
|
||||||
"reactor_taken_out": "",
|
"timeout": int(os.getenv("BIOYOND_TIMEOUT", "30")),
|
||||||
"reactor_taken_in": "",
|
|
||||||
"Solid_feeding_vials": "",
|
# 报送配置
|
||||||
"Liquid_feeding_vials(non-titration)": "",
|
"report_token": os.getenv("BIOYOND_REPORT_TOKEN", "CHANGE_ME_TOKEN"),
|
||||||
"Liquid_feeding_solvents": "",
|
|
||||||
"Liquid_feeding(titration)": "",
|
# HTTP 服务配置
|
||||||
"liquid_feeding_beaker": "",
|
"HTTP_host": os.getenv("BIOYOND_HTTP_HOST", "172.16.11.206"), # HTTP服务监听地址,监听计算机飞连ip地址
|
||||||
"Drip_back": "",
|
"HTTP_port": int(os.getenv("BIOYOND_HTTP_PORT", "8080")),
|
||||||
}
|
"debug_mode": False,# 调试模式
|
||||||
|
|
||||||
# 工作流名称到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': '反应器取出'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 库位映射配置
|
# 库位映射配置
|
||||||
WAREHOUSE_MAPPING = {
|
WAREHOUSE_MAPPING = {
|
||||||
"粉末堆栈": {
|
"粉末加样头堆栈": {
|
||||||
"uuid": "",
|
"uuid": "",
|
||||||
"site_uuids": {
|
"site_uuids": {
|
||||||
# 样品板
|
"A01": "3a19da56-1379-ff7c-1745-07e200b44ce2",
|
||||||
"A1": "3a14198e-6929-31f0-8a22-0f98f72260df",
|
"B01": "3a19da56-1379-2424-d751-fe6e94cef938",
|
||||||
"A2": "3a14198e-6929-4379-affa-9a2935c17f99",
|
"C01": "3a19da56-1379-271c-03e3-6bdb590e395e",
|
||||||
"A3": "3a14198e-6929-56da-9a1c-7f5fbd4ae8af",
|
"D01": "3a19da56-1379-277f-2b1b-0d11f7cf92c6",
|
||||||
"A4": "3a14198e-6929-5e99-2b79-80720f7cfb54",
|
"E01": "3a19da56-1379-2f1c-a15b-e01db90eb39a",
|
||||||
"B1": "3a14198e-6929-f525-9a1b-1857552b28ee",
|
"F01": "3a19da56-1379-3fa1-846b-088158ac0b3d",
|
||||||
"B2": "3a14198e-6929-bf98-0fd5-26e1d68bf62d",
|
"G01": "3a19da56-1379-5aeb-d0cd-d3b4609d66e1",
|
||||||
"B3": "3a14198e-6929-2d86-a468-602175a2b5aa",
|
"H01": "3a19da56-1379-6077-8258-bdc036870b78",
|
||||||
"B4": "3a14198e-6929-1a98-ae57-e97660c489ad",
|
"I01": "3a19da56-1379-863b-a120-f606baf04617",
|
||||||
# 分装板
|
"J01": "3a19da56-1379-8a74-74e5-35a9b41d4fd5",
|
||||||
"C1": "3a14198e-6929-46fe-841e-03dd753f1e4a",
|
"K01": "3a19da56-1379-b270-b7af-f18773918abe",
|
||||||
"C2": "3a14198e-6929-1bc9-a9bd-3b7ca66e7f95",
|
"L01": "3a19da56-1379-ba54-6d78-fd770a671ffc",
|
||||||
"C3": "3a14198e-6929-72ac-32ce-9b50245682b8",
|
"M01": "3a19da56-1379-c22d-c96f-0ceb5eb54a04",
|
||||||
"C4": "3a14198e-6929-3bd8-e6c7-4a9fd93be118",
|
"N01": "3a19da56-1379-d64e-c6c5-c72ea4829888",
|
||||||
"D1": "3a14198e-6929-8a0b-b686-6f4a2955c4e2",
|
"O01": "3a19da56-1379-d887-1a3c-6f9cce90f90e",
|
||||||
"D2": "3a14198e-6929-dde1-fc78-34a84b71afdf",
|
"P01": "3a19da56-1379-e77d-0e65-7463b238a3b9",
|
||||||
"D3": "3a14198e-6929-a0ec-5f15-c0f9f339f963",
|
"Q01": "3a19da56-1379-edf6-1472-802ddb628774",
|
||||||
"D4": "3a14198e-6929-7ac8-915a-fea51cb2e884"
|
"R01": "3a19da56-1379-f281-0273-e0ef78f0fd97",
|
||||||
|
"S01": "3a19da56-1379-f924-7f68-df1fa51489f4",
|
||||||
|
"T01": "3a19da56-1379-ff7c-1745-07e200b44ce2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"溶液堆栈": {
|
"配液站内试剂仓库": {
|
||||||
"uuid": "",
|
"uuid": "",
|
||||||
"site_uuids": {
|
"site_uuids": {
|
||||||
"A1": "3a14198e-d724-e036-afdc-2ae39a7f3383",
|
"A01": "3a19da43-57b5-294f-d663-154a1cc32270",
|
||||||
"A2": "3a14198e-d724-afa4-fc82-0ac8a9016791",
|
"B01": "3a19da43-57b5-7394-5f49-54efe2c9bef2",
|
||||||
"A3": "3a14198e-d724-ca48-bb9e-7e85751e55b6",
|
"C01": "3a19da43-57b5-5e75-552f-8dbd0ad1075f",
|
||||||
"A4": "3a14198e-d724-df6d-5e32-5483b3cab583",
|
"A02": "3a19da43-57b5-8441-db94-b4d3875a4b6c",
|
||||||
"B1": "3a14198e-d724-d818-6d4f-5725191a24b5",
|
"B02": "3a19da43-57b5-3e41-c181-5119dddaf50c",
|
||||||
"B2": "3a14198e-d724-be8a-5e0b-012675e195c6",
|
"C02": "3a19da43-57b5-269b-282d-fba61fe8ce96",
|
||||||
"B3": "3a14198e-d724-cc1e-5c2c-228a130f40a8",
|
"A03": "3a19da43-57b5-7c1e-d02e-c40e8c33f8a1",
|
||||||
"B4": "3a14198e-d724-1e28-c885-574c3df468d0",
|
"B03": "3a19da43-57b5-659f-621f-1dcf3f640363",
|
||||||
"C1": "3a14198e-d724-b5bb-adf3-4c5a0da6fb31",
|
"C03": "3a19da43-57b5-855a-6e71-f398e376dee1",
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"试剂堆栈": {
|
"试剂替换仓库": {
|
||||||
"uuid": "",
|
"uuid": "",
|
||||||
"site_uuids": {
|
"site_uuids": {
|
||||||
"A1": "3a14198c-c2cf-8b40-af28-b467808f1c36",
|
"A01": "3a19da51-8f4e-30f3-ea08-4f8498e9b097",
|
||||||
"A2": "3a14198c-c2d0-f3e7-871a-e470d144296f",
|
"B01": "3a19da51-8f4e-1da7-beb0-80a4a01e67a8",
|
||||||
"A3": "3a14198c-c2d0-dc7d-b8d0-e1d88cee3094",
|
"C01": "3a19da51-8f4e-337d-2675-bfac46880b06",
|
||||||
"A4": "3a14198c-c2d0-2070-efc8-44e245f10c6f",
|
"D01": "3a19da51-8f4e-e514-b92c-9c44dc5e489d",
|
||||||
"B1": "3a14198c-c2d0-354f-39ad-642e1a72fcb8",
|
"E01": "3a19da51-8f4e-22d1-dd5b-9774ddc80402",
|
||||||
"B2": "3a14198c-c2d0-1559-105d-0ea30682cab4",
|
"F01": "3a19da51-8f4e-273a-4871-dff41c29bfd9",
|
||||||
"B3": "3a14198c-c2d0-725e-523d-34c037ac2440",
|
"G01": "3a19da51-8f4e-b32f-454f-74bc1a665653",
|
||||||
"B4": "3a14198c-c2d0-efce-0939-69ca5a7dfd39"
|
"H01": "3a19da51-8f4e-8c93-68c9-0b4382320f59",
|
||||||
|
"I01": "3a19da51-8f4e-360c-0149-291b47c6089b",
|
||||||
|
"J01": "3a19da51-8f4e-4152-9bca-8d64df8c1af0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"自动堆栈-左": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"A01": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
|
||||||
|
"A02": "3a19debc-84b5-033b-b31f-6b87f7c2bf52",
|
||||||
|
"B01": "3a19debc-84b5-3924-172f-719ab01b125c",
|
||||||
|
"B02": "3a19debc-84b5-aad8-70c6-b8c6bb2d8750"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"自动堆栈-右": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"A01": "3a19debe-5200-7df2-1dd9-7d202f158864",
|
||||||
|
"A02": "3a19debe-5200-573b-6120-8b51f50e1e50",
|
||||||
|
"B01": "3a19debe-5200-7cd8-7666-851b0a97e309",
|
||||||
|
"B02": "3a19debe-5200-e6d3-96a3-baa6e3d5e484"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"手动堆栈": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||||
|
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
|
||||||
|
"A03": "3a19deae-2c7a-5876-c454-6b7e224ca927",
|
||||||
|
"B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1",
|
||||||
|
"B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3",
|
||||||
|
"B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||||
|
"C01": "3a19deae-2c7a-32bc-768e-556647e292f3",
|
||||||
|
"C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4",
|
||||||
|
"C03": "3a19deae-2c7a-3056-6504-10dc73fbc276",
|
||||||
|
"D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440",
|
||||||
|
"D02": "3a19deae-2c7a-61be-601c-b6fb5610499a",
|
||||||
|
"D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560",
|
||||||
|
"E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363",
|
||||||
|
"E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910",
|
||||||
|
"E03": "3a19deae-2c7a-b163-2219-23df15200311",
|
||||||
|
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
|
||||||
|
"F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679",
|
||||||
|
"F03": "3a19deae-2c7a-f7c4-12bd-425799425698",
|
||||||
|
"G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955",
|
||||||
|
"G02": "3a19deae-2c7a-204e-95ed-1f1950f28343",
|
||||||
|
"G03": "3a19deae-2c7a-392b-62f1-4907c66343f8",
|
||||||
|
"H01": "3a19deae-2c7a-5602-e876-d27aca4e3201",
|
||||||
|
"H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702",
|
||||||
|
"H03": "3a19deae-2c7a-780b-8965-2e1345f7e834",
|
||||||
|
"I01": "3a19deae-2c7a-8849-e172-07de14ede928",
|
||||||
|
"I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0",
|
||||||
|
"I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4",
|
||||||
|
"J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6",
|
||||||
|
"J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205",
|
||||||
|
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"手动传递窗右": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
|
||||||
|
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
|
||||||
|
"A03": "3a19deae-2c7a-5876-c454-6b7e224ca927",
|
||||||
|
"B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1",
|
||||||
|
"B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3",
|
||||||
|
"B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
|
||||||
|
"C01": "3a19deae-2c7a-32bc-768e-556647e292f3",
|
||||||
|
"C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4",
|
||||||
|
"C03": "3a19deae-2c7a-3056-6504-10dc73fbc276",
|
||||||
|
"D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440",
|
||||||
|
"D02": "3a19deae-2c7a-61be-601c-b6fb5610499a",
|
||||||
|
"D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560",
|
||||||
|
"E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363",
|
||||||
|
"E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910",
|
||||||
|
"E03": "3a19deae-2c7a-b163-2219-23df15200311",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"手动传递窗左": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
|
||||||
|
"F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679",
|
||||||
|
"F03": "3a19deae-2c7a-f7c4-12bd-425799425698",
|
||||||
|
"G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955",
|
||||||
|
"G02": "3a19deae-2c7a-204e-95ed-1f1950f28343",
|
||||||
|
"G03": "3a19deae-2c7a-392b-62f1-4907c66343f8",
|
||||||
|
"H01": "3a19deae-2c7a-5602-e876-d27aca4e3201",
|
||||||
|
"H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702",
|
||||||
|
"H03": "3a19deae-2c7a-780b-8965-2e1345f7e834",
|
||||||
|
"I01": "3a19deae-2c7a-8849-e172-07de14ede928",
|
||||||
|
"I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0",
|
||||||
|
"I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4",
|
||||||
|
"J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6",
|
||||||
|
"J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205",
|
||||||
|
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"4号手套箱内部堆栈": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"A01": "3a1baa20-a7b1-c665-8b9c-d8099d07d2f6",
|
||||||
|
"A02": "3a1baa20-a7b1-93a7-c988-f9c8ad6c58c9",
|
||||||
|
"A03": "3a1baa20-a7b1-00ee-f751-da9b20b6c464",
|
||||||
|
"A04": "3a1baa20-a7b1-4712-c37b-0b5b658ef7b9",
|
||||||
|
"B01": "3a1baa20-a7b1-9847-fc9c-96d604cd1a8e",
|
||||||
|
"B02": "3a1baa20-a7b1-4ae9-e604-0601db06249c",
|
||||||
|
"B03": "3a1baa20-a7b1-8329-ea75-81ca559d9ce1",
|
||||||
|
"B04": "3a1baa20-a7b1-89c5-d96f-36e98a8f7268",
|
||||||
|
"C01": "3a1baa20-a7b1-32ec-39e6-8044733839d6",
|
||||||
|
"C02": "3a1baa20-a7b1-b573-e426-4c86040348b2",
|
||||||
|
"C03": "3a1baa20-a7b1-cca7-781e-0522b729bf5d",
|
||||||
|
"C04": "3a1baa20-a7b1-7c98-5fd9-5855355ae4b3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"大分液瓶堆栈": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"A01": "3a19da3d-4f3d-bcac-2932-7542041e10e0",
|
||||||
|
"A02": "3a19da3d-4f3d-4d75-38ac-fb58ad0687c3",
|
||||||
|
"A03": "3a19da3d-4f3d-b25e-f2b1-85342a5b7eae",
|
||||||
|
"B01": "3a19da3d-4f3d-fd3e-058a-2733a0925767",
|
||||||
|
"B02": "3a19da3d-4f3d-37bd-a944-c391ad56857f",
|
||||||
|
"B03": "3a19da3d-4f3d-e353-7862-c6d1d4bc667f",
|
||||||
|
"C01": "3a19da3d-4f3d-9519-5da7-76179c958e70",
|
||||||
|
"C02": "3a19da3d-4f3d-b586-d7ed-9ec244f6f937",
|
||||||
|
"C03": "3a19da3d-4f3d-5061-249b-35dfef732811"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"小分液瓶堆栈": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"C03": "3a19da40-55bf-8943-d20d-a8b3ea0d16c0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"站内Tip头盒堆栈": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"A01": "3a19deab-d5cc-be1e-5c37-4e9e5a664388",
|
||||||
|
"A02": "3a19deab-d5cc-b394-8141-27cb3853e8ea",
|
||||||
|
"B01": "3a19deab-d5cc-4dca-596e-ca7414d5f501",
|
||||||
|
"B02": "3a19deab-d5cc-9bc0-442b-12d9d59aa62a",
|
||||||
|
"C01": "3a19deab-d5cc-2eaf-b6a4-f0d54e4f1246",
|
||||||
|
"C02": "3a19deab-d5cc-d9f4-25df-b8018c372bc7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"配液站内配液大板仓库(无需提前上料)": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"A01": "3a1a21dc-06af-3915-9cb9-80a9dc42f386"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"配液站内配液小板仓库(无需以前入料)": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"A01": "3a1a21de-8e8b-7938-2d06-858b36c10e31"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"移液站内大瓶板仓库(无需提前如料)": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"A01": "3a1a224c-c727-fa62-1f2b-0037a84b9fca"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"移液站内小瓶板仓库(无需提前入料)": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"A01": "3a1a224d-ed49-710c-a9c3-3fc61d479cbb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"适配器位仓库": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"A01": "3a1abd46-18fe-1f56-6ced-a1f7fe08e36c"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1号2号手套箱交接堆栈": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"A01": "3a1baa49-7f77-35aa-60b1-e55a45d065fa"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"2号手套箱内部堆栈": {
|
||||||
|
"uuid": "",
|
||||||
|
"site_uuids": {
|
||||||
|
"A01": "3a1baa4b-393e-9f86-3921-7a18b0a8e371",
|
||||||
|
"A02": "3a1baa4b-393e-9425-928b-ee0f6f679d44",
|
||||||
|
"A03": "3a1baa4b-393e-0baf-632b-59dfdc931a3a",
|
||||||
|
"B01": "3a1baa4b-393e-f8aa-c8a9-956f3132f05c",
|
||||||
|
"B02": "3a1baa4b-393e-ef05-42f6-53f4c6e89d70",
|
||||||
|
"B03": "3a1baa4b-393e-c07b-a924-a9f0dfda9711",
|
||||||
|
"C01": "3a1baa4b-393e-4c2b-821a-16a7fe025c48",
|
||||||
|
"C02": "3a1baa4b-393e-2eaf-61a1-9063c832d5a2",
|
||||||
|
"C03": "3a1baa4b-393e-034e-8e28-8626d934a85f"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# 物料类型配置
|
# 物料类型配置
|
||||||
MATERIAL_TYPE_MAPPINGS = {
|
MATERIAL_TYPE_MAPPINGS = {
|
||||||
"烧杯": ("BIOYOND_PolymerStation_1FlaskCarrier", "3a14196b-24f2-ca49-9081-0cab8021bf1a"),
|
"100ml液体": ("YB_100ml_yeti", "d37166b3-ecaa-481e-bd84-3032b795ba07"),
|
||||||
"试剂瓶": ("BIOYOND_PolymerStation_1BottleCarrier", ""),
|
"液": ("YB_ye", "3a190ca1-2add-2b23-f8e1-bbd348b7f790"),
|
||||||
"样品板": ("BIOYOND_PolymerStation_6StockCarrier", "3a14196e-b7a0-a5da-1931-35f3000281e9"),
|
"高粘液": ("YB_gaonianye", "abe8df30-563d-43d2-85e0-cabec59ddc16"),
|
||||||
"分装板": ("BIOYOND_PolymerStation_6VialCarrier", "3a14196e-5dfe-6e21-0c79-fe2036d052c4"),
|
"加样头(大)": ("YB_jia_yang_tou_da_Carrier", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),
|
||||||
"样品瓶": ("BIOYOND_PolymerStation_Solid_Stock", "3a14196a-cf7d-8aea-48d8-b9662c7dba94"),
|
# "加样头(大)板": ("YB_jia_yang_tou_da", "a8e714ae-2a4e-4eb9-9614-e4c140ec3f16"),
|
||||||
"90%分装小瓶": ("BIOYOND_PolymerStation_Solid_Vial", "3a14196c-cdcf-088d-dc7d-5cf38f0ad9ea"),
|
"5ml分液瓶板": ("YB_5ml_fenyepingban", "3a192fa4-007d-ec7b-456e-2a8be7a13f23"),
|
||||||
"10%分装小瓶": ("BIOYOND_PolymerStation_Liquid_Vial", "3a14196c-76be-2279-4e22-7310d69aed68"),
|
"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)
|
SOLID_LIQUID_MAPPINGS = {
|
||||||
WORKFLOW_STEP_IDS = {
|
# 固体
|
||||||
"reactor_taken_in": {
|
"LiDFOB": {
|
||||||
"config": ""
|
"typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
||||||
|
"code": "",
|
||||||
|
"barCode": "",
|
||||||
|
"name": "LiDFOB",
|
||||||
|
"unit": "g",
|
||||||
|
"parameters": "",
|
||||||
|
"quantity": "2",
|
||||||
|
"warningQuantity": "1",
|
||||||
|
"details": []
|
||||||
},
|
},
|
||||||
"liquid_feeding_beaker": {
|
# "LiPF6": {
|
||||||
"liquid": "",
|
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
||||||
"observe": ""
|
# "code": "",
|
||||||
},
|
# "barCode": "",
|
||||||
"liquid_feeding_vials_non_titration": {
|
# "name": "LiPF6",
|
||||||
"liquid": "",
|
# "unit": "g",
|
||||||
"observe": ""
|
# "parameters": "",
|
||||||
},
|
# "quantity": 2,
|
||||||
"liquid_feeding_solvents": {
|
# "warningQuantity": 1,
|
||||||
"liquid": "",
|
# "details": []
|
||||||
"observe": ""
|
# },
|
||||||
},
|
# "LiFSI": {
|
||||||
"solid_feeding_vials": {
|
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
|
||||||
"feeding": "",
|
# "code": "",
|
||||||
"observe": ""
|
# "barCode": "",
|
||||||
},
|
# "name": "LiFSI",
|
||||||
"liquid_feeding_titration": {
|
# "unit": "g",
|
||||||
"liquid": "",
|
# "parameters": "",
|
||||||
"observe": ""
|
# "quantity": 2,
|
||||||
},
|
# "warningQuantity": 1,
|
||||||
"drip_back": {
|
# "details": []
|
||||||
"liquid": "",
|
# },
|
||||||
"observe": ""
|
# "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 = {}
|
LOCATION_MAPPING = {}
|
||||||
|
|
||||||
HTTP_SERVICE_CONFIG = {}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
|||||||
|
# Modbus CSV 地址映射说明
|
||||||
|
|
||||||
|
本文档说明 `coin_cell_assembly_a.csv` 文件如何将命名节点映射到实际的 Modbus 地址,以及如何在代码中使用它们。
|
||||||
|
|
||||||
|
## 1. CSV 文件结构
|
||||||
|
|
||||||
|
地址表文件位于同级目录下:`coin_cell_assembly_a.csv`
|
||||||
|
|
||||||
|
每一行定义了一个 Modbus 节点,包含以下关键列:
|
||||||
|
|
||||||
|
| 列名 | 说明 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| **Name** | **节点名称** (代码中引用的 Key) | `COIL_ALUMINUM_FOIL` |
|
||||||
|
| **DataType** | 数据类型 (BOOL, INT16, FLOAT32, STRING) | `BOOL` |
|
||||||
|
| **Comment** | 注释说明 | `使用铝箔垫` |
|
||||||
|
| **Attribute** | 属性 (通常留空或用于额外标记) | |
|
||||||
|
| **DeviceType** | Modbus 寄存器类型 (`coil`, `hold_register`) | `coil` |
|
||||||
|
| **Address** | **Modbus 地址** (十进制) | `8340` |
|
||||||
|
|
||||||
|
### 示例行 (铝箔垫片)
|
||||||
|
|
||||||
|
```csv
|
||||||
|
COIL_ALUMINUM_FOIL,BOOL,,使用铝箔垫,,coil,8340,
|
||||||
|
```
|
||||||
|
|
||||||
|
- **名称**: `COIL_ALUMINUM_FOIL`
|
||||||
|
- **类型**: `coil` (线圈,读写单个位)
|
||||||
|
- **地址**: `8340`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 加载与注册流程
|
||||||
|
|
||||||
|
在 `coin_cell_assembly.py` 的初始化代码中:
|
||||||
|
|
||||||
|
1. **加载 CSV**: `BaseClient.load_csv()` 读取 CSV 并解析每行定义。
|
||||||
|
2. **注册节点**: `modbus_client.register_node_list()` 将解析后的节点注册到 Modbus 客户端实例中。
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 代码位置: coin_cell_assembly.py (L174-175)
|
||||||
|
self.nodes = BaseClient.load_csv(os.path.join(os.path.dirname(__file__), 'coin_cell_assembly_a.csv'))
|
||||||
|
self.client = modbus_client.register_node_list(self.nodes)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 代码中的使用方式
|
||||||
|
|
||||||
|
注册后,通过 `self.client.use_node('节点名称')` 即可获取该节点对象并进行读写操作,无需关心具体地址。
|
||||||
|
|
||||||
|
### 控制铝箔垫片 (COIL_ALUMINUM_FOIL)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 代码位置: qiming_coin_cell_code 函数 (L1048)
|
||||||
|
self.client.use_node('COIL_ALUMINUM_FOIL').write(not lvbodian)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **写入 True**: 对应 Modbus 功能码 05 (Write Single Coil),向地址 `8340` 写入 `1` (ON)。
|
||||||
|
- **写入 False**: 向地址 `8340` 写入 `0` (OFF)。
|
||||||
|
|
||||||
|
> **注意**: 代码中使用了 `not lvbodian`,这意味着逻辑是反转的。如果 `lvbodian` 参数为 `True` (默认),写入的是 `False` (不使用铝箔垫)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 地址转换注意事项 (Modbus vs PLC)
|
||||||
|
|
||||||
|
CSV 中的 `Address` 列(如 `8340`)是 **Modbus 协议地址**。
|
||||||
|
|
||||||
|
如果使用 InoProShop (汇川 PLC 编程软件),看到的可能是 **PLC 内部地址** (如 `%QX...` 或 `%MW...`)。这两者之间通常需要转换。
|
||||||
|
|
||||||
|
### 常见的转换规则 (示例)
|
||||||
|
|
||||||
|
- **Coil (线圈) %QX**:
|
||||||
|
- `Modbus地址 = 字节地址 * 8 + 位偏移`
|
||||||
|
- *例子*: `%QX834.0` -> `834 * 8 + 0` = `6672`
|
||||||
|
- *注意*: 如果 CSV 中配置的是 `8340`,这可能是一个自定义映射,或者是基于不同规则(如直接对应 Word 地址的某种映射,或者可能就是地址写错了/使用了非标准映射)。
|
||||||
|
|
||||||
|
- **Register (寄存器) %MW**:
|
||||||
|
- 通常直接对应,或者有偏移量 (如 Modbus 40001 = PLC MW0)。
|
||||||
|
|
||||||
|
### 验证方法
|
||||||
|
由于 `test_unilab_interact.py` 中发现 `8450` (CSV风格) 不工作,而 `6760` (%QX845.0 计算值) 工作正常,**建议对 CSV 中的其他地址也进行核实**,特别是像 `8340` 这样以 0 结尾看起来像是 "字节地址+0" 的数值,可能实际上应该是 `%QX834.0` 对应的 `6672`。
|
||||||
|
|
||||||
|
如果发现设备控制无反应,请尝试按照标准的 Modbus 计算方式转换 PLC 地址。
|
||||||
@@ -0,0 +1,645 @@
|
|||||||
|
"""
|
||||||
|
纽扣电池组装工作站物料类定义
|
||||||
|
Button Battery Assembly Station Resource Classes
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
from typing import Any, Dict, List, Optional, TypedDict, Union, cast
|
||||||
|
|
||||||
|
from pylabrobot.resources.coordinate import Coordinate
|
||||||
|
from pylabrobot.resources.container import Container
|
||||||
|
from pylabrobot.resources.deck import Deck
|
||||||
|
from pylabrobot.resources.itemized_resource import ItemizedResource
|
||||||
|
from pylabrobot.resources.resource import Resource
|
||||||
|
from pylabrobot.resources.resource_stack import ResourceStack
|
||||||
|
from pylabrobot.resources.tip_rack import TipRack, TipSpot
|
||||||
|
from pylabrobot.resources.trash import Trash
|
||||||
|
from pylabrobot.resources.utils import create_ordered_items_2d
|
||||||
|
|
||||||
|
from unilabos.resources.battery.magazine import MagazineHolder_4_Cathode, MagazineHolder_6_Cathode, MagazineHolder_6_Anode, MagazineHolder_6_Battery
|
||||||
|
from unilabos.resources.battery.bottle_carriers import YIHUA_Electrolyte_12VialCarrier
|
||||||
|
from unilabos.resources.battery.electrode_sheet import ElectrodeSheet
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: 这个应该只能放一个极片
|
||||||
|
class MaterialHoleState(TypedDict):
|
||||||
|
diameter: int
|
||||||
|
depth: int
|
||||||
|
max_sheets: int
|
||||||
|
info: Optional[str] # 附加信息
|
||||||
|
|
||||||
|
class MaterialHole(Resource):
|
||||||
|
"""料板洞位类"""
|
||||||
|
children: List[ElectrodeSheet] = []
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
size_x: float,
|
||||||
|
size_y: float,
|
||||||
|
size_z: float,
|
||||||
|
category: str = "material_hole",
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
size_x=size_x,
|
||||||
|
size_y=size_y,
|
||||||
|
size_z=size_z,
|
||||||
|
category=category,
|
||||||
|
)
|
||||||
|
self._unilabos_state: MaterialHoleState = MaterialHoleState(
|
||||||
|
diameter=20,
|
||||||
|
depth=10,
|
||||||
|
max_sheets=1,
|
||||||
|
info=None
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_all_sheet_info(self):
|
||||||
|
info_list = []
|
||||||
|
for sheet in self.children:
|
||||||
|
info_list.append(sheet._unilabos_state["info"])
|
||||||
|
return info_list
|
||||||
|
|
||||||
|
#这个函数函数好像没用,一般不会集中赋值质量
|
||||||
|
def set_all_sheet_mass(self):
|
||||||
|
for sheet in self.children:
|
||||||
|
sheet._unilabos_state["mass"] = 0.5 # 示例:设置质量为0.5g
|
||||||
|
|
||||||
|
def load_state(self, state: Dict[str, Any]) -> None:
|
||||||
|
"""格式不变"""
|
||||||
|
super().load_state(state)
|
||||||
|
self._unilabos_state = state
|
||||||
|
|
||||||
|
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""格式不变"""
|
||||||
|
data = super().serialize_state()
|
||||||
|
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
||||||
|
return data
|
||||||
|
#移动极片前先取出对象
|
||||||
|
def get_sheet_with_name(self, name: str) -> Optional[ElectrodeSheet]:
|
||||||
|
for sheet in self.children:
|
||||||
|
if sheet.name == name:
|
||||||
|
return sheet
|
||||||
|
return None
|
||||||
|
|
||||||
|
def has_electrode_sheet(self) -> bool:
|
||||||
|
"""检查洞位是否有极片"""
|
||||||
|
return len(self.children) > 0
|
||||||
|
|
||||||
|
def assign_child_resource(
|
||||||
|
self,
|
||||||
|
resource: ElectrodeSheet,
|
||||||
|
location: Optional[Coordinate],
|
||||||
|
reassign: bool = True,
|
||||||
|
):
|
||||||
|
"""放置极片"""
|
||||||
|
# TODO: 这里要改,diameter找不到,加入._unilabos_state后应该没问题
|
||||||
|
#if resource._unilabos_state["diameter"] > self._unilabos_state["diameter"]:
|
||||||
|
# raise ValueError(f"极片直径 {resource._unilabos_state['diameter']} 超过洞位直径 {self._unilabos_state['diameter']}")
|
||||||
|
#if len(self.children) >= self._unilabos_state["max_sheets"]:
|
||||||
|
# raise ValueError(f"洞位已满,无法放置更多极片")
|
||||||
|
super().assign_child_resource(resource, location, reassign)
|
||||||
|
|
||||||
|
# 根据children的编号取物料对象。
|
||||||
|
def get_electrode_sheet_info(self, index: int) -> ElectrodeSheet:
|
||||||
|
return self.children[index]
|
||||||
|
|
||||||
|
|
||||||
|
class MaterialPlateState(TypedDict):
|
||||||
|
hole_spacing_x: float
|
||||||
|
hole_spacing_y: float
|
||||||
|
hole_diameter: float
|
||||||
|
info: Optional[str] # 附加信息
|
||||||
|
|
||||||
|
class MaterialPlate(ItemizedResource[MaterialHole]):
|
||||||
|
"""料板类 - 4x4个洞位,每个洞位放1个极片"""
|
||||||
|
|
||||||
|
children: List[MaterialHole]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
size_x: float,
|
||||||
|
size_y: float,
|
||||||
|
size_z: float,
|
||||||
|
ordered_items: Optional[Dict[str, MaterialHole]] = None,
|
||||||
|
ordering: Optional[OrderedDict[str, str]] = None,
|
||||||
|
category: str = "material_plate",
|
||||||
|
model: Optional[str] = None,
|
||||||
|
fill: bool = False
|
||||||
|
):
|
||||||
|
"""初始化料板
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 料板名称
|
||||||
|
size_x: 长度 (mm)
|
||||||
|
size_y: 宽度 (mm)
|
||||||
|
size_z: 高度 (mm)
|
||||||
|
hole_diameter: 洞直径 (mm)
|
||||||
|
hole_depth: 洞深度 (mm)
|
||||||
|
hole_spacing_x: X方向洞位间距 (mm)
|
||||||
|
hole_spacing_y: Y方向洞位间距 (mm)
|
||||||
|
number: 编号
|
||||||
|
category: 类别
|
||||||
|
model: 型号
|
||||||
|
"""
|
||||||
|
self._unilabos_state: MaterialPlateState = MaterialPlateState(
|
||||||
|
hole_spacing_x=24.0,
|
||||||
|
hole_spacing_y=24.0,
|
||||||
|
hole_diameter=20.0,
|
||||||
|
info="",
|
||||||
|
)
|
||||||
|
# 创建4x4的洞位
|
||||||
|
# TODO: 这里要改,对应不同形状
|
||||||
|
holes = create_ordered_items_2d(
|
||||||
|
klass=MaterialHole,
|
||||||
|
num_items_x=4,
|
||||||
|
num_items_y=4,
|
||||||
|
dx=(size_x - 4 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中
|
||||||
|
dy=(size_y - 4 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中
|
||||||
|
dz=size_z,
|
||||||
|
item_dx=self._unilabos_state["hole_spacing_x"],
|
||||||
|
item_dy=self._unilabos_state["hole_spacing_y"],
|
||||||
|
size_x = 16,
|
||||||
|
size_y = 16,
|
||||||
|
size_z = 16,
|
||||||
|
)
|
||||||
|
if fill:
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
size_x=size_x,
|
||||||
|
size_y=size_y,
|
||||||
|
size_z=size_z,
|
||||||
|
ordered_items=holes,
|
||||||
|
category=category,
|
||||||
|
model=model,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
size_x=size_x,
|
||||||
|
size_y=size_y,
|
||||||
|
size_z=size_z,
|
||||||
|
ordered_items=ordered_items,
|
||||||
|
ordering=ordering,
|
||||||
|
category=category,
|
||||||
|
model=model,
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_locations(self):
|
||||||
|
# TODO:调多次相加
|
||||||
|
holes = create_ordered_items_2d(
|
||||||
|
klass=MaterialHole,
|
||||||
|
num_items_x=4,
|
||||||
|
num_items_y=4,
|
||||||
|
dx=(self._size_x - 3 * self._unilabos_state["hole_spacing_x"]) / 2, # 居中
|
||||||
|
dy=(self._size_y - 3 * self._unilabos_state["hole_spacing_y"]) / 2, # 居中
|
||||||
|
dz=self._size_z,
|
||||||
|
item_dx=self._unilabos_state["hole_spacing_x"],
|
||||||
|
item_dy=self._unilabos_state["hole_spacing_y"],
|
||||||
|
size_x = 1,
|
||||||
|
size_y = 1,
|
||||||
|
size_z = 1,
|
||||||
|
)
|
||||||
|
for item, original_item in zip(holes.items(), self.children):
|
||||||
|
original_item.location = item[1].location
|
||||||
|
|
||||||
|
|
||||||
|
class PlateSlot(ResourceStack):
|
||||||
|
"""板槽位类 - 1个槽上能堆放8个板,移板只能操作最上方的板"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
size_x: float,
|
||||||
|
size_y: float,
|
||||||
|
size_z: float,
|
||||||
|
max_plates: int = 8,
|
||||||
|
category: str = "plate_slot",
|
||||||
|
model: Optional[str] = None
|
||||||
|
):
|
||||||
|
"""初始化板槽位
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 槽位名称
|
||||||
|
max_plates: 最大板数量
|
||||||
|
category: 类别
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
direction="z", # Z方向堆叠
|
||||||
|
resources=[],
|
||||||
|
)
|
||||||
|
self.max_plates = max_plates
|
||||||
|
self.category = category
|
||||||
|
|
||||||
|
def can_add_plate(self) -> bool:
|
||||||
|
"""检查是否可以添加板"""
|
||||||
|
return len(self.children) < self.max_plates
|
||||||
|
|
||||||
|
def add_plate(self, plate: MaterialPlate) -> None:
|
||||||
|
"""添加料板"""
|
||||||
|
if not self.can_add_plate():
|
||||||
|
raise ValueError(f"槽位 {self.name} 已满,无法添加更多板")
|
||||||
|
self.assign_child_resource(plate)
|
||||||
|
|
||||||
|
def get_top_plate(self) -> MaterialPlate:
|
||||||
|
"""获取最上方的板"""
|
||||||
|
if len(self.children) == 0:
|
||||||
|
raise ValueError(f"槽位 {self.name} 为空")
|
||||||
|
return cast(MaterialPlate, self.get_top_item())
|
||||||
|
|
||||||
|
def take_top_plate(self) -> MaterialPlate:
|
||||||
|
"""取出最上方的板"""
|
||||||
|
top_plate = self.get_top_plate()
|
||||||
|
self.unassign_child_resource(top_plate)
|
||||||
|
return top_plate
|
||||||
|
|
||||||
|
def can_access_for_picking(self) -> bool:
|
||||||
|
"""检查是否可以进行取料操作(只有最上方的板能进行取料操作)"""
|
||||||
|
return len(self.children) > 0
|
||||||
|
|
||||||
|
def serialize(self) -> dict:
|
||||||
|
return {
|
||||||
|
**super().serialize(),
|
||||||
|
"max_plates": self.max_plates,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#是一种类型注解,不用self
|
||||||
|
class BatteryState(TypedDict):
|
||||||
|
"""电池状态字典"""
|
||||||
|
diameter: float
|
||||||
|
height: float
|
||||||
|
assembly_pressure: float
|
||||||
|
electrolyte_volume: float
|
||||||
|
electrolyte_name: str
|
||||||
|
|
||||||
|
class Battery(Resource):
|
||||||
|
"""电池类 - 可容纳极片"""
|
||||||
|
children: List[ElectrodeSheet] = []
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
size_x=1,
|
||||||
|
size_y=1,
|
||||||
|
size_z=1,
|
||||||
|
category: str = "battery",
|
||||||
|
):
|
||||||
|
"""初始化电池
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 电池名称
|
||||||
|
diameter: 直径 (mm)
|
||||||
|
height: 高度 (mm)
|
||||||
|
max_volume: 最大容量 (μL)
|
||||||
|
barcode: 二维码编号
|
||||||
|
category: 类别
|
||||||
|
model: 型号
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
size_x=1,
|
||||||
|
size_y=1,
|
||||||
|
size_z=1,
|
||||||
|
category=category,
|
||||||
|
)
|
||||||
|
self._unilabos_state: BatteryState = BatteryState(
|
||||||
|
diameter = 1.0,
|
||||||
|
height = 1.0,
|
||||||
|
assembly_pressure = 1.0,
|
||||||
|
electrolyte_volume = 1.0,
|
||||||
|
electrolyte_name = "DP001"
|
||||||
|
)
|
||||||
|
|
||||||
|
def add_electrolyte_with_bottle(self, bottle: Bottle) -> bool:
|
||||||
|
to_add_name = bottle._unilabos_state["electrolyte_name"]
|
||||||
|
if bottle.aspirate_electrolyte(10):
|
||||||
|
if self.add_electrolyte(to_add_name, 10):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
bottle._unilabos_state["electrolyte_volume"] += 10
|
||||||
|
|
||||||
|
def set_electrolyte(self, name: str, volume: float) -> None:
|
||||||
|
"""设置电解液信息"""
|
||||||
|
self._unilabos_state["electrolyte_name"] = name
|
||||||
|
self._unilabos_state["electrolyte_volume"] = volume
|
||||||
|
#这个应该没用,不会有加了后再加的事情
|
||||||
|
def add_electrolyte(self, name: str, volume: float) -> bool:
|
||||||
|
"""添加电解液信息"""
|
||||||
|
if name != self._unilabos_state["electrolyte_name"]:
|
||||||
|
return False
|
||||||
|
self._unilabos_state["electrolyte_volume"] += volume
|
||||||
|
|
||||||
|
def load_state(self, state: Dict[str, Any]) -> None:
|
||||||
|
"""格式不变"""
|
||||||
|
super().load_state(state)
|
||||||
|
self._unilabos_state = state
|
||||||
|
|
||||||
|
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""格式不变"""
|
||||||
|
data = super().serialize_state()
|
||||||
|
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
||||||
|
return data
|
||||||
|
|
||||||
|
# 电解液作为属性放进去
|
||||||
|
|
||||||
|
class BatteryPressSlotState(TypedDict):
|
||||||
|
"""电池状态字典"""
|
||||||
|
diameter: float =20.0
|
||||||
|
depth: float = 4.0
|
||||||
|
|
||||||
|
class BatteryPressSlot(Resource):
|
||||||
|
"""电池压制槽类 - 设备,可容纳一个电池"""
|
||||||
|
children: List[Battery] = []
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str = "BatteryPressSlot",
|
||||||
|
category: str = "battery_press_slot",
|
||||||
|
):
|
||||||
|
"""初始化电池压制槽
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 压制槽名称
|
||||||
|
diameter: 直径 (mm)
|
||||||
|
depth: 深度 (mm)
|
||||||
|
category: 类别
|
||||||
|
model: 型号
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
size_x=10,
|
||||||
|
size_y=12,
|
||||||
|
size_z=13,
|
||||||
|
category=category,
|
||||||
|
)
|
||||||
|
self._unilabos_state: BatteryPressSlotState = BatteryPressSlotState()
|
||||||
|
|
||||||
|
def has_battery(self) -> bool:
|
||||||
|
"""检查是否有电池"""
|
||||||
|
return len(self.children) > 0
|
||||||
|
|
||||||
|
def load_state(self, state: Dict[str, Any]) -> None:
|
||||||
|
"""格式不变"""
|
||||||
|
super().load_state(state)
|
||||||
|
self._unilabos_state = state
|
||||||
|
|
||||||
|
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""格式不变"""
|
||||||
|
data = super().serialize_state()
|
||||||
|
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def assign_child_resource(
|
||||||
|
self,
|
||||||
|
resource: Battery,
|
||||||
|
location: Optional[Coordinate],
|
||||||
|
reassign: bool = True,
|
||||||
|
):
|
||||||
|
"""放置极片"""
|
||||||
|
# TODO: 让高京看下槽位只有一个电池时是否这么写。
|
||||||
|
if self.has_battery():
|
||||||
|
raise ValueError(f"槽位已含有一个电池,无法再放置其他电池")
|
||||||
|
super().assign_child_resource(resource, location, reassign)
|
||||||
|
|
||||||
|
# 根据children的编号取物料对象。
|
||||||
|
def get_battery_info(self, index: int) -> Battery:
|
||||||
|
return self.children[0]
|
||||||
|
|
||||||
|
|
||||||
|
def TipBox64(
|
||||||
|
name: str,
|
||||||
|
size_x: float = 127.8,
|
||||||
|
size_y: float = 85.5,
|
||||||
|
size_z: float = 60.0,
|
||||||
|
category: str = "tip_rack",
|
||||||
|
model: Optional[str] = None,
|
||||||
|
):
|
||||||
|
"""64孔枪头盒类"""
|
||||||
|
from pylabrobot.resources.tip import Tip
|
||||||
|
|
||||||
|
# 创建12x8=96个枪头位
|
||||||
|
def make_tip():
|
||||||
|
return Tip(
|
||||||
|
has_filter=False,
|
||||||
|
total_tip_length=20.0,
|
||||||
|
maximal_volume=1000, # 1mL
|
||||||
|
fitting_depth=8.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
tip_spots = create_ordered_items_2d(
|
||||||
|
klass=TipSpot,
|
||||||
|
num_items_x=12,
|
||||||
|
num_items_y=8,
|
||||||
|
dx=8.0,
|
||||||
|
dy=8.0,
|
||||||
|
dz=0.0,
|
||||||
|
item_dx=9.0,
|
||||||
|
item_dy=9.0,
|
||||||
|
size_x=10,
|
||||||
|
size_y=10,
|
||||||
|
size_z=0.0,
|
||||||
|
make_tip=make_tip,
|
||||||
|
)
|
||||||
|
idx_available = list(range(0, 32)) + list(range(64, 96))
|
||||||
|
tip_spots_available = {k: v for i, (k, v) in enumerate(tip_spots.items()) if i in idx_available}
|
||||||
|
tip_rack = TipRack(
|
||||||
|
name=name,
|
||||||
|
size_x=size_x,
|
||||||
|
size_y=size_y,
|
||||||
|
size_z=size_z,
|
||||||
|
# ordered_items=tip_spots_available,
|
||||||
|
ordered_items=tip_spots,
|
||||||
|
category=category,
|
||||||
|
model=model,
|
||||||
|
with_tips=False,
|
||||||
|
)
|
||||||
|
tip_rack.set_tip_state([True]*32 + [False]*32 + [True]*32) # 前32和后32个有枪头,中间32个无枪头
|
||||||
|
return tip_rack
|
||||||
|
|
||||||
|
|
||||||
|
class WasteTipBoxstate(TypedDict):
|
||||||
|
""""废枪头盒状态字典"""
|
||||||
|
max_tips: int = 100
|
||||||
|
tip_count: int = 0
|
||||||
|
|
||||||
|
#枪头不是一次性的(同一溶液则反复使用),根据寄存器判断
|
||||||
|
class WasteTipBox(Trash):
|
||||||
|
"""废枪头盒类 - 100个枪头容量"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
size_x: float = 127.8,
|
||||||
|
size_y: float = 85.5,
|
||||||
|
size_z: float = 60.0,
|
||||||
|
material_z_thickness=0,
|
||||||
|
max_volume=float("inf"),
|
||||||
|
category="trash",
|
||||||
|
model=None,
|
||||||
|
compute_volume_from_height=None,
|
||||||
|
compute_height_from_volume=None,
|
||||||
|
):
|
||||||
|
"""初始化废枪头盒
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 废枪头盒名称
|
||||||
|
size_x: 长度 (mm)
|
||||||
|
size_y: 宽度 (mm)
|
||||||
|
size_z: 高度 (mm)
|
||||||
|
max_tips: 最大枪头容量
|
||||||
|
category: 类别
|
||||||
|
model: 型号
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
size_x=size_x,
|
||||||
|
size_y=size_y,
|
||||||
|
size_z=size_z,
|
||||||
|
category=category,
|
||||||
|
model=model,
|
||||||
|
)
|
||||||
|
self._unilabos_state: WasteTipBoxstate = WasteTipBoxstate()
|
||||||
|
|
||||||
|
def add_tip(self) -> None:
|
||||||
|
"""添加废枪头"""
|
||||||
|
if self._unilabos_state["tip_count"] >= self._unilabos_state["max_tips"]:
|
||||||
|
raise ValueError(f"废枪头盒 {self.name} 已满")
|
||||||
|
self._unilabos_state["tip_count"] += 1
|
||||||
|
|
||||||
|
def get_tip_count(self) -> int:
|
||||||
|
"""获取枪头数量"""
|
||||||
|
return self._unilabos_state["tip_count"]
|
||||||
|
|
||||||
|
def empty(self) -> None:
|
||||||
|
"""清空废枪头盒"""
|
||||||
|
self._unilabos_state["tip_count"] = 0
|
||||||
|
|
||||||
|
|
||||||
|
def load_state(self, state: Dict[str, Any]) -> None:
|
||||||
|
"""格式不变"""
|
||||||
|
super().load_state(state)
|
||||||
|
self._unilabos_state = state
|
||||||
|
|
||||||
|
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
|
||||||
|
"""格式不变"""
|
||||||
|
data = super().serialize_state()
|
||||||
|
data.update(self._unilabos_state) # Container自身的信息,云端物料将保存这一data,本地也通过这里的data进行读写,当前类用来表示这个物料的长宽高大小的属性,而data(state用来表示物料的内容,细节等)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class CoincellDeck(Deck):
|
||||||
|
"""纽扣电池组装工作站台面类"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str = "coin_cell_deck",
|
||||||
|
size_x: float = 1450.0, # 1m
|
||||||
|
size_y: float = 1450.0, # 1m
|
||||||
|
size_z: float = 100.0, # 0.9m
|
||||||
|
origin: Coordinate = Coordinate(-2200, 0, 0),
|
||||||
|
category: str = "coin_cell_deck",
|
||||||
|
setup: bool = False, # 是否自动执行 setup
|
||||||
|
):
|
||||||
|
"""初始化纽扣电池组装工作站台面
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: 台面名称
|
||||||
|
size_x: 长度 (mm) - 1m
|
||||||
|
size_y: 宽度 (mm) - 1m
|
||||||
|
size_z: 高度 (mm) - 0.9m
|
||||||
|
origin: 原点坐标
|
||||||
|
category: 类别
|
||||||
|
setup: 是否自动执行 setup 配置标准布局
|
||||||
|
"""
|
||||||
|
super().__init__(
|
||||||
|
name=name,
|
||||||
|
size_x=1450.0,
|
||||||
|
size_y=1450.0,
|
||||||
|
size_z=100.0,
|
||||||
|
origin=origin,
|
||||||
|
)
|
||||||
|
if setup:
|
||||||
|
self.setup()
|
||||||
|
|
||||||
|
def setup(self) -> None:
|
||||||
|
"""设置工作站的标准布局 - 包含子弹夹、料盘、瓶架等完整配置"""
|
||||||
|
# ====================================== 子弹夹 ============================================
|
||||||
|
|
||||||
|
# 正极片(4个洞位,2x2布局)
|
||||||
|
zhengji_zip = MagazineHolder_4_Cathode("正极&铝箔弹夹")
|
||||||
|
self.assign_child_resource(zhengji_zip, Coordinate(x=402.0, y=830.0, z=0))
|
||||||
|
|
||||||
|
# 正极壳、平垫片(6个洞位,2x2+2布局)
|
||||||
|
zhengjike_zip = MagazineHolder_6_Cathode("正极壳&平垫片弹夹")
|
||||||
|
self.assign_child_resource(zhengjike_zip, Coordinate(x=566.0, y=272.0, z=0))
|
||||||
|
|
||||||
|
# 负极壳、弹垫片(6个洞位,2x2+2布局)
|
||||||
|
fujike_zip = MagazineHolder_6_Anode("负极壳&弹垫片弹夹")
|
||||||
|
self.assign_child_resource(fujike_zip, Coordinate(x=474.0, y=276.0, z=0))
|
||||||
|
|
||||||
|
# 成品弹夹(6个洞位,3x2布局)
|
||||||
|
chengpindanjia_zip = MagazineHolder_6_Battery("成品弹夹")
|
||||||
|
self.assign_child_resource(chengpindanjia_zip, Coordinate(x=260.0, y=156.0, z=0))
|
||||||
|
|
||||||
|
# ====================================== 物料板 ============================================
|
||||||
|
# 创建物料板(料盘carrier)- 4x4布局
|
||||||
|
# 负极料盘
|
||||||
|
fujiliaopan = MaterialPlate(name="负极料盘", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||||
|
self.assign_child_resource(fujiliaopan, Coordinate(x=708.0, y=794.0, z=0))
|
||||||
|
# for i in range(16):
|
||||||
|
# fujipian = ElectrodeSheet(name=f"{fujiliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
# fujiliaopan.children[i].assign_child_resource(fujipian, location=None)
|
||||||
|
|
||||||
|
# 隔膜料盘
|
||||||
|
gemoliaopan = MaterialPlate(name="隔膜料盘", size_x=120, size_y=100, size_z=10.0, fill=True)
|
||||||
|
self.assign_child_resource(gemoliaopan, Coordinate(x=718.0, y=918.0, z=0))
|
||||||
|
# for i in range(16):
|
||||||
|
# gemopian = ElectrodeSheet(name=f"{gemoliaopan.name}_jipian_{i}", size_x=12, size_y=12, size_z=0.1)
|
||||||
|
# gemoliaopan.children[i].assign_child_resource(gemopian, location=None)
|
||||||
|
|
||||||
|
# ====================================== 瓶架、移液枪 ============================================
|
||||||
|
# 在台面上放置 3x4 瓶架、6x2 瓶架 与 64孔移液枪头盒
|
||||||
|
# 奔耀上料5ml分液瓶小板 - 由奔曜跨站转运而来,不单独写,但是这里应该有一个堆栈用于摆放分液瓶小板
|
||||||
|
|
||||||
|
# bottle_rack_3x4 = BottleRack(
|
||||||
|
# name="bottle_rack_3x4",
|
||||||
|
# size_x=210.0,
|
||||||
|
# size_y=140.0,
|
||||||
|
# size_z=100.0,
|
||||||
|
# num_items_x=2,
|
||||||
|
# num_items_y=4,
|
||||||
|
# position_spacing=35.0,
|
||||||
|
# orientation="vertical",
|
||||||
|
# )
|
||||||
|
# self.assign_child_resource(bottle_rack_3x4, Coordinate(x=1542.0, y=717.0, z=0))
|
||||||
|
|
||||||
|
# 电解液缓存位 - 6x2布局
|
||||||
|
bottle_rack_6x2 = YIHUA_Electrolyte_12VialCarrier(name="bottle_rack_6x2")
|
||||||
|
self.assign_child_resource(bottle_rack_6x2, Coordinate(x=1050.0, y=358.0, z=0))
|
||||||
|
# 电解液回收位6x2
|
||||||
|
bottle_rack_6x2_2 = YIHUA_Electrolyte_12VialCarrier(name="bottle_rack_6x2_2")
|
||||||
|
self.assign_child_resource(bottle_rack_6x2_2, Coordinate(x=914.0, y=358.0, z=0))
|
||||||
|
|
||||||
|
tip_box = TipBox64(name="tip_box_64")
|
||||||
|
self.assign_child_resource(tip_box, Coordinate(x=782.0, y=514.0, z=0))
|
||||||
|
|
||||||
|
waste_tip_box = WasteTipBox(name="waste_tip_box")
|
||||||
|
self.assign_child_resource(waste_tip_box, Coordinate(x=778.0, y=622.0, z=0))
|
||||||
|
|
||||||
|
|
||||||
|
def YH_Deck(name=""):
|
||||||
|
cd = CoincellDeck(name=name)
|
||||||
|
cd.setup()
|
||||||
|
return cd
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
deck = create_coin_cell_deck()
|
||||||
|
print(deck)
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,130 @@
|
|||||||
|
Name,DataType,InitValue,Comment,Attribute,DeviceType,Address,
|
||||||
|
COIL_SYS_START_CMD,BOOL,,,,coil,8010,
|
||||||
|
COIL_SYS_STOP_CMD,BOOL,,,,coil,8020,
|
||||||
|
COIL_SYS_RESET_CMD,BOOL,,,,coil,8030,
|
||||||
|
COIL_SYS_HAND_CMD,BOOL,,,,coil,8040,
|
||||||
|
COIL_SYS_AUTO_CMD,BOOL,,,,coil,8050,
|
||||||
|
COIL_SYS_INIT_CMD,BOOL,,,,coil,8060,
|
||||||
|
COIL_UNILAB_SEND_MSG_SUCC_CMD,BOOL,,,,coil,8700,
|
||||||
|
COIL_UNILAB_REC_MSG_SUCC_CMD,BOOL,,,,coil,8710,unilab_rec_msg_succ_cmd
|
||||||
|
COIL_SYS_START_STATUS,BOOL,,,,coil,8210,
|
||||||
|
COIL_SYS_STOP_STATUS,BOOL,,,,coil,8220,
|
||||||
|
COIL_SYS_RESET_STATUS,BOOL,,,,coil,8230,
|
||||||
|
COIL_SYS_HAND_STATUS,BOOL,,,,coil,8240,
|
||||||
|
COIL_SYS_AUTO_STATUS,BOOL,,,,coil,8250,
|
||||||
|
COIL_SYS_INIT_STATUS,BOOL,,,,coil,8260,
|
||||||
|
COIL_REQUEST_REC_MSG_STATUS,BOOL,,,,coil,8500,
|
||||||
|
COIL_REQUEST_SEND_MSG_STATUS,BOOL,,,,coil,8510,request_send_msg_status
|
||||||
|
REG_MSG_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,11000,
|
||||||
|
REG_MSG_ELECTROLYTE_NUM,INT16,,,,hold_register,11002,unilab_send_msg_electrolyte_num
|
||||||
|
REG_MSG_ELECTROLYTE_VOLUME,INT16,,,,hold_register,11004,unilab_send_msg_electrolyte_vol
|
||||||
|
REG_MSG_ASSEMBLY_TYPE,INT16,,,,hold_register,11006,unilab_send_msg_assembly_type
|
||||||
|
REG_MSG_ASSEMBLY_PRESSURE,INT16,,,,hold_register,11008,unilab_send_msg_assembly_pressure
|
||||||
|
REG_DATA_ASSEMBLY_COIN_CELL_NUM,INT16,,,,hold_register,10000,data_assembly_coin_cell_num
|
||||||
|
REG_DATA_OPEN_CIRCUIT_VOLTAGE,FLOAT32,,,,hold_register,10002,data_open_circuit_voltage
|
||||||
|
REG_DATA_AXIS_X_POS,FLOAT32,,,,hold_register,10004,
|
||||||
|
REG_DATA_AXIS_Y_POS,FLOAT32,,,,hold_register,10006,
|
||||||
|
REG_DATA_AXIS_Z_POS,FLOAT32,,,,hold_register,10008,
|
||||||
|
REG_DATA_POLE_WEIGHT,FLOAT32,,,,hold_register,10010,data_pole_weight
|
||||||
|
REG_DATA_ASSEMBLY_PER_TIME,FLOAT32,,,,hold_register,10012,data_assembly_time
|
||||||
|
REG_DATA_ASSEMBLY_PRESSURE,INT16,,,,hold_register,10014,data_assembly_pressure
|
||||||
|
REG_DATA_ELECTROLYTE_VOLUME,INT16,,,,hold_register,10016,data_electrolyte_volume
|
||||||
|
REG_DATA_COIN_NUM,INT16,,,,hold_register,10018,data_coin_num
|
||||||
|
REG_DATA_ELECTROLYTE_CODE,STRING,,,,hold_register,10020,data_electrolyte_code()
|
||||||
|
REG_DATA_COIN_CELL_CODE,STRING,,,,hold_register,10030,data_coin_cell_code()
|
||||||
|
REG_DATA_STACK_VISON_CODE,STRING,,,,hold_register,12004,data_stack_vision_code()
|
||||||
|
REG_DATA_GLOVE_BOX_PRESSURE,FLOAT32,,,,hold_register,10050,data_glove_box_pressure
|
||||||
|
REG_DATA_GLOVE_BOX_WATER_CONTENT,FLOAT32,,,,hold_register,10052,data_glove_box_water_content
|
||||||
|
REG_DATA_GLOVE_BOX_O2_CONTENT,FLOAT32,,,,hold_register,10054,data_glove_box_o2_content
|
||||||
|
UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8720,
|
||||||
|
UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM,BOOL,,,,coil,8520,
|
||||||
|
REG_MSG_ELECTROLYTE_NUM_USED,INT16,,,,hold_register,496,
|
||||||
|
REG_DATA_ELECTROLYTE_USE_NUM,INT16,,,,hold_register,10000,
|
||||||
|
UNILAB_SEND_FINISHED_CMD,BOOL,,,,coil,8730,
|
||||||
|
UNILAB_RECE_FINISHED_CMD,BOOL,,,,coil,8530,
|
||||||
|
REG_DATA_ASSEMBLY_TYPE,INT16,,,,hold_register,10018,ASSEMBLY_TYPE7or8
|
||||||
|
REG_UNILAB_INTERACT,BOOL,,,,coil,8450,
|
||||||
|
,,,,,coil,8320,
|
||||||
|
COIL_ALUMINUM_FOIL,BOOL,,,,coil,8340,
|
||||||
|
REG_MSG_NE_PLATE_MATRIX,INT16,,,,hold_register,440,
|
||||||
|
REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,,,hold_register,450,
|
||||||
|
REG_MSG_TIP_BOX_MATRIX,INT16,,,,hold_register,480,
|
||||||
|
REG_MSG_NE_PLATE_NUM,INT16,,,,hold_register,443,
|
||||||
|
REG_MSG_SEPARATOR_PLATE_NUM,INT16,,,,hold_register,453,
|
||||||
|
REG_MSG_PRESS_MODE,BOOL,,,,coil,8360,
|
||||||
|
,BOOL,,,,coil,8300,
|
||||||
|
,BOOL,,,,coil,8310,
|
||||||
|
COIL_GB_L_IGNORE_CMD,BOOL,,,,coil,8320,
|
||||||
|
COIL_GB_R_IGNORE_CMD,BOOL,,,,coil,8420,
|
||||||
|
,BOOL,,,,coil,8350,
|
||||||
|
COIL_ELECTROLYTE_DUAL_DROP_MODE,BOOL,,,,coil,8370,
|
||||||
|
,BOOL,,,,coil,8380,
|
||||||
|
,BOOL,,,,coil,8390,
|
||||||
|
,BOOL,,,,coil,8400,
|
||||||
|
,BOOL,,,,coil,8410,
|
||||||
|
REG_MSG_DUAL_DROP_FIRST_VOLUME,INT16,,,,hold_register,4001,
|
||||||
|
COIL_DUAL_DROP_SUCTION_TIMING,BOOL,,,,coil,8430,
|
||||||
|
COIL_DUAL_DROP_START_TIMING,BOOL,,,,coil,8470,
|
||||||
|
REG_MSG_BATTERY_CLEAN_IGNORE,BOOL,,,,coil,8460,
|
||||||
|
COIL_ALARM_100_SYSTEM_ERROR,BOOL,,,,coil,1000,异常100-系统异常
|
||||||
|
COIL_ALARM_101_EMERGENCY_STOP,BOOL,,,,coil,1010,异常101-急停
|
||||||
|
COIL_ALARM_111_GLOVEBOX_EMERGENCY_STOP,BOOL,,,,coil,1110,异常111-手套箱急停
|
||||||
|
COIL_ALARM_112_GLOVEBOX_GRATING_BLOCKED,BOOL,,,,coil,1120,异常112-手套箱内光栅遮挡
|
||||||
|
COIL_ALARM_160_PIPETTE_TIP_SHORTAGE,BOOL,,,,coil,1600,异常160-移液枪头缺料
|
||||||
|
COIL_ALARM_161_POSITIVE_SHELL_SHORTAGE,BOOL,,,,coil,1610,异常161-正极壳缺料
|
||||||
|
COIL_ALARM_162_ALUMINUM_FOIL_SHORTAGE,BOOL,,,,coil,1620,异常162-铝箔垫缺料
|
||||||
|
COIL_ALARM_163_POSITIVE_PLATE_SHORTAGE,BOOL,,,,coil,1630,异常163-正极片缺料
|
||||||
|
COIL_ALARM_164_SEPARATOR_SHORTAGE,BOOL,,,,coil,1640,异常164-隔膜缺料
|
||||||
|
COIL_ALARM_165_NEGATIVE_PLATE_SHORTAGE,BOOL,,,,coil,1650,异常165-负极片缺料
|
||||||
|
COIL_ALARM_166_FLAT_WASHER_SHORTAGE,BOOL,,,,coil,1660,异常166-平垫缺料
|
||||||
|
COIL_ALARM_167_SPRING_WASHER_SHORTAGE,BOOL,,,,coil,1670,异常167-弹垫缺料
|
||||||
|
COIL_ALARM_168_NEGATIVE_SHELL_SHORTAGE,BOOL,,,,coil,1680,异常168-负极壳缺料
|
||||||
|
COIL_ALARM_169_FINISHED_BATTERY_FULL,BOOL,,,,coil,1690,异常169-成品电池满料
|
||||||
|
COIL_ALARM_201_SERVO_AXIS_01_ERROR,BOOL,,,,coil,2010,异常201-伺服轴01异常
|
||||||
|
COIL_ALARM_202_SERVO_AXIS_02_ERROR,BOOL,,,,coil,2020,异常202-伺服轴02异常
|
||||||
|
COIL_ALARM_203_SERVO_AXIS_03_ERROR,BOOL,,,,coil,2030,异常203-伺服轴03异常
|
||||||
|
COIL_ALARM_204_SERVO_AXIS_04_ERROR,BOOL,,,,coil,2040,异常204-伺服轴04异常
|
||||||
|
COIL_ALARM_205_SERVO_AXIS_05_ERROR,BOOL,,,,coil,2050,异常205-伺服轴05异常
|
||||||
|
COIL_ALARM_206_SERVO_AXIS_06_ERROR,BOOL,,,,coil,2060,异常206-伺服轴06异常
|
||||||
|
COIL_ALARM_207_SERVO_AXIS_07_ERROR,BOOL,,,,coil,2070,异常207-伺服轴07异常
|
||||||
|
COIL_ALARM_208_SERVO_AXIS_08_ERROR,BOOL,,,,coil,2080,异常208-伺服轴08异常
|
||||||
|
COIL_ALARM_209_SERVO_AXIS_09_ERROR,BOOL,,,,coil,2090,异常209-伺服轴09异常
|
||||||
|
COIL_ALARM_210_SERVO_AXIS_10_ERROR,BOOL,,,,coil,2100,异常210-伺服轴10异常
|
||||||
|
COIL_ALARM_211_SERVO_AXIS_11_ERROR,BOOL,,,,coil,2110,异常211-伺服轴11异常
|
||||||
|
COIL_ALARM_212_SERVO_AXIS_12_ERROR,BOOL,,,,coil,2120,异常212-伺服轴12异常
|
||||||
|
COIL_ALARM_213_SERVO_AXIS_13_ERROR,BOOL,,,,coil,2130,异常213-伺服轴13异常
|
||||||
|
COIL_ALARM_214_SERVO_AXIS_14_ERROR,BOOL,,,,coil,2140,异常214-伺服轴14异常
|
||||||
|
COIL_ALARM_250_OTHER_COMPONENT_ERROR,BOOL,,,,coil,2500,异常250-其他元件异常
|
||||||
|
COIL_ALARM_251_PIPETTE_COMM_ERROR,BOOL,,,,coil,2510,异常251-移液枪通讯异常
|
||||||
|
COIL_ALARM_252_PIPETTE_ALARM,BOOL,,,,coil,2520,异常252-移液枪报警
|
||||||
|
COIL_ALARM_256_ELECTRIC_GRIPPER_ERROR,BOOL,,,,coil,2560,异常256-电爪异常
|
||||||
|
COIL_ALARM_262_RB_UNKNOWN_POSITION_ERROR,BOOL,,,,coil,2620,异常262-RB报警:未知点位错误
|
||||||
|
COIL_ALARM_263_RB_XYZ_PARAM_LIMIT_ERROR,BOOL,,,,coil,2630,异常263-RB报警:X、Y、Z参数超限制
|
||||||
|
COIL_ALARM_264_RB_VISION_PARAM_ERROR,BOOL,,,,coil,2640,异常264-RB报警:视觉参数误差过大
|
||||||
|
COIL_ALARM_265_RB_NOZZLE_1_PICK_FAIL,BOOL,,,,coil,2650,异常265-RB报警:1#吸嘴取料失败
|
||||||
|
COIL_ALARM_266_RB_NOZZLE_2_PICK_FAIL,BOOL,,,,coil,2660,异常266-RB报警:2#吸嘴取料失败
|
||||||
|
COIL_ALARM_267_RB_NOZZLE_3_PICK_FAIL,BOOL,,,,coil,2670,异常267-RB报警:3#吸嘴取料失败
|
||||||
|
COIL_ALARM_268_RB_NOZZLE_4_PICK_FAIL,BOOL,,,,coil,2680,异常268-RB报警:4#吸嘴取料失败
|
||||||
|
COIL_ALARM_269_RB_TRAY_PICK_FAIL,BOOL,,,,coil,2690,异常269-RB报警:取物料盘失败
|
||||||
|
COIL_ALARM_280_RB_COLLISION_ERROR,BOOL,,,,coil,2800,异常280-RB碰撞异常
|
||||||
|
COIL_ALARM_290_VISION_SYSTEM_COMM_ERROR,BOOL,,,,coil,2900,异常290-视觉系统通讯异常
|
||||||
|
COIL_ALARM_291_VISION_ALIGNMENT_NG,BOOL,,,,coil,2910,异常291-视觉对位NG异常
|
||||||
|
COIL_ALARM_292_BARCODE_SCANNER_COMM_ERROR,BOOL,,,,coil,2920,异常292-扫码枪通讯异常
|
||||||
|
COIL_ALARM_310_OCV_TRANSFER_NOZZLE_SUCTION_ERROR,BOOL,,,,coil,3100,异常310-开电移载吸嘴吸真空异常
|
||||||
|
COIL_ALARM_311_OCV_TRANSFER_NOZZLE_BREAK_ERROR,BOOL,,,,coil,3110,异常311-开电移载吸嘴破真空异常
|
||||||
|
COIL_ALARM_312_WEIGHT_TRANSFER_NOZZLE_SUCTION_ERROR,BOOL,,,,coil,3120,异常312-称重移载吸嘴吸真空异常
|
||||||
|
COIL_ALARM_313_WEIGHT_TRANSFER_NOZZLE_BREAK_ERROR,BOOL,,,,coil,3130,异常313-称重移载吸嘴破真空异常
|
||||||
|
COIL_ALARM_340_OCV_NOZZLE_TRANSFER_CYLINDER_ERROR,BOOL,,,,coil,3400,异常340-开路电压吸嘴移载气缸异常
|
||||||
|
COIL_ALARM_342_OCV_NOZZLE_LIFT_CYLINDER_ERROR,BOOL,,,,coil,3420,异常342-开路电压吸嘴升降气缸异常
|
||||||
|
COIL_ALARM_344_OCV_CRIMPING_CYLINDER_ERROR,BOOL,,,,coil,3440,异常344-开路电压旋压气缸异常
|
||||||
|
COIL_ALARM_350_WEIGHT_NOZZLE_TRANSFER_CYLINDER_ERROR,BOOL,,,,coil,3500,异常350-称重吸嘴移载气缸异常
|
||||||
|
COIL_ALARM_352_WEIGHT_NOZZLE_LIFT_CYLINDER_ERROR,BOOL,,,,coil,3520,异常352-称重吸嘴升降气缸异常
|
||||||
|
COIL_ALARM_354_CLEANING_CLOTH_TRANSFER_CYLINDER_ERROR,BOOL,,,,coil,3540,异常354-清洗无尘布移载气缸异常
|
||||||
|
COIL_ALARM_356_CLEANING_CLOTH_PRESS_CYLINDER_ERROR,BOOL,,,,coil,3560,异常356-清洗无尘布压紧气缸异常
|
||||||
|
COIL_ALARM_360_ELECTROLYTE_BOTTLE_POSITION_CYLINDER_ERROR,BOOL,,,,coil,3600,异常360-电解液瓶定位气缸异常
|
||||||
|
COIL_ALARM_362_PIPETTE_TIP_BOX_POSITION_CYLINDER_ERROR,BOOL,,,,coil,3620,异常362-移液枪头盒定位气缸异常
|
||||||
|
COIL_ALARM_364_REAGENT_BOTTLE_GRIPPER_LIFT_CYLINDER_ERROR,BOOL,,,,coil,3640,异常364-试剂瓶夹爪升降气缸异常
|
||||||
|
COIL_ALARM_366_REAGENT_BOTTLE_GRIPPER_CYLINDER_ERROR,BOOL,,,,coil,3660,异常366-试剂瓶夹爪气缸异常
|
||||||
|
COIL_ALARM_370_PRESS_MODULE_BLOW_CYLINDER_ERROR,BOOL,,,,coil,3700,异常370-压制模块吹气气缸异常
|
||||||
|
COIL_ALARM_151_ELECTROLYTE_BOTTLE_POSITION_ERROR,BOOL,,,,coil,1510,异常151-电解液瓶定位在籍异常
|
||||||
|
COIL_ALARM_152_ELECTROLYTE_BOTTLE_CAP_ERROR,BOOL,,,,coil,1520,异常152-电解液瓶盖在籍异常
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
Time,open_circuit_voltage,pole_weight,assembly_time,assembly_pressure,electrolyte_volume,coin_num,electrolyte_code,coin_cell_code
|
||||||
|
20260110_153356,0.0,23.889999389648438,17.0,3609,40,7,deaoR,
|
||||||
|
20260110_153640,3.430999994277954,23.719999313354492,162.0,3560,40,7,deaoR,
|
||||||
|
20260110_162905,0.0,23.920000076293945,772.0,3625,30,7,LG600001,YS103130
|
||||||
|
20260110_163526,3.430999994277954,24.010000228881836,234.0,3690,30,7,LG600001,YS102964
|
||||||
|
20260110_164530,0.0,23.589998245239258,219.0,3690,30,7,LG600001,YS102857
|
||||||
|
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"id": "bioyond_cell_workstation",
|
||||||
|
"name": "配液分液工站",
|
||||||
|
"children": [
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "bioyond_cell",
|
||||||
|
"config": {
|
||||||
|
"protocol_type": [],
|
||||||
|
"station_resource": {}
|
||||||
|
},
|
||||||
|
"data": {}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": "BatteryStation",
|
||||||
|
"name": "扣电工作站",
|
||||||
|
"children": [
|
||||||
|
"coin_cell_deck"
|
||||||
|
],
|
||||||
|
"parent": null,
|
||||||
|
"type": "device",
|
||||||
|
"class": "coincellassemblyworkstation_device",
|
||||||
|
"position": {
|
||||||
|
"x": -600,
|
||||||
|
"y": -400,
|
||||||
|
"z": 0
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"debug_mode": false,
|
||||||
|
"protocol_type": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": []
|
||||||
|
}
|
||||||
107
unilabos/devices/workstation/coin_cell_assembly/电池资源冲突修复说明.md
Normal file
107
unilabos/devices/workstation/coin_cell_assembly/电池资源冲突修复说明.md
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# 电池组装资源冲突问题修复说明
|
||||||
|
|
||||||
|
## 问题描述
|
||||||
|
|
||||||
|
在运行 `func_allpack_cmd` 函数时,遇到以下错误:
|
||||||
|
|
||||||
|
```
|
||||||
|
ValueError: Resource 'battery_0' already assigned to deck
|
||||||
|
```
|
||||||
|
|
||||||
|
**错误位置**:`coin_cell_assembly.py` 第 849 行
|
||||||
|
```python
|
||||||
|
liaopan3.children[self.coin_num_N].assign_child_resource(battery, location=None)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 原因分析
|
||||||
|
|
||||||
|
1. **资源名称冲突**:
|
||||||
|
- 每次创建电池资源使用固定格式 `battery_{coin_num_N}`
|
||||||
|
- 如果程序重启或断点恢复,`coin_num_N` 可能重置为 0
|
||||||
|
- Deck 上可能已存在 `battery_0` 等同名资源
|
||||||
|
|
||||||
|
2. **缺少冲突处理**:
|
||||||
|
- 在分配资源前没有检查目标位置是否已有资源
|
||||||
|
- 没有清理机制来移除旧资源
|
||||||
|
|
||||||
|
## 解决方案
|
||||||
|
|
||||||
|
### 1. 使用时间戳确保资源名称唯一
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 之前
|
||||||
|
battery = ElectrodeSheet(name=f"battery_{self.coin_num_N}", ...)
|
||||||
|
|
||||||
|
# 修复后
|
||||||
|
timestamp_suffix = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
|
||||||
|
battery_name = f"battery_{self.coin_num_N}_{timestamp_suffix}"
|
||||||
|
battery = ElectrodeSheet(name=battery_name, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 添加资源冲突检查和清理
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 检查目标位置是否已有资源
|
||||||
|
target_slot = liaopan3.children[self.coin_num_N]
|
||||||
|
if target_slot.children:
|
||||||
|
logger.warning(f"位置 {self.coin_num_N} 已有资源,将先卸载旧资源")
|
||||||
|
try:
|
||||||
|
# 卸载所有现有子资源
|
||||||
|
for child in list(target_slot.children):
|
||||||
|
target_slot.unassign_child_resource(child)
|
||||||
|
logger.info(f"已卸载旧资源: {child.name}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"卸载旧资源时出错: {e}")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 增强错误处理
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 分配新资源到目标位置
|
||||||
|
try:
|
||||||
|
target_slot.assign_child_resource(battery, location=None)
|
||||||
|
logger.info(f"成功分配电池 {battery_name} 到位置 {self.coin_num_N}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"分配电池资源失败: {e}")
|
||||||
|
raise
|
||||||
|
```
|
||||||
|
|
||||||
|
## 修复效果
|
||||||
|
|
||||||
|
✅ **不再出现重复资源名称错误**
|
||||||
|
- 每个电池资源都有唯一的时间戳后缀
|
||||||
|
- 即使 `coin_num_N` 相同,资源名称也不会冲突
|
||||||
|
|
||||||
|
✅ **自动清理旧资源**
|
||||||
|
- 在分配新资源前检查目标位置
|
||||||
|
- 自动卸载已存在的旧资源
|
||||||
|
|
||||||
|
✅ **增强日志记录**
|
||||||
|
- 记录资源卸载操作
|
||||||
|
- 记录资源分配成功/失败
|
||||||
|
- 便于调试和问题追踪
|
||||||
|
|
||||||
|
## 测试建议
|
||||||
|
|
||||||
|
1. **正常运行测试**:
|
||||||
|
```python
|
||||||
|
workstation.func_allpack_cmd(
|
||||||
|
elec_num=1,
|
||||||
|
elec_use_num=1,
|
||||||
|
elec_vol=20,
|
||||||
|
file_path="..."
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **断点恢复测试**:
|
||||||
|
- 运行一次后中断
|
||||||
|
- 再次运行相同参数
|
||||||
|
- 验证不会出现资源冲突错误
|
||||||
|
|
||||||
|
3. **连续运行测试**:
|
||||||
|
- 连续多次运行
|
||||||
|
- 验证每次都能正常分配资源
|
||||||
|
|
||||||
|
## 相关文件
|
||||||
|
|
||||||
|
- `coin_cell_assembly.py` - 第 838-875 行(`func_pack_get_msg_cmd` 函数)
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d
|
|
||||||
|
|
||||||
from unilabos.resources.itemized_carrier import BottleCarrier
|
|
||||||
from unilabos.devices.workstation.post_process.bottles import POST_PROCESS_PolymerStation_Reagent_Bottle
|
|
||||||
|
|
||||||
# 命名约定:试剂瓶-Bottle,烧杯-Beaker,烧瓶-Flask,小瓶-Vial
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# 聚合站(PolymerStation)载体定义(统一入口)
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
def POST_PROCESS_Raw_1BottleCarrier(name: str) -> BottleCarrier:
|
|
||||||
"""聚合站-单试剂瓶载架
|
|
||||||
|
|
||||||
参数:
|
|
||||||
- name: 载架名称前缀
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 载架尺寸 (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="POST_PROCESS_Raw_1BottleCarrier",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 1
|
|
||||||
carrier.num_items_y = 1
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
# 统一后缀采用 "flask_1" 命名(可按需调整)
|
|
||||||
carrier[0] = POST_PROCESS_PolymerStation_Reagent_Bottle(f"{name}_flask_1")
|
|
||||||
return carrier
|
|
||||||
|
|
||||||
def POST_PROCESS_Reaction_1BottleCarrier(name: str) -> BottleCarrier:
|
|
||||||
"""聚合站-单试剂瓶载架
|
|
||||||
|
|
||||||
参数:
|
|
||||||
- name: 载架名称前缀
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 载架尺寸 (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="POST_PROCESS_Reaction_1BottleCarrier",
|
|
||||||
)
|
|
||||||
carrier.num_items_x = 1
|
|
||||||
carrier.num_items_y = 1
|
|
||||||
carrier.num_items_z = 1
|
|
||||||
# 统一后缀采用 "flask_1" 命名(可按需调整)
|
|
||||||
carrier[0] = POST_PROCESS_PolymerStation_Reagent_Bottle(f"{name}_flask_1")
|
|
||||||
return carrier
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
from unilabos.resources.itemized_carrier import Bottle
|
|
||||||
|
|
||||||
|
|
||||||
def POST_PROCESS_PolymerStation_Reagent_Bottle(
|
|
||||||
name: str,
|
|
||||||
diameter: float = 70.0,
|
|
||||||
height: float = 120.0,
|
|
||||||
max_volume: float = 500000.0, # 500mL
|
|
||||||
barcode: str = None,
|
|
||||||
) -> Bottle:
|
|
||||||
"""创建试剂瓶"""
|
|
||||||
return Bottle(
|
|
||||||
name=name,
|
|
||||||
diameter=diameter,
|
|
||||||
height=height,
|
|
||||||
max_volume=max_volume,
|
|
||||||
barcode=barcode,
|
|
||||||
model="POST_PROCESS_PolymerStation_Reagent_Bottle",
|
|
||||||
)
|
|
||||||
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
from os import name
|
|
||||||
from pylabrobot.resources import Deck, Coordinate, Rotation
|
|
||||||
|
|
||||||
from unilabos.devices.workstation.post_process.warehouses import (
|
|
||||||
post_process_warehouse_4x3x1,
|
|
||||||
post_process_warehouse_4x3x1_2,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class post_process_deck(Deck):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str = "post_process_deck",
|
|
||||||
size_x: float = 2000.0,
|
|
||||||
size_y: float = 1000.0,
|
|
||||||
size_z: float = 2670.0,
|
|
||||||
category: str = "deck",
|
|
||||||
setup: bool = True,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(name=name, size_x=1700.0, size_y=1350.0, size_z=2670.0)
|
|
||||||
if setup:
|
|
||||||
self.setup()
|
|
||||||
|
|
||||||
def setup(self) -> None:
|
|
||||||
# 添加仓库
|
|
||||||
self.warehouses = {
|
|
||||||
"原料罐堆栈": post_process_warehouse_4x3x1("原料罐堆栈"),
|
|
||||||
"反应罐堆栈": post_process_warehouse_4x3x1_2("反应罐堆栈"),
|
|
||||||
|
|
||||||
}
|
|
||||||
# warehouse 的位置
|
|
||||||
self.warehouse_locations = {
|
|
||||||
"原料罐堆栈": Coordinate(350.0, 55.0, 0.0),
|
|
||||||
"反应罐堆栈": Coordinate(1000.0, 55.0, 0.0),
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
for warehouse_name, warehouse in self.warehouses.items():
|
|
||||||
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
{
|
|
||||||
"register_node_list_from_csv_path": {
|
|
||||||
"path": "opcua_nodes_huairou.csv"
|
|
||||||
},
|
|
||||||
"create_flow": [
|
|
||||||
{
|
|
||||||
"name": "trigger_grab_action",
|
|
||||||
"description": "触发反应罐及原料罐抓取动作",
|
|
||||||
"parameters": ["reaction_tank_number", "raw_tank_number"],
|
|
||||||
"action": [
|
|
||||||
{
|
|
||||||
"init_function": {
|
|
||||||
"func_name": "init_grab_params",
|
|
||||||
"write_nodes": ["reaction_tank_number", "raw_tank_number"]
|
|
||||||
},
|
|
||||||
"start_function": {
|
|
||||||
"func_name": "start_grab",
|
|
||||||
"write_nodes": {"grab_trigger": true},
|
|
||||||
"condition_nodes": ["grab_complete"],
|
|
||||||
"stop_condition_expression": "grab_complete == True",
|
|
||||||
"timeout_seconds": 999999.0
|
|
||||||
},
|
|
||||||
"stop_function": {
|
|
||||||
"func_name": "stop_grab",
|
|
||||||
"write_nodes": {"grab_trigger": false}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "trigger_post_processing",
|
|
||||||
"description": "触发后处理动作",
|
|
||||||
"parameters": ["atomization_fast_speed", "wash_slow_speed","injection_pump_suction_speed",
|
|
||||||
"injection_pump_push_speed","raw_liquid_suction_count","first_wash_water_amount",
|
|
||||||
"second_wash_water_amount","first_powder_mixing_time","second_powder_mixing_time",
|
|
||||||
"first_powder_wash_count","second_powder_wash_count","initial_water_amount",
|
|
||||||
"pre_filtration_mixing_time","atomization_pressure_kpa"],
|
|
||||||
"action": [
|
|
||||||
{
|
|
||||||
"init_function": {
|
|
||||||
"func_name": "init_post_processing_params",
|
|
||||||
"write_nodes": ["atomization_fast_speed", "wash_slow_speed","injection_pump_suction_speed",
|
|
||||||
"injection_pump_push_speed","raw_liquid_suction_count","first_wash_water_amount",
|
|
||||||
"second_wash_water_amount","first_powder_mixing_time","second_powder_mixing_time",
|
|
||||||
"first_powder_wash_count","second_powder_wash_count","initial_water_amount",
|
|
||||||
"pre_filtration_mixing_time","atomization_pressure_kpa"]
|
|
||||||
},
|
|
||||||
"start_function": {
|
|
||||||
"func_name": "start_post_processing",
|
|
||||||
"write_nodes": {"post_process_trigger": true},
|
|
||||||
"condition_nodes": ["post_process_complete"],
|
|
||||||
"stop_condition_expression": "post_process_complete == True",
|
|
||||||
"timeout_seconds": 999999.0
|
|
||||||
},
|
|
||||||
"stop_function": {
|
|
||||||
"func_name": "stop_post_processing",
|
|
||||||
"write_nodes": {"post_process_trigger": false}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "trigger_cleaning_action",
|
|
||||||
"description": "触发清洗及管路吹气动作",
|
|
||||||
"parameters": ["nmp_outer_wall_cleaning_injection", "nmp_outer_wall_cleaning_count","nmp_outer_wall_cleaning_wait_time",
|
|
||||||
"nmp_outer_wall_cleaning_waste_time","nmp_inner_wall_cleaning_injection","nmp_inner_wall_cleaning_count",
|
|
||||||
"nmp_pump_cleaning_suction_count",
|
|
||||||
"nmp_inner_wall_cleaning_waste_time",
|
|
||||||
"nmp_stirrer_cleaning_injection",
|
|
||||||
"nmp_stirrer_cleaning_count",
|
|
||||||
"nmp_stirrer_cleaning_wait_time",
|
|
||||||
"nmp_stirrer_cleaning_waste_time",
|
|
||||||
"water_outer_wall_cleaning_injection",
|
|
||||||
"water_outer_wall_cleaning_count",
|
|
||||||
"water_outer_wall_cleaning_wait_time",
|
|
||||||
"water_outer_wall_cleaning_waste_time",
|
|
||||||
"water_inner_wall_cleaning_injection",
|
|
||||||
"water_inner_wall_cleaning_count",
|
|
||||||
"water_pump_cleaning_suction_count",
|
|
||||||
"water_inner_wall_cleaning_waste_time",
|
|
||||||
"water_stirrer_cleaning_injection",
|
|
||||||
"water_stirrer_cleaning_count",
|
|
||||||
"water_stirrer_cleaning_wait_time",
|
|
||||||
"water_stirrer_cleaning_waste_time",
|
|
||||||
"acetone_outer_wall_cleaning_injection",
|
|
||||||
"acetone_outer_wall_cleaning_count",
|
|
||||||
"acetone_outer_wall_cleaning_wait_time",
|
|
||||||
"acetone_outer_wall_cleaning_waste_time",
|
|
||||||
"acetone_inner_wall_cleaning_injection",
|
|
||||||
"acetone_inner_wall_cleaning_count",
|
|
||||||
"acetone_pump_cleaning_suction_count",
|
|
||||||
"acetone_inner_wall_cleaning_waste_time",
|
|
||||||
"acetone_stirrer_cleaning_injection",
|
|
||||||
"acetone_stirrer_cleaning_count",
|
|
||||||
"acetone_stirrer_cleaning_wait_time",
|
|
||||||
"acetone_stirrer_cleaning_waste_time",
|
|
||||||
"pipe_blowing_time",
|
|
||||||
"injection_pump_forward_empty_suction_count",
|
|
||||||
"injection_pump_reverse_empty_suction_count",
|
|
||||||
"filtration_liquid_selection"],
|
|
||||||
"action": [
|
|
||||||
{
|
|
||||||
"init_function": {
|
|
||||||
"func_name": "init_cleaning_params",
|
|
||||||
"write_nodes": ["nmp_outer_wall_cleaning_injection", "nmp_outer_wall_cleaning_count","nmp_outer_wall_cleaning_wait_time",
|
|
||||||
"nmp_outer_wall_cleaning_waste_time","nmp_inner_wall_cleaning_injection","nmp_inner_wall_cleaning_count",
|
|
||||||
"nmp_pump_cleaning_suction_count",
|
|
||||||
"nmp_inner_wall_cleaning_waste_time",
|
|
||||||
"nmp_stirrer_cleaning_injection",
|
|
||||||
"nmp_stirrer_cleaning_count",
|
|
||||||
"nmp_stirrer_cleaning_wait_time",
|
|
||||||
"nmp_stirrer_cleaning_waste_time",
|
|
||||||
"water_outer_wall_cleaning_injection",
|
|
||||||
"water_outer_wall_cleaning_count",
|
|
||||||
"water_outer_wall_cleaning_wait_time",
|
|
||||||
"water_outer_wall_cleaning_waste_time",
|
|
||||||
"water_inner_wall_cleaning_injection",
|
|
||||||
"water_inner_wall_cleaning_count",
|
|
||||||
"water_pump_cleaning_suction_count",
|
|
||||||
"water_inner_wall_cleaning_waste_time",
|
|
||||||
"water_stirrer_cleaning_injection",
|
|
||||||
"water_stirrer_cleaning_count",
|
|
||||||
"water_stirrer_cleaning_wait_time",
|
|
||||||
"water_stirrer_cleaning_waste_time",
|
|
||||||
"acetone_outer_wall_cleaning_injection",
|
|
||||||
"acetone_outer_wall_cleaning_count",
|
|
||||||
"acetone_outer_wall_cleaning_wait_time",
|
|
||||||
"acetone_outer_wall_cleaning_waste_time",
|
|
||||||
"acetone_inner_wall_cleaning_injection",
|
|
||||||
"acetone_inner_wall_cleaning_count",
|
|
||||||
"acetone_pump_cleaning_suction_count",
|
|
||||||
"acetone_inner_wall_cleaning_waste_time",
|
|
||||||
"acetone_stirrer_cleaning_injection",
|
|
||||||
"acetone_stirrer_cleaning_count",
|
|
||||||
"acetone_stirrer_cleaning_wait_time",
|
|
||||||
"acetone_stirrer_cleaning_waste_time",
|
|
||||||
"pipe_blowing_time",
|
|
||||||
"injection_pump_forward_empty_suction_count",
|
|
||||||
"injection_pump_reverse_empty_suction_count",
|
|
||||||
"filtration_liquid_selection"]
|
|
||||||
},
|
|
||||||
"start_function": {
|
|
||||||
"func_name": "start_cleaning",
|
|
||||||
"write_nodes": {"cleaning_and_pipe_blowing_trigger": true},
|
|
||||||
"condition_nodes": ["cleaning_complete"],
|
|
||||||
"stop_condition_expression": "cleaning_complete == True",
|
|
||||||
"timeout_seconds": 999999.0
|
|
||||||
},
|
|
||||||
"stop_function": {
|
|
||||||
"func_name": "stop_cleaning",
|
|
||||||
"write_nodes": {"cleaning_and_pipe_blowing_trigger": false}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
Name,EnglishName,NodeType,DataType,NodeLanguage,NodeId
|
|
||||||
原料罐号码,raw_tank_number,VARIABLE,INT16,Chinese,ns=4;s=OPC|原料罐号码
|
|
||||||
反应罐号码,reaction_tank_number,VARIABLE,INT16,Chinese,ns=4;s=OPC|反应罐号码
|
|
||||||
反应罐及原料罐抓取触发,grab_trigger,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|反应罐及原料罐抓取触发
|
|
||||||
后处理动作触发,post_process_trigger,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|后处理动作触发
|
|
||||||
搅拌桨雾化快速,atomization_fast_speed,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|搅拌桨雾化快速
|
|
||||||
搅拌桨洗涤慢速,wash_slow_speed,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|搅拌桨洗涤慢速
|
|
||||||
注射泵抽液速度,injection_pump_suction_speed,VARIABLE,INT16,Chinese,ns=4;s=OPC|注射泵抽液速度
|
|
||||||
注射泵推液速度,injection_pump_push_speed,VARIABLE,INT16,Chinese,ns=4;s=OPC|注射泵推液速度
|
|
||||||
抽原液次数,raw_liquid_suction_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|抽原液次数
|
|
||||||
第1次洗涤加水量,first_wash_water_amount,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|第1次洗涤加水量
|
|
||||||
第2次洗涤加水量,second_wash_water_amount,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|第2次洗涤加水量
|
|
||||||
第1次粉末搅拌时间,first_powder_mixing_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|第1次粉末搅拌时间
|
|
||||||
第2次粉末搅拌时间,second_powder_mixing_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|第2次粉末搅拌时间
|
|
||||||
第1次粉末洗涤次数,first_powder_wash_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|第1次粉末洗涤次数
|
|
||||||
第2次粉末洗涤次数,second_powder_wash_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|第2次粉末洗涤次数
|
|
||||||
最开始加水量,initial_water_amount,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|最开始加水量
|
|
||||||
抽滤前搅拌时间,pre_filtration_mixing_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|抽滤前搅拌时间
|
|
||||||
雾化压力Kpa,atomization_pressure_kpa,VARIABLE,INT16,Chinese,ns=4;s=OPC|雾化压力Kpa
|
|
||||||
清洗及管路吹气触发,cleaning_and_pipe_blowing_trigger,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|清洗及管路吹气触发
|
|
||||||
废液桶满报警,waste_tank_full_alarm,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|废液桶满报警
|
|
||||||
清水桶空报警,water_tank_empty_alarm,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|清水桶空报警
|
|
||||||
NMP桶空报警,nmp_tank_empty_alarm,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|NMP桶空报警
|
|
||||||
丙酮桶空报警,acetone_tank_empty_alarm,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|丙酮桶空报警
|
|
||||||
门开报警,door_open_alarm,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|门开报警
|
|
||||||
反应罐及原料罐抓取完成PLCtoPC,grab_complete,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|反应罐及原料罐抓取完成PLCtoPC
|
|
||||||
后处理动作完成PLCtoPC,post_process_complete,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|后处理动作完成PLCtoPC
|
|
||||||
清洗及管路吹气完成PLCtoPC,cleaning_complete,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|清洗及管路吹气完成PLCtoPC
|
|
||||||
远程模式PLCtoPC,remote_mode,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|远程模式PLCtoPC
|
|
||||||
设备准备就绪PLCtoPC,device_ready,VARIABLE,BOOLEAN,Chinese,ns=4;s=OPC|设备准备就绪PLCtoPC
|
|
||||||
NMP外壁清洗加注,nmp_outer_wall_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|NMP外壁清洗加注
|
|
||||||
NMP外壁清洗次数,nmp_outer_wall_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|NMP外壁清洗次数
|
|
||||||
NMP外壁清洗等待时间,nmp_outer_wall_cleaning_wait_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|NMP外壁清洗等待时间
|
|
||||||
NMP外壁清洗抽废时间,nmp_outer_wall_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|NMP外壁清洗抽废时间
|
|
||||||
NMP内壁清洗加注,nmp_inner_wall_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|NMP内壁清洗加注
|
|
||||||
NMP内壁清洗次数,nmp_inner_wall_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|NMP内壁清洗次数
|
|
||||||
NMP泵清洗抽次数,nmp_pump_cleaning_suction_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|NMP泵清洗抽次数
|
|
||||||
NMP内壁清洗抽废时间,nmp_inner_wall_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|NMP内壁清洗抽废时间
|
|
||||||
NMP搅拌桨清洗加注,nmp_stirrer_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|NMP搅拌桨清洗加注
|
|
||||||
NMP搅拌桨清洗次数,nmp_stirrer_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|NMP搅拌桨清洗次数
|
|
||||||
NMP搅拌桨清洗等待时间,nmp_stirrer_cleaning_wait_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|NMP搅拌桨清洗等待时间
|
|
||||||
NMP搅拌桨清洗抽废时间,nmp_stirrer_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|NMP搅拌桨清洗抽废时间
|
|
||||||
清水外壁清洗加注,water_outer_wall_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|清水外壁清洗加注
|
|
||||||
清水外壁清洗次数,water_outer_wall_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|清水外壁清洗次数
|
|
||||||
清水外壁清洗等待时间,water_outer_wall_cleaning_wait_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|清水外壁清洗等待时间
|
|
||||||
清水外壁清洗抽废时间,water_outer_wall_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|清水外壁清洗抽废时间
|
|
||||||
清水内壁清洗加注,water_inner_wall_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|清水内壁清洗加注
|
|
||||||
清水内壁清洗次数,water_inner_wall_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|清水内壁清洗次数
|
|
||||||
清水泵清洗抽次数,water_pump_cleaning_suction_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|清水泵清洗抽次数
|
|
||||||
清水内壁清洗抽废时间,water_inner_wall_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|清水内壁清洗抽废时间
|
|
||||||
清水搅拌桨清洗加注,water_stirrer_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|清水搅拌桨清洗加注
|
|
||||||
清水搅拌桨清洗次数,water_stirrer_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|清水搅拌桨清洗次数
|
|
||||||
清水搅拌桨清洗等待时间,water_stirrer_cleaning_wait_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|清水搅拌桨清洗等待时间
|
|
||||||
清水搅拌桨清洗抽废时间,water_stirrer_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|清水搅拌桨清洗抽废时间
|
|
||||||
丙酮外壁清洗加注,acetone_outer_wall_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|丙酮外壁清洗加注
|
|
||||||
丙酮外壁清洗次数,acetone_outer_wall_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|丙酮外壁清洗次数
|
|
||||||
丙酮外壁清洗等待时间,acetone_outer_wall_cleaning_wait_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|丙酮外壁清洗等待时间
|
|
||||||
丙酮外壁清洗抽废时间,acetone_outer_wall_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|丙酮外壁清洗抽废时间
|
|
||||||
丙酮内壁清洗加注,acetone_inner_wall_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|丙酮内壁清洗加注
|
|
||||||
丙酮内壁清洗次数,acetone_inner_wall_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|丙酮内壁清洗次数
|
|
||||||
丙酮泵清洗抽次数,acetone_pump_cleaning_suction_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|丙酮泵清洗抽次数
|
|
||||||
丙酮内壁清洗抽废时间,acetone_inner_wall_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|丙酮内壁清洗抽废时间
|
|
||||||
丙酮搅拌桨清洗加注,acetone_stirrer_cleaning_injection,VARIABLE,FLOAT,Chinese,ns=4;s=OPC|丙酮搅拌桨清洗加注
|
|
||||||
丙酮搅拌桨清洗次数,acetone_stirrer_cleaning_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|丙酮搅拌桨清洗次数
|
|
||||||
丙酮搅拌桨清洗等待时间,acetone_stirrer_cleaning_wait_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|丙酮搅拌桨清洗等待时间
|
|
||||||
丙酮搅拌桨清洗抽废时间,acetone_stirrer_cleaning_waste_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|丙酮搅拌桨清洗抽废时间
|
|
||||||
管道吹气时间,pipe_blowing_time,VARIABLE,INT32,Chinese,ns=4;s=OPC|管道吹气时间
|
|
||||||
注射泵正向空抽次数,injection_pump_forward_empty_suction_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|注射泵正向空抽次数
|
|
||||||
注射泵反向空抽次数,injection_pump_reverse_empty_suction_count,VARIABLE,INT16,Chinese,ns=4;s=OPC|注射泵反向空抽次数
|
|
||||||
抽滤液选择0水1丙酮,filtration_liquid_selection,VARIABLE,INT16,Chinese,ns=4;s=OPC|抽滤液选择0水1丙酮
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,45 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "post_process_station",
|
|
||||||
"name": "post_process_station",
|
|
||||||
"children": [
|
|
||||||
"post_process_deck"
|
|
||||||
],
|
|
||||||
"parent": null,
|
|
||||||
"type": "device",
|
|
||||||
"class": "post_process_station",
|
|
||||||
"config": {
|
|
||||||
"url": "opc.tcp://LAPTOP-AN6QGCSD:53530/OPCUA/SimulationServer",
|
|
||||||
"config_path": "C:\\Users\\Roy\\Desktop\\DPLC\\Uni-Lab-OS\\unilabos\\devices\\workstation\\post_process\\opcua_huairou.json",
|
|
||||||
"deck": {
|
|
||||||
"data": {
|
|
||||||
"_resource_child_name": "post_process_deck",
|
|
||||||
"_resource_type": "unilabos.devices.workstation.post_process.decks:post_process_deck"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "post_process_deck",
|
|
||||||
"name": "post_process_deck",
|
|
||||||
"sample_id": null,
|
|
||||||
"children": [],
|
|
||||||
"parent": "post_process_station",
|
|
||||||
"type": "deck",
|
|
||||||
"class": "post_process_deck",
|
|
||||||
"position": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"z": 0
|
|
||||||
},
|
|
||||||
"config": {
|
|
||||||
"type": "post_process_deck",
|
|
||||||
"setup": true
|
|
||||||
},
|
|
||||||
"data": {}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
from typing import Dict, Optional, List, Union
|
|
||||||
from pylabrobot.resources import Coordinate
|
|
||||||
from pylabrobot.resources.carrier import ResourceHolder, create_homogeneous_resources
|
|
||||||
|
|
||||||
from unilabos.resources.itemized_carrier import ItemizedCarrier, ResourcePLR
|
|
||||||
|
|
||||||
|
|
||||||
LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
|
|
||||||
|
|
||||||
def warehouse_factory(
|
|
||||||
name: str,
|
|
||||||
num_items_x: int = 1,
|
|
||||||
num_items_y: int = 4,
|
|
||||||
num_items_z: int = 4,
|
|
||||||
dx: float = 137.0,
|
|
||||||
dy: float = 96.0,
|
|
||||||
dz: float = 120.0,
|
|
||||||
item_dx: float = 10.0,
|
|
||||||
item_dy: float = 10.0,
|
|
||||||
item_dz: float = 10.0,
|
|
||||||
resource_size_x: float = 127.0,
|
|
||||||
resource_size_y: float = 86.0,
|
|
||||||
resource_size_z: float = 25.0,
|
|
||||||
removed_positions: Optional[List[int]] = None,
|
|
||||||
empty: bool = False,
|
|
||||||
category: str = "warehouse",
|
|
||||||
model: Optional[str] = None,
|
|
||||||
col_offset: int = 0, # 列起始偏移量,用于生成5-8等命名
|
|
||||||
layout: str = "col-major", # 新增:排序方式,"col-major"=列优先,"row-major"=行优先
|
|
||||||
):
|
|
||||||
# 创建位置坐标
|
|
||||||
locations = []
|
|
||||||
|
|
||||||
for layer in range(num_items_z): # 层
|
|
||||||
for row in range(num_items_y): # 行
|
|
||||||
for col in range(num_items_x): # 列
|
|
||||||
# 计算位置
|
|
||||||
x = dx + col * item_dx
|
|
||||||
|
|
||||||
# 根据 layout 决定 y 坐标计算
|
|
||||||
if layout == "row-major":
|
|
||||||
# 行优先:row=0(第1行) 应该显示在上方,y 值最小
|
|
||||||
y = dy + row * item_dy
|
|
||||||
else:
|
|
||||||
# 列优先:保持原逻辑
|
|
||||||
y = dy + (num_items_y - row - 1) * item_dy
|
|
||||||
|
|
||||||
z = dz + (num_items_z - layer - 1) * item_dz
|
|
||||||
locations.append(Coordinate(x, y, z))
|
|
||||||
|
|
||||||
if removed_positions:
|
|
||||||
locations = [loc for i, loc in enumerate(locations) if i not in removed_positions]
|
|
||||||
|
|
||||||
_sites = create_homogeneous_resources(
|
|
||||||
klass=ResourceHolder,
|
|
||||||
locations=locations,
|
|
||||||
resource_size_x=resource_size_x,
|
|
||||||
resource_size_y=resource_size_y,
|
|
||||||
resource_size_z=resource_size_z,
|
|
||||||
name_prefix=name,
|
|
||||||
)
|
|
||||||
|
|
||||||
len_x, len_y = (num_items_x, num_items_y) if num_items_z == 1 else (num_items_y, num_items_z) if num_items_x == 1 else (num_items_x, num_items_z)
|
|
||||||
|
|
||||||
# 🔑 修改:使用数字命名,最上面是4321,最下面是12,11,10,9
|
|
||||||
# 命名顺序必须与坐标生成顺序一致:层 → 行 → 列
|
|
||||||
keys = []
|
|
||||||
for layer in range(num_items_z): # 遍历每一层
|
|
||||||
for row in range(num_items_y): # 遍历每一行
|
|
||||||
for col in range(num_items_x): # 遍历每一列
|
|
||||||
# 倒序计算全局行号:row=0 应该对应 global_row=0(第1行:4321)
|
|
||||||
# row=1 应该对应 global_row=1(第2行:8765)
|
|
||||||
# row=2 应该对应 global_row=2(第3行:12,11,10,9)
|
|
||||||
# 但前端显示时 row=2 在最上面,所以需要反转
|
|
||||||
reversed_row = (num_items_y - 1 - row) # row=0→reversed_row=2, row=1→reversed_row=1, row=2→reversed_row=0
|
|
||||||
global_row = layer * num_items_y + reversed_row
|
|
||||||
|
|
||||||
# 每行的最大数字 = (global_row + 1) * num_items_x + col_offset
|
|
||||||
base_num = (global_row + 1) * num_items_x + col_offset
|
|
||||||
|
|
||||||
# 从右到左递减:4,3,2,1
|
|
||||||
key = str(base_num - col)
|
|
||||||
keys.append(key)
|
|
||||||
|
|
||||||
sites = {i: site for i, site in zip(keys, _sites.values())}
|
|
||||||
|
|
||||||
return WareHouse(
|
|
||||||
name=name,
|
|
||||||
size_x=dx + item_dx * num_items_x,
|
|
||||||
size_y=dy + item_dy * num_items_y,
|
|
||||||
size_z=dz + item_dz * num_items_z,
|
|
||||||
num_items_x = num_items_x,
|
|
||||||
num_items_y = num_items_y,
|
|
||||||
num_items_z = num_items_z,
|
|
||||||
ordering_layout=layout, # 传递排序方式到 ordering_layout
|
|
||||||
sites=sites,
|
|
||||||
category=category,
|
|
||||||
model=model,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WareHouse(ItemizedCarrier):
|
|
||||||
"""堆栈载体类 - 可容纳16个板位的载体(4层x4行x1列)"""
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
size_x: float,
|
|
||||||
size_y: float,
|
|
||||||
size_z: float,
|
|
||||||
num_items_x: int,
|
|
||||||
num_items_y: int,
|
|
||||||
num_items_z: int,
|
|
||||||
layout: str = "x-y",
|
|
||||||
sites: Optional[Dict[Union[int, str], Optional[ResourcePLR]]] = None,
|
|
||||||
category: str = "warehouse",
|
|
||||||
model: Optional[str] = None,
|
|
||||||
ordering_layout: str = "col-major",
|
|
||||||
**kwargs
|
|
||||||
):
|
|
||||||
super().__init__(
|
|
||||||
name=name,
|
|
||||||
size_x=size_x,
|
|
||||||
size_y=size_y,
|
|
||||||
size_z=size_z,
|
|
||||||
# ordered_items=ordered_items,
|
|
||||||
# ordering=ordering,
|
|
||||||
num_items_x=num_items_x,
|
|
||||||
num_items_y=num_items_y,
|
|
||||||
num_items_z=num_items_z,
|
|
||||||
layout=layout,
|
|
||||||
sites=sites,
|
|
||||||
category=category,
|
|
||||||
model=model,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 保存排序方式,供graphio.py的坐标映射使用
|
|
||||||
# 使用独立属性避免与父类的layout冲突
|
|
||||||
self.ordering_layout = ordering_layout
|
|
||||||
|
|
||||||
def serialize(self) -> dict:
|
|
||||||
"""序列化时保存 ordering_layout 属性"""
|
|
||||||
data = super().serialize()
|
|
||||||
data['ordering_layout'] = self.ordering_layout
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_site_by_layer_position(self, row: int, col: int, layer: int) -> ResourceHolder:
|
|
||||||
if not (0 <= layer < 4 and 0 <= row < 4 and 0 <= col < 1):
|
|
||||||
raise ValueError("无效的位置: layer={}, row={}, col={}".format(layer, row, col))
|
|
||||||
|
|
||||||
site_index = layer * 4 + row * 1 + col
|
|
||||||
return self.sites[site_index]
|
|
||||||
|
|
||||||
def add_rack_to_position(self, row: int, col: int, layer: int, rack) -> None:
|
|
||||||
site = self.get_site_by_layer_position(row, col, layer)
|
|
||||||
site.assign_child_resource(rack)
|
|
||||||
|
|
||||||
def get_rack_at_position(self, row: int, col: int, layer: int):
|
|
||||||
site = self.get_site_by_layer_position(row, col, layer)
|
|
||||||
return site.resource
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
from unilabos.devices.workstation.post_process.post_process_warehouse import WareHouse, warehouse_factory
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# =================== Other ===================
|
|
||||||
|
|
||||||
|
|
||||||
def post_process_warehouse_4x3x1(name: str) -> WareHouse:
|
|
||||||
"""创建post_process 4x3x1仓库"""
|
|
||||||
return warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=4,
|
|
||||||
num_items_y=3,
|
|
||||||
num_items_z=1,
|
|
||||||
dx=10.0,
|
|
||||||
dy=10.0,
|
|
||||||
dz=10.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="warehouse",
|
|
||||||
)
|
|
||||||
|
|
||||||
def post_process_warehouse_4x3x1_2(name: str) -> WareHouse:
|
|
||||||
"""已弃用:创建post_process 4x3x1仓库"""
|
|
||||||
return warehouse_factory(
|
|
||||||
name=name,
|
|
||||||
num_items_x=4,
|
|
||||||
num_items_y=3,
|
|
||||||
num_items_z=1,
|
|
||||||
dx=12.0,
|
|
||||||
dy=12.0,
|
|
||||||
dz=12.0,
|
|
||||||
item_dx=137.0,
|
|
||||||
item_dy=96.0,
|
|
||||||
item_dz=120.0,
|
|
||||||
category="warehouse",
|
|
||||||
)
|
|
||||||
@@ -147,7 +147,7 @@ class WorkstationBase(ABC):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
deck: Optional[Deck],
|
deck: Deck,
|
||||||
*args,
|
*args,
|
||||||
**kwargs, # 必须有kwargs
|
**kwargs, # 必须有kwargs
|
||||||
):
|
):
|
||||||
@@ -349,5 +349,5 @@ class WorkstationBase(ABC):
|
|||||||
|
|
||||||
|
|
||||||
class ProtocolNode(WorkstationBase):
|
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)
|
super().__init__(deck, *args, **kwargs)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Workstation HTTP Service Module
|
|||||||
|
|
||||||
统一的工作站报送接收服务,基于LIMS协议规范:
|
统一的工作站报送接收服务,基于LIMS协议规范:
|
||||||
1. 步骤完成报送 - POST /report/step_finish
|
1. 步骤完成报送 - POST /report/step_finish
|
||||||
2. 通量完成报送 - POST /report/sample_finish
|
2. 通量完成报送 - POST /report/sample_finish
|
||||||
3. 任务完成报送 - POST /report/order_finish
|
3. 任务完成报送 - POST /report/order_finish
|
||||||
4. 批量更新报送 - POST /report/batch_update
|
4. 批量更新报送 - POST /report/batch_update
|
||||||
5. 物料变更报送 - POST /report/material_change
|
5. 物料变更报送 - POST /report/material_change
|
||||||
@@ -22,7 +22,6 @@ from http.server import BaseHTTPRequestHandler, HTTPServer
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from dataclasses import dataclass, asdict
|
from dataclasses import dataclass, asdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from unilabos.utils.log import logger
|
from unilabos.utils.log import logger
|
||||||
|
|
||||||
@@ -55,18 +54,18 @@ class HttpResponse:
|
|||||||
|
|
||||||
class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
||||||
"""工作站HTTP请求处理器"""
|
"""工作站HTTP请求处理器"""
|
||||||
|
|
||||||
def __init__(self, workstation_instance, *args, **kwargs):
|
def __init__(self, workstation_instance, *args, **kwargs):
|
||||||
self.workstation = workstation_instance
|
self.workstation = workstation_instance
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
"""处理POST请求 - 统一的工作站报送接口"""
|
"""处理POST请求 - 统一的工作站报送接口"""
|
||||||
try:
|
try:
|
||||||
# 解析请求路径
|
# 解析请求路径
|
||||||
parsed_path = urlparse(self.path)
|
parsed_path = urlparse(self.path)
|
||||||
endpoint = parsed_path.path
|
endpoint = parsed_path.path
|
||||||
|
|
||||||
# 读取请求体
|
# 读取请求体
|
||||||
content_length = int(self.headers.get('Content-Length', 0))
|
content_length = int(self.headers.get('Content-Length', 0))
|
||||||
if content_length > 0:
|
if content_length > 0:
|
||||||
@@ -74,17 +73,9 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
request_data = json.loads(post_data.decode('utf-8'))
|
request_data = json.loads(post_data.decode('utf-8'))
|
||||||
else:
|
else:
|
||||||
request_data = {}
|
request_data = {}
|
||||||
|
|
||||||
logger.info(f"收到工作站报送: {endpoint} - {request_data.get('token', 'unknown')}")
|
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协议规范)
|
# 统一的报送端点路由(基于LIMS协议规范)
|
||||||
if endpoint == '/report/step_finish':
|
if endpoint == '/report/step_finish':
|
||||||
response = self._handle_step_finish_report(request_data)
|
response = self._handle_step_finish_report(request_data)
|
||||||
@@ -99,8 +90,6 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
response = self._handle_material_change_report(request_data)
|
response = self._handle_material_change_report(request_data)
|
||||||
elif endpoint == '/report/error_handling':
|
elif endpoint == '/report/error_handling':
|
||||||
response = self._handle_error_handling_report(request_data)
|
response = self._handle_error_handling_report(request_data)
|
||||||
elif endpoint == '/report/temperature-cutoff':
|
|
||||||
response = self._handle_temperature_cutoff_report(request_data)
|
|
||||||
# 保留LIMS协议端点以兼容现有系统
|
# 保留LIMS协议端点以兼容现有系统
|
||||||
elif endpoint == '/LIMS/step_finish':
|
elif endpoint == '/LIMS/step_finish':
|
||||||
response = self._handle_step_finish_report(request_data)
|
response = self._handle_step_finish_report(request_data)
|
||||||
@@ -113,19 +102,18 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
success=False,
|
success=False,
|
||||||
message=f"不支持的报送端点: {endpoint}",
|
message=f"不支持的报送端点: {endpoint}",
|
||||||
data={"supported_endpoints": [
|
data={"supported_endpoints": [
|
||||||
"/report/step_finish",
|
"/report/step_finish",
|
||||||
"/report/sample_finish",
|
"/report/sample_finish",
|
||||||
"/report/order_finish",
|
"/report/order_finish",
|
||||||
"/report/batch_update",
|
"/report/batch_update",
|
||||||
"/report/material_change",
|
"/report/material_change",
|
||||||
"/report/error_handling",
|
"/report/error_handling"
|
||||||
"/report/temperature-cutoff"
|
|
||||||
]}
|
]}
|
||||||
)
|
)
|
||||||
|
|
||||||
# 发送响应
|
# 发送响应
|
||||||
self._send_response(response)
|
self._send_response(response)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"处理工作站报送失败: {e}\\n{traceback.format_exc()}")
|
logger.error(f"处理工作站报送失败: {e}\\n{traceback.format_exc()}")
|
||||||
error_response = HttpResponse(
|
error_response = HttpResponse(
|
||||||
@@ -133,18 +121,13 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
message=f"请求处理失败: {str(e)}"
|
message=f"请求处理失败: {str(e)}"
|
||||||
)
|
)
|
||||||
self._send_response(error_response)
|
self._send_response(error_response)
|
||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
"""处理GET请求 - 健康检查和状态查询"""
|
"""处理GET请求 - 健康检查和状态查询"""
|
||||||
try:
|
try:
|
||||||
parsed_path = urlparse(self.path)
|
parsed_path = urlparse(self.path)
|
||||||
endpoint = parsed_path.path
|
endpoint = parsed_path.path
|
||||||
|
|
||||||
try:
|
|
||||||
self._save_raw_request(endpoint, {"method": "GET"})
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if endpoint == '/status':
|
if endpoint == '/status':
|
||||||
response = self._handle_status_check()
|
response = self._handle_status_check()
|
||||||
elif endpoint == '/health':
|
elif endpoint == '/health':
|
||||||
@@ -155,9 +138,9 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
message=f"不支持的查询端点: {endpoint}",
|
message=f"不支持的查询端点: {endpoint}",
|
||||||
data={"supported_endpoints": ["/status", "/health"]}
|
data={"supported_endpoints": ["/status", "/health"]}
|
||||||
)
|
)
|
||||||
|
|
||||||
self._send_response(response)
|
self._send_response(response)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"GET请求处理失败: {e}")
|
logger.error(f"GET请求处理失败: {e}")
|
||||||
error_response = HttpResponse(
|
error_response = HttpResponse(
|
||||||
@@ -165,7 +148,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
message=f"GET请求处理失败: {str(e)}"
|
message=f"GET请求处理失败: {str(e)}"
|
||||||
)
|
)
|
||||||
self._send_response(error_response)
|
self._send_response(error_response)
|
||||||
|
|
||||||
def do_OPTIONS(self):
|
def do_OPTIONS(self):
|
||||||
"""处理OPTIONS请求 - CORS预检请求"""
|
"""处理OPTIONS请求 - CORS预检请求"""
|
||||||
try:
|
try:
|
||||||
@@ -176,12 +159,12 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
||||||
self.send_header('Access-Control-Max-Age', '86400')
|
self.send_header('Access-Control-Max-Age', '86400')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"OPTIONS请求处理失败: {e}")
|
logger.error(f"OPTIONS请求处理失败: {e}")
|
||||||
self.send_response(500)
|
self.send_response(500)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
def _handle_step_finish_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
def _handle_step_finish_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
||||||
"""处理步骤完成报送(统一LIMS协议规范)"""
|
"""处理步骤完成报送(统一LIMS协议规范)"""
|
||||||
try:
|
try:
|
||||||
@@ -192,7 +175,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
success=False,
|
success=False,
|
||||||
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 验证data字段内容
|
# 验证data字段内容
|
||||||
data = request_data['data']
|
data = request_data['data']
|
||||||
data_required_fields = ['orderCode', 'orderName', 'stepName', 'stepId', 'sampleId', 'startTime', 'endTime']
|
data_required_fields = ['orderCode', 'orderName', 'stepName', 'stepId', 'sampleId', 'startTime', 'endTime']
|
||||||
@@ -201,31 +184,31 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
success=False,
|
success=False,
|
||||||
message=f"data字段缺少必要内容: {', '.join(data_missing_fields)}"
|
message=f"data字段缺少必要内容: {', '.join(data_missing_fields)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 创建统一请求对象
|
# 创建统一请求对象
|
||||||
report_request = WorkstationReportRequest(
|
report_request = WorkstationReportRequest(
|
||||||
token=request_data['token'],
|
token=request_data['token'],
|
||||||
request_time=request_data['request_time'],
|
request_time=request_data['request_time'],
|
||||||
data=data
|
data=data
|
||||||
)
|
)
|
||||||
|
|
||||||
# 调用工作站处理方法
|
# 调用工作站处理方法
|
||||||
result = self.workstation.process_step_finish_report(report_request)
|
result = self.workstation.process_step_finish_report(report_request)
|
||||||
|
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=True,
|
success=True,
|
||||||
message=f"步骤完成报送已处理: {data['stepName']} ({data['orderCode']})",
|
message=f"步骤完成报送已处理: {data['stepName']} ({data['orderCode']})",
|
||||||
acknowledgment_id=f"STEP_{int(time.time() * 1000)}_{data['stepId']}",
|
acknowledgment_id=f"STEP_{int(time.time() * 1000)}_{data['stepId']}",
|
||||||
data=result
|
data=result
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"处理步骤完成报送失败: {e}")
|
logger.error(f"处理步骤完成报送失败: {e}")
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=False,
|
success=False,
|
||||||
message=f"步骤完成报送处理失败: {str(e)}"
|
message=f"步骤完成报送处理失败: {str(e)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _handle_sample_finish_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
def _handle_sample_finish_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
||||||
"""处理通量完成报送(统一LIMS协议规范)"""
|
"""处理通量完成报送(统一LIMS协议规范)"""
|
||||||
try:
|
try:
|
||||||
@@ -236,7 +219,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
success=False,
|
success=False,
|
||||||
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 验证data字段内容
|
# 验证data字段内容
|
||||||
data = request_data['data']
|
data = request_data['data']
|
||||||
data_required_fields = ['orderCode', 'orderName', 'sampleId', 'startTime', 'endTime', 'status']
|
data_required_fields = ['orderCode', 'orderName', 'sampleId', 'startTime', 'endTime', 'status']
|
||||||
@@ -245,37 +228,37 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
success=False,
|
success=False,
|
||||||
message=f"data字段缺少必要内容: {', '.join(data_missing_fields)}"
|
message=f"data字段缺少必要内容: {', '.join(data_missing_fields)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 创建统一请求对象
|
# 创建统一请求对象
|
||||||
report_request = WorkstationReportRequest(
|
report_request = WorkstationReportRequest(
|
||||||
token=request_data['token'],
|
token=request_data['token'],
|
||||||
request_time=request_data['request_time'],
|
request_time=request_data['request_time'],
|
||||||
data=data
|
data=data
|
||||||
)
|
)
|
||||||
|
|
||||||
# 调用工作站处理方法
|
# 调用工作站处理方法
|
||||||
result = self.workstation.process_sample_finish_report(report_request)
|
result = self.workstation.process_sample_finish_report(report_request)
|
||||||
|
|
||||||
status_names = {
|
status_names = {
|
||||||
"0": "待生产", "2": "进样", "10": "开始",
|
"0": "待生产", "2": "进样", "10": "开始",
|
||||||
"20": "完成", "-2": "异常停止", "-3": "人工停止"
|
"20": "完成", "-2": "异常停止", "-3": "人工停止"
|
||||||
}
|
}
|
||||||
status_desc = status_names.get(str(data['status']), f"状态{data['status']}")
|
status_desc = status_names.get(str(data['status']), f"状态{data['status']}")
|
||||||
|
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=True,
|
success=True,
|
||||||
message=f"通量完成报送已处理: {data['sampleId']} ({data['orderCode']}) - {status_desc}",
|
message=f"通量完成报送已处理: {data['sampleId']} ({data['orderCode']}) - {status_desc}",
|
||||||
acknowledgment_id=f"SAMPLE_{int(time.time() * 1000)}_{data['sampleId']}",
|
acknowledgment_id=f"SAMPLE_{int(time.time() * 1000)}_{data['sampleId']}",
|
||||||
data=result
|
data=result
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"处理通量完成报送失败: {e}")
|
logger.error(f"处理通量完成报送失败: {e}")
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=False,
|
success=False,
|
||||||
message=f"通量完成报送处理失败: {str(e)}"
|
message=f"通量完成报送处理失败: {str(e)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _handle_order_finish_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
def _handle_order_finish_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
||||||
"""处理任务完成报送(统一LIMS协议规范)"""
|
"""处理任务完成报送(统一LIMS协议规范)"""
|
||||||
try:
|
try:
|
||||||
@@ -286,7 +269,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
success=False,
|
success=False,
|
||||||
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 验证data字段内容
|
# 验证data字段内容
|
||||||
data = request_data['data']
|
data = request_data['data']
|
||||||
data_required_fields = ['orderCode', 'orderName', 'startTime', 'endTime', 'status']
|
data_required_fields = ['orderCode', 'orderName', 'startTime', 'endTime', 'status']
|
||||||
@@ -295,7 +278,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
success=False,
|
success=False,
|
||||||
message=f"data字段缺少必要内容: {', '.join(data_missing_fields)}"
|
message=f"data字段缺少必要内容: {', '.join(data_missing_fields)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 处理物料使用记录
|
# 处理物料使用记录
|
||||||
used_materials = []
|
used_materials = []
|
||||||
if 'usedMaterials' in data:
|
if 'usedMaterials' in data:
|
||||||
@@ -307,85 +290,41 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
usedQuantity=material_data.get('usedQuantity', 0.0)
|
usedQuantity=material_data.get('usedQuantity', 0.0)
|
||||||
)
|
)
|
||||||
used_materials.append(material)
|
used_materials.append(material)
|
||||||
|
|
||||||
# 创建统一请求对象
|
# 创建统一请求对象
|
||||||
report_request = WorkstationReportRequest(
|
report_request = WorkstationReportRequest(
|
||||||
token=request_data['token'],
|
token=request_data['token'],
|
||||||
request_time=request_data['request_time'],
|
request_time=request_data['request_time'],
|
||||||
data=data
|
data=data
|
||||||
)
|
)
|
||||||
|
|
||||||
# 调用工作站处理方法
|
# 调用工作站处理方法
|
||||||
result = self.workstation.process_order_finish_report(report_request, used_materials)
|
result = self.workstation.process_order_finish_report(report_request, used_materials)
|
||||||
|
|
||||||
status_names = {"30": "完成", "-11": "异常停止", "-12": "人工停止"}
|
status_names = {"30": "完成", "-11": "异常停止", "-12": "人工停止"}
|
||||||
status_desc = status_names.get(str(data['status']), f"状态{data['status']}")
|
status_desc = status_names.get(str(data['status']), f"状态{data['status']}")
|
||||||
|
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=True,
|
success=True,
|
||||||
message=f"任务完成报送已处理: {data['orderName']} ({data['orderCode']}) - {status_desc}",
|
message=f"任务完成报送已处理: {data['orderName']} ({data['orderCode']}) - {status_desc}",
|
||||||
acknowledgment_id=f"ORDER_{int(time.time() * 1000)}_{data['orderCode']}",
|
acknowledgment_id=f"ORDER_{int(time.time() * 1000)}_{data['orderCode']}",
|
||||||
data=result
|
data=result
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"处理任务完成报送失败: {e}")
|
logger.error(f"处理任务完成报送失败: {e}")
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=False,
|
success=False,
|
||||||
message=f"任务完成报送处理失败: {str(e)}"
|
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:
|
def _handle_batch_update_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
||||||
"""处理批量报送"""
|
"""处理批量报送"""
|
||||||
try:
|
try:
|
||||||
step_updates = request_data.get('step_updates', [])
|
step_updates = request_data.get('step_updates', [])
|
||||||
sample_updates = request_data.get('sample_updates', [])
|
sample_updates = request_data.get('sample_updates', [])
|
||||||
order_updates = request_data.get('order_updates', [])
|
order_updates = request_data.get('order_updates', [])
|
||||||
|
|
||||||
results = {
|
results = {
|
||||||
'step_results': [],
|
'step_results': [],
|
||||||
'sample_results': [],
|
'sample_results': [],
|
||||||
@@ -393,7 +332,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
'total_processed': 0,
|
'total_processed': 0,
|
||||||
'total_failed': 0
|
'total_failed': 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# 处理批量步骤更新
|
# 处理批量步骤更新
|
||||||
for step_data in step_updates:
|
for step_data in step_updates:
|
||||||
try:
|
try:
|
||||||
@@ -408,7 +347,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
results['step_results'].append(HttpResponse(success=False, message=str(e)))
|
results['step_results'].append(HttpResponse(success=False, message=str(e)))
|
||||||
results['total_failed'] += 1
|
results['total_failed'] += 1
|
||||||
|
|
||||||
# 处理批量通量更新
|
# 处理批量通量更新
|
||||||
for sample_data in sample_updates:
|
for sample_data in sample_updates:
|
||||||
try:
|
try:
|
||||||
@@ -423,7 +362,7 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
results['sample_results'].append(HttpResponse(success=False, message=str(e)))
|
results['sample_results'].append(HttpResponse(success=False, message=str(e)))
|
||||||
results['total_failed'] += 1
|
results['total_failed'] += 1
|
||||||
|
|
||||||
# 处理批量任务更新
|
# 处理批量任务更新
|
||||||
for order_data in order_updates:
|
for order_data in order_updates:
|
||||||
try:
|
try:
|
||||||
@@ -438,33 +377,33 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
results['order_results'].append(HttpResponse(success=False, message=str(e)))
|
results['order_results'].append(HttpResponse(success=False, message=str(e)))
|
||||||
results['total_failed'] += 1
|
results['total_failed'] += 1
|
||||||
|
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=results['total_failed'] == 0,
|
success=results['total_failed'] == 0,
|
||||||
message=f"批量报送处理完成: {results['total_processed']} 成功, {results['total_failed']} 失败",
|
message=f"批量报送处理完成: {results['total_processed']} 成功, {results['total_failed']} 失败",
|
||||||
acknowledgment_id=f"BATCH_{int(time.time() * 1000)}",
|
acknowledgment_id=f"BATCH_{int(time.time() * 1000)}",
|
||||||
data=results
|
data=results
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"处理批量报送失败: {e}")
|
logger.error(f"处理批量报送失败: {e}")
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=False,
|
success=False,
|
||||||
message=f"批量报送处理失败: {str(e)}"
|
message=f"批量报送处理失败: {str(e)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _handle_material_change_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
def _handle_material_change_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
||||||
"""处理物料变更报送"""
|
"""处理物料变更报送"""
|
||||||
try:
|
try:
|
||||||
# 验证必需字段
|
# 验证必需字段
|
||||||
if 'brand' in request_data:
|
if 'brand' in request_data:
|
||||||
if request_data['brand'] == "bioyond": # 奔曜
|
if request_data['brand'] == "bioyond": # 奔曜
|
||||||
material_data = request_data["text"]
|
error_msg = request_data["text"]
|
||||||
logger.info(f"收到奔曜物料变更报送: {material_data}")
|
logger.info(f"收到奔曜错误处理报送: {error_msg}")
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=True,
|
success=True,
|
||||||
message=f"物料变更报送已收到: {material_data}",
|
message=f"错误处理报送已收到: {error_msg}",
|
||||||
acknowledgment_id=f"MATERIAL_{int(time.time() * 1000)}_{material_data.get('id', 'unknown')}",
|
acknowledgment_id=f"ERROR_{int(time.time() * 1000)}_{error_msg.get('action_id', 'unknown')}",
|
||||||
data=None
|
data=None
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -478,24 +417,24 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
success=False,
|
success=False,
|
||||||
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 调用工作站的处理方法
|
# 调用工作站的处理方法
|
||||||
result = self.workstation.process_material_change_report(request_data)
|
result = self.workstation.process_material_change_report(request_data)
|
||||||
|
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=True,
|
success=True,
|
||||||
message=f"物料变更报送已处理: {request_data['resource_id']} ({request_data['change_type']})",
|
message=f"物料变更报送已处理: {request_data['resource_id']} ({request_data['change_type']})",
|
||||||
acknowledgment_id=f"MATERIAL_{int(time.time() * 1000)}_{request_data['resource_id']}",
|
acknowledgment_id=f"MATERIAL_{int(time.time() * 1000)}_{request_data['resource_id']}",
|
||||||
data=result
|
data=result
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"处理物料变更报送失败: {e}")
|
logger.error(f"处理物料变更报送失败: {e}")
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=False,
|
success=False,
|
||||||
message=f"物料变更报送处理失败: {str(e)}"
|
message=f"物料变更报送处理失败: {str(e)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _handle_error_handling_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
def _handle_error_handling_report(self, request_data: Dict[str, Any]) -> HttpResponse:
|
||||||
"""处理错误处理报送"""
|
"""处理错误处理报送"""
|
||||||
try:
|
try:
|
||||||
@@ -507,13 +446,13 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
success=False,
|
success=False,
|
||||||
message="奔曜格式缺少text字段"
|
message="奔曜格式缺少text字段"
|
||||||
)
|
)
|
||||||
|
|
||||||
error_data = request_data["text"]
|
error_data = request_data["text"]
|
||||||
logger.info(f"收到奔曜错误处理报送: {error_data}")
|
logger.info(f"收到奔曜错误处理报送: {error_data}")
|
||||||
|
|
||||||
# 调用工作站的处理方法
|
# 调用工作站的处理方法
|
||||||
result = self.workstation.handle_external_error(error_data)
|
result = self.workstation.handle_external_error(error_data)
|
||||||
|
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=True,
|
success=True,
|
||||||
message=f"错误处理报送已收到: 任务{error_data.get('task', 'unknown')}, 错误代码{error_data.get('code', 'unknown')}",
|
message=f"错误处理报送已收到: 任务{error_data.get('task', 'unknown')}, 错误代码{error_data.get('code', 'unknown')}",
|
||||||
@@ -528,50 +467,42 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
success=False,
|
success=False,
|
||||||
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
message=f"缺少必要字段: {', '.join(missing_fields)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 调用工作站的处理方法
|
# 调用工作站的处理方法
|
||||||
result = self.workstation.handle_external_error(request_data)
|
result = self.workstation.handle_external_error(request_data)
|
||||||
|
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=True,
|
success=True,
|
||||||
message=f"错误处理报送已处理: {request_data['error_type']} - {request_data['error_message']}",
|
message=f"错误处理报送已处理: {request_data['error_type']} - {request_data['error_message']}",
|
||||||
acknowledgment_id=f"ERROR_{int(time.time() * 1000)}_{request_data.get('action_id', 'unknown')}",
|
acknowledgment_id=f"ERROR_{int(time.time() * 1000)}_{request_data.get('action_id', 'unknown')}",
|
||||||
data=result
|
data=result
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"处理错误处理报送失败: {e}")
|
logger.error(f"处理错误处理报送失败: {e}")
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
success=False,
|
success=False,
|
||||||
message=f"错误处理报送处理失败: {str(e)}"
|
message=f"错误处理报送处理失败: {str(e)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _handle_status_check(self) -> HttpResponse:
|
def _handle_status_check(self) -> HttpResponse:
|
||||||
"""处理状态查询"""
|
"""处理状态查询"""
|
||||||
try:
|
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(
|
return HttpResponse(
|
||||||
success=True,
|
success=True,
|
||||||
message="工作站报送服务正常运行",
|
message="工作站报送服务正常运行",
|
||||||
data={
|
data={
|
||||||
"workstation_id": device_id,
|
"workstation_id": self.workstation.device_id,
|
||||||
"service_type": "unified_reporting_service",
|
"service_type": "unified_reporting_service",
|
||||||
"uptime": time.time() - getattr(self.workstation, '_start_time', time.time()),
|
"uptime": time.time() - getattr(self.workstation, '_start_time', time.time()),
|
||||||
"reports_received": getattr(self.workstation, '_reports_received_count', 0),
|
"reports_received": getattr(self.workstation, '_reports_received_count', 0),
|
||||||
"supported_endpoints": [
|
"supported_endpoints": [
|
||||||
"POST /report/step_finish",
|
"POST /report/step_finish",
|
||||||
"POST /report/sample_finish",
|
"POST /report/sample_finish",
|
||||||
"POST /report/order_finish",
|
"POST /report/order_finish",
|
||||||
"POST /report/batch_update",
|
"POST /report/batch_update",
|
||||||
"POST /report/material_change",
|
"POST /report/material_change",
|
||||||
"POST /report/error_handling",
|
"POST /report/error_handling",
|
||||||
"POST /report/temperature-cutoff",
|
|
||||||
"GET /status",
|
"GET /status",
|
||||||
"GET /health"
|
"GET /health"
|
||||||
]
|
]
|
||||||
@@ -583,52 +514,36 @@ class WorkstationHTTPHandler(BaseHTTPRequestHandler):
|
|||||||
success=False,
|
success=False,
|
||||||
message=f"状态查询失败: {str(e)}"
|
message=f"状态查询失败: {str(e)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _send_response(self, response: HttpResponse):
|
def _send_response(self, response: HttpResponse):
|
||||||
"""发送响应"""
|
"""发送响应"""
|
||||||
try:
|
try:
|
||||||
# 设置响应状态码
|
# 设置响应状态码
|
||||||
status_code = 200 if response.success else 400
|
status_code = 200 if response.success else 400
|
||||||
self.send_response(status_code)
|
self.send_response(status_code)
|
||||||
|
|
||||||
# 设置响应头
|
# 设置响应头
|
||||||
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
||||||
self.send_header('Access-Control-Allow-Origin', '*')
|
self.send_header('Access-Control-Allow-Origin', '*')
|
||||||
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
||||||
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
self.send_header('Access-Control-Allow-Headers', 'Content-Type')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
# 发送响应体
|
# 发送响应体
|
||||||
response_json = json.dumps(asdict(response), ensure_ascii=False, indent=2)
|
response_json = json.dumps(asdict(response), ensure_ascii=False, indent=2)
|
||||||
self.wfile.write(response_json.encode('utf-8'))
|
self.wfile.write(response_json.encode('utf-8'))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"发送响应失败: {e}")
|
logger.error(f"发送响应失败: {e}")
|
||||||
|
|
||||||
def log_message(self, format, *args):
|
def log_message(self, format, *args):
|
||||||
"""重写日志方法"""
|
"""重写日志方法"""
|
||||||
logger.debug(f"HTTP请求: {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:
|
class WorkstationHTTPService:
|
||||||
"""工作站HTTP服务"""
|
"""工作站HTTP服务"""
|
||||||
|
|
||||||
def __init__(self, workstation_instance, host: str = "127.0.0.1", port: int = 8080):
|
def __init__(self, workstation_instance, host: str = "127.0.0.1", port: int = 8080):
|
||||||
self.workstation = workstation_instance
|
self.workstation = workstation_instance
|
||||||
self.host = host
|
self.host = host
|
||||||
@@ -636,42 +551,31 @@ class WorkstationHTTPService:
|
|||||||
self.server = None
|
self.server = None
|
||||||
self.server_thread = None
|
self.server_thread = None
|
||||||
self.running = False
|
self.running = False
|
||||||
|
|
||||||
# 初始化统计信息
|
# 初始化统计信息
|
||||||
self.workstation._start_time = time.time()
|
self.workstation._start_time = time.time()
|
||||||
self.workstation._reports_received_count = 0
|
self.workstation._reports_received_count = 0
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""启动HTTP服务"""
|
"""启动HTTP服务"""
|
||||||
try:
|
try:
|
||||||
# 创建处理器工厂函数
|
# 创建处理器工厂函数
|
||||||
def handler_factory(*args, **kwargs):
|
def handler_factory(*args, **kwargs):
|
||||||
return WorkstationHTTPHandler(self.workstation, *args, **kwargs)
|
return WorkstationHTTPHandler(self.workstation, *args, **kwargs)
|
||||||
|
|
||||||
# 创建HTTP服务器
|
# 创建HTTP服务器
|
||||||
self.server = HTTPServer((self.host, self.port), handler_factory)
|
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(
|
self.server_thread = threading.Thread(
|
||||||
target=self._run_server,
|
target=self._run_server,
|
||||||
daemon=True,
|
daemon=True,
|
||||||
name=f"WorkstationHTTP-{device_id}"
|
name=f"WorkstationHTTP-{self.workstation.device_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
self.running = True
|
self.running = True
|
||||||
self.server_thread.start()
|
self.server_thread.start()
|
||||||
|
|
||||||
logger.info(f"工作站HTTP报送服务已启动: http://{self.host}:{self.port}")
|
logger.info(f"工作站HTTP报送服务已启动: http://{self.host}:{self.port}")
|
||||||
logger.info("统一的报送端点 (基于LIMS协议规范):")
|
logger.info("统一的报送端点 (基于LIMS协议规范):")
|
||||||
logger.info(" - POST /report/step_finish # 步骤完成报送")
|
logger.info(" - POST /report/step_finish # 步骤完成报送")
|
||||||
@@ -681,7 +585,6 @@ class WorkstationHTTPService:
|
|||||||
logger.info("扩展报送端点:")
|
logger.info("扩展报送端点:")
|
||||||
logger.info(" - POST /report/material_change # 物料变更报送")
|
logger.info(" - POST /report/material_change # 物料变更报送")
|
||||||
logger.info(" - POST /report/error_handling # 错误处理报送")
|
logger.info(" - POST /report/error_handling # 错误处理报送")
|
||||||
logger.info(" - POST /report/temperature-cutoff # 温度/粘度报送")
|
|
||||||
logger.info("兼容端点:")
|
logger.info("兼容端点:")
|
||||||
logger.info(" - POST /LIMS/step_finish # 兼容LIMS步骤完成")
|
logger.info(" - POST /LIMS/step_finish # 兼容LIMS步骤完成")
|
||||||
logger.info(" - POST /LIMS/preintake_finish # 兼容LIMS通量完成")
|
logger.info(" - POST /LIMS/preintake_finish # 兼容LIMS通量完成")
|
||||||
@@ -689,33 +592,33 @@ class WorkstationHTTPService:
|
|||||||
logger.info("服务端点:")
|
logger.info("服务端点:")
|
||||||
logger.info(" - GET /status # 服务状态查询")
|
logger.info(" - GET /status # 服务状态查询")
|
||||||
logger.info(" - GET /health # 健康检查")
|
logger.info(" - GET /health # 健康检查")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"启动HTTP服务失败: {e}")
|
logger.error(f"启动HTTP服务失败: {e}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""停止HTTP服务"""
|
"""停止HTTP服务"""
|
||||||
try:
|
try:
|
||||||
if self.running and self.server:
|
if self.running and self.server:
|
||||||
logger.info("正在停止工作站HTTP报送服务...")
|
logger.info("正在停止工作站HTTP报送服务...")
|
||||||
self.running = False
|
self.running = False
|
||||||
|
|
||||||
# 停止serve_forever循环
|
# 停止serve_forever循环
|
||||||
self.server.shutdown()
|
self.server.shutdown()
|
||||||
|
|
||||||
# 等待服务器线程结束
|
# 等待服务器线程结束
|
||||||
if self.server_thread and self.server_thread.is_alive():
|
if self.server_thread and self.server_thread.is_alive():
|
||||||
self.server_thread.join(timeout=5.0)
|
self.server_thread.join(timeout=5.0)
|
||||||
|
|
||||||
# 关闭服务器套接字
|
# 关闭服务器套接字
|
||||||
self.server.server_close()
|
self.server.server_close()
|
||||||
|
|
||||||
logger.info("工作站HTTP报送服务已停止")
|
logger.info("工作站HTTP报送服务已停止")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"停止HTTP服务失败: {e}")
|
logger.error(f"停止HTTP服务失败: {e}")
|
||||||
|
|
||||||
def _run_server(self):
|
def _run_server(self):
|
||||||
"""运行HTTP服务器"""
|
"""运行HTTP服务器"""
|
||||||
try:
|
try:
|
||||||
@@ -726,12 +629,12 @@ class WorkstationHTTPService:
|
|||||||
logger.error(f"HTTP服务运行错误: {e}")
|
logger.error(f"HTTP服务运行错误: {e}")
|
||||||
finally:
|
finally:
|
||||||
logger.info("HTTP服务器线程已退出")
|
logger.info("HTTP服务器线程已退出")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_running(self) -> bool:
|
def is_running(self) -> bool:
|
||||||
"""检查服务是否正在运行"""
|
"""检查服务是否正在运行"""
|
||||||
return self.running and self.server_thread and self.server_thread.is_alive()
|
return self.running and self.server_thread and self.server_thread.is_alive()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def service_url(self) -> str:
|
def service_url(self) -> str:
|
||||||
"""获取服务URL"""
|
"""获取服务URL"""
|
||||||
@@ -745,7 +648,7 @@ class MaterialChangeReport:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TaskExecutionReport:
|
class TaskExecutionReport:
|
||||||
"""已废弃:任务执行报送,请使用统一的WorkstationReportRequest"""
|
"""已废弃:任务执行报送,请使用统一的WorkstationReportRequest"""
|
||||||
pass
|
pass
|
||||||
@@ -765,43 +668,40 @@ __all__ = [
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# 简单测试HTTP服务
|
# 简单测试HTTP服务
|
||||||
class BioyondWorkstation:
|
class DummyWorkstation:
|
||||||
device_id = "WS-001"
|
device_id = "WS-001"
|
||||||
|
|
||||||
def process_step_finish_report(self, report_request):
|
def process_step_finish_report(self, report_request):
|
||||||
return {"processed": True}
|
return {"processed": True}
|
||||||
|
|
||||||
def process_sample_finish_report(self, report_request):
|
def process_sample_finish_report(self, report_request):
|
||||||
return {"processed": True}
|
return {"processed": True}
|
||||||
|
|
||||||
def process_order_finish_report(self, report_request, used_materials):
|
def process_order_finish_report(self, report_request, used_materials):
|
||||||
return {"processed": True}
|
return {"processed": True}
|
||||||
|
|
||||||
def process_material_change_report(self, report_data):
|
def process_material_change_report(self, report_data):
|
||||||
return {"processed": True}
|
return {"processed": True}
|
||||||
|
|
||||||
def handle_external_error(self, error_data):
|
def handle_external_error(self, error_data):
|
||||||
return {"handled": True}
|
return {"handled": True}
|
||||||
|
|
||||||
def process_temperature_cutoff_report(self, report_request):
|
workstation = DummyWorkstation()
|
||||||
return {"processed": True, "metrics": report_request.data}
|
|
||||||
|
|
||||||
workstation = BioyondWorkstation()
|
|
||||||
http_service = WorkstationHTTPService(workstation)
|
http_service = WorkstationHTTPService(workstation)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
http_service.start()
|
http_service.start()
|
||||||
print(f"测试服务器已启动: {http_service.service_url}")
|
print(f"测试服务器已启动: {http_service.service_url}")
|
||||||
print("按 Ctrl+C 停止服务器")
|
print("按 Ctrl+C 停止服务器")
|
||||||
print("服务将持续运行,等待接收HTTP请求...")
|
print("服务将持续运行,等待接收HTTP请求...")
|
||||||
|
|
||||||
# 保持服务器运行 - 使用更好的等待机制
|
# 保持服务器运行 - 使用更好的等待机制
|
||||||
try:
|
try:
|
||||||
while http_service.is_running:
|
while http_service.is_running:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n接收到停止信号...")
|
print("\n接收到停止信号...")
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n正在停止服务器...")
|
print("\n正在停止服务器...")
|
||||||
http_service.stop()
|
http_service.stop()
|
||||||
@@ -809,3 +709,4 @@ if __name__ == "__main__":
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"服务器运行错误: {e}")
|
print(f"服务器运行错误: {e}")
|
||||||
http_service.stop()
|
http_service.stop()
|
||||||
|
|
||||||
|
|||||||
@@ -1,589 +0,0 @@
|
|||||||
workstation.bioyond_dispensing_station:
|
|
||||||
category:
|
|
||||||
- workstation
|
|
||||||
- bioyond
|
|
||||||
class:
|
|
||||||
action_value_mappings:
|
|
||||||
auto-batch_create_90_10_vial_feeding_tasks:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
delay_time: null
|
|
||||||
hold_m_name: null
|
|
||||||
liquid_material_name: NMP
|
|
||||||
speed: null
|
|
||||||
temperature: null
|
|
||||||
titration: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
delay_time:
|
|
||||||
type: string
|
|
||||||
hold_m_name:
|
|
||||||
type: string
|
|
||||||
liquid_material_name:
|
|
||||||
default: NMP
|
|
||||||
type: string
|
|
||||||
speed:
|
|
||||||
type: string
|
|
||||||
temperature:
|
|
||||||
type: string
|
|
||||||
titration:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- titration
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: batch_create_90_10_vial_feeding_tasks参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-batch_create_diamine_solution_tasks:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
delay_time: null
|
|
||||||
liquid_material_name: NMP
|
|
||||||
solutions: null
|
|
||||||
speed: null
|
|
||||||
temperature: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
delay_time:
|
|
||||||
type: string
|
|
||||||
liquid_material_name:
|
|
||||||
default: NMP
|
|
||||||
type: string
|
|
||||||
solutions:
|
|
||||||
type: string
|
|
||||||
speed:
|
|
||||||
type: string
|
|
||||||
temperature:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- solutions
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: batch_create_diamine_solution_tasks参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-brief_step_parameters:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
data: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
data:
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- data
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: brief_step_parameters参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-compute_experiment_design:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
m_tot: '70'
|
|
||||||
ratio: null
|
|
||||||
titration_percent: '0.03'
|
|
||||||
wt_percent: '0.25'
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
m_tot:
|
|
||||||
default: '70'
|
|
||||||
type: string
|
|
||||||
ratio:
|
|
||||||
type: object
|
|
||||||
titration_percent:
|
|
||||||
default: '0.03'
|
|
||||||
type: string
|
|
||||||
wt_percent:
|
|
||||||
default: '0.25'
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- ratio
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
properties:
|
|
||||||
feeding_order:
|
|
||||||
items: {}
|
|
||||||
title: Feeding Order
|
|
||||||
type: array
|
|
||||||
return_info:
|
|
||||||
title: Return Info
|
|
||||||
type: string
|
|
||||||
solutions:
|
|
||||||
items: {}
|
|
||||||
title: Solutions
|
|
||||||
type: array
|
|
||||||
solvents:
|
|
||||||
additionalProperties: true
|
|
||||||
title: Solvents
|
|
||||||
type: object
|
|
||||||
titration:
|
|
||||||
additionalProperties: true
|
|
||||||
title: Titration
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- solutions
|
|
||||||
- titration
|
|
||||||
- solvents
|
|
||||||
- feeding_order
|
|
||||||
- return_info
|
|
||||||
title: ComputeExperimentDesignReturn
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: compute_experiment_design参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-process_order_finish_report:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
report_request: null
|
|
||||||
used_materials: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
report_request:
|
|
||||||
type: string
|
|
||||||
used_materials:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- report_request
|
|
||||||
- used_materials
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: process_order_finish_report参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-project_order_report:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
order_id: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
order_id:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- order_id
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: project_order_report参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-query_resource_by_name:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
material_name: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
material_name:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- material_name
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: query_resource_by_name参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-transfer_materials_to_reaction_station:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
target_device_id: null
|
|
||||||
transfer_groups: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
target_device_id:
|
|
||||||
type: string
|
|
||||||
transfer_groups:
|
|
||||||
type: array
|
|
||||||
required:
|
|
||||||
- target_device_id
|
|
||||||
- transfer_groups
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: transfer_materials_to_reaction_station参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-wait_for_multiple_orders_and_get_reports:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
batch_create_result: null
|
|
||||||
check_interval: 10
|
|
||||||
timeout: 7200
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
batch_create_result:
|
|
||||||
type: string
|
|
||||||
check_interval:
|
|
||||||
default: 10
|
|
||||||
type: integer
|
|
||||||
timeout:
|
|
||||||
default: 7200
|
|
||||||
type: integer
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: wait_for_multiple_orders_and_get_reports参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-workflow_sample_locations:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
workflow_id: null
|
|
||||||
handles: {}
|
|
||||||
placeholder_keys: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
workflow_id:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- workflow_id
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: workflow_sample_locations参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
create_90_10_vial_feeding_task:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
delay_time: delay_time
|
|
||||||
hold_m_name: hold_m_name
|
|
||||||
order_name: order_name
|
|
||||||
percent_10_1_assign_material_name: percent_10_1_assign_material_name
|
|
||||||
percent_10_1_liquid_material_name: percent_10_1_liquid_material_name
|
|
||||||
percent_10_1_target_weigh: percent_10_1_target_weigh
|
|
||||||
percent_10_1_volume: percent_10_1_volume
|
|
||||||
percent_10_2_assign_material_name: percent_10_2_assign_material_name
|
|
||||||
percent_10_2_liquid_material_name: percent_10_2_liquid_material_name
|
|
||||||
percent_10_2_target_weigh: percent_10_2_target_weigh
|
|
||||||
percent_10_2_volume: percent_10_2_volume
|
|
||||||
percent_10_3_assign_material_name: percent_10_3_assign_material_name
|
|
||||||
percent_10_3_liquid_material_name: percent_10_3_liquid_material_name
|
|
||||||
percent_10_3_target_weigh: percent_10_3_target_weigh
|
|
||||||
percent_10_3_volume: percent_10_3_volume
|
|
||||||
percent_90_1_assign_material_name: percent_90_1_assign_material_name
|
|
||||||
percent_90_1_target_weigh: percent_90_1_target_weigh
|
|
||||||
percent_90_2_assign_material_name: percent_90_2_assign_material_name
|
|
||||||
percent_90_2_target_weigh: percent_90_2_target_weigh
|
|
||||||
percent_90_3_assign_material_name: percent_90_3_assign_material_name
|
|
||||||
percent_90_3_target_weigh: percent_90_3_target_weigh
|
|
||||||
speed: speed
|
|
||||||
temperature: temperature
|
|
||||||
goal_default:
|
|
||||||
delay_time: ''
|
|
||||||
hold_m_name: ''
|
|
||||||
order_name: ''
|
|
||||||
percent_10_1_assign_material_name: ''
|
|
||||||
percent_10_1_liquid_material_name: ''
|
|
||||||
percent_10_1_target_weigh: ''
|
|
||||||
percent_10_1_volume: ''
|
|
||||||
percent_10_2_assign_material_name: ''
|
|
||||||
percent_10_2_liquid_material_name: ''
|
|
||||||
percent_10_2_target_weigh: ''
|
|
||||||
percent_10_2_volume: ''
|
|
||||||
percent_10_3_assign_material_name: ''
|
|
||||||
percent_10_3_liquid_material_name: ''
|
|
||||||
percent_10_3_target_weigh: ''
|
|
||||||
percent_10_3_volume: ''
|
|
||||||
percent_90_1_assign_material_name: ''
|
|
||||||
percent_90_1_target_weigh: ''
|
|
||||||
percent_90_2_assign_material_name: ''
|
|
||||||
percent_90_2_target_weigh: ''
|
|
||||||
percent_90_3_assign_material_name: ''
|
|
||||||
percent_90_3_target_weigh: ''
|
|
||||||
speed: ''
|
|
||||||
temperature: ''
|
|
||||||
handles: {}
|
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
title: DispenStationVialFeed_Feedback
|
|
||||||
type: object
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
delay_time:
|
|
||||||
type: string
|
|
||||||
hold_m_name:
|
|
||||||
type: string
|
|
||||||
order_name:
|
|
||||||
type: string
|
|
||||||
percent_10_1_assign_material_name:
|
|
||||||
type: string
|
|
||||||
percent_10_1_liquid_material_name:
|
|
||||||
type: string
|
|
||||||
percent_10_1_target_weigh:
|
|
||||||
type: string
|
|
||||||
percent_10_1_volume:
|
|
||||||
type: string
|
|
||||||
percent_10_2_assign_material_name:
|
|
||||||
type: string
|
|
||||||
percent_10_2_liquid_material_name:
|
|
||||||
type: string
|
|
||||||
percent_10_2_target_weigh:
|
|
||||||
type: string
|
|
||||||
percent_10_2_volume:
|
|
||||||
type: string
|
|
||||||
percent_10_3_assign_material_name:
|
|
||||||
type: string
|
|
||||||
percent_10_3_liquid_material_name:
|
|
||||||
type: string
|
|
||||||
percent_10_3_target_weigh:
|
|
||||||
type: string
|
|
||||||
percent_10_3_volume:
|
|
||||||
type: string
|
|
||||||
percent_90_1_assign_material_name:
|
|
||||||
type: string
|
|
||||||
percent_90_1_target_weigh:
|
|
||||||
type: string
|
|
||||||
percent_90_2_assign_material_name:
|
|
||||||
type: string
|
|
||||||
percent_90_2_target_weigh:
|
|
||||||
type: string
|
|
||||||
percent_90_3_assign_material_name:
|
|
||||||
type: string
|
|
||||||
percent_90_3_target_weigh:
|
|
||||||
type: string
|
|
||||||
speed:
|
|
||||||
type: string
|
|
||||||
temperature:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- order_name
|
|
||||||
- percent_90_1_assign_material_name
|
|
||||||
- percent_90_1_target_weigh
|
|
||||||
- percent_90_2_assign_material_name
|
|
||||||
- percent_90_2_target_weigh
|
|
||||||
- percent_90_3_assign_material_name
|
|
||||||
- percent_90_3_target_weigh
|
|
||||||
- percent_10_1_assign_material_name
|
|
||||||
- percent_10_1_target_weigh
|
|
||||||
- percent_10_1_volume
|
|
||||||
- percent_10_1_liquid_material_name
|
|
||||||
- percent_10_2_assign_material_name
|
|
||||||
- percent_10_2_target_weigh
|
|
||||||
- percent_10_2_volume
|
|
||||||
- percent_10_2_liquid_material_name
|
|
||||||
- percent_10_3_assign_material_name
|
|
||||||
- percent_10_3_target_weigh
|
|
||||||
- percent_10_3_volume
|
|
||||||
- percent_10_3_liquid_material_name
|
|
||||||
- speed
|
|
||||||
- temperature
|
|
||||||
- delay_time
|
|
||||||
- hold_m_name
|
|
||||||
title: DispenStationVialFeed_Goal
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
properties:
|
|
||||||
return_info:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- return_info
|
|
||||||
title: DispenStationVialFeed_Result
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: DispenStationVialFeed
|
|
||||||
type: object
|
|
||||||
type: DispenStationVialFeed
|
|
||||||
create_diamine_solution_task:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
delay_time: delay_time
|
|
||||||
hold_m_name: hold_m_name
|
|
||||||
liquid_material_name: liquid_material_name
|
|
||||||
material_name: material_name
|
|
||||||
order_name: order_name
|
|
||||||
speed: speed
|
|
||||||
target_weigh: target_weigh
|
|
||||||
temperature: temperature
|
|
||||||
volume: volume
|
|
||||||
goal_default:
|
|
||||||
delay_time: ''
|
|
||||||
hold_m_name: ''
|
|
||||||
liquid_material_name: ''
|
|
||||||
material_name: ''
|
|
||||||
order_name: ''
|
|
||||||
speed: ''
|
|
||||||
target_weigh: ''
|
|
||||||
temperature: ''
|
|
||||||
volume: ''
|
|
||||||
handles: {}
|
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
title: DispenStationSolnPrep_Feedback
|
|
||||||
type: object
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
delay_time:
|
|
||||||
type: string
|
|
||||||
hold_m_name:
|
|
||||||
type: string
|
|
||||||
liquid_material_name:
|
|
||||||
type: string
|
|
||||||
material_name:
|
|
||||||
type: string
|
|
||||||
order_name:
|
|
||||||
type: string
|
|
||||||
speed:
|
|
||||||
type: string
|
|
||||||
target_weigh:
|
|
||||||
type: string
|
|
||||||
temperature:
|
|
||||||
type: string
|
|
||||||
volume:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- order_name
|
|
||||||
- material_name
|
|
||||||
- target_weigh
|
|
||||||
- volume
|
|
||||||
- liquid_material_name
|
|
||||||
- speed
|
|
||||||
- temperature
|
|
||||||
- delay_time
|
|
||||||
- hold_m_name
|
|
||||||
title: DispenStationSolnPrep_Goal
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
properties:
|
|
||||||
return_info:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- return_info
|
|
||||||
title: DispenStationSolnPrep_Result
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: DispenStationSolnPrep
|
|
||||||
type: object
|
|
||||||
type: DispenStationSolnPrep
|
|
||||||
module: unilabos.devices.workstation.bioyond_studio.dispensing_station:BioyondDispensingStation
|
|
||||||
status_types: {}
|
|
||||||
type: python
|
|
||||||
config_info: []
|
|
||||||
description: ''
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema:
|
|
||||||
config:
|
|
||||||
properties:
|
|
||||||
config:
|
|
||||||
type: string
|
|
||||||
deck:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- config
|
|
||||||
- deck
|
|
||||||
type: object
|
|
||||||
data:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
version: 1.0.0
|
|
||||||
822
unilabos/registry/devices/bioyond_cell.yaml
Normal file
822
unilabos/registry/devices/bioyond_cell.yaml
Normal file
@@ -0,0 +1,822 @@
|
|||||||
|
bioyond_cell:
|
||||||
|
category:
|
||||||
|
- bioyond_cell
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
auto-auto_batch_outbound_from_xlsx:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
xlsx_path: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
xlsx_path:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- xlsx_path
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: auto_batch_outbound_from_xlsx参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-auto_feeding4to3:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
xlsx_path: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
xlsx_path:
|
||||||
|
default: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/2025122301.xlsx
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: auto_feeding4to3参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-create_and_inbound_materials:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
material_names: null
|
||||||
|
type_id: 3a190ca0-b2f6-9aeb-8067-547e72c11469
|
||||||
|
warehouse_name: 粉末加样头堆栈
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
material_names:
|
||||||
|
type: string
|
||||||
|
type_id:
|
||||||
|
default: 3a190ca0-b2f6-9aeb-8067-547e72c11469
|
||||||
|
type: string
|
||||||
|
warehouse_name:
|
||||||
|
default: 粉末加样头堆栈
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: create_and_inbound_materials参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-create_material:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
location_name_or_id: null
|
||||||
|
material_name: null
|
||||||
|
type_id: null
|
||||||
|
warehouse_name: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
location_name_or_id:
|
||||||
|
type: string
|
||||||
|
material_name:
|
||||||
|
type: string
|
||||||
|
type_id:
|
||||||
|
type: string
|
||||||
|
warehouse_name:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- material_name
|
||||||
|
- type_id
|
||||||
|
- warehouse_name
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: create_material参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-create_materials:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
mappings: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
mappings:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- mappings
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: create_materials参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-create_orders:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
xlsx_path: null
|
||||||
|
handles:
|
||||||
|
output:
|
||||||
|
- data_key: total_orders
|
||||||
|
data_source: executor
|
||||||
|
data_type: integer
|
||||||
|
handler_key: bottle_count
|
||||||
|
io_type: sink
|
||||||
|
label: 配液瓶数
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
xlsx_path:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- xlsx_path
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: create_orders参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-create_orders_v2:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
xlsx_path: null
|
||||||
|
handles:
|
||||||
|
output:
|
||||||
|
- data_key: total_orders
|
||||||
|
data_source: executor
|
||||||
|
data_type: integer
|
||||||
|
handler_key: bottle_count
|
||||||
|
io_type: sink
|
||||||
|
label: 配液瓶数
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 从Excel解析并创建实验(V2版本)
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
xlsx_path:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- xlsx_path
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: create_orders_v2参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-create_sample:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
board_type: null
|
||||||
|
bottle_type: null
|
||||||
|
location_code: null
|
||||||
|
name: null
|
||||||
|
warehouse_name: 手动堆栈
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
board_type:
|
||||||
|
type: string
|
||||||
|
bottle_type:
|
||||||
|
type: string
|
||||||
|
location_code:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
warehouse_name:
|
||||||
|
default: 手动堆栈
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
- board_type
|
||||||
|
- bottle_type
|
||||||
|
- location_code
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: create_sample参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-order_list_v2:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
beginTime: ''
|
||||||
|
endTime: ''
|
||||||
|
filter: ''
|
||||||
|
pageCount: 1
|
||||||
|
skipCount: 0
|
||||||
|
sorting: ''
|
||||||
|
status: ''
|
||||||
|
timeType: ''
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
beginTime:
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
endTime:
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
filter:
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
pageCount:
|
||||||
|
default: 1
|
||||||
|
type: integer
|
||||||
|
skipCount:
|
||||||
|
default: 0
|
||||||
|
type: integer
|
||||||
|
sorting:
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
status:
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
timeType:
|
||||||
|
default: ''
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: order_list_v2参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-process_order_finish_report:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
report_request: null
|
||||||
|
used_materials: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
report_request:
|
||||||
|
type: string
|
||||||
|
used_materials:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- report_request
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: process_order_finish_report参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-process_sample_finish_report:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
report_request: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
report_request:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- report_request
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: process_sample_finish_report参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-process_step_finish_report:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
report_request: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
report_request:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- report_request
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: process_step_finish_report参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-report_material_change:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
material_obj: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
material_obj:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- material_obj
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: report_material_change参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-resource_tree_transfer:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
old_parent: null
|
||||||
|
parent_resource: null
|
||||||
|
plr_resource: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
old_parent:
|
||||||
|
type: object
|
||||||
|
parent_resource:
|
||||||
|
type: object
|
||||||
|
plr_resource:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- old_parent
|
||||||
|
- plr_resource
|
||||||
|
- parent_resource
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: resource_tree_transfer参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-scheduler_continue:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: scheduler_continue参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-scheduler_reset:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: scheduler_reset参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-scheduler_start:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: scheduler_start参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-scheduler_start_and_auto_feeding:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
xlsx_path: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 组合函数:先启动调度,然后执行自动化上料
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
xlsx_path:
|
||||||
|
default: D:/UniLab/Uni-Lab-OS/unilabos/devices/workstation/bioyond_studio/bioyond_cell/material_template.xlsx
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: scheduler_start_and_auto_feeding参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-scheduler_stop:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: scheduler_stop参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-storage_batch_inbound:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
items: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
required:
|
||||||
|
- items
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: storage_batch_inbound参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-storage_inbound:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
location_id: null
|
||||||
|
material_id: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
location_id:
|
||||||
|
type: string
|
||||||
|
material_id:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- material_id
|
||||||
|
- location_id
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: storage_inbound参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-transfer_1_to_2:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties: {}
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: transfer_1_to_2参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-transfer_3_to_2:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
source_wh_id: 3a19debc-84b4-0359-e2d4-b3beea49348b
|
||||||
|
source_x: 1
|
||||||
|
source_y: 1
|
||||||
|
source_z: 1
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: 3-2 物料转运,从3号位置转运到2号位置
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
source_wh_id:
|
||||||
|
default: 3a19debc-84b4-0359-e2d4-b3beea49348b
|
||||||
|
description: 来源仓库ID
|
||||||
|
type: string
|
||||||
|
source_x:
|
||||||
|
default: 1
|
||||||
|
description: 来源位置X坐标
|
||||||
|
type: integer
|
||||||
|
source_y:
|
||||||
|
default: 1
|
||||||
|
description: 来源位置Y坐标
|
||||||
|
type: integer
|
||||||
|
source_z:
|
||||||
|
default: 1
|
||||||
|
description: 来源位置Z坐标
|
||||||
|
type: integer
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: transfer_3_to_2参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-transfer_3_to_2_to_1:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
source_wh_id: 3a19debc-84b4-0359-e2d4-b3beea49348b
|
||||||
|
source_x: 1
|
||||||
|
source_y: 1
|
||||||
|
source_z: 1
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
source_wh_id:
|
||||||
|
default: 3a19debc-84b4-0359-e2d4-b3beea49348b
|
||||||
|
type: string
|
||||||
|
source_x:
|
||||||
|
default: 1
|
||||||
|
type: integer
|
||||||
|
source_y:
|
||||||
|
default: 1
|
||||||
|
type: integer
|
||||||
|
source_z:
|
||||||
|
default: 1
|
||||||
|
type: integer
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: transfer_3_to_2_to_1参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-update_push_ip:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
ip: null
|
||||||
|
port: null
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
ip:
|
||||||
|
type: string
|
||||||
|
port:
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: update_push_ip参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-wait_for_order_finish:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
order_code: null
|
||||||
|
timeout: 36000
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
order_code:
|
||||||
|
type: string
|
||||||
|
timeout:
|
||||||
|
default: 36000
|
||||||
|
type: integer
|
||||||
|
required:
|
||||||
|
- order_code
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: wait_for_order_finish参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
auto-wait_for_transfer_task:
|
||||||
|
feedback: {}
|
||||||
|
goal: {}
|
||||||
|
goal_default:
|
||||||
|
filter_text: null
|
||||||
|
interval: 5
|
||||||
|
timeout: 3000
|
||||||
|
handles: {}
|
||||||
|
placeholder_keys: {}
|
||||||
|
result: {}
|
||||||
|
schema:
|
||||||
|
description: ''
|
||||||
|
properties:
|
||||||
|
feedback: {}
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
filter_text:
|
||||||
|
type: string
|
||||||
|
interval:
|
||||||
|
default: 5
|
||||||
|
type: integer
|
||||||
|
timeout:
|
||||||
|
default: 3000
|
||||||
|
type: integer
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
result: {}
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: wait_for_transfer_task参数
|
||||||
|
type: object
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
module: unilabos.devices.workstation.bioyond_studio.bioyond_cell.bioyond_cell_workstation:BioyondCellWorkstation
|
||||||
|
status_types:
|
||||||
|
device_id: String
|
||||||
|
type: python
|
||||||
|
config_info: []
|
||||||
|
description: ''
|
||||||
|
handles: []
|
||||||
|
icon: benyao2.webp
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
config:
|
||||||
|
type: object
|
||||||
|
deck:
|
||||||
|
type: string
|
||||||
|
protocol_type:
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
device_id:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- device_id
|
||||||
|
type: object
|
||||||
|
registry_type: device
|
||||||
|
version: 1.0.0
|
||||||
@@ -5,6 +5,200 @@ bioyond_dispensing_station:
|
|||||||
- bioyond_dispensing_station
|
- bioyond_dispensing_station
|
||||||
class:
|
class:
|
||||||
action_value_mappings:
|
action_value_mappings:
|
||||||
|
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-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
|
||||||
batch_create_90_10_vial_feeding_tasks:
|
batch_create_90_10_vial_feeding_tasks:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -171,99 +365,6 @@ bioyond_dispensing_station:
|
|||||||
title: BatchCreateDiamineSolutionTasks
|
title: BatchCreateDiamineSolutionTasks
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommand
|
type: UniLabJsonCommand
|
||||||
compute_experiment_design:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
m_tot: m_tot
|
|
||||||
ratio: ratio
|
|
||||||
titration_percent: titration_percent
|
|
||||||
wt_percent: wt_percent
|
|
||||||
goal_default:
|
|
||||||
m_tot: '70'
|
|
||||||
ratio: ''
|
|
||||||
titration_percent: '0.03'
|
|
||||||
wt_percent: '0.25'
|
|
||||||
handles:
|
|
||||||
output:
|
|
||||||
- data_key: solutions
|
|
||||||
data_source: executor
|
|
||||||
data_type: array
|
|
||||||
handler_key: solutions
|
|
||||||
io_type: sink
|
|
||||||
label: Solution Data From Python
|
|
||||||
- data_key: titration
|
|
||||||
data_source: executor
|
|
||||||
data_type: object
|
|
||||||
handler_key: titration
|
|
||||||
io_type: sink
|
|
||||||
label: Titration Data From Calculation Node
|
|
||||||
- data_key: solvents
|
|
||||||
data_source: executor
|
|
||||||
data_type: object
|
|
||||||
handler_key: solvents
|
|
||||||
io_type: sink
|
|
||||||
label: Solvents Data From Calculation Node
|
|
||||||
- data_key: feeding_order
|
|
||||||
data_source: executor
|
|
||||||
data_type: array
|
|
||||||
handler_key: feeding_order
|
|
||||||
io_type: sink
|
|
||||||
label: Feeding Order Data From Calculation Node
|
|
||||||
result:
|
|
||||||
feeding_order: feeding_order
|
|
||||||
return_info: return_info
|
|
||||||
solutions: solutions
|
|
||||||
solvents: solvents
|
|
||||||
titration: titration
|
|
||||||
schema:
|
|
||||||
description: 计算实验设计,输出solutions/titration/solvents/feeding_order用于后续节点。
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
m_tot:
|
|
||||||
default: '70'
|
|
||||||
description: 总质量(g)
|
|
||||||
type: string
|
|
||||||
ratio:
|
|
||||||
description: 组分摩尔比的对象,保持输入顺序,如{"MDA":1,"BTDA":1}
|
|
||||||
type: string
|
|
||||||
titration_percent:
|
|
||||||
default: '0.03'
|
|
||||||
description: 滴定比例(10%部分)
|
|
||||||
type: string
|
|
||||||
wt_percent:
|
|
||||||
default: '0.25'
|
|
||||||
description: 目标固含质量分数
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- ratio
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
properties:
|
|
||||||
feeding_order:
|
|
||||||
type: array
|
|
||||||
return_info:
|
|
||||||
type: string
|
|
||||||
solutions:
|
|
||||||
type: array
|
|
||||||
solvents:
|
|
||||||
type: object
|
|
||||||
titration:
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- solutions
|
|
||||||
- titration
|
|
||||||
- solvents
|
|
||||||
- feeding_order
|
|
||||||
- return_info
|
|
||||||
title: ComputeExperimentDesign_Result
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: ComputeExperimentDesign
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
create_90_10_vial_feeding_task:
|
create_90_10_vial_feeding_task:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -490,35 +591,6 @@ bioyond_dispensing_station:
|
|||||||
title: DispenStationSolnPrep
|
title: DispenStationSolnPrep
|
||||||
type: object
|
type: object
|
||||||
type: DispenStationSolnPrep
|
type: DispenStationSolnPrep
|
||||||
scheduler_start:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
result:
|
|
||||||
return_info: return_info
|
|
||||||
schema:
|
|
||||||
description: 启动调度器 - 启动Bioyond配液站的任务调度器,开始执行队列中的任务
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result:
|
|
||||||
properties:
|
|
||||||
return_info:
|
|
||||||
description: 调度器启动结果,成功返回1,失败返回0
|
|
||||||
type: integer
|
|
||||||
required:
|
|
||||||
- return_info
|
|
||||||
title: scheduler_start结果
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: scheduler_start参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
transfer_materials_to_reaction_station:
|
transfer_materials_to_reaction_station:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
@@ -551,11 +623,7 @@ bioyond_dispensing_station:
|
|||||||
description: 目标库位(手动输入,如"A01")
|
description: 目标库位(手动输入,如"A01")
|
||||||
type: string
|
type: string
|
||||||
target_stack:
|
target_stack:
|
||||||
description: 目标堆栈名称(从列表选择)
|
description: 目标堆栈名称(手动输入,如"堆栈1左")
|
||||||
enum:
|
|
||||||
- 堆栈1左
|
|
||||||
- 堆栈1右
|
|
||||||
- 站内试剂存放堆栈
|
|
||||||
type: string
|
type: string
|
||||||
required:
|
required:
|
||||||
- materials
|
- materials
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
cameracontroller_device:
|
|
||||||
category:
|
|
||||||
- cameraSII
|
|
||||||
class:
|
|
||||||
action_value_mappings:
|
|
||||||
auto-start:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
config: null
|
|
||||||
handles: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
config:
|
|
||||||
type: string
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: start参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-stop:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default: {}
|
|
||||||
handles: {}
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties: {}
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: stop参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
module: unilabos.devices.cameraSII.cameraUSB:CameraController
|
|
||||||
status_types:
|
|
||||||
status: dict
|
|
||||||
type: python
|
|
||||||
config_info: []
|
|
||||||
description: Uni-Lab-OS 摄像头驱动(Linux USB 摄像头版,无 PTZ)
|
|
||||||
handles: []
|
|
||||||
icon: ''
|
|
||||||
init_param_schema:
|
|
||||||
config:
|
|
||||||
properties:
|
|
||||||
audio_bitrate:
|
|
||||||
default: 64k
|
|
||||||
type: string
|
|
||||||
audio_device:
|
|
||||||
type: string
|
|
||||||
fps:
|
|
||||||
default: 30
|
|
||||||
type: integer
|
|
||||||
height:
|
|
||||||
default: 720
|
|
||||||
type: integer
|
|
||||||
host_id:
|
|
||||||
default: demo-host
|
|
||||||
type: string
|
|
||||||
rtmp_url:
|
|
||||||
default: rtmp://srs.sciol.ac.cn:4499/live/camera-01
|
|
||||||
type: string
|
|
||||||
signal_backend_url:
|
|
||||||
default: wss://sciol.ac.cn/api/realtime/signal/host
|
|
||||||
type: string
|
|
||||||
video_bitrate:
|
|
||||||
default: 1500k
|
|
||||||
type: string
|
|
||||||
video_device:
|
|
||||||
default: /dev/video0
|
|
||||||
type: string
|
|
||||||
webrtc_api:
|
|
||||||
default: https://srs.sciol.ac.cn/rtc/v1/play/
|
|
||||||
type: string
|
|
||||||
webrtc_stream_url:
|
|
||||||
default: webrtc://srs.sciol.ac.cn:4500/live/camera-01
|
|
||||||
type: string
|
|
||||||
width:
|
|
||||||
default: 1280
|
|
||||||
type: integer
|
|
||||||
required: []
|
|
||||||
type: object
|
|
||||||
data:
|
|
||||||
properties:
|
|
||||||
status:
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- status
|
|
||||||
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:
|
hplc.agilent-zhida:
|
||||||
category:
|
category:
|
||||||
- characterization_chromatic
|
- characterization_chromatic
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user