Merge remote-tracking branch 'upstream/workstation_dev_YB3' into workstation_dev_YB3

This commit is contained in:
lixinyu1011
2025-10-27 19:33:45 +08:00
16 changed files with 5197 additions and 450 deletions

View File

@@ -26,7 +26,7 @@ WAREHOUSE_MAPPING = {
"粉末加样头堆栈": {
"uuid": "",
"site_uuids": {
"A01": "3a19da56-1379-20c8-5886-f7c4fbcb5733",
"A01": "3a19da56-1379-ff7c-1745-07e200b44ce2",
"B01": "3a19da56-1379-2424-d751-fe6e94cef938",
"C01": "3a19da56-1379-271c-03e3-6bdb590e395e",
"D01": "3a19da56-1379-277f-2b1b-0d11f7cf92c6",
@@ -47,7 +47,89 @@ WAREHOUSE_MAPPING = {
"S01": "3a19da56-1379-f924-7f68-df1fa51489f4",
"T01": "3a19da56-1379-ff7c-1745-07e200b44ce2"
}
}
},
"配液站内试剂仓库": {
"uuid": "",
"site_uuids": {
"A01": "3a19da43-57b5-294f-d663-154a1cc32270",
"B01": "3a19da43-57b5-7394-5f49-54efe2c9bef2",
"C01": "3a19da43-57b5-5e75-552f-8dbd0ad1075f",
"A02": "3a19da43-57b5-8441-db94-b4d3875a4b6c",
"B02": "3a19da43-57b5-3e41-c181-5119dddaf50c",
"C02": "3a19da43-57b5-269b-282d-fba61fe8ce96",
"A03": "3a19da43-57b5-7c1e-d02e-c40e8c33f8a1",
"B03": "3a19da43-57b5-659f-621f-1dcf3f640363",
"C03": "3a19da43-57b5-855a-6e71-f398e376dee1",
}
},
"试剂替换仓库": {
"uuid": "",
"site_uuids": {
"A01": "3a19da51-8f4e-30f3-ea08-4f8498e9b097",
"B01": "3a19da51-8f4e-1da7-beb0-80a4a01e67a8",
"C01": "3a19da51-8f4e-337d-2675-bfac46880b06",
"D01": "3a19da51-8f4e-e514-b92c-9c44dc5e489d",
"E01": "3a19da51-8f4e-22d1-dd5b-9774ddc80402",
"F01": "3a19da51-8f4e-273a-4871-dff41c29bfd9",
"G01": "3a19da51-8f4e-b32f-454f-74bc1a665653",
"H01": "3a19da51-8f4e-8c93-68c9-0b4382320f59",
"I01": "3a19da51-8f4e-360c-0149-291b47c6089b",
"J01": "3a19da51-8f4e-4152-9bca-8d64df8c1af0"
}
},
"自动堆栈-左": {
"uuid": "",
"site_uuids": {
"A01": "3a19debc-84b5-4c1c-d3a1-26830cf273ff",
"A02": "3a19debc-84b5-033b-b31f-6b87f7c2bf52",
"B01": "3a19debc-84b5-3924-172f-719ab01b125c",
"B02": "3a19debc-84b5-aad8-70c6-b8c6bb2d8750"
}
},
"自动堆栈-右": {
"uuid": "",
"site_uuids": {
"A01": "3a19debe-5200-7df2-1dd9-7d202f158864",
"A02": "3a19debe-5200-573b-6120-8b51f50e1e50",
"B01": "3a19debe-5200-7cd8-7666-851b0a97e309",
"B02": "3a19debe-5200-e6d3-96a3-baa6e3d5e484"
}
},
"手动堆栈": {
"uuid": "",
"site_uuids": {
"A01": "3a19deae-2c7a-36f5-5e41-02c5b66feaea",
"A02": "3a19deae-2c7a-dc6d-c41e-ef285d946cfe",
"A03": "3a19deae-2c7a-5876-c454-6b7e224ca927",
"B01": "3a19deae-2c7a-2426-6d71-e9de3cb158b1",
"B02": "3a19deae-2c7a-79b0-5e44-efaafd1e4cf3",
"B03": "3a19deae-2c7a-b9eb-f4e3-e308e0cf839a",
"C01": "3a19deae-2c7a-32bc-768e-556647e292f3",
"C02": "3a19deae-2c7a-e97a-8484-f5a4599447c4",
"C03": "3a19deae-2c7a-3056-6504-10dc73fbc276",
"D01": "3a19deae-2c7a-ffad-875e-8c4cda61d440",
"D02": "3a19deae-2c7a-61be-601c-b6fb5610499a",
"D03": "3a19deae-2c7a-c0f7-05a7-e3fe2491e560",
"E01": "3a19deae-2c7a-a6f4-edd1-b436a7576363",
"E02": "3a19deae-2c7a-4367-96dd-1ca2186f4910",
"E03": "3a19deae-2c7a-b163-2219-23df15200311",
"F01": "3a19deae-2c7a-d594-fd6a-0d20de3c7c4a",
"F02": "3a19deae-2c7a-a194-ea63-8b342b8d8679",
"F03": "3a19deae-2c7a-f7c4-12bd-425799425698",
"G01": "3a19deae-2c7a-0b56-72f1-8ab86e53b955",
"G02": "3a19deae-2c7a-204e-95ed-1f1950f28343",
"G03": "3a19deae-2c7a-392b-62f1-4907c66343f8",
"H01": "3a19deae-2c7a-5602-e876-d27aca4e3201",
"H02": "3a19deae-2c7a-f15c-70e0-25b58a8c9702",
"H03": "3a19deae-2c7a-780b-8965-2e1345f7e834",
"I01": "3a19deae-2c7a-8849-e172-07de14ede928",
"I02": "3a19deae-2c7a-4772-a37f-ff99270bafc0",
"I03": "3a19deae-2c7a-cce7-6e4a-25ea4a2068c4",
"J01": "3a19deae-2c7a-1848-de92-b5d5ed054cc6",
"J02": "3a19deae-2c7a-1d45-b4f8-6f866530e205",
"J03": "3a19deae-2c7a-f237-89d9-8fe19025dee9"
}
},
}
# 物料类型配置
@@ -88,10 +170,10 @@ SOLID_LIQUID_MAPPINGS = {
# "barCode": "",
# "name": "LiFSI",
# "unit": "g",
# "parameters": {"Density": "1.533"},
# "parameters": "",
# "quantity": 2,
# "warningQuantity": 1,
# "details": [{}]
# "details": []
# },
# "DTC": {
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
@@ -99,10 +181,10 @@ SOLID_LIQUID_MAPPINGS = {
# "barCode": "",
# "name": "DTC",
# "unit": "g",
# "parameters": {"Density": "1.533"},
# "parameters": "",
# "quantity": 2,
# "warningQuantity": 1,
# "details": [{}]
# "details": []
# },
# "LiPO2F2": {
# "typeId": "3a190ca0-b2f6-9aeb-8067-547e72c11469",
@@ -110,10 +192,10 @@ SOLID_LIQUID_MAPPINGS = {
# "barCode": "",
# "name": "LiPO2F2",
# "unit": "g",
# "parameters": {"Density": "1.533"},
# "parameters": "",
# "quantity": 2,
# "warningQuantity": 1,
# "details": [{}]
# "details": []
# },
# 液体
# "SA": ("BIOYOND_PolymerStation_Solid_Stock", "3a190ca0-b2f6-9aeb-8067-547e72c11469"),

View File

@@ -0,0 +1,63 @@
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
COIL_ALUMINUM_FOIL,BOOL,,ʹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>,,coil,8340,
REG_MSG_NE_PLATE_MATRIX,INT16,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ,,hold_register,440,
REG_MSG_SEPARATOR_PLATE_MATRIX,INT16,,<EFBFBD><EFBFBD>Ĥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ,,hold_register,450,
REG_MSG_TIP_BOX_MATRIX,INT16,,<EFBFBD><EFBFBD>Һǹͷ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>λ,,hold_register,480,
REG_MSG_NE_PLATE_NUM,INT16,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,,hold_register,443,
REG_MSG_SEPARATOR_PLATE_NUM,INT16,,<EFBFBD><EFBFBD>Ĥ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,,hold_register,453,
REG_MSG_PRESS_MODE,BOOL,,ѹ<EFBFBD><EFBFBD>ģʽ<EFBFBD><EFBFBD>false:ѹ<><D1B9><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<C4A3><CABD>True:<3A><><EFBFBD><EFBFBD>ģʽ<C4A3><CABD>,,coil,8360,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѹ<EFBFBD><EFBFBD>ģʽ
,,,,,,,
,BOOL,,<EFBFBD>Ӿ<EFBFBD><EFBFBD><EFBFBD>λ<EFBFBD><EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,,coil,8300,<EFBFBD>Ӿ<EFBFBD><EFBFBD><EFBFBD>λ
,BOOL,,<EFBFBD><EFBFBD><EFBFBD>죨false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,,coil,8310,<EFBFBD>Ӿ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
,BOOL,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><EFBFBD><EFBFBD>֣<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,,coil,8320,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
,BOOL,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD>Ҳ֣<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,,coil,8420,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ҳ<EFBFBD>
,BOOL,,<EFBFBD><EFBFBD><EFBFBD>ռ<EFBFBD>֪<EFBFBD><EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,,coil,8350,<EFBFBD><EFBFBD><EFBFBD>ռ<EFBFBD>֪
,BOOL,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Һ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>ģʽ<EFBFBD><EFBFBD>false:<3A><><EFBFBD>ε<EFBFBD>Һ<EFBFBD><D2BA>true:<3A><><EFBFBD>ε<EFBFBD>Һ<EFBFBD><D2BA>,,coil,8370,<EFBFBD><EFBFBD>Һģʽ
,BOOL,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD>أ<EFBFBD>false:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,,coil,8380,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>
,BOOL,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD>װ<EFBFBD><EFBFBD>ʽ<EFBFBD><EFBFBD>false:<3A><>װ<EFBFBD><D7B0>true:<3A><>װ<EFBFBD><D7B0>,,coil,8390,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>װ
,BOOL,,ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ࣨfalse:ʹ<>ã<EFBFBD>true:<3A><><EFBFBD>ԣ<EFBFBD>,,coil,8400,ѹ<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
,BOOL,,<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>̰<EFBFBD><EFBFBD>̷<EFBFBD>ʽ<EFBFBD><EFBFBD>false:ˮƽ<CBAE><C6BD><EFBFBD>̣<EFBFBD>true:<3A>ѵ<EFBFBD><D1B5><EFBFBD><EFBFBD>̣<EFBFBD>,,coil,8410,<EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ƭ<EFBFBD><EFBFBD><EFBFBD>̷<EFBFBD>ʽ
1 Name DataType InitValue Comment Attribute DeviceType Address
2 COIL_SYS_START_CMD BOOL coil 8010
3 COIL_SYS_STOP_CMD BOOL coil 8020
4 COIL_SYS_RESET_CMD BOOL coil 8030
5 COIL_SYS_HAND_CMD BOOL coil 8040
6 COIL_SYS_AUTO_CMD BOOL coil 8050
7 COIL_SYS_INIT_CMD BOOL coil 8060
8 COIL_UNILAB_SEND_MSG_SUCC_CMD BOOL coil 8700
9 COIL_UNILAB_REC_MSG_SUCC_CMD BOOL coil 8710 unilab_rec_msg_succ_cmd
10 COIL_SYS_START_STATUS BOOL coil 8210
11 COIL_SYS_STOP_STATUS BOOL coil 8220
12 COIL_SYS_RESET_STATUS BOOL coil 8230
13 COIL_SYS_HAND_STATUS BOOL coil 8240
14 COIL_SYS_AUTO_STATUS BOOL coil 8250
15 COIL_SYS_INIT_STATUS BOOL coil 8260
16 COIL_REQUEST_REC_MSG_STATUS BOOL coil 8500
17 COIL_REQUEST_SEND_MSG_STATUS BOOL coil 8510 request_send_msg_status
18 REG_MSG_ELECTROLYTE_USE_NUM INT16 hold_register 11000
19 REG_MSG_ELECTROLYTE_NUM INT16 hold_register 11002 unilab_send_msg_electrolyte_num
20 REG_MSG_ELECTROLYTE_VOLUME INT16 hold_register 11004 unilab_send_msg_electrolyte_vol
21 REG_MSG_ASSEMBLY_TYPE INT16 hold_register 11006 unilab_send_msg_assembly_type
22 REG_MSG_ASSEMBLY_PRESSURE INT16 hold_register 11008 unilab_send_msg_assembly_pressure
23 REG_DATA_ASSEMBLY_COIN_CELL_NUM INT16 hold_register 10000 data_assembly_coin_cell_num
24 REG_DATA_OPEN_CIRCUIT_VOLTAGE FLOAT32 hold_register 10002 data_open_circuit_voltage
25 REG_DATA_AXIS_X_POS FLOAT32 hold_register 10004
26 REG_DATA_AXIS_Y_POS FLOAT32 hold_register 10006
27 REG_DATA_AXIS_Z_POS FLOAT32 hold_register 10008
28 REG_DATA_POLE_WEIGHT FLOAT32 hold_register 10010 data_pole_weight
29 REG_DATA_ASSEMBLY_PER_TIME FLOAT32 hold_register 10012 data_assembly_time
30 REG_DATA_ASSEMBLY_PRESSURE INT16 hold_register 10014 data_assembly_pressure
31 REG_DATA_ELECTROLYTE_VOLUME INT16 hold_register 10016 data_electrolyte_volume
32 REG_DATA_COIN_NUM INT16 hold_register 10018 data_coin_num
33 REG_DATA_ELECTROLYTE_CODE STRING hold_register 10020 data_electrolyte_code()
34 REG_DATA_COIN_CELL_CODE STRING hold_register 10030 data_coin_cell_code()
35 REG_DATA_STACK_VISON_CODE STRING hold_register 12004 data_stack_vision_code()
36 REG_DATA_GLOVE_BOX_PRESSURE FLOAT32 hold_register 10050 data_glove_box_pressure
37 REG_DATA_GLOVE_BOX_WATER_CONTENT FLOAT32 hold_register 10052 data_glove_box_water_content
38 REG_DATA_GLOVE_BOX_O2_CONTENT FLOAT32 hold_register 10054 data_glove_box_o2_content
39 UNILAB_SEND_ELECTROLYTE_BOTTLE_NUM BOOL coil 8720
40 UNILAB_RECE_ELECTROLYTE_BOTTLE_NUM BOOL coil 8520
41 REG_MSG_ELECTROLYTE_NUM_USED INT16 hold_register 496
42 REG_DATA_ELECTROLYTE_USE_NUM INT16 hold_register 10000
43 UNILAB_SEND_FINISHED_CMD BOOL coil 8730
44 UNILAB_RECE_FINISHED_CMD BOOL coil 8530
45 REG_DATA_ASSEMBLY_TYPE INT16 hold_register 10018 ASSEMBLY_TYPE7or8
46 COIL_ALUMINUM_FOIL BOOL ʹ coil 8340
47 REG_MSG_NE_PLATE_MATRIX INT16 Ƭλ hold_register 440
48 REG_MSG_SEPARATOR_PLATE_MATRIX INT16 Ĥλ hold_register 450
49 REG_MSG_TIP_BOX_MATRIX INT16 Һǹͷλ hold_register 480
50 REG_MSG_NE_PLATE_NUM INT16 Ƭ hold_register 443
51 REG_MSG_SEPARATOR_PLATE_NUM INT16 Ĥ hold_register 453
52 REG_MSG_PRESS_MODE BOOL ѹģʽfalse:ѹģʽTrue:ģʽ coil 8360 ѹģʽ
53
54 BOOL Ӿλfalse:ʹãtrue:ԣ coil 8300 Ӿλ
55 BOOL 죨false:ʹãtrue:ԣ coil 8310 Ӿ
56 BOOL _֣false:ʹãtrue:ԣ coil 8320
57 BOOL _Ҳ֣false:ʹãtrue:ԣ coil 8420 Ҳ
58 BOOL ռ֪false:ʹãtrue:ԣ coil 8350 ռ֪
59 BOOL Һģʽfalse:εҺtrue:εҺ coil 8370 Һģʽ
60 BOOL Ƭأfalse:ʹãtrue:ԣ coil 8380 Ƭ
61 BOOL Ƭװʽfalse:װtrue:װ coil 8390 װ
62 BOOL ѹࣨfalse:ʹãtrue:ԣ coil 8400 ѹ
63 BOOL ̷̰ʽfalse:ˮƽ̣true:ѵ̣ coil 8410 Ƭ̷ʽ

View File

@@ -0,0 +1,691 @@
{
"nodes": [
{
"id": "BatteryStation",
"name": "扣电工作站",
"children": [
"coin_cell_deck"
],
"parent": null,
"type": "device",
"class": "bettery_station_registry",
"position": {
"x": 600,
"y": 400,
"z": 0
},
"config": {
"debug_mode": false,
"_comment": "protocol_type接外部工站固定写法字段一般为空station_resource写法也固定",
"protocol_type": [],
"station_resource": {
"data": {
"_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
},
"data": {}
},
{
"id": "coin_cell_deck",
"name": "coin_cell_deck",
"sample_id": null,
"children": [
"\u7535\u6c60\u6599\u76d8"
],
"parent": null,
"type": "container",
"class": "",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "CoincellDeck",
"size_x": 1000,
"size_y": 1000,
"size_z": 900,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "coin_cell_deck",
"barcode": null
},
"data": {}
},
{
"id": "\u7535\u6c60\u6599\u76d8",
"name": "\u7535\u6c60\u6599\u76d8",
"sample_id": null,
"children": [
"\u7535\u6c60\u6599\u76d8_materialhole_0_0",
"\u7535\u6c60\u6599\u76d8_materialhole_0_1",
"\u7535\u6c60\u6599\u76d8_materialhole_0_2",
"\u7535\u6c60\u6599\u76d8_materialhole_0_3",
"\u7535\u6c60\u6599\u76d8_materialhole_1_0",
"\u7535\u6c60\u6599\u76d8_materialhole_1_1",
"\u7535\u6c60\u6599\u76d8_materialhole_1_2",
"\u7535\u6c60\u6599\u76d8_materialhole_1_3",
"\u7535\u6c60\u6599\u76d8_materialhole_2_0",
"\u7535\u6c60\u6599\u76d8_materialhole_2_1",
"\u7535\u6c60\u6599\u76d8_materialhole_2_2",
"\u7535\u6c60\u6599\u76d8_materialhole_2_3",
"\u7535\u6c60\u6599\u76d8_materialhole_3_0",
"\u7535\u6c60\u6599\u76d8_materialhole_3_1",
"\u7535\u6c60\u6599\u76d8_materialhole_3_2",
"\u7535\u6c60\u6599\u76d8_materialhole_3_3"
],
"parent": "coin_cell_deck",
"type": "container",
"class": "",
"position": {
"x": 100,
"y": 100,
"z": 0
},
"config": {
"type": "MaterialPlate",
"size_x": 120.8,
"size_y": 160.5,
"size_z": 10.0,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_plate",
"model": null,
"barcode": null,
"ordering": {
"A1": "\u7535\u6c60\u6599\u76d8_materialhole_0_0",
"B1": "\u7535\u6c60\u6599\u76d8_materialhole_0_1",
"C1": "\u7535\u6c60\u6599\u76d8_materialhole_0_2",
"D1": "\u7535\u6c60\u6599\u76d8_materialhole_0_3",
"A2": "\u7535\u6c60\u6599\u76d8_materialhole_1_0",
"B2": "\u7535\u6c60\u6599\u76d8_materialhole_1_1",
"C2": "\u7535\u6c60\u6599\u76d8_materialhole_1_2",
"D2": "\u7535\u6c60\u6599\u76d8_materialhole_1_3",
"A3": "\u7535\u6c60\u6599\u76d8_materialhole_2_0",
"B3": "\u7535\u6c60\u6599\u76d8_materialhole_2_1",
"C3": "\u7535\u6c60\u6599\u76d8_materialhole_2_2",
"D3": "\u7535\u6c60\u6599\u76d8_materialhole_2_3",
"A4": "\u7535\u6c60\u6599\u76d8_materialhole_3_0",
"B4": "\u7535\u6c60\u6599\u76d8_materialhole_3_1",
"C4": "\u7535\u6c60\u6599\u76d8_materialhole_3_2",
"D4": "\u7535\u6c60\u6599\u76d8_materialhole_3_3"
}
},
"data": {}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_0_0",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_0_0",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 12.4,
"y": 104.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_0_1",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_0_1",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 12.4,
"y": 80.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_0_2",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_0_2",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 12.4,
"y": 56.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_0_3",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_0_3",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 12.4,
"y": 32.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_1_0",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_1_0",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 36.4,
"y": 104.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_1_1",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_1_1",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 36.4,
"y": 80.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_1_2",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_1_2",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 36.4,
"y": 56.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_1_3",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_1_3",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 36.4,
"y": 32.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_2_0",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_2_0",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 60.4,
"y": 104.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_2_1",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_2_1",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 60.4,
"y": 80.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_2_2",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_2_2",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 60.4,
"y": 56.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_2_3",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_2_3",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 60.4,
"y": 32.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_3_0",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_3_0",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 84.4,
"y": 104.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_3_1",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_3_1",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 84.4,
"y": 80.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_3_2",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_3_2",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 84.4,
"y": 56.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
},
{
"id": "\u7535\u6c60\u6599\u76d8_materialhole_3_3",
"name": "\u7535\u6c60\u6599\u76d8_materialhole_3_3",
"sample_id": null,
"children": [],
"parent": "\u7535\u6c60\u6599\u76d8",
"type": "container",
"class": "",
"position": {
"x": 84.4,
"y": 32.25,
"z": 10.0
},
"config": {
"type": "MaterialHole",
"size_x": 16,
"size_y": 16,
"size_z": 16,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
},
"category": "material_hole",
"model": null,
"barcode": null
},
"data": {
"diameter": 20,
"depth": 10,
"max_sheets": 1,
"info": null
}
}
],
"links": []
}

View File

@@ -0,0 +1,489 @@
"""
工作站基类
Workstation Base Class - 简化版
基于PLR Deck的简化工作站架构
专注于核心物料系统和工作流管理
"""
import collections
import time
from typing import Dict, Any, List, Optional, Union
from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import Enum
from pylabrobot.resources import Deck, Plate, Resource as PLRResource
from pylabrobot.resources.coordinate import Coordinate
from unilabos.ros.nodes.presets.workstation import ROS2WorkstationNode
from unilabos.utils.log import logger
class WorkflowStatus(Enum):
"""工作流状态"""
IDLE = "idle"
INITIALIZING = "initializing"
RUNNING = "running"
PAUSED = "paused"
STOPPING = "stopping"
STOPPED = "stopped"
ERROR = "error"
COMPLETED = "completed"
@dataclass
class WorkflowInfo:
"""工作流信息"""
name: str
description: str
estimated_duration: float # 预估持续时间(秒)
required_materials: List[str] # 所需物料类型
output_product: str # 输出产品类型
parameters_schema: Dict[str, Any] # 参数架构
class WorkStationContainer(Plate):
"""
WorkStation 专用 Container 类,继承自 Plate和TipRack
注意这个物料必须通过plr_additional_res_reg.py注册到edge才能正常序列化
"""
def __init__(
self,
name: str,
size_x: float,
size_y: float,
size_z: float,
category: str,
ordering: collections.OrderedDict,
model: Optional[str] = None,
):
"""
这里的初始化入参要和plr的保持一致
"""
super().__init__(name, size_x, size_y, size_z, category=category, ordering=ordering, model=model)
self._unilabos_state = {} # 必须有此行,自己的类描述的是物料的
def load_state(self, state: Dict[str, Any]) -> None:
"""从给定的状态加载工作台信息。"""
super().load_state(state)
self._unilabos_state = state
def serialize_state(self) -> Dict[str, Dict[str, Any]]:
data = super().serialize_state()
data.update(
self._unilabos_state
) # Container自身的信息云端物料将保存这一data本地也通过这里的data进行读写当前类用来表示这个物料的长宽高大小的属性而datastate用来表示物料的内容细节等
return data
def get_workstation_plate_resource(name: str) -> PLRResource: # 要给定一个返回plr的方法
"""
用于获取一些模板,例如返回一个带有特定信息/子物料的 Plate这里需要到注册表注册例如unilabos/registry/resources/organic/workstation.yaml
可以直接运行该函数或者利用注册表补全机制,来检查是否资源出错
:param name: 资源名称
:return: Resource对象
"""
plate = WorkStationContainer(
name, size_x=50, size_y=50, size_z=10, category="plate", ordering=collections.OrderedDict()
)
tip_rack = WorkStationContainer(
"tip_rack_inside_plate",
size_x=50,
size_y=50,
size_z=10,
category="tip_rack",
ordering=collections.OrderedDict(),
)
plate.assign_child_resource(tip_rack, Coordinate.zero())
return plate
class ResourceSynchronizer(ABC):
"""资源同步器基类
负责与外部物料系统的同步,并对 self.deck 做修改
"""
def __init__(self, workstation: "WorkstationBase"):
self.workstation = workstation
@abstractmethod
async def sync_from_external(self) -> bool:
"""从外部系统同步物料到本地deck"""
pass
@abstractmethod
async def sync_to_external(self, plr_resource: PLRResource) -> bool:
"""将本地物料同步到外部系统"""
pass
@abstractmethod
async def handle_external_change(self, change_info: Dict[str, Any]) -> bool:
"""处理外部系统的变更通知"""
pass
class WorkstationBase(ABC):
"""工作站基类 - 简化版
核心功能:
1. 基于 PLR Deck 的物料系统,支持格式转换
2. 可选的资源同步器支持外部物料系统
3. 简化的工作流管理
"""
_ros_node: ROS2WorkstationNode
@property
def _children(self) -> Dict[str, Any]: # 不要删除这个下划线,不然会自动导入注册表,后面改成装饰器识别
return self._ros_node.children
async def update_resource_example(self):
return await self._ros_node.update_resource([get_workstation_plate_resource("test")])
def __init__(
self,
station_resource: PLRResource,
*args,
**kwargs, # 必须有kwargs
):
# 基本配置
print(station_resource)
self.deck_config = station_resource
# PLR 物料系统
self.deck: Optional[Deck] = None
self.plr_resources: Dict[str, PLRResource] = {}
# 资源同步器(可选)
# self.resource_synchronizer = ResourceSynchronizer(self) # 要在driver中自行初始化只有workstation用
# 硬件接口
self.hardware_interface: Union[Any, str] = None
# 工作流状态
self.current_workflow_status = WorkflowStatus.IDLE
self.current_workflow_info = None
self.workflow_start_time = None
self.workflow_parameters = {}
# 支持的工作流(静态预定义)
self.supported_workflows: Dict[str, WorkflowInfo] = {}
# 初始化物料系统
self._initialize_material_system()
# 注册支持的工作流
# self._register_supported_workflows()
# logger.info(f"工作站 {device_id} 初始化完成(简化版)")
def _initialize_material_system(self):
"""初始化物料系统 - 使用 graphio 转换"""
try:
from unilabos.resources.graphio import resource_ulab_to_plr
# # 1. 合并 deck_config 和 children 创建完整的资源树
# complete_resource_config = self._create_complete_resource_config()
# # 2. 使用 graphio 转换为 PLR 资源
# self.deck = resource_ulab_to_plr(complete_resource_config, plr_model=True)
# # 3. 建立资源映射
# self._build_resource_mappings(self.deck)
# # 4. 如果有资源同步器,执行初始同步
# if self.resource_synchronizer:
# # 这里可以异步执行,暂时跳过
# pass
# logger.info(f"工作站 {self.device_id} 物料系统初始化成功,创建了 {len(self.plr_resources)} 个资源")
pass
except Exception as e:
# logger.error(f"工作站 {self.device_id} 物料系统初始化失败: {e}")
raise
def _create_complete_resource_config(self) -> Dict[str, Any]:
"""创建完整的资源配置 - 合并 deck_config 和 children"""
# 创建主 deck 配置
deck_resource = {
"id": f"{self.device_id}_deck",
"name": f"{self.device_id}_deck",
"type": "deck",
"position": {"x": 0, "y": 0, "z": 0},
"config": {
"size_x": self.deck_config.get("size_x", 1000.0),
"size_y": self.deck_config.get("size_y", 1000.0),
"size_z": self.deck_config.get("size_z", 100.0),
**{k: v for k, v in self.deck_config.items() if k not in ["size_x", "size_y", "size_z"]},
},
"data": {},
"children": [],
"parent": None,
}
# 添加子资源
if self._children:
children_list = []
for child_id, child_config in self._children.items():
child_resource = self._normalize_child_resource(child_id, child_config, deck_resource["id"])
children_list.append(child_resource)
deck_resource["children"] = children_list
return deck_resource
def _normalize_child_resource(self, resource_id: str, config: Dict[str, Any], parent_id: str) -> Dict[str, Any]:
"""标准化子资源配置"""
return {
"id": resource_id,
"name": config.get("name", resource_id),
"type": config.get("type", "container"),
"position": self._normalize_position(config.get("position", {})),
"config": config.get("config", {}),
"data": config.get("data", {}),
"children": [], # 简化版本:只支持一层子资源
"parent": parent_id,
}
def _normalize_position(self, position: Any) -> Dict[str, float]:
"""标准化位置信息"""
if isinstance(position, dict):
return {
"x": float(position.get("x", 0)),
"y": float(position.get("y", 0)),
"z": float(position.get("z", 0)),
}
elif isinstance(position, (list, tuple)) and len(position) >= 2:
return {
"x": float(position[0]),
"y": float(position[1]),
"z": float(position[2]) if len(position) > 2 else 0.0,
}
else:
return {"x": 0.0, "y": 0.0, "z": 0.0}
def _build_resource_mappings(self, deck: Deck):
"""递归构建资源映射"""
def add_resource_recursive(resource: PLRResource):
if hasattr(resource, "name"):
self.plr_resources[resource.name] = resource
if hasattr(resource, "children"):
for child in resource.children:
add_resource_recursive(child)
add_resource_recursive(deck)
# ============ 硬件接口管理 ============
def set_hardware_interface(self, hardware_interface: Union[Any, str]):
"""设置硬件接口"""
self.hardware_interface = hardware_interface
logger.info(f"工作站 {self.device_id} 硬件接口设置: {type(hardware_interface).__name__}")
def set_workstation_node(self, workstation_node: "ROS2WorkstationNode"):
"""设置协议节点引用(用于代理模式)"""
self._ros_node = workstation_node
logger.info(f"工作站 {self.device_id} 关联协议节点")
# ============ 设备操作接口 ============
def call_device_method(self, method: str, *args, **kwargs) -> Any:
"""调用设备方法的统一接口"""
# 1. 代理模式:通过协议节点转发
if isinstance(self.hardware_interface, str) and self.hardware_interface.startswith("proxy:"):
if not self._ros_node:
raise RuntimeError("代理模式需要设置workstation_node")
device_id = self.hardware_interface[6:] # 移除 "proxy:" 前缀
return self._ros_node.call_device_method(device_id, method, *args, **kwargs)
# 2. 直接模式:直接调用硬件接口方法
elif self.hardware_interface and hasattr(self.hardware_interface, method):
return getattr(self.hardware_interface, method)(*args, **kwargs)
else:
raise AttributeError(f"硬件接口不支持方法: {method}")
def get_device_status(self) -> Dict[str, Any]:
"""获取设备状态"""
try:
return self.call_device_method("get_status")
except AttributeError:
# 如果设备不支持get_status方法返回基础状态
return {
"status": "unknown",
"interface_type": type(self.hardware_interface).__name__,
"timestamp": time.time(),
}
def is_device_available(self) -> bool:
"""检查设备是否可用"""
try:
self.get_device_status()
return True
except:
return False
# ============ 物料系统接口 ============
def get_deck(self) -> Deck:
"""获取主 Deck"""
return self.deck
def get_all_resources(self) -> Dict[str, PLRResource]:
"""获取所有 PLR 资源"""
return self.plr_resources.copy()
def find_resource_by_name(self, name: str) -> Optional[PLRResource]:
"""按名称查找资源"""
return self.plr_resources.get(name)
def find_resources_by_type(self, resource_type: type) -> List[PLRResource]:
"""按类型查找资源"""
return [res for res in self.plr_resources.values() if isinstance(res, resource_type)]
async def sync_with_external_system(self) -> bool:
"""与外部物料系统同步"""
if not self.resource_synchronizer:
logger.info(f"工作站 {self.device_id} 没有配置资源同步器")
return True
try:
success = await self.resource_synchronizer.sync_from_external()
if success:
logger.info(f"工作站 {self.device_id} 外部同步成功")
else:
logger.warning(f"工作站 {self.device_id} 外部同步失败")
return success
except Exception as e:
logger.error(f"工作站 {self.device_id} 外部同步异常: {e}")
return False
# ============ 简化的工作流控制 ============
def execute_workflow(self, workflow_name: str, parameters: Dict[str, Any]) -> bool:
"""执行工作流"""
try:
# 设置工作流状态
self.current_workflow_status = WorkflowStatus.INITIALIZING
self.workflow_parameters = parameters
self.workflow_start_time = time.time()
# 委托给子类实现
success = self._execute_workflow_impl(workflow_name, parameters)
if success:
self.current_workflow_status = WorkflowStatus.RUNNING
logger.info(f"工作站 {self.device_id} 工作流 {workflow_name} 启动成功")
else:
self.current_workflow_status = WorkflowStatus.ERROR
logger.error(f"工作站 {self.device_id} 工作流 {workflow_name} 启动失败")
return success
except Exception as e:
self.current_workflow_status = WorkflowStatus.ERROR
logger.error(f"工作站 {self.device_id} 执行工作流失败: {e}")
return False
def stop_workflow(self, emergency: bool = False) -> bool:
"""停止工作流"""
try:
if self.current_workflow_status in [WorkflowStatus.IDLE, WorkflowStatus.STOPPED]:
logger.warning(f"工作站 {self.device_id} 没有正在运行的工作流")
return True
self.current_workflow_status = WorkflowStatus.STOPPING
# 委托给子类实现
success = self._stop_workflow_impl(emergency)
if success:
self.current_workflow_status = WorkflowStatus.STOPPED
logger.info(f"工作站 {self.device_id} 工作流停止成功 (紧急: {emergency})")
else:
self.current_workflow_status = WorkflowStatus.ERROR
logger.error(f"工作站 {self.device_id} 工作流停止失败")
return success
except Exception as e:
self.current_workflow_status = WorkflowStatus.ERROR
logger.error(f"工作站 {self.device_id} 停止工作流失败: {e}")
return False
# ============ 状态属性 ============
@property
def workflow_status(self) -> WorkflowStatus:
"""获取当前工作流状态"""
return self.current_workflow_status
@property
def is_busy(self) -> bool:
"""检查工作站是否忙碌"""
return self.current_workflow_status in [
WorkflowStatus.INITIALIZING,
WorkflowStatus.RUNNING,
WorkflowStatus.STOPPING,
]
@property
def workflow_runtime(self) -> float:
"""获取工作流运行时间(秒)"""
if self.workflow_start_time is None:
return 0.0
return time.time() - self.workflow_start_time
# ============ 抽象方法 - 子类必须实现 ============
# @abstractmethod
# def _register_supported_workflows(self):
# """注册支持的工作流 - 子类必须实现"""
# pass
# @abstractmethod
# def _execute_workflow_impl(self, workflow_name: str, parameters: Dict[str, Any]) -> bool:
# """执行工作流的具体实现 - 子类必须实现"""
# pass
# @abstractmethod
# def _stop_workflow_impl(self, emergency: bool = False) -> bool:
# """停止工作流的具体实现 - 子类必须实现"""
# pass
class WorkstationExample(WorkstationBase):
"""工作站示例实现"""
def _register_supported_workflows(self):
"""注册支持的工作流"""
self.supported_workflows["example_workflow"] = WorkflowInfo(
name="example_workflow",
description="这是一个示例工作流",
estimated_duration=300.0,
required_materials=["sample_plate"],
output_product="processed_plate",
parameters_schema={"param1": "string", "param2": "integer"},
)
def _execute_workflow_impl(self, workflow_name: str, parameters: Dict[str, Any]) -> bool:
"""执行工作流的具体实现"""
if workflow_name not in self.supported_workflows:
logger.error(f"工作站 {self.device_id} 不支持工作流: {workflow_name}")
return False
# 这里添加实际的工作流逻辑
logger.info(f"工作站 {self.device_id} 正在执行工作流: {workflow_name} with parameters {parameters}")
return True
def _stop_workflow_impl(self, emergency: bool = False) -> bool:
"""停止工作流的具体实现"""
# 这里添加实际的停止逻辑
logger.info(f"工作站 {self.device_id} 正在停止工作流 (紧急: {emergency})")
return True