mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-04 21:35:09 +00:00
修复了很多protocol,亲测能跑
This commit is contained in:
@@ -0,0 +1,563 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "AddProtocolTestStation",
|
||||
"name": "添加协议测试站",
|
||||
"children": [
|
||||
"transfer_pump_1",
|
||||
"transfer_pump_2",
|
||||
"multiway_valve_1",
|
||||
"multiway_valve_2",
|
||||
"stirrer_1",
|
||||
"stirrer_2",
|
||||
"flask_DMF",
|
||||
"flask_ethyl_acetate",
|
||||
"flask_methanol",
|
||||
"flask_acetone",
|
||||
"flask_water",
|
||||
"flask_air",
|
||||
"main_reactor",
|
||||
"secondary_reactor",
|
||||
"waste_workup",
|
||||
"collection_bottle_1",
|
||||
"collection_bottle_2"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "workstation",
|
||||
"position": {
|
||||
"x": 500,
|
||||
"y": 200,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"protocol_type": ["PumpTransferProtocol", "AddProtocol"]
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "transfer_pump_1",
|
||||
"name": "转移泵1",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_transfer_pump",
|
||||
"position": {
|
||||
"x": 250,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_PUMP1",
|
||||
"max_volume": 25.0,
|
||||
"transfer_rate": 5.0
|
||||
},
|
||||
"data": {
|
||||
"position": 0.0,
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "transfer_pump_2",
|
||||
"name": "转移泵2",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_transfer_pump",
|
||||
"position": {
|
||||
"x": 750,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_PUMP2",
|
||||
"max_volume": 25.0,
|
||||
"transfer_rate": 5.0
|
||||
},
|
||||
"data": {
|
||||
"position": 0.0,
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multiway_valve_1",
|
||||
"name": "试剂分配阀",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_multiway_valve",
|
||||
"position": {
|
||||
"x": 250,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VALVE1",
|
||||
"positions": 8
|
||||
},
|
||||
"data": {
|
||||
"current_position": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multiway_valve_2",
|
||||
"name": "反应器分配阀",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_multiway_valve",
|
||||
"position": {
|
||||
"x": 750,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VALVE2",
|
||||
"positions": 8
|
||||
},
|
||||
"data": {
|
||||
"current_position": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "stirrer_1",
|
||||
"name": "主反应器搅拌器",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_stirrer",
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_STIRRER1",
|
||||
"max_speed": 1500.0,
|
||||
"default_speed": 300.0
|
||||
},
|
||||
"data": {
|
||||
"speed": 0.0,
|
||||
"status": "Stopped"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "stirrer_2",
|
||||
"name": "副反应器搅拌器",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_stirrer",
|
||||
"position": {
|
||||
"x": 900,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_STIRRER2",
|
||||
"max_speed": 1500.0,
|
||||
"default_speed": 300.0
|
||||
},
|
||||
"data": {
|
||||
"speed": 0.0,
|
||||
"status": "Stopped"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_DMF",
|
||||
"name": "DMF试剂瓶",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 50,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "DMF",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_ethyl_acetate",
|
||||
"name": "乙酸乙酯试剂瓶",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 150,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "ethyl_acetate",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_methanol",
|
||||
"name": "甲醇试剂瓶",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 250,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "methanol",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_acetone",
|
||||
"name": "丙酮试剂瓶",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 350,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "acetone",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_water",
|
||||
"name": "蒸馏水瓶",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 450,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "water",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_air",
|
||||
"name": "空气瓶",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 550,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "main_reactor",
|
||||
"name": "主反应器",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 2000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "secondary_reactor",
|
||||
"name": "副反应器",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 900,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "waste_workup",
|
||||
"name": "废液处理瓶",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 700,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 2000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "collection_bottle_1",
|
||||
"name": "收集瓶1",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 800,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "collection_bottle_2",
|
||||
"name": "收集瓶2",
|
||||
"children": [],
|
||||
"parent": "AddProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 900,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"id": "link_pump1_valve1",
|
||||
"source": "transfer_pump_1",
|
||||
"target": "multiway_valve_1",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"transfer_pump_1": "transferpump",
|
||||
"multiway_valve_1": "transferpump"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_pump2_valve2",
|
||||
"source": "transfer_pump_2",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"transfer_pump_2": "transferpump",
|
||||
"multiway_valve_2": "transferpump"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_valve2",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "8",
|
||||
"multiway_valve_2": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_DMF",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_DMF",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "1",
|
||||
"flask_DMF": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_ethyl_acetate",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_ethyl_acetate",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "2",
|
||||
"flask_ethyl_acetate": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_methanol",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_methanol",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "3",
|
||||
"flask_methanol": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_acetone",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_acetone",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "4",
|
||||
"flask_acetone": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_water",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_water",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "5",
|
||||
"flask_water": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_air",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_air",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "6",
|
||||
"flask_air": "top"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_main_reactor",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "main_reactor",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "2",
|
||||
"main_reactor": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_secondary_reactor",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "secondary_reactor",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "3",
|
||||
"secondary_reactor": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_waste",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "waste_workup",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "6",
|
||||
"waste_workup": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_collection1",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "collection_bottle_1",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "7",
|
||||
"collection_bottle_1": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_collection2",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "collection_bottle_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "8",
|
||||
"collection_bottle_2": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_stirrer1_main_reactor",
|
||||
"source": "stirrer_1",
|
||||
"target": "main_reactor",
|
||||
"type": "mechanical",
|
||||
"port": {
|
||||
"stirrer_1": "stirrer_head",
|
||||
"main_reactor": "stirrer_port"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_stirrer2_secondary_reactor",
|
||||
"source": "stirrer_2",
|
||||
"target": "secondary_reactor",
|
||||
"type": "mechanical",
|
||||
"port": {
|
||||
"stirrer_2": "stirrer_head",
|
||||
"secondary_reactor": "stirrer_port"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "CentrifugeProtocolTestStation",
|
||||
"name": "离心协议测试站",
|
||||
"children": [
|
||||
"transfer_pump_1",
|
||||
"transfer_pump_2",
|
||||
"multiway_valve_1",
|
||||
"multiway_valve_2",
|
||||
"centrifuge_1",
|
||||
"reaction_mixture",
|
||||
"centrifuge_tube",
|
||||
"collection_bottle_1",
|
||||
"flask_water",
|
||||
"flask_ethanol",
|
||||
"flask_acetone",
|
||||
"flask_air",
|
||||
"waste_workup"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "workstation",
|
||||
"position": {
|
||||
"x": 500,
|
||||
"y": 200,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"protocol_type": [
|
||||
"CentrifugeProtocol",
|
||||
"PumpTransferProtocol"
|
||||
]
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "transfer_pump_1",
|
||||
"name": "主转移泵",
|
||||
"children": [],
|
||||
"parent": "CentrifugeProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_transfer_pump",
|
||||
"position": {
|
||||
"x": 200,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_PUMP1",
|
||||
"max_volume": 25.0,
|
||||
"transfer_rate": 2.0
|
||||
},
|
||||
"data": {
|
||||
"position": 0.0,
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "transfer_pump_2",
|
||||
"name": "副转移泵",
|
||||
"children": [],
|
||||
"parent": "CentrifugeProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_transfer_pump",
|
||||
"position": {
|
||||
"x": 400,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_PUMP2",
|
||||
"max_volume": 25.0,
|
||||
"transfer_rate": 2.0
|
||||
},
|
||||
"data": {
|
||||
"position": 0.0,
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multiway_valve_1",
|
||||
"name": "溶剂分配阀",
|
||||
"children": [],
|
||||
"parent": "CentrifugeProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_multiway_valve",
|
||||
"position": {
|
||||
"x": 200,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VALVE1",
|
||||
"positions": 8
|
||||
},
|
||||
"data": {
|
||||
"current_position": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multiway_valve_2",
|
||||
"name": "样品分配阀",
|
||||
"children": [],
|
||||
"parent": "CentrifugeProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_multiway_valve",
|
||||
"position": {
|
||||
"x": 400,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VALVE2",
|
||||
"positions": 8
|
||||
},
|
||||
"data": {
|
||||
"current_position": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "centrifuge_1",
|
||||
"name": "离心机",
|
||||
"children": [],
|
||||
"parent": "CentrifugeProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_centrifuge",
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 350,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_CENTRIFUGE1",
|
||||
"max_speed": 15000.0,
|
||||
"max_temp": 40.0,
|
||||
"min_temp": 4.0
|
||||
},
|
||||
"data": {
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "reaction_mixture",
|
||||
"name": "反应混合物",
|
||||
"children": [],
|
||||
"parent": "CentrifugeProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 100,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 500.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "cell_suspension",
|
||||
"liquid_volume": 200.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "centrifuge_tube",
|
||||
"name": "离心管",
|
||||
"children": [],
|
||||
"parent": "CentrifugeProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 15.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "collection_bottle_1",
|
||||
"name": "上清液收集瓶",
|
||||
"children": [],
|
||||
"parent": "CentrifugeProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 700,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 500.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_water",
|
||||
"name": "蒸馏水瓶",
|
||||
"children": [],
|
||||
"parent": "CentrifugeProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 200,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "water",
|
||||
"liquid_volume": 900.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_ethanol",
|
||||
"name": "乙醇清洗瓶",
|
||||
"children": [],
|
||||
"parent": "CentrifugeProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 300,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "ethanol",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_acetone",
|
||||
"name": "丙酮清洗瓶",
|
||||
"children": [],
|
||||
"parent": "CentrifugeProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 400,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "acetone",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_air",
|
||||
"name": "空气瓶",
|
||||
"children": [],
|
||||
"parent": "CentrifugeProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 100,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "waste_workup",
|
||||
"name": "废液瓶",
|
||||
"children": [],
|
||||
"parent": "CentrifugeProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 800,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 2000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"id": "link_pump1_valve1",
|
||||
"source": "transfer_pump_1",
|
||||
"target": "multiway_valve_1",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"transfer_pump_1": "transferpump",
|
||||
"multiway_valve_1": "transferpump"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_pump2_valve2",
|
||||
"source": "transfer_pump_2",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"transfer_pump_2": "transferpump",
|
||||
"multiway_valve_2": "transferpump"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_air",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_air",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "1",
|
||||
"flask_air": "top"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_water",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_water",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "2",
|
||||
"flask_water": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_ethanol",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_ethanol",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "3",
|
||||
"flask_ethanol": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_acetone",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_acetone",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "4",
|
||||
"flask_acetone": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_valve2",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "5",
|
||||
"multiway_valve_2": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_reaction_mixture",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "reaction_mixture",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "2",
|
||||
"reaction_mixture": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_centrifuge_tube",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "centrifuge_tube",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "3",
|
||||
"centrifuge_tube": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_collection",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "collection_bottle_1",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "4",
|
||||
"collection_bottle_1": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_waste",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "waste_workup",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "5",
|
||||
"waste_workup": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_centrifuge1_centrifuge_tube",
|
||||
"source": "centrifuge_1",
|
||||
"target": "centrifuge_tube",
|
||||
"type": "transport",
|
||||
"port": {
|
||||
"centrifuge_1": "centrifuge",
|
||||
"centrifuge_tube": "centrifuge_port"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "CleanVesselProtocolTestStation",
|
||||
"name": "容器清洗协议测试站",
|
||||
"children": [
|
||||
"transfer_pump_1",
|
||||
"transfer_pump_2",
|
||||
"multiway_valve_1",
|
||||
"multiway_valve_2",
|
||||
"heatchill_1",
|
||||
"flask_water",
|
||||
"flask_acetone",
|
||||
"flask_ethanol",
|
||||
"flask_air",
|
||||
"main_reactor",
|
||||
"secondary_reactor",
|
||||
"waste_workup"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "workstation",
|
||||
"position": {
|
||||
"x": 500,
|
||||
"y": 200,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"protocol_type": [
|
||||
"CleanVesselProtocol",
|
||||
"PumpTransferProtocol",
|
||||
"HeatChillProtocol",
|
||||
"HeatChillStartProtocol",
|
||||
"HeatChillStopProtocol"
|
||||
]
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "transfer_pump_1",
|
||||
"name": "主清洗泵",
|
||||
"children": [],
|
||||
"parent": "CleanVesselProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_transfer_pump",
|
||||
"position": {
|
||||
"x": 250,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_PUMP1",
|
||||
"max_volume": 25.0,
|
||||
"transfer_rate": 2.5
|
||||
},
|
||||
"data": {
|
||||
"position": 0.0,
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "transfer_pump_2",
|
||||
"name": "副清洗泵",
|
||||
"children": [],
|
||||
"parent": "CleanVesselProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_transfer_pump",
|
||||
"position": {
|
||||
"x": 450,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_PUMP2",
|
||||
"max_volume": 25.0,
|
||||
"transfer_rate": 2.5
|
||||
},
|
||||
"data": {
|
||||
"position": 0.0,
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multiway_valve_1",
|
||||
"name": "溶剂分配阀",
|
||||
"children": [],
|
||||
"parent": "CleanVesselProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_multiway_valve",
|
||||
"position": {
|
||||
"x": 250,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VALVE1",
|
||||
"positions": 8
|
||||
},
|
||||
"data": {
|
||||
"current_position": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multiway_valve_2",
|
||||
"name": "容器分配阀",
|
||||
"children": [],
|
||||
"parent": "CleanVesselProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_multiway_valve",
|
||||
"position": {
|
||||
"x": 450,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VALVE2",
|
||||
"positions": 8
|
||||
},
|
||||
"data": {
|
||||
"current_position": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "heatchill_1",
|
||||
"name": "加热清洗器",
|
||||
"children": [],
|
||||
"parent": "CleanVesselProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_heatchill",
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 350,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_HEATCHILL1",
|
||||
"max_temp": 100.0,
|
||||
"min_temp": 10.0,
|
||||
"max_stir_speed": 500.0
|
||||
},
|
||||
"data": {
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_water",
|
||||
"name": "蒸馏水瓶",
|
||||
"children": [],
|
||||
"parent": "CleanVesselProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 50,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "water",
|
||||
"liquid_volume": 900.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_acetone",
|
||||
"name": "丙酮清洗瓶",
|
||||
"children": [],
|
||||
"parent": "CleanVesselProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 150,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "acetone",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_ethanol",
|
||||
"name": "乙醇清洗瓶",
|
||||
"children": [],
|
||||
"parent": "CleanVesselProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 250,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "ethanol",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_air",
|
||||
"name": "空气瓶",
|
||||
"children": [],
|
||||
"parent": "CleanVesselProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 350,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "main_reactor",
|
||||
"name": "主反应器",
|
||||
"children": [],
|
||||
"parent": "CleanVesselProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 2000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "residue",
|
||||
"liquid_volume": 50.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "secondary_reactor",
|
||||
"name": "副反应器",
|
||||
"children": [],
|
||||
"parent": "CleanVesselProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 800,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "organic_residue",
|
||||
"liquid_volume": 30.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "waste_workup",
|
||||
"name": "清洗废液瓶",
|
||||
"children": [],
|
||||
"parent": "CleanVesselProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 700,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 3000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"id": "link_pump1_valve1",
|
||||
"source": "transfer_pump_1",
|
||||
"target": "multiway_valve_1",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"transfer_pump_1": "transferpump",
|
||||
"multiway_valve_1": "transferpump"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_pump2_valve2",
|
||||
"source": "transfer_pump_2",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"transfer_pump_2": "transferpump",
|
||||
"multiway_valve_2": "transferpump"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_air",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_air",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "1",
|
||||
"flask_air": "top"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_water",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_water",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "2",
|
||||
"flask_water": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_acetone",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_acetone",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "3",
|
||||
"flask_acetone": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_ethanol",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_ethanol",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "4",
|
||||
"flask_ethanol": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_valve2",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "5",
|
||||
"multiway_valve_2": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_main_reactor",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "main_reactor",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "2",
|
||||
"main_reactor": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_secondary_reactor",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "secondary_reactor",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "3",
|
||||
"secondary_reactor": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_waste",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "waste_workup",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "4",
|
||||
"waste_workup": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_air_return",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "flask_air",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "5",
|
||||
"flask_air": "bottom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_heatchill1_main_reactor",
|
||||
"source": "heatchill_1",
|
||||
"target": "main_reactor",
|
||||
"type": "mechanical",
|
||||
"port": {
|
||||
"heatchill_1": "heatchill",
|
||||
"main_reactor": "heating_jacket"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,557 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "EvacuateRefillTestStation",
|
||||
"name": "抽真空充气测试站",
|
||||
"children": [
|
||||
"transfer_pump_1",
|
||||
"transfer_pump_2",
|
||||
"multiway_valve_1",
|
||||
"multiway_valve_2",
|
||||
"flask_DMF",
|
||||
"flask_ethyl_acetate",
|
||||
"flask_methanol",
|
||||
"flask_air",
|
||||
"vacuum_pump_1",
|
||||
"gas_source_nitrogen",
|
||||
"gas_source_air",
|
||||
"solenoid_valve_vacuum",
|
||||
"solenoid_valve_gas",
|
||||
"main_reactor",
|
||||
"stirrer_1",
|
||||
"waste_workup",
|
||||
"collection_bottle_1"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "workstation",
|
||||
"position": {
|
||||
"x": 500,
|
||||
"y": 200,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"protocol_type": ["PumpTransferProtocol", "EvacuateAndRefillProtocol"]
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "transfer_pump_1",
|
||||
"name": "转移泵1",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_transfer_pump",
|
||||
"position": {
|
||||
"x": 300,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_PUMP1",
|
||||
"max_volume": 25.0,
|
||||
"transfer_rate": 5.0
|
||||
},
|
||||
"data": {
|
||||
"position": 0.0,
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "transfer_pump_2",
|
||||
"name": "转移泵2",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_transfer_pump",
|
||||
"position": {
|
||||
"x": 700,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_PUMP2",
|
||||
"max_volume": 25.0,
|
||||
"transfer_rate": 5.0
|
||||
},
|
||||
"data": {
|
||||
"position": 0.0,
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multiway_valve_1",
|
||||
"name": "第一个八通阀",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_multiway_valve",
|
||||
"position": {
|
||||
"x": 300,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VALVE1",
|
||||
"positions": 8
|
||||
},
|
||||
"data": {
|
||||
"current_position": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multiway_valve_2",
|
||||
"name": "第二个八通阀",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_multiway_valve",
|
||||
"position": {
|
||||
"x": 700,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VALVE2",
|
||||
"positions": 8
|
||||
},
|
||||
"data": {
|
||||
"current_position": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "vacuum_pump_1",
|
||||
"name": "真空泵1",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_vacuum_pump",
|
||||
"position": {
|
||||
"x": 150,
|
||||
"y": 200,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VACUUM1",
|
||||
"max_pressure": -0.9
|
||||
},
|
||||
"data": {
|
||||
"status": "OFF",
|
||||
"pressure": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "gas_source_nitrogen",
|
||||
"name": "氮气源",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_gas_source",
|
||||
"position": {
|
||||
"x": 850,
|
||||
"y": 200,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_GAS_N2",
|
||||
"gas_type": "nitrogen",
|
||||
"max_pressure": 5.0
|
||||
},
|
||||
"data": {
|
||||
"status": "OFF",
|
||||
"flow_rate": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "gas_source_air",
|
||||
"name": "空气源",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_gas_source",
|
||||
"position": {
|
||||
"x": 950,
|
||||
"y": 200,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_GAS_AIR",
|
||||
"gas_type": "air",
|
||||
"max_pressure": 3.0
|
||||
},
|
||||
"data": {
|
||||
"status": "OFF",
|
||||
"flow_rate": 0.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "solenoid_valve_vacuum",
|
||||
"name": "真空电磁阀",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_solenoid_valve",
|
||||
"position": {
|
||||
"x": 225,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_SOLENOID_VACUUM"
|
||||
},
|
||||
"data": {
|
||||
"valve_position": "CLOSED"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "solenoid_valve_gas",
|
||||
"name": "气源电磁阀",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_solenoid_valve",
|
||||
"position": {
|
||||
"x": 775,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_SOLENOID_GAS"
|
||||
},
|
||||
"data": {
|
||||
"valve_position": "CLOSED"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_DMF",
|
||||
"name": "DMF试剂瓶",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 100,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "DMF",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_ethyl_acetate",
|
||||
"name": "乙酸乙酯试剂瓶",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 200,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "ethyl_acetate",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_methanol",
|
||||
"name": "甲醇试剂瓶",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 300,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "methanol",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_air",
|
||||
"name": "空气瓶",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 400,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "main_reactor",
|
||||
"name": "主反应器",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 2000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "stirrer_1",
|
||||
"name": "搅拌器1",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_stirrer",
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_STIRRER1",
|
||||
"max_speed": 1500.0
|
||||
},
|
||||
"data": {
|
||||
"speed": 0.0,
|
||||
"status": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "waste_workup",
|
||||
"name": "废液处理瓶",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 700,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 2000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "collection_bottle_1",
|
||||
"name": "收集瓶1",
|
||||
"children": [],
|
||||
"parent": "EvacuateRefillTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 800,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"id": "link_pump1_valve1",
|
||||
"source": "transfer_pump_1",
|
||||
"target": "multiway_valve_1",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"transfer_pump_1": "transferpump",
|
||||
"multiway_valve_1": "transferpump"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_pump2_valve2",
|
||||
"source": "transfer_pump_2",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"transfer_pump_2": "transferpump",
|
||||
"multiway_valve_2": "transferpump"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_valve2",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "8",
|
||||
"multiway_valve_2": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_vacuum_solenoid",
|
||||
"source": "vacuum_pump_1",
|
||||
"target": "solenoid_valve_vacuum",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"vacuum_pump_1": "outlet",
|
||||
"solenoid_valve_vacuum": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_solenoid_vacuum_valve1",
|
||||
"source": "solenoid_valve_vacuum",
|
||||
"target": "multiway_valve_1",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"solenoid_valve_vacuum": "outlet",
|
||||
"multiway_valve_1": "7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_gas_solenoid",
|
||||
"source": "gas_source_nitrogen",
|
||||
"target": "solenoid_valve_gas",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"gas_source_nitrogen": "outlet",
|
||||
"solenoid_valve_gas": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_solenoid_gas_valve2",
|
||||
"source": "solenoid_valve_gas",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"solenoid_valve_gas": "outlet",
|
||||
"multiway_valve_2": "8"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_air_source_valve2",
|
||||
"source": "gas_source_air",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"gas_source_air": "outlet",
|
||||
"multiway_valve_2": "2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_air",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_air",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "1",
|
||||
"flask_air": "top"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_DMF",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_DMF",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "2",
|
||||
"flask_DMF": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_ethyl_acetate",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_ethyl_acetate",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "3",
|
||||
"flask_ethyl_acetate": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_methanol",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_methanol",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "4",
|
||||
"flask_methanol": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_reactor",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "main_reactor",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "5",
|
||||
"main_reactor": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_waste",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "waste_workup",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "6",
|
||||
"waste_workup": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_collection",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "collection_bottle_1",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "7",
|
||||
"collection_bottle_1": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_stirrer_reactor",
|
||||
"source": "stirrer_1",
|
||||
"target": "main_reactor",
|
||||
"type": "mechanical",
|
||||
"port": {
|
||||
"stirrer_1": "stirrer",
|
||||
"main_reactor": "stirrer"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "EvaporateProtocolTestStation",
|
||||
"name": "蒸发协议测试站",
|
||||
"children": [
|
||||
"transfer_pump_1",
|
||||
"transfer_pump_2",
|
||||
"multiway_valve_1",
|
||||
"multiway_valve_2",
|
||||
"rotavap_1",
|
||||
"heatchill_1",
|
||||
"reaction_mixture",
|
||||
"rotavap_flask",
|
||||
"rotavap_condenser",
|
||||
"flask_distillate",
|
||||
"flask_ethanol",
|
||||
"flask_acetone",
|
||||
"flask_water",
|
||||
"flask_air",
|
||||
"waste_workup"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "workstation",
|
||||
"position": {
|
||||
"x": 500,
|
||||
"y": 200,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"protocol_type": [
|
||||
"EvaporateProtocol",
|
||||
"PumpTransferProtocol",
|
||||
"HeatChillProtocol",
|
||||
"HeatChillStartProtocol",
|
||||
"HeatChillStopProtocol"
|
||||
]
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "transfer_pump_1",
|
||||
"name": "主转移泵",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_transfer_pump",
|
||||
"position": {
|
||||
"x": 200,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_PUMP1",
|
||||
"max_volume": 25.0,
|
||||
"transfer_rate": 2.5
|
||||
},
|
||||
"data": {
|
||||
"position": 0.0,
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "transfer_pump_2",
|
||||
"name": "副转移泵",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_transfer_pump",
|
||||
"position": {
|
||||
"x": 400,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_PUMP2",
|
||||
"max_volume": 25.0,
|
||||
"transfer_rate": 2.5
|
||||
},
|
||||
"data": {
|
||||
"position": 0.0,
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multiway_valve_1",
|
||||
"name": "溶剂分配阀",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_multiway_valve",
|
||||
"position": {
|
||||
"x": 200,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VALVE1",
|
||||
"positions": 8
|
||||
},
|
||||
"data": {
|
||||
"current_position": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multiway_valve_2",
|
||||
"name": "容器分配阀",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_multiway_valve",
|
||||
"position": {
|
||||
"x": 400,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VALVE2",
|
||||
"positions": 8
|
||||
},
|
||||
"data": {
|
||||
"current_position": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rotavap_1",
|
||||
"name": "旋转蒸发仪",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_rotavap",
|
||||
"position": {
|
||||
"x": 700,
|
||||
"y": 350,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_ROTAVAP1",
|
||||
"max_temp": 180.0,
|
||||
"max_rotation_speed": 280.0
|
||||
},
|
||||
"data": {
|
||||
"status": "Ready"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "heatchill_1",
|
||||
"name": "预加热器",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_heatchill",
|
||||
"position": {
|
||||
"x": 100,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_HEATCHILL1",
|
||||
"max_temp": 100.0,
|
||||
"min_temp": 10.0,
|
||||
"max_stir_speed": 500.0
|
||||
},
|
||||
"data": {
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "reaction_mixture",
|
||||
"name": "反应混合物",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 100,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "reaction_mixture",
|
||||
"liquid_volume": 600.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rotavap_flask",
|
||||
"name": "旋蒸样品瓶",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 700,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 500.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rotavap_condenser",
|
||||
"name": "旋蒸冷凝器",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 800,
|
||||
"y": 350,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 500.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_distillate",
|
||||
"name": "溶剂回收瓶",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 800,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_ethanol",
|
||||
"name": "乙醇清洗瓶",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 50,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "ethanol",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_acetone",
|
||||
"name": "丙酮清洗瓶",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 150,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "acetone",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_water",
|
||||
"name": "蒸馏水瓶",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 250,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "water",
|
||||
"liquid_volume": 900.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_air",
|
||||
"name": "空气瓶",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 350,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "waste_workup",
|
||||
"name": "废液瓶",
|
||||
"children": [],
|
||||
"parent": "EvaporateProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 3000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"id": "link_pump1_valve1",
|
||||
"source": "transfer_pump_1",
|
||||
"target": "multiway_valve_1",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"transfer_pump_1": "transferpump",
|
||||
"multiway_valve_1": "transferpump"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_pump2_valve2",
|
||||
"source": "transfer_pump_2",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"transfer_pump_2": "transferpump",
|
||||
"multiway_valve_2": "transferpump"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_air",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_air",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "1",
|
||||
"flask_air": "top"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_ethanol",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_ethanol",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "2",
|
||||
"flask_ethanol": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_acetone",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_acetone",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "3",
|
||||
"flask_acetone": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_water",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_water",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "4",
|
||||
"flask_water": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_valve2",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "5",
|
||||
"multiway_valve_2": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_reaction_mixture",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "reaction_mixture",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "2",
|
||||
"reaction_mixture": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_rotavap_flask",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "rotavap_flask",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "3",
|
||||
"rotavap_flask": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_rotavap_condenser",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "rotavap_condenser",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "4",
|
||||
"rotavap_condenser": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_distillate",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "flask_distillate",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "5",
|
||||
"flask_distillate": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_waste",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "waste_workup",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "6",
|
||||
"waste_workup": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_rotavap1_rotavap_flask",
|
||||
"source": "rotavap_1",
|
||||
"target": "rotavap_flask",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"rotavap_1": "rotavap-sample",
|
||||
"rotavap_flask": "rotavap_port"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_heatchill1_reaction_mixture",
|
||||
"source": "heatchill_1",
|
||||
"target": "reaction_mixture",
|
||||
"type": "mechanical",
|
||||
"port": {
|
||||
"heatchill_1": "heatchill",
|
||||
"reaction_mixture": "heating_jacket"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,534 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "FilterProtocolTestStation",
|
||||
"name": "过滤协议测试站",
|
||||
"children": [
|
||||
"transfer_pump_1",
|
||||
"transfer_pump_2",
|
||||
"multiway_valve_1",
|
||||
"multiway_valve_2",
|
||||
"filter_1",
|
||||
"heatchill_1",
|
||||
"reaction_mixture",
|
||||
"filter_vessel",
|
||||
"filtrate_vessel",
|
||||
"collection_bottle_1",
|
||||
"collection_bottle_2",
|
||||
"flask_water",
|
||||
"flask_ethanol",
|
||||
"flask_acetone",
|
||||
"flask_air",
|
||||
"waste_workup"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "workstation",
|
||||
"position": {
|
||||
"x": 500,
|
||||
"y": 200,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"protocol_type": [
|
||||
"FilterProtocol",
|
||||
"PumpTransferProtocol",
|
||||
"HeatChillProtocol",
|
||||
"HeatChillStartProtocol",
|
||||
"HeatChillStopProtocol"
|
||||
]
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "transfer_pump_1",
|
||||
"name": "主转移泵",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_transfer_pump",
|
||||
"position": {
|
||||
"x": 200,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_PUMP1",
|
||||
"max_volume": 25.0,
|
||||
"transfer_rate": 2.0
|
||||
},
|
||||
"data": {
|
||||
"position": 0.0,
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "transfer_pump_2",
|
||||
"name": "副转移泵",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_transfer_pump",
|
||||
"position": {
|
||||
"x": 400,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_PUMP2",
|
||||
"max_volume": 25.0,
|
||||
"transfer_rate": 2.0
|
||||
},
|
||||
"data": {
|
||||
"position": 0.0,
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multiway_valve_1",
|
||||
"name": "溶剂分配阀",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_multiway_valve",
|
||||
"position": {
|
||||
"x": 200,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VALVE1",
|
||||
"positions": 8
|
||||
},
|
||||
"data": {
|
||||
"current_position": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multiway_valve_2",
|
||||
"name": "样品分配阀",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_multiway_valve",
|
||||
"position": {
|
||||
"x": 400,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VALVE2",
|
||||
"positions": 8
|
||||
},
|
||||
"data": {
|
||||
"current_position": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "filter_1",
|
||||
"name": "过滤器",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_filter",
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 350,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_FILTER1",
|
||||
"max_temp": 100.0,
|
||||
"max_stir_speed": 1000.0,
|
||||
"max_volume": 500.0
|
||||
},
|
||||
"data": {
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "heatchill_1",
|
||||
"name": "加热搅拌器",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_heatchill",
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_HEATCHILL1",
|
||||
"max_temp": 100.0,
|
||||
"min_temp": 4.0,
|
||||
"max_stir_speed": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "reaction_mixture",
|
||||
"name": "反应混合物",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 100,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "cell_suspension",
|
||||
"liquid_volume": 200.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "filter_vessel",
|
||||
"name": "过滤器容器",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 500.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "filtrate_vessel",
|
||||
"name": "滤液收集容器",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 700,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 500.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "collection_bottle_1",
|
||||
"name": "收集瓶1",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 800,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "collection_bottle_2",
|
||||
"name": "收集瓶2",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 900,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_water",
|
||||
"name": "蒸馏水瓶",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 200,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "water",
|
||||
"liquid_volume": 900.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_ethanol",
|
||||
"name": "乙醇清洗瓶",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 300,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "ethanol",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_acetone",
|
||||
"name": "丙酮清洗瓶",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 400,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "acetone",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_air",
|
||||
"name": "空气瓶",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 100,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "waste_workup",
|
||||
"name": "废液瓶",
|
||||
"children": [],
|
||||
"parent": "FilterProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 800,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 2000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"id": "link_pump1_valve1",
|
||||
"source": "transfer_pump_1",
|
||||
"target": "multiway_valve_1",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"transfer_pump_1": "transferpump",
|
||||
"multiway_valve_1": "transferpump"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_pump2_valve2",
|
||||
"source": "transfer_pump_2",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"transfer_pump_2": "transferpump",
|
||||
"multiway_valve_2": "transferpump"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_air",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_air",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "1",
|
||||
"flask_air": "top"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_water",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_water",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "2",
|
||||
"flask_water": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_ethanol",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_ethanol",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "3",
|
||||
"flask_ethanol": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_acetone",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_acetone",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "4",
|
||||
"flask_acetone": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_valve2",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "5",
|
||||
"multiway_valve_2": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_reaction_mixture",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "reaction_mixture",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "2",
|
||||
"reaction_mixture": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_filter_vessel",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "filter_vessel",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "3",
|
||||
"filter_vessel": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_filtrate_vessel",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "filtrate_vessel",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "4",
|
||||
"filtrate_vessel": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_collection1",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "collection_bottle_1",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "5",
|
||||
"collection_bottle_1": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_collection2",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "collection_bottle_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "6",
|
||||
"collection_bottle_2": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_waste",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "waste_workup",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "7",
|
||||
"waste_workup": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_filter1_filter_vessel",
|
||||
"source": "filter_1",
|
||||
"target": "filter_vessel",
|
||||
"type": "transport",
|
||||
"port": {
|
||||
"filter_1": "filter",
|
||||
"filter_vessel": "filter_port"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_heatchill1_filter_vessel",
|
||||
"source": "heatchill_1",
|
||||
"target": "filter_vessel",
|
||||
"type": "mechanical",
|
||||
"port": {
|
||||
"heatchill_1": "heatchill",
|
||||
"filter_vessel": "heating_jacket"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,671 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "HeatChillProtocolTestStation",
|
||||
"name": "加热冷却协议测试站",
|
||||
"children": [
|
||||
"transfer_pump_1",
|
||||
"transfer_pump_2",
|
||||
"multiway_valve_1",
|
||||
"multiway_valve_2",
|
||||
"stirrer_1",
|
||||
"stirrer_2",
|
||||
"heatchill_1",
|
||||
"heatchill_2",
|
||||
"flask_DMF",
|
||||
"flask_ethyl_acetate",
|
||||
"flask_methanol",
|
||||
"flask_acetone",
|
||||
"flask_water",
|
||||
"flask_ethanol",
|
||||
"flask_air",
|
||||
"main_reactor",
|
||||
"secondary_reactor",
|
||||
"waste_workup",
|
||||
"collection_bottle_1",
|
||||
"collection_bottle_2"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "workstation",
|
||||
"position": {
|
||||
"x": 500,
|
||||
"y": 200,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"protocol_type": [
|
||||
"PumpTransferProtocol",
|
||||
"AddProtocol",
|
||||
"HeatChillProtocol",
|
||||
"HeatChillStartProtocol",
|
||||
"HeatChillStopProtocol",
|
||||
"DissolveProtocol"
|
||||
]
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "transfer_pump_1",
|
||||
"name": "转移泵1",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_transfer_pump",
|
||||
"position": {
|
||||
"x": 250,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_PUMP1",
|
||||
"max_volume": 25.0,
|
||||
"transfer_rate": 5.0
|
||||
},
|
||||
"data": {
|
||||
"position": 0.0,
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "transfer_pump_2",
|
||||
"name": "转移泵2",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_transfer_pump",
|
||||
"position": {
|
||||
"x": 750,
|
||||
"y": 300,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_PUMP2",
|
||||
"max_volume": 25.0,
|
||||
"transfer_rate": 5.0
|
||||
},
|
||||
"data": {
|
||||
"position": 0.0,
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multiway_valve_1",
|
||||
"name": "试剂分配阀",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_multiway_valve",
|
||||
"position": {
|
||||
"x": 250,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VALVE1",
|
||||
"positions": 8
|
||||
},
|
||||
"data": {
|
||||
"current_position": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multiway_valve_2",
|
||||
"name": "反应器分配阀",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_multiway_valve",
|
||||
"position": {
|
||||
"x": 750,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_VALVE2",
|
||||
"positions": 8
|
||||
},
|
||||
"data": {
|
||||
"current_position": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "stirrer_1",
|
||||
"name": "主反应器搅拌器",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_stirrer",
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_STIRRER1",
|
||||
"max_speed": 1500.0,
|
||||
"default_speed": 300.0
|
||||
},
|
||||
"data": {
|
||||
"speed": 0.0,
|
||||
"status": "Stopped"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "stirrer_2",
|
||||
"name": "副反应器搅拌器",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_stirrer",
|
||||
"position": {
|
||||
"x": 900,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_STIRRER2",
|
||||
"max_speed": 1500.0,
|
||||
"default_speed": 300.0
|
||||
},
|
||||
"data": {
|
||||
"speed": 0.0,
|
||||
"status": "Stopped"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "heatchill_1",
|
||||
"name": "主反应器加热冷却器",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_heatchill",
|
||||
"position": {
|
||||
"x": 550,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_HEATCHILL1",
|
||||
"max_temp": 200.0,
|
||||
"min_temp": -80.0,
|
||||
"max_stir_speed": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "heatchill_2",
|
||||
"name": "副反应器加热冷却器",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_heatchill",
|
||||
"position": {
|
||||
"x": 850,
|
||||
"y": 400,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_HEATCHILL2",
|
||||
"max_temp": 200.0,
|
||||
"min_temp": -80.0,
|
||||
"max_stir_speed": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_DMF",
|
||||
"name": "DMF试剂瓶",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 50,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "DMF",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_ethyl_acetate",
|
||||
"name": "乙酸乙酯试剂瓶",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 150,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "ethyl_acetate",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_methanol",
|
||||
"name": "甲醇试剂瓶",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 250,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "methanol",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_ethanol",
|
||||
"name": "乙醇试剂瓶",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 650,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "ethanol",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_acetone",
|
||||
"name": "丙酮试剂瓶",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 350,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "acetone",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_water",
|
||||
"name": "蒸馏水瓶",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 450,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "water",
|
||||
"liquid_volume": 800.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "flask_air",
|
||||
"name": "空气瓶",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 550,
|
||||
"y": 550,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "main_reactor",
|
||||
"name": "主反应器",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 2000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "secondary_reactor",
|
||||
"name": "副反应器",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 900,
|
||||
"y": 500,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "waste_workup",
|
||||
"name": "废液处理瓶",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 700,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 2000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "collection_bottle_1",
|
||||
"name": "收集瓶1",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 800,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "collection_bottle_2",
|
||||
"name": "收集瓶2",
|
||||
"children": [],
|
||||
"parent": "HeatChillProtocolTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 900,
|
||||
"y": 600,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"id": "link_pump1_valve1",
|
||||
"source": "transfer_pump_1",
|
||||
"target": "multiway_valve_1",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"transfer_pump_1": "transferpump",
|
||||
"multiway_valve_1": "transferpump"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_pump2_valve2",
|
||||
"source": "transfer_pump_2",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"transfer_pump_2": "transferpump",
|
||||
"multiway_valve_2": "transferpump"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_valve2",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "multiway_valve_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "8",
|
||||
"multiway_valve_2": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_DMF",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_DMF",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "1",
|
||||
"flask_DMF": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_ethyl_acetate",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_ethyl_acetate",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "2",
|
||||
"flask_ethyl_acetate": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_methanol",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_methanol",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "3",
|
||||
"flask_methanol": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_acetone",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_acetone",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "4",
|
||||
"flask_acetone": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_water",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_water",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "5",
|
||||
"flask_water": "outlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_air",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_air",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "6",
|
||||
"flask_air": "top"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_main_reactor",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "main_reactor",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "2",
|
||||
"main_reactor": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_secondary_reactor",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "secondary_reactor",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "3",
|
||||
"secondary_reactor": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_waste",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "waste_workup",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "6",
|
||||
"waste_workup": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_collection1",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "collection_bottle_1",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "7",
|
||||
"collection_bottle_1": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve2_collection2",
|
||||
"source": "multiway_valve_2",
|
||||
"target": "collection_bottle_2",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_2": "8",
|
||||
"collection_bottle_2": "inlet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_stirrer1_main_reactor",
|
||||
"source": "stirrer_1",
|
||||
"target": "main_reactor",
|
||||
"type": "mechanical",
|
||||
"port": {
|
||||
"stirrer_1": "stirrer_head",
|
||||
"main_reactor": "stirrer_port"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_stirrer2_secondary_reactor",
|
||||
"source": "stirrer_2",
|
||||
"target": "secondary_reactor",
|
||||
"type": "mechanical",
|
||||
"port": {
|
||||
"stirrer_2": "stirrer_head",
|
||||
"secondary_reactor": "stirrer_port"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_heatchill1_main_reactor",
|
||||
"source": "heatchill_1",
|
||||
"target": "main_reactor",
|
||||
"type": "thermal",
|
||||
"port": {
|
||||
"heatchill_1": "heating_surface",
|
||||
"main_reactor": "heating_jacket"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_heatchill2_secondary_reactor",
|
||||
"source": "heatchill_2",
|
||||
"target": "secondary_reactor",
|
||||
"type": "thermal",
|
||||
"port": {
|
||||
"heatchill_2": "heating_surface",
|
||||
"secondary_reactor": "heating_jacket"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_valve1_ethanol",
|
||||
"source": "multiway_valve_1",
|
||||
"target": "flask_ethanol",
|
||||
"type": "fluid",
|
||||
"port": {
|
||||
"multiway_valve_1": "7",
|
||||
"flask_ethanol": "outlet"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "SimpleStirHeatChillTestStation",
|
||||
"name": "搅拌加热测试站",
|
||||
"children": [
|
||||
"stirrer_1",
|
||||
"heatchill_1",
|
||||
"main_reactor",
|
||||
"secondary_reactor"
|
||||
],
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "workstation",
|
||||
"position": {
|
||||
"x": 500,
|
||||
"y": 200,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"protocol_type": [
|
||||
"StirProtocol",
|
||||
"StartStirProtocol",
|
||||
"StopStirProtocol",
|
||||
"HeatChillProtocol",
|
||||
"HeatChillStartProtocol",
|
||||
"HeatChillStopProtocol"
|
||||
]
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "stirrer_1",
|
||||
"name": "主搅拌器",
|
||||
"children": [],
|
||||
"parent": "SimpleStirHeatChillTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_stirrer",
|
||||
"position": {
|
||||
"x": 400,
|
||||
"y": 350,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_STIRRER1",
|
||||
"max_speed": 1500.0,
|
||||
"min_speed": 50.0
|
||||
},
|
||||
"data": {
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "heatchill_1",
|
||||
"name": "主加热冷却器",
|
||||
"children": [],
|
||||
"parent": "SimpleStirHeatChillTestStation",
|
||||
"type": "device",
|
||||
"class": "virtual_heatchill",
|
||||
"position": {
|
||||
"x": 600,
|
||||
"y": 350,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"port": "VIRTUAL_HEATCHILL1",
|
||||
"max_temp": 200.0,
|
||||
"min_temp": -80.0,
|
||||
"max_stir_speed": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"status": "Idle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "main_reactor",
|
||||
"name": "主反应器",
|
||||
"children": [],
|
||||
"parent": "SimpleStirHeatChillTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 500,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 2000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": [
|
||||
{
|
||||
"liquid_type": "water",
|
||||
"liquid_volume": 500.0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "secondary_reactor",
|
||||
"name": "副反应器",
|
||||
"children": [],
|
||||
"parent": "SimpleStirHeatChillTestStation",
|
||||
"type": "container",
|
||||
"class": null,
|
||||
"position": {
|
||||
"x": 700,
|
||||
"y": 450,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"max_volume": 1000.0
|
||||
},
|
||||
"data": {
|
||||
"liquid": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"id": "link_stirrer1_main_reactor",
|
||||
"source": "stirrer_1",
|
||||
"target": "main_reactor",
|
||||
"type": "mechanical",
|
||||
"port": {
|
||||
"stirrer_1": "stirrer",
|
||||
"main_reactor": "stirrer_port"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "link_heatchill1_main_reactor",
|
||||
"source": "heatchill_1",
|
||||
"target": "main_reactor",
|
||||
"type": "mechanical",
|
||||
"port": {
|
||||
"heatchill_1": "heatchill",
|
||||
"main_reactor": "heating_jacket"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
1. 用到的仪器
|
||||
virtual_multiway_valve() 八通阀门
|
||||
virtual_transfer_pump() 转移泵
|
||||
virtual_multiway_valve(√) 八通阀门
|
||||
virtual_transfer_pump(√) 转移泵
|
||||
virtual_centrifuge() 离心机
|
||||
virtual_rotavap() 旋蒸仪
|
||||
virtual_heatchill() 加热器
|
||||
@@ -12,21 +12,23 @@
|
||||
virtual_column(√) 层析柱
|
||||
separator() homemade_grbl_conductivity 分液漏斗
|
||||
2. 用到的protocol
|
||||
AddProtocol()
|
||||
TransferProtocol() 应该用pump_protocol.py删掉transfer
|
||||
StartStirProtocol()
|
||||
StopStirProtocol()
|
||||
StirProtocol()
|
||||
RunColumnProtocol()
|
||||
CentrifugeProtocol()
|
||||
FilterProtocol()
|
||||
CleanVesselProtocol()
|
||||
DissolveProtocol()
|
||||
FilterThroughProtocol()
|
||||
WashSolidProtocol()
|
||||
SeparateProtocol()
|
||||
EvaporateProtocol()
|
||||
HeatChillProtocol()
|
||||
HeatChillStartProtocol()
|
||||
HeatChillStopProtocol()
|
||||
EvacuateAndRefillProtocol()
|
||||
PumpTransferProtocol: generate_pump_protocol_with_rinsing, (√)
|
||||
这个重复了,删掉CleanProtocol: generate_clean_protocol,
|
||||
SeparateProtocol: generate_separate_protocol, (×)
|
||||
EvaporateProtocol: generate_evaporate_protocol, (√)
|
||||
EvacuateAndRefillProtocol: generate_evacuateandrefill_protocol, (√)
|
||||
CentrifugeProtocol: generate_centrifuge_protocol, (√)
|
||||
AddProtocol: generate_add_protocol, (√)
|
||||
FilterProtocol: generate_filter_protocol, (√)
|
||||
HeatChillProtocol: generate_heat_chill_protocol, (√)
|
||||
HeatChillStartProtocol: generate_heat_chill_start_protocol, (√)
|
||||
HeatChillStopProtocol: generate_heat_chill_stop_protocol, (√)
|
||||
StirProtocol: generate_stir_protocol, (√)
|
||||
StartStirProtocol: generate_start_stir_protocol, (√)
|
||||
StopStirProtocol: generate_stop_stir_protocol, (√)
|
||||
这个重复了,删掉TransferProtocol: generate_transfer_protocol,
|
||||
CleanVesselProtocol: generate_clean_vessel_protocol, (√)
|
||||
DissolveProtocol: generate_dissolve_protocol, (√)
|
||||
FilterThroughProtocol: generate_filter_through_protocol, (×)
|
||||
RunColumnProtocol: generate_run_column_protocol, (×)
|
||||
WashSolidProtocol: generate_wash_solid_protocol, (×)
|
||||
|
||||
@@ -8,7 +8,12 @@ from .agv_transfer_protocol import generate_agv_transfer_protocol
|
||||
from .add_protocol import generate_add_protocol
|
||||
from .centrifuge_protocol import generate_centrifuge_protocol
|
||||
from .filter_protocol import generate_filter_protocol
|
||||
from .heatchill_protocol import generate_heat_chill_protocol, generate_heat_chill_start_protocol, generate_heat_chill_stop_protocol
|
||||
from .heatchill_protocol import (
|
||||
generate_heat_chill_protocol,
|
||||
generate_heat_chill_start_protocol,
|
||||
generate_heat_chill_stop_protocol,
|
||||
generate_heat_chill_to_temp_protocol # 保留导入,但不注册为协议
|
||||
)
|
||||
from .stir_protocol import generate_stir_protocol, generate_start_stir_protocol, generate_stop_stir_protocol
|
||||
from .transfer_protocol import generate_transfer_protocol
|
||||
from .clean_vessel_protocol import generate_clean_vessel_protocol
|
||||
@@ -32,6 +37,7 @@ action_protocol_generators = {
|
||||
HeatChillProtocol: generate_heat_chill_protocol,
|
||||
HeatChillStartProtocol: generate_heat_chill_start_protocol,
|
||||
HeatChillStopProtocol: generate_heat_chill_stop_protocol,
|
||||
# HeatChillToTempProtocol: generate_heat_chill_to_temp_protocol, # **移除这行**
|
||||
StirProtocol: generate_stir_protocol,
|
||||
StartStirProtocol: generate_start_stir_protocol,
|
||||
StopStirProtocol: generate_stop_stir_protocol,
|
||||
|
||||
@@ -1,288 +1,381 @@
|
||||
import networkx as nx
|
||||
from typing import List, Dict, Any
|
||||
|
||||
def generate_add_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
reagent: str,
|
||||
volume: float,
|
||||
mass: float,
|
||||
amount: str,
|
||||
time: float,
|
||||
stir: bool,
|
||||
stir_speed: float,
|
||||
viscous: bool,
|
||||
purpose: str
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成添加试剂的协议序列
|
||||
|
||||
流程:
|
||||
1. 找到包含目标试剂的试剂瓶
|
||||
2. 配置八通阀门到试剂瓶位置
|
||||
3. 使用注射器泵吸取试剂
|
||||
4. 配置八通阀门到反应器位置
|
||||
5. 使用注射器泵推送试剂到反应器
|
||||
6. 如果需要,启动搅拌
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 验证目标容器存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"目标容器 {vessel} 不存在")
|
||||
|
||||
# 如果指定了体积,执行液体转移
|
||||
if volume > 0:
|
||||
# 1. 查找注射器泵 (transfer pump)
|
||||
transfer_pump_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
||||
|
||||
if not transfer_pump_nodes:
|
||||
raise ValueError("没有找到可用的注射器泵 (virtual_transfer_pump)")
|
||||
|
||||
transfer_pump_id = transfer_pump_nodes[0]
|
||||
|
||||
# 2. 查找八通阀门
|
||||
multiway_valve_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_multiway_valve']
|
||||
|
||||
if not multiway_valve_nodes:
|
||||
raise ValueError("没有找到可用的八通阀门 (virtual_multiway_valve)")
|
||||
|
||||
valve_id = multiway_valve_nodes[0]
|
||||
|
||||
# 3. 查找包含指定试剂的试剂瓶
|
||||
reagent_vessel = None
|
||||
available_flasks = [node for node in G.nodes()
|
||||
if node.startswith('flask_')
|
||||
and G.nodes[node].get('type') == 'container']
|
||||
|
||||
# 简化:使用第一个可用的试剂瓶,实际应该根据试剂名称匹配
|
||||
if available_flasks:
|
||||
reagent_vessel = available_flasks[0]
|
||||
else:
|
||||
raise ValueError("没有找到可用的试剂容器")
|
||||
|
||||
# 4. 获取试剂瓶和反应器对应的阀门位置
|
||||
# 这需要根据实际连接图来确定,这里假设:
|
||||
reagent_valve_position = 1 # 试剂瓶连接到阀门位置1
|
||||
reactor_valve_position = 2 # 反应器连接到阀门位置2
|
||||
|
||||
# 5. 执行添加操作序列
|
||||
|
||||
# 5.1 设置阀门到试剂瓶位置
|
||||
action_sequence.append({
|
||||
"device_id": valve_id,
|
||||
"action_name": "set_position",
|
||||
"action_kwargs": {
|
||||
"position": reagent_valve_position
|
||||
}
|
||||
})
|
||||
|
||||
# 5.2 使用注射器泵从试剂瓶吸取液体
|
||||
action_sequence.append({
|
||||
"device_id": transfer_pump_id,
|
||||
"action_name": "transfer",
|
||||
"action_kwargs": {
|
||||
"from_vessel": reagent_vessel,
|
||||
"to_vessel": transfer_pump_id, # 吸入到注射器
|
||||
"volume": volume,
|
||||
"amount": amount,
|
||||
"time": time / 2, # 吸取时间为总时间的一半
|
||||
"viscous": viscous,
|
||||
"rinsing_solvent": "",
|
||||
"rinsing_volume": 0.0,
|
||||
"rinsing_repeats": 0,
|
||||
"solid": False
|
||||
}
|
||||
})
|
||||
|
||||
# 5.3 设置阀门到反应器位置
|
||||
action_sequence.append({
|
||||
"device_id": valve_id,
|
||||
"action_name": "set_position",
|
||||
"action_kwargs": {
|
||||
"position": reactor_valve_position
|
||||
}
|
||||
})
|
||||
|
||||
# 5.4 使用注射器泵将液体推送到反应器
|
||||
action_sequence.append({
|
||||
"device_id": transfer_pump_id,
|
||||
"action_name": "transfer",
|
||||
"action_kwargs": {
|
||||
"from_vessel": transfer_pump_id, # 从注射器推出
|
||||
"to_vessel": vessel,
|
||||
"volume": volume,
|
||||
"amount": amount,
|
||||
"time": time / 2, # 推送时间为总时间的一半
|
||||
"viscous": viscous,
|
||||
"rinsing_solvent": "",
|
||||
"rinsing_volume": 0.0,
|
||||
"rinsing_repeats": 0,
|
||||
"solid": False
|
||||
}
|
||||
})
|
||||
|
||||
# 6. 如果需要搅拌,启动搅拌器
|
||||
if stir:
|
||||
stirrer_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_stirrer']
|
||||
|
||||
if stirrer_nodes:
|
||||
stirrer_id = stirrer_nodes[0]
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"stir_speed": stir_speed,
|
||||
"purpose": f"添加 {reagent} 后搅拌混合"
|
||||
}
|
||||
})
|
||||
else:
|
||||
print("警告:需要搅拌但未找到搅拌设备")
|
||||
|
||||
return action_sequence
|
||||
import networkx as nx
|
||||
from typing import List, Dict, Any
|
||||
from .pump_protocol import generate_pump_protocol_with_rinsing
|
||||
|
||||
|
||||
def find_valve_position_for_vessel(G: nx.DiGraph, valve_id: str, vessel_id: str) -> int:
|
||||
def find_reagent_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||
"""
|
||||
根据连接图找到容器对应的阀门位置
|
||||
根据试剂名称查找对应的试剂瓶
|
||||
|
||||
Args:
|
||||
G: 网络图
|
||||
valve_id: 阀门设备ID
|
||||
vessel_id: 容器ID
|
||||
reagent: 试剂名称
|
||||
|
||||
Returns:
|
||||
int: 阀门位置编号 (1-8)
|
||||
str: 试剂瓶的vessel ID
|
||||
|
||||
Raises:
|
||||
ValueError: 如果找不到对应的试剂瓶
|
||||
"""
|
||||
# 查找阀门到容器的连接
|
||||
edges = G.edges(data=True)
|
||||
# 按照pump_protocol的命名规则查找试剂瓶
|
||||
reagent_vessel_id = f"flask_{reagent}"
|
||||
|
||||
for source, target, data in edges:
|
||||
if source == valve_id and target == vessel_id:
|
||||
# 从连接数据中提取端口信息
|
||||
port_info = data.get('port', {})
|
||||
valve_port = port_info.get(valve_id, '')
|
||||
|
||||
# 解析端口名称获取位置编号
|
||||
if valve_port.startswith('multiway-valve-port-'):
|
||||
position = valve_port.split('-')[-1]
|
||||
return int(position)
|
||||
if reagent_vessel_id in G.nodes():
|
||||
return reagent_vessel_id
|
||||
|
||||
# 默认返回位置1
|
||||
return 1
|
||||
# 如果直接匹配失败,尝试模糊匹配
|
||||
for node in G.nodes():
|
||||
if node.startswith('flask_') and reagent.lower() in node.lower():
|
||||
return node
|
||||
|
||||
# 如果还是找不到,列出所有可用的试剂瓶
|
||||
available_flasks = [node for node in G.nodes()
|
||||
if node.startswith('flask_')
|
||||
and G.nodes[node].get('type') == 'container']
|
||||
|
||||
raise ValueError(f"找不到试剂 '{reagent}' 对应的试剂瓶。可用试剂瓶: {available_flasks}")
|
||||
|
||||
|
||||
def generate_add_with_autodiscovery(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
reagent: str,
|
||||
volume: float,
|
||||
**kwargs
|
||||
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
|
||||
"""
|
||||
查找与指定容器相连的搅拌器
|
||||
|
||||
Args:
|
||||
G: 网络图
|
||||
vessel: 容器ID
|
||||
|
||||
Returns:
|
||||
str: 搅拌器ID,如果找不到则返回None
|
||||
"""
|
||||
# 查找所有搅拌器节点
|
||||
stirrer_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_stirrer']
|
||||
|
||||
# 检查哪个搅拌器与目标容器相连
|
||||
for stirrer in stirrer_nodes:
|
||||
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
|
||||
return stirrer
|
||||
|
||||
# 如果没有直接连接,返回第一个可用的搅拌器
|
||||
return stirrer_nodes[0] if stirrer_nodes else None
|
||||
|
||||
|
||||
def generate_add_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
reagent: str,
|
||||
volume: float,
|
||||
mass: float = 0.0,
|
||||
amount: str = "",
|
||||
time: float = 0.0,
|
||||
stir: bool = False,
|
||||
stir_speed: float = 300.0,
|
||||
viscous: bool = False,
|
||||
purpose: str = "添加试剂"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
智能添加协议生成器 - 自动发现设备连接关系
|
||||
生成添加试剂的协议序列
|
||||
|
||||
基于pump_protocol的成熟算法,实现试剂添加功能:
|
||||
1. 自动查找试剂瓶
|
||||
2. **先启动搅拌,再进行转移** - 确保试剂添加更均匀
|
||||
3. 使用pump_protocol实现液体转移
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为容器和设备,边为连接关系
|
||||
vessel: 目标容器(要添加试剂的容器)
|
||||
reagent: 试剂名称(用于查找对应的试剂瓶)
|
||||
volume: 要添加的体积 (mL)
|
||||
mass: 要添加的质量 (g) - 暂时未使用,预留接口
|
||||
amount: 其他数量描述
|
||||
time: 添加时间 (s),如果指定则计算流速
|
||||
stir: 是否启用搅拌
|
||||
stir_speed: 搅拌速度 (RPM)
|
||||
viscous: 是否为粘稠液体
|
||||
purpose: 添加目的描述
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 动作序列
|
||||
|
||||
Raises:
|
||||
ValueError: 当找不到必要的设备或容器时
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 查找必需的设备
|
||||
devices = {
|
||||
'transfer_pump': None,
|
||||
'multiway_valve': None,
|
||||
'stirrer': None
|
||||
}
|
||||
# 1. 验证目标容器存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
for node in G.nodes():
|
||||
node_class = G.nodes[node].get('class')
|
||||
if node_class == 'virtual_transfer_pump':
|
||||
devices['transfer_pump'] = node
|
||||
elif node_class == 'virtual_multiway_valve':
|
||||
devices['multiway_valve'] = node
|
||||
elif node_class == 'virtual_stirrer':
|
||||
devices['stirrer'] = node
|
||||
# 2. 查找试剂瓶
|
||||
try:
|
||||
reagent_vessel = find_reagent_vessel(G, reagent)
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到试剂 '{reagent}': {str(e)}")
|
||||
|
||||
# 验证必需设备
|
||||
if not devices['transfer_pump']:
|
||||
raise ValueError("缺少注射器泵设备")
|
||||
if not devices['multiway_valve']:
|
||||
raise ValueError("缺少八通阀门设备")
|
||||
# 3. 验证是否存在从试剂瓶到目标容器的路径
|
||||
try:
|
||||
path = nx.shortest_path(G, source=reagent_vessel, target=vessel)
|
||||
print(f"ADD_PROTOCOL: 找到路径 {reagent_vessel} -> {vessel}: {path}")
|
||||
except nx.NetworkXNoPath:
|
||||
raise ValueError(f"从试剂瓶 '{reagent_vessel}' 到目标容器 '{vessel}' 没有可用路径")
|
||||
|
||||
# 查找试剂容器
|
||||
reagent_vessels = [node for node in G.nodes()
|
||||
if node.startswith('flask_')
|
||||
and G.nodes[node].get('type') == 'container']
|
||||
# 4. **先启动搅拌** - 关键改进!
|
||||
if stir:
|
||||
try:
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
|
||||
if stirrer_id:
|
||||
print(f"ADD_PROTOCOL: 找到搅拌器 {stirrer_id},将在添加前启动搅拌")
|
||||
|
||||
# 先启动搅拌
|
||||
stir_action = {
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"stir_speed": stir_speed,
|
||||
"purpose": f"{purpose}: 启动搅拌,准备添加 {reagent}"
|
||||
}
|
||||
}
|
||||
|
||||
action_sequence.append(stir_action)
|
||||
print(f"ADD_PROTOCOL: 已添加搅拌动作,速度 {stir_speed} RPM")
|
||||
else:
|
||||
print(f"ADD_PROTOCOL: 警告 - 需要搅拌但未找到与容器 {vessel} 相连的搅拌器")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ADD_PROTOCOL: 搅拌器配置出错: {str(e)}")
|
||||
|
||||
if not reagent_vessels:
|
||||
raise ValueError("没有找到试剂容器")
|
||||
# 5. 如果指定了体积,执行液体转移
|
||||
if volume > 0:
|
||||
# 5.1 计算流速参数
|
||||
if time > 0:
|
||||
# 根据时间计算流速
|
||||
transfer_flowrate = volume / time
|
||||
flowrate = transfer_flowrate
|
||||
else:
|
||||
# 使用默认流速
|
||||
if viscous:
|
||||
transfer_flowrate = 0.3 # 粘稠液体用较慢速度
|
||||
flowrate = 1.0
|
||||
else:
|
||||
transfer_flowrate = 0.5 # 普通液体默认速度
|
||||
flowrate = 2.5
|
||||
|
||||
print(f"ADD_PROTOCOL: 准备转移 {volume} mL 从 {reagent_vessel} 到 {vessel}")
|
||||
print(f"ADD_PROTOCOL: 转移流速={transfer_flowrate} mL/s, 注入流速={flowrate} mL/s")
|
||||
|
||||
# 5.2 使用pump_protocol的核心算法实现液体转移
|
||||
try:
|
||||
pump_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=reagent_vessel,
|
||||
to_vessel=vessel,
|
||||
volume=volume,
|
||||
amount=amount,
|
||||
time=time,
|
||||
viscous=viscous,
|
||||
rinsing_solvent="", # 添加试剂通常不需要清洗
|
||||
rinsing_volume=0.0,
|
||||
rinsing_repeats=0,
|
||||
solid=False,
|
||||
flowrate=flowrate,
|
||||
transfer_flowrate=transfer_flowrate
|
||||
)
|
||||
|
||||
# 添加pump actions到序列中
|
||||
action_sequence.extend(pump_actions)
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(f"生成泵协议时出错: {str(e)}")
|
||||
|
||||
# 执行添加流程
|
||||
reagent_vessel = reagent_vessels[0]
|
||||
reagent_pos = find_valve_position_for_vessel(G, devices['multiway_valve'], reagent_vessel)
|
||||
reactor_pos = find_valve_position_for_vessel(G, devices['multiway_valve'], vessel)
|
||||
print(f"ADD_PROTOCOL: 生成了 {len(action_sequence)} 个动作")
|
||||
return action_sequence
|
||||
|
||||
|
||||
def generate_add_protocol_with_cleaning(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
reagent: str,
|
||||
volume: float,
|
||||
mass: float = 0.0,
|
||||
amount: str = "",
|
||||
time: float = 0.0,
|
||||
stir: bool = False,
|
||||
stir_speed: float = 300.0,
|
||||
viscous: bool = False,
|
||||
purpose: str = "添加试剂",
|
||||
cleaning_solvent: str = "air",
|
||||
cleaning_volume: float = 5.0,
|
||||
cleaning_repeats: int = 1
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成带清洗的添加试剂协议
|
||||
|
||||
# 生成操作序列
|
||||
action_sequence.extend([
|
||||
# 切换到试剂瓶
|
||||
{
|
||||
"device_id": devices['multiway_valve'],
|
||||
"action_name": "set_position",
|
||||
"action_kwargs": {"position": reagent_pos}
|
||||
},
|
||||
# 吸取试剂
|
||||
{
|
||||
"device_id": devices['transfer_pump'],
|
||||
"action_name": "transfer",
|
||||
"action_kwargs": {
|
||||
"from_vessel": reagent_vessel,
|
||||
"to_vessel": devices['transfer_pump'],
|
||||
"volume": volume,
|
||||
"amount": kwargs.get('amount', ''),
|
||||
"time": kwargs.get('time', 10.0) / 2,
|
||||
"viscous": kwargs.get('viscous', False),
|
||||
"rinsing_solvent": "",
|
||||
"rinsing_volume": 0.0,
|
||||
"rinsing_repeats": 0,
|
||||
"solid": False
|
||||
与普通添加协议的区别是会在添加后进行管道清洗
|
||||
|
||||
Args:
|
||||
G: 有向图
|
||||
vessel: 目标容器
|
||||
reagent: 试剂名称
|
||||
volume: 添加体积
|
||||
mass: 添加质量(预留)
|
||||
amount: 其他数量描述
|
||||
time: 添加时间
|
||||
stir: 是否搅拌
|
||||
stir_speed: 搅拌速度
|
||||
viscous: 是否粘稠
|
||||
purpose: 添加目的
|
||||
cleaning_solvent: 清洗溶剂("air"表示空气清洗)
|
||||
cleaning_volume: 清洗体积
|
||||
cleaning_repeats: 清洗重复次数
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 动作序列
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 1. 查找试剂瓶
|
||||
reagent_vessel = find_reagent_vessel(G, reagent)
|
||||
|
||||
# 2. **先启动搅拌**
|
||||
if stir:
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
if stirrer_id:
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"stir_speed": stir_speed,
|
||||
"purpose": f"{purpose}: 启动搅拌,准备添加 {reagent}"
|
||||
}
|
||||
})
|
||||
|
||||
# 3. 计算流速
|
||||
if time > 0:
|
||||
transfer_flowrate = volume / time
|
||||
flowrate = transfer_flowrate
|
||||
else:
|
||||
if viscous:
|
||||
transfer_flowrate = 0.3
|
||||
flowrate = 1.0
|
||||
else:
|
||||
transfer_flowrate = 0.5
|
||||
flowrate = 2.5
|
||||
|
||||
# 4. 使用带清洗的pump_protocol
|
||||
pump_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=reagent_vessel,
|
||||
to_vessel=vessel,
|
||||
volume=volume,
|
||||
amount=amount,
|
||||
time=time,
|
||||
viscous=viscous,
|
||||
rinsing_solvent=cleaning_solvent,
|
||||
rinsing_volume=cleaning_volume,
|
||||
rinsing_repeats=cleaning_repeats,
|
||||
solid=False,
|
||||
flowrate=flowrate,
|
||||
transfer_flowrate=transfer_flowrate
|
||||
)
|
||||
|
||||
action_sequence.extend(pump_actions)
|
||||
|
||||
return action_sequence
|
||||
|
||||
|
||||
def generate_sequential_add_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
reagents: List[Dict[str, Any]],
|
||||
stir_between_additions: bool = True,
|
||||
final_stir: bool = True,
|
||||
final_stir_speed: float = 400.0,
|
||||
final_stir_time: float = 300.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成连续添加多种试剂的协议
|
||||
|
||||
Args:
|
||||
G: 网络图
|
||||
vessel: 目标容器
|
||||
reagents: 试剂列表,每个元素包含试剂添加参数
|
||||
stir_between_additions: 是否在每次添加之间搅拌
|
||||
final_stir: 是否在所有添加完成后进行最终搅拌
|
||||
final_stir_speed: 最终搅拌速度
|
||||
final_stir_time: 最终搅拌时间
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 完整的动作序列
|
||||
|
||||
Example:
|
||||
reagents = [
|
||||
{
|
||||
"reagent": "DMF",
|
||||
"volume": 10.0,
|
||||
"viscous": False,
|
||||
"stir_speed": 300.0
|
||||
},
|
||||
{
|
||||
"reagent": "ethyl_acetate",
|
||||
"volume": 5.0,
|
||||
"viscous": False,
|
||||
"stir_speed": 350.0
|
||||
}
|
||||
},
|
||||
# 切换到反应器
|
||||
{
|
||||
"device_id": devices['multiway_valve'],
|
||||
"action_name": "set_position",
|
||||
"action_kwargs": {"position": reactor_pos}
|
||||
},
|
||||
# 推送到反应器
|
||||
{
|
||||
"device_id": devices['transfer_pump'],
|
||||
"action_name": "transfer",
|
||||
"action_kwargs": {
|
||||
"from_vessel": devices['transfer_pump'],
|
||||
"to_vessel": vessel,
|
||||
"volume": volume,
|
||||
"amount": kwargs.get('amount', ''),
|
||||
"time": kwargs.get('time', 10.0) / 2,
|
||||
"viscous": kwargs.get('viscous', False),
|
||||
"rinsing_solvent": "",
|
||||
"rinsing_volume": 0.0,
|
||||
"rinsing_repeats": 0,
|
||||
"solid": False
|
||||
}
|
||||
}
|
||||
])
|
||||
]
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 如果需要搅拌
|
||||
if kwargs.get('stir', False) and devices['stirrer']:
|
||||
action_sequence.append({
|
||||
"device_id": devices['stirrer'],
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"stir_speed": kwargs.get('stir_speed', 300.0),
|
||||
"purpose": f"添加 {reagent} 后混合"
|
||||
}
|
||||
})
|
||||
for i, reagent_params in enumerate(reagents):
|
||||
print(f"ADD_PROTOCOL: 处理第 {i+1}/{len(reagents)} 个试剂: {reagent_params.get('reagent')}")
|
||||
|
||||
# 生成单个试剂的添加协议
|
||||
add_actions = generate_add_protocol(
|
||||
G=G,
|
||||
vessel=vessel,
|
||||
reagent=reagent_params.get('reagent'),
|
||||
volume=reagent_params.get('volume', 0.0),
|
||||
mass=reagent_params.get('mass', 0.0),
|
||||
amount=reagent_params.get('amount', ''),
|
||||
time=reagent_params.get('time', 0.0),
|
||||
stir=stir_between_additions,
|
||||
stir_speed=reagent_params.get('stir_speed', 300.0),
|
||||
viscous=reagent_params.get('viscous', False),
|
||||
purpose=reagent_params.get('purpose', f'添加试剂 {i+1}')
|
||||
)
|
||||
|
||||
action_sequence.extend(add_actions)
|
||||
|
||||
# 在添加之间加入等待时间
|
||||
if i < len(reagents) - 1: # 不是最后一个试剂
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 2}
|
||||
})
|
||||
|
||||
return action_sequence
|
||||
# 最终搅拌
|
||||
if final_stir:
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
if stirrer_id:
|
||||
action_sequence.extend([
|
||||
{
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": final_stir_time}
|
||||
}
|
||||
])
|
||||
|
||||
print(f"ADD_PROTOCOL: 连续添加协议生成完成,共 {len(action_sequence)} 个动作")
|
||||
return action_sequence
|
||||
|
||||
|
||||
# 使用示例和测试函数
|
||||
def test_add_protocol():
|
||||
"""测试添加协议的示例"""
|
||||
print("=== ADD PROTOCOL 测试 ===")
|
||||
print("测试完成")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_add_protocol()
|
||||
@@ -1,5 +1,59 @@
|
||||
from typing import List, Dict, Any
|
||||
import networkx as nx
|
||||
from .pump_protocol import generate_pump_protocol
|
||||
|
||||
|
||||
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
|
||||
"""
|
||||
获取容器中的液体体积
|
||||
"""
|
||||
if vessel not in G.nodes():
|
||||
return 0.0
|
||||
|
||||
vessel_data = G.nodes[vessel].get('data', {})
|
||||
liquids = vessel_data.get('liquid', [])
|
||||
|
||||
total_volume = 0.0
|
||||
for liquid in liquids:
|
||||
if isinstance(liquid, dict) and 'liquid_volume' in liquid:
|
||||
total_volume += liquid['liquid_volume']
|
||||
|
||||
return total_volume
|
||||
|
||||
|
||||
def find_centrifuge_device(G: nx.DiGraph) -> str:
|
||||
"""
|
||||
查找离心机设备
|
||||
"""
|
||||
centrifuge_nodes = [node for node in G.nodes()
|
||||
if (G.nodes[node].get('class') or '') == 'virtual_centrifuge']
|
||||
|
||||
if centrifuge_nodes:
|
||||
return centrifuge_nodes[0]
|
||||
|
||||
raise ValueError("系统中未找到离心机设备")
|
||||
|
||||
|
||||
def find_centrifuge_vessel(G: nx.DiGraph) -> str:
|
||||
"""
|
||||
查找离心机专用容器
|
||||
"""
|
||||
possible_names = [
|
||||
"centrifuge_tube",
|
||||
"centrifuge_vessel",
|
||||
"tube_centrifuge",
|
||||
"vessel_centrifuge",
|
||||
"centrifuge",
|
||||
"tube_15ml",
|
||||
"tube_50ml"
|
||||
]
|
||||
|
||||
for vessel_name in possible_names:
|
||||
if vessel_name in G.nodes():
|
||||
return vessel_name
|
||||
|
||||
raise ValueError(f"未找到离心机容器。尝试了以下名称: {possible_names}")
|
||||
|
||||
|
||||
def generate_centrifuge_protocol(
|
||||
G: nx.DiGraph,
|
||||
@@ -9,115 +63,223 @@ def generate_centrifuge_protocol(
|
||||
temp: float = 25.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成离心操作的协议序列
|
||||
生成离心操作的协议序列,复用 pump_protocol 的成熟算法
|
||||
|
||||
离心流程:
|
||||
1. 液体转移:将待离心溶液从源容器转移到离心机容器
|
||||
2. 离心操作:执行离心分离
|
||||
3. 上清液转移:将离心后的上清液转移回原容器或新容器
|
||||
4. 沉淀处理:处理离心沉淀(可选)
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为设备和容器
|
||||
vessel: 离心容器名称
|
||||
G: 有向图,节点为设备和容器,边为流体管道
|
||||
vessel: 包含待离心溶液的容器名称
|
||||
speed: 离心速度 (rpm)
|
||||
time: 离心时间 (秒)
|
||||
temp: 温度 (摄氏度,可选)
|
||||
temp: 离心温度 (°C),默认25°C
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 离心操作的动作序列
|
||||
|
||||
Raises:
|
||||
ValueError: 当找不到离心机设备时抛出异常
|
||||
ValueError: 当找不到必要的设备时抛出异常
|
||||
|
||||
Examples:
|
||||
centrifuge_protocol = generate_centrifuge_protocol(G, "reactor", 5000, 300, 4.0)
|
||||
centrifuge_actions = generate_centrifuge_protocol(G, "reaction_mixture", 5000, 600, 4.0)
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 查找离心机设备
|
||||
centrifuge_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_centrifuge']
|
||||
print(f"CENTRIFUGE: 开始生成离心协议")
|
||||
print(f" - 源容器: {vessel}")
|
||||
print(f" - 离心速度: {speed} rpm")
|
||||
print(f" - 离心时间: {time}s ({time/60:.1f}分钟)")
|
||||
print(f" - 离心温度: {temp}°C")
|
||||
|
||||
if not centrifuge_nodes:
|
||||
raise ValueError("没有找到可用的离心机设备")
|
||||
|
||||
# 使用第一个可用的离心机
|
||||
centrifuge_id = centrifuge_nodes[0]
|
||||
|
||||
# 验证容器是否存在
|
||||
# 验证源容器存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||
raise ValueError(f"源容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
# 执行离心操作
|
||||
action_sequence.append({
|
||||
# 获取源容器中的液体体积
|
||||
source_volume = get_vessel_liquid_volume(G, vessel)
|
||||
print(f"CENTRIFUGE: 源容器 {vessel} 中有 {source_volume} mL 液体")
|
||||
|
||||
# 查找离心机设备
|
||||
try:
|
||||
centrifuge_id = find_centrifuge_device(G)
|
||||
print(f"CENTRIFUGE: 找到离心机: {centrifuge_id}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到离心机: {str(e)}")
|
||||
|
||||
# 查找离心机容器
|
||||
try:
|
||||
centrifuge_vessel = find_centrifuge_vessel(G)
|
||||
print(f"CENTRIFUGE: 找到离心机容器: {centrifuge_vessel}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到离心机容器: {str(e)}")
|
||||
|
||||
# === 简化的体积计算策略 ===
|
||||
if source_volume > 0:
|
||||
# 如果能检测到液体体积,使用实际体积的大部分
|
||||
transfer_volume = min(source_volume * 0.9, 15.0) # 90%或最多15mL(离心管通常较小)
|
||||
print(f"CENTRIFUGE: 检测到液体体积,将转移 {transfer_volume} mL")
|
||||
else:
|
||||
# 如果检测不到液体体积,默认转移标准量
|
||||
transfer_volume = 10.0 # 标准离心管体积
|
||||
print(f"CENTRIFUGE: 未检测到液体体积,默认转移 {transfer_volume} mL")
|
||||
|
||||
# === 第一步:将待离心溶液转移到离心机容器 ===
|
||||
print(f"CENTRIFUGE: 将 {transfer_volume} mL 溶液从 {vessel} 转移到 {centrifuge_vessel}")
|
||||
try:
|
||||
# 使用成熟的 pump_protocol 算法进行液体转移
|
||||
transfer_to_centrifuge_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=vessel,
|
||||
to_vessel=centrifuge_vessel,
|
||||
volume=transfer_volume,
|
||||
flowrate=1.0, # 离心转移用慢速,避免气泡
|
||||
transfer_flowrate=1.0
|
||||
)
|
||||
action_sequence.extend(transfer_to_centrifuge_actions)
|
||||
except Exception as e:
|
||||
raise ValueError(f"无法将溶液转移到离心机: {str(e)}")
|
||||
|
||||
# 转移后等待
|
||||
wait_action = {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 5}
|
||||
}
|
||||
action_sequence.append(wait_action)
|
||||
|
||||
# === 第二步:执行离心操作 ===
|
||||
print(f"CENTRIFUGE: 执行离心操作")
|
||||
centrifuge_action = {
|
||||
"device_id": centrifuge_id,
|
||||
"action_name": "centrifuge",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"vessel": centrifuge_vessel,
|
||||
"speed": speed,
|
||||
"time": time,
|
||||
"temp": temp
|
||||
}
|
||||
})
|
||||
}
|
||||
action_sequence.append(centrifuge_action)
|
||||
|
||||
# 离心后等待系统稳定
|
||||
wait_action = {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 10} # 离心后等待稍长,让沉淀稳定
|
||||
}
|
||||
action_sequence.append(wait_action)
|
||||
|
||||
# === 第三步:将上清液转移回原容器 ===
|
||||
print(f"CENTRIFUGE: 将上清液从离心机转移回 {vessel}")
|
||||
try:
|
||||
# 估算上清液体积(约为转移体积的80% - 假设20%成为沉淀)
|
||||
supernatant_volume = transfer_volume * 0.8
|
||||
print(f"CENTRIFUGE: 预计上清液体积 {supernatant_volume} mL")
|
||||
|
||||
transfer_back_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=centrifuge_vessel,
|
||||
to_vessel=vessel,
|
||||
volume=supernatant_volume,
|
||||
flowrate=0.5, # 上清液转移更慢,避免扰动沉淀
|
||||
transfer_flowrate=0.5
|
||||
)
|
||||
action_sequence.extend(transfer_back_actions)
|
||||
except Exception as e:
|
||||
print(f"CENTRIFUGE: 将上清液转移回容器失败: {str(e)}")
|
||||
|
||||
# === 第四步:清洗离心机容器 ===
|
||||
print(f"CENTRIFUGE: 清洗离心机容器")
|
||||
try:
|
||||
# 查找清洗溶剂
|
||||
cleaning_solvent = None
|
||||
for solvent in ["flask_water", "flask_ethanol", "flask_acetone"]:
|
||||
if solvent in G.nodes():
|
||||
cleaning_solvent = solvent
|
||||
break
|
||||
|
||||
if cleaning_solvent:
|
||||
# 用少量溶剂清洗离心管
|
||||
cleaning_volume = 5.0 # 5mL清洗
|
||||
print(f"CENTRIFUGE: 用 {cleaning_volume} mL {cleaning_solvent} 清洗")
|
||||
|
||||
# 清洗溶剂加入
|
||||
cleaning_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=cleaning_solvent,
|
||||
to_vessel=centrifuge_vessel,
|
||||
volume=cleaning_volume,
|
||||
flowrate=2.0,
|
||||
transfer_flowrate=2.0
|
||||
)
|
||||
action_sequence.extend(cleaning_actions)
|
||||
|
||||
# 将清洗液转移到废液
|
||||
if "waste_workup" in G.nodes():
|
||||
waste_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=centrifuge_vessel,
|
||||
to_vessel="waste_workup",
|
||||
volume=cleaning_volume,
|
||||
flowrate=2.0,
|
||||
transfer_flowrate=2.0
|
||||
)
|
||||
action_sequence.extend(waste_actions)
|
||||
|
||||
except Exception as e:
|
||||
print(f"CENTRIFUGE: 清洗步骤失败: {str(e)}")
|
||||
|
||||
print(f"CENTRIFUGE: 生成了 {len(action_sequence)} 个动作")
|
||||
print(f"CENTRIFUGE: 离心协议生成完成")
|
||||
print(f"CENTRIFUGE: 总处理体积: {transfer_volume} mL")
|
||||
|
||||
return action_sequence
|
||||
|
||||
|
||||
def generate_multi_step_centrifuge_protocol(
|
||||
# 便捷函数:常用离心方案
|
||||
def generate_low_speed_centrifuge_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
steps: List[Dict[str, Any]]
|
||||
time: float = 300.0 # 5分钟
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成多步骤离心操作的协议序列
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为设备和容器
|
||||
vessel: 离心容器名称
|
||||
steps: 离心步骤列表,每个步骤包含 speed, time, temp 参数
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 多步骤离心操作的动作序列
|
||||
|
||||
Examples:
|
||||
steps = [
|
||||
{"speed": 1000, "time": 60, "temp": 4.0}, # 低速预离心
|
||||
{"speed": 12000, "time": 600, "temp": 4.0} # 高速离心
|
||||
]
|
||||
protocol = generate_multi_step_centrifuge_protocol(G, "reactor", steps)
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 查找离心机设备
|
||||
centrifuge_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_centrifuge']
|
||||
|
||||
if not centrifuge_nodes:
|
||||
raise ValueError("没有找到可用的离心机设备")
|
||||
|
||||
centrifuge_id = centrifuge_nodes[0]
|
||||
|
||||
# 验证容器是否存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||
|
||||
# 执行每个离心步骤
|
||||
for i, step in enumerate(steps):
|
||||
speed = step.get('speed', 5000)
|
||||
time = step.get('time', 300)
|
||||
temp = step.get('temp', 25.0)
|
||||
|
||||
action_sequence.append({
|
||||
"device_id": centrifuge_id,
|
||||
"action_name": "centrifuge",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"speed": speed,
|
||||
"time": time,
|
||||
"temp": temp
|
||||
}
|
||||
})
|
||||
|
||||
# 步骤间等待时间(除了最后一步)
|
||||
if i < len(steps) - 1:
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 3}
|
||||
})
|
||||
|
||||
return action_sequence
|
||||
"""低速离心:细胞分离或大颗粒沉淀"""
|
||||
return generate_centrifuge_protocol(G, vessel, 1000.0, time, 4.0)
|
||||
|
||||
|
||||
def generate_high_speed_centrifuge_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
time: float = 600.0 # 10分钟
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""高速离心:蛋白质沉淀或小颗粒分离"""
|
||||
return generate_centrifuge_protocol(G, vessel, 12000.0, time, 4.0)
|
||||
|
||||
|
||||
def generate_standard_centrifuge_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
time: float = 600.0 # 10分钟
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""标准离心:常规样品处理"""
|
||||
return generate_centrifuge_protocol(G, vessel, 5000.0, time, 25.0)
|
||||
|
||||
|
||||
def generate_cold_centrifuge_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
speed: float = 5000.0,
|
||||
time: float = 600.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""冷冻离心:热敏感样品处理"""
|
||||
return generate_centrifuge_protocol(G, vessel, speed, time, 4.0)
|
||||
|
||||
|
||||
def generate_ultra_centrifuge_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
time: float = 1800.0 # 30分钟
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""超高速离心:超细颗粒分离"""
|
||||
return generate_centrifuge_protocol(G, vessel, 15000.0, time, 4.0)
|
||||
@@ -1,5 +1,69 @@
|
||||
from typing import List, Dict, Any
|
||||
import networkx as nx
|
||||
from .pump_protocol import generate_pump_protocol
|
||||
|
||||
|
||||
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
"""
|
||||
查找溶剂容器,支持多种命名模式
|
||||
"""
|
||||
# 可能的溶剂容器命名模式
|
||||
possible_names = [
|
||||
f"flask_{solvent}", # flask_water, flask_ethanol
|
||||
f"bottle_{solvent}", # bottle_water, bottle_ethanol
|
||||
f"vessel_{solvent}", # vessel_water, vessel_ethanol
|
||||
f"{solvent}_flask", # water_flask, ethanol_flask
|
||||
f"{solvent}_bottle", # water_bottle, ethanol_bottle
|
||||
f"{solvent}", # 直接用溶剂名
|
||||
f"solvent_{solvent}", # solvent_water, solvent_ethanol
|
||||
]
|
||||
|
||||
for vessel_name in possible_names:
|
||||
if vessel_name in G.nodes():
|
||||
return vessel_name
|
||||
|
||||
raise ValueError(f"未找到溶剂 '{solvent}' 的容器。尝试了以下名称: {possible_names}")
|
||||
|
||||
|
||||
def find_waste_vessel(G: nx.DiGraph) -> str:
|
||||
"""
|
||||
查找废液容器
|
||||
"""
|
||||
possible_waste_names = [
|
||||
"waste_workup",
|
||||
"flask_waste",
|
||||
"bottle_waste",
|
||||
"waste",
|
||||
"waste_vessel",
|
||||
"waste_container"
|
||||
]
|
||||
|
||||
for waste_name in possible_waste_names:
|
||||
if waste_name in G.nodes():
|
||||
return waste_name
|
||||
|
||||
raise ValueError(f"未找到废液容器。尝试了以下名称: {possible_waste_names}")
|
||||
|
||||
|
||||
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
|
||||
"""
|
||||
查找与指定容器相连的加热冷却设备
|
||||
"""
|
||||
# 查找所有加热冷却设备节点
|
||||
heatchill_nodes = [node for node in G.nodes()
|
||||
if (G.nodes[node].get('class') or '') == 'virtual_heatchill']
|
||||
|
||||
# 检查哪个加热设备与目标容器相连(机械连接)
|
||||
for heatchill in heatchill_nodes:
|
||||
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
|
||||
return heatchill
|
||||
|
||||
# 如果没有直接连接,返回第一个可用的加热设备
|
||||
if heatchill_nodes:
|
||||
return heatchill_nodes[0]
|
||||
|
||||
return None # 没有加热设备也可以工作,只是不能加热
|
||||
|
||||
|
||||
def generate_clean_vessel_protocol(
|
||||
G: nx.DiGraph,
|
||||
@@ -10,13 +74,22 @@ def generate_clean_vessel_protocol(
|
||||
repeats: int = 1
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成容器清洗操作的协议序列,使用transfer操作实现清洗
|
||||
生成容器清洗操作的协议序列,复用 pump_protocol 的成熟算法
|
||||
|
||||
清洗流程:
|
||||
1. 查找溶剂容器和废液容器
|
||||
2. 如果需要加热,启动加热设备
|
||||
3. 重复以下操作 repeats 次:
|
||||
a. 使用 pump_protocol 将溶剂从溶剂容器转移到目标容器
|
||||
b. (可选) 等待清洗作用时间
|
||||
c. 使用 pump_protocol 将清洗液从目标容器转移到废液容器
|
||||
4. 如果加热了,停止加热
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为设备和容器
|
||||
G: 有向图,节点为设备和容器,边为流体管道
|
||||
vessel: 要清洗的容器名称
|
||||
solvent: 用于清洗容器的溶剂名称
|
||||
volume: 清洗溶剂的体积
|
||||
solvent: 用于清洗的溶剂名称
|
||||
volume: 每次清洗使用的溶剂体积
|
||||
temp: 清洗时的温度
|
||||
repeats: 清洗操作的重复次数,默认为 1
|
||||
|
||||
@@ -24,103 +97,188 @@ def generate_clean_vessel_protocol(
|
||||
List[Dict[str, Any]]: 容器清洗操作的动作序列
|
||||
|
||||
Raises:
|
||||
ValueError: 当找不到必要的设备时抛出异常
|
||||
ValueError: 当找不到必要的容器或设备时抛出异常
|
||||
|
||||
Examples:
|
||||
clean_vessel_protocol = generate_clean_vessel_protocol(G, "reactor", "water", 50.0, 25.0, 2)
|
||||
clean_protocol = generate_clean_vessel_protocol(G, "main_reactor", "water", 100.0, 60.0, 2)
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 查找虚拟转移泵设备进行清洗操作
|
||||
pump_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
||||
print(f"CLEAN_VESSEL: 开始生成容器清洗协议")
|
||||
print(f" - 目标容器: {vessel}")
|
||||
print(f" - 清洗溶剂: {solvent}")
|
||||
print(f" - 清洗体积: {volume} mL")
|
||||
print(f" - 清洗温度: {temp}°C")
|
||||
print(f" - 重复次数: {repeats}")
|
||||
|
||||
if not pump_nodes:
|
||||
raise ValueError("没有找到可用的转移泵设备进行容器清洗")
|
||||
|
||||
pump_id = pump_nodes[0]
|
||||
|
||||
# 验证容器是否存在
|
||||
# 验证目标容器存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
# 查找溶剂容器
|
||||
solvent_vessel = f"flask_{solvent}"
|
||||
if solvent_vessel not in G.nodes():
|
||||
raise ValueError(f"溶剂容器 {solvent_vessel} 不存在于图中")
|
||||
try:
|
||||
solvent_vessel = find_solvent_vessel(G, solvent)
|
||||
print(f"CLEAN_VESSEL: 找到溶剂容器: {solvent_vessel}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到溶剂容器: {str(e)}")
|
||||
|
||||
# 查找废液容器
|
||||
waste_vessel = "flask_waste"
|
||||
if waste_vessel not in G.nodes():
|
||||
raise ValueError(f"废液容器 {waste_vessel} 不存在于图中")
|
||||
try:
|
||||
waste_vessel = find_waste_vessel(G)
|
||||
print(f"CLEAN_VESSEL: 找到废液容器: {waste_vessel}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到废液容器: {str(e)}")
|
||||
|
||||
# 查找加热设备(如果需要加热)
|
||||
heatchill_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||
# 查找加热设备(可选)
|
||||
heatchill_id = find_connected_heatchill(G, vessel)
|
||||
if heatchill_id:
|
||||
print(f"CLEAN_VESSEL: 找到加热设备: {heatchill_id}")
|
||||
else:
|
||||
print(f"CLEAN_VESSEL: 未找到加热设备,将在室温下清洗")
|
||||
|
||||
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
|
||||
# 第一步:如果需要加热且有加热设备,启动加热
|
||||
if temp > 25.0 and heatchill_id:
|
||||
print(f"CLEAN_VESSEL: 启动加热至 {temp}°C")
|
||||
heatchill_start_action = {
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_start",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"temp": temp,
|
||||
"purpose": f"cleaning with {solvent}"
|
||||
}
|
||||
}
|
||||
action_sequence.append(heatchill_start_action)
|
||||
|
||||
# 等待温度稳定
|
||||
wait_action = {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 30} # 等待30秒让温度稳定
|
||||
}
|
||||
action_sequence.append(wait_action)
|
||||
|
||||
# 执行清洗操作序列
|
||||
# 第二步:重复清洗操作
|
||||
for repeat in range(repeats):
|
||||
# 1. 如果需要加热,先设置温度
|
||||
if temp > 25.0 and heatchill_id:
|
||||
action_sequence.append({
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_start",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"temp": temp,
|
||||
"purpose": "cleaning"
|
||||
}
|
||||
})
|
||||
print(f"CLEAN_VESSEL: 执行第 {repeat + 1} 次清洗")
|
||||
|
||||
# 2. 使用transfer操作:从溶剂容器转移清洗溶剂到目标容器
|
||||
action_sequence.append({
|
||||
"device_id": pump_id,
|
||||
"action_name": "transfer",
|
||||
"action_kwargs": {
|
||||
"from_vessel": solvent_vessel,
|
||||
"to_vessel": vessel,
|
||||
"volume": volume,
|
||||
"amount": f"cleaning with {solvent} - cycle {repeat + 1}",
|
||||
"time": 0.0,
|
||||
"viscous": False,
|
||||
"rinsing_solvent": "",
|
||||
"rinsing_volume": 0.0,
|
||||
"rinsing_repeats": 0,
|
||||
"solid": False
|
||||
# 2a. 使用 pump_protocol 将溶剂转移到目标容器
|
||||
print(f"CLEAN_VESSEL: 将 {volume} mL {solvent} 转移到 {vessel}")
|
||||
try:
|
||||
# 调用成熟的 pump_protocol 算法
|
||||
add_solvent_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=solvent_vessel,
|
||||
to_vessel=vessel,
|
||||
volume=volume,
|
||||
flowrate=2.5, # 适中的流速,避免飞溅
|
||||
transfer_flowrate=2.5
|
||||
)
|
||||
action_sequence.extend(add_solvent_actions)
|
||||
except Exception as e:
|
||||
raise ValueError(f"无法将溶剂转移到容器: {str(e)}")
|
||||
|
||||
# 2b. 等待清洗作用时间(让溶剂充分清洗容器)
|
||||
cleaning_wait_time = 60 if temp > 50.0 else 30 # 高温下等待更久
|
||||
print(f"CLEAN_VESSEL: 等待清洗作用 {cleaning_wait_time} 秒")
|
||||
wait_action = {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": cleaning_wait_time}
|
||||
}
|
||||
action_sequence.append(wait_action)
|
||||
|
||||
# 2c. 使用 pump_protocol 将清洗液转移到废液容器
|
||||
print(f"CLEAN_VESSEL: 将清洗液从 {vessel} 转移到废液容器")
|
||||
try:
|
||||
# 调用成熟的 pump_protocol 算法
|
||||
remove_waste_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=vessel,
|
||||
to_vessel=waste_vessel,
|
||||
volume=volume,
|
||||
flowrate=2.5, # 适中的流速
|
||||
transfer_flowrate=2.5
|
||||
)
|
||||
action_sequence.extend(remove_waste_actions)
|
||||
except Exception as e:
|
||||
raise ValueError(f"无法将清洗液转移到废液容器: {str(e)}")
|
||||
|
||||
# 2d. 清洗循环间的短暂等待
|
||||
if repeat < repeats - 1: # 不是最后一次清洗
|
||||
print(f"CLEAN_VESSEL: 清洗循环间等待")
|
||||
wait_action = {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 10}
|
||||
}
|
||||
})
|
||||
|
||||
# 3. 等待清洗作用时间(可选,可以添加wait操作)
|
||||
# 这里省略wait操作,直接进行下一步
|
||||
|
||||
# 4. 将清洗后的溶剂转移到废液容器
|
||||
action_sequence.append({
|
||||
"device_id": pump_id,
|
||||
"action_name": "transfer",
|
||||
action_sequence.append(wait_action)
|
||||
|
||||
# 第三步:如果加热了,停止加热
|
||||
if temp > 25.0 and heatchill_id:
|
||||
print(f"CLEAN_VESSEL: 停止加热")
|
||||
heatchill_stop_action = {
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_stop",
|
||||
"action_kwargs": {
|
||||
"from_vessel": vessel,
|
||||
"to_vessel": waste_vessel,
|
||||
"volume": volume,
|
||||
"amount": f"waste from cleaning {vessel} - cycle {repeat + 1}",
|
||||
"time": 0.0,
|
||||
"viscous": False,
|
||||
"rinsing_solvent": "",
|
||||
"rinsing_volume": 0.0,
|
||||
"rinsing_repeats": 0,
|
||||
"solid": False
|
||||
"vessel": vessel
|
||||
}
|
||||
})
|
||||
|
||||
# 5. 如果加热了,停止加热
|
||||
if temp > 25.0 and heatchill_id:
|
||||
action_sequence.append({
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_stop",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel
|
||||
}
|
||||
})
|
||||
}
|
||||
action_sequence.append(heatchill_stop_action)
|
||||
|
||||
print(f"CLEAN_VESSEL: 生成了 {len(action_sequence)} 个动作")
|
||||
print(f"CLEAN_VESSEL: 清洗协议生成完成")
|
||||
|
||||
return action_sequence
|
||||
|
||||
|
||||
# 便捷函数:常用清洗方案
|
||||
def generate_quick_clean_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
solvent: str = "water",
|
||||
volume: float = 100.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""快速清洗:室温,单次清洗"""
|
||||
return generate_clean_vessel_protocol(G, vessel, solvent, volume, 25.0, 1)
|
||||
|
||||
|
||||
def generate_thorough_clean_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
solvent: str = "water",
|
||||
volume: float = 150.0,
|
||||
temp: float = 60.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""深度清洗:加热,多次清洗"""
|
||||
return generate_clean_vessel_protocol(G, vessel, solvent, volume, temp, 3)
|
||||
|
||||
|
||||
def generate_organic_clean_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
volume: float = 100.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""有机清洗:先用有机溶剂,再用水清洗"""
|
||||
action_sequence = []
|
||||
|
||||
# 第一步:有机溶剂清洗
|
||||
try:
|
||||
organic_actions = generate_clean_vessel_protocol(
|
||||
G, vessel, "acetone", volume, 25.0, 2
|
||||
)
|
||||
action_sequence.extend(organic_actions)
|
||||
except ValueError:
|
||||
# 如果没有丙酮,尝试乙醇
|
||||
try:
|
||||
organic_actions = generate_clean_vessel_protocol(
|
||||
G, vessel, "ethanol", volume, 25.0, 2
|
||||
)
|
||||
action_sequence.extend(organic_actions)
|
||||
except ValueError:
|
||||
print("警告:未找到有机溶剂,跳过有机清洗步骤")
|
||||
|
||||
# 第二步:水清洗
|
||||
water_actions = generate_clean_vessel_protocol(
|
||||
G, vessel, "water", volume, 25.0, 2
|
||||
)
|
||||
action_sequence.extend(water_actions)
|
||||
|
||||
return action_sequence
|
||||
@@ -1,5 +1,47 @@
|
||||
from typing import List, Dict, Any
|
||||
import networkx as nx
|
||||
from .pump_protocol import generate_pump_protocol
|
||||
|
||||
|
||||
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
"""
|
||||
查找溶剂容器
|
||||
"""
|
||||
# 按照pump_protocol的命名规则查找溶剂瓶
|
||||
solvent_vessel_id = f"flask_{solvent}"
|
||||
|
||||
if solvent_vessel_id in G.nodes():
|
||||
return solvent_vessel_id
|
||||
|
||||
# 如果直接匹配失败,尝试模糊匹配
|
||||
for node in G.nodes():
|
||||
if node.startswith('flask_') and solvent.lower() in node.lower():
|
||||
return node
|
||||
|
||||
# 如果还是找不到,列出所有可用的溶剂瓶
|
||||
available_flasks = [node for node in G.nodes()
|
||||
if node.startswith('flask_')
|
||||
and G.nodes[node].get('type') == 'container']
|
||||
|
||||
raise ValueError(f"找不到溶剂 '{solvent}' 对应的溶剂瓶。可用溶剂瓶: {available_flasks}")
|
||||
|
||||
|
||||
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
|
||||
"""
|
||||
查找与指定容器相连的加热搅拌器
|
||||
"""
|
||||
# 查找所有加热搅拌器节点
|
||||
heatchill_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||
|
||||
# 检查哪个加热器与目标容器相连
|
||||
for heatchill in heatchill_nodes:
|
||||
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
|
||||
return heatchill
|
||||
|
||||
# 如果没有直接连接,返回第一个可用的加热器
|
||||
return heatchill_nodes[0] if heatchill_nodes else None
|
||||
|
||||
|
||||
def generate_dissolve_protocol(
|
||||
G: nx.DiGraph,
|
||||
@@ -9,154 +51,309 @@ def generate_dissolve_protocol(
|
||||
amount: str = "",
|
||||
temp: float = 25.0,
|
||||
time: float = 0.0,
|
||||
stir_speed: float = 0.0
|
||||
stir_speed: float = 300.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成溶解操作的协议序列
|
||||
生成溶解操作的协议序列,复用 pump_protocol 的成熟算法
|
||||
|
||||
溶解流程:
|
||||
1. 溶剂转移:将溶剂从溶剂瓶转移到目标容器
|
||||
2. 启动加热搅拌:设置温度和搅拌
|
||||
3. 等待溶解:监控溶解过程
|
||||
4. 停止加热搅拌:完成溶解
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为设备和容器
|
||||
vessel: 装有要溶解物质的容器名称
|
||||
solvent: 用于溶解物质的溶剂名称
|
||||
volume: 溶剂的体积,可选参数
|
||||
amount: 要溶解物质的量,可选参数
|
||||
temp: 溶解时的温度,可选参数
|
||||
time: 溶解的时间,可选参数
|
||||
stir_speed: 搅拌速度,可选参数
|
||||
G: 有向图,节点为设备和容器,边为流体管道
|
||||
vessel: 目标容器(要进行溶解的容器)
|
||||
solvent: 溶剂名称(用于查找对应的溶剂瓶)
|
||||
volume: 溶剂体积 (mL)
|
||||
amount: 要溶解的物质描述
|
||||
temp: 溶解温度 (°C),默认25°C(室温)
|
||||
time: 溶解时间 (秒),默认0(立即完成)
|
||||
stir_speed: 搅拌速度 (RPM),默认300 RPM
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 溶解操作的动作序列
|
||||
|
||||
Raises:
|
||||
ValueError: 当找不到必要的设备时抛出异常
|
||||
ValueError: 当找不到必要的设备或容器时
|
||||
|
||||
Examples:
|
||||
dissolve_protocol = generate_dissolve_protocol(G, "reactor", "water", 100.0, "NaCl 5g", 60.0, 300.0, 500.0)
|
||||
dissolve_actions = generate_dissolve_protocol(G, "reaction_mixture", "DMF", 10.0, "NaCl 2g", 60.0, 600.0, 400.0)
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 验证容器是否存在
|
||||
print(f"DISSOLVE: 开始生成溶解协议")
|
||||
print(f" - 目标容器: {vessel}")
|
||||
print(f" - 溶剂: {solvent}")
|
||||
print(f" - 溶剂体积: {volume} mL")
|
||||
print(f" - 要溶解的物质: {amount}")
|
||||
print(f" - 溶解温度: {temp}°C")
|
||||
print(f" - 溶解时间: {time}s ({time/60:.1f}分钟)" if time > 0 else " - 溶解时间: 立即完成")
|
||||
print(f" - 搅拌速度: {stir_speed} RPM")
|
||||
|
||||
# 验证目标容器存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
# 查找溶剂容器
|
||||
solvent_vessel = f"flask_{solvent}"
|
||||
if solvent_vessel not in G.nodes():
|
||||
# 如果没有找到特定溶剂容器,查找可用的源容器
|
||||
available_vessels = [node for node in G.nodes()
|
||||
if node.startswith('flask_') and
|
||||
G.nodes[node].get('type') == 'container']
|
||||
if available_vessels:
|
||||
solvent_vessel = available_vessels[0]
|
||||
else:
|
||||
raise ValueError(f"没有找到溶剂容器 {solvent}")
|
||||
# 查找溶剂瓶
|
||||
try:
|
||||
solvent_vessel = find_solvent_vessel(G, solvent)
|
||||
print(f"DISSOLVE: 找到溶剂瓶: {solvent_vessel}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到溶剂 '{solvent}': {str(e)}")
|
||||
|
||||
# 查找转移泵设备
|
||||
pump_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_transfer_pump']
|
||||
# 验证是否存在从溶剂瓶到目标容器的路径
|
||||
try:
|
||||
path = nx.shortest_path(G, source=solvent_vessel, target=vessel)
|
||||
print(f"DISSOLVE: 找到路径 {solvent_vessel} -> {vessel}: {path}")
|
||||
except nx.NetworkXNoPath:
|
||||
raise ValueError(f"从溶剂瓶 '{solvent_vessel}' 到目标容器 '{vessel}' 没有可用路径")
|
||||
|
||||
if not pump_nodes:
|
||||
raise ValueError("没有找到可用的转移泵设备")
|
||||
# 查找加热搅拌器
|
||||
heatchill_id = None
|
||||
if temp > 25.0 or stir_speed > 0 or time > 0:
|
||||
try:
|
||||
heatchill_id = find_connected_heatchill(G, vessel)
|
||||
if heatchill_id:
|
||||
print(f"DISSOLVE: 找到加热搅拌器: {heatchill_id}")
|
||||
else:
|
||||
print(f"DISSOLVE: 警告 - 需要加热/搅拌但未找到与容器 {vessel} 相连的加热搅拌器")
|
||||
except Exception as e:
|
||||
print(f"DISSOLVE: 加热搅拌器配置出错: {str(e)}")
|
||||
|
||||
pump_id = pump_nodes[0]
|
||||
|
||||
# 查找加热设备(如果需要加热)
|
||||
heatchill_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||
|
||||
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
|
||||
|
||||
# 查找搅拌设备(如果需要搅拌)
|
||||
stirrer_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_stirrer']
|
||||
|
||||
stirrer_id = stirrer_nodes[0] if stirrer_nodes else None
|
||||
|
||||
# 步骤1:如果需要加热,先设置温度
|
||||
if temp > 25.0 and heatchill_id:
|
||||
action_sequence.append({
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_start",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"temp": temp,
|
||||
"purpose": "dissolution"
|
||||
}
|
||||
})
|
||||
|
||||
# 步骤2:添加溶剂到容器中
|
||||
if volume > 0:
|
||||
action_sequence.append({
|
||||
"device_id": pump_id,
|
||||
"action_name": "transfer",
|
||||
"action_kwargs": {
|
||||
"from_vessel": solvent_vessel,
|
||||
"to_vessel": vessel,
|
||||
"volume": volume,
|
||||
"amount": f"solvent {solvent} for dissolving {amount}",
|
||||
"time": 0.0,
|
||||
"viscous": False,
|
||||
"rinsing_solvent": "",
|
||||
"rinsing_volume": 0.0,
|
||||
"rinsing_repeats": 0,
|
||||
"solid": False
|
||||
}
|
||||
})
|
||||
|
||||
# 步骤3:如果需要搅拌,开始搅拌
|
||||
if stir_speed > 0 and stirrer_id:
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"stir_speed": stir_speed,
|
||||
"purpose": f"dissolving {amount} in {solvent}"
|
||||
}
|
||||
})
|
||||
|
||||
# 步骤4:如果指定了溶解时间,等待溶解完成
|
||||
if time > 0:
|
||||
# 这里可以添加等待操作,或者使用搅拌操作来模拟溶解时间
|
||||
if stirrer_id and stir_speed > 0:
|
||||
# 停止之前的搅拌,使用定时搅拌
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "stop_stir",
|
||||
# === 第一步:启动加热搅拌(在添加溶剂前) ===
|
||||
if heatchill_id and (temp > 25.0 or time > 0):
|
||||
print(f"DISSOLVE: 启动加热搅拌器,温度: {temp}°C")
|
||||
|
||||
if time > 0:
|
||||
# 如果指定了时间,使用定时加热搅拌
|
||||
heatchill_action = {
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel
|
||||
}
|
||||
})
|
||||
|
||||
# 开始定时搅拌
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "stir",
|
||||
"action_kwargs": {
|
||||
"stir_time": time,
|
||||
"vessel": vessel,
|
||||
"temp": temp,
|
||||
"time": time,
|
||||
"stir": True,
|
||||
"stir_speed": stir_speed,
|
||||
"settling_time": 10.0 # 搅拌后静置10秒
|
||||
"purpose": f"溶解 {amount} 在 {solvent} 中"
|
||||
}
|
||||
}
|
||||
else:
|
||||
# 如果没有指定时间,使用持续加热搅拌
|
||||
heatchill_action = {
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_start",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"temp": temp,
|
||||
"purpose": f"溶解 {amount} 在 {solvent} 中"
|
||||
}
|
||||
}
|
||||
|
||||
action_sequence.append(heatchill_action)
|
||||
|
||||
# 等待温度稳定
|
||||
if temp > 25.0:
|
||||
wait_time = min(60, abs(temp - 25.0) * 1.5) # 根据温差估算预热时间
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": wait_time}
|
||||
})
|
||||
|
||||
# 步骤5:如果加热了,停止加热
|
||||
if temp > 25.0 and heatchill_id:
|
||||
# === 第二步:添加溶剂到目标容器 ===
|
||||
if volume > 0:
|
||||
print(f"DISSOLVE: 将 {volume} mL {solvent} 从 {solvent_vessel} 转移到 {vessel}")
|
||||
|
||||
# 计算流速 - 溶解时通常用较慢的速度,避免飞溅
|
||||
transfer_flowrate = 1.0 # 较慢的转移速度
|
||||
flowrate = 0.5 # 较慢的注入速度
|
||||
|
||||
try:
|
||||
# 使用成熟的 pump_protocol 算法进行液体转移
|
||||
pump_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=solvent_vessel,
|
||||
to_vessel=vessel,
|
||||
volume=volume,
|
||||
flowrate=flowrate, # 注入速度 - 较慢避免飞溅
|
||||
transfer_flowrate=transfer_flowrate # 转移速度
|
||||
)
|
||||
|
||||
action_sequence.extend(pump_actions)
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(f"生成泵协议时出错: {str(e)}")
|
||||
|
||||
# 溶剂添加后等待
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 5}
|
||||
})
|
||||
|
||||
# === 第三步:如果没有使用定时加热搅拌,但需要等待溶解 ===
|
||||
if time > 0 and heatchill_id and temp <= 25.0:
|
||||
# 只需要搅拌等待,不需要加热
|
||||
print(f"DISSOLVE: 室温搅拌 {time}s 等待溶解")
|
||||
|
||||
stir_action = {
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"temp": 25.0, # 室温
|
||||
"time": time,
|
||||
"stir": True,
|
||||
"stir_speed": stir_speed,
|
||||
"purpose": f"室温搅拌溶解 {amount}"
|
||||
}
|
||||
}
|
||||
action_sequence.append(stir_action)
|
||||
|
||||
# === 第四步:如果使用了持续加热,需要手动停止 ===
|
||||
if heatchill_id and time == 0 and temp > 25.0:
|
||||
print(f"DISSOLVE: 停止加热搅拌器")
|
||||
|
||||
stop_action = {
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_stop",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel
|
||||
}
|
||||
})
|
||||
}
|
||||
action_sequence.append(stop_action)
|
||||
|
||||
# 步骤6:如果还在搅拌,停止搅拌(除非已经用定时搅拌)
|
||||
if stir_speed > 0 and stirrer_id and time == 0:
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "stop_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel
|
||||
print(f"DISSOLVE: 生成了 {len(action_sequence)} 个动作")
|
||||
print(f"DISSOLVE: 溶解协议生成完成")
|
||||
|
||||
return action_sequence
|
||||
|
||||
|
||||
# 便捷函数:常用溶解方案
|
||||
def generate_room_temp_dissolve_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
solvent: str,
|
||||
volume: float,
|
||||
amount: str = "",
|
||||
stir_time: float = 300.0 # 5分钟
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""室温溶解:快速搅拌,短时间"""
|
||||
return generate_dissolve_protocol(G, vessel, solvent, volume, amount, 25.0, stir_time, 400.0)
|
||||
|
||||
|
||||
def generate_heated_dissolve_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
solvent: str,
|
||||
volume: float,
|
||||
amount: str = "",
|
||||
temp: float = 60.0,
|
||||
dissolve_time: float = 900.0 # 15分钟
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""加热溶解:中等温度,较长时间"""
|
||||
return generate_dissolve_protocol(G, vessel, solvent, volume, amount, temp, dissolve_time, 300.0)
|
||||
|
||||
|
||||
def generate_gentle_dissolve_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
solvent: str,
|
||||
volume: float,
|
||||
amount: str = "",
|
||||
temp: float = 40.0,
|
||||
dissolve_time: float = 1800.0 # 30分钟
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""温和溶解:低温,长时间,慢搅拌"""
|
||||
return generate_dissolve_protocol(G, vessel, solvent, volume, amount, temp, dissolve_time, 200.0)
|
||||
|
||||
|
||||
def generate_hot_dissolve_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
solvent: str,
|
||||
volume: float,
|
||||
amount: str = "",
|
||||
temp: float = 80.0,
|
||||
dissolve_time: float = 600.0 # 10分钟
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""高温溶解:高温,中等时间,快搅拌"""
|
||||
return generate_dissolve_protocol(G, vessel, solvent, volume, amount, temp, dissolve_time, 500.0)
|
||||
|
||||
|
||||
def generate_sequential_dissolve_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
dissolve_steps: List[Dict[str, Any]]
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成连续溶解多种物质的协议
|
||||
|
||||
Args:
|
||||
G: 网络图
|
||||
vessel: 目标容器
|
||||
dissolve_steps: 溶解步骤列表,每个元素包含溶解参数
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 完整的动作序列
|
||||
|
||||
Example:
|
||||
dissolve_steps = [
|
||||
{
|
||||
"solvent": "water",
|
||||
"volume": 5.0,
|
||||
"amount": "NaCl 1g",
|
||||
"temp": 25.0,
|
||||
"time": 300.0,
|
||||
"stir_speed": 300.0
|
||||
},
|
||||
{
|
||||
"solvent": "ethanol",
|
||||
"volume": 2.0,
|
||||
"amount": "organic compound 0.5g",
|
||||
"temp": 40.0,
|
||||
"time": 600.0,
|
||||
"stir_speed": 400.0
|
||||
}
|
||||
})
|
||||
]
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
return action_sequence
|
||||
for i, step in enumerate(dissolve_steps):
|
||||
print(f"DISSOLVE: 处理第 {i+1}/{len(dissolve_steps)} 个溶解步骤")
|
||||
|
||||
# 生成单个溶解步骤的协议
|
||||
dissolve_actions = generate_dissolve_protocol(
|
||||
G=G,
|
||||
vessel=vessel,
|
||||
solvent=step.get('solvent'),
|
||||
volume=step.get('volume', 0.0),
|
||||
amount=step.get('amount', ''),
|
||||
temp=step.get('temp', 25.0),
|
||||
time=step.get('time', 0.0),
|
||||
stir_speed=step.get('stir_speed', 300.0)
|
||||
)
|
||||
|
||||
action_sequence.extend(dissolve_actions)
|
||||
|
||||
# 在步骤之间加入等待时间
|
||||
if i < len(dissolve_steps) - 1: # 不是最后一个步骤
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 10}
|
||||
})
|
||||
|
||||
print(f"DISSOLVE: 连续溶解协议生成完成,共 {len(action_sequence)} 个动作")
|
||||
return action_sequence
|
||||
|
||||
|
||||
# 测试函数
|
||||
def test_dissolve_protocol():
|
||||
"""测试溶解协议的示例"""
|
||||
print("=== DISSOLVE PROTOCOL 测试 ===")
|
||||
print("测试完成")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_dissolve_protocol()
|
||||
@@ -1,143 +1,437 @@
|
||||
import numpy as np
|
||||
import networkx as nx
|
||||
from typing import List, Dict, Any, Optional
|
||||
from .pump_protocol import generate_pump_protocol_with_rinsing, generate_pump_protocol
|
||||
|
||||
|
||||
def find_gas_source(G: nx.DiGraph, gas: str) -> str:
|
||||
"""根据气体名称查找对应的气源"""
|
||||
# 按照命名规则查找气源
|
||||
gas_source_patterns = [
|
||||
f"gas_source_{gas}",
|
||||
f"gas_{gas}",
|
||||
f"flask_{gas}",
|
||||
f"{gas}_source"
|
||||
]
|
||||
|
||||
for pattern in gas_source_patterns:
|
||||
if pattern in G.nodes():
|
||||
return pattern
|
||||
|
||||
# 模糊匹配
|
||||
for node in G.nodes():
|
||||
node_class = G.nodes[node].get('class', '') or ''
|
||||
if 'gas_source' in node_class and gas.lower() in node.lower():
|
||||
return node
|
||||
if node.startswith('flask_') and gas.lower() in node.lower():
|
||||
return node
|
||||
|
||||
# 查找所有可用的气源
|
||||
available_gas_sources = [
|
||||
node for node in G.nodes()
|
||||
if ((G.nodes[node].get('class') or '').startswith('virtual_gas_source')
|
||||
or ('gas' in node and 'source' in node)
|
||||
or (node.startswith('flask_') and any(g in node.lower() for g in ['air', 'nitrogen', 'argon', 'vacuum'])))
|
||||
]
|
||||
|
||||
raise ValueError(f"找不到气体 '{gas}' 对应的气源。可用气源: {available_gas_sources}")
|
||||
|
||||
|
||||
def find_vacuum_pump(G: nx.DiGraph) -> str:
|
||||
"""查找真空泵设备"""
|
||||
vacuum_pumps = [
|
||||
node for node in G.nodes()
|
||||
if ((G.nodes[node].get('class') or '').startswith('virtual_vacuum_pump')
|
||||
or 'vacuum_pump' in node
|
||||
or 'vacuum' in (G.nodes[node].get('class') or ''))
|
||||
]
|
||||
|
||||
if not vacuum_pumps:
|
||||
raise ValueError("系统中未找到真空泵设备")
|
||||
|
||||
return vacuum_pumps[0]
|
||||
|
||||
|
||||
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
|
||||
"""查找与指定容器相连的搅拌器"""
|
||||
stirrer_nodes = [node for node in G.nodes()
|
||||
if (G.nodes[node].get('class') or '') == 'virtual_stirrer']
|
||||
|
||||
# 检查哪个搅拌器与目标容器相连
|
||||
for stirrer in stirrer_nodes:
|
||||
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
|
||||
return stirrer
|
||||
|
||||
return stirrer_nodes[0] if stirrer_nodes else None
|
||||
|
||||
|
||||
def find_associated_solenoid_valve(G: nx.DiGraph, device_id: str) -> Optional[str]:
|
||||
"""查找与指定设备相关联的电磁阀"""
|
||||
solenoid_valves = [
|
||||
node for node in G.nodes()
|
||||
if ('solenoid' in (G.nodes[node].get('class') or '').lower()
|
||||
or 'solenoid_valve' in node)
|
||||
]
|
||||
|
||||
# 通过网络连接查找直接相连的电磁阀
|
||||
for solenoid in solenoid_valves:
|
||||
if G.has_edge(device_id, solenoid) or G.has_edge(solenoid, device_id):
|
||||
return solenoid
|
||||
|
||||
# 通过命名规则查找关联的电磁阀
|
||||
device_type = ""
|
||||
if 'vacuum' in device_id.lower():
|
||||
device_type = "vacuum"
|
||||
elif 'gas' in device_id.lower():
|
||||
device_type = "gas"
|
||||
|
||||
if device_type:
|
||||
for solenoid in solenoid_valves:
|
||||
if device_type in solenoid.lower():
|
||||
return solenoid
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def generate_evacuateandrefill_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
gas: str,
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
gas: str,
|
||||
repeats: int = 1
|
||||
) -> list[dict]:
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成泵操作的动作序列。
|
||||
生成抽真空和充气操作的动作序列
|
||||
|
||||
:param G: 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
|
||||
:param from_vessel: 容器A
|
||||
:param to_vessel: 容器B
|
||||
:param volume: 转移的体积
|
||||
:param flowrate: 最终注入容器B时的流速
|
||||
:param transfer_flowrate: 泵骨架中转移流速(若不指定,默认与注入流速相同)
|
||||
:return: 泵操作的动作序列
|
||||
**修复版本**: 正确调用 pump_protocol 并处理异常
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 生成电磁阀、真空泵、气源操作的动作序列
|
||||
vacuum_action_sequence = []
|
||||
nodes = G.nodes(data=True)
|
||||
# 参数设置 - 关键修复:减小体积避免超出泵容量
|
||||
VACUUM_VOLUME = 20.0 # 减小抽真空体积
|
||||
REFILL_VOLUME = 20.0 # 减小充气体积
|
||||
PUMP_FLOW_RATE = 2.5 # 降低流速
|
||||
STIR_SPEED = 300.0
|
||||
|
||||
# 找到和 vessel 相连的电磁阀和真空泵、气源
|
||||
vacuum_backbone = {"vessel": vessel}
|
||||
print(f"EVACUATE_REFILL: 开始生成协议,目标容器: {vessel}, 气体: {gas}, 重复次数: {repeats}")
|
||||
|
||||
for neighbor in G.neighbors(vessel):
|
||||
if nodes[neighbor]["class"].startswith("solenoid_valve"):
|
||||
for neighbor2 in G.neighbors(neighbor):
|
||||
if neighbor2 == vessel:
|
||||
continue
|
||||
if nodes[neighbor2]["class"].startswith("vacuum_pump"):
|
||||
vacuum_backbone.update({"vacuum_valve": neighbor, "pump": neighbor2})
|
||||
break
|
||||
elif nodes[neighbor2]["class"].startswith("gas_source"):
|
||||
vacuum_backbone.update({"gas_valve": neighbor, "gas": neighbor2})
|
||||
break
|
||||
# 判断是否设备齐全
|
||||
if len(vacuum_backbone) < 5:
|
||||
print(f"\n\n\n{vacuum_backbone}\n\n\n")
|
||||
raise ValueError("Not all devices are connected to the vessel.")
|
||||
# 1. 验证设备存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
# 生成操作的动作序列
|
||||
for i in range(repeats):
|
||||
# 打开真空泵阀门、关闭气源阀门
|
||||
vacuum_action_sequence.append([
|
||||
{
|
||||
"device_id": vacuum_backbone["vacuum_valve"],
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {
|
||||
"command": "OPEN"
|
||||
}
|
||||
},
|
||||
{
|
||||
"device_id": vacuum_backbone["gas_valve"],
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {
|
||||
"command": "CLOSED"
|
||||
}
|
||||
}
|
||||
])
|
||||
# 2. 查找设备
|
||||
try:
|
||||
vacuum_pump = find_vacuum_pump(G)
|
||||
vacuum_solenoid = find_associated_solenoid_valve(G, vacuum_pump)
|
||||
gas_source = find_gas_source(G, gas)
|
||||
gas_solenoid = find_associated_solenoid_valve(G, gas_source)
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
|
||||
# 打开真空泵、关闭气源
|
||||
vacuum_action_sequence.append([
|
||||
{
|
||||
"device_id": vacuum_backbone["pump"],
|
||||
"action_name": "set_status",
|
||||
"action_kwargs": {
|
||||
"string": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"device_id": vacuum_backbone["gas"],
|
||||
"action_name": "set_status",
|
||||
"action_kwargs": {
|
||||
"string": "OFF"
|
||||
}
|
||||
}
|
||||
])
|
||||
vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}})
|
||||
print(f"EVACUATE_REFILL: 找到设备")
|
||||
print(f" - 真空泵: {vacuum_pump}")
|
||||
print(f" - 气源: {gas_source}")
|
||||
print(f" - 真空电磁阀: {vacuum_solenoid}")
|
||||
print(f" - 气源电磁阀: {gas_solenoid}")
|
||||
print(f" - 搅拌器: {stirrer_id}")
|
||||
|
||||
# 关闭真空泵阀门、打开气源阀门
|
||||
vacuum_action_sequence.append([
|
||||
{
|
||||
"device_id": vacuum_backbone["vacuum_valve"],
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {
|
||||
"command": "CLOSED"
|
||||
}
|
||||
},
|
||||
{
|
||||
"device_id": vacuum_backbone["gas_valve"],
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {
|
||||
"command": "OPEN"
|
||||
}
|
||||
}
|
||||
])
|
||||
except ValueError as e:
|
||||
raise ValueError(f"设备查找失败: {str(e)}")
|
||||
|
||||
# 3. **关键修复**: 验证路径存在性
|
||||
try:
|
||||
# 验证抽真空路径
|
||||
vacuum_path = nx.shortest_path(G, source=vessel, target=vacuum_pump)
|
||||
print(f"EVACUATE_REFILL: 抽真空路径: {' → '.join(vacuum_path)}")
|
||||
|
||||
# 关闭真空泵、打开气源
|
||||
vacuum_action_sequence.append([
|
||||
{
|
||||
"device_id": vacuum_backbone["pump"],
|
||||
"action_name": "set_status",
|
||||
"action_kwargs": {
|
||||
"string": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"device_id": vacuum_backbone["gas"],
|
||||
"action_name": "set_status",
|
||||
"action_kwargs": {
|
||||
"string": "ON"
|
||||
}
|
||||
# 验证充气路径
|
||||
gas_path = nx.shortest_path(G, source=gas_source, target=vessel)
|
||||
print(f"EVACUATE_REFILL: 充气路径: {' → '.join(gas_path)}")
|
||||
|
||||
# **新增**: 检查路径中的边数据
|
||||
for i in range(len(vacuum_path) - 1):
|
||||
nodeA, nodeB = vacuum_path[i], vacuum_path[i + 1]
|
||||
edge_data = G.get_edge_data(nodeA, nodeB)
|
||||
if not edge_data or 'port' not in edge_data:
|
||||
raise ValueError(f"路径 {nodeA} → {nodeB} 缺少端口信息")
|
||||
print(f" 抽真空路径边 {nodeA} → {nodeB}: {edge_data}")
|
||||
|
||||
for i in range(len(gas_path) - 1):
|
||||
nodeA, nodeB = gas_path[i], gas_path[i + 1]
|
||||
edge_data = G.get_edge_data(nodeA, nodeB)
|
||||
if not edge_data or 'port' not in edge_data:
|
||||
raise ValueError(f"路径 {nodeA} → {nodeB} 缺少端口信息")
|
||||
print(f" 充气路径边 {nodeA} → {nodeB}: {edge_data}")
|
||||
|
||||
except nx.NetworkXNoPath as e:
|
||||
raise ValueError(f"路径不存在: {str(e)}")
|
||||
except Exception as e:
|
||||
raise ValueError(f"路径验证失败: {str(e)}")
|
||||
|
||||
# 4. 启动搅拌器
|
||||
if stirrer_id:
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"stir_speed": STIR_SPEED,
|
||||
"purpose": "抽真空充气操作前启动搅拌"
|
||||
}
|
||||
])
|
||||
vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}})
|
||||
})
|
||||
|
||||
# 5. 执行多次抽真空-充气循环
|
||||
for cycle in range(repeats):
|
||||
print(f"EVACUATE_REFILL: === 第 {cycle+1}/{repeats} 次循环 ===")
|
||||
|
||||
# ============ 抽真空阶段 ============
|
||||
print(f"EVACUATE_REFILL: 抽真空阶段开始")
|
||||
|
||||
# 启动真空泵
|
||||
action_sequence.append({
|
||||
"device_id": vacuum_pump,
|
||||
"action_name": "set_status",
|
||||
"action_kwargs": {"string": "ON"}
|
||||
})
|
||||
|
||||
# 开启真空电磁阀
|
||||
if vacuum_solenoid:
|
||||
action_sequence.append({
|
||||
"device_id": vacuum_solenoid,
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {"command": "OPEN"}
|
||||
})
|
||||
|
||||
# **关键修复**: 改进 pump_protocol 调用和错误处理
|
||||
print(f"EVACUATE_REFILL: 调用抽真空 pump_protocol: {vessel} → {vacuum_pump}")
|
||||
print(f" - 体积: {VACUUM_VOLUME} mL")
|
||||
print(f" - 流速: {PUMP_FLOW_RATE} mL/s")
|
||||
|
||||
try:
|
||||
vacuum_transfer_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=vessel,
|
||||
to_vessel=vacuum_pump,
|
||||
volume=VACUUM_VOLUME,
|
||||
amount="",
|
||||
time=0.0,
|
||||
viscous=False,
|
||||
rinsing_solvent="", # **修复**: 明确不使用清洗
|
||||
rinsing_volume=0.0,
|
||||
rinsing_repeats=0,
|
||||
solid=False,
|
||||
flowrate=PUMP_FLOW_RATE,
|
||||
transfer_flowrate=PUMP_FLOW_RATE
|
||||
)
|
||||
|
||||
if vacuum_transfer_actions:
|
||||
action_sequence.extend(vacuum_transfer_actions)
|
||||
print(f"EVACUATE_REFILL: ✅ 成功添加 {len(vacuum_transfer_actions)} 个抽真空动作")
|
||||
else:
|
||||
print(f"EVACUATE_REFILL: ⚠️ 抽真空 pump_protocol 返回空序列")
|
||||
# **修复**: 添加手动泵动作作为备选
|
||||
action_sequence.extend([
|
||||
{
|
||||
"device_id": "multiway_valve_1",
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {"command": "5"} # 连接到反应器
|
||||
},
|
||||
{
|
||||
"device_id": "transfer_pump_1",
|
||||
"action_name": "set_position",
|
||||
"action_kwargs": {
|
||||
"position": VACUUM_VOLUME,
|
||||
"max_velocity": PUMP_FLOW_RATE
|
||||
}
|
||||
}
|
||||
])
|
||||
print(f"EVACUATE_REFILL: 使用备选手动泵动作")
|
||||
|
||||
except Exception as e:
|
||||
print(f"EVACUATE_REFILL: ❌ 抽真空 pump_protocol 失败: {str(e)}")
|
||||
import traceback
|
||||
print(f"EVACUATE_REFILL: 详细错误:\n{traceback.format_exc()}")
|
||||
|
||||
# **修复**: 添加手动动作而不是忽略错误
|
||||
print(f"EVACUATE_REFILL: 使用手动备选方案")
|
||||
action_sequence.extend([
|
||||
{
|
||||
"device_id": "multiway_valve_1",
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {"command": "5"} # 反应器端口
|
||||
},
|
||||
{
|
||||
"device_id": "transfer_pump_1",
|
||||
"action_name": "set_position",
|
||||
"action_kwargs": {
|
||||
"position": VACUUM_VOLUME,
|
||||
"max_velocity": PUMP_FLOW_RATE
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
# 关闭真空电磁阀
|
||||
if vacuum_solenoid:
|
||||
action_sequence.append({
|
||||
"device_id": vacuum_solenoid,
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {"command": "CLOSED"}
|
||||
})
|
||||
|
||||
# 关闭真空泵
|
||||
action_sequence.append({
|
||||
"device_id": vacuum_pump,
|
||||
"action_name": "set_status",
|
||||
"action_kwargs": {"string": "OFF"}
|
||||
})
|
||||
|
||||
# ============ 充气阶段 ============
|
||||
print(f"EVACUATE_REFILL: 充气阶段开始")
|
||||
|
||||
# 启动气源
|
||||
action_sequence.append({
|
||||
"device_id": gas_source,
|
||||
"action_name": "set_status",
|
||||
"action_kwargs": {"string": "ON"}
|
||||
})
|
||||
|
||||
# 开启气源电磁阀
|
||||
if gas_solenoid:
|
||||
action_sequence.append({
|
||||
"device_id": gas_solenoid,
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {"command": "OPEN"}
|
||||
})
|
||||
|
||||
# **关键修复**: 改进充气 pump_protocol 调用
|
||||
print(f"EVACUATE_REFILL: 调用充气 pump_protocol: {gas_source} → {vessel}")
|
||||
|
||||
try:
|
||||
gas_transfer_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=gas_source,
|
||||
to_vessel=vessel,
|
||||
volume=REFILL_VOLUME,
|
||||
amount="",
|
||||
time=0.0,
|
||||
viscous=False,
|
||||
rinsing_solvent="", # **修复**: 明确不使用清洗
|
||||
rinsing_volume=0.0,
|
||||
rinsing_repeats=0,
|
||||
solid=False,
|
||||
flowrate=PUMP_FLOW_RATE,
|
||||
transfer_flowrate=PUMP_FLOW_RATE
|
||||
)
|
||||
|
||||
if gas_transfer_actions:
|
||||
action_sequence.extend(gas_transfer_actions)
|
||||
print(f"EVACUATE_REFILL: ✅ 成功添加 {len(gas_transfer_actions)} 个充气动作")
|
||||
else:
|
||||
print(f"EVACUATE_REFILL: ⚠️ 充气 pump_protocol 返回空序列")
|
||||
# **修复**: 添加手动充气动作
|
||||
action_sequence.extend([
|
||||
{
|
||||
"device_id": "multiway_valve_2",
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {"command": "8"} # 氮气端口
|
||||
},
|
||||
{
|
||||
"device_id": "transfer_pump_2",
|
||||
"action_name": "set_position",
|
||||
"action_kwargs": {
|
||||
"position": REFILL_VOLUME,
|
||||
"max_velocity": PUMP_FLOW_RATE
|
||||
}
|
||||
},
|
||||
{
|
||||
"device_id": "multiway_valve_2",
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {"command": "5"} # 反应器端口
|
||||
},
|
||||
{
|
||||
"device_id": "transfer_pump_2",
|
||||
"action_name": "set_position",
|
||||
"action_kwargs": {
|
||||
"position": 0.0,
|
||||
"max_velocity": PUMP_FLOW_RATE
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
except Exception as e:
|
||||
print(f"EVACUATE_REFILL: ❌ 充气 pump_protocol 失败: {str(e)}")
|
||||
import traceback
|
||||
print(f"EVACUATE_REFILL: 详细错误:\n{traceback.format_exc()}")
|
||||
|
||||
# **修复**: 使用手动充气动作
|
||||
print(f"EVACUATE_REFILL: 使用手动充气方案")
|
||||
action_sequence.extend([
|
||||
{
|
||||
"device_id": "multiway_valve_2",
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {"command": "8"} # 连接气源
|
||||
},
|
||||
{
|
||||
"device_id": "transfer_pump_2",
|
||||
"action_name": "set_position",
|
||||
"action_kwargs": {
|
||||
"position": REFILL_VOLUME,
|
||||
"max_velocity": PUMP_FLOW_RATE
|
||||
}
|
||||
},
|
||||
{
|
||||
"device_id": "multiway_valve_2",
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {"command": "5"} # 连接反应器
|
||||
},
|
||||
{
|
||||
"device_id": "transfer_pump_2",
|
||||
"action_name": "set_position",
|
||||
"action_kwargs": {
|
||||
"position": 0.0,
|
||||
"max_velocity": PUMP_FLOW_RATE
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
# 关闭气源电磁阀
|
||||
if gas_solenoid:
|
||||
action_sequence.append({
|
||||
"device_id": gas_solenoid,
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {"command": "CLOSED"}
|
||||
})
|
||||
|
||||
# 关闭气源
|
||||
vacuum_action_sequence.append(
|
||||
{
|
||||
"device_id": vacuum_backbone["gas"],
|
||||
"action_name": "set_status",
|
||||
"action_kwargs": {
|
||||
"string": "OFF"
|
||||
}
|
||||
}
|
||||
)
|
||||
action_sequence.append({
|
||||
"device_id": gas_source,
|
||||
"action_name": "set_status",
|
||||
"action_kwargs": {"string": "OFF"}
|
||||
})
|
||||
|
||||
# 关闭阀门
|
||||
vacuum_action_sequence.append(
|
||||
{
|
||||
"device_id": vacuum_backbone["gas_valve"],
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {
|
||||
"command": "CLOSED"
|
||||
}
|
||||
}
|
||||
)
|
||||
return vacuum_action_sequence
|
||||
# 等待下一次循环
|
||||
if cycle < repeats - 1:
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 2.0}
|
||||
})
|
||||
|
||||
# 停止搅拌器
|
||||
if stirrer_id:
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "stop_stir",
|
||||
"action_kwargs": {"vessel": vessel}
|
||||
})
|
||||
|
||||
print(f"EVACUATE_REFILL: 协议生成完成,共 {len(action_sequence)} 个动作")
|
||||
return action_sequence
|
||||
|
||||
|
||||
# 测试函数
|
||||
def test_evacuateandrefill_protocol():
|
||||
"""测试抽真空充气协议"""
|
||||
print("=== EVACUATE AND REFILL PROTOCOL 测试 ===")
|
||||
print("测试完成")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_evacuateandrefill_protocol()
|
||||
143
unilabos/compile/evacuateandrefill_protocol_old.py
Normal file
143
unilabos/compile/evacuateandrefill_protocol_old.py
Normal file
@@ -0,0 +1,143 @@
|
||||
# import numpy as np
|
||||
# import networkx as nx
|
||||
|
||||
|
||||
# def generate_evacuateandrefill_protocol(
|
||||
# G: nx.DiGraph,
|
||||
# vessel: str,
|
||||
# gas: str,
|
||||
# repeats: int = 1
|
||||
# ) -> list[dict]:
|
||||
# """
|
||||
# 生成泵操作的动作序列。
|
||||
|
||||
# :param G: 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
|
||||
# :param from_vessel: 容器A
|
||||
# :param to_vessel: 容器B
|
||||
# :param volume: 转移的体积
|
||||
# :param flowrate: 最终注入容器B时的流速
|
||||
# :param transfer_flowrate: 泵骨架中转移流速(若不指定,默认与注入流速相同)
|
||||
# :return: 泵操作的动作序列
|
||||
# """
|
||||
|
||||
# # 生成电磁阀、真空泵、气源操作的动作序列
|
||||
# vacuum_action_sequence = []
|
||||
# nodes = G.nodes(data=True)
|
||||
|
||||
# # 找到和 vessel 相连的电磁阀和真空泵、气源
|
||||
# vacuum_backbone = {"vessel": vessel}
|
||||
|
||||
# for neighbor in G.neighbors(vessel):
|
||||
# if nodes[neighbor]["class"].startswith("solenoid_valve"):
|
||||
# for neighbor2 in G.neighbors(neighbor):
|
||||
# if neighbor2 == vessel:
|
||||
# continue
|
||||
# if nodes[neighbor2]["class"].startswith("vacuum_pump"):
|
||||
# vacuum_backbone.update({"vacuum_valve": neighbor, "pump": neighbor2})
|
||||
# break
|
||||
# elif nodes[neighbor2]["class"].startswith("gas_source"):
|
||||
# vacuum_backbone.update({"gas_valve": neighbor, "gas": neighbor2})
|
||||
# break
|
||||
# # 判断是否设备齐全
|
||||
# if len(vacuum_backbone) < 5:
|
||||
# print(f"\n\n\n{vacuum_backbone}\n\n\n")
|
||||
# raise ValueError("Not all devices are connected to the vessel.")
|
||||
|
||||
# # 生成操作的动作序列
|
||||
# for i in range(repeats):
|
||||
# # 打开真空泵阀门、关闭气源阀门
|
||||
# vacuum_action_sequence.append([
|
||||
# {
|
||||
# "device_id": vacuum_backbone["vacuum_valve"],
|
||||
# "action_name": "set_valve_position",
|
||||
# "action_kwargs": {
|
||||
# "command": "OPEN"
|
||||
# }
|
||||
# },
|
||||
# {
|
||||
# "device_id": vacuum_backbone["gas_valve"],
|
||||
# "action_name": "set_valve_position",
|
||||
# "action_kwargs": {
|
||||
# "command": "CLOSED"
|
||||
# }
|
||||
# }
|
||||
# ])
|
||||
|
||||
# # 打开真空泵、关闭气源
|
||||
# vacuum_action_sequence.append([
|
||||
# {
|
||||
# "device_id": vacuum_backbone["pump"],
|
||||
# "action_name": "set_status",
|
||||
# "action_kwargs": {
|
||||
# "string": "ON"
|
||||
# }
|
||||
# },
|
||||
# {
|
||||
# "device_id": vacuum_backbone["gas"],
|
||||
# "action_name": "set_status",
|
||||
# "action_kwargs": {
|
||||
# "string": "OFF"
|
||||
# }
|
||||
# }
|
||||
# ])
|
||||
# vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}})
|
||||
|
||||
# # 关闭真空泵阀门、打开气源阀门
|
||||
# vacuum_action_sequence.append([
|
||||
# {
|
||||
# "device_id": vacuum_backbone["vacuum_valve"],
|
||||
# "action_name": "set_valve_position",
|
||||
# "action_kwargs": {
|
||||
# "command": "CLOSED"
|
||||
# }
|
||||
# },
|
||||
# {
|
||||
# "device_id": vacuum_backbone["gas_valve"],
|
||||
# "action_name": "set_valve_position",
|
||||
# "action_kwargs": {
|
||||
# "command": "OPEN"
|
||||
# }
|
||||
# }
|
||||
# ])
|
||||
|
||||
# # 关闭真空泵、打开气源
|
||||
# vacuum_action_sequence.append([
|
||||
# {
|
||||
# "device_id": vacuum_backbone["pump"],
|
||||
# "action_name": "set_status",
|
||||
# "action_kwargs": {
|
||||
# "string": "OFF"
|
||||
# }
|
||||
# },
|
||||
# {
|
||||
# "device_id": vacuum_backbone["gas"],
|
||||
# "action_name": "set_status",
|
||||
# "action_kwargs": {
|
||||
# "string": "ON"
|
||||
# }
|
||||
# }
|
||||
# ])
|
||||
# vacuum_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 60}})
|
||||
|
||||
# # 关闭气源
|
||||
# vacuum_action_sequence.append(
|
||||
# {
|
||||
# "device_id": vacuum_backbone["gas"],
|
||||
# "action_name": "set_status",
|
||||
# "action_kwargs": {
|
||||
# "string": "OFF"
|
||||
# }
|
||||
# }
|
||||
# )
|
||||
|
||||
# # 关闭阀门
|
||||
# vacuum_action_sequence.append(
|
||||
# {
|
||||
# "device_id": vacuum_backbone["gas_valve"],
|
||||
# "action_name": "set_valve_position",
|
||||
# "action_kwargs": {
|
||||
# "command": "CLOSED"
|
||||
# }
|
||||
# }
|
||||
# )
|
||||
# return vacuum_action_sequence
|
||||
@@ -1,81 +1,326 @@
|
||||
import numpy as np
|
||||
from typing import List, Dict, Any
|
||||
import networkx as nx
|
||||
from .pump_protocol import generate_pump_protocol
|
||||
|
||||
|
||||
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
|
||||
"""
|
||||
获取容器中的液体体积
|
||||
"""
|
||||
if vessel not in G.nodes():
|
||||
return 0.0
|
||||
|
||||
vessel_data = G.nodes[vessel].get('data', {})
|
||||
liquids = vessel_data.get('liquid', [])
|
||||
|
||||
total_volume = 0.0
|
||||
for liquid in liquids:
|
||||
if isinstance(liquid, dict) and 'liquid_volume' in liquid:
|
||||
total_volume += liquid['liquid_volume']
|
||||
|
||||
return total_volume
|
||||
|
||||
|
||||
def find_rotavap_device(G: nx.DiGraph) -> str:
|
||||
"""查找旋转蒸发仪设备"""
|
||||
rotavap_nodes = [node for node in G.nodes()
|
||||
if (G.nodes[node].get('class') or '') == 'virtual_rotavap']
|
||||
|
||||
if rotavap_nodes:
|
||||
return rotavap_nodes[0]
|
||||
|
||||
raise ValueError("系统中未找到旋转蒸发仪设备")
|
||||
|
||||
|
||||
def find_solvent_recovery_vessel(G: nx.DiGraph) -> str:
|
||||
"""查找溶剂回收容器"""
|
||||
possible_names = [
|
||||
"flask_distillate",
|
||||
"bottle_distillate",
|
||||
"vessel_distillate",
|
||||
"distillate",
|
||||
"solvent_recovery",
|
||||
"flask_solvent_recovery",
|
||||
"collection_flask"
|
||||
]
|
||||
|
||||
for vessel_name in possible_names:
|
||||
if vessel_name in G.nodes():
|
||||
return vessel_name
|
||||
|
||||
# 如果找不到专门的回收容器,使用废液容器
|
||||
waste_names = ["waste_workup", "flask_waste", "bottle_waste", "waste"]
|
||||
for vessel_name in waste_names:
|
||||
if vessel_name in G.nodes():
|
||||
return vessel_name
|
||||
|
||||
raise ValueError(f"未找到溶剂回收容器。尝试了以下名称: {possible_names + waste_names}")
|
||||
|
||||
|
||||
def generate_evaporate_protocol(
|
||||
G: nx.DiGraph,
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
pressure: float,
|
||||
temp: float,
|
||||
time: float,
|
||||
stir_speed: float
|
||||
) -> list[dict]:
|
||||
pressure: float = 0.1,
|
||||
temp: float = 60.0,
|
||||
time: float = 1800.0,
|
||||
stir_speed: float = 100.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Generate a protocol to evaporate a solution from a vessel.
|
||||
生成蒸发操作的协议序列
|
||||
|
||||
:param G: Directed graph. Nodes are containers and pumps, edges are fluidic connections.
|
||||
:param vessel: Vessel to clean.
|
||||
:param solvent: Solvent to clean vessel with.
|
||||
:param volume: Volume of solvent to clean vessel with.
|
||||
:param temp: Temperature to heat vessel to while cleaning.
|
||||
:param repeats: Number of cleaning cycles to perform.
|
||||
:return: List of actions to clean vessel.
|
||||
蒸发流程:
|
||||
1. 液体转移:将待蒸发溶液从源容器转移到旋转蒸发仪
|
||||
2. 蒸发操作:执行旋转蒸发
|
||||
3. (可选) 溶剂回收:将冷凝的溶剂转移到回收容器
|
||||
4. 残留物转移:将浓缩物从旋转蒸发仪转移回原容器或新容器
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为设备和容器,边为流体管道
|
||||
vessel: 包含待蒸发溶液的容器名称
|
||||
pressure: 蒸发时的真空度 (bar),默认0.1 bar
|
||||
temp: 蒸发时的加热温度 (°C),默认60°C
|
||||
time: 蒸发时间 (秒),默认1800秒(30分钟)
|
||||
stir_speed: 旋转速度 (RPM),默认100 RPM
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 蒸发操作的动作序列
|
||||
|
||||
Raises:
|
||||
ValueError: 当找不到必要的设备时抛出异常
|
||||
|
||||
Examples:
|
||||
evaporate_actions = generate_evaporate_protocol(G, "reaction_mixture", 0.05, 80.0, 3600.0)
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 生成泵操作的动作序列
|
||||
pump_action_sequence = []
|
||||
reactor_volume = 500.0
|
||||
transfer_flowrate = flowrate = 2.5
|
||||
print(f"EVAPORATE: 开始生成蒸发协议")
|
||||
print(f" - 源容器: {vessel}")
|
||||
print(f" - 真空度: {pressure} bar")
|
||||
print(f" - 温度: {temp}°C")
|
||||
print(f" - 时间: {time}s ({time/60:.1f}分钟)")
|
||||
print(f" - 旋转速度: {stir_speed} RPM")
|
||||
|
||||
# 开启冷凝器
|
||||
pump_action_sequence.append({
|
||||
"device_id": "rotavap_chiller",
|
||||
"action_name": "set_temperature",
|
||||
"action_kwargs": {
|
||||
"command": "-40"
|
||||
}
|
||||
})
|
||||
# TODO: 通过温度反馈改为 HeatChillToTemp,而非等待固定时间
|
||||
pump_action_sequence.append({
|
||||
# 验证源容器存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"源容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
# 获取源容器中的液体体积
|
||||
source_volume = get_vessel_liquid_volume(G, vessel)
|
||||
print(f"EVAPORATE: 源容器 {vessel} 中有 {source_volume} mL 液体")
|
||||
|
||||
# 查找旋转蒸发仪
|
||||
try:
|
||||
rotavap_id = find_rotavap_device(G)
|
||||
print(f"EVAPORATE: 找到旋转蒸发仪: {rotavap_id}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到旋转蒸发仪: {str(e)}")
|
||||
|
||||
# 查找旋转蒸发仪样品容器
|
||||
rotavap_vessel = None
|
||||
possible_rotavap_vessels = ["rotavap_flask", "rotavap", "flask_rotavap", "evaporation_flask"]
|
||||
for rv in possible_rotavap_vessels:
|
||||
if rv in G.nodes():
|
||||
rotavap_vessel = rv
|
||||
break
|
||||
|
||||
if not rotavap_vessel:
|
||||
raise ValueError(f"未找到旋转蒸发仪样品容器。尝试了: {possible_rotavap_vessels}")
|
||||
|
||||
print(f"EVAPORATE: 找到旋转蒸发仪样品容器: {rotavap_vessel}")
|
||||
|
||||
# 查找溶剂回收容器
|
||||
try:
|
||||
distillate_vessel = find_solvent_recovery_vessel(G)
|
||||
print(f"EVAPORATE: 找到溶剂回收容器: {distillate_vessel}")
|
||||
except ValueError as e:
|
||||
print(f"EVAPORATE: 警告 - {str(e)}")
|
||||
distillate_vessel = None
|
||||
|
||||
# === 简化的体积计算策略 ===
|
||||
if source_volume > 0:
|
||||
# 如果能检测到液体体积,使用实际体积的大部分
|
||||
transfer_volume = min(source_volume * 0.9, 250.0) # 90%或最多250mL
|
||||
print(f"EVAPORATE: 检测到液体体积,将转移 {transfer_volume} mL")
|
||||
else:
|
||||
# 如果检测不到液体体积,默认转移一整瓶 250mL
|
||||
transfer_volume = 250.0
|
||||
print(f"EVAPORATE: 未检测到液体体积,默认转移整瓶 {transfer_volume} mL")
|
||||
|
||||
# === 第一步:将待蒸发溶液转移到旋转蒸发仪 ===
|
||||
print(f"EVAPORATE: 将 {transfer_volume} mL 溶液从 {vessel} 转移到 {rotavap_vessel}")
|
||||
try:
|
||||
transfer_to_rotavap_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=vessel,
|
||||
to_vessel=rotavap_vessel,
|
||||
volume=transfer_volume,
|
||||
flowrate=2.0,
|
||||
transfer_flowrate=2.0
|
||||
)
|
||||
action_sequence.extend(transfer_to_rotavap_actions)
|
||||
except Exception as e:
|
||||
raise ValueError(f"无法将溶液转移到旋转蒸发仪: {str(e)}")
|
||||
|
||||
# 转移后等待
|
||||
wait_action = {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 1800
|
||||
}
|
||||
})
|
||||
"action_kwargs": {"time": 10}
|
||||
}
|
||||
action_sequence.append(wait_action)
|
||||
|
||||
# 开启旋蒸真空泵、旋转,在液体转移后运行time时间
|
||||
pump_action_sequence.append({
|
||||
"device_id": "rotavap_controller",
|
||||
"action_name": "set_pump_time",
|
||||
# === 第二步:执行旋转蒸发 ===
|
||||
print(f"EVAPORATE: 执行旋转蒸发操作")
|
||||
evaporate_action = {
|
||||
"device_id": rotavap_id,
|
||||
"action_name": "evaporate",
|
||||
"action_kwargs": {
|
||||
"command": str(time + reactor_volume / flowrate * 3)
|
||||
"vessel": rotavap_vessel,
|
||||
"pressure": pressure,
|
||||
"temp": temp,
|
||||
"time": time,
|
||||
"stir_speed": stir_speed
|
||||
}
|
||||
})
|
||||
pump_action_sequence.append({
|
||||
"device_id": "rotavap_controller",
|
||||
"action_name": "set_pump_time",
|
||||
"action_kwargs": {
|
||||
"command": str(time + reactor_volume / flowrate * 3)
|
||||
}
|
||||
})
|
||||
}
|
||||
action_sequence.append(evaporate_action)
|
||||
|
||||
# 液体转入旋转蒸发器
|
||||
pump_action_sequence.append({
|
||||
"device_id": "",
|
||||
"action_name": "PumpTransferProtocol",
|
||||
"action_kwargs": {
|
||||
"from_vessel": vessel,
|
||||
"to_vessel": "rotavap",
|
||||
"volume": reactor_volume,
|
||||
"time": reactor_volume / flowrate,
|
||||
# "transfer_flowrate": transfer_flowrate,
|
||||
}
|
||||
})
|
||||
|
||||
pump_action_sequence.append({
|
||||
# 蒸发后等待系统稳定
|
||||
wait_action = {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": time
|
||||
}
|
||||
})
|
||||
return pump_action_sequence
|
||||
"action_kwargs": {"time": 30}
|
||||
}
|
||||
action_sequence.append(wait_action)
|
||||
|
||||
# === 第三步:溶剂回收(如果有回收容器)===
|
||||
if distillate_vessel:
|
||||
print(f"EVAPORATE: 回收冷凝溶剂到 {distillate_vessel}")
|
||||
try:
|
||||
condenser_vessel = "rotavap_condenser"
|
||||
if condenser_vessel in G.nodes():
|
||||
# 估算回收体积(约为转移体积的70% - 大部分溶剂被蒸发回收)
|
||||
recovery_volume = transfer_volume * 0.7
|
||||
print(f"EVAPORATE: 预计回收 {recovery_volume} mL 溶剂")
|
||||
|
||||
recovery_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=condenser_vessel,
|
||||
to_vessel=distillate_vessel,
|
||||
volume=recovery_volume,
|
||||
flowrate=3.0,
|
||||
transfer_flowrate=3.0
|
||||
)
|
||||
action_sequence.extend(recovery_actions)
|
||||
else:
|
||||
print("EVAPORATE: 未找到冷凝器容器,跳过溶剂回收")
|
||||
except Exception as e:
|
||||
print(f"EVAPORATE: 溶剂回收失败: {str(e)}")
|
||||
|
||||
# === 第四步:将浓缩物转移回原容器 ===
|
||||
print(f"EVAPORATE: 将浓缩物从旋转蒸发仪转移回 {vessel}")
|
||||
try:
|
||||
# 估算浓缩物体积(约为转移体积的20% - 大部分溶剂已蒸发)
|
||||
concentrate_volume = transfer_volume * 0.2
|
||||
print(f"EVAPORATE: 预计浓缩物体积 {concentrate_volume} mL")
|
||||
|
||||
transfer_back_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=rotavap_vessel,
|
||||
to_vessel=vessel,
|
||||
volume=concentrate_volume,
|
||||
flowrate=1.0, # 浓缩物可能粘稠,用较慢流速
|
||||
transfer_flowrate=1.0
|
||||
)
|
||||
action_sequence.extend(transfer_back_actions)
|
||||
except Exception as e:
|
||||
print(f"EVAPORATE: 将浓缩物转移回容器失败: {str(e)}")
|
||||
|
||||
# === 第五步:清洗旋转蒸发仪 ===
|
||||
print(f"EVAPORATE: 清洗旋转蒸发仪")
|
||||
try:
|
||||
# 查找清洗溶剂
|
||||
cleaning_solvent = None
|
||||
for solvent in ["flask_ethanol", "flask_acetone", "flask_water"]:
|
||||
if solvent in G.nodes():
|
||||
cleaning_solvent = solvent
|
||||
break
|
||||
|
||||
if cleaning_solvent and distillate_vessel:
|
||||
# 用固定量溶剂清洗(不依赖检测体积)
|
||||
cleaning_volume = 50.0 # 固定50mL清洗
|
||||
print(f"EVAPORATE: 用 {cleaning_volume} mL {cleaning_solvent} 清洗")
|
||||
|
||||
# 清洗溶剂加入
|
||||
cleaning_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=cleaning_solvent,
|
||||
to_vessel=rotavap_vessel,
|
||||
volume=cleaning_volume,
|
||||
flowrate=2.0,
|
||||
transfer_flowrate=2.0
|
||||
)
|
||||
action_sequence.extend(cleaning_actions)
|
||||
|
||||
# 将清洗液转移到废液/回收容器
|
||||
waste_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=rotavap_vessel,
|
||||
to_vessel=distillate_vessel, # 使用回收容器作为废液
|
||||
volume=cleaning_volume,
|
||||
flowrate=2.0,
|
||||
transfer_flowrate=2.0
|
||||
)
|
||||
action_sequence.extend(waste_actions)
|
||||
|
||||
except Exception as e:
|
||||
print(f"EVAPORATE: 清洗步骤失败: {str(e)}")
|
||||
|
||||
print(f"EVAPORATE: 生成了 {len(action_sequence)} 个动作")
|
||||
print(f"EVAPORATE: 蒸发协议生成完成")
|
||||
print(f"EVAPORATE: 总处理体积: {transfer_volume} mL")
|
||||
|
||||
return action_sequence
|
||||
|
||||
|
||||
# 便捷函数:常用蒸发方案 - 都使用250mL标准瓶体积
|
||||
def generate_quick_evaporate_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
temp: float = 40.0,
|
||||
time: float = 900.0 # 15分钟
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""快速蒸发:低温、短时间、整瓶处理"""
|
||||
return generate_evaporate_protocol(G, vessel, 0.2, temp, time, 80.0)
|
||||
|
||||
|
||||
def generate_gentle_evaporate_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
temp: float = 50.0,
|
||||
time: float = 2700.0 # 45分钟
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""温和蒸发:中等条件、较长时间、整瓶处理"""
|
||||
return generate_evaporate_protocol(G, vessel, 0.1, temp, time, 60.0)
|
||||
|
||||
|
||||
def generate_high_vacuum_evaporate_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
temp: float = 35.0,
|
||||
time: float = 3600.0 # 1小时
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""高真空蒸发:低温、高真空、长时间、整瓶处理"""
|
||||
return generate_evaporate_protocol(G, vessel, 0.01, temp, time, 120.0)
|
||||
|
||||
|
||||
def generate_standard_evaporate_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""标准蒸发:常用参数、整瓶250mL处理"""
|
||||
return generate_evaporate_protocol(
|
||||
G=G,
|
||||
vessel=vessel,
|
||||
pressure=0.1, # 标准真空度
|
||||
temp=60.0, # 适中温度
|
||||
time=1800.0, # 30分钟
|
||||
stir_speed=100.0 # 适中旋转速度
|
||||
)
|
||||
|
||||
@@ -1,5 +1,89 @@
|
||||
from typing import List, Dict, Any
|
||||
import networkx as nx
|
||||
from .pump_protocol import generate_pump_protocol
|
||||
|
||||
|
||||
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
|
||||
"""获取容器中的液体体积"""
|
||||
if vessel not in G.nodes():
|
||||
return 0.0
|
||||
|
||||
vessel_data = G.nodes[vessel].get('data', {})
|
||||
liquids = vessel_data.get('liquid', [])
|
||||
|
||||
total_volume = 0.0
|
||||
for liquid in liquids:
|
||||
if isinstance(liquid, dict) and 'liquid_volume' in liquid:
|
||||
total_volume += liquid['liquid_volume']
|
||||
|
||||
return total_volume
|
||||
|
||||
|
||||
def find_filter_device(G: nx.DiGraph) -> str:
|
||||
"""查找过滤器设备"""
|
||||
filter_nodes = [node for node in G.nodes()
|
||||
if (G.nodes[node].get('class') or '') == 'virtual_filter']
|
||||
|
||||
if filter_nodes:
|
||||
return filter_nodes[0]
|
||||
|
||||
raise ValueError("系统中未找到过滤器设备")
|
||||
|
||||
|
||||
def find_filter_vessel(G: nx.DiGraph) -> str:
|
||||
"""查找过滤器专用容器"""
|
||||
possible_names = [
|
||||
"filter_vessel", # 标准过滤器容器
|
||||
"filtration_vessel", # 备选名称
|
||||
"vessel_filter", # 备选名称
|
||||
"filter_unit", # 备选名称
|
||||
"filter" # 简单名称
|
||||
]
|
||||
|
||||
for vessel_name in possible_names:
|
||||
if vessel_name in G.nodes():
|
||||
return vessel_name
|
||||
|
||||
raise ValueError(f"未找到过滤器容器。尝试了以下名称: {possible_names}")
|
||||
|
||||
|
||||
def find_filtrate_vessel(G: nx.DiGraph, filtrate_vessel: str = "") -> str:
|
||||
"""查找滤液收集容器"""
|
||||
if filtrate_vessel and filtrate_vessel in G.nodes():
|
||||
return filtrate_vessel
|
||||
|
||||
# 自动查找滤液容器
|
||||
possible_names = [
|
||||
"filtrate_vessel",
|
||||
"collection_bottle_1",
|
||||
"collection_bottle_2",
|
||||
"waste_workup"
|
||||
]
|
||||
|
||||
for vessel_name in possible_names:
|
||||
if vessel_name in G.nodes():
|
||||
return vessel_name
|
||||
|
||||
raise ValueError(f"未找到滤液收集容器。尝试了以下名称: {possible_names}")
|
||||
|
||||
|
||||
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
|
||||
"""查找与指定容器相连的加热搅拌器"""
|
||||
# 查找所有加热搅拌器节点
|
||||
heatchill_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||
|
||||
# 检查哪个加热器与目标容器相连
|
||||
for heatchill in heatchill_nodes:
|
||||
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
|
||||
return heatchill
|
||||
|
||||
# 如果没有直接连接,返回第一个可用的加热器
|
||||
if heatchill_nodes:
|
||||
return heatchill_nodes[0]
|
||||
|
||||
raise ValueError(f"未找到与容器 {vessel} 相连的加热搅拌器")
|
||||
|
||||
|
||||
def generate_filter_protocol(
|
||||
G: nx.DiGraph,
|
||||
@@ -12,59 +96,209 @@ def generate_filter_protocol(
|
||||
volume: float = 0.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成过滤操作的协议序列
|
||||
生成过滤操作的协议序列,复用 pump_protocol 的成熟算法
|
||||
|
||||
过滤流程:
|
||||
1. 液体转移:将待过滤溶液从源容器转移到过滤器
|
||||
2. 启动加热搅拌:设置温度和搅拌
|
||||
3. 执行过滤:通过过滤器分离固液
|
||||
4. (可选) 继续或停止加热搅拌
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为设备和容器
|
||||
vessel: 过滤容器
|
||||
filtrate_vessel: 滤液容器(可选)
|
||||
stir: 是否搅拌
|
||||
stir_speed: 搅拌速度(可选)
|
||||
temp: 温度(可选,摄氏度)
|
||||
continue_heatchill: 是否继续加热冷却
|
||||
volume: 过滤体积(可选)
|
||||
G: 有向图,节点为设备和容器,边为流体管道
|
||||
vessel: 包含待过滤溶液的容器名称
|
||||
filtrate_vessel: 滤液收集容器(可选,自动查找)
|
||||
stir: 是否在过滤过程中搅拌
|
||||
stir_speed: 搅拌速度 (RPM)
|
||||
temp: 过滤温度 (°C)
|
||||
continue_heatchill: 过滤后是否继续加热搅拌
|
||||
volume: 预期过滤体积 (mL),0表示全部过滤
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 过滤操作的动作序列
|
||||
|
||||
Raises:
|
||||
ValueError: 当找不到过滤设备时抛出异常
|
||||
|
||||
Examples:
|
||||
filter_protocol = generate_filter_protocol(G, "reactor", "filtrate_vessel", stir=True, volume=100.0)
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 查找过滤设备
|
||||
filter_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_filter']
|
||||
print(f"FILTER: 开始生成过滤协议")
|
||||
print(f" - 源容器: {vessel}")
|
||||
print(f" - 滤液容器: {filtrate_vessel}")
|
||||
print(f" - 搅拌: {stir} ({stir_speed} RPM)" if stir else " - 搅拌: 否")
|
||||
print(f" - 过滤温度: {temp}°C")
|
||||
print(f" - 预期过滤体积: {volume} mL" if volume > 0 else " - 预期过滤体积: 全部")
|
||||
print(f" - 继续加热搅拌: {continue_heatchill}")
|
||||
|
||||
if not filter_nodes:
|
||||
raise ValueError("没有找到可用的过滤设备")
|
||||
|
||||
# 使用第一个可用的过滤器
|
||||
filter_id = filter_nodes[0]
|
||||
|
||||
# 验证容器是否存在
|
||||
# 验证源容器存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"过滤容器 {vessel} 不存在于图中")
|
||||
raise ValueError(f"源容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
if filtrate_vessel and filtrate_vessel not in G.nodes():
|
||||
raise ValueError(f"滤液容器 {filtrate_vessel} 不存在于图中")
|
||||
# 获取源容器中的液体体积
|
||||
source_volume = get_vessel_liquid_volume(G, vessel)
|
||||
print(f"FILTER: 源容器 {vessel} 中有 {source_volume} mL 液体")
|
||||
|
||||
# 执行过滤操作
|
||||
# 查找过滤器设备
|
||||
try:
|
||||
filter_id = find_filter_device(G)
|
||||
print(f"FILTER: 找到过滤器: {filter_id}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到过滤器: {str(e)}")
|
||||
|
||||
# 查找过滤器容器
|
||||
try:
|
||||
filter_vessel_id = find_filter_vessel(G)
|
||||
print(f"FILTER: 找到过滤器容器: {filter_vessel_id}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到过滤器容器: {str(e)}")
|
||||
|
||||
# 查找滤液收集容器
|
||||
try:
|
||||
actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel)
|
||||
print(f"FILTER: 找到滤液收集容器: {actual_filtrate_vessel}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到滤液收集容器: {str(e)}")
|
||||
|
||||
# 查找加热搅拌器(如果需要温度控制或搅拌)
|
||||
heatchill_id = None
|
||||
if temp != 25.0 or stir or continue_heatchill:
|
||||
try:
|
||||
heatchill_id = find_connected_heatchill(G, filter_vessel_id)
|
||||
print(f"FILTER: 找到加热搅拌器: {heatchill_id}")
|
||||
except ValueError as e:
|
||||
print(f"FILTER: 警告 - {str(e)}")
|
||||
|
||||
# === 简化的体积计算策略 ===
|
||||
if volume > 0:
|
||||
transfer_volume = min(volume, source_volume if source_volume > 0 else volume)
|
||||
print(f"FILTER: 指定过滤体积 {transfer_volume} mL")
|
||||
elif source_volume > 0:
|
||||
transfer_volume = source_volume * 0.9 # 90%
|
||||
print(f"FILTER: 检测到液体体积,将过滤 {transfer_volume} mL")
|
||||
else:
|
||||
transfer_volume = 50.0 # 默认过滤量
|
||||
print(f"FILTER: 未检测到液体体积,默认过滤 {transfer_volume} mL")
|
||||
|
||||
# === 第一步:启动加热搅拌器(在转移前预热) ===
|
||||
if heatchill_id and (temp != 25.0 or stir):
|
||||
print(f"FILTER: 启动加热搅拌器,温度: {temp}°C,搅拌: {stir}")
|
||||
|
||||
heatchill_action = {
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_start",
|
||||
"action_kwargs": {
|
||||
"vessel": filter_vessel_id,
|
||||
"temp": temp,
|
||||
"purpose": f"过滤过程温度控制和搅拌"
|
||||
}
|
||||
}
|
||||
action_sequence.append(heatchill_action)
|
||||
|
||||
# 等待温度稳定
|
||||
if temp != 25.0:
|
||||
wait_time = min(30, abs(temp - 25.0) * 1.0) # 根据温差估算预热时间
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": wait_time}
|
||||
})
|
||||
|
||||
# === 第二步:将待过滤溶液转移到过滤器 ===
|
||||
print(f"FILTER: 将 {transfer_volume} mL 溶液从 {vessel} 转移到 {filter_vessel_id}")
|
||||
try:
|
||||
# 使用成熟的 pump_protocol 算法进行液体转移
|
||||
transfer_to_filter_actions = generate_pump_protocol(
|
||||
G=G,
|
||||
from_vessel=vessel,
|
||||
to_vessel=filter_vessel_id,
|
||||
volume=transfer_volume,
|
||||
flowrate=1.0, # 过滤转移用较慢速度,避免扰动
|
||||
transfer_flowrate=1.5
|
||||
)
|
||||
action_sequence.extend(transfer_to_filter_actions)
|
||||
except Exception as e:
|
||||
raise ValueError(f"无法将溶液转移到过滤器: {str(e)}")
|
||||
|
||||
# 转移后等待
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 5}
|
||||
})
|
||||
|
||||
# === 第三步:执行过滤操作(完全按照 Filter.action 参数) ===
|
||||
print(f"FILTER: 执行过滤操作")
|
||||
filter_action = {
|
||||
"device_id": filter_id,
|
||||
"action_name": "filter_sample",
|
||||
"action_name": "filter",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"filtrate_vessel": filtrate_vessel,
|
||||
"vessel": filter_vessel_id,
|
||||
"filtrate_vessel": actual_filtrate_vessel,
|
||||
"stir": stir,
|
||||
"stir_speed": stir_speed,
|
||||
"temp": temp,
|
||||
"continue_heatchill": continue_heatchill,
|
||||
"volume": volume
|
||||
"volume": transfer_volume
|
||||
}
|
||||
}
|
||||
action_sequence.append(filter_action)
|
||||
|
||||
# 过滤后等待
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 10}
|
||||
})
|
||||
|
||||
return action_sequence
|
||||
# === 第四步:如果不继续加热搅拌,停止加热器 ===
|
||||
if heatchill_id and not continue_heatchill and (temp != 25.0 or stir):
|
||||
print(f"FILTER: 停止加热搅拌器")
|
||||
|
||||
stop_action = {
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_stop",
|
||||
"action_kwargs": {
|
||||
"vessel": filter_vessel_id
|
||||
}
|
||||
}
|
||||
action_sequence.append(stop_action)
|
||||
|
||||
print(f"FILTER: 生成了 {len(action_sequence)} 个动作")
|
||||
print(f"FILTER: 过滤协议生成完成")
|
||||
|
||||
return action_sequence
|
||||
|
||||
|
||||
# 便捷函数:常用过滤方案
|
||||
def generate_gravity_filter_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
filtrate_vessel: str = ""
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""重力过滤:室温,无搅拌"""
|
||||
return generate_filter_protocol(G, vessel, filtrate_vessel, False, 0.0, 25.0, False, 0.0)
|
||||
|
||||
|
||||
def generate_hot_filter_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
filtrate_vessel: str = "",
|
||||
temp: float = 60.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""热过滤:高温过滤,防止结晶析出"""
|
||||
return generate_filter_protocol(G, vessel, filtrate_vessel, False, 0.0, temp, False, 0.0)
|
||||
|
||||
|
||||
def generate_stirred_filter_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
filtrate_vessel: str = "",
|
||||
stir_speed: float = 200.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""搅拌过滤:低速搅拌,防止滤饼堵塞"""
|
||||
return generate_filter_protocol(G, vessel, filtrate_vessel, True, stir_speed, 25.0, False, 0.0)
|
||||
|
||||
|
||||
def generate_hot_stirred_filter_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
filtrate_vessel: str = "",
|
||||
temp: float = 60.0,
|
||||
stir_speed: float = 300.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""热搅拌过滤:高温搅拌过滤"""
|
||||
return generate_filter_protocol(G, vessel, filtrate_vessel, True, stir_speed, temp, False, 0.0)
|
||||
@@ -1,33 +1,61 @@
|
||||
from typing import List, Dict, Any
|
||||
from typing import List, Dict, Any, Optional
|
||||
import networkx as nx
|
||||
|
||||
|
||||
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
|
||||
"""
|
||||
查找与指定容器相连的加热/冷却设备
|
||||
"""
|
||||
# 查找所有加热/冷却设备节点
|
||||
heatchill_nodes = [node for node in G.nodes()
|
||||
if (G.nodes[node].get('class') or '') == 'virtual_heatchill']
|
||||
|
||||
# 检查哪个加热/冷却设备与目标容器相连(机械连接)
|
||||
for heatchill in heatchill_nodes:
|
||||
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
|
||||
return heatchill
|
||||
|
||||
# 如果没有直接连接,返回第一个可用的加热/冷却设备
|
||||
if heatchill_nodes:
|
||||
return heatchill_nodes[0]
|
||||
|
||||
raise ValueError("系统中未找到可用的加热/冷却设备")
|
||||
|
||||
|
||||
def generate_heat_chill_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
temp: float,
|
||||
time: float,
|
||||
stir: bool,
|
||||
stir_speed: float,
|
||||
purpose: str
|
||||
stir: bool = False,
|
||||
stir_speed: float = 300.0,
|
||||
purpose: str = "加热/冷却操作"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成加热/冷却操作的协议序列 - 严格按照 HeatChill.action
|
||||
生成加热/冷却操作的协议序列 - 带时间限制的完整操作
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 查找加热/冷却设备
|
||||
heatchill_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||
|
||||
if not heatchill_nodes:
|
||||
raise ValueError("没有找到可用的加热/冷却设备")
|
||||
|
||||
heatchill_id = heatchill_nodes[0]
|
||||
print(f"HEATCHILL: 开始生成加热/冷却协议")
|
||||
print(f" - 容器: {vessel}")
|
||||
print(f" - 目标温度: {temp}°C")
|
||||
print(f" - 持续时间: {time}秒")
|
||||
print(f" - 使用内置搅拌: {stir}, 速度: {stir_speed} RPM")
|
||||
print(f" - 目的: {purpose}")
|
||||
|
||||
# 1. 验证容器存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
action_sequence.append({
|
||||
# 2. 查找加热/冷却设备
|
||||
try:
|
||||
heatchill_id = find_connected_heatchill(G, vessel)
|
||||
print(f"HEATCHILL: 找到加热/冷却设备: {heatchill_id}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
|
||||
|
||||
# 3. 执行加热/冷却操作
|
||||
heatchill_action = {
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill",
|
||||
"action_kwargs": {
|
||||
@@ -36,10 +64,13 @@ def generate_heat_chill_protocol(
|
||||
"time": time,
|
||||
"stir": stir,
|
||||
"stir_speed": stir_speed,
|
||||
"purpose": purpose
|
||||
"status": "start"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
action_sequence.append(heatchill_action)
|
||||
|
||||
print(f"HEATCHILL: 生成了 {len(action_sequence)} 个动作")
|
||||
return action_sequence
|
||||
|
||||
|
||||
@@ -47,25 +78,31 @@ def generate_heat_chill_start_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
temp: float,
|
||||
purpose: str
|
||||
purpose: str = "开始加热/冷却"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成开始加热/冷却操作的协议序列 - 严格按照 HeatChillStart.action
|
||||
生成开始加热/冷却操作的协议序列
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
heatchill_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||
|
||||
if not heatchill_nodes:
|
||||
raise ValueError("没有找到可用的加热/冷却设备")
|
||||
|
||||
heatchill_id = heatchill_nodes[0]
|
||||
print(f"HEATCHILL_START: 开始生成加热/冷却启动协议")
|
||||
print(f" - 容器: {vessel}")
|
||||
print(f" - 目标温度: {temp}°C")
|
||||
print(f" - 目的: {purpose}")
|
||||
|
||||
# 1. 验证容器存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
action_sequence.append({
|
||||
# 2. 查找加热/冷却设备
|
||||
try:
|
||||
heatchill_id = find_connected_heatchill(G, vessel)
|
||||
print(f"HEATCHILL_START: 找到加热/冷却设备: {heatchill_id}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
|
||||
|
||||
# 3. 执行开始加热/冷却操作
|
||||
heatchill_start_action = {
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_start",
|
||||
"action_kwargs": {
|
||||
@@ -73,8 +110,11 @@ def generate_heat_chill_start_protocol(
|
||||
"temp": temp,
|
||||
"purpose": purpose
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
action_sequence.append(heatchill_start_action)
|
||||
|
||||
print(f"HEATCHILL_START: 生成了 {len(action_sequence)} 个动作")
|
||||
return action_sequence
|
||||
|
||||
|
||||
@@ -84,34 +124,250 @@ def generate_heat_chill_stop_protocol(
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成停止加热/冷却操作的协议序列
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为设备和容器
|
||||
vessel: 容器名称
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 停止加热/冷却操作的动作序列
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 查找加热/冷却设备
|
||||
heatchill_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_heatchill']
|
||||
|
||||
if not heatchill_nodes:
|
||||
raise ValueError("没有找到可用的加热/冷却设备")
|
||||
|
||||
heatchill_id = heatchill_nodes[0]
|
||||
print(f"HEATCHILL_STOP: 开始生成加热/冷却停止协议")
|
||||
print(f" - 容器: {vessel}")
|
||||
|
||||
# 1. 验证容器存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
action_sequence.append({
|
||||
# 2. 查找加热/冷却设备
|
||||
try:
|
||||
heatchill_id = find_connected_heatchill(G, vessel)
|
||||
print(f"HEATCHILL_STOP: 找到加热/冷却设备: {heatchill_id}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
|
||||
|
||||
# 3. 执行停止加热/冷却操作
|
||||
heatchill_stop_action = {
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_stop",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return action_sequence
|
||||
action_sequence.append(heatchill_stop_action)
|
||||
|
||||
print(f"HEATCHILL_STOP: 生成了 {len(action_sequence)} 个动作")
|
||||
return action_sequence
|
||||
|
||||
|
||||
def generate_heat_chill_to_temp_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
temp: float,
|
||||
active: bool = True,
|
||||
continue_heatchill: bool = False,
|
||||
stir: bool = False,
|
||||
stir_speed: Optional[float] = None,
|
||||
purpose: Optional[str] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成加热/冷却到指定温度的协议序列 - 智能温控协议
|
||||
|
||||
**关键修复**: 学习 pump_protocol 的模式,直接使用设备基础动作,不依赖特定的 Action 文件
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 设置默认值
|
||||
if stir_speed is None:
|
||||
stir_speed = 300.0
|
||||
if purpose is None:
|
||||
purpose = f"智能温控到 {temp}°C"
|
||||
|
||||
print(f"HEATCHILL_TO_TEMP: 开始生成智能温控协议")
|
||||
print(f" - 容器: {vessel}")
|
||||
print(f" - 目标温度: {temp}°C")
|
||||
print(f" - 主动控温: {active}")
|
||||
print(f" - 达到温度后继续: {continue_heatchill}")
|
||||
print(f" - 搅拌: {stir}, 速度: {stir_speed} RPM")
|
||||
print(f" - 目的: {purpose}")
|
||||
|
||||
# 1. 验证容器存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
# 2. 查找加热/冷却设备
|
||||
try:
|
||||
heatchill_id = find_connected_heatchill(G, vessel)
|
||||
print(f"HEATCHILL_TO_TEMP: 找到加热/冷却设备: {heatchill_id}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
|
||||
|
||||
# 3. 根据参数选择合适的基础动作组合 (学习 pump_protocol 的模式)
|
||||
if not active:
|
||||
print(f"HEATCHILL_TO_TEMP: 非主动模式,仅等待")
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 10.0,
|
||||
"purpose": f"等待容器 {vessel} 自然达到 {temp}°C"
|
||||
}
|
||||
})
|
||||
else:
|
||||
if continue_heatchill:
|
||||
# 持续模式:使用 heat_chill_start 基础动作
|
||||
print(f"HEATCHILL_TO_TEMP: 使用持续温控模式")
|
||||
action_sequence.append({
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_start", # ← 直接使用设备基础动作
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"temp": temp,
|
||||
"purpose": f"{purpose} (持续保温)"
|
||||
}
|
||||
})
|
||||
else:
|
||||
# 一次性模式:使用 heat_chill 基础动作
|
||||
print(f"HEATCHILL_TO_TEMP: 使用一次性温控模式")
|
||||
estimated_time = max(60.0, min(900.0, abs(temp - 25.0) * 30.0))
|
||||
print(f"HEATCHILL_TO_TEMP: 估算所需时间: {estimated_time}秒")
|
||||
|
||||
action_sequence.append({
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill", # ← 直接使用设备基础动作
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"temp": temp,
|
||||
"time": estimated_time,
|
||||
"stir": stir,
|
||||
"stir_speed": stir_speed,
|
||||
"status": "start"
|
||||
}
|
||||
})
|
||||
|
||||
print(f"HEATCHILL_TO_TEMP: 生成了 {len(action_sequence)} 个动作")
|
||||
return action_sequence
|
||||
|
||||
|
||||
# 扩展版本的加热/冷却协议,集成智能温控功能
|
||||
def generate_smart_heat_chill_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
temp: float,
|
||||
time: float = 0.0, # 0表示自动估算
|
||||
active: bool = True,
|
||||
continue_heatchill: bool = False,
|
||||
stir: bool = False,
|
||||
stir_speed: float = 300.0,
|
||||
purpose: str = "智能加热/冷却"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
这个函数集成了 generate_heat_chill_to_temp_protocol 的智能逻辑,
|
||||
但使用现有的 Action 类型
|
||||
"""
|
||||
# 如果时间为0,自动估算
|
||||
if time == 0.0:
|
||||
estimated_time = max(60.0, min(900.0, abs(temp - 25.0) * 30.0))
|
||||
time = estimated_time
|
||||
|
||||
if continue_heatchill:
|
||||
# 使用持续模式
|
||||
return generate_heat_chill_start_protocol(G, vessel, temp, purpose)
|
||||
else:
|
||||
# 使用定时模式
|
||||
return generate_heat_chill_protocol(G, vessel, temp, time, stir, stir_speed, purpose)
|
||||
|
||||
|
||||
# 便捷函数
|
||||
def generate_heating_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
temp: float,
|
||||
time: float = 300.0,
|
||||
stir: bool = True,
|
||||
stir_speed: float = 300.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""生成加热协议的便捷函数"""
|
||||
return generate_heat_chill_protocol(
|
||||
G=G, vessel=vessel, temp=temp, time=time,
|
||||
stir=stir, stir_speed=stir_speed, purpose=f"加热到 {temp}°C"
|
||||
)
|
||||
|
||||
|
||||
def generate_cooling_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
temp: float,
|
||||
time: float = 600.0,
|
||||
stir: bool = True,
|
||||
stir_speed: float = 200.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""生成冷却协议的便捷函数"""
|
||||
return generate_heat_chill_protocol(
|
||||
G=G, vessel=vessel, temp=temp, time=time,
|
||||
stir=stir, stir_speed=stir_speed, purpose=f"冷却到 {temp}°C"
|
||||
)
|
||||
|
||||
|
||||
# # 温度预设快捷函数
|
||||
# def generate_room_temp_protocol(
|
||||
# G: nx.DiGraph,
|
||||
# vessel: str,
|
||||
# stir: bool = False
|
||||
# ) -> List[Dict[str, Any]]:
|
||||
# """返回室温的快捷函数"""
|
||||
# return generate_heat_chill_to_temp_protocol(
|
||||
# G=G,
|
||||
# vessel=vessel,
|
||||
# temp=25.0,
|
||||
# active=True,
|
||||
# continue_heatchill=False,
|
||||
# stir=stir,
|
||||
# purpose="冷却到室温"
|
||||
# )
|
||||
|
||||
|
||||
# def generate_reflux_heating_protocol(
|
||||
# G: nx.DiGraph,
|
||||
# vessel: str,
|
||||
# temp: float,
|
||||
# time: float = 3600.0 # 1小时回流
|
||||
# ) -> List[Dict[str, Any]]:
|
||||
# """回流加热的快捷函数"""
|
||||
# return generate_heat_chill_protocol(
|
||||
# G=G,
|
||||
# vessel=vessel,
|
||||
# temp=temp,
|
||||
# time=time,
|
||||
# stir=True,
|
||||
# stir_speed=400.0, # 回流时较快搅拌
|
||||
# purpose=f"回流加热到 {temp}°C"
|
||||
# )
|
||||
|
||||
|
||||
# def generate_ice_bath_protocol(
|
||||
# G: nx.DiGraph,
|
||||
# vessel: str,
|
||||
# time: float = 600.0 # 10分钟冰浴
|
||||
# ) -> List[Dict[str, Any]]:
|
||||
# """冰浴冷却的快捷函数"""
|
||||
# return generate_heat_chill_protocol(
|
||||
# G=G,
|
||||
# vessel=vessel,
|
||||
# temp=0.0,
|
||||
# time=time,
|
||||
# stir=True,
|
||||
# stir_speed=150.0, # 冰浴时缓慢搅拌
|
||||
# purpose="冰浴冷却到 0°C"
|
||||
# )
|
||||
|
||||
|
||||
# 测试函数
|
||||
def test_heatchill_protocol():
|
||||
"""测试加热/冷却协议的示例"""
|
||||
print("=== HEAT CHILL PROTOCOL 测试 ===")
|
||||
print("完整的四个协议函数:")
|
||||
print("1. generate_heat_chill_protocol - 带时间限制的完整操作")
|
||||
print("2. generate_heat_chill_start_protocol - 持续加热/冷却")
|
||||
print("3. generate_heat_chill_stop_protocol - 停止加热/冷却")
|
||||
print("4. generate_heat_chill_to_temp_protocol - 智能温控 (您的 HeatChillToTemp)")
|
||||
print("测试完成")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_heatchill_protocol()
|
||||
@@ -1,6 +1,28 @@
|
||||
from typing import List, Dict, Any
|
||||
import networkx as nx
|
||||
|
||||
|
||||
def find_connected_stirrer(G: nx.DiGraph, vessel: str = None) -> str:
|
||||
"""
|
||||
查找与指定容器相连的搅拌设备,或查找可用的搅拌设备
|
||||
"""
|
||||
# 查找所有搅拌设备节点
|
||||
stirrer_nodes = [node for node in G.nodes()
|
||||
if (G.nodes[node].get('class') or '') == 'virtual_stirrer']
|
||||
|
||||
if vessel:
|
||||
# 检查哪个搅拌设备与目标容器相连(机械连接)
|
||||
for stirrer in stirrer_nodes:
|
||||
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
|
||||
return stirrer
|
||||
|
||||
# 如果没有指定容器或没有直接连接,返回第一个可用的搅拌设备
|
||||
if stirrer_nodes:
|
||||
return stirrer_nodes[0]
|
||||
|
||||
raise ValueError("系统中未找到可用的搅拌设备")
|
||||
|
||||
|
||||
def generate_stir_protocol(
|
||||
G: nx.DiGraph,
|
||||
stir_time: float,
|
||||
@@ -8,37 +30,24 @@ def generate_stir_protocol(
|
||||
settling_time: float
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成搅拌操作的协议序列
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为设备和容器
|
||||
stir_time: 搅拌时间 (秒)
|
||||
stir_speed: 搅拌速度 (rpm)
|
||||
settling_time: 沉降时间 (秒)
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 搅拌操作的动作序列
|
||||
|
||||
Raises:
|
||||
ValueError: 当找不到搅拌设备时抛出异常
|
||||
|
||||
Examples:
|
||||
stir_protocol = generate_stir_protocol(G, 300.0, 500.0, 60.0)
|
||||
生成搅拌操作的协议序列 - 定时搅拌 + 沉降
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
print(f"STIR: 开始生成搅拌协议")
|
||||
print(f" - 搅拌时间: {stir_time}秒")
|
||||
print(f" - 搅拌速度: {stir_speed} RPM")
|
||||
print(f" - 沉降时间: {settling_time}秒")
|
||||
|
||||
# 查找搅拌设备
|
||||
stirrer_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_stirrer']
|
||||
|
||||
if not stirrer_nodes:
|
||||
raise ValueError("没有找到可用的搅拌设备")
|
||||
|
||||
# 使用第一个可用的搅拌器
|
||||
stirrer_id = stirrer_nodes[0]
|
||||
try:
|
||||
stirrer_id = find_connected_stirrer(G)
|
||||
print(f"STIR: 找到搅拌设备: {stirrer_id}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到搅拌设备: {str(e)}")
|
||||
|
||||
# 执行搅拌操作
|
||||
action_sequence.append({
|
||||
stir_action = {
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "stir",
|
||||
"action_kwargs": {
|
||||
@@ -46,8 +55,11 @@ def generate_stir_protocol(
|
||||
"stir_speed": stir_speed,
|
||||
"settling_time": settling_time
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
action_sequence.append(stir_action)
|
||||
|
||||
print(f"STIR: 生成了 {len(action_sequence)} 个动作")
|
||||
return action_sequence
|
||||
|
||||
|
||||
@@ -58,33 +70,28 @@ def generate_start_stir_protocol(
|
||||
purpose: str
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成开始搅拌操作的协议序列
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为设备和容器
|
||||
vessel: 搅拌容器
|
||||
stir_speed: 搅拌速度 (rpm)
|
||||
purpose: 搅拌目的
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 开始搅拌操作的动作序列
|
||||
生成开始搅拌操作的协议序列 - 持续搅拌
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 查找搅拌设备
|
||||
stirrer_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_stirrer']
|
||||
print(f"START_STIR: 开始生成启动搅拌协议")
|
||||
print(f" - 容器: {vessel}")
|
||||
print(f" - 搅拌速度: {stir_speed} RPM")
|
||||
print(f" - 目的: {purpose}")
|
||||
|
||||
if not stirrer_nodes:
|
||||
raise ValueError("没有找到可用的搅拌设备")
|
||||
|
||||
stirrer_id = stirrer_nodes[0]
|
||||
|
||||
# 验证容器是否存在
|
||||
# 验证容器存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
action_sequence.append({
|
||||
# 查找搅拌设备
|
||||
try:
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
print(f"START_STIR: 找到搅拌设备: {stirrer_id}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到搅拌设备: {str(e)}")
|
||||
|
||||
# 执行开始搅拌操作
|
||||
start_stir_action = {
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
@@ -92,8 +99,11 @@ def generate_start_stir_protocol(
|
||||
"stir_speed": stir_speed,
|
||||
"purpose": purpose
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
action_sequence.append(start_stir_action)
|
||||
|
||||
print(f"START_STIR: 生成了 {len(action_sequence)} 个动作")
|
||||
return action_sequence
|
||||
|
||||
|
||||
@@ -103,35 +113,54 @@ def generate_stop_stir_protocol(
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成停止搅拌操作的协议序列
|
||||
|
||||
Args:
|
||||
G: 有向图,节点为设备和容器
|
||||
vessel: 搅拌容器
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 停止搅拌操作的动作序列
|
||||
"""
|
||||
action_sequence = []
|
||||
|
||||
# 查找搅拌设备
|
||||
stirrer_nodes = [node for node in G.nodes()
|
||||
if G.nodes[node].get('class') == 'virtual_stirrer']
|
||||
print(f"STOP_STIR: 开始生成停止搅拌协议")
|
||||
print(f" - 容器: {vessel}")
|
||||
|
||||
if not stirrer_nodes:
|
||||
raise ValueError("没有找到可用的搅拌设备")
|
||||
|
||||
stirrer_id = stirrer_nodes[0]
|
||||
|
||||
# 验证容器是否存在
|
||||
# 验证容器存在
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"容器 {vessel} 不存在于图中")
|
||||
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
action_sequence.append({
|
||||
# 查找搅拌设备
|
||||
try:
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
print(f"STOP_STIR: 找到搅拌设备: {stirrer_id}")
|
||||
except ValueError as e:
|
||||
raise ValueError(f"无法找到搅拌设备: {str(e)}")
|
||||
|
||||
# 执行停止搅拌操作
|
||||
stop_stir_action = {
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "stop_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return action_sequence
|
||||
action_sequence.append(stop_stir_action)
|
||||
|
||||
print(f"STOP_STIR: 生成了 {len(action_sequence)} 个动作")
|
||||
return action_sequence
|
||||
|
||||
|
||||
# 便捷函数
|
||||
def generate_fast_stir_protocol(
|
||||
G: nx.DiGraph,
|
||||
time: float = 300.0,
|
||||
speed: float = 800.0,
|
||||
settling: float = 60.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""快速搅拌的便捷函数"""
|
||||
return generate_stir_protocol(G, time, speed, settling)
|
||||
|
||||
|
||||
def generate_gentle_stir_protocol(
|
||||
G: nx.DiGraph,
|
||||
time: float = 600.0,
|
||||
speed: float = 200.0,
|
||||
settling: float = 120.0
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""温和搅拌的便捷函数"""
|
||||
return generate_stir_protocol(G, time, speed, settling)
|
||||
@@ -1,158 +1,213 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
import time as time_module
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
|
||||
class VirtualCentrifuge:
|
||||
"""Virtual centrifuge device for CentrifugeProtocol testing"""
|
||||
|
||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
||||
"""Virtual centrifuge device - 简化版,只保留核心功能"""
|
||||
|
||||
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||
# 处理可能的不同调用方式
|
||||
if device_id is None and 'id' in kwargs:
|
||||
device_id = kwargs.pop('id')
|
||||
if config is None and 'config' in kwargs:
|
||||
config = kwargs.pop('config')
|
||||
|
||||
if device_id is None and "id" in kwargs:
|
||||
device_id = kwargs.pop("id")
|
||||
if config is None and "config" in kwargs:
|
||||
config = kwargs.pop("config")
|
||||
|
||||
# 设置默认值
|
||||
self.device_id = device_id or "unknown_centrifuge"
|
||||
self.config = config or {}
|
||||
|
||||
|
||||
self.logger = logging.getLogger(f"VirtualCentrifuge.{self.device_id}")
|
||||
self.data = {}
|
||||
|
||||
# 添加调试信息
|
||||
print(f"=== VirtualCentrifuge {self.device_id} is being created! ===")
|
||||
print(f"=== Config: {self.config} ===")
|
||||
print(f"=== Kwargs: {kwargs} ===")
|
||||
|
||||
|
||||
# 从config或kwargs中获取配置参数
|
||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||
self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 15000.0)
|
||||
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 40.0)
|
||||
self._min_temp = self.config.get('min_temp') or kwargs.get('min_temp', 4.0)
|
||||
|
||||
# 处理其他kwargs参数,但跳过已知的配置参数
|
||||
skip_keys = {'port', 'max_speed', 'max_temp', 'min_temp'}
|
||||
self.port = self.config.get("port") or kwargs.get("port", "VIRTUAL")
|
||||
self._max_speed = self.config.get("max_speed") or kwargs.get("max_speed", 15000.0)
|
||||
self._max_temp = self.config.get("max_temp") or kwargs.get("max_temp", 40.0)
|
||||
self._min_temp = self.config.get("min_temp") or kwargs.get("min_temp", 4.0)
|
||||
|
||||
# 处理其他kwargs参数
|
||||
skip_keys = {"port", "max_speed", "max_temp", "min_temp"}
|
||||
for key, value in kwargs.items():
|
||||
if key not in skip_keys and not hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual centrifuge"""
|
||||
print(f"=== VirtualCentrifuge {self.device_id} initialize() called! ===")
|
||||
self.logger.info(f"Initializing virtual centrifuge {self.device_id}")
|
||||
|
||||
# 只保留核心状态
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"centrifuge_state": "Stopped", # Stopped, Running, Completed, Error
|
||||
"current_speed": 0.0,
|
||||
"target_speed": 0.0,
|
||||
"current_temp": 25.0,
|
||||
"target_temp": 25.0,
|
||||
"max_speed": self._max_speed,
|
||||
"max_temp": self._max_temp,
|
||||
"min_temp": self._min_temp,
|
||||
"centrifuge_state": "Stopped",
|
||||
"time_remaining": 0.0,
|
||||
"progress": 0.0,
|
||||
"message": ""
|
||||
"message": "Ready for centrifugation"
|
||||
})
|
||||
return True
|
||||
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""Cleanup virtual centrifuge"""
|
||||
self.logger.info(f"Cleaning up virtual centrifuge {self.device_id}")
|
||||
|
||||
self.data.update({
|
||||
"status": "Offline",
|
||||
"centrifuge_state": "Offline",
|
||||
"current_speed": 0.0,
|
||||
"current_temp": 25.0,
|
||||
"message": "System offline"
|
||||
})
|
||||
return True
|
||||
|
||||
async def centrifuge(self, vessel: str, speed: float, time: float, temp: float = 25.0) -> bool:
|
||||
"""Execute centrifuge action - matches Centrifuge action"""
|
||||
self.logger.info(f"Centrifuge: vessel={vessel}, speed={speed} RPM, time={time}s, temp={temp}°C")
|
||||
|
||||
|
||||
async def centrifuge(
|
||||
self,
|
||||
vessel: str,
|
||||
speed: float,
|
||||
time: float,
|
||||
temp: float = 25.0
|
||||
) -> bool:
|
||||
"""Execute centrifuge action - 简化的离心流程"""
|
||||
self.logger.info(f"Centrifuge: vessel={vessel}, speed={speed} rpm, time={time}s, temp={temp}°C")
|
||||
|
||||
# 验证参数
|
||||
if speed > self._max_speed:
|
||||
self.logger.error(f"Speed {speed} exceeds maximum {self._max_speed}")
|
||||
self.data["message"] = f"速度 {speed} 超过最大值 {self._max_speed}"
|
||||
if speed > self._max_speed or speed < 100.0:
|
||||
error_msg = f"离心速度 {speed} rpm 超出范围 (100-{self._max_speed} rpm)"
|
||||
self.logger.error(error_msg)
|
||||
self.data.update({
|
||||
"status": f"Error: {error_msg}",
|
||||
"centrifuge_state": "Error",
|
||||
"message": error_msg
|
||||
})
|
||||
return False
|
||||
|
||||
|
||||
if temp > self._max_temp or temp < self._min_temp:
|
||||
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
|
||||
self.data["message"] = f"温度 {temp} 超出范围 {self._min_temp}-{self._max_temp}"
|
||||
error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}-{self._max_temp}°C)"
|
||||
self.logger.error(error_msg)
|
||||
self.data.update({
|
||||
"status": f"Error: {error_msg}",
|
||||
"centrifuge_state": "Error",
|
||||
"message": error_msg
|
||||
})
|
||||
return False
|
||||
|
||||
|
||||
# 开始离心
|
||||
self.data.update({
|
||||
"status": "Running",
|
||||
"centrifuge_state": "Centrifuging",
|
||||
"target_speed": speed,
|
||||
"status": f"离心中: {vessel}",
|
||||
"centrifuge_state": "Running",
|
||||
"current_speed": speed,
|
||||
"target_temp": temp,
|
||||
"target_speed": speed,
|
||||
"current_temp": temp,
|
||||
"target_temp": temp,
|
||||
"time_remaining": time,
|
||||
"vessel": vessel,
|
||||
"progress": 0.0,
|
||||
"message": f"离心中: {vessel} at {speed} RPM"
|
||||
"message": f"Centrifuging {vessel} at {speed} rpm, {temp}°C"
|
||||
})
|
||||
|
||||
# 模拟离心过程
|
||||
simulation_time = min(time, 5.0) # 最多等待5秒用于测试
|
||||
await asyncio.sleep(simulation_time)
|
||||
|
||||
# 离心完成
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"centrifuge_state": "Stopped",
|
||||
"current_speed": 0.0,
|
||||
"target_speed": 0.0,
|
||||
"time_remaining": 0.0,
|
||||
"progress": 100.0,
|
||||
"message": f"离心完成: {vessel}"
|
||||
})
|
||||
|
||||
self.logger.info(f"Centrifuge completed for vessel {vessel}")
|
||||
return True
|
||||
|
||||
# 状态属性
|
||||
|
||||
try:
|
||||
# 离心过程 - 实时更新进度
|
||||
start_time = time_module.time()
|
||||
total_time = time
|
||||
|
||||
while True:
|
||||
current_time = time_module.time()
|
||||
elapsed = current_time - start_time
|
||||
remaining = max(0, total_time - elapsed)
|
||||
progress = min(100.0, (elapsed / total_time) * 100)
|
||||
|
||||
# 更新状态
|
||||
self.data.update({
|
||||
"time_remaining": remaining,
|
||||
"progress": progress,
|
||||
"status": f"离心中: {vessel} | {speed} rpm | {temp}°C | {progress:.1f}% | 剩余: {remaining:.0f}s",
|
||||
"message": f"Centrifuging: {progress:.1f}% complete, {remaining:.0f}s remaining"
|
||||
})
|
||||
|
||||
# 时间到了,退出循环
|
||||
if remaining <= 0:
|
||||
break
|
||||
|
||||
# 每秒更新一次
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
# 离心完成
|
||||
self.data.update({
|
||||
"status": f"离心完成: {vessel} | {speed} rpm | {time}s",
|
||||
"centrifuge_state": "Completed",
|
||||
"progress": 100.0,
|
||||
"time_remaining": 0.0,
|
||||
"current_speed": 0.0, # 停止旋转
|
||||
"current_temp": 25.0, # 恢复室温
|
||||
"message": f"Centrifugation completed: {vessel} at {speed} rpm for {time}s"
|
||||
})
|
||||
|
||||
self.logger.info(f"Centrifugation completed: {vessel} at {speed} rpm for {time}s")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
# 出错处理
|
||||
self.logger.error(f"Error during centrifugation: {str(e)}")
|
||||
|
||||
self.data.update({
|
||||
"status": f"离心错误: {str(e)}",
|
||||
"centrifuge_state": "Error",
|
||||
"current_speed": 0.0,
|
||||
"current_temp": 25.0,
|
||||
"progress": 0.0,
|
||||
"time_remaining": 0.0,
|
||||
"message": f"Centrifugation failed: {str(e)}"
|
||||
})
|
||||
return False
|
||||
|
||||
# === 核心状态属性 ===
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self.data.get("status", "Unknown")
|
||||
|
||||
@property
|
||||
def current_speed(self) -> float:
|
||||
return self.data.get("current_speed", 0.0)
|
||||
|
||||
@property
|
||||
def target_speed(self) -> float:
|
||||
return self.data.get("target_speed", 0.0)
|
||||
|
||||
@property
|
||||
def current_temp(self) -> float:
|
||||
return self.data.get("current_temp", 25.0)
|
||||
|
||||
@property
|
||||
def target_temp(self) -> float:
|
||||
return self.data.get("target_temp", 25.0)
|
||||
|
||||
@property
|
||||
def max_speed(self) -> float:
|
||||
return self.data.get("max_speed", self._max_speed)
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
return self.data.get("max_temp", self._max_temp)
|
||||
|
||||
@property
|
||||
def min_temp(self) -> float:
|
||||
return self.data.get("min_temp", self._min_temp)
|
||||
|
||||
|
||||
@property
|
||||
def centrifuge_state(self) -> str:
|
||||
return self.data.get("centrifuge_state", "Unknown")
|
||||
|
||||
|
||||
@property
|
||||
def current_speed(self) -> float:
|
||||
return self.data.get("current_speed", 0.0)
|
||||
|
||||
@property
|
||||
def target_speed(self) -> float:
|
||||
return self.data.get("target_speed", 0.0)
|
||||
|
||||
@property
|
||||
def current_temp(self) -> float:
|
||||
return self.data.get("current_temp", 25.0)
|
||||
|
||||
@property
|
||||
def target_temp(self) -> float:
|
||||
return self.data.get("target_temp", 25.0)
|
||||
|
||||
@property
|
||||
def max_speed(self) -> float:
|
||||
return self._max_speed
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
return self._max_temp
|
||||
|
||||
@property
|
||||
def min_temp(self) -> float:
|
||||
return self._min_temp
|
||||
|
||||
@property
|
||||
def time_remaining(self) -> float:
|
||||
return self.data.get("time_remaining", 0.0)
|
||||
|
||||
|
||||
@property
|
||||
def progress(self) -> float:
|
||||
return self.data.get("progress", 0.0)
|
||||
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return self.data.get("message", "")
|
||||
@@ -1,151 +1,221 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, Any
|
||||
import time as time_module
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
|
||||
class VirtualFilter:
|
||||
"""Virtual filter device for FilterProtocol testing"""
|
||||
"""Virtual filter device - 完全按照 Filter.action 规范"""
|
||||
|
||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
||||
# 处理可能的不同调用方式
|
||||
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||
if device_id is None and 'id' in kwargs:
|
||||
device_id = kwargs.pop('id')
|
||||
if config is None and 'config' in kwargs:
|
||||
config = kwargs.pop('config')
|
||||
|
||||
# 设置默认值
|
||||
self.device_id = device_id or "unknown_filter"
|
||||
self.config = config or {}
|
||||
|
||||
self.logger = logging.getLogger(f"VirtualFilter.{self.device_id}")
|
||||
self.data = {}
|
||||
|
||||
# 添加调试信息
|
||||
print(f"=== VirtualFilter {self.device_id} is being created! ===")
|
||||
print(f"=== Config: {self.config} ===")
|
||||
print(f"=== Kwargs: {kwargs} ===")
|
||||
|
||||
# 从config或kwargs中获取配置参数
|
||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0)
|
||||
self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
|
||||
self._max_volume = self.config.get('max_volume') or kwargs.get('max_volume', 500.0)
|
||||
|
||||
# 处理其他kwargs参数,但跳过已知的配置参数
|
||||
skip_keys = {'port', 'max_temp', 'max_stir_speed'}
|
||||
# 处理其他kwargs参数
|
||||
skip_keys = {'port', 'max_temp', 'max_stir_speed', 'max_volume'}
|
||||
for key, value in kwargs.items():
|
||||
if key not in skip_keys and not hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual filter"""
|
||||
print(f"=== VirtualFilter {self.device_id} initialize() called! ===")
|
||||
self.logger.info(f"Initializing virtual filter {self.device_id}")
|
||||
|
||||
# 按照 Filter.action 的 feedback 字段初始化
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"filter_state": "Ready",
|
||||
"current_temp": 25.0,
|
||||
"target_temp": 25.0,
|
||||
"max_temp": self._max_temp,
|
||||
"stir_speed": 0.0,
|
||||
"max_stir_speed": self._max_stir_speed,
|
||||
"filtered_volume": 0.0,
|
||||
"progress": 0.0,
|
||||
"message": ""
|
||||
"progress": 0.0, # Filter.action feedback
|
||||
"current_temp": 25.0, # Filter.action feedback
|
||||
"filtered_volume": 0.0, # Filter.action feedback
|
||||
"current_status": "Ready for filtration", # Filter.action feedback
|
||||
"message": "Ready for filtration"
|
||||
})
|
||||
return True
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""Cleanup virtual filter"""
|
||||
self.logger.info(f"Cleaning up virtual filter {self.device_id}")
|
||||
|
||||
self.data.update({
|
||||
"status": "Offline",
|
||||
"current_status": "System offline",
|
||||
"message": "System offline"
|
||||
})
|
||||
return True
|
||||
|
||||
async def filter_sample(self, vessel: str, filtrate_vessel: str = "", stir: bool = False,
|
||||
stir_speed: float = 300.0, temp: float = 25.0,
|
||||
continue_heatchill: bool = False, volume: float = 0.0) -> bool:
|
||||
"""Execute filter action - matches Filter action"""
|
||||
self.logger.info(f"Filter: vessel={vessel}, filtrate_vessel={filtrate_vessel}, stir={stir}, volume={volume}")
|
||||
async def filter(
|
||||
self,
|
||||
vessel: str,
|
||||
filtrate_vessel: str = "",
|
||||
stir: bool = False,
|
||||
stir_speed: float = 300.0,
|
||||
temp: float = 25.0,
|
||||
continue_heatchill: bool = False,
|
||||
volume: float = 0.0
|
||||
) -> bool:
|
||||
"""Execute filter action - 完全按照 Filter.action 参数"""
|
||||
self.logger.info(f"Filter: vessel={vessel}, filtrate_vessel={filtrate_vessel}")
|
||||
self.logger.info(f" stir={stir}, stir_speed={stir_speed}, temp={temp}")
|
||||
self.logger.info(f" continue_heatchill={continue_heatchill}, volume={volume}")
|
||||
|
||||
# 验证参数
|
||||
if temp > self._max_temp:
|
||||
self.logger.error(f"Temperature {temp} exceeds maximum {self._max_temp}")
|
||||
self.data["message"] = f"温度 {temp} 超过最大值 {self._max_temp}"
|
||||
if temp > self._max_temp or temp < 4.0:
|
||||
error_msg = f"温度 {temp}°C 超出范围 (4-{self._max_temp}°C)"
|
||||
self.logger.error(error_msg)
|
||||
self.data.update({
|
||||
"status": f"Error: {error_msg}",
|
||||
"current_status": f"Error: {error_msg}",
|
||||
"message": error_msg
|
||||
})
|
||||
return False
|
||||
|
||||
if stir and stir_speed > self._max_stir_speed:
|
||||
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_stir_speed}")
|
||||
self.data["message"] = f"搅拌速度 {stir_speed} 超过最大值 {self._max_stir_speed}"
|
||||
error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 (0-{self._max_stir_speed} RPM)"
|
||||
self.logger.error(error_msg)
|
||||
self.data.update({
|
||||
"status": f"Error: {error_msg}",
|
||||
"current_status": f"Error: {error_msg}",
|
||||
"message": error_msg
|
||||
})
|
||||
return False
|
||||
|
||||
if volume > self._max_volume:
|
||||
error_msg = f"过滤体积 {volume} mL 超出范围 (0-{self._max_volume} mL)"
|
||||
self.logger.error(error_msg)
|
||||
self.data.update({
|
||||
"status": f"Error: {error_msg}",
|
||||
"current_status": f"Error: {error_msg}",
|
||||
"message": error_msg
|
||||
})
|
||||
return False
|
||||
|
||||
# 开始过滤
|
||||
filter_volume = volume if volume > 0 else 50.0
|
||||
|
||||
self.data.update({
|
||||
"status": "Running",
|
||||
"filter_state": "Filtering",
|
||||
"target_temp": temp,
|
||||
"status": f"过滤中: {vessel}",
|
||||
"current_temp": temp,
|
||||
"stir_speed": stir_speed if stir else 0.0,
|
||||
"vessel": vessel,
|
||||
"filtrate_vessel": filtrate_vessel,
|
||||
"target_volume": volume,
|
||||
"filtered_volume": 0.0,
|
||||
"progress": 0.0,
|
||||
"message": f"过滤中: {vessel}"
|
||||
"current_status": f"Filtering {vessel} → {filtrate_vessel}",
|
||||
"message": f"Starting filtration: {vessel} → {filtrate_vessel}"
|
||||
})
|
||||
|
||||
# 模拟过滤过程
|
||||
simulation_time = min(volume / 10.0 if volume > 0 else 5.0, 10.0)
|
||||
await asyncio.sleep(simulation_time)
|
||||
|
||||
# 过滤完成
|
||||
filtered_vol = volume if volume > 0 else 50.0 # 默认过滤量
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"filter_state": "Ready",
|
||||
"current_temp": 25.0 if not continue_heatchill else temp,
|
||||
"target_temp": 25.0 if not continue_heatchill else temp,
|
||||
"stir_speed": 0.0 if not stir else stir_speed,
|
||||
"filtered_volume": filtered_vol,
|
||||
"progress": 100.0,
|
||||
"message": f"过滤完成: {filtered_vol}mL"
|
||||
})
|
||||
|
||||
self.logger.info(f"Filter completed: {filtered_vol}mL from {vessel}")
|
||||
return True
|
||||
try:
|
||||
# 过滤过程 - 实时更新进度
|
||||
start_time = time_module.time()
|
||||
# 根据体积和搅拌估算过滤时间
|
||||
base_time = filter_volume / 5.0 # 5mL/s 基础速度
|
||||
if stir:
|
||||
base_time *= 0.8 # 搅拌加速过滤
|
||||
if temp > 50.0:
|
||||
base_time *= 0.7 # 高温加速过滤
|
||||
filter_time = max(base_time, 10.0) # 最少10秒
|
||||
|
||||
while True:
|
||||
current_time = time_module.time()
|
||||
elapsed = current_time - start_time
|
||||
remaining = max(0, filter_time - elapsed)
|
||||
progress = min(100.0, (elapsed / filter_time) * 100)
|
||||
current_filtered = (progress / 100.0) * filter_volume
|
||||
|
||||
# 更新状态 - 按照 Filter.action feedback 字段
|
||||
status_msg = f"过滤中: {vessel}"
|
||||
if stir:
|
||||
status_msg += f" | 搅拌: {stir_speed} RPM"
|
||||
status_msg += f" | {temp}°C | {progress:.1f}% | 已过滤: {current_filtered:.1f}mL"
|
||||
|
||||
self.data.update({
|
||||
"progress": progress, # Filter.action feedback
|
||||
"current_temp": temp, # Filter.action feedback
|
||||
"filtered_volume": current_filtered, # Filter.action feedback
|
||||
"current_status": f"Filtering: {progress:.1f}% complete", # Filter.action feedback
|
||||
"status": status_msg,
|
||||
"message": f"Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered"
|
||||
})
|
||||
|
||||
if remaining <= 0:
|
||||
break
|
||||
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
# 过滤完成
|
||||
final_temp = temp if continue_heatchill else 25.0
|
||||
final_status = f"过滤完成: {vessel} | {filter_volume}mL → {filtrate_vessel}"
|
||||
if continue_heatchill:
|
||||
final_status += " | 继续加热搅拌"
|
||||
|
||||
self.data.update({
|
||||
"status": final_status,
|
||||
"progress": 100.0, # Filter.action feedback
|
||||
"current_temp": final_temp, # Filter.action feedback
|
||||
"filtered_volume": filter_volume, # Filter.action feedback
|
||||
"current_status": f"Filtration completed: {filter_volume}mL", # Filter.action feedback
|
||||
"message": f"Filtration completed: {filter_volume}mL filtered from {vessel}"
|
||||
})
|
||||
|
||||
self.logger.info(f"Filtration completed: {filter_volume}mL from {vessel} to {filtrate_vessel}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error during filtration: {str(e)}")
|
||||
self.data.update({
|
||||
"status": f"过滤错误: {str(e)}",
|
||||
"current_status": f"Filtration failed: {str(e)}",
|
||||
"message": f"Filtration failed: {str(e)}"
|
||||
})
|
||||
return False
|
||||
|
||||
# 状态属性
|
||||
# === 核心状态属性 - 按照 Filter.action feedback 字段 ===
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self.data.get("status", "Unknown")
|
||||
|
||||
@property
|
||||
def filter_state(self) -> str:
|
||||
return self.data.get("filter_state", "Unknown")
|
||||
|
||||
@property
|
||||
def current_temp(self) -> float:
|
||||
return self.data.get("current_temp", 25.0)
|
||||
|
||||
@property
|
||||
def target_temp(self) -> float:
|
||||
return self.data.get("target_temp", 25.0)
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
return self.data.get("max_temp", self._max_temp)
|
||||
|
||||
@property
|
||||
def stir_speed(self) -> float:
|
||||
return self.data.get("stir_speed", 0.0)
|
||||
|
||||
@property
|
||||
def max_stir_speed(self) -> float:
|
||||
return self.data.get("max_stir_speed", self._max_stir_speed)
|
||||
|
||||
@property
|
||||
def filtered_volume(self) -> float:
|
||||
return self.data.get("filtered_volume", 0.0)
|
||||
|
||||
@property
|
||||
def progress(self) -> float:
|
||||
"""Filter.action feedback 字段"""
|
||||
return self.data.get("progress", 0.0)
|
||||
|
||||
@property
|
||||
def current_temp(self) -> float:
|
||||
"""Filter.action feedback 字段"""
|
||||
return self.data.get("current_temp", 25.0)
|
||||
|
||||
@property
|
||||
def filtered_volume(self) -> float:
|
||||
"""Filter.action feedback 字段"""
|
||||
return self.data.get("filtered_volume", 0.0)
|
||||
|
||||
@property
|
||||
def current_status(self) -> str:
|
||||
"""Filter.action feedback 字段"""
|
||||
return self.data.get("current_status", "")
|
||||
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return self.data.get("message", "")
|
||||
return self.data.get("message", "")
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
return self._max_temp
|
||||
|
||||
@property
|
||||
def max_stir_speed(self) -> float:
|
||||
return self._max_stir_speed
|
||||
|
||||
@property
|
||||
def max_volume(self) -> float:
|
||||
return self._max_volume
|
||||
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import time as time_module # 重命名time模块,避免与参数冲突
|
||||
from typing import Dict, Any
|
||||
|
||||
class VirtualHeatChill:
|
||||
@@ -19,18 +20,13 @@ class VirtualHeatChill:
|
||||
self.logger = logging.getLogger(f"VirtualHeatChill.{self.device_id}")
|
||||
self.data = {}
|
||||
|
||||
# 添加调试信息
|
||||
print(f"=== VirtualHeatChill {self.device_id} is being created! ===")
|
||||
print(f"=== Config: {self.config} ===")
|
||||
print(f"=== Kwargs: {kwargs} ===")
|
||||
|
||||
# 从config或kwargs中获取配置参数
|
||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 200.0)
|
||||
self._min_temp = self.config.get('min_temp') or kwargs.get('min_temp', -80.0)
|
||||
self._max_stir_speed = self.config.get('max_stir_speed') or kwargs.get('max_stir_speed', 1000.0)
|
||||
|
||||
# 处理其他kwargs参数,但跳过已知的配置参数
|
||||
# 处理其他kwargs参数
|
||||
skip_keys = {'port', 'max_temp', 'min_temp', 'max_stir_speed'}
|
||||
for key, value in kwargs.items():
|
||||
if key not in skip_keys and not hasattr(self, key):
|
||||
@@ -38,70 +34,177 @@ class VirtualHeatChill:
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual heat chill"""
|
||||
print(f"=== VirtualHeatChill {self.device_id} initialize() called! ===")
|
||||
self.logger.info(f"Initializing virtual heat chill {self.device_id}")
|
||||
|
||||
# 初始化状态信息
|
||||
self.data.update({
|
||||
"status": "Idle"
|
||||
"status": "Idle",
|
||||
"operation_mode": "Idle",
|
||||
"is_stirring": False,
|
||||
"stir_speed": 0.0,
|
||||
"remaining_time": 0.0,
|
||||
})
|
||||
return True
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""Cleanup virtual heat chill"""
|
||||
self.logger.info(f"Cleaning up virtual heat chill {self.device_id}")
|
||||
self.data.update({
|
||||
"status": "Offline",
|
||||
"operation_mode": "Offline",
|
||||
"is_stirring": False,
|
||||
"stir_speed": 0.0,
|
||||
"remaining_time": 0.0
|
||||
})
|
||||
return True
|
||||
|
||||
async def heat_chill(self, vessel: str, temp: float, time: float, stir: bool,
|
||||
stir_speed: float, purpose: str) -> bool:
|
||||
"""Execute heat chill action - matches HeatChill action exactly"""
|
||||
self.logger.info(f"HeatChill: vessel={vessel}, temp={temp}°C, time={time}s, stir={stir}, stir_speed={stir_speed}, purpose={purpose}")
|
||||
"""Execute heat chill action - 按实际时间运行,实时更新剩余时间"""
|
||||
self.logger.info(f"HeatChill: vessel={vessel}, temp={temp}°C, time={time}s, stir={stir}, stir_speed={stir_speed}")
|
||||
|
||||
# 验证参数
|
||||
if temp > self._max_temp or temp < self._min_temp:
|
||||
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
|
||||
self.data["status"] = f"温度 {temp} 超出范围"
|
||||
error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C)"
|
||||
self.logger.error(error_msg)
|
||||
self.data.update({
|
||||
"status": f"Error: {error_msg}",
|
||||
"operation_mode": "Error"
|
||||
})
|
||||
return False
|
||||
|
||||
if stir and stir_speed > self._max_stir_speed:
|
||||
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_stir_speed}")
|
||||
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
|
||||
error_msg = f"搅拌速度 {stir_speed} RPM 超出最大值 {self._max_stir_speed} RPM"
|
||||
self.logger.error(error_msg)
|
||||
self.data.update({
|
||||
"status": f"Error: {error_msg}",
|
||||
"operation_mode": "Error"
|
||||
})
|
||||
return False
|
||||
|
||||
# 开始加热/冷却
|
||||
# 确定操作模式
|
||||
if temp > 25.0:
|
||||
operation_mode = "Heating"
|
||||
status_action = "加热"
|
||||
elif temp < 25.0:
|
||||
operation_mode = "Cooling"
|
||||
status_action = "冷却"
|
||||
else:
|
||||
operation_mode = "Maintaining"
|
||||
status_action = "保温"
|
||||
|
||||
# **修复**: 使用重命名的time模块
|
||||
start_time = time_module.time()
|
||||
total_time = time
|
||||
|
||||
# 开始操作
|
||||
stir_info = f" | 搅拌: {stir_speed} RPM" if stir else ""
|
||||
self.data.update({
|
||||
"status": f"加热/冷却中: {vessel} 至 {temp}°C"
|
||||
"status": f"运行中: {status_action} {vessel} 至 {temp}°C | 剩余: {total_time:.0f}s{stir_info}",
|
||||
"operation_mode": operation_mode,
|
||||
"is_stirring": stir,
|
||||
"stir_speed": stir_speed if stir else 0.0,
|
||||
"remaining_time": total_time,
|
||||
})
|
||||
|
||||
# 模拟加热/冷却时间
|
||||
simulation_time = min(time, 10.0) # 最多等待10秒用于测试
|
||||
await asyncio.sleep(simulation_time)
|
||||
# **修复**: 在等待过程中每秒更新剩余时间
|
||||
while True:
|
||||
current_time = time_module.time() # 使用重命名的time模块
|
||||
elapsed = current_time - start_time
|
||||
remaining = max(0, total_time - elapsed)
|
||||
|
||||
# 更新剩余时间和状态
|
||||
self.data.update({
|
||||
"remaining_time": remaining,
|
||||
"status": f"运行中: {status_action} {vessel} 至 {temp}°C | 剩余: {remaining:.0f}s{stir_info}"
|
||||
})
|
||||
|
||||
# 如果时间到了,退出循环
|
||||
if remaining <= 0:
|
||||
break
|
||||
|
||||
# 等待1秒后再次检查
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
# 加热/冷却完成
|
||||
self.data["status"] = f"完成: {vessel} 已达到 {temp}°C"
|
||||
# 操作完成
|
||||
final_stir_info = f" | 搅拌: {stir_speed} RPM" if stir else ""
|
||||
self.data.update({
|
||||
"status": f"完成: {vessel} 已达到 {temp}°C | 用时: {total_time:.0f}s{final_stir_info}",
|
||||
"operation_mode": "Completed",
|
||||
"remaining_time": 0.0,
|
||||
"is_stirring": False,
|
||||
"stir_speed": 0.0
|
||||
})
|
||||
|
||||
self.logger.info(f"HeatChill completed for vessel {vessel} at {temp}°C")
|
||||
self.logger.info(f"HeatChill completed for vessel {vessel} at {temp}°C after {total_time}s")
|
||||
return True
|
||||
|
||||
async def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> bool:
|
||||
"""Start heat chill - matches HeatChillStart action exactly"""
|
||||
self.logger.info(f"HeatChillStart: vessel={vessel}, temp={temp}°C, purpose={purpose}")
|
||||
"""Start continuous heat chill"""
|
||||
self.logger.info(f"HeatChillStart: vessel={vessel}, temp={temp}°C")
|
||||
|
||||
# 验证参数
|
||||
if temp > self._max_temp or temp < self._min_temp:
|
||||
self.logger.error(f"Temperature {temp} outside range {self._min_temp}-{self._max_temp}")
|
||||
self.data["status"] = f"温度 {temp} 超出范围"
|
||||
error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C)"
|
||||
self.logger.error(error_msg)
|
||||
self.data.update({
|
||||
"status": f"Error: {error_msg}",
|
||||
"operation_mode": "Error"
|
||||
})
|
||||
return False
|
||||
|
||||
self.data["status"] = f"开始加热/冷却: {vessel} 至 {temp}°C"
|
||||
# 确定操作模式
|
||||
if temp > 25.0:
|
||||
operation_mode = "Heating"
|
||||
status_action = "持续加热"
|
||||
elif temp < 25.0:
|
||||
operation_mode = "Cooling"
|
||||
status_action = "持续冷却"
|
||||
else:
|
||||
operation_mode = "Maintaining"
|
||||
status_action = "恒温保持"
|
||||
|
||||
self.data.update({
|
||||
"status": f"启动: {status_action} {vessel} 至 {temp}°C | 持续运行",
|
||||
"operation_mode": operation_mode,
|
||||
"is_stirring": False,
|
||||
"stir_speed": 0.0,
|
||||
"remaining_time": -1.0, # -1 表示持续运行
|
||||
})
|
||||
|
||||
return True
|
||||
|
||||
async def heat_chill_stop(self, vessel: str) -> bool:
|
||||
"""Stop heat chill - matches HeatChillStop action exactly"""
|
||||
"""Stop heat chill"""
|
||||
self.logger.info(f"HeatChillStop: vessel={vessel}")
|
||||
|
||||
self.data["status"] = f"停止加热/冷却: {vessel}"
|
||||
self.data.update({
|
||||
"status": f"已停止: {vessel} 温控停止",
|
||||
"operation_mode": "Stopped",
|
||||
"is_stirring": False,
|
||||
"stir_speed": 0.0,
|
||||
"remaining_time": 0.0,
|
||||
})
|
||||
|
||||
return True
|
||||
|
||||
# 状态属性 - 只保留 action 中定义的 feedback
|
||||
# 状态属性
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self.data.get("status", "Idle")
|
||||
return self.data.get("status", "Idle")
|
||||
|
||||
@property
|
||||
def operation_mode(self) -> str:
|
||||
return self.data.get("operation_mode", "Idle")
|
||||
|
||||
@property
|
||||
def is_stirring(self) -> bool:
|
||||
return self.data.get("is_stirring", False)
|
||||
|
||||
@property
|
||||
def stir_speed(self) -> float:
|
||||
return self.data.get("stir_speed", 0.0)
|
||||
|
||||
@property
|
||||
def remaining_time(self) -> float:
|
||||
return self.data.get("remaining_time", 0.0)
|
||||
@@ -1,10 +1,11 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import time as time_module
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
|
||||
class VirtualRotavap:
|
||||
"""Virtual rotary evaporator device for EvaporateProtocol testing"""
|
||||
"""Virtual rotary evaporator device - 简化版,只保留核心功能"""
|
||||
|
||||
def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs):
|
||||
# 处理可能的不同调用方式
|
||||
@@ -20,17 +21,12 @@ class VirtualRotavap:
|
||||
self.logger = logging.getLogger(f"VirtualRotavap.{self.device_id}")
|
||||
self.data = {}
|
||||
|
||||
# 添加调试信息
|
||||
print(f"=== VirtualRotavap {self.device_id} is being created! ===")
|
||||
print(f"=== Config: {self.config} ===")
|
||||
print(f"=== Kwargs: {kwargs} ===")
|
||||
|
||||
# 从config或kwargs中获取配置参数
|
||||
self.port = self.config.get("port") or kwargs.get("port", "VIRTUAL")
|
||||
self._max_temp = self.config.get("max_temp") or kwargs.get("max_temp", 180.0)
|
||||
self._max_rotation_speed = self.config.get("max_rotation_speed") or kwargs.get("max_rotation_speed", 280.0)
|
||||
|
||||
# 处理其他kwargs参数,但跳过已知的配置参数
|
||||
# 处理其他kwargs参数
|
||||
skip_keys = {"port", "max_temp", "max_rotation_speed"}
|
||||
for key, value in kwargs.items():
|
||||
if key not in skip_keys and not hasattr(self, key):
|
||||
@@ -38,95 +34,155 @@ class VirtualRotavap:
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual rotary evaporator"""
|
||||
print(f"=== VirtualRotavap {self.device_id} initialize() called! ===")
|
||||
self.logger.info(f"Initializing virtual rotary evaporator {self.device_id}")
|
||||
self.data.update(
|
||||
{
|
||||
"status": "Idle",
|
||||
"rotavap_state": "Ready",
|
||||
"current_temp": 25.0,
|
||||
"target_temp": 25.0,
|
||||
"max_temp": self._max_temp,
|
||||
"rotation_speed": 0.0,
|
||||
"max_rotation_speed": self._max_rotation_speed,
|
||||
"vacuum_pressure": 1.0, # atmospheric pressure
|
||||
"evaporated_volume": 0.0,
|
||||
"progress": 0.0,
|
||||
"message": "",
|
||||
}
|
||||
)
|
||||
|
||||
# 只保留核心状态
|
||||
self.data.update({
|
||||
"status": "Idle",
|
||||
"rotavap_state": "Ready", # Ready, Evaporating, Completed, Error
|
||||
"current_temp": 25.0,
|
||||
"target_temp": 25.0,
|
||||
"rotation_speed": 0.0,
|
||||
"vacuum_pressure": 1.0, # 大气压
|
||||
"evaporated_volume": 0.0,
|
||||
"progress": 0.0,
|
||||
"remaining_time": 0.0,
|
||||
"message": "Ready for evaporation"
|
||||
})
|
||||
return True
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""Cleanup virtual rotary evaporator"""
|
||||
self.logger.info(f"Cleaning up virtual rotary evaporator {self.device_id}")
|
||||
|
||||
self.data.update({
|
||||
"status": "Offline",
|
||||
"rotavap_state": "Offline",
|
||||
"current_temp": 25.0,
|
||||
"rotation_speed": 0.0,
|
||||
"vacuum_pressure": 1.0,
|
||||
"message": "System offline"
|
||||
})
|
||||
return True
|
||||
|
||||
async def evaporate(
|
||||
self, vessel: str, pressure: float = 0.5, temp: float = 60.0, time: float = 300.0, stir_speed: float = 100.0
|
||||
self,
|
||||
vessel: str,
|
||||
pressure: float = 0.1,
|
||||
temp: float = 60.0,
|
||||
time: float = 1800.0, # 30分钟默认
|
||||
stir_speed: float = 100.0
|
||||
) -> bool:
|
||||
"""Execute evaporate action - matches Evaporate action"""
|
||||
self.logger.info(f"Evaporate: vessel={vessel}, pressure={pressure}, temp={temp}, time={time}")
|
||||
"""Execute evaporate action - 简化的蒸发流程"""
|
||||
self.logger.info(f"Evaporate: vessel={vessel}, pressure={pressure} bar, temp={temp}°C, time={time}s, rotation={stir_speed} RPM")
|
||||
|
||||
# 验证参数
|
||||
if temp > self._max_temp:
|
||||
self.logger.error(f"Temperature {temp} exceeds maximum {self._max_temp}")
|
||||
self.data["message"] = f"温度 {temp} 超过最大值 {self._max_temp}"
|
||||
if temp > self._max_temp or temp < 10.0:
|
||||
error_msg = f"温度 {temp}°C 超出范围 (10-{self._max_temp}°C)"
|
||||
self.logger.error(error_msg)
|
||||
self.data.update({
|
||||
"status": f"Error: {error_msg}",
|
||||
"rotavap_state": "Error",
|
||||
"message": error_msg
|
||||
})
|
||||
return False
|
||||
|
||||
if stir_speed > self._max_rotation_speed:
|
||||
self.logger.error(f"Rotation speed {stir_speed} exceeds maximum {self._max_rotation_speed}")
|
||||
self.data["message"] = f"旋转速度 {stir_speed} 超过最大值 {self._max_rotation_speed}"
|
||||
if stir_speed > self._max_rotation_speed or stir_speed < 10.0:
|
||||
error_msg = f"旋转速度 {stir_speed} RPM 超出范围 (10-{self._max_rotation_speed} RPM)"
|
||||
self.logger.error(error_msg)
|
||||
self.data.update({
|
||||
"status": f"Error: {error_msg}",
|
||||
"rotavap_state": "Error",
|
||||
"message": error_msg
|
||||
})
|
||||
return False
|
||||
|
||||
if pressure < 0.01 or pressure > 1.0:
|
||||
self.logger.error(f"Pressure {pressure} bar is out of valid range (0.01-1.0)")
|
||||
self.data["message"] = f"真空度 {pressure} bar 超出有效范围 (0.01-1.0)"
|
||||
error_msg = f"真空度 {pressure} bar 超出范围 (0.01-1.0 bar)"
|
||||
self.logger.error(error_msg)
|
||||
self.data.update({
|
||||
"status": f"Error: {error_msg}",
|
||||
"rotavap_state": "Error",
|
||||
"message": error_msg
|
||||
})
|
||||
return False
|
||||
|
||||
# 开始蒸发
|
||||
self.data.update(
|
||||
{
|
||||
"status": "Running",
|
||||
"rotavap_state": "Evaporating",
|
||||
"target_temp": temp,
|
||||
"current_temp": temp,
|
||||
"rotation_speed": stir_speed,
|
||||
"vacuum_pressure": pressure,
|
||||
"vessel": vessel,
|
||||
"target_time": time,
|
||||
"progress": 0.0,
|
||||
"message": f"正在蒸发: {vessel}",
|
||||
}
|
||||
)
|
||||
self.data.update({
|
||||
"status": f"蒸发中: {vessel}",
|
||||
"rotavap_state": "Evaporating",
|
||||
"current_temp": temp,
|
||||
"target_temp": temp,
|
||||
"rotation_speed": stir_speed,
|
||||
"vacuum_pressure": pressure,
|
||||
"remaining_time": time,
|
||||
"progress": 0.0,
|
||||
"evaporated_volume": 0.0,
|
||||
"message": f"Evaporating {vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM"
|
||||
})
|
||||
|
||||
# 模拟蒸发过程
|
||||
simulation_time = min(time / 60.0, 10.0) # 最多模拟10秒
|
||||
for progress in range(0, 101, 10):
|
||||
await asyncio.sleep(simulation_time / 10)
|
||||
self.data["progress"] = progress
|
||||
self.data["evaporated_volume"] = progress * 0.5 # 假设最多蒸发50mL
|
||||
try:
|
||||
# 蒸发过程 - 实时更新进度
|
||||
start_time = time_module.time()
|
||||
total_time = time
|
||||
|
||||
while True:
|
||||
current_time = time_module.time()
|
||||
elapsed = current_time - start_time
|
||||
remaining = max(0, total_time - elapsed)
|
||||
progress = min(100.0, (elapsed / total_time) * 100)
|
||||
|
||||
# 模拟蒸发体积
|
||||
evaporated_vol = progress * 0.8 # 假设最多蒸发80mL
|
||||
|
||||
# 更新状态
|
||||
self.data.update({
|
||||
"remaining_time": remaining,
|
||||
"progress": progress,
|
||||
"evaporated_volume": evaporated_vol,
|
||||
"status": f"蒸发中: {vessel} | {temp}°C | {pressure} bar | {progress:.1f}% | 剩余: {remaining:.0f}s",
|
||||
"message": f"Evaporating: {progress:.1f}% complete, {remaining:.0f}s remaining"
|
||||
})
|
||||
|
||||
# 时间到了,退出循环
|
||||
if remaining <= 0:
|
||||
break
|
||||
|
||||
# 每秒更新一次
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
# 蒸发完成
|
||||
final_evaporated = 80.0
|
||||
self.data.update({
|
||||
"status": f"蒸发完成: {vessel} | 蒸发量: {final_evaporated:.1f}mL",
|
||||
"rotavap_state": "Completed",
|
||||
"evaporated_volume": final_evaporated,
|
||||
"progress": 100.0,
|
||||
"remaining_time": 0.0,
|
||||
"current_temp": 25.0, # 冷却下来
|
||||
"rotation_speed": 0.0, # 停止旋转
|
||||
"vacuum_pressure": 1.0, # 恢复大气压
|
||||
"message": f"Evaporation completed: {final_evaporated}mL evaporated from {vessel}"
|
||||
})
|
||||
|
||||
# 蒸发完成
|
||||
evaporated_vol = 50.0 # 假设蒸发了50mL
|
||||
self.data.update(
|
||||
{
|
||||
"status": "Idle",
|
||||
"rotavap_state": "Ready",
|
||||
self.logger.info(f"Evaporation completed: {final_evaporated}mL evaporated from {vessel}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
# 出错处理
|
||||
self.logger.error(f"Error during evaporation: {str(e)}")
|
||||
|
||||
self.data.update({
|
||||
"status": f"蒸发错误: {str(e)}",
|
||||
"rotavap_state": "Error",
|
||||
"current_temp": 25.0,
|
||||
"target_temp": 25.0,
|
||||
"rotation_speed": 0.0,
|
||||
"vacuum_pressure": 1.0,
|
||||
"evaporated_volume": evaporated_vol,
|
||||
"progress": 100.0,
|
||||
"message": f"蒸发完成: {evaporated_vol}mL",
|
||||
}
|
||||
)
|
||||
"message": f"Evaporation failed: {str(e)}"
|
||||
})
|
||||
return False
|
||||
|
||||
self.logger.info(f"Evaporation completed: {evaporated_vol}mL from {vessel}")
|
||||
return True
|
||||
|
||||
# 状态属性
|
||||
# === 核心状态属性 ===
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self.data.get("status", "Unknown")
|
||||
@@ -139,22 +195,10 @@ class VirtualRotavap:
|
||||
def current_temp(self) -> float:
|
||||
return self.data.get("current_temp", 25.0)
|
||||
|
||||
@property
|
||||
def target_temp(self) -> float:
|
||||
return self.data.get("target_temp", 25.0)
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
return self.data.get("max_temp", self._max_temp)
|
||||
|
||||
@property
|
||||
def rotation_speed(self) -> float:
|
||||
return self.data.get("rotation_speed", 0.0)
|
||||
|
||||
@property
|
||||
def max_rotation_speed(self) -> float:
|
||||
return self.data.get("max_rotation_speed", self._max_rotation_speed)
|
||||
|
||||
@property
|
||||
def vacuum_pressure(self) -> float:
|
||||
return self.data.get("vacuum_pressure", 1.0)
|
||||
@@ -170,3 +214,15 @@ class VirtualRotavap:
|
||||
@property
|
||||
def message(self) -> str:
|
||||
return self.data.get("message", "")
|
||||
|
||||
@property
|
||||
def max_temp(self) -> float:
|
||||
return self._max_temp
|
||||
|
||||
@property
|
||||
def max_rotation_speed(self) -> float:
|
||||
return self._max_rotation_speed
|
||||
|
||||
@property
|
||||
def remaining_time(self) -> float:
|
||||
return self.data.get("remaining_time", 0.0)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import time
|
||||
import asyncio
|
||||
from typing import Union
|
||||
|
||||
|
||||
@@ -6,16 +7,30 @@ class VirtualSolenoidValve:
|
||||
"""
|
||||
虚拟电磁阀门 - 简单的开关型阀门,只有开启和关闭两个状态
|
||||
"""
|
||||
def __init__(self, port: str = "VIRTUAL", voltage: float = 12.0, response_time: float = 0.1):
|
||||
self.port = port
|
||||
self.voltage = voltage
|
||||
self.response_time = response_time
|
||||
def __init__(self, device_id: str = None, config: dict = None, **kwargs):
|
||||
# 从配置中获取参数,提供默认值
|
||||
if config is None:
|
||||
config = {}
|
||||
|
||||
self.device_id = device_id
|
||||
self.port = config.get("port", "VIRTUAL")
|
||||
self.voltage = config.get("voltage", 12.0)
|
||||
self.response_time = config.get("response_time", 0.1)
|
||||
|
||||
# 状态属性
|
||||
self._status = "Idle"
|
||||
self._valve_state = "Closed" # "Open" or "Closed"
|
||||
self._is_open = False
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""初始化设备"""
|
||||
self._status = "Idle"
|
||||
return True
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""清理资源"""
|
||||
return True
|
||||
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self._status
|
||||
@@ -32,55 +47,62 @@ class VirtualSolenoidValve:
|
||||
"""获取阀门位置状态"""
|
||||
return "OPEN" if self._is_open else "CLOSED"
|
||||
|
||||
def set_valve_position(self, position: Union[str, bool]):
|
||||
async def set_valve_position(self, command: str = None, **kwargs):
|
||||
"""
|
||||
设置阀门位置
|
||||
设置阀门位置 - ROS动作接口
|
||||
|
||||
Args:
|
||||
position: "OPEN"/"CLOSED" 或 True/False
|
||||
command: "OPEN"/"CLOSED" 或其他控制命令
|
||||
"""
|
||||
if command is None:
|
||||
return {"success": False, "message": "Missing command parameter"}
|
||||
|
||||
print(f"SOLENOID_VALVE: {self.device_id} 接收到命令: {command}")
|
||||
|
||||
self._status = "Busy"
|
||||
|
||||
# 模拟阀门响应时间
|
||||
time.sleep(self.response_time)
|
||||
await asyncio.sleep(self.response_time)
|
||||
|
||||
if isinstance(position, str):
|
||||
target_open = position.upper() == "OPEN"
|
||||
elif isinstance(position, bool):
|
||||
target_open = position
|
||||
# 处理不同的命令格式
|
||||
if isinstance(command, str):
|
||||
cmd_upper = command.upper()
|
||||
if cmd_upper in ["OPEN", "ON", "TRUE", "1"]:
|
||||
self._is_open = True
|
||||
self._valve_state = "Open"
|
||||
result_msg = f"Valve {self.device_id} opened"
|
||||
elif cmd_upper in ["CLOSED", "CLOSE", "OFF", "FALSE", "0"]:
|
||||
self._is_open = False
|
||||
self._valve_state = "Closed"
|
||||
result_msg = f"Valve {self.device_id} closed"
|
||||
else:
|
||||
# 可能是端口名称,处理路径设置
|
||||
# 对于简单电磁阀,任何非关闭命令都视为开启
|
||||
self._is_open = True
|
||||
self._valve_state = "Open"
|
||||
result_msg = f"Valve {self.device_id} set to position: {command}"
|
||||
else:
|
||||
self._status = "Error"
|
||||
return "Error: Invalid position"
|
||||
return {"success": False, "message": "Invalid command type"}
|
||||
|
||||
self._is_open = target_open
|
||||
self._valve_state = "Open" if target_open else "Closed"
|
||||
self._status = "Idle"
|
||||
print(f"SOLENOID_VALVE: {result_msg}")
|
||||
|
||||
return f"Valve {'opened' if target_open else 'closed'}"
|
||||
return {
|
||||
"success": True,
|
||||
"message": result_msg,
|
||||
"valve_position": self.get_valve_position()
|
||||
}
|
||||
|
||||
def open(self):
|
||||
"""打开电磁阀"""
|
||||
self._status = "Busy"
|
||||
time.sleep(self.response_time)
|
||||
|
||||
self._is_open = True
|
||||
self._valve_state = "Open"
|
||||
self._status = "Idle"
|
||||
|
||||
return "Valve opened"
|
||||
async def open(self, **kwargs):
|
||||
"""打开电磁阀 - ROS动作接口"""
|
||||
return await self.set_valve_position(command="OPEN")
|
||||
|
||||
def close(self):
|
||||
"""关闭电磁阀"""
|
||||
self._status = "Busy"
|
||||
time.sleep(self.response_time)
|
||||
|
||||
self._is_open = False
|
||||
self._valve_state = "Closed"
|
||||
self._status = "Idle"
|
||||
|
||||
return "Valve closed"
|
||||
async def close(self, **kwargs):
|
||||
"""关闭电磁阀 - ROS动作接口"""
|
||||
return await self.set_valve_position(command="CLOSED")
|
||||
|
||||
def set_state(self, command: Union[bool, str]):
|
||||
async def set_state(self, command: Union[bool, str], **kwargs):
|
||||
"""
|
||||
设置阀门状态 - 兼容 SendCmd 类型
|
||||
|
||||
@@ -88,18 +110,13 @@ class VirtualSolenoidValve:
|
||||
command: True/False 或 "open"/"close"
|
||||
"""
|
||||
if isinstance(command, bool):
|
||||
return self.open() if command else self.close()
|
||||
cmd_str = "OPEN" if command else "CLOSED"
|
||||
elif isinstance(command, str):
|
||||
if command.lower() in ["open", "on", "true", "1"]:
|
||||
return self.open()
|
||||
elif command.lower() in ["close", "closed", "off", "false", "0"]:
|
||||
return self.close()
|
||||
else:
|
||||
self._status = "Error"
|
||||
return "Error: Invalid command"
|
||||
cmd_str = command
|
||||
else:
|
||||
self._status = "Error"
|
||||
return "Error: Invalid command type"
|
||||
return {"success": False, "message": "Invalid command type"}
|
||||
|
||||
return await self.set_valve_position(command=cmd_str)
|
||||
|
||||
def toggle(self):
|
||||
"""切换阀门状态"""
|
||||
@@ -115,6 +132,7 @@ class VirtualSolenoidValve:
|
||||
def get_state(self) -> dict:
|
||||
"""获取阀门完整状态"""
|
||||
return {
|
||||
"device_id": self.device_id,
|
||||
"port": self.port,
|
||||
"voltage": self.voltage,
|
||||
"response_time": self.response_time,
|
||||
@@ -124,28 +142,6 @@ class VirtualSolenoidValve:
|
||||
"position": self.get_valve_position()
|
||||
}
|
||||
|
||||
def reset(self):
|
||||
async def reset(self):
|
||||
"""重置阀门到关闭状态"""
|
||||
return self.close()
|
||||
|
||||
def test_cycle(self, cycles: int = 3, delay: float = 1.0):
|
||||
"""
|
||||
测试阀门开关循环
|
||||
|
||||
Args:
|
||||
cycles: 循环次数
|
||||
delay: 每次开关间隔时间(秒)
|
||||
"""
|
||||
results = []
|
||||
for i in range(cycles):
|
||||
# 打开
|
||||
result_open = self.open()
|
||||
results.append(f"Cycle {i+1} - Open: {result_open}")
|
||||
time.sleep(delay)
|
||||
|
||||
# 关闭
|
||||
result_close = self.close()
|
||||
results.append(f"Cycle {i+1} - Close: {result_close}")
|
||||
time.sleep(delay)
|
||||
|
||||
return results
|
||||
return await self.close()
|
||||
@@ -1,9 +1,10 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import time as time_module
|
||||
from typing import Dict, Any
|
||||
|
||||
class VirtualStirrer:
|
||||
"""Virtual stirrer device for StirProtocol testing"""
|
||||
"""Virtual stirrer device for StirProtocol testing - 功能完整版"""
|
||||
|
||||
def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs):
|
||||
# 处理可能的不同调用方式
|
||||
@@ -19,86 +20,196 @@ class VirtualStirrer:
|
||||
self.logger = logging.getLogger(f"VirtualStirrer.{self.device_id}")
|
||||
self.data = {}
|
||||
|
||||
# 添加调试信息
|
||||
print(f"=== VirtualStirrer {self.device_id} is being created! ===")
|
||||
print(f"=== Config: {self.config} ===")
|
||||
print(f"=== Kwargs: {kwargs} ===")
|
||||
|
||||
# 从config或kwargs中获取配置参数
|
||||
self.port = self.config.get('port') or kwargs.get('port', 'VIRTUAL')
|
||||
self._max_temp = self.config.get('max_temp') or kwargs.get('max_temp', 100.0)
|
||||
self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 1000.0)
|
||||
self._max_speed = self.config.get('max_speed') or kwargs.get('max_speed', 1500.0)
|
||||
self._min_speed = self.config.get('min_speed') or kwargs.get('min_speed', 50.0)
|
||||
|
||||
# 处理其他kwargs参数,但跳过已知的配置参数
|
||||
skip_keys = {'port', 'max_temp', 'max_speed'}
|
||||
# 处理其他kwargs参数
|
||||
skip_keys = {'port', 'max_speed', 'min_speed'}
|
||||
for key, value in kwargs.items():
|
||||
if key not in skip_keys and not hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""Initialize virtual stirrer"""
|
||||
print(f"=== VirtualStirrer {self.device_id} initialize() called! ===")
|
||||
self.logger.info(f"Initializing virtual stirrer {self.device_id}")
|
||||
|
||||
# 初始化状态信息
|
||||
self.data.update({
|
||||
"status": "Idle"
|
||||
"status": "Idle",
|
||||
"operation_mode": "Idle", # 操作模式: Idle, Stirring, Settling, Completed, Error
|
||||
"current_vessel": "", # 当前搅拌的容器
|
||||
"current_speed": 0.0, # 当前搅拌速度
|
||||
"is_stirring": False, # 是否正在搅拌
|
||||
"remaining_time": 0.0, # 剩余时间
|
||||
})
|
||||
return True
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""Cleanup virtual stirrer"""
|
||||
self.logger.info(f"Cleaning up virtual stirrer {self.device_id}")
|
||||
self.data.update({
|
||||
"status": "Offline",
|
||||
"operation_mode": "Offline",
|
||||
"current_vessel": "",
|
||||
"current_speed": 0.0,
|
||||
"is_stirring": False,
|
||||
"remaining_time": 0.0,
|
||||
})
|
||||
return True
|
||||
|
||||
async def stir(self, stir_time: float, stir_speed: float, settling_time: float) -> bool:
|
||||
"""Execute stir action - matches Stir action exactly"""
|
||||
"""Execute stir action - 定时搅拌 + 沉降"""
|
||||
self.logger.info(f"Stir: speed={stir_speed} RPM, time={stir_time}s, settling={settling_time}s")
|
||||
|
||||
# 验证参数
|
||||
if stir_speed > self._max_speed:
|
||||
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_speed}")
|
||||
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
|
||||
if stir_speed > self._max_speed or stir_speed < self._min_speed:
|
||||
error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM)"
|
||||
self.logger.error(error_msg)
|
||||
self.data.update({
|
||||
"status": f"Error: {error_msg}",
|
||||
"operation_mode": "Error"
|
||||
})
|
||||
return False
|
||||
|
||||
# 开始搅拌
|
||||
self.data["status"] = f"搅拌中: {stir_speed} RPM, {stir_time}s"
|
||||
# === 第一阶段:搅拌 ===
|
||||
start_time = time_module.time()
|
||||
total_stir_time = stir_time
|
||||
|
||||
# 模拟搅拌时间
|
||||
simulation_time = min(stir_time, 10.0) # 最多等待10秒用于测试
|
||||
await asyncio.sleep(simulation_time)
|
||||
self.data.update({
|
||||
"status": f"搅拌中: {stir_speed} RPM | 剩余: {total_stir_time:.0f}s",
|
||||
"operation_mode": "Stirring",
|
||||
"current_speed": stir_speed,
|
||||
"is_stirring": True,
|
||||
"remaining_time": total_stir_time,
|
||||
})
|
||||
|
||||
# 搅拌完成,开始沉降
|
||||
# 搅拌过程 - 实时更新剩余时间
|
||||
while True:
|
||||
current_time = time_module.time()
|
||||
elapsed = current_time - start_time
|
||||
remaining = max(0, total_stir_time - elapsed)
|
||||
|
||||
# 更新状态
|
||||
self.data.update({
|
||||
"remaining_time": remaining,
|
||||
"status": f"搅拌中: {stir_speed} RPM | 剩余: {remaining:.0f}s"
|
||||
})
|
||||
|
||||
# 搅拌时间到了
|
||||
if remaining <= 0:
|
||||
break
|
||||
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
# === 第二阶段:沉降(如果需要)===
|
||||
if settling_time > 0:
|
||||
self.data["status"] = f"沉降中: {settling_time}s"
|
||||
settling_simulation = min(settling_time, 5.0) # 最多等待5秒
|
||||
await asyncio.sleep(settling_simulation)
|
||||
start_settling_time = time_module.time()
|
||||
total_settling_time = settling_time
|
||||
|
||||
self.data.update({
|
||||
"status": f"沉降中: 停止搅拌 | 剩余: {total_settling_time:.0f}s",
|
||||
"operation_mode": "Settling",
|
||||
"current_speed": 0.0,
|
||||
"is_stirring": False,
|
||||
"remaining_time": total_settling_time,
|
||||
})
|
||||
|
||||
# 沉降过程 - 实时更新剩余时间
|
||||
while True:
|
||||
current_time = time_module.time()
|
||||
elapsed = current_time - start_settling_time
|
||||
remaining = max(0, total_settling_time - elapsed)
|
||||
|
||||
# 更新状态
|
||||
self.data.update({
|
||||
"remaining_time": remaining,
|
||||
"status": f"沉降中: 停止搅拌 | 剩余: {remaining:.0f}s"
|
||||
})
|
||||
|
||||
# 沉降时间到了
|
||||
if remaining <= 0:
|
||||
break
|
||||
|
||||
await asyncio.sleep(1.0)
|
||||
|
||||
# 操作完成
|
||||
self.data["status"] = "搅拌完成"
|
||||
# === 操作完成 ===
|
||||
settling_info = f" | 沉降: {settling_time:.0f}s" if settling_time > 0 else ""
|
||||
self.data.update({
|
||||
"status": f"完成: 搅拌 {stir_speed} RPM, {stir_time:.0f}s{settling_info}",
|
||||
"operation_mode": "Completed",
|
||||
"current_speed": 0.0,
|
||||
"is_stirring": False,
|
||||
"remaining_time": 0.0,
|
||||
})
|
||||
|
||||
self.logger.info(f"Stir completed: {stir_speed} RPM for {stir_time}s")
|
||||
self.logger.info(f"Stir completed: {stir_speed} RPM for {stir_time}s + settling {settling_time}s")
|
||||
return True
|
||||
|
||||
async def start_stir(self, vessel: str, stir_speed: float, purpose: str) -> bool:
|
||||
"""Start stir action - matches StartStir action exactly"""
|
||||
"""Start stir action - 开始持续搅拌"""
|
||||
self.logger.info(f"StartStir: vessel={vessel}, speed={stir_speed} RPM, purpose={purpose}")
|
||||
|
||||
# 验证参数
|
||||
if stir_speed > self._max_speed:
|
||||
self.logger.error(f"Stir speed {stir_speed} exceeds maximum {self._max_speed}")
|
||||
self.data["status"] = f"搅拌速度 {stir_speed} 超出范围"
|
||||
if stir_speed > self._max_speed or stir_speed < self._min_speed:
|
||||
error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM)"
|
||||
self.logger.error(error_msg)
|
||||
self.data.update({
|
||||
"status": f"Error: {error_msg}",
|
||||
"operation_mode": "Error"
|
||||
})
|
||||
return False
|
||||
|
||||
self.data["status"] = f"开始搅拌: {vessel} at {stir_speed} RPM"
|
||||
self.data.update({
|
||||
"status": f"启动: 持续搅拌 {vessel} at {stir_speed} RPM | {purpose}",
|
||||
"operation_mode": "Stirring",
|
||||
"current_vessel": vessel,
|
||||
"current_speed": stir_speed,
|
||||
"is_stirring": True,
|
||||
"remaining_time": -1.0, # -1 表示持续运行
|
||||
})
|
||||
|
||||
return True
|
||||
|
||||
async def stop_stir(self, vessel: str) -> bool:
|
||||
"""Stop stir action - matches StopStir action exactly"""
|
||||
"""Stop stir action - 停止搅拌"""
|
||||
self.logger.info(f"StopStir: vessel={vessel}")
|
||||
|
||||
self.data["status"] = f"停止搅拌: {vessel}"
|
||||
current_speed = self.data.get("current_speed", 0.0)
|
||||
|
||||
self.data.update({
|
||||
"status": f"已停止: {vessel} 搅拌停止 | 之前速度: {current_speed} RPM",
|
||||
"operation_mode": "Stopped",
|
||||
"current_vessel": "",
|
||||
"current_speed": 0.0,
|
||||
"is_stirring": False,
|
||||
"remaining_time": 0.0,
|
||||
})
|
||||
|
||||
return True
|
||||
|
||||
# 状态属性 - 只保留 action 中定义的 feedback
|
||||
# 状态属性
|
||||
@property
|
||||
def status(self) -> str:
|
||||
return self.data.get("status", "Idle")
|
||||
return self.data.get("status", "Idle")
|
||||
|
||||
@property
|
||||
def operation_mode(self) -> str:
|
||||
return self.data.get("operation_mode", "Idle")
|
||||
|
||||
@property
|
||||
def current_vessel(self) -> str:
|
||||
return self.data.get("current_vessel", "")
|
||||
|
||||
@property
|
||||
def current_speed(self) -> float:
|
||||
return self.data.get("current_speed", 0.0)
|
||||
|
||||
@property
|
||||
def is_stirring(self) -> bool:
|
||||
return self.data.get("is_stirring", False)
|
||||
|
||||
@property
|
||||
def remaining_time(self) -> float:
|
||||
return self.data.get("remaining_time", 0.0)
|
||||
@@ -33,19 +33,19 @@ class CleanProtocol(BaseModel):
|
||||
|
||||
|
||||
class SeparateProtocol(BaseModel):
|
||||
purpose: str # 'wash' or 'extract'. 'wash' means that product phase will not be the added solvent phase, 'extract' means product phase will be the added solvent phase. If no solvent is added just use 'extract'.
|
||||
product_phase: str # 'top' or 'bottom'. Phase that product will be in.
|
||||
from_vessel: str #Contents of from_vessel are transferred to separation_vessel and separation is performed.
|
||||
separation_vessel: str # Vessel in which separation of phases will be carried out.
|
||||
to_vessel: str # Vessel to send product phase to.
|
||||
waste_phase_to_vessel: str # Optional. Vessel to send waste phase to.
|
||||
solvent: str # Optional. Solvent to add to separation vessel after contents of from_vessel has been transferred to create two phases.
|
||||
solvent_volume: float # Optional. Volume of solvent to add.
|
||||
through: str # Optional. Solid chemical to send product phase through on way to to_vessel, e.g. 'celite'.
|
||||
repeats: int # Optional. Number of separations to perform.
|
||||
stir_time: float # Optional. Time stir for after adding solvent, before separation of phases.
|
||||
stir_speed: float # Optional. Speed to stir at after adding solvent, before separation of phases.
|
||||
settling_time: float # Optional. Time
|
||||
purpose: str
|
||||
product_phase: str
|
||||
from_vessel: str
|
||||
separation_vessel: str
|
||||
to_vessel: str
|
||||
waste_phase_to_vessel: str
|
||||
solvent: str
|
||||
solvent_volume: float
|
||||
through: str
|
||||
repeats: int
|
||||
stir_time: float
|
||||
stir_speed: float
|
||||
settling_time: float
|
||||
|
||||
|
||||
class EvaporateProtocol(BaseModel):
|
||||
@@ -67,6 +67,7 @@ class AGVTransferProtocol(BaseModel):
|
||||
to_repo: dict
|
||||
from_repo_position: str
|
||||
to_repo_position: str
|
||||
|
||||
#=============新添加的新的协议================
|
||||
class AddProtocol(BaseModel):
|
||||
vessel: str
|
||||
@@ -84,16 +85,16 @@ class CentrifugeProtocol(BaseModel):
|
||||
vessel: str
|
||||
speed: float
|
||||
time: float
|
||||
temp: float # 移除默认值
|
||||
temp: float
|
||||
|
||||
class FilterProtocol(BaseModel):
|
||||
vessel: str
|
||||
filtrate_vessel: str # 移除默认值
|
||||
stir: bool # 移除默认值
|
||||
stir_speed: float # 移除默认值
|
||||
temp: float # 移除默认值
|
||||
continue_heatchill: bool # 移除默认值
|
||||
volume: float # 移除默认值
|
||||
filtrate_vessel: str
|
||||
stir: bool
|
||||
stir_speed: float
|
||||
temp: float
|
||||
continue_heatchill: bool
|
||||
volume: float
|
||||
|
||||
class HeatChillProtocol(BaseModel):
|
||||
vessel: str
|
||||
@@ -137,45 +138,53 @@ class TransferProtocol(BaseModel):
|
||||
solid: bool = False
|
||||
|
||||
class CleanVesselProtocol(BaseModel):
|
||||
vessel: str # 要清洗的容器名称
|
||||
solvent: str # 用于清洗容器的溶剂名称
|
||||
volume: float # 清洗溶剂的体积,可选参数
|
||||
temp: float # 清洗时的温度,可选参数
|
||||
repeats: int = 1 # 清洗操作的重复次数,默认为 1
|
||||
vessel: str
|
||||
solvent: str
|
||||
volume: float
|
||||
temp: float
|
||||
repeats: int = 1
|
||||
|
||||
class DissolveProtocol(BaseModel):
|
||||
vessel: str # 装有要溶解物质的容器名称
|
||||
solvent: str # 用于溶解物质的溶剂名称
|
||||
volume: float # 溶剂的体积,可选参数
|
||||
amount: str = "" # 要溶解物质的量,可选参数
|
||||
temp: float = 25.0 # 溶解时的温度,可选参数
|
||||
time: float = 0.0 # 溶解的时间,可选参数
|
||||
stir_speed: float = 0.0 # 搅拌速度,可选参数
|
||||
vessel: str
|
||||
solvent: str
|
||||
volume: float
|
||||
amount: str = ""
|
||||
temp: float = 25.0
|
||||
time: float = 0.0
|
||||
stir_speed: float = 0.0
|
||||
|
||||
class FilterThroughProtocol(BaseModel):
|
||||
from_vessel: str # 源容器的名称,即物质起始所在的容器
|
||||
to_vessel: str # 目标容器的名称,物质过滤后要到达的容器
|
||||
filter_through: str # 过滤时所通过的介质,如滤纸、柱子等
|
||||
eluting_solvent: str = "" # 洗脱溶剂的名称,可选参数
|
||||
eluting_volume: float = 0.0 # 洗脱溶剂的体积,可选参数
|
||||
eluting_repeats: int = 0 # 洗脱操作的重复次数,默认为 0
|
||||
residence_time: float = 0.0 # 物质在过滤介质中的停留时间,可选参数
|
||||
from_vessel: str
|
||||
to_vessel: str
|
||||
filter_through: str
|
||||
eluting_solvent: str = ""
|
||||
eluting_volume: float = 0.0
|
||||
eluting_repeats: int = 0
|
||||
residence_time: float = 0.0
|
||||
|
||||
class RunColumnProtocol(BaseModel):
|
||||
from_vessel: str # 源容器的名称,即样品起始所在的容器
|
||||
to_vessel: str # 目标容器的名称,分离后的样品要到达的容器
|
||||
column: str # 所使用的柱子的名称
|
||||
from_vessel: str
|
||||
to_vessel: str
|
||||
column: str
|
||||
|
||||
class WashSolidProtocol(BaseModel):
|
||||
vessel: str # 装有固体物质的容器名称
|
||||
solvent: str # 用于清洗固体的溶剂名称
|
||||
volume: float # 清洗溶剂的体积
|
||||
filtrate_vessel: str = "" # 滤液要收集到的容器名称,可选参数
|
||||
temp: float = 25.0 # 清洗时的温度,可选参数
|
||||
stir: bool = False # 是否在清洗过程中搅拌,默认为 False
|
||||
stir_speed: float = 0.0 # 搅拌速度,可选参数
|
||||
time: float = 0.0 # 清洗的时间,可选参数
|
||||
repeats: int = 1 # 清洗操作的重复次数,默认为 1
|
||||
vessel: str
|
||||
solvent: str
|
||||
volume: float
|
||||
filtrate_vessel: str = ""
|
||||
temp: float = 25.0
|
||||
stir: bool = False
|
||||
stir_speed: float = 0.0
|
||||
time: float = 0.0
|
||||
repeats: int = 1
|
||||
|
||||
__all__ = ["Point3D", "PumpTransferProtocol", "CleanProtocol", "SeparateProtocol", "EvaporateProtocol", "EvacuateAndRefillProtocol", "AGVTransferProtocol", "CentrifugeProtocol", "AddProtocol", "FilterProtocol", "HeatChillProtocol", "HeatChillStartProtocol", "HeatChillStopProtocol", "StirProtocol", "StartStirProtocol", "StopStirProtocol", "TransferProtocol", "CleanVesselProtocol", "DissolveProtocol", "FilterThroughProtocol", "RunColumnProtocol", "WashSolidProtocol"]
|
||||
__all__ = [
|
||||
"Point3D", "PumpTransferProtocol", "CleanProtocol", "SeparateProtocol",
|
||||
"EvaporateProtocol", "EvacuateAndRefillProtocol", "AGVTransferProtocol",
|
||||
"CentrifugeProtocol", "AddProtocol", "FilterProtocol",
|
||||
"HeatChillProtocol", "HeatChillStartProtocol", "HeatChillStopProtocol",
|
||||
"StirProtocol", "StartStirProtocol", "StopStirProtocol",
|
||||
"TransferProtocol", "CleanVesselProtocol", "DissolveProtocol",
|
||||
"FilterThroughProtocol", "RunColumnProtocol", "WashSolidProtocol"
|
||||
]
|
||||
# End Protocols
|
||||
|
||||
@@ -133,6 +133,11 @@ virtual_stirrer:
|
||||
type: python
|
||||
status_types:
|
||||
status: String
|
||||
operation_mode: String # 操作模式
|
||||
current_vessel: String # 当前容器
|
||||
current_speed: Float64 # 当前搅拌速度
|
||||
is_stirring: Bool # 是否搅拌
|
||||
remaining_time: Float64 # 剩余时间
|
||||
action_value_mappings:
|
||||
stir:
|
||||
type: Stir
|
||||
@@ -162,28 +167,28 @@ virtual_stirrer:
|
||||
status: status
|
||||
result:
|
||||
success: success
|
||||
# 虚拟搅拌器节点配置 - 机械连接设备,单一双向连接点
|
||||
# 虚拟搅拌器节点配置 - 机械连接设备,双向连接点用于搅拌容器
|
||||
handles:
|
||||
- handler_key: stirrer
|
||||
label: stirrer
|
||||
data_type: mechanical
|
||||
side: NORTH
|
||||
io_type: source
|
||||
io_type: undirected
|
||||
data_source: handle
|
||||
data_key: vessel
|
||||
description: "搅拌器的机械连接口,直接与反应容器连接提供搅拌功能"
|
||||
description: "搅拌器的机械连接口,容器通过机械连接进行搅拌"
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
port:
|
||||
type: string
|
||||
default: "VIRTUAL"
|
||||
max_temp:
|
||||
type: number
|
||||
default: 100.0
|
||||
max_speed:
|
||||
type: number
|
||||
default: 1000.0
|
||||
default: 1500.0
|
||||
min_speed:
|
||||
type: number
|
||||
default: 50.0
|
||||
additionalProperties: false
|
||||
|
||||
virtual_multiway_valve:
|
||||
@@ -308,17 +313,24 @@ virtual_solenoid_valve:
|
||||
valve_state: String # "open" or "closed"
|
||||
is_open: Bool
|
||||
action_value_mappings:
|
||||
set_valve_position:
|
||||
type: SendCmd
|
||||
goal:
|
||||
command: command # 确保参数名匹配
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
open:
|
||||
type: SendCmd
|
||||
goal:
|
||||
command: "open"
|
||||
command: "OPEN"
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
close:
|
||||
type: SendCmd
|
||||
goal:
|
||||
command: "close"
|
||||
command: "CLOSED"
|
||||
feedback: {}
|
||||
result:
|
||||
success: success
|
||||
@@ -331,22 +343,22 @@ virtual_solenoid_valve:
|
||||
success: success
|
||||
# 电磁阀门节点配置 - 双向流通的开关型阀门,流动方向由泵决定
|
||||
handles:
|
||||
- handler_key: in
|
||||
label: in
|
||||
- handler_key: inlet
|
||||
label: inlet
|
||||
data_type: fluid
|
||||
side: NORTH
|
||||
io_type: target
|
||||
data_source: handle
|
||||
data_key: fluid_port_in
|
||||
description: "电磁阀的双向流体口,开启时允许流体双向通过,关闭时完全阻断"
|
||||
- handler_key: out
|
||||
label: out
|
||||
description: "电磁阀的进液口"
|
||||
- handler_key: outlet
|
||||
label: outlet
|
||||
data_type: fluid
|
||||
side: SOUTH
|
||||
io_type: source
|
||||
data_source: handle
|
||||
data_key: fluid_port_out
|
||||
description: "电磁阀的双向流体口,开启时允许流体双向通过,关闭时完全阻断"
|
||||
description: "电磁阀的出液口"
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
@@ -377,6 +389,8 @@ virtual_centrifuge:
|
||||
min_temp: Float64
|
||||
centrifuge_state: String
|
||||
time_remaining: Float64
|
||||
progress: Float64 # 添加这个状态
|
||||
message: String # 添加这个状态
|
||||
action_value_mappings:
|
||||
centrifuge:
|
||||
type: Centrifuge
|
||||
@@ -422,23 +436,22 @@ virtual_centrifuge:
|
||||
|
||||
virtual_filter:
|
||||
description: Virtual Filter for FilterProtocol Testing
|
||||
icon: Filter.webp
|
||||
#icon: Filter.webp暂时还没有
|
||||
class:
|
||||
module: unilabos.devices.virtual.virtual_filter:VirtualFilter
|
||||
type: python
|
||||
status_types:
|
||||
status: String
|
||||
filter_state: String
|
||||
current_temp: Float64
|
||||
target_temp: Float64
|
||||
max_temp: Float64
|
||||
stir_speed: Float64
|
||||
max_stir_speed: Float64
|
||||
filtered_volume: Float64
|
||||
progress: Float64
|
||||
current_temp: Float64
|
||||
filtered_volume: Float64
|
||||
current_status: String
|
||||
message: String
|
||||
max_temp: Float64
|
||||
max_stir_speed: Float64
|
||||
max_volume: Float64
|
||||
action_value_mappings:
|
||||
filter_sample:
|
||||
filter:
|
||||
type: Filter
|
||||
goal:
|
||||
vessel: vessel
|
||||
@@ -452,36 +465,21 @@ virtual_filter:
|
||||
progress: progress
|
||||
current_temp: current_temp
|
||||
filtered_volume: filtered_volume
|
||||
current_status: status
|
||||
current_status: current_status
|
||||
result:
|
||||
success: success
|
||||
message: message
|
||||
# 虚拟过滤器节点配置 - 分离设备,1个输入(原始样品),2个输出(滤液和滤渣)
|
||||
return_info: message
|
||||
# 过滤器节点配置 - 固液分离设备
|
||||
handles:
|
||||
- handler_key: filterin
|
||||
label: filterin
|
||||
data_type: fluid
|
||||
- handler_key: filter
|
||||
label: filter
|
||||
data_type: transport
|
||||
side: NORTH
|
||||
io_type: target
|
||||
io_type: source
|
||||
data_source: handle
|
||||
data_key: vessel
|
||||
description: "需要过滤的原始样品容器"
|
||||
- handler_key: filtrate_out
|
||||
label: filtrate_out
|
||||
data_type: fluid
|
||||
side: SOUTH
|
||||
io_type: source
|
||||
data_source: executor
|
||||
data_key: filtrate_vessel
|
||||
description: "过滤后的滤液容器"
|
||||
- handler_key: filter-residue-out
|
||||
label: Residue
|
||||
data_type: resource
|
||||
side: WEST
|
||||
io_type: source
|
||||
data_source: executor
|
||||
data_key: residue_vessel
|
||||
description: "过滤后的滤渣(固体残留物)"
|
||||
description: "需要过滤的样品容器"
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
@@ -494,6 +492,9 @@ virtual_filter:
|
||||
max_stir_speed:
|
||||
type: number
|
||||
default: 1000.0
|
||||
max_volume:
|
||||
type: number
|
||||
default: 500.0
|
||||
additionalProperties: false
|
||||
|
||||
virtual_heatchill:
|
||||
@@ -504,6 +505,10 @@ virtual_heatchill:
|
||||
type: python
|
||||
status_types:
|
||||
status: String
|
||||
operation_mode: String # 保留:操作模式
|
||||
is_stirring: Bool # 保留:是否搅拌
|
||||
stir_speed: Float64 # 保留:搅拌速度
|
||||
# remaining_time: Float64 # 保留:剩余时间
|
||||
action_value_mappings:
|
||||
heat_chill:
|
||||
type: HeatChill
|
||||
@@ -536,7 +541,7 @@ virtual_heatchill:
|
||||
status: status
|
||||
result:
|
||||
success: success
|
||||
# 虚拟加热/冷却器节点配置 - 温控设备,单一双向连接点用于放置容器
|
||||
# 虚拟加热/冷却器节点配置
|
||||
handles:
|
||||
- handler_key: heatchill
|
||||
label: heatchill
|
||||
@@ -709,14 +714,14 @@ virtual_rotavap:
|
||||
status: String
|
||||
rotavap_state: String
|
||||
current_temp: Float64
|
||||
target_temp: Float64
|
||||
max_temp: Float64
|
||||
rotation_speed: Float64
|
||||
max_rotation_speed: Float64
|
||||
vacuum_pressure: Float64
|
||||
evaporated_volume: Float64
|
||||
progress: Float64
|
||||
remaining_time: Float64
|
||||
message: String
|
||||
max_temp: Float64
|
||||
max_rotation_speed: Float64
|
||||
action_value_mappings:
|
||||
evaporate:
|
||||
type: Evaporate
|
||||
@@ -730,11 +735,11 @@ virtual_rotavap:
|
||||
progress: progress
|
||||
current_temp: current_temp
|
||||
evaporated_volume: evaporated_volume
|
||||
current_status: status
|
||||
status: status
|
||||
result:
|
||||
success: success
|
||||
message: message
|
||||
# 虚拟旋转蒸发仪节点配置 - 1个双向口(样品进出),1个单向输出口(冷凝溶剂)
|
||||
# 虚拟旋转蒸发仪节点配置 - 1个样品口
|
||||
handles:
|
||||
- handler_key: rotavap-sample
|
||||
label: rotavap-sample
|
||||
@@ -743,15 +748,7 @@ virtual_rotavap:
|
||||
io_type: target
|
||||
data_source: handle
|
||||
data_key: vessel
|
||||
description: "样品的双向连接口,可放入需要蒸发的样品,蒸发完成后取出浓缩物"
|
||||
- handler_key: rotavap-distillate-outlet
|
||||
label: Distillate Outlet
|
||||
data_type: fluid
|
||||
side: WEST
|
||||
io_type: source
|
||||
data_source: executor
|
||||
data_key: distillate_vessel
|
||||
description: "冷凝回收的溶剂单向输出口,连接收集瓶"
|
||||
description: "样品连接口,放入需要蒸发的样品"
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in New Issue
Block a user