mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-06 23:15:10 +00:00
Compare commits
21 Commits
ac294194e6
...
22ed7efa64
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22ed7efa64 | ||
|
|
ed3b22a738 | ||
|
|
540c5e94b7 | ||
|
|
f9aae44174 | ||
|
|
10cb645191 | ||
|
|
4456529cfb | ||
|
|
694a779c66 | ||
|
|
5d214ebcd8 | ||
|
|
0e11dacead | ||
|
|
7b68545db3 | ||
|
|
25960c2ed5 | ||
|
|
72c67ba25c | ||
|
|
cd9e7ef12c | ||
|
|
b85722f44d | ||
|
|
5a2cc2d709 | ||
|
|
644feced55 | ||
|
|
61ee446542 | ||
|
|
18f6685e18 | ||
|
|
e2052d4a2c | ||
|
|
50282664e0 | ||
|
|
ce8667f937 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
||||
configs/
|
||||
temp/
|
||||
## Python
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
|
||||
217
test/experiments/prcxi.json
Normal file
217
test/experiments/prcxi.json
Normal file
@@ -0,0 +1,217 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "PRCXI",
|
||||
"name": "PRCXI",
|
||||
"parent": null,
|
||||
"type": "device",
|
||||
"class": "liquid_handler.prcxi",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"deck": {
|
||||
"_resource_child_name": "deck",
|
||||
"_resource_type": "unilabos.devices.liquid_handling.prcxi.prcxi:PRCXI9300Deck"
|
||||
},
|
||||
"host": "192.168.3.9",
|
||||
"port": 9999,
|
||||
"timeout": 10.0,
|
||||
"setup": false,
|
||||
"debug": true
|
||||
},
|
||||
"data": {},
|
||||
"children": [
|
||||
"deck"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "deck",
|
||||
"name": "deck",
|
||||
"sample_id": null,
|
||||
"children": [
|
||||
"rackT1",
|
||||
"plateT2",
|
||||
"plateT3",
|
||||
"rackT4",
|
||||
"plateT5",
|
||||
"plateT6"
|
||||
],
|
||||
"parent": "PRCXI",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Deck"
|
||||
},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
"id": "rackT1",
|
||||
"name": "rackT1",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "deck",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"size_x": 120.98,
|
||||
"size_y": 82.12,
|
||||
"size_z": 50.3
|
||||
},
|
||||
"data": {
|
||||
"Material": {
|
||||
"uuid": "80652665f6a54402b2408d50b40398df",
|
||||
"Code": "ZX-001-1000",
|
||||
"Name": "1000μL Tip头",
|
||||
"SummaryName": "1000μL Tip头",
|
||||
"PipetteHeight": 100,
|
||||
"materialEnum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "plateT2",
|
||||
"name": "plateT2",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "deck",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"size_x": 120.98,
|
||||
"size_y": 82.12,
|
||||
"size_z": 50.3
|
||||
},
|
||||
"data": {
|
||||
"Material": {
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "plateT3",
|
||||
"name": "plateT3",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "deck",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"size_x": 120.98,
|
||||
"size_y": 82.12,
|
||||
"size_z": 50.3
|
||||
},
|
||||
"data": {
|
||||
"Material": {
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "rackT4",
|
||||
"name": "rackT4",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "deck",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"size_x": 120.98,
|
||||
"size_y": 82.12,
|
||||
"size_z": 50.3
|
||||
},
|
||||
"data": {
|
||||
"Material": {
|
||||
"uuid": "80652665f6a54402b2408d50b40398df",
|
||||
"Code": "ZX-001-1000",
|
||||
"Name": "1000μL Tip头",
|
||||
"SummaryName": "1000μL Tip头",
|
||||
"PipetteHeight": 100,
|
||||
"materialEnum": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "plateT5",
|
||||
"name": "plateT5",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "deck",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"size_x": 120.98,
|
||||
"size_y": 82.12,
|
||||
"size_z": 50.3
|
||||
},
|
||||
"data": {
|
||||
"Material": {
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "plateT6",
|
||||
"name": "plateT6",
|
||||
"sample_id": null,
|
||||
"children": [],
|
||||
"parent": "deck",
|
||||
"type": "device",
|
||||
"class": "",
|
||||
"position": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": {
|
||||
"type": "PRCXI9300Container",
|
||||
"size_x": 120.98,
|
||||
"size_y": 82.12,
|
||||
"size_z": 50.3
|
||||
},
|
||||
"data": {
|
||||
"Material": {
|
||||
"uuid": "57b1e4711e9e4a32b529f3132fc5931f"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
|
||||
import json
|
||||
import traceback
|
||||
import uuid
|
||||
from unilabos.app.model import JobAddReq, JobData
|
||||
from unilabos.ros.nodes.presets.host_node import HostNode
|
||||
from unilabos.utils.type_check import serialize_result_info
|
||||
|
||||
|
||||
def get_resources() -> tuple:
|
||||
@@ -33,5 +35,10 @@ def job_add(req: JobAddReq) -> JobData:
|
||||
if "command" in action_args:
|
||||
action_args = action_args["command"]
|
||||
# print(f"job_add:{req.device_id} {action_name} {action_kwargs}")
|
||||
HostNode.get_instance().send_goal(req.device_id, action_type=action_type, action_name=action_name, action_kwargs=action_args, goal_uuid=req.job_id, server_info=req.server_info)
|
||||
try:
|
||||
HostNode.get_instance().send_goal(req.device_id, action_type=action_type, action_name=action_name, action_kwargs=action_args, goal_uuid=req.job_id, server_info=req.server_info)
|
||||
except Exception as e:
|
||||
for bridge in HostNode.get_instance().bridges:
|
||||
if hasattr(bridge, "publish_job_status"):
|
||||
bridge.publish_job_status({}, req.job_id, "failed", serialize_result_info(traceback.format_exc(), False, {}))
|
||||
return JobData(jobId=req.job_id)
|
||||
|
||||
@@ -163,7 +163,7 @@ class MQTTClient:
|
||||
# status = device_status.get(device_id, {})
|
||||
if self.mqtt_disable:
|
||||
return
|
||||
status = {"data": device_status.get(device_id, {}), "device_id": device_id}
|
||||
status = {"data": device_status.get(device_id, {}), "device_id": device_id, "timestamp": time.time()}
|
||||
address = f"labs/{MQConfig.lab_id}/devices/"
|
||||
self.client.publish(address, json.dumps(status), qos=2)
|
||||
logger.debug(f"Device status published: address: {address}, {status}")
|
||||
|
||||
@@ -22,18 +22,20 @@ def parse_volume_input(volume_input: Union[str, float]) -> float:
|
||||
float: 体积(毫升)
|
||||
"""
|
||||
if isinstance(volume_input, (int, float)):
|
||||
debug_print(f"📏 体积输入为数值: {volume_input}")
|
||||
return float(volume_input)
|
||||
|
||||
if not volume_input or not str(volume_input).strip():
|
||||
debug_print(f"⚠️ 体积输入为空,返回0.0mL")
|
||||
return 0.0
|
||||
|
||||
volume_str = str(volume_input).lower().strip()
|
||||
debug_print(f"解析体积输入: '{volume_str}'")
|
||||
debug_print(f"🔍 解析体积输入: '{volume_str}'")
|
||||
|
||||
# 处理未知体积
|
||||
if volume_str in ['?', 'unknown', 'tbd', 'to be determined']:
|
||||
default_volume = 10.0 # 默认10mL
|
||||
debug_print(f"检测到未知体积,使用默认值: {default_volume}mL")
|
||||
debug_print(f"❓ 检测到未知体积,使用默认值: {default_volume}mL 🎯")
|
||||
return default_volume
|
||||
|
||||
# 移除空格并提取数字和单位
|
||||
@@ -43,7 +45,7 @@ def parse_volume_input(volume_input: Union[str, float]) -> float:
|
||||
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter)?', volume_clean)
|
||||
|
||||
if not match:
|
||||
debug_print(f"⚠️ 无法解析体积: '{volume_str}',使用默认值10mL")
|
||||
debug_print(f"❌ 无法解析体积: '{volume_str}',使用默认值10mL")
|
||||
return 10.0
|
||||
|
||||
value = float(match.group(1))
|
||||
@@ -52,12 +54,14 @@ def parse_volume_input(volume_input: Union[str, float]) -> float:
|
||||
# 转换为毫升
|
||||
if unit in ['l', 'liter']:
|
||||
volume = value * 1000.0 # L -> mL
|
||||
debug_print(f"🔄 体积转换: {value}L → {volume}mL")
|
||||
elif unit in ['μl', 'ul', 'microliter']:
|
||||
volume = value / 1000.0 # μL -> mL
|
||||
debug_print(f"🔄 体积转换: {value}μL → {volume}mL")
|
||||
else: # ml, milliliter 或默认
|
||||
volume = value # 已经是mL
|
||||
debug_print(f"✅ 体积已为mL: {volume}mL")
|
||||
|
||||
debug_print(f"体积转换: {value}{unit} → {volume}mL")
|
||||
return volume
|
||||
|
||||
def parse_mass_input(mass_input: Union[str, float]) -> float:
|
||||
@@ -71,13 +75,15 @@ def parse_mass_input(mass_input: Union[str, float]) -> float:
|
||||
float: 质量(克)
|
||||
"""
|
||||
if isinstance(mass_input, (int, float)):
|
||||
debug_print(f"⚖️ 质量输入为数值: {mass_input}g")
|
||||
return float(mass_input)
|
||||
|
||||
if not mass_input or not str(mass_input).strip():
|
||||
debug_print(f"⚠️ 质量输入为空,返回0.0g")
|
||||
return 0.0
|
||||
|
||||
mass_str = str(mass_input).lower().strip()
|
||||
debug_print(f"解析质量输入: '{mass_str}'")
|
||||
debug_print(f"🔍 解析质量输入: '{mass_str}'")
|
||||
|
||||
# 移除空格并提取数字和单位
|
||||
mass_clean = re.sub(r'\s+', '', mass_str)
|
||||
@@ -86,7 +92,7 @@ def parse_mass_input(mass_input: Union[str, float]) -> float:
|
||||
match = re.match(r'([0-9]*\.?[0-9]+)\s*(g|mg|kg|gram|milligram|kilogram)?', mass_clean)
|
||||
|
||||
if not match:
|
||||
debug_print(f"⚠️ 无法解析质量: '{mass_str}',返回0.0g")
|
||||
debug_print(f"❌ 无法解析质量: '{mass_str}',返回0.0g")
|
||||
return 0.0
|
||||
|
||||
value = float(match.group(1))
|
||||
@@ -95,12 +101,14 @@ def parse_mass_input(mass_input: Union[str, float]) -> float:
|
||||
# 转换为克
|
||||
if unit in ['mg', 'milligram']:
|
||||
mass = value / 1000.0 # mg -> g
|
||||
debug_print(f"🔄 质量转换: {value}mg → {mass}g")
|
||||
elif unit in ['kg', 'kilogram']:
|
||||
mass = value * 1000.0 # kg -> g
|
||||
debug_print(f"🔄 质量转换: {value}kg → {mass}g")
|
||||
else: # g, gram 或默认
|
||||
mass = value # 已经是g
|
||||
debug_print(f"✅ 质量已为g: {mass}g")
|
||||
|
||||
debug_print(f"质量转换: {value}{unit} → {mass}g")
|
||||
return mass
|
||||
|
||||
def parse_time_input(time_input: Union[str, float]) -> float:
|
||||
@@ -114,18 +122,20 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
||||
float: 时间(秒)
|
||||
"""
|
||||
if isinstance(time_input, (int, float)):
|
||||
debug_print(f"⏱️ 时间输入为数值: {time_input}秒")
|
||||
return float(time_input)
|
||||
|
||||
if not time_input or not str(time_input).strip():
|
||||
debug_print(f"⚠️ 时间输入为空,返回0秒")
|
||||
return 0.0
|
||||
|
||||
time_str = str(time_input).lower().strip()
|
||||
debug_print(f"解析时间输入: '{time_str}'")
|
||||
debug_print(f"🔍 解析时间输入: '{time_str}'")
|
||||
|
||||
# 处理未知时间
|
||||
if time_str in ['?', 'unknown', 'tbd']:
|
||||
default_time = 60.0 # 默认1分钟
|
||||
debug_print(f"检测到未知时间,使用默认值: {default_time}s")
|
||||
debug_print(f"❓ 检测到未知时间,使用默认值: {default_time}s (1分钟) ⏰")
|
||||
return default_time
|
||||
|
||||
# 移除空格并提取数字和单位
|
||||
@@ -135,7 +145,7 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
||||
match = re.match(r'([0-9]*\.?[0-9]+)\s*(s|sec|second|min|minute|h|hr|hour|d|day)?', time_clean)
|
||||
|
||||
if not match:
|
||||
debug_print(f"⚠️ 无法解析时间: '{time_str}',返回0s")
|
||||
debug_print(f"❌ 无法解析时间: '{time_str}',返回0s")
|
||||
return 0.0
|
||||
|
||||
value = float(match.group(1))
|
||||
@@ -144,21 +154,25 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
||||
# 转换为秒
|
||||
if unit in ['min', 'minute']:
|
||||
time_sec = value * 60.0 # min -> s
|
||||
debug_print(f"🔄 时间转换: {value}分钟 → {time_sec}秒")
|
||||
elif unit in ['h', 'hr', 'hour']:
|
||||
time_sec = value * 3600.0 # h -> s
|
||||
debug_print(f"🔄 时间转换: {value}小时 → {time_sec}秒")
|
||||
elif unit in ['d', 'day']:
|
||||
time_sec = value * 86400.0 # d -> s
|
||||
debug_print(f"🔄 时间转换: {value}天 → {time_sec}秒")
|
||||
else: # s, sec, second 或默认
|
||||
time_sec = value # 已经是s
|
||||
debug_print(f"✅ 时间已为秒: {time_sec}秒")
|
||||
|
||||
debug_print(f"时间转换: {value}{unit} → {time_sec}s")
|
||||
return time_sec
|
||||
|
||||
def find_reagent_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||
"""增强版试剂容器查找,支持固体和液体"""
|
||||
debug_print(f"查找试剂 '{reagent}' 的容器...")
|
||||
debug_print(f"🔍 开始查找试剂 '{reagent}' 的容器...")
|
||||
|
||||
# 🔧 方法1:直接搜索 data.reagent_name 和 config.reagent
|
||||
debug_print(f"📋 方法1: 搜索reagent字段...")
|
||||
for node in G.nodes():
|
||||
node_data = G.nodes[node].get('data', {})
|
||||
node_type = G.nodes[node].get('type', '')
|
||||
@@ -171,16 +185,17 @@ def find_reagent_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||
|
||||
# 精确匹配
|
||||
if reagent_name == reagent.lower() or config_reagent == reagent.lower():
|
||||
debug_print(f"✅ 通过reagent字段找到容器: {node}")
|
||||
debug_print(f"✅ 通过reagent字段精确匹配到容器: {node} 🎯")
|
||||
return node
|
||||
|
||||
# 模糊匹配
|
||||
if (reagent.lower() in reagent_name and reagent_name) or \
|
||||
(reagent.lower() in config_reagent and config_reagent):
|
||||
debug_print(f"✅ 通过reagent字段模糊匹配到容器: {node}")
|
||||
debug_print(f"✅ 通过reagent字段模糊匹配到容器: {node} 🔍")
|
||||
return node
|
||||
|
||||
# 🔧 方法2:常见的容器命名规则
|
||||
debug_print(f"📋 方法2: 使用命名规则查找...")
|
||||
reagent_clean = reagent.lower().replace(' ', '_').replace('-', '_')
|
||||
possible_names = [
|
||||
reagent_clean,
|
||||
@@ -197,20 +212,23 @@ def find_reagent_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||
f"reagent_bottle_3"
|
||||
]
|
||||
|
||||
debug_print(f"🔍 尝试的容器名称: {possible_names[:5]}... (共{len(possible_names)}个)")
|
||||
|
||||
for name in possible_names:
|
||||
if name in G.nodes():
|
||||
node_type = G.nodes[name].get('type', '')
|
||||
if node_type == 'container':
|
||||
debug_print(f"✅ 通过命名规则找到容器: {name}")
|
||||
debug_print(f"✅ 通过命名规则找到容器: {name} 📝")
|
||||
return name
|
||||
|
||||
# 🔧 方法3:节点名称模糊匹配
|
||||
debug_print(f"📋 方法3: 节点名称模糊匹配...")
|
||||
for node_id in G.nodes():
|
||||
node_data = G.nodes[node_id]
|
||||
if node_data.get('type') == 'container':
|
||||
# 检查节点名称是否包含试剂名称
|
||||
if reagent_clean in node_id.lower():
|
||||
debug_print(f"✅ 通过节点名称模糊匹配到容器: {node_id}")
|
||||
debug_print(f"✅ 通过节点名称模糊匹配到容器: {node_id} 🔍")
|
||||
return node_id
|
||||
|
||||
# 检查液体类型匹配
|
||||
@@ -220,51 +238,77 @@ def find_reagent_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||
if isinstance(liquid, dict):
|
||||
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
|
||||
if liquid_type.lower() == reagent.lower():
|
||||
debug_print(f"✅ 通过液体类型匹配到容器: {node_id}")
|
||||
debug_print(f"✅ 通过液体类型匹配到容器: {node_id} 💧")
|
||||
return node_id
|
||||
|
||||
# 🔧 方法4:使用第一个试剂瓶作为备选
|
||||
debug_print(f"📋 方法4: 查找备选试剂瓶...")
|
||||
for node_id in G.nodes():
|
||||
node_data = G.nodes[node_id]
|
||||
if (node_data.get('type') == 'container' and
|
||||
('reagent' in node_id.lower() or 'bottle' in node_id.lower())):
|
||||
debug_print(f"⚠️ 未找到专用容器,使用备选试剂瓶: {node_id}")
|
||||
debug_print(f"⚠️ 未找到专用容器,使用备选试剂瓶: {node_id} 🔄")
|
||||
return node_id
|
||||
|
||||
debug_print(f"❌ 所有方法都失败了,无法找到容器!")
|
||||
raise ValueError(f"找不到试剂 '{reagent}' 对应的容器")
|
||||
|
||||
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
|
||||
"""查找连接到指定容器的搅拌器"""
|
||||
debug_print(f"🔍 查找连接到容器 '{vessel}' 的搅拌器...")
|
||||
|
||||
stirrer_nodes = []
|
||||
for node in G.nodes():
|
||||
node_class = G.nodes[node].get('class', '').lower()
|
||||
if 'stirrer' in node_class:
|
||||
stirrer_nodes.append(node)
|
||||
debug_print(f"📋 发现搅拌器: {node}")
|
||||
|
||||
debug_print(f"📊 共找到 {len(stirrer_nodes)} 个搅拌器")
|
||||
|
||||
# 查找连接到容器的搅拌器
|
||||
for stirrer in stirrer_nodes:
|
||||
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
|
||||
debug_print(f"找到连接的搅拌器: {stirrer}")
|
||||
debug_print(f"✅ 找到连接的搅拌器: {stirrer} 🔗")
|
||||
return stirrer
|
||||
|
||||
# 返回第一个搅拌器
|
||||
if stirrer_nodes:
|
||||
debug_print(f"使用第一个搅拌器: {stirrer_nodes[0]}")
|
||||
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个: {stirrer_nodes[0]} 🔄")
|
||||
return stirrer_nodes[0]
|
||||
|
||||
debug_print(f"❌ 未找到任何搅拌器")
|
||||
return ""
|
||||
|
||||
def find_solid_dispenser(G: nx.DiGraph) -> str:
|
||||
"""查找固体加样器"""
|
||||
debug_print(f"🔍 查找固体加样器...")
|
||||
|
||||
for node in G.nodes():
|
||||
node_class = G.nodes[node].get('class', '').lower()
|
||||
if 'solid_dispenser' in node_class or 'dispenser' in node_class:
|
||||
debug_print(f"找到固体加样器: {node}")
|
||||
debug_print(f"✅ 找到固体加样器: {node} 🥄")
|
||||
return node
|
||||
|
||||
debug_print("⚠️ 未找到固体加样器")
|
||||
debug_print(f"❌ 未找到固体加样器")
|
||||
return ""
|
||||
|
||||
# 🆕 创建进度日志动作
|
||||
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
|
||||
"""创建一个动作日志"""
|
||||
full_message = f"{emoji} {message}"
|
||||
debug_print(full_message)
|
||||
logger.info(full_message)
|
||||
print(f"[ACTION] {full_message}", flush=True)
|
||||
|
||||
return {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 0.1,
|
||||
"log_message": full_message
|
||||
}
|
||||
}
|
||||
|
||||
def generate_add_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
@@ -301,51 +345,58 @@ def generate_add_protocol(
|
||||
"""
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print("开始生成添加试剂协议")
|
||||
debug_print(f"原始参数:")
|
||||
debug_print(f" - vessel: '{vessel}'")
|
||||
debug_print(f" - reagent: '{reagent}'")
|
||||
debug_print(f" - volume: {volume} (类型: {type(volume)})")
|
||||
debug_print(f" - mass: {mass} (类型: {type(mass)})")
|
||||
debug_print(f" - time: {time} (类型: {type(time)})")
|
||||
debug_print(f" - mol: '{mol}'")
|
||||
debug_print(f" - event: '{event}'")
|
||||
debug_print(f" - rate_spec: '{rate_spec}'")
|
||||
debug_print("🚀 开始生成添加试剂协议")
|
||||
debug_print(f"📋 原始参数:")
|
||||
debug_print(f" 🥼 vessel: '{vessel}'")
|
||||
debug_print(f" 🧪 reagent: '{reagent}'")
|
||||
debug_print(f" 📏 volume: {volume} (类型: {type(volume)})")
|
||||
debug_print(f" ⚖️ mass: {mass} (类型: {type(mass)})")
|
||||
debug_print(f" ⏱️ time: {time} (类型: {type(time)})")
|
||||
debug_print(f" 🧬 mol: '{mol}'")
|
||||
debug_print(f" 🎯 event: '{event}'")
|
||||
debug_print(f" ⚡ rate_spec: '{rate_spec}'")
|
||||
debug_print(f" 🌪️ stir: {stir}")
|
||||
debug_print(f" 🔄 stir_speed: {stir_speed} rpm")
|
||||
debug_print("=" * 60)
|
||||
|
||||
action_sequence = []
|
||||
|
||||
# === 参数验证 ===
|
||||
debug_print("步骤1: 参数验证...")
|
||||
debug_print("🔍 步骤1: 参数验证...")
|
||||
action_sequence.append(create_action_log(f"开始添加试剂 '{reagent}' 到容器 '{vessel}'", "🎬"))
|
||||
|
||||
if not vessel:
|
||||
debug_print("❌ vessel 参数不能为空")
|
||||
raise ValueError("vessel 参数不能为空")
|
||||
if not reagent:
|
||||
debug_print("❌ reagent 参数不能为空")
|
||||
raise ValueError("reagent 参数不能为空")
|
||||
|
||||
if vessel not in G.nodes():
|
||||
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中")
|
||||
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
debug_print("✅ 基本参数验证通过")
|
||||
|
||||
# === 🔧 关键修复:参数解析 ===
|
||||
debug_print("步骤2: 参数解析...")
|
||||
debug_print("🔍 步骤2: 参数解析...")
|
||||
action_sequence.append(create_action_log("正在解析添加参数...", "🔍"))
|
||||
|
||||
# 解析各种参数为数值
|
||||
final_volume = parse_volume_input(volume)
|
||||
final_mass = parse_mass_input(mass)
|
||||
final_time = parse_time_input(time)
|
||||
|
||||
debug_print(f"解析结果:")
|
||||
debug_print(f" - 体积: {final_volume}mL")
|
||||
debug_print(f" - 质量: {final_mass}g")
|
||||
debug_print(f" - 时间: {final_time}s")
|
||||
debug_print(f" - 摩尔: '{mol}'")
|
||||
debug_print(f" - 事件: '{event}'")
|
||||
debug_print(f" - 速率: '{rate_spec}'")
|
||||
debug_print(f"📊 解析结果:")
|
||||
debug_print(f" 📏 体积: {final_volume}mL")
|
||||
debug_print(f" ⚖️ 质量: {final_mass}g")
|
||||
debug_print(f" ⏱️ 时间: {final_time}s")
|
||||
debug_print(f" 🧬 摩尔: '{mol}'")
|
||||
debug_print(f" 🎯 事件: '{event}'")
|
||||
debug_print(f" ⚡ 速率: '{rate_spec}'")
|
||||
|
||||
# === 判断添加类型 ===
|
||||
debug_print("步骤3: 判断添加类型...")
|
||||
debug_print("🔍 步骤3: 判断添加类型...")
|
||||
|
||||
# 🔧 修复:现在使用解析后的数值进行比较
|
||||
is_solid = (final_mass > 0 or (mol and mol.strip() != ""))
|
||||
@@ -357,22 +408,34 @@ def generate_add_protocol(
|
||||
final_volume = 10.0
|
||||
debug_print("⚠️ 未指定体积或质量,默认为10mL液体")
|
||||
|
||||
debug_print(f"添加类型: {'固体' if is_solid else '液体'}")
|
||||
add_type = "固体" if is_solid else "液体"
|
||||
add_emoji = "🧂" if is_solid else "💧"
|
||||
debug_print(f"📋 添加类型: {add_type} {add_emoji}")
|
||||
|
||||
action_sequence.append(create_action_log(f"确定添加类型: {add_type} {add_emoji}", "📋"))
|
||||
|
||||
# === 执行添加流程 ===
|
||||
debug_print("步骤4: 执行添加流程...")
|
||||
debug_print("🔍 步骤4: 执行添加流程...")
|
||||
|
||||
try:
|
||||
if is_solid:
|
||||
# === 固体添加路径 ===
|
||||
debug_print(f"使用固体添加路径")
|
||||
debug_print(f"🧂 使用固体添加路径")
|
||||
action_sequence.append(create_action_log("开始固体试剂添加流程", "🧂"))
|
||||
|
||||
solid_dispenser = find_solid_dispenser(G)
|
||||
if solid_dispenser:
|
||||
action_sequence.append(create_action_log(f"找到固体加样器: {solid_dispenser}", "🥄"))
|
||||
|
||||
# 启动搅拌
|
||||
if stir:
|
||||
debug_print("🌪️ 准备启动搅拌...")
|
||||
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
|
||||
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
if stirrer_id:
|
||||
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed} rpm)", "🔄"))
|
||||
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
@@ -383,6 +446,7 @@ def generate_add_protocol(
|
||||
}
|
||||
})
|
||||
# 等待搅拌稳定
|
||||
action_sequence.append(create_action_log("等待搅拌稳定...", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 3}
|
||||
@@ -399,19 +463,27 @@ def generate_add_protocol(
|
||||
|
||||
if final_mass > 0:
|
||||
add_kwargs["mass"] = str(final_mass)
|
||||
action_sequence.append(create_action_log(f"准备添加固体: {final_mass}g", "⚖️"))
|
||||
if mol and mol.strip():
|
||||
add_kwargs["mol"] = mol
|
||||
action_sequence.append(create_action_log(f"按摩尔数添加: {mol}", "🧬"))
|
||||
if equiv and equiv.strip():
|
||||
add_kwargs["equiv"] = equiv
|
||||
action_sequence.append(create_action_log(f"当量: {equiv}", "🔢"))
|
||||
|
||||
action_sequence.append(create_action_log("开始固体加样操作", "🥄"))
|
||||
action_sequence.append({
|
||||
"device_id": solid_dispenser,
|
||||
"action_name": "add_solid",
|
||||
"action_kwargs": add_kwargs
|
||||
})
|
||||
|
||||
action_sequence.append(create_action_log("固体加样完成", "✅"))
|
||||
|
||||
# 添加后等待
|
||||
if final_time > 0:
|
||||
wait_minutes = final_time / 60
|
||||
action_sequence.append(create_action_log(f"等待反应进行 ({wait_minutes:.1f}分钟)", "⏰"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": final_time}
|
||||
@@ -419,19 +491,28 @@ def generate_add_protocol(
|
||||
|
||||
debug_print(f"✅ 固体添加完成")
|
||||
else:
|
||||
debug_print("⚠️ 未找到固体加样器,跳过固体添加")
|
||||
debug_print("❌ 未找到固体加样器,跳过固体添加")
|
||||
action_sequence.append(create_action_log("未找到固体加样器,无法添加固体", "❌"))
|
||||
|
||||
else:
|
||||
# === 液体添加路径 ===
|
||||
debug_print(f"使用液体添加路径")
|
||||
debug_print(f"💧 使用液体添加路径")
|
||||
action_sequence.append(create_action_log("开始液体试剂添加流程", "💧"))
|
||||
|
||||
# 查找试剂容器
|
||||
action_sequence.append(create_action_log("正在查找试剂容器...", "🔍"))
|
||||
reagent_vessel = find_reagent_vessel(G, reagent)
|
||||
action_sequence.append(create_action_log(f"找到试剂容器: {reagent_vessel}", "🧪"))
|
||||
|
||||
# 启动搅拌
|
||||
if stir:
|
||||
debug_print("🌪️ 准备启动搅拌...")
|
||||
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
|
||||
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
if stirrer_id:
|
||||
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed} rpm)", "🔄"))
|
||||
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
@@ -442,6 +523,7 @@ def generate_add_protocol(
|
||||
}
|
||||
})
|
||||
# 等待搅拌稳定
|
||||
action_sequence.append(create_action_log("等待搅拌稳定...", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 5}
|
||||
@@ -451,18 +533,23 @@ def generate_add_protocol(
|
||||
if final_time > 0:
|
||||
flowrate = final_volume / final_time * 60 # mL/min
|
||||
transfer_flowrate = flowrate
|
||||
debug_print(f"⚡ 根据时间计算流速: {flowrate:.2f} mL/min")
|
||||
else:
|
||||
if rate_spec == "dropwise":
|
||||
flowrate = 0.5 # 滴加,很慢
|
||||
transfer_flowrate = 0.2
|
||||
debug_print(f"💧 滴加模式,流速: {flowrate} mL/min")
|
||||
elif viscous:
|
||||
flowrate = 1.0 # 粘性液体
|
||||
transfer_flowrate = 0.3
|
||||
debug_print(f"🍯 粘性液体,流速: {flowrate} mL/min")
|
||||
else:
|
||||
flowrate = 2.5 # 正常流速
|
||||
transfer_flowrate = 0.5
|
||||
debug_print(f"⚡ 正常流速: {flowrate} mL/min")
|
||||
|
||||
debug_print(f"流速设置: {flowrate} mL/min")
|
||||
action_sequence.append(create_action_log(f"设置流速: {flowrate:.2f} mL/min", "⚡"))
|
||||
action_sequence.append(create_action_log(f"开始转移 {final_volume}mL 液体", "🚰"))
|
||||
|
||||
# 调用pump protocol
|
||||
pump_actions = generate_pump_protocol_with_rinsing(
|
||||
@@ -486,9 +573,11 @@ def generate_add_protocol(
|
||||
)
|
||||
action_sequence.extend(pump_actions)
|
||||
debug_print(f"✅ 液体转移完成,添加了 {len(pump_actions)} 个动作")
|
||||
action_sequence.append(create_action_log(f"液体转移完成 ({len(pump_actions)} 个操作)", "✅"))
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"⚠️ 试剂添加失败: {str(e)}")
|
||||
debug_print(f"❌ 试剂添加失败: {str(e)}")
|
||||
action_sequence.append(create_action_log(f"试剂添加失败: {str(e)}", "❌"))
|
||||
# 添加错误日志
|
||||
action_sequence.append({
|
||||
"device_id": "system",
|
||||
@@ -500,19 +589,28 @@ def generate_add_protocol(
|
||||
|
||||
# === 最终结果 ===
|
||||
debug_print("=" * 60)
|
||||
debug_print(f"✅ 添加试剂协议生成完成")
|
||||
debug_print(f"🎉 添加试剂协议生成完成")
|
||||
debug_print(f"📊 总动作数: {len(action_sequence)}")
|
||||
debug_print(f"📋 处理总结:")
|
||||
debug_print(f" - 试剂: {reagent}")
|
||||
debug_print(f" - 添加类型: {'固体' if is_solid else '液体'}")
|
||||
debug_print(f" - 目标容器: {vessel}")
|
||||
debug_print(f" 🧪 试剂: {reagent}")
|
||||
debug_print(f" {add_emoji} 添加类型: {add_type}")
|
||||
debug_print(f" 🥼 目标容器: {vessel}")
|
||||
if is_liquid:
|
||||
debug_print(f" - 体积: {final_volume}mL")
|
||||
debug_print(f" 📏 体积: {final_volume}mL")
|
||||
if is_solid:
|
||||
debug_print(f" - 质量: {final_mass}g")
|
||||
debug_print(f" - 摩尔: {mol}")
|
||||
debug_print(f" ⚖️ 质量: {final_mass}g")
|
||||
debug_print(f" 🧬 摩尔: {mol}")
|
||||
debug_print("=" * 60)
|
||||
|
||||
# 添加完成日志
|
||||
summary_msg = f"试剂添加协议完成: {reagent} → {vessel}"
|
||||
if is_liquid:
|
||||
summary_msg += f" ({final_volume}mL)"
|
||||
if is_solid:
|
||||
summary_msg += f" ({final_mass}g)"
|
||||
|
||||
action_sequence.append(create_action_log(summary_msg, "🎉"))
|
||||
|
||||
return action_sequence
|
||||
|
||||
# === 便捷函数 ===
|
||||
@@ -520,6 +618,7 @@ def generate_add_protocol(
|
||||
def add_liquid_volume(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[str, float],
|
||||
time: Union[str, float] = 0.0, rate_spec: str = "") -> List[Dict[str, Any]]:
|
||||
"""添加指定体积的液体试剂"""
|
||||
debug_print(f"💧 快速添加液体: {reagent} ({volume}) → {vessel}")
|
||||
return generate_add_protocol(
|
||||
G, vessel, reagent,
|
||||
volume=volume,
|
||||
@@ -530,6 +629,7 @@ def add_liquid_volume(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[st
|
||||
def add_solid_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float],
|
||||
event: str = "") -> List[Dict[str, Any]]:
|
||||
"""添加指定质量的固体试剂"""
|
||||
debug_print(f"🧂 快速添加固体: {reagent} ({mass}) → {vessel}")
|
||||
return generate_add_protocol(
|
||||
G, vessel, reagent,
|
||||
mass=mass,
|
||||
@@ -539,6 +639,7 @@ def add_solid_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, fl
|
||||
def add_solid_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
|
||||
event: str = "") -> List[Dict[str, Any]]:
|
||||
"""按摩尔数添加固体试剂"""
|
||||
debug_print(f"🧬 按摩尔数添加固体: {reagent} ({mol}) → {vessel}")
|
||||
return generate_add_protocol(
|
||||
G, vessel, reagent,
|
||||
mol=mol,
|
||||
@@ -548,6 +649,7 @@ def add_solid_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
|
||||
def add_dropwise_liquid(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[str, float],
|
||||
time: Union[str, float] = "20 min", event: str = "") -> List[Dict[str, Any]]:
|
||||
"""滴加液体试剂"""
|
||||
debug_print(f"💧 滴加液体: {reagent} ({volume}) → {vessel} (用时: {time})")
|
||||
return generate_add_protocol(
|
||||
G, vessel, reagent,
|
||||
volume=volume,
|
||||
@@ -559,6 +661,7 @@ def add_dropwise_liquid(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[
|
||||
def add_portionwise_solid(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float],
|
||||
time: Union[str, float] = "1 h", event: str = "") -> List[Dict[str, Any]]:
|
||||
"""分批添加固体试剂"""
|
||||
debug_print(f"🧂 分批添加固体: {reagent} ({mass}) → {vessel} (用时: {time})")
|
||||
return generate_add_protocol(
|
||||
G, vessel, reagent,
|
||||
mass=mass,
|
||||
@@ -573,22 +676,25 @@ def test_add_protocol():
|
||||
print("=== ADD PROTOCOL 增强版测试 ===")
|
||||
|
||||
# 测试体积解析
|
||||
debug_print("🧪 测试体积解析...")
|
||||
volumes = ["2.7 mL", "2.67 mL", "?", 10.0, "1 L", "500 μL"]
|
||||
for vol in volumes:
|
||||
result = parse_volume_input(vol)
|
||||
print(f"体积解析: {vol} → {result}mL")
|
||||
print(f"📏 体积解析: {vol} → {result}mL")
|
||||
|
||||
# 测试质量解析
|
||||
debug_print("⚖️ 测试质量解析...")
|
||||
masses = ["19.3 g", "4.5 g", 2.5, "500 mg", "1 kg"]
|
||||
for mass in masses:
|
||||
result = parse_mass_input(mass)
|
||||
print(f"质量解析: {mass} → {result}g")
|
||||
print(f"⚖️ 质量解析: {mass} → {result}g")
|
||||
|
||||
# 测试时间解析
|
||||
debug_print("⏱️ 测试时间解析...")
|
||||
times = ["1 h", "20 min", "30 s", 60.0, "?"]
|
||||
for time in times:
|
||||
result = parse_time_input(time)
|
||||
print(f"时间解析: {time} → {result}s")
|
||||
print(f"⏱️ 时间解析: {time} → {result}s")
|
||||
|
||||
print("✅ 测试完成")
|
||||
|
||||
|
||||
@@ -1,7 +1,30 @@
|
||||
import networkx as nx
|
||||
import logging
|
||||
from typing import List, Dict, Any
|
||||
from .pump_protocol import generate_pump_protocol_with_rinsing
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def debug_print(message):
|
||||
"""调试输出"""
|
||||
print(f"[ADJUST_PH] {message}", flush=True)
|
||||
logger.info(f"[ADJUST_PH] {message}")
|
||||
|
||||
# 🆕 创建进度日志动作
|
||||
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
|
||||
"""创建一个动作日志"""
|
||||
full_message = f"{emoji} {message}"
|
||||
debug_print(full_message)
|
||||
logger.info(full_message)
|
||||
print(f"[ACTION] {full_message}", flush=True)
|
||||
|
||||
return {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 0.1,
|
||||
"log_message": full_message
|
||||
}
|
||||
}
|
||||
|
||||
def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||
"""
|
||||
@@ -14,7 +37,7 @@ def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||
Returns:
|
||||
str: 试剂容器ID
|
||||
"""
|
||||
print(f"ADJUST_PH: 正在查找试剂 '{reagent}' 的容器...")
|
||||
debug_print(f"🔍 正在查找试剂 '{reagent}' 的容器...")
|
||||
|
||||
# 常见酸碱试剂的别名映射
|
||||
reagent_aliases = {
|
||||
@@ -29,11 +52,16 @@ def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||
|
||||
# 构建搜索名称列表
|
||||
search_names = [reagent.lower()]
|
||||
debug_print(f"📋 基础搜索名称: {reagent.lower()}")
|
||||
|
||||
# 添加别名
|
||||
for base_name, aliases in reagent_aliases.items():
|
||||
if reagent.lower() in base_name.lower() or base_name.lower() in reagent.lower():
|
||||
search_names.extend([alias.lower() for alias in aliases])
|
||||
debug_print(f"🔗 添加别名: {aliases}")
|
||||
break
|
||||
|
||||
debug_print(f"📝 完整搜索列表: {search_names}")
|
||||
|
||||
# 构建可能的容器名称
|
||||
possible_names = []
|
||||
@@ -49,13 +77,17 @@ def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||
name_clean
|
||||
])
|
||||
|
||||
debug_print(f"🎯 可能的容器名称 (前5个): {possible_names[:5]}... (共{len(possible_names)}个)")
|
||||
|
||||
# 第一步:通过容器名称匹配
|
||||
debug_print(f"📋 方法1: 精确名称匹配...")
|
||||
for vessel_name in possible_names:
|
||||
if vessel_name in G.nodes():
|
||||
print(f"ADJUST_PH: 通过名称匹配找到容器: {vessel_name}")
|
||||
debug_print(f"✅ 通过名称匹配找到容器: {vessel_name} 🎯")
|
||||
return vessel_name
|
||||
|
||||
# 第二步:通过模糊匹配
|
||||
debug_print(f"📋 方法2: 模糊名称匹配...")
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
node_name = G.nodes[node_id].get('name', '').lower()
|
||||
@@ -63,10 +95,11 @@ def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||
# 检查是否包含任何搜索名称
|
||||
for search_name in search_names:
|
||||
if search_name in node_id.lower() or search_name in node_name:
|
||||
print(f"ADJUST_PH: 通过模糊匹配找到容器: {node_id}")
|
||||
debug_print(f"✅ 通过模糊匹配找到容器: {node_id} 🔍")
|
||||
return node_id
|
||||
|
||||
# 第三步:通过液体类型匹配
|
||||
debug_print(f"📋 方法3: 液体类型匹配...")
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
vessel_data = G.nodes[node_id].get('data', {})
|
||||
@@ -79,10 +112,11 @@ def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||
|
||||
for search_name in search_names:
|
||||
if search_name in liquid_type or search_name in reagent_name:
|
||||
print(f"ADJUST_PH: 通过液体类型匹配找到容器: {node_id}")
|
||||
debug_print(f"✅ 通过液体类型匹配找到容器: {node_id} 💧")
|
||||
return node_id
|
||||
|
||||
# 列出可用容器帮助调试
|
||||
debug_print(f"📊 列出可用容器帮助调试...")
|
||||
available_containers = []
|
||||
for node_id in G.nodes():
|
||||
if G.nodes[node_id].get('type') == 'container':
|
||||
@@ -98,67 +132,92 @@ def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
|
||||
'reagent_name': vessel_data.get('reagent_name', '')
|
||||
})
|
||||
|
||||
print(f"ADJUST_PH: 可用容器列表:")
|
||||
debug_print(f"📋 可用容器列表:")
|
||||
for container in available_containers:
|
||||
print(f" - {container['id']}: {container['name']}")
|
||||
print(f" 液体: {container['liquids']}")
|
||||
print(f" 试剂: {container['reagent_name']}")
|
||||
debug_print(f" - 🧪 {container['id']}: {container['name']}")
|
||||
debug_print(f" 💧 液体: {container['liquids']}")
|
||||
debug_print(f" 🏷️ 试剂: {container['reagent_name']}")
|
||||
|
||||
raise ValueError(f"找不到试剂 '{reagent}' 对应的容器。尝试了: {possible_names}")
|
||||
|
||||
debug_print(f"❌ 所有匹配方法都失败了")
|
||||
raise ValueError(f"找不到试剂 '{reagent}' 对应的容器。尝试了: {possible_names[:10]}...")
|
||||
|
||||
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
|
||||
"""查找与容器相连的搅拌器"""
|
||||
debug_print(f"🔍 查找连接到容器 '{vessel}' 的搅拌器...")
|
||||
|
||||
stirrer_nodes = [node for node in G.nodes()
|
||||
if (G.nodes[node].get('class') or '') == 'virtual_stirrer']
|
||||
|
||||
debug_print(f"📊 发现 {len(stirrer_nodes)} 个搅拌器: {stirrer_nodes}")
|
||||
|
||||
for stirrer in stirrer_nodes:
|
||||
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
|
||||
debug_print(f"✅ 找到连接的搅拌器: {stirrer} 🔗")
|
||||
return stirrer
|
||||
|
||||
return stirrer_nodes[0] if stirrer_nodes else None
|
||||
if stirrer_nodes:
|
||||
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个: {stirrer_nodes[0]} 🔄")
|
||||
return stirrer_nodes[0]
|
||||
|
||||
debug_print(f"❌ 未找到任何搅拌器")
|
||||
return None
|
||||
|
||||
|
||||
def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume: float = 100.0) -> float: # 改为 target_ph_value
|
||||
def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume: float = 100.0) -> float:
|
||||
"""
|
||||
估算需要的试剂体积来调节pH
|
||||
|
||||
Args:
|
||||
target_ph_value: 目标pH值 # 改为 target_ph_value
|
||||
target_ph_value: 目标pH值
|
||||
reagent: 试剂名称
|
||||
vessel_volume: 容器体积 (mL)
|
||||
|
||||
Returns:
|
||||
float: 估算的试剂体积 (mL)
|
||||
"""
|
||||
debug_print(f"🧮 计算试剂体积...")
|
||||
debug_print(f" 📍 目标pH: {target_ph_value}")
|
||||
debug_print(f" 🧪 试剂: {reagent}")
|
||||
debug_print(f" 📏 容器体积: {vessel_volume}mL")
|
||||
|
||||
# 简化的pH调节体积估算(实际应用中需要更精确的计算)
|
||||
if "acid" in reagent.lower() or "hcl" in reagent.lower():
|
||||
debug_print(f"🍋 检测到酸性试剂")
|
||||
# 酸性试剂:pH越低需要的体积越大
|
||||
if target_ph_value < 3: # 改为 target_ph_value
|
||||
return vessel_volume * 0.05 # 5%
|
||||
elif target_ph_value < 5: # 改为 target_ph_value
|
||||
return vessel_volume * 0.02 # 2%
|
||||
if target_ph_value < 3:
|
||||
volume = vessel_volume * 0.05 # 5%
|
||||
debug_print(f" 💪 强酸性 (pH<3): 使用 5% 体积")
|
||||
elif target_ph_value < 5:
|
||||
volume = vessel_volume * 0.02 # 2%
|
||||
debug_print(f" 🔸 中酸性 (pH<5): 使用 2% 体积")
|
||||
else:
|
||||
return vessel_volume * 0.01 # 1%
|
||||
volume = vessel_volume * 0.01 # 1%
|
||||
debug_print(f" 🔹 弱酸性 (pH≥5): 使用 1% 体积")
|
||||
|
||||
elif "hydroxide" in reagent.lower() or "naoh" in reagent.lower():
|
||||
debug_print(f"🧂 检测到碱性试剂")
|
||||
# 碱性试剂:pH越高需要的体积越大
|
||||
if target_ph_value > 11: # 改为 target_ph_value
|
||||
return vessel_volume * 0.05 # 5%
|
||||
elif target_ph_value > 9: # 改为 target_ph_value
|
||||
return vessel_volume * 0.02 # 2%
|
||||
if target_ph_value > 11:
|
||||
volume = vessel_volume * 0.05 # 5%
|
||||
debug_print(f" 💪 强碱性 (pH>11): 使用 5% 体积")
|
||||
elif target_ph_value > 9:
|
||||
volume = vessel_volume * 0.02 # 2%
|
||||
debug_print(f" 🔸 中碱性 (pH>9): 使用 2% 体积")
|
||||
else:
|
||||
return vessel_volume * 0.01 # 1%
|
||||
volume = vessel_volume * 0.01 # 1%
|
||||
debug_print(f" 🔹 弱碱性 (pH≤9): 使用 1% 体积")
|
||||
|
||||
else:
|
||||
# 未知试剂,使用默认值
|
||||
return vessel_volume * 0.01
|
||||
|
||||
volume = vessel_volume * 0.01
|
||||
debug_print(f"❓ 未知试剂类型,使用默认 1% 体积")
|
||||
|
||||
debug_print(f"📊 计算结果: {volume:.2f}mL")
|
||||
return volume
|
||||
|
||||
def generate_adjust_ph_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
ph_value: float, # 改为 ph_value
|
||||
ph_value: float,
|
||||
reagent: str,
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
@@ -168,13 +227,23 @@ def generate_adjust_ph_protocol(
|
||||
Args:
|
||||
G: 有向图,节点为容器和设备
|
||||
vessel: 目标容器(需要调节pH的容器)
|
||||
ph_value: 目标pH值(从XDL传入) # 改为 ph_value
|
||||
ph_value: 目标pH值(从XDL传入)
|
||||
reagent: 酸碱试剂名称(从XDL传入)
|
||||
**kwargs: 其他可选参数,使用默认值
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 动作序列
|
||||
"""
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print("🧪 开始生成pH调节协议")
|
||||
debug_print(f"📋 原始参数:")
|
||||
debug_print(f" 🥼 vessel: '{vessel}'")
|
||||
debug_print(f" 📊 ph_value: {ph_value}")
|
||||
debug_print(f" 🧪 reagent: '{reagent}'")
|
||||
debug_print(f" 📦 kwargs: {kwargs}")
|
||||
debug_print("=" * 60)
|
||||
|
||||
action_sequence = []
|
||||
|
||||
# 从kwargs中获取可选参数,如果没有则使用默认值
|
||||
@@ -184,48 +253,84 @@ def generate_adjust_ph_protocol(
|
||||
stir_time = kwargs.get('stir_time', 60.0) # 默认搅拌时间
|
||||
settling_time = kwargs.get('settling_time', 30.0) # 默认平衡时间
|
||||
|
||||
print(f"ADJUST_PH: 开始生成pH调节协议")
|
||||
print(f" - 目标容器: {vessel}")
|
||||
print(f" - 目标pH: {ph_value}") # 改为 ph_value
|
||||
print(f" - 试剂: {reagent}")
|
||||
print(f" - 使用默认参数: 体积=自动估算, 搅拌=True, 搅拌速度=300RPM")
|
||||
debug_print(f"🔧 处理后的参数:")
|
||||
debug_print(f" 📏 volume: {volume}mL (0.0表示自动估算)")
|
||||
debug_print(f" 🌪️ stir: {stir}")
|
||||
debug_print(f" 🔄 stir_speed: {stir_speed}rpm")
|
||||
debug_print(f" ⏱️ stir_time: {stir_time}s")
|
||||
debug_print(f" ⏳ settling_time: {settling_time}s")
|
||||
|
||||
# 开始处理
|
||||
action_sequence.append(create_action_log(f"开始调节pH至 {ph_value}", "🧪"))
|
||||
action_sequence.append(create_action_log(f"目标容器: {vessel}", "🥼"))
|
||||
action_sequence.append(create_action_log(f"使用试剂: {reagent}", "⚗️"))
|
||||
|
||||
# 1. 验证目标容器存在
|
||||
debug_print(f"🔍 步骤1: 验证目标容器...")
|
||||
if vessel not in G.nodes():
|
||||
debug_print(f"❌ 目标容器 '{vessel}' 不存在于系统中")
|
||||
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
debug_print(f"✅ 目标容器验证通过")
|
||||
action_sequence.append(create_action_log("目标容器验证通过", "✅"))
|
||||
|
||||
# 2. 查找酸碱试剂容器
|
||||
debug_print(f"🔍 步骤2: 查找试剂容器...")
|
||||
action_sequence.append(create_action_log("正在查找试剂容器...", "🔍"))
|
||||
|
||||
try:
|
||||
reagent_vessel = find_acid_base_vessel(G, reagent)
|
||||
print(f"ADJUST_PH: 找到试剂容器: {reagent_vessel}")
|
||||
debug_print(f"✅ 找到试剂容器: {reagent_vessel}")
|
||||
action_sequence.append(create_action_log(f"找到试剂容器: {reagent_vessel}", "🧪"))
|
||||
except ValueError as e:
|
||||
debug_print(f"❌ 无法找到试剂容器: {str(e)}")
|
||||
action_sequence.append(create_action_log(f"试剂容器查找失败: {str(e)}", "❌"))
|
||||
raise ValueError(f"无法找到试剂 '{reagent}': {str(e)}")
|
||||
|
||||
# 3. 如果未指定体积,自动估算
|
||||
# 3. 体积估算
|
||||
debug_print(f"🔍 步骤3: 体积处理...")
|
||||
if volume <= 0:
|
||||
action_sequence.append(create_action_log("开始自动估算试剂体积", "🧮"))
|
||||
|
||||
# 获取目标容器的体积信息
|
||||
vessel_data = G.nodes[vessel].get('data', {})
|
||||
vessel_volume = vessel_data.get('max_volume', 100.0) # 默认100mL
|
||||
debug_print(f"📏 容器最大体积: {vessel_volume}mL")
|
||||
|
||||
estimated_volume = calculate_reagent_volume(ph_value, reagent, vessel_volume) # 改为 ph_value
|
||||
estimated_volume = calculate_reagent_volume(ph_value, reagent, vessel_volume)
|
||||
volume = estimated_volume
|
||||
print(f"ADJUST_PH: 自动估算试剂体积: {volume:.2f} mL")
|
||||
debug_print(f"✅ 自动估算试剂体积: {volume:.2f} mL")
|
||||
action_sequence.append(create_action_log(f"估算试剂体积: {volume:.2f}mL", "📊"))
|
||||
else:
|
||||
debug_print(f"📏 使用指定体积: {volume}mL")
|
||||
action_sequence.append(create_action_log(f"使用指定体积: {volume}mL", "📏"))
|
||||
|
||||
# 4. 验证路径存在
|
||||
debug_print(f"🔍 步骤4: 路径验证...")
|
||||
action_sequence.append(create_action_log("验证转移路径...", "🛤️"))
|
||||
|
||||
try:
|
||||
path = nx.shortest_path(G, source=reagent_vessel, target=vessel)
|
||||
print(f"ADJUST_PH: 找到路径: {' → '.join(path)}")
|
||||
debug_print(f"✅ 找到路径: {' → '.join(path)}")
|
||||
action_sequence.append(create_action_log(f"找到转移路径: {' → '.join(path)}", "🛤️"))
|
||||
except nx.NetworkXNoPath:
|
||||
debug_print(f"❌ 无法找到转移路径")
|
||||
action_sequence.append(create_action_log("转移路径不存在", "❌"))
|
||||
raise ValueError(f"从试剂容器 '{reagent_vessel}' 到目标容器 '{vessel}' 没有可用路径")
|
||||
|
||||
# 5. 先启动搅拌(如果需要)
|
||||
# 5. 搅拌器设置
|
||||
debug_print(f"🔍 步骤5: 搅拌器设置...")
|
||||
stirrer_id = None
|
||||
if stir:
|
||||
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
|
||||
|
||||
try:
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
|
||||
if stirrer_id:
|
||||
print(f"ADJUST_PH: 找到搅拌器 {stirrer_id},启动搅拌")
|
||||
debug_print(f"✅ 找到搅拌器 {stirrer_id},启动搅拌")
|
||||
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed}rpm)", "🔄"))
|
||||
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
@@ -237,23 +342,34 @@ def generate_adjust_ph_protocol(
|
||||
})
|
||||
|
||||
# 等待搅拌稳定
|
||||
action_sequence.append(create_action_log("等待搅拌稳定...", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 5}
|
||||
})
|
||||
else:
|
||||
print(f"ADJUST_PH: 警告 - 未找到搅拌器,继续执行")
|
||||
debug_print(f"⚠️ 未找到搅拌器,继续执行")
|
||||
action_sequence.append(create_action_log("未找到搅拌器,跳过搅拌", "⚠️"))
|
||||
|
||||
except Exception as e:
|
||||
print(f"ADJUST_PH: 搅拌器配置出错: {str(e)}")
|
||||
debug_print(f"❌ 搅拌器配置出错: {str(e)}")
|
||||
action_sequence.append(create_action_log(f"搅拌器配置失败: {str(e)}", "❌"))
|
||||
else:
|
||||
debug_print(f"📋 跳过搅拌设置")
|
||||
action_sequence.append(create_action_log("跳过搅拌设置", "⏭️"))
|
||||
|
||||
# 6. 缓慢添加试剂 - 使用pump_protocol
|
||||
print(f"ADJUST_PH: 开始添加试剂 {volume:.2f} mL")
|
||||
# 6. 试剂添加
|
||||
debug_print(f"🔍 步骤6: 试剂添加...")
|
||||
action_sequence.append(create_action_log(f"开始添加试剂 {volume:.2f}mL", "🚰"))
|
||||
|
||||
# 计算添加时间(pH调节需要缓慢添加)
|
||||
addition_time = max(30.0, volume * 2.0) # 至少30秒,每mL需要2秒
|
||||
debug_print(f"⏱️ 计算添加时间: {addition_time}s (缓慢注入)")
|
||||
action_sequence.append(create_action_log(f"设置添加时间: {addition_time:.0f}s (缓慢注入)", "⏱️"))
|
||||
|
||||
try:
|
||||
action_sequence.append(create_action_log("调用泵协议进行试剂转移", "🔄"))
|
||||
|
||||
pump_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=reagent_vessel,
|
||||
@@ -266,17 +382,24 @@ def generate_adjust_ph_protocol(
|
||||
rinsing_volume=0.0,
|
||||
rinsing_repeats=0,
|
||||
solid=False,
|
||||
flowrate=0.5 # 缓慢注入
|
||||
flowrate=0.5, # 缓慢注入
|
||||
transfer_flowrate=0.3
|
||||
)
|
||||
|
||||
action_sequence.extend(pump_actions)
|
||||
debug_print(f"✅ 泵协议生成完成,添加了 {len(pump_actions)} 个动作")
|
||||
action_sequence.append(create_action_log(f"试剂转移完成 ({len(pump_actions)} 个操作)", "✅"))
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"❌ 生成泵协议时出错: {str(e)}")
|
||||
action_sequence.append(create_action_log(f"泵协议生成失败: {str(e)}", "❌"))
|
||||
raise ValueError(f"生成泵协议时出错: {str(e)}")
|
||||
|
||||
# 7. 持续搅拌以混合和平衡
|
||||
# 7. 混合搅拌
|
||||
if stir and stirrer_id:
|
||||
print(f"ADJUST_PH: 持续搅拌 {stir_time} 秒以混合试剂")
|
||||
debug_print(f"🔍 步骤7: 混合搅拌...")
|
||||
action_sequence.append(create_action_log(f"开始混合搅拌 {stir_time:.0f}s", "🌀"))
|
||||
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "stir",
|
||||
@@ -284,25 +407,47 @@ def generate_adjust_ph_protocol(
|
||||
"stir_time": stir_time,
|
||||
"stir_speed": stir_speed,
|
||||
"settling_time": settling_time,
|
||||
"purpose": f"pH调节: 混合试剂,目标pH={ph_value}" # 改为 ph_value
|
||||
"purpose": f"pH调节: 混合试剂,目标pH={ph_value}"
|
||||
}
|
||||
})
|
||||
|
||||
debug_print(f"✅ 混合搅拌设置完成")
|
||||
else:
|
||||
debug_print(f"⏭️ 跳过混合搅拌")
|
||||
action_sequence.append(create_action_log("跳过混合搅拌", "⏭️"))
|
||||
|
||||
# 8. 等待平衡
|
||||
debug_print(f"🔍 步骤8: 反应平衡...")
|
||||
action_sequence.append(create_action_log(f"等待pH平衡 {settling_time:.0f}s", "⚖️"))
|
||||
|
||||
# 8. 等待反应平衡
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": settling_time,
|
||||
"description": f"等待pH平衡到目标值 {ph_value}" # 改为 ph_value
|
||||
"description": f"等待pH平衡到目标值 {ph_value}"
|
||||
}
|
||||
})
|
||||
|
||||
print(f"ADJUST_PH: 协议生成完成,共 {len(action_sequence)} 个动作")
|
||||
print(f"ADJUST_PH: 预计总时间: {addition_time + stir_time + settling_time:.0f} 秒")
|
||||
# 9. 完成总结
|
||||
total_time = addition_time + stir_time + settling_time
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print(f"🎉 pH调节协议生成完成")
|
||||
debug_print(f"📊 协议统计:")
|
||||
debug_print(f" 📋 总动作数: {len(action_sequence)}")
|
||||
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f}分钟)")
|
||||
debug_print(f" 🧪 试剂: {reagent}")
|
||||
debug_print(f" 📏 体积: {volume:.2f}mL")
|
||||
debug_print(f" 📊 目标pH: {ph_value}")
|
||||
debug_print(f" 🥼 目标容器: {vessel}")
|
||||
debug_print("=" * 60)
|
||||
|
||||
# 添加完成日志
|
||||
summary_msg = f"pH调节协议完成: {vessel} → pH {ph_value} (使用 {volume:.2f}mL {reagent})"
|
||||
action_sequence.append(create_action_log(summary_msg, "🎉"))
|
||||
|
||||
return action_sequence
|
||||
|
||||
|
||||
def generate_adjust_ph_protocol_stepwise(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
@@ -317,7 +462,7 @@ def generate_adjust_ph_protocol_stepwise(
|
||||
Args:
|
||||
G: 网络图
|
||||
vessel: 目标容器
|
||||
pH: 目标pH值
|
||||
ph_value: 目标pH值
|
||||
reagent: 酸碱试剂
|
||||
max_volume: 最大试剂体积
|
||||
steps: 分步数量
|
||||
@@ -325,15 +470,28 @@ def generate_adjust_ph_protocol_stepwise(
|
||||
Returns:
|
||||
List[Dict[str, Any]]: 动作序列
|
||||
"""
|
||||
action_sequence = []
|
||||
debug_print("=" * 60)
|
||||
debug_print(f"🔄 开始分步pH调节")
|
||||
debug_print(f"📋 分步参数:")
|
||||
debug_print(f" 🥼 vessel: {vessel}")
|
||||
debug_print(f" 📊 ph_value: {ph_value}")
|
||||
debug_print(f" 🧪 reagent: {reagent}")
|
||||
debug_print(f" 📏 max_volume: {max_volume}mL")
|
||||
debug_print(f" 🔢 steps: {steps}")
|
||||
debug_print("=" * 60)
|
||||
|
||||
print(f"ADJUST_PH: 开始分步pH调节({steps}步)")
|
||||
action_sequence = []
|
||||
|
||||
# 每步添加的体积
|
||||
step_volume = max_volume / steps
|
||||
debug_print(f"📊 每步体积: {step_volume:.2f}mL")
|
||||
|
||||
action_sequence.append(create_action_log(f"开始分步pH调节 ({steps}步)", "🔄"))
|
||||
action_sequence.append(create_action_log(f"每步添加: {step_volume:.2f}mL", "📏"))
|
||||
|
||||
for i in range(steps):
|
||||
print(f"ADJUST_PH: 第 {i+1}/{steps} 步,添加 {step_volume} mL")
|
||||
debug_print(f"🔄 执行第 {i+1}/{steps} 步,添加 {step_volume:.2f}mL")
|
||||
action_sequence.append(create_action_log(f"第 {i+1}/{steps} 步开始", "🚀"))
|
||||
|
||||
# 生成单步协议
|
||||
step_actions = generate_adjust_ph_protocol(
|
||||
@@ -349,9 +507,13 @@ def generate_adjust_ph_protocol_stepwise(
|
||||
)
|
||||
|
||||
action_sequence.extend(step_actions)
|
||||
debug_print(f"✅ 第 {i+1}/{steps} 步完成,添加了 {len(step_actions)} 个动作")
|
||||
action_sequence.append(create_action_log(f"第 {i+1}/{steps} 步完成", "✅"))
|
||||
|
||||
# 步骤间等待
|
||||
if i < steps - 1:
|
||||
debug_print(f"⏳ 步骤间等待30s")
|
||||
action_sequence.append(create_action_log("步骤间等待...", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
@@ -360,10 +522,11 @@ def generate_adjust_ph_protocol_stepwise(
|
||||
}
|
||||
})
|
||||
|
||||
print(f"ADJUST_PH: 分步pH调节完成")
|
||||
debug_print(f"🎉 分步pH调节完成,共 {len(action_sequence)} 个动作")
|
||||
action_sequence.append(create_action_log("分步pH调节全部完成", "🎉"))
|
||||
|
||||
return action_sequence
|
||||
|
||||
|
||||
# 便捷函数:常用pH调节
|
||||
def generate_acidify_protocol(
|
||||
G: nx.DiGraph,
|
||||
@@ -372,11 +535,11 @@ def generate_acidify_protocol(
|
||||
acid: str = "hydrochloric acid"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""酸化协议"""
|
||||
debug_print(f"🍋 生成酸化协议: {vessel} → pH {target_ph} (使用 {acid})")
|
||||
return generate_adjust_ph_protocol(
|
||||
G, vessel, target_ph, acid, 0.0, True, 300.0, 120.0, 60.0
|
||||
G, vessel, target_ph, acid
|
||||
)
|
||||
|
||||
|
||||
def generate_basify_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
@@ -384,28 +547,42 @@ def generate_basify_protocol(
|
||||
base: str = "sodium hydroxide"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""碱化协议"""
|
||||
debug_print(f"🧂 生成碱化协议: {vessel} → pH {target_ph} (使用 {base})")
|
||||
return generate_adjust_ph_protocol(
|
||||
G, vessel, target_ph, base, 0.0, True, 300.0, 120.0, 60.0
|
||||
G, vessel, target_ph, base
|
||||
)
|
||||
|
||||
|
||||
def generate_neutralize_protocol(
|
||||
G: nx.DiGraph,
|
||||
vessel: str,
|
||||
reagent: str = "sodium hydroxide"
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""中和协议(pH=7)"""
|
||||
debug_print(f"⚖️ 生成中和协议: {vessel} → pH 7.0 (使用 {reagent})")
|
||||
return generate_adjust_ph_protocol(
|
||||
G, vessel, 7.0, reagent, 0.0, True, 350.0, 180.0, 90.0
|
||||
G, vessel, 7.0, reagent
|
||||
)
|
||||
|
||||
|
||||
# 测试函数
|
||||
def test_adjust_ph_protocol():
|
||||
"""测试pH调节协议"""
|
||||
print("=== ADJUST PH PROTOCOL 测试 ===")
|
||||
print("测试完成")
|
||||
|
||||
debug_print("=== ADJUST PH PROTOCOL 增强版测试 ===")
|
||||
|
||||
# 测试体积计算
|
||||
debug_print("🧮 测试体积计算...")
|
||||
test_cases = [
|
||||
(2.0, "hydrochloric acid", 100.0),
|
||||
(4.0, "hydrochloric acid", 100.0),
|
||||
(12.0, "sodium hydroxide", 100.0),
|
||||
(10.0, "sodium hydroxide", 100.0),
|
||||
(7.0, "unknown reagent", 100.0)
|
||||
]
|
||||
|
||||
for ph, reagent, volume in test_cases:
|
||||
result = calculate_reagent_volume(ph, reagent, volume)
|
||||
debug_print(f"📊 {reagent} → pH {ph}: {result:.2f}mL")
|
||||
|
||||
debug_print("✅ 测试完成")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_adjust_ph_protocol()
|
||||
@@ -11,6 +11,22 @@ def debug_print(message):
|
||||
print(f"[DISSOLVE] {message}", flush=True)
|
||||
logger.info(f"[DISSOLVE] {message}")
|
||||
|
||||
# 🆕 创建进度日志动作
|
||||
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
|
||||
"""创建一个动作日志"""
|
||||
full_message = f"{emoji} {message}"
|
||||
debug_print(full_message)
|
||||
logger.info(full_message)
|
||||
print(f"[ACTION] {full_message}", flush=True)
|
||||
|
||||
return {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 0.1,
|
||||
"log_message": full_message
|
||||
}
|
||||
}
|
||||
|
||||
def parse_volume_input(volume_input: Union[str, float]) -> float:
|
||||
"""
|
||||
解析体积输入,支持带单位的字符串
|
||||
@@ -22,18 +38,20 @@ def parse_volume_input(volume_input: Union[str, float]) -> float:
|
||||
float: 体积(毫升)
|
||||
"""
|
||||
if isinstance(volume_input, (int, float)):
|
||||
debug_print(f"📏 体积输入为数值: {volume_input}")
|
||||
return float(volume_input)
|
||||
|
||||
if not volume_input or not str(volume_input).strip():
|
||||
debug_print(f"⚠️ 体积输入为空,返回0.0mL")
|
||||
return 0.0
|
||||
|
||||
volume_str = str(volume_input).lower().strip()
|
||||
debug_print(f"解析体积输入: '{volume_str}'")
|
||||
debug_print(f"🔍 解析体积输入: '{volume_str}'")
|
||||
|
||||
# 处理未知体积
|
||||
if volume_str in ['?', 'unknown', 'tbd', 'to be determined']:
|
||||
default_volume = 50.0 # 默认50mL
|
||||
debug_print(f"检测到未知体积,使用默认值: {default_volume}mL")
|
||||
debug_print(f"❓ 检测到未知体积,使用默认值: {default_volume}mL 🎯")
|
||||
return default_volume
|
||||
|
||||
# 移除空格并提取数字和单位
|
||||
@@ -43,7 +61,7 @@ def parse_volume_input(volume_input: Union[str, float]) -> float:
|
||||
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter)?', volume_clean)
|
||||
|
||||
if not match:
|
||||
debug_print(f"⚠️ 无法解析体积: '{volume_str}',使用默认值50mL")
|
||||
debug_print(f"❌ 无法解析体积: '{volume_str}',使用默认值50mL")
|
||||
return 50.0
|
||||
|
||||
value = float(match.group(1))
|
||||
@@ -52,12 +70,14 @@ def parse_volume_input(volume_input: Union[str, float]) -> float:
|
||||
# 转换为毫升
|
||||
if unit in ['l', 'liter']:
|
||||
volume = value * 1000.0 # L -> mL
|
||||
debug_print(f"🔄 体积转换: {value}L → {volume}mL")
|
||||
elif unit in ['μl', 'ul', 'microliter']:
|
||||
volume = value / 1000.0 # μL -> mL
|
||||
debug_print(f"🔄 体积转换: {value}μL → {volume}mL")
|
||||
else: # ml, milliliter 或默认
|
||||
volume = value # 已经是mL
|
||||
debug_print(f"✅ 体积已为mL: {volume}mL")
|
||||
|
||||
debug_print(f"体积转换: {value}{unit} → {volume}mL")
|
||||
return volume
|
||||
|
||||
def parse_mass_input(mass_input: Union[str, float]) -> float:
|
||||
@@ -71,18 +91,20 @@ def parse_mass_input(mass_input: Union[str, float]) -> float:
|
||||
float: 质量(克)
|
||||
"""
|
||||
if isinstance(mass_input, (int, float)):
|
||||
debug_print(f"⚖️ 质量输入为数值: {mass_input}g")
|
||||
return float(mass_input)
|
||||
|
||||
if not mass_input or not str(mass_input).strip():
|
||||
debug_print(f"⚠️ 质量输入为空,返回0.0g")
|
||||
return 0.0
|
||||
|
||||
mass_str = str(mass_input).lower().strip()
|
||||
debug_print(f"解析质量输入: '{mass_str}'")
|
||||
debug_print(f"🔍 解析质量输入: '{mass_str}'")
|
||||
|
||||
# 处理未知质量
|
||||
if mass_str in ['?', 'unknown', 'tbd', 'to be determined']:
|
||||
default_mass = 1.0 # 默认1g
|
||||
debug_print(f"检测到未知质量,使用默认值: {default_mass}g")
|
||||
debug_print(f"❓ 检测到未知质量,使用默认值: {default_mass}g 🎯")
|
||||
return default_mass
|
||||
|
||||
# 移除空格并提取数字和单位
|
||||
@@ -92,7 +114,7 @@ def parse_mass_input(mass_input: Union[str, float]) -> float:
|
||||
match = re.match(r'([0-9]*\.?[0-9]+)\s*(g|mg|kg|gram|milligram|kilogram)?', mass_clean)
|
||||
|
||||
if not match:
|
||||
debug_print(f"⚠️ 无法解析质量: '{mass_str}',返回0.0g")
|
||||
debug_print(f"❌ 无法解析质量: '{mass_str}',返回0.0g")
|
||||
return 0.0
|
||||
|
||||
value = float(match.group(1))
|
||||
@@ -101,12 +123,14 @@ def parse_mass_input(mass_input: Union[str, float]) -> float:
|
||||
# 转换为克
|
||||
if unit in ['mg', 'milligram']:
|
||||
mass = value / 1000.0 # mg -> g
|
||||
debug_print(f"🔄 质量转换: {value}mg → {mass}g")
|
||||
elif unit in ['kg', 'kilogram']:
|
||||
mass = value * 1000.0 # kg -> g
|
||||
debug_print(f"🔄 质量转换: {value}kg → {mass}g")
|
||||
else: # g, gram 或默认
|
||||
mass = value # 已经是g
|
||||
debug_print(f"✅ 质量已为g: {mass}g")
|
||||
|
||||
debug_print(f"质量转换: {value}{unit} → {mass}g")
|
||||
return mass
|
||||
|
||||
def parse_time_input(time_input: Union[str, float]) -> float:
|
||||
@@ -120,18 +144,20 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
||||
float: 时间(秒)
|
||||
"""
|
||||
if isinstance(time_input, (int, float)):
|
||||
debug_print(f"⏱️ 时间输入为数值: {time_input}秒")
|
||||
return float(time_input)
|
||||
|
||||
if not time_input or not str(time_input).strip():
|
||||
debug_print(f"⚠️ 时间输入为空,返回0秒")
|
||||
return 0.0
|
||||
|
||||
time_str = str(time_input).lower().strip()
|
||||
debug_print(f"解析时间输入: '{time_str}'")
|
||||
debug_print(f"🔍 解析时间输入: '{time_str}'")
|
||||
|
||||
# 处理未知时间
|
||||
if time_str in ['?', 'unknown', 'tbd']:
|
||||
default_time = 600.0 # 默认10分钟
|
||||
debug_print(f"检测到未知时间,使用默认值: {default_time}s")
|
||||
debug_print(f"❓ 检测到未知时间,使用默认值: {default_time}s (10分钟) ⏰")
|
||||
return default_time
|
||||
|
||||
# 移除空格并提取数字和单位
|
||||
@@ -141,7 +167,7 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
||||
match = re.match(r'([0-9]*\.?[0-9]+)\s*(s|sec|second|min|minute|h|hr|hour|d|day)?', time_clean)
|
||||
|
||||
if not match:
|
||||
debug_print(f"⚠️ 无法解析时间: '{time_str}',返回0s")
|
||||
debug_print(f"❌ 无法解析时间: '{time_str}',返回0s")
|
||||
return 0.0
|
||||
|
||||
value = float(match.group(1))
|
||||
@@ -150,14 +176,17 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
||||
# 转换为秒
|
||||
if unit in ['min', 'minute']:
|
||||
time_sec = value * 60.0 # min -> s
|
||||
debug_print(f"🔄 时间转换: {value}分钟 → {time_sec}秒")
|
||||
elif unit in ['h', 'hr', 'hour']:
|
||||
time_sec = value * 3600.0 # h -> s
|
||||
debug_print(f"🔄 时间转换: {value}小时 → {time_sec}秒")
|
||||
elif unit in ['d', 'day']:
|
||||
time_sec = value * 86400.0 # d -> s
|
||||
debug_print(f"🔄 时间转换: {value}天 → {time_sec}秒")
|
||||
else: # s, sec, second 或默认
|
||||
time_sec = value # 已经是s
|
||||
debug_print(f"✅ 时间已为秒: {time_sec}秒")
|
||||
|
||||
debug_print(f"时间转换: {value}{unit} → {time_sec}s")
|
||||
return time_sec
|
||||
|
||||
def parse_temperature_input(temp_input: Union[str, float]) -> float:
|
||||
@@ -171,13 +200,15 @@ def parse_temperature_input(temp_input: Union[str, float]) -> float:
|
||||
float: 温度(摄氏度)
|
||||
"""
|
||||
if isinstance(temp_input, (int, float)):
|
||||
debug_print(f"🌡️ 温度输入为数值: {temp_input}°C")
|
||||
return float(temp_input)
|
||||
|
||||
if not temp_input or not str(temp_input).strip():
|
||||
debug_print(f"⚠️ 温度输入为空,使用默认室温25°C")
|
||||
return 25.0 # 默认室温
|
||||
|
||||
temp_str = str(temp_input).lower().strip()
|
||||
debug_print(f"解析温度输入: '{temp_str}'")
|
||||
debug_print(f"🔍 解析温度输入: '{temp_str}'")
|
||||
|
||||
# 处理特殊温度描述
|
||||
temp_aliases = {
|
||||
@@ -193,7 +224,7 @@ def parse_temperature_input(temp_input: Union[str, float]) -> float:
|
||||
|
||||
if temp_str in temp_aliases:
|
||||
result = temp_aliases[temp_str]
|
||||
debug_print(f"温度别名解析: '{temp_str}' → {result}°C")
|
||||
debug_print(f"🏷️ 温度别名解析: '{temp_str}' → {result}°C")
|
||||
return result
|
||||
|
||||
# 移除空格并提取数字和单位
|
||||
@@ -203,7 +234,7 @@ def parse_temperature_input(temp_input: Union[str, float]) -> float:
|
||||
match = re.match(r'([0-9]*\.?[0-9]+)\s*(°c|c|celsius|°f|f|fahrenheit|k|kelvin)?', temp_clean)
|
||||
|
||||
if not match:
|
||||
debug_print(f"⚠️ 无法解析温度: '{temp_str}',使用默认值25°C")
|
||||
debug_print(f"❌ 无法解析温度: '{temp_str}',使用默认值25°C")
|
||||
return 25.0
|
||||
|
||||
value = float(match.group(1))
|
||||
@@ -212,19 +243,22 @@ def parse_temperature_input(temp_input: Union[str, float]) -> float:
|
||||
# 转换为摄氏度
|
||||
if unit in ['°f', 'f', 'fahrenheit']:
|
||||
temp_c = (value - 32) * 5/9 # F -> C
|
||||
debug_print(f"🔄 温度转换: {value}°F → {temp_c:.1f}°C")
|
||||
elif unit in ['k', 'kelvin']:
|
||||
temp_c = value - 273.15 # K -> C
|
||||
debug_print(f"🔄 温度转换: {value}K → {temp_c:.1f}°C")
|
||||
else: # °c, c, celsius 或默认
|
||||
temp_c = value # 已经是C
|
||||
debug_print(f"✅ 温度已为°C: {temp_c}°C")
|
||||
|
||||
debug_print(f"温度转换: {value}{unit} → {temp_c}°C")
|
||||
return temp_c
|
||||
|
||||
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
"""增强版溶剂容器查找"""
|
||||
debug_print(f"查找溶剂 '{solvent}' 的容器...")
|
||||
"""增强版溶剂容器查找,支持多种匹配模式"""
|
||||
debug_print(f"🔍 开始查找溶剂 '{solvent}' 的容器...")
|
||||
|
||||
# 🔧 方法1:直接搜索 data.reagent_name 和 config.reagent
|
||||
debug_print(f"📋 方法1: 搜索reagent字段...")
|
||||
for node in G.nodes():
|
||||
node_data = G.nodes[node].get('data', {})
|
||||
node_type = G.nodes[node].get('type', '')
|
||||
@@ -237,18 +271,20 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
|
||||
# 精确匹配
|
||||
if reagent_name == solvent.lower() or config_reagent == solvent.lower():
|
||||
debug_print(f"✅ 通过reagent字段找到容器: {node}")
|
||||
debug_print(f"✅ 通过reagent字段精确匹配到容器: {node} 🎯")
|
||||
return node
|
||||
|
||||
# 模糊匹配
|
||||
if (solvent.lower() in reagent_name and reagent_name) or \
|
||||
(solvent.lower() in config_reagent and config_reagent):
|
||||
debug_print(f"✅ 通过reagent字段模糊匹配到容器: {node}")
|
||||
debug_print(f"✅ 通过reagent字段模糊匹配到容器: {node} 🔍")
|
||||
return node
|
||||
|
||||
# 🔧 方法2:常见的容器命名规则
|
||||
debug_print(f"📋 方法2: 使用命名规则查找...")
|
||||
solvent_clean = solvent.lower().replace(' ', '_').replace('-', '_')
|
||||
possible_names = [
|
||||
solvent_clean,
|
||||
f"flask_{solvent_clean}",
|
||||
f"bottle_{solvent_clean}",
|
||||
f"vessel_{solvent_clean}",
|
||||
@@ -256,77 +292,118 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
f"{solvent_clean}_bottle",
|
||||
f"solvent_{solvent_clean}",
|
||||
f"reagent_{solvent_clean}",
|
||||
f"reagent_bottle_{solvent_clean}"
|
||||
f"reagent_bottle_{solvent_clean}",
|
||||
f"reagent_bottle_1", # 通用试剂瓶
|
||||
f"reagent_bottle_2",
|
||||
f"reagent_bottle_3"
|
||||
]
|
||||
|
||||
debug_print(f"🔍 尝试的容器名称: {possible_names[:5]}... (共{len(possible_names)}个)")
|
||||
|
||||
for name in possible_names:
|
||||
if name in G.nodes():
|
||||
node_type = G.nodes[name].get('type', '')
|
||||
if node_type == 'container':
|
||||
debug_print(f"✅ 通过命名规则找到容器: {name}")
|
||||
debug_print(f"✅ 通过命名规则找到容器: {name} 📝")
|
||||
return name
|
||||
|
||||
# 🔧 方法3:使用第一个试剂瓶作为备选
|
||||
# 🔧 方法3:节点名称模糊匹配
|
||||
debug_print(f"📋 方法3: 节点名称模糊匹配...")
|
||||
for node_id in G.nodes():
|
||||
node_data = G.nodes[node_id]
|
||||
if node_data.get('type') == 'container':
|
||||
# 检查节点名称是否包含溶剂名称
|
||||
if solvent_clean in node_id.lower():
|
||||
debug_print(f"✅ 通过节点名称模糊匹配到容器: {node_id} 🔍")
|
||||
return node_id
|
||||
|
||||
# 检查液体类型匹配
|
||||
vessel_data = node_data.get('data', {})
|
||||
liquids = vessel_data.get('liquid', [])
|
||||
for liquid in liquids:
|
||||
if isinstance(liquid, dict):
|
||||
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
|
||||
if liquid_type.lower() == solvent.lower():
|
||||
debug_print(f"✅ 通过液体类型匹配到容器: {node_id} 💧")
|
||||
return node_id
|
||||
|
||||
# 🔧 方法4:使用第一个试剂瓶作为备选
|
||||
debug_print(f"📋 方法4: 查找备选试剂瓶...")
|
||||
for node_id in G.nodes():
|
||||
node_data = G.nodes[node_id]
|
||||
if (node_data.get('type') == 'container' and
|
||||
('reagent' in node_id.lower() or 'bottle' in node_id.lower() or 'flask' in node_id.lower())):
|
||||
debug_print(f"⚠️ 未找到专用容器,使用备选容器: {node_id}")
|
||||
debug_print(f"⚠️ 未找到专用容器,使用备选试剂瓶: {node_id} 🔄")
|
||||
return node_id
|
||||
|
||||
debug_print(f"❌ 所有方法都失败了,无法找到容器!")
|
||||
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器")
|
||||
|
||||
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
|
||||
"""查找连接到指定容器的加热搅拌器"""
|
||||
debug_print(f"🔍 查找连接到容器 '{vessel}' 的加热搅拌器...")
|
||||
|
||||
heatchill_nodes = []
|
||||
for node in G.nodes():
|
||||
node_class = G.nodes[node].get('class', '').lower()
|
||||
if 'heatchill' in node_class:
|
||||
heatchill_nodes.append(node)
|
||||
debug_print(f"📋 发现加热搅拌器: {node}")
|
||||
|
||||
debug_print(f"📊 共找到 {len(heatchill_nodes)} 个加热搅拌器")
|
||||
|
||||
# 查找连接到容器的加热器
|
||||
for heatchill in heatchill_nodes:
|
||||
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
|
||||
debug_print(f"找到连接的加热器: {heatchill}")
|
||||
debug_print(f"✅ 找到连接的加热搅拌器: {heatchill} 🔗")
|
||||
return heatchill
|
||||
|
||||
# 返回第一个加热器
|
||||
if heatchill_nodes:
|
||||
debug_print(f"使用第一个加热器: {heatchill_nodes[0]}")
|
||||
debug_print(f"⚠️ 未找到直接连接的加热搅拌器,使用第一个: {heatchill_nodes[0]} 🔄")
|
||||
return heatchill_nodes[0]
|
||||
|
||||
debug_print(f"❌ 未找到任何加热搅拌器")
|
||||
return ""
|
||||
|
||||
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
|
||||
"""查找连接到指定容器的搅拌器"""
|
||||
debug_print(f"🔍 查找连接到容器 '{vessel}' 的搅拌器...")
|
||||
|
||||
stirrer_nodes = []
|
||||
for node in G.nodes():
|
||||
node_class = G.nodes[node].get('class', '').lower()
|
||||
if 'stirrer' in node_class:
|
||||
stirrer_nodes.append(node)
|
||||
debug_print(f"📋 发现搅拌器: {node}")
|
||||
|
||||
debug_print(f"📊 共找到 {len(stirrer_nodes)} 个搅拌器")
|
||||
|
||||
# 查找连接到容器的搅拌器
|
||||
for stirrer in stirrer_nodes:
|
||||
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
|
||||
debug_print(f"找到连接的搅拌器: {stirrer}")
|
||||
debug_print(f"✅ 找到连接的搅拌器: {stirrer} 🔗")
|
||||
return stirrer
|
||||
|
||||
# 返回第一个搅拌器
|
||||
if stirrer_nodes:
|
||||
debug_print(f"使用第一个搅拌器: {stirrer_nodes[0]}")
|
||||
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个: {stirrer_nodes[0]} 🔄")
|
||||
return stirrer_nodes[0]
|
||||
|
||||
debug_print(f"❌ 未找到任何搅拌器")
|
||||
return ""
|
||||
|
||||
def find_solid_dispenser(G: nx.DiGraph) -> str:
|
||||
"""查找固体加样器"""
|
||||
debug_print(f"🔍 查找固体加样器...")
|
||||
|
||||
for node in G.nodes():
|
||||
node_class = G.nodes[node].get('class', '').lower()
|
||||
if 'solid_dispenser' in node_class or 'dispenser' in node_class:
|
||||
debug_print(f"找到固体加样器: {node}")
|
||||
debug_print(f"✅ 找到固体加样器: {node} 🥄")
|
||||
return node
|
||||
|
||||
debug_print("⚠️ 未找到固体加样器")
|
||||
debug_print(f"❌ 未找到固体加样器")
|
||||
return ""
|
||||
|
||||
def generate_dissolve_protocol(
|
||||
@@ -347,12 +424,13 @@ def generate_dissolve_protocol(
|
||||
**kwargs # 🔧 关键:接受所有其他参数,防止unexpected keyword错误
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成溶解操作的协议序列 - 修复版
|
||||
生成溶解操作的协议序列 - 增强版
|
||||
|
||||
🔧 修复要点:
|
||||
1. 添加action文件中的所有参数(mass, mol, reagent, event)
|
||||
2. 使用 **kwargs 接受所有额外参数,防止 unexpected keyword argument 错误
|
||||
3. 支持固体溶解和液体溶解两种模式
|
||||
4. 添加详细的emoji日志系统
|
||||
|
||||
支持两种溶解模式:
|
||||
1. 液体溶解:指定 solvent + volume,使用pump protocol转移溶剂
|
||||
@@ -367,35 +445,40 @@ def generate_dissolve_protocol(
|
||||
"""
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print("开始生成溶解协议 - 修复版")
|
||||
debug_print(f"原始参数:")
|
||||
debug_print(f" - vessel: '{vessel}'")
|
||||
debug_print(f" - solvent: '{solvent}'")
|
||||
debug_print(f" - volume: {volume} (类型: {type(volume)})")
|
||||
debug_print(f" - mass: {mass} (类型: {type(mass)})")
|
||||
debug_print(f" - temp: {temp} (类型: {type(temp)})")
|
||||
debug_print(f" - time: {time} (类型: {type(time)})")
|
||||
debug_print(f" - reagent: '{reagent}'")
|
||||
debug_print(f" - mol: '{mol}'")
|
||||
debug_print(f" - event: '{event}'")
|
||||
debug_print(f" - kwargs: {kwargs}") # 显示额外参数
|
||||
debug_print("🧪 开始生成溶解协议")
|
||||
debug_print(f"📋 原始参数:")
|
||||
debug_print(f" 🥼 vessel: '{vessel}'")
|
||||
debug_print(f" 💧 solvent: '{solvent}'")
|
||||
debug_print(f" 📏 volume: {volume} (类型: {type(volume)})")
|
||||
debug_print(f" ⚖️ mass: {mass} (类型: {type(mass)})")
|
||||
debug_print(f" 🌡️ temp: {temp} (类型: {type(temp)})")
|
||||
debug_print(f" ⏱️ time: {time} (类型: {type(time)})")
|
||||
debug_print(f" 🧪 reagent: '{reagent}'")
|
||||
debug_print(f" 🧬 mol: '{mol}'")
|
||||
debug_print(f" 🎯 event: '{event}'")
|
||||
debug_print(f" 📦 kwargs: {kwargs}") # 显示额外参数
|
||||
debug_print("=" * 60)
|
||||
|
||||
action_sequence = []
|
||||
|
||||
# === 参数验证 ===
|
||||
debug_print("步骤1: 参数验证...")
|
||||
debug_print("🔍 步骤1: 参数验证...")
|
||||
action_sequence.append(create_action_log(f"开始溶解操作 - 容器: {vessel}", "🎬"))
|
||||
|
||||
if not vessel:
|
||||
debug_print("❌ vessel 参数不能为空")
|
||||
raise ValueError("vessel 参数不能为空")
|
||||
|
||||
if vessel not in G.nodes():
|
||||
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中")
|
||||
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||
|
||||
debug_print("✅ 基本参数验证通过")
|
||||
action_sequence.append(create_action_log("参数验证通过", "✅"))
|
||||
|
||||
# === 🔧 关键修复:参数解析 ===
|
||||
debug_print("步骤2: 参数解析...")
|
||||
debug_print("🔍 步骤2: 参数解析...")
|
||||
action_sequence.append(create_action_log("正在解析溶解参数...", "🔍"))
|
||||
|
||||
# 解析各种参数为数值
|
||||
final_volume = parse_volume_input(volume)
|
||||
@@ -403,17 +486,18 @@ def generate_dissolve_protocol(
|
||||
final_temp = parse_temperature_input(temp)
|
||||
final_time = parse_time_input(time)
|
||||
|
||||
debug_print(f"解析结果:")
|
||||
debug_print(f" - 体积: {final_volume}mL")
|
||||
debug_print(f" - 质量: {final_mass}g")
|
||||
debug_print(f" - 温度: {final_temp}°C")
|
||||
debug_print(f" - 时间: {final_time}s")
|
||||
debug_print(f" - 试剂: '{reagent}'")
|
||||
debug_print(f" - 摩尔: '{mol}'")
|
||||
debug_print(f" - 事件: '{event}'")
|
||||
debug_print(f"📊 解析结果:")
|
||||
debug_print(f" 📏 体积: {final_volume}mL")
|
||||
debug_print(f" ⚖️ 质量: {final_mass}g")
|
||||
debug_print(f" 🌡️ 温度: {final_temp}°C")
|
||||
debug_print(f" ⏱️ 时间: {final_time}s")
|
||||
debug_print(f" 🧪 试剂: '{reagent}'")
|
||||
debug_print(f" 🧬 摩尔: '{mol}'")
|
||||
debug_print(f" 🎯 事件: '{event}'")
|
||||
|
||||
# === 判断溶解类型 ===
|
||||
debug_print("步骤3: 判断溶解类型...")
|
||||
debug_print("🔍 步骤3: 判断溶解类型...")
|
||||
action_sequence.append(create_action_log("正在判断溶解类型...", "🔍"))
|
||||
|
||||
# 判断是固体溶解还是液体溶解
|
||||
is_solid_dissolve = (final_mass > 0 or (mol and mol.strip() != "") or (reagent and reagent.strip() != ""))
|
||||
@@ -427,10 +511,15 @@ def generate_dissolve_protocol(
|
||||
solvent = "water" # 默认溶剂
|
||||
debug_print("⚠️ 未明确指定溶解参数,默认为50mL水溶解")
|
||||
|
||||
debug_print(f"溶解类型: {'固体溶解' if is_solid_dissolve else '液体溶解'}")
|
||||
dissolve_type = "固体溶解" if is_solid_dissolve else "液体溶解"
|
||||
dissolve_emoji = "🧂" if is_solid_dissolve else "💧"
|
||||
debug_print(f"📋 溶解类型: {dissolve_type} {dissolve_emoji}")
|
||||
|
||||
action_sequence.append(create_action_log(f"确定溶解类型: {dissolve_type} {dissolve_emoji}", "📋"))
|
||||
|
||||
# === 查找设备 ===
|
||||
debug_print("步骤4: 查找设备...")
|
||||
debug_print("🔍 步骤4: 查找设备...")
|
||||
action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
|
||||
|
||||
# 查找加热搅拌器
|
||||
heatchill_id = find_connected_heatchill(G, vessel)
|
||||
@@ -439,21 +528,31 @@ def generate_dissolve_protocol(
|
||||
# 优先使用加热搅拌器,否则使用独立搅拌器
|
||||
stir_device_id = heatchill_id or stirrer_id
|
||||
|
||||
debug_print(f"设备映射:")
|
||||
debug_print(f" - 加热器: '{heatchill_id}'")
|
||||
debug_print(f" - 搅拌器: '{stirrer_id}'")
|
||||
debug_print(f" - 使用设备: '{stir_device_id}'")
|
||||
debug_print(f"📊 设备映射:")
|
||||
debug_print(f" 🔥 加热器: '{heatchill_id}'")
|
||||
debug_print(f" 🌪️ 搅拌器: '{stirrer_id}'")
|
||||
debug_print(f" 🎯 使用设备: '{stir_device_id}'")
|
||||
|
||||
if heatchill_id:
|
||||
action_sequence.append(create_action_log(f"找到加热搅拌器: {heatchill_id}", "🔥"))
|
||||
elif stirrer_id:
|
||||
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_id}", "🌪️"))
|
||||
else:
|
||||
action_sequence.append(create_action_log("未找到搅拌设备,将跳过搅拌", "⚠️"))
|
||||
|
||||
# === 执行溶解流程 ===
|
||||
debug_print("步骤5: 执行溶解流程...")
|
||||
debug_print("🔍 步骤5: 执行溶解流程...")
|
||||
|
||||
try:
|
||||
# 步骤5.1: 启动加热搅拌(如果需要)
|
||||
if stir_device_id and (final_temp > 25.0 or final_time > 0 or stir_speed > 0):
|
||||
debug_print(f"5.1: 启动加热搅拌,温度: {final_temp}°C")
|
||||
debug_print(f"🔍 5.1: 启动加热搅拌,温度: {final_temp}°C")
|
||||
action_sequence.append(create_action_log(f"准备加热搅拌 (目标温度: {final_temp}°C)", "🔥"))
|
||||
|
||||
if heatchill_id and (final_temp > 25.0 or final_time > 0):
|
||||
# 使用加热搅拌器
|
||||
action_sequence.append(create_action_log(f"启动加热搅拌器 {heatchill_id}", "🔥"))
|
||||
|
||||
heatchill_action = {
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_start",
|
||||
@@ -468,6 +567,7 @@ def generate_dissolve_protocol(
|
||||
# 等待温度稳定
|
||||
if final_temp > 25.0:
|
||||
wait_time = min(60, abs(final_temp - 25.0) * 1.5)
|
||||
action_sequence.append(create_action_log(f"等待温度稳定 ({wait_time:.0f}秒)", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": wait_time}
|
||||
@@ -475,6 +575,8 @@ def generate_dissolve_protocol(
|
||||
|
||||
elif stirrer_id:
|
||||
# 使用独立搅拌器
|
||||
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed}rpm)", "🌪️"))
|
||||
|
||||
stir_action = {
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
@@ -487,6 +589,7 @@ def generate_dissolve_protocol(
|
||||
action_sequence.append(stir_action)
|
||||
|
||||
# 等待搅拌稳定
|
||||
action_sequence.append(create_action_log("等待搅拌稳定...", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 5}
|
||||
@@ -494,10 +597,13 @@ def generate_dissolve_protocol(
|
||||
|
||||
if is_solid_dissolve:
|
||||
# === 固体溶解路径 ===
|
||||
debug_print(f"5.2: 使用固体溶解路径")
|
||||
debug_print(f"🔍 5.2: 使用固体溶解路径")
|
||||
action_sequence.append(create_action_log("开始固体溶解流程", "🧂"))
|
||||
|
||||
solid_dispenser = find_solid_dispenser(G)
|
||||
if solid_dispenser:
|
||||
action_sequence.append(create_action_log(f"找到固体加样器: {solid_dispenser}", "🥄"))
|
||||
|
||||
# 固体加样
|
||||
add_kwargs = {
|
||||
"vessel": vessel,
|
||||
@@ -508,9 +614,12 @@ def generate_dissolve_protocol(
|
||||
|
||||
if final_mass > 0:
|
||||
add_kwargs["mass"] = str(final_mass)
|
||||
action_sequence.append(create_action_log(f"准备添加固体: {final_mass}g", "⚖️"))
|
||||
if mol and mol.strip():
|
||||
add_kwargs["mol"] = mol
|
||||
action_sequence.append(create_action_log(f"按摩尔数添加: {mol}", "🧬"))
|
||||
|
||||
action_sequence.append(create_action_log("开始固体加样操作", "🥄"))
|
||||
action_sequence.append({
|
||||
"device_id": solid_dispenser,
|
||||
"action_name": "add_solid",
|
||||
@@ -518,18 +627,24 @@ def generate_dissolve_protocol(
|
||||
})
|
||||
|
||||
debug_print(f"✅ 固体加样完成")
|
||||
action_sequence.append(create_action_log("固体加样完成", "✅"))
|
||||
else:
|
||||
debug_print("⚠️ 未找到固体加样器,跳过固体添加")
|
||||
action_sequence.append(create_action_log("未找到固体加样器,无法添加固体", "❌"))
|
||||
|
||||
elif is_liquid_dissolve:
|
||||
# === 液体溶解路径 ===
|
||||
debug_print(f"5.3: 使用液体溶解路径")
|
||||
debug_print(f"🔍 5.3: 使用液体溶解路径")
|
||||
action_sequence.append(create_action_log("开始液体溶解流程", "💧"))
|
||||
|
||||
# 查找溶剂容器
|
||||
action_sequence.append(create_action_log("正在查找溶剂容器...", "🔍"))
|
||||
try:
|
||||
solvent_vessel = find_solvent_vessel(G, solvent)
|
||||
action_sequence.append(create_action_log(f"找到溶剂容器: {solvent_vessel}", "🧪"))
|
||||
except ValueError as e:
|
||||
debug_print(f"⚠️ {str(e)},跳过溶剂添加")
|
||||
action_sequence.append(create_action_log(f"溶剂容器查找失败: {str(e)}", "❌"))
|
||||
solvent_vessel = None
|
||||
|
||||
if solvent_vessel:
|
||||
@@ -537,6 +652,9 @@ def generate_dissolve_protocol(
|
||||
flowrate = 1.0 # 较慢的注入速度
|
||||
transfer_flowrate = 0.5 # 较慢的转移速度
|
||||
|
||||
action_sequence.append(create_action_log(f"设置流速: {flowrate}mL/min (缓慢注入)", "⚡"))
|
||||
action_sequence.append(create_action_log(f"开始转移 {final_volume}mL {solvent}", "🚰"))
|
||||
|
||||
# 调用pump protocol
|
||||
pump_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
@@ -559,8 +677,10 @@ def generate_dissolve_protocol(
|
||||
)
|
||||
action_sequence.extend(pump_actions)
|
||||
debug_print(f"✅ 溶剂转移完成,添加了 {len(pump_actions)} 个动作")
|
||||
action_sequence.append(create_action_log(f"溶剂转移完成 ({len(pump_actions)} 个操作)", "✅"))
|
||||
|
||||
# 溶剂添加后等待
|
||||
action_sequence.append(create_action_log("溶剂添加后短暂等待...", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 5}
|
||||
@@ -568,10 +688,14 @@ def generate_dissolve_protocol(
|
||||
|
||||
# 步骤5.4: 等待溶解完成
|
||||
if final_time > 0:
|
||||
debug_print(f"5.4: 等待溶解完成 - {final_time}s")
|
||||
debug_print(f"🔍 5.4: 等待溶解完成 - {final_time}s")
|
||||
wait_minutes = final_time / 60
|
||||
action_sequence.append(create_action_log(f"开始溶解等待 ({wait_minutes:.1f}分钟)", "⏰"))
|
||||
|
||||
if heatchill_id:
|
||||
# 使用定时加热搅拌
|
||||
action_sequence.append(create_action_log(f"使用加热搅拌器进行定时溶解", "🔥"))
|
||||
|
||||
dissolve_action = {
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill",
|
||||
@@ -588,6 +712,8 @@ def generate_dissolve_protocol(
|
||||
|
||||
elif stirrer_id:
|
||||
# 使用定时搅拌
|
||||
action_sequence.append(create_action_log(f"使用搅拌器进行定时溶解", "🌪️"))
|
||||
|
||||
stir_action = {
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "stir",
|
||||
@@ -603,6 +729,7 @@ def generate_dissolve_protocol(
|
||||
|
||||
else:
|
||||
# 简单等待
|
||||
action_sequence.append(create_action_log(f"简单等待溶解完成", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": final_time}
|
||||
@@ -610,7 +737,8 @@ def generate_dissolve_protocol(
|
||||
|
||||
# 步骤5.5: 停止加热搅拌(如果需要)
|
||||
if heatchill_id and final_time == 0 and final_temp > 25.0:
|
||||
debug_print(f"5.5: 停止加热器")
|
||||
debug_print(f"🔍 5.5: 停止加热器")
|
||||
action_sequence.append(create_action_log("停止加热搅拌器", "🛑"))
|
||||
|
||||
stop_action = {
|
||||
"device_id": heatchill_id,
|
||||
@@ -622,7 +750,8 @@ def generate_dissolve_protocol(
|
||||
action_sequence.append(stop_action)
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"⚠️ 溶解流程执行失败: {str(e)}")
|
||||
debug_print(f"❌ 溶解流程执行失败: {str(e)}")
|
||||
action_sequence.append(create_action_log(f"溶解流程失败: {str(e)}", "❌"))
|
||||
# 添加错误日志
|
||||
action_sequence.append({
|
||||
"device_id": "system",
|
||||
@@ -634,21 +763,30 @@ def generate_dissolve_protocol(
|
||||
|
||||
# === 最终结果 ===
|
||||
debug_print("=" * 60)
|
||||
debug_print(f"✅ 溶解协议生成完成")
|
||||
debug_print(f"📊 总动作数: {len(action_sequence)}")
|
||||
debug_print(f"📋 处理总结:")
|
||||
debug_print(f" - 容器: {vessel}")
|
||||
debug_print(f" - 溶解类型: {'固体溶解' if is_solid_dissolve else '液体溶解'}")
|
||||
debug_print(f"🎉 溶解协议生成完成")
|
||||
debug_print(f"📊 协议统计:")
|
||||
debug_print(f" 📋 总动作数: {len(action_sequence)}")
|
||||
debug_print(f" 🥼 容器: {vessel}")
|
||||
debug_print(f" {dissolve_emoji} 溶解类型: {dissolve_type}")
|
||||
if is_liquid_dissolve:
|
||||
debug_print(f" - 溶剂: {solvent} ({final_volume}mL)")
|
||||
debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL)")
|
||||
if is_solid_dissolve:
|
||||
debug_print(f" - 试剂: {reagent}")
|
||||
debug_print(f" - 质量: {final_mass}g")
|
||||
debug_print(f" - 摩尔: {mol}")
|
||||
debug_print(f" - 温度: {final_temp}°C")
|
||||
debug_print(f" - 时间: {final_time}s")
|
||||
debug_print(f" 🧪 试剂: {reagent}")
|
||||
debug_print(f" ⚖️ 质量: {final_mass}g")
|
||||
debug_print(f" 🧬 摩尔: {mol}")
|
||||
debug_print(f" 🌡️ 温度: {final_temp}°C")
|
||||
debug_print(f" ⏱️ 时间: {final_time}s")
|
||||
debug_print("=" * 60)
|
||||
|
||||
# 添加完成日志
|
||||
summary_msg = f"溶解协议完成: {vessel}"
|
||||
if is_liquid_dissolve:
|
||||
summary_msg += f" (使用 {final_volume}mL {solvent})"
|
||||
if is_solid_dissolve:
|
||||
summary_msg += f" (溶解 {final_mass}g {reagent})"
|
||||
|
||||
action_sequence.append(create_action_log(summary_msg, "🎉"))
|
||||
|
||||
return action_sequence
|
||||
|
||||
# === 便捷函数 ===
|
||||
@@ -656,6 +794,7 @@ def generate_dissolve_protocol(
|
||||
def dissolve_solid_by_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float],
|
||||
temp: Union[str, float] = 25.0, time: Union[str, float] = "10 min") -> List[Dict[str, Any]]:
|
||||
"""按质量溶解固体"""
|
||||
debug_print(f"🧂 快速固体溶解: {reagent} ({mass}) → {vessel}")
|
||||
return generate_dissolve_protocol(
|
||||
G, vessel,
|
||||
mass=mass,
|
||||
@@ -667,6 +806,7 @@ def dissolve_solid_by_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union
|
||||
def dissolve_solid_by_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
|
||||
temp: Union[str, float] = 25.0, time: Union[str, float] = "10 min") -> List[Dict[str, Any]]:
|
||||
"""按摩尔数溶解固体"""
|
||||
debug_print(f"🧬 按摩尔数溶解固体: {reagent} ({mol}) → {vessel}")
|
||||
return generate_dissolve_protocol(
|
||||
G, vessel,
|
||||
mol=mol,
|
||||
@@ -678,6 +818,7 @@ def dissolve_solid_by_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
|
||||
def dissolve_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
|
||||
temp: Union[str, float] = 25.0, time: Union[str, float] = "5 min") -> List[Dict[str, Any]]:
|
||||
"""用溶剂溶解"""
|
||||
debug_print(f"💧 溶剂溶解: {solvent} ({volume}) → {vessel}")
|
||||
return generate_dissolve_protocol(
|
||||
G, vessel,
|
||||
solvent=solvent,
|
||||
@@ -688,6 +829,7 @@ def dissolve_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Unio
|
||||
|
||||
def dissolve_at_room_temp(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float]) -> List[Dict[str, Any]]:
|
||||
"""室温溶解"""
|
||||
debug_print(f"🌡️ 室温溶解: {solvent} ({volume}) → {vessel}")
|
||||
return generate_dissolve_protocol(
|
||||
G, vessel,
|
||||
solvent=solvent,
|
||||
@@ -699,6 +841,7 @@ def dissolve_at_room_temp(G: nx.DiGraph, vessel: str, solvent: str, volume: Unio
|
||||
def dissolve_with_heating(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
|
||||
temp: Union[str, float] = "60 °C", time: Union[str, float] = "15 min") -> List[Dict[str, Any]]:
|
||||
"""加热溶解"""
|
||||
debug_print(f"🔥 加热溶解: {solvent} ({volume}) → {vessel} @ {temp}")
|
||||
return generate_dissolve_protocol(
|
||||
G, vessel,
|
||||
solvent=solvent,
|
||||
@@ -710,33 +853,37 @@ def dissolve_with_heating(G: nx.DiGraph, vessel: str, solvent: str, volume: Unio
|
||||
# 测试函数
|
||||
def test_dissolve_protocol():
|
||||
"""测试溶解协议的各种参数解析"""
|
||||
print("=== DISSOLVE PROTOCOL 修复版测试 ===")
|
||||
debug_print("=== DISSOLVE PROTOCOL 增强版测试 ===")
|
||||
|
||||
# 测试体积解析
|
||||
debug_print("💧 测试体积解析...")
|
||||
volumes = ["10 mL", "?", 10.0, "1 L", "500 μL"]
|
||||
for vol in volumes:
|
||||
result = parse_volume_input(vol)
|
||||
print(f"体积解析: {vol} → {result}mL")
|
||||
debug_print(f"📏 体积解析: {vol} → {result}mL")
|
||||
|
||||
# 测试质量解析
|
||||
debug_print("⚖️ 测试质量解析...")
|
||||
masses = ["2.9 g", "?", 2.5, "500 mg"]
|
||||
for mass in masses:
|
||||
result = parse_mass_input(mass)
|
||||
print(f"质量解析: {mass} → {result}g")
|
||||
debug_print(f"⚖️ 质量解析: {mass} → {result}g")
|
||||
|
||||
# 测试温度解析
|
||||
debug_print("🌡️ 测试温度解析...")
|
||||
temps = ["60 °C", "room temperature", "?", 25.0, "reflux"]
|
||||
for temp in temps:
|
||||
result = parse_temperature_input(temp)
|
||||
print(f"温度解析: {temp} → {result}°C")
|
||||
debug_print(f"🌡️ 温度解析: {temp} → {result}°C")
|
||||
|
||||
# 测试时间解析
|
||||
debug_print("⏱️ 测试时间解析...")
|
||||
times = ["30 min", "1 h", "?", 60.0]
|
||||
for time in times:
|
||||
result = parse_time_input(time)
|
||||
print(f"时间解析: {time} → {result}s")
|
||||
debug_print(f"⏱️ 时间解析: {time} → {result}s")
|
||||
|
||||
print("✅ 测试完成")
|
||||
debug_print("✅ 测试完成")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_dissolve_protocol()
|
||||
@@ -1,16 +1,68 @@
|
||||
import networkx as nx
|
||||
import logging
|
||||
import uuid # 🔧 移到顶部
|
||||
import uuid
|
||||
import sys
|
||||
from typing import List, Dict, Any, Optional
|
||||
from .pump_protocol import generate_pump_protocol_with_rinsing, generate_pump_protocol
|
||||
|
||||
# 设置日志
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 确保输出编码为UTF-8
|
||||
if hasattr(sys.stdout, 'reconfigure'):
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
sys.stderr.reconfigure(encoding='utf-8')
|
||||
except:
|
||||
pass
|
||||
|
||||
def debug_print(message):
|
||||
"""调试输出函数"""
|
||||
print(f"[EVACUATE_REFILL] {message}", flush=True)
|
||||
logger.info(f"[EVACUATE_REFILL] {message}")
|
||||
"""调试输出函数 - 支持中文"""
|
||||
try:
|
||||
# 确保消息是字符串格式
|
||||
safe_message = str(message)
|
||||
print(f"[抽真空充气] {safe_message}", flush=True)
|
||||
logger.info(f"[抽真空充气] {safe_message}")
|
||||
except UnicodeEncodeError:
|
||||
# 如果编码失败,尝试替换不支持的字符
|
||||
safe_message = str(message).encode('utf-8', errors='replace').decode('utf-8')
|
||||
print(f"[抽真空充气] {safe_message}", flush=True)
|
||||
logger.info(f"[抽真空充气] {safe_message}")
|
||||
except Exception as e:
|
||||
# 最后的安全措施
|
||||
fallback_message = f"日志输出错误: {repr(message)}"
|
||||
print(f"[抽真空充气] {fallback_message}", flush=True)
|
||||
logger.info(f"[抽真空充气] {fallback_message}")
|
||||
|
||||
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
|
||||
"""创建一个动作日志 - 支持中文和emoji"""
|
||||
try:
|
||||
full_message = f"{emoji} {message}"
|
||||
debug_print(full_message)
|
||||
logger.info(full_message)
|
||||
|
||||
return {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 0.1,
|
||||
"log_message": full_message,
|
||||
"progress_message": full_message
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
# 如果emoji有问题,使用纯文本
|
||||
safe_message = f"[日志] {message}"
|
||||
debug_print(safe_message)
|
||||
logger.info(safe_message)
|
||||
|
||||
return {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 0.1,
|
||||
"log_message": safe_message,
|
||||
"progress_message": safe_message
|
||||
}
|
||||
}
|
||||
|
||||
def find_gas_source(G: nx.DiGraph, gas: str) -> str:
|
||||
"""
|
||||
@@ -19,9 +71,10 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
|
||||
2. 气体类型匹配(data.gas_type)
|
||||
3. 默认气源
|
||||
"""
|
||||
debug_print(f"正在查找气体 '{gas}' 的气源...")
|
||||
debug_print(f"🔍 正在查找气体 '{gas}' 的气源...")
|
||||
|
||||
# 第一步:通过容器名称匹配
|
||||
debug_print(f"📋 方法1: 容器名称匹配...")
|
||||
gas_source_patterns = [
|
||||
f"gas_source_{gas}",
|
||||
f"gas_{gas}",
|
||||
@@ -32,12 +85,15 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
|
||||
f"bottle_{gas}"
|
||||
]
|
||||
|
||||
debug_print(f"🎯 尝试的容器名称: {gas_source_patterns}")
|
||||
|
||||
for pattern in gas_source_patterns:
|
||||
if pattern in G.nodes():
|
||||
debug_print(f"通过名称匹配找到气源: {pattern}")
|
||||
debug_print(f"✅ 通过名称找到气源: {pattern}")
|
||||
return pattern
|
||||
|
||||
# 第二步:通过气体类型匹配 (data.gas_type)
|
||||
debug_print(f"📋 方法2: 气体类型匹配...")
|
||||
for node_id in G.nodes():
|
||||
node_data = G.nodes[node_id]
|
||||
node_class = node_data.get('class', '') or ''
|
||||
@@ -52,7 +108,7 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
|
||||
gas_type = data.get('gas_type', '')
|
||||
|
||||
if gas_type.lower() == gas.lower():
|
||||
debug_print(f"通过气体类型匹配找到气源: {node_id} (gas_type: {gas_type})")
|
||||
debug_print(f"✅ 通过气体类型找到气源: {node_id} (气体类型: {gas_type})")
|
||||
return node_id
|
||||
|
||||
# 检查 config.gas_type
|
||||
@@ -60,10 +116,11 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
|
||||
config_gas_type = config.get('gas_type', '')
|
||||
|
||||
if config_gas_type.lower() == gas.lower():
|
||||
debug_print(f"通过配置气体类型匹配找到气源: {node_id} (config.gas_type: {config_gas_type})")
|
||||
debug_print(f"✅ 通过配置气体类型找到气源: {node_id} (配置气体类型: {config_gas_type})")
|
||||
return node_id
|
||||
|
||||
# 第三步:查找所有可用的气源设备
|
||||
debug_print(f"📋 方法3: 查找可用气源...")
|
||||
available_gas_sources = []
|
||||
for node_id in G.nodes():
|
||||
node_data = G.nodes[node_id]
|
||||
@@ -74,12 +131,13 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
|
||||
(node_id.startswith('flask_') and any(g in node_id.lower() for g in ['air', 'nitrogen', 'argon']))):
|
||||
|
||||
data = node_data.get('data', {})
|
||||
gas_type = data.get('gas_type', 'unknown')
|
||||
available_gas_sources.append(f"{node_id} (gas_type: {gas_type})")
|
||||
gas_type = data.get('gas_type', '未知')
|
||||
available_gas_sources.append(f"{node_id} (气体类型: {gas_type})")
|
||||
|
||||
debug_print(f"可用气源列表: {available_gas_sources}")
|
||||
debug_print(f"📊 可用气源: {available_gas_sources}")
|
||||
|
||||
# 第四步:如果找不到特定气体,使用默认的第一个气源
|
||||
debug_print(f"📋 方法4: 查找默认气源...")
|
||||
default_gas_sources = [
|
||||
node for node in G.nodes()
|
||||
if ((G.nodes[node].get('class') or '').find('virtual_gas_source') != -1
|
||||
@@ -91,11 +149,12 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
|
||||
debug_print(f"⚠️ 未找到特定气体 '{gas}',使用默认气源: {default_source}")
|
||||
return default_source
|
||||
|
||||
raise ValueError(f"找不到气体 '{gas}' 对应的气源。可用气源: {available_gas_sources}")
|
||||
debug_print(f"❌ 所有方法都失败了!")
|
||||
raise ValueError(f"无法找到气体 '{gas}' 的气源。可用气源: {available_gas_sources}")
|
||||
|
||||
def find_vacuum_pump(G: nx.DiGraph) -> str:
|
||||
"""查找真空泵设备"""
|
||||
debug_print("查找真空泵设备...")
|
||||
debug_print("🔍 正在查找真空泵...")
|
||||
|
||||
vacuum_pumps = []
|
||||
for node in G.nodes():
|
||||
@@ -106,16 +165,18 @@ def find_vacuum_pump(G: nx.DiGraph) -> str:
|
||||
'vacuum_pump' in node.lower() or
|
||||
'vacuum' in node_class.lower()):
|
||||
vacuum_pumps.append(node)
|
||||
debug_print(f"📋 发现真空泵: {node}")
|
||||
|
||||
if not vacuum_pumps:
|
||||
raise ValueError("系统中未找到真空泵设备")
|
||||
debug_print(f"❌ 系统中未找到真空泵")
|
||||
raise ValueError("系统中未找到真空泵")
|
||||
|
||||
debug_print(f"找到真空泵: {vacuum_pumps[0]}")
|
||||
debug_print(f"✅ 使用真空泵: {vacuum_pumps[0]}")
|
||||
return vacuum_pumps[0]
|
||||
|
||||
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> Optional[str]:
|
||||
"""查找与指定容器相连的搅拌器"""
|
||||
debug_print(f"查找与容器 {vessel} 相连的搅拌器...")
|
||||
debug_print(f"🔍 正在查找与容器 {vessel} 连接的搅拌器...")
|
||||
|
||||
stirrer_nodes = []
|
||||
for node in G.nodes():
|
||||
@@ -124,24 +185,27 @@ def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> Optional[str]:
|
||||
|
||||
if 'virtual_stirrer' in node_class or 'stirrer' in node.lower():
|
||||
stirrer_nodes.append(node)
|
||||
debug_print(f"📋 发现搅拌器: {node}")
|
||||
|
||||
debug_print(f"📊 找到的搅拌器总数: {len(stirrer_nodes)}")
|
||||
|
||||
# 检查哪个搅拌器与目标容器相连
|
||||
for stirrer in stirrer_nodes:
|
||||
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
|
||||
debug_print(f"找到连接的搅拌器: {stirrer}")
|
||||
debug_print(f"✅ 找到连接的搅拌器: {stirrer}")
|
||||
return stirrer
|
||||
|
||||
# 如果没有连接的搅拌器,返回第一个可用的
|
||||
if stirrer_nodes:
|
||||
debug_print(f"未找到直接连接的搅拌器,使用第一个可用的: {stirrer_nodes[0]}")
|
||||
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个可用的: {stirrer_nodes[0]}")
|
||||
return stirrer_nodes[0]
|
||||
|
||||
debug_print("未找到搅拌器")
|
||||
debug_print("❌ 未找到搅拌器")
|
||||
return None
|
||||
|
||||
def find_vacuum_solenoid_valve(G: nx.DiGraph, vacuum_pump: str) -> Optional[str]:
|
||||
"""查找真空泵相关的电磁阀 - 根据实际连接逻辑"""
|
||||
debug_print(f"查找真空泵 {vacuum_pump} 相关的电磁阀...")
|
||||
"""查找真空泵相关的电磁阀"""
|
||||
debug_print(f"🔍 正在查找真空泵 {vacuum_pump} 的电磁阀...")
|
||||
|
||||
# 查找所有电磁阀
|
||||
solenoid_valves = []
|
||||
@@ -151,29 +215,30 @@ def find_vacuum_solenoid_valve(G: nx.DiGraph, vacuum_pump: str) -> Optional[str]
|
||||
|
||||
if ('solenoid' in node_class.lower() or 'solenoid_valve' in node.lower()):
|
||||
solenoid_valves.append(node)
|
||||
debug_print(f"📋 发现电磁阀: {node}")
|
||||
|
||||
debug_print(f"找到的电磁阀: {solenoid_valves}")
|
||||
debug_print(f"📊 找到的电磁阀: {solenoid_valves}")
|
||||
|
||||
# 🔧 修复:根据实际组态图连接逻辑查找
|
||||
# vacuum_pump_1 <- solenoid_valve_1 <- multiway_valve_2
|
||||
# 检查连接关系
|
||||
debug_print(f"📋 方法1: 检查连接关系...")
|
||||
for solenoid in solenoid_valves:
|
||||
# 检查电磁阀是否连接到真空泵
|
||||
if G.has_edge(solenoid, vacuum_pump) or G.has_edge(vacuum_pump, solenoid):
|
||||
debug_print(f"✓ 找到连接真空泵的电磁阀: {solenoid}")
|
||||
debug_print(f"✅ 找到连接的真空电磁阀: {solenoid}")
|
||||
return solenoid
|
||||
|
||||
# 通过命名规则查找(备选方案)
|
||||
# 通过命名规则查找
|
||||
debug_print(f"📋 方法2: 检查命名规则...")
|
||||
for solenoid in solenoid_valves:
|
||||
if 'vacuum' in solenoid.lower() or solenoid == 'solenoid_valve_1':
|
||||
debug_print(f"✓ 通过命名规则找到真空电磁阀: {solenoid}")
|
||||
debug_print(f"✅ 通过命名找到真空电磁阀: {solenoid}")
|
||||
return solenoid
|
||||
|
||||
debug_print("⚠️ 未找到真空电磁阀")
|
||||
return None
|
||||
|
||||
def find_gas_solenoid_valve(G: nx.DiGraph, gas_source: str) -> Optional[str]:
|
||||
"""查找气源相关的电磁阀 - 根据实际连接逻辑"""
|
||||
debug_print(f"查找气源 {gas_source} 相关的电磁阀...")
|
||||
"""查找气源相关的电磁阀"""
|
||||
debug_print(f"🔍 正在查找气源 {gas_source} 的电磁阀...")
|
||||
|
||||
# 查找所有电磁阀
|
||||
solenoid_valves = []
|
||||
@@ -184,18 +249,20 @@ def find_gas_solenoid_valve(G: nx.DiGraph, gas_source: str) -> Optional[str]:
|
||||
if ('solenoid' in node_class.lower() or 'solenoid_valve' in node.lower()):
|
||||
solenoid_valves.append(node)
|
||||
|
||||
# 🔧 修复:根据实际组态图连接逻辑查找
|
||||
# gas_source_1 -> solenoid_valve_2 -> multiway_valve_2
|
||||
debug_print(f"📊 找到的电磁阀: {solenoid_valves}")
|
||||
|
||||
# 检查连接关系
|
||||
debug_print(f"📋 方法1: 检查连接关系...")
|
||||
for solenoid in solenoid_valves:
|
||||
# 检查气源是否连接到电磁阀
|
||||
if G.has_edge(gas_source, solenoid) or G.has_edge(solenoid, gas_source):
|
||||
debug_print(f"✓ 找到连接气源的电磁阀: {solenoid}")
|
||||
debug_print(f"✅ 找到连接的气源电磁阀: {solenoid}")
|
||||
return solenoid
|
||||
|
||||
# 通过命名规则查找(备选方案)
|
||||
# 通过命名规则查找
|
||||
debug_print(f"📋 方法2: 检查命名规则...")
|
||||
for solenoid in solenoid_valves:
|
||||
if 'gas' in solenoid.lower() or solenoid == 'solenoid_valve_2':
|
||||
debug_print(f"✓ 通过命名规则找到气源电磁阀: {solenoid}")
|
||||
debug_print(f"✅ 通过命名找到气源电磁阀: {solenoid}")
|
||||
return solenoid
|
||||
|
||||
debug_print("⚠️ 未找到气源电磁阀")
|
||||
@@ -208,7 +275,7 @@ def generate_evacuateandrefill_protocol(
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成抽真空和充气操作的动作序列 - 最终修复版本
|
||||
生成抽真空和充气操作的动作序列 - 中文版
|
||||
|
||||
Args:
|
||||
G: 设备图
|
||||
@@ -223,75 +290,113 @@ def generate_evacuateandrefill_protocol(
|
||||
# 硬编码重复次数为 3
|
||||
repeats = 3
|
||||
|
||||
# 🔧 修复:在函数开始就生成协议ID
|
||||
# 生成协议ID
|
||||
protocol_id = str(uuid.uuid4())
|
||||
debug_print(f"生成协议ID: {protocol_id}")
|
||||
debug_print(f"🆔 生成协议ID: {protocol_id}")
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print("开始生成抽真空充气协议")
|
||||
debug_print(f"输入参数:")
|
||||
debug_print(f" - vessel: {vessel}")
|
||||
debug_print(f" - gas: {gas}")
|
||||
debug_print(f" - repeats: {repeats} (硬编码)")
|
||||
debug_print(f" - 其他参数: {kwargs}")
|
||||
debug_print("🧪 开始生成抽真空充气协议")
|
||||
debug_print(f"📋 原始参数:")
|
||||
debug_print(f" 🥼 容器: '{vessel}'")
|
||||
debug_print(f" 💨 气体: '{gas}'")
|
||||
debug_print(f" 🔄 循环次数: {repeats} (硬编码)")
|
||||
debug_print(f" 📦 其他参数: {kwargs}")
|
||||
debug_print("=" * 60)
|
||||
|
||||
action_sequence = []
|
||||
|
||||
# === 参数验证和修正 ===
|
||||
debug_print("步骤1: 参数验证和修正...")
|
||||
debug_print("🔍 步骤1: 参数验证和修正...")
|
||||
action_sequence.append(create_action_log(f"开始抽真空充气操作 - 容器: {vessel}", "🎬"))
|
||||
action_sequence.append(create_action_log(f"目标气体: {gas}", "💨"))
|
||||
action_sequence.append(create_action_log(f"循环次数: {repeats}", "🔄"))
|
||||
|
||||
# 验证必需参数
|
||||
if not vessel:
|
||||
raise ValueError("vessel 参数不能为空")
|
||||
debug_print("❌ 容器参数不能为空")
|
||||
raise ValueError("容器参数不能为空")
|
||||
|
||||
if not gas:
|
||||
raise ValueError("gas 参数不能为空")
|
||||
debug_print("❌ 气体参数不能为空")
|
||||
raise ValueError("气体参数不能为空")
|
||||
|
||||
if vessel not in G.nodes():
|
||||
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
|
||||
debug_print(f"❌ 容器 '{vessel}' 在系统中不存在")
|
||||
raise ValueError(f"容器 '{vessel}' 在系统中不存在")
|
||||
|
||||
debug_print("✅ 基本参数验证通过")
|
||||
action_sequence.append(create_action_log("参数验证通过", "✅"))
|
||||
|
||||
# 标准化气体名称
|
||||
debug_print("🔧 标准化气体名称...")
|
||||
gas_aliases = {
|
||||
'n2': 'nitrogen',
|
||||
'ar': 'argon',
|
||||
'air': 'air',
|
||||
'o2': 'oxygen',
|
||||
'co2': 'carbon_dioxide',
|
||||
'h2': 'hydrogen'
|
||||
'h2': 'hydrogen',
|
||||
'氮气': 'nitrogen',
|
||||
'氩气': 'argon',
|
||||
'空气': 'air',
|
||||
'氧气': 'oxygen',
|
||||
'二氧化碳': 'carbon_dioxide',
|
||||
'氢气': 'hydrogen'
|
||||
}
|
||||
|
||||
original_gas = gas
|
||||
gas_lower = gas.lower().strip()
|
||||
if gas_lower in gas_aliases:
|
||||
gas = gas_aliases[gas_lower]
|
||||
debug_print(f"标准化气体名称: {original_gas} -> {gas}")
|
||||
debug_print(f"🔄 标准化气体名称: {original_gas} -> {gas}")
|
||||
action_sequence.append(create_action_log(f"气体名称标准化: {original_gas} -> {gas}", "🔄"))
|
||||
|
||||
debug_print(f"最终参数: vessel={vessel}, gas={gas}, repeats={repeats}")
|
||||
debug_print(f"📋 最终参数: 容器={vessel}, 气体={gas}, 重复={repeats}")
|
||||
|
||||
# === 查找设备 ===
|
||||
debug_print("步骤2: 查找设备...")
|
||||
debug_print("🔍 步骤2: 查找设备...")
|
||||
action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
|
||||
|
||||
try:
|
||||
vacuum_pump = find_vacuum_pump(G)
|
||||
gas_source = find_gas_source(G, gas)
|
||||
vacuum_solenoid = find_vacuum_solenoid_valve(G, vacuum_pump)
|
||||
gas_solenoid = find_gas_solenoid_valve(G, gas_source)
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
action_sequence.append(create_action_log(f"找到真空泵: {vacuum_pump}", "🌪️"))
|
||||
|
||||
debug_print(f"设备配置:")
|
||||
debug_print(f" - 真空泵: {vacuum_pump}")
|
||||
debug_print(f" - 气源: {gas_source}")
|
||||
debug_print(f" - 真空电磁阀: {vacuum_solenoid}")
|
||||
debug_print(f" - 气源电磁阀: {gas_solenoid}")
|
||||
debug_print(f" - 搅拌器: {stirrer_id}")
|
||||
gas_source = find_gas_source(G, gas)
|
||||
action_sequence.append(create_action_log(f"找到气源: {gas_source}", "💨"))
|
||||
|
||||
vacuum_solenoid = find_vacuum_solenoid_valve(G, vacuum_pump)
|
||||
if vacuum_solenoid:
|
||||
action_sequence.append(create_action_log(f"找到真空电磁阀: {vacuum_solenoid}", "🚪"))
|
||||
else:
|
||||
action_sequence.append(create_action_log("未找到真空电磁阀", "⚠️"))
|
||||
|
||||
gas_solenoid = find_gas_solenoid_valve(G, gas_source)
|
||||
if gas_solenoid:
|
||||
action_sequence.append(create_action_log(f"找到气源电磁阀: {gas_solenoid}", "🚪"))
|
||||
else:
|
||||
action_sequence.append(create_action_log("未找到气源电磁阀", "⚠️"))
|
||||
|
||||
stirrer_id = find_connected_stirrer(G, vessel)
|
||||
if stirrer_id:
|
||||
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_id}", "🌪️"))
|
||||
else:
|
||||
action_sequence.append(create_action_log("未找到搅拌器", "⚠️"))
|
||||
|
||||
debug_print(f"📊 设备配置:")
|
||||
debug_print(f" 🌪️ 真空泵: {vacuum_pump}")
|
||||
debug_print(f" 💨 气源: {gas_source}")
|
||||
debug_print(f" 🚪 真空电磁阀: {vacuum_solenoid}")
|
||||
debug_print(f" 🚪 气源电磁阀: {gas_solenoid}")
|
||||
debug_print(f" 🌪️ 搅拌器: {stirrer_id}")
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"❌ 设备查找失败: {str(e)}")
|
||||
action_sequence.append(create_action_log(f"设备查找失败: {str(e)}", "❌"))
|
||||
raise ValueError(f"设备查找失败: {str(e)}")
|
||||
|
||||
# === 参数设置 ===
|
||||
debug_print("步骤3: 参数设置...")
|
||||
debug_print("🔍 步骤3: 参数设置...")
|
||||
action_sequence.append(create_action_log("设置操作参数...", "⚙️"))
|
||||
|
||||
# 根据气体类型调整参数
|
||||
if gas.lower() in ['nitrogen', 'argon']:
|
||||
@@ -300,87 +405,108 @@ def generate_evacuateandrefill_protocol(
|
||||
PUMP_FLOW_RATE = 2.0
|
||||
VACUUM_TIME = 30.0
|
||||
REFILL_TIME = 20.0
|
||||
debug_print("惰性气体:使用标准参数")
|
||||
debug_print("💨 惰性气体: 使用标准参数")
|
||||
action_sequence.append(create_action_log("检测到惰性气体,使用标准参数", "💨"))
|
||||
elif gas.lower() in ['air', 'oxygen']:
|
||||
VACUUM_VOLUME = 20.0
|
||||
REFILL_VOLUME = 20.0
|
||||
PUMP_FLOW_RATE = 1.5
|
||||
VACUUM_TIME = 45.0
|
||||
REFILL_TIME = 25.0
|
||||
debug_print("活性气体:使用保守参数")
|
||||
debug_print("🔥 活性气体: 使用保守参数")
|
||||
action_sequence.append(create_action_log("检测到活性气体,使用保守参数", "🔥"))
|
||||
else:
|
||||
VACUUM_VOLUME = 15.0
|
||||
REFILL_VOLUME = 15.0
|
||||
PUMP_FLOW_RATE = 1.0
|
||||
VACUUM_TIME = 60.0
|
||||
REFILL_TIME = 30.0
|
||||
debug_print("未知气体:使用安全参数")
|
||||
debug_print("❓ 未知气体: 使用安全参数")
|
||||
action_sequence.append(create_action_log("未知气体类型,使用安全参数", "❓"))
|
||||
|
||||
STIR_SPEED = 200.0
|
||||
|
||||
debug_print(f"操作参数:")
|
||||
debug_print(f" - 抽真空体积: {VACUUM_VOLUME}mL")
|
||||
debug_print(f" - 充气体积: {REFILL_VOLUME}mL")
|
||||
debug_print(f" - 泵流速: {PUMP_FLOW_RATE}mL/s")
|
||||
debug_print(f" - 抽真空时间: {VACUUM_TIME}s")
|
||||
debug_print(f" - 充气时间: {REFILL_TIME}s")
|
||||
debug_print(f" - 搅拌速度: {STIR_SPEED}RPM")
|
||||
debug_print(f"⚙️ 操作参数:")
|
||||
debug_print(f" 📏 真空体积: {VACUUM_VOLUME}mL")
|
||||
debug_print(f" 📏 充气体积: {REFILL_VOLUME}mL")
|
||||
debug_print(f" ⚡ 泵流速: {PUMP_FLOW_RATE}mL/s")
|
||||
debug_print(f" ⏱️ 真空时间: {VACUUM_TIME}s")
|
||||
debug_print(f" ⏱️ 充气时间: {REFILL_TIME}s")
|
||||
debug_print(f" 🌪️ 搅拌速度: {STIR_SPEED}RPM")
|
||||
|
||||
action_sequence.append(create_action_log(f"真空体积: {VACUUM_VOLUME}mL", "📏"))
|
||||
action_sequence.append(create_action_log(f"充气体积: {REFILL_VOLUME}mL", "📏"))
|
||||
action_sequence.append(create_action_log(f"泵流速: {PUMP_FLOW_RATE}mL/s", "⚡"))
|
||||
|
||||
# === 路径验证 ===
|
||||
debug_print("步骤4: 路径验证...")
|
||||
debug_print("🔍 步骤4: 路径验证...")
|
||||
action_sequence.append(create_action_log("验证传输路径...", "🛤️"))
|
||||
|
||||
try:
|
||||
# 验证抽真空路径: vessel -> vacuum_pump (通过八通阀和电磁阀)
|
||||
# 验证抽真空路径
|
||||
if nx.has_path(G, vessel, vacuum_pump):
|
||||
vacuum_path = nx.shortest_path(G, source=vessel, target=vacuum_pump)
|
||||
debug_print(f"抽真空路径: {' → '.join(vacuum_path)}")
|
||||
debug_print(f"✅ 真空路径: {' -> '.join(vacuum_path)}")
|
||||
action_sequence.append(create_action_log(f"真空路径: {' -> '.join(vacuum_path)}", "🛤️"))
|
||||
else:
|
||||
debug_print(f"⚠️ 抽真空路径不存在,继续执行但可能有问题")
|
||||
debug_print(f"⚠️ 真空路径不存在,继续执行但可能有问题")
|
||||
action_sequence.append(create_action_log("真空路径检查: 路径不存在", "⚠️"))
|
||||
|
||||
# 验证充气路径: gas_source -> vessel (通过电磁阀和八通阀)
|
||||
# 验证充气路径
|
||||
if nx.has_path(G, gas_source, vessel):
|
||||
gas_path = nx.shortest_path(G, source=gas_source, target=vessel)
|
||||
debug_print(f"充气路径: {' → '.join(gas_path)}")
|
||||
debug_print(f"✅ 气体路径: {' -> '.join(gas_path)}")
|
||||
action_sequence.append(create_action_log(f"气体路径: {' -> '.join(gas_path)}", "🛤️"))
|
||||
else:
|
||||
debug_print(f"⚠️ 充气路径不存在,继续执行但可能有问题")
|
||||
debug_print(f"⚠️ 气体路径不存在,继续执行但可能有问题")
|
||||
action_sequence.append(create_action_log("气体路径检查: 路径不存在", "⚠️"))
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"⚠️ 路径验证失败: {str(e)},继续执行")
|
||||
action_sequence.append(create_action_log(f"路径验证失败: {str(e)}", "⚠️"))
|
||||
|
||||
# === 启动搅拌器 ===
|
||||
debug_print("步骤5: 启动搅拌器...")
|
||||
debug_print("🔍 步骤5: 启动搅拌器...")
|
||||
|
||||
if stirrer_id:
|
||||
debug_print(f"启动搅拌器: {stirrer_id}")
|
||||
debug_print(f"🌪️ 启动搅拌器: {stirrer_id}")
|
||||
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {STIR_SPEED}rpm)", "🌪️"))
|
||||
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": vessel,
|
||||
"stir_speed": STIR_SPEED,
|
||||
"purpose": "抽真空充气操作前启动搅拌"
|
||||
"purpose": "抽真空充气前预搅拌"
|
||||
}
|
||||
})
|
||||
|
||||
# 等待搅拌稳定
|
||||
action_sequence.append(create_action_log("等待搅拌稳定...", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 5.0}
|
||||
})
|
||||
else:
|
||||
debug_print("未找到搅拌器,跳过搅拌启动")
|
||||
debug_print("⚠️ 未找到搅拌器,跳过搅拌器启动")
|
||||
action_sequence.append(create_action_log("跳过搅拌器启动", "⏭️"))
|
||||
|
||||
# === 执行 3 次抽真空-充气循环 ===
|
||||
debug_print("步骤6: 执行抽真空-充气循环...")
|
||||
# === 执行循环 ===
|
||||
debug_print("🔍 步骤6: 执行抽真空-充气循环...")
|
||||
action_sequence.append(create_action_log(f"开始 {repeats} 次抽真空-充气循环", "🔄"))
|
||||
|
||||
for cycle in range(repeats):
|
||||
debug_print(f"=== 第 {cycle+1}/{repeats} 次循环 ===")
|
||||
debug_print(f"=== 第 {cycle+1}/{repeats} 轮循环 ===")
|
||||
action_sequence.append(create_action_log(f"第 {cycle+1}/{repeats} 轮循环开始", "🚀"))
|
||||
|
||||
# ============ 抽真空阶段 ============
|
||||
debug_print(f"抽真空阶段开始")
|
||||
debug_print(f"🌪️ 抽真空阶段开始")
|
||||
action_sequence.append(create_action_log("开始抽真空阶段", "🌪️"))
|
||||
|
||||
# 启动真空泵
|
||||
debug_print(f"启动真空泵: {vacuum_pump}")
|
||||
debug_print(f"🔛 启动真空泵: {vacuum_pump}")
|
||||
action_sequence.append(create_action_log(f"启动真空泵: {vacuum_pump}", "🔛"))
|
||||
action_sequence.append({
|
||||
"device_id": vacuum_pump,
|
||||
"action_name": "set_status",
|
||||
@@ -389,17 +515,19 @@ def generate_evacuateandrefill_protocol(
|
||||
|
||||
# 开启真空电磁阀
|
||||
if vacuum_solenoid:
|
||||
debug_print(f"开启真空电磁阀: {vacuum_solenoid}")
|
||||
debug_print(f"🚪 打开真空电磁阀: {vacuum_solenoid}")
|
||||
action_sequence.append(create_action_log(f"打开真空电磁阀: {vacuum_solenoid}", "🚪"))
|
||||
action_sequence.append({
|
||||
"device_id": vacuum_solenoid,
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {"command": "OPEN"}
|
||||
})
|
||||
|
||||
# 抽真空操作 - 使用液体转移协议
|
||||
debug_print(f"抽真空操作: {vessel} → {vacuum_pump}")
|
||||
# 抽真空操作
|
||||
debug_print(f"🌪️ 抽真空操作: {vessel} -> {vacuum_pump}")
|
||||
action_sequence.append(create_action_log(f"开始抽真空: {vessel} -> {vacuum_pump}", "🌪️"))
|
||||
|
||||
try:
|
||||
|
||||
vacuum_transfer_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=vessel,
|
||||
@@ -419,8 +547,10 @@ def generate_evacuateandrefill_protocol(
|
||||
if vacuum_transfer_actions:
|
||||
action_sequence.extend(vacuum_transfer_actions)
|
||||
debug_print(f"✅ 添加了 {len(vacuum_transfer_actions)} 个抽真空动作")
|
||||
action_sequence.append(create_action_log(f"抽真空协议完成 ({len(vacuum_transfer_actions)} 个操作)", "✅"))
|
||||
else:
|
||||
debug_print("⚠️ 抽真空协议返回空序列,添加手动动作")
|
||||
action_sequence.append(create_action_log("抽真空协议为空,使用手动等待", "⚠️"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": VACUUM_TIME}
|
||||
@@ -428,13 +558,15 @@ def generate_evacuateandrefill_protocol(
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"❌ 抽真空失败: {str(e)}")
|
||||
# 添加等待时间作为备选
|
||||
action_sequence.append(create_action_log(f"抽真空失败: {str(e)}", "❌"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": VACUUM_TIME}
|
||||
})
|
||||
|
||||
# 抽真空后等待
|
||||
wait_minutes = VACUUM_TIME / 60
|
||||
action_sequence.append(create_action_log(f"抽真空后等待 ({wait_minutes:.1f} 分钟)", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": VACUUM_TIME}
|
||||
@@ -442,7 +574,8 @@ def generate_evacuateandrefill_protocol(
|
||||
|
||||
# 关闭真空电磁阀
|
||||
if vacuum_solenoid:
|
||||
debug_print(f"关闭真空电磁阀: {vacuum_solenoid}")
|
||||
debug_print(f"🚪 关闭真空电磁阀: {vacuum_solenoid}")
|
||||
action_sequence.append(create_action_log(f"关闭真空电磁阀: {vacuum_solenoid}", "🚪"))
|
||||
action_sequence.append({
|
||||
"device_id": vacuum_solenoid,
|
||||
"action_name": "set_valve_position",
|
||||
@@ -450,24 +583,28 @@ def generate_evacuateandrefill_protocol(
|
||||
})
|
||||
|
||||
# 关闭真空泵
|
||||
debug_print(f"关闭真空泵: {vacuum_pump}")
|
||||
debug_print(f"🔴 停止真空泵: {vacuum_pump}")
|
||||
action_sequence.append(create_action_log(f"停止真空泵: {vacuum_pump}", "🔴"))
|
||||
action_sequence.append({
|
||||
"device_id": vacuum_pump,
|
||||
"action_name": "set_status",
|
||||
"action_kwargs": {"string": "OFF"}
|
||||
})
|
||||
|
||||
# 抽真空后等待
|
||||
# 阶段间等待
|
||||
action_sequence.append(create_action_log("抽真空阶段完成,短暂等待", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 5.0}
|
||||
})
|
||||
|
||||
# ============ 充气阶段 ============
|
||||
debug_print(f"充气阶段开始")
|
||||
debug_print(f"💨 充气阶段开始")
|
||||
action_sequence.append(create_action_log("开始气体充气阶段", "💨"))
|
||||
|
||||
# 启动气源
|
||||
debug_print(f"启动气源: {gas_source}")
|
||||
debug_print(f"🔛 启动气源: {gas_source}")
|
||||
action_sequence.append(create_action_log(f"启动气源: {gas_source}", "🔛"))
|
||||
action_sequence.append({
|
||||
"device_id": gas_source,
|
||||
"action_name": "set_status",
|
||||
@@ -476,17 +613,19 @@ def generate_evacuateandrefill_protocol(
|
||||
|
||||
# 开启气源电磁阀
|
||||
if gas_solenoid:
|
||||
debug_print(f"开启气源电磁阀: {gas_solenoid}")
|
||||
debug_print(f"🚪 打开气源电磁阀: {gas_solenoid}")
|
||||
action_sequence.append(create_action_log(f"打开气源电磁阀: {gas_solenoid}", "🚪"))
|
||||
action_sequence.append({
|
||||
"device_id": gas_solenoid,
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {"command": "OPEN"}
|
||||
})
|
||||
|
||||
# 充气操作 - 使用液体转移协议
|
||||
debug_print(f"充气操作: {gas_source} → {vessel}")
|
||||
# 充气操作
|
||||
debug_print(f"💨 充气操作: {gas_source} -> {vessel}")
|
||||
action_sequence.append(create_action_log(f"开始气体充气: {gas_source} -> {vessel}", "💨"))
|
||||
|
||||
try:
|
||||
|
||||
gas_transfer_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=gas_source,
|
||||
@@ -506,22 +645,26 @@ def generate_evacuateandrefill_protocol(
|
||||
if gas_transfer_actions:
|
||||
action_sequence.extend(gas_transfer_actions)
|
||||
debug_print(f"✅ 添加了 {len(gas_transfer_actions)} 个充气动作")
|
||||
action_sequence.append(create_action_log(f"气体充气协议完成 ({len(gas_transfer_actions)} 个操作)", "✅"))
|
||||
else:
|
||||
debug_print("⚠️ 充气协议返回空序列,添加手动动作")
|
||||
action_sequence.append(create_action_log("充气协议为空,使用手动等待", "⚠️"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": REFILL_TIME}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"❌ 充气失败: {str(e)}")
|
||||
# 添加等待时间作为备选
|
||||
debug_print(f"❌ 气体充气失败: {str(e)}")
|
||||
action_sequence.append(create_action_log(f"气体充气失败: {str(e)}", "❌"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": REFILL_TIME}
|
||||
})
|
||||
|
||||
# 充气后等待
|
||||
refill_wait_minutes = REFILL_TIME / 60
|
||||
action_sequence.append(create_action_log(f"充气后等待 ({refill_wait_minutes:.1f} 分钟)", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": REFILL_TIME}
|
||||
@@ -529,7 +672,8 @@ def generate_evacuateandrefill_protocol(
|
||||
|
||||
# 关闭气源电磁阀
|
||||
if gas_solenoid:
|
||||
debug_print(f"关闭气源电磁阀: {gas_solenoid}")
|
||||
debug_print(f"🚪 关闭气源电磁阀: {gas_solenoid}")
|
||||
action_sequence.append(create_action_log(f"关闭气源电磁阀: {gas_solenoid}", "🚪"))
|
||||
action_sequence.append({
|
||||
"device_id": gas_solenoid,
|
||||
"action_name": "set_valve_position",
|
||||
@@ -537,68 +681,92 @@ def generate_evacuateandrefill_protocol(
|
||||
})
|
||||
|
||||
# 关闭气源
|
||||
debug_print(f"关闭气源: {gas_source}")
|
||||
debug_print(f"🔴 停止气源: {gas_source}")
|
||||
action_sequence.append(create_action_log(f"停止气源: {gas_source}", "🔴"))
|
||||
action_sequence.append({
|
||||
"device_id": gas_source,
|
||||
"action_name": "set_status",
|
||||
"action_kwargs": {"string": "OFF"}
|
||||
})
|
||||
|
||||
# 等待下一次循环
|
||||
# 循环间等待
|
||||
if cycle < repeats - 1:
|
||||
debug_print(f"等待下一次循环...")
|
||||
debug_print(f"⏳ 等待下一个循环...")
|
||||
action_sequence.append(create_action_log("等待下一个循环...", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 10.0}
|
||||
})
|
||||
else:
|
||||
action_sequence.append(create_action_log(f"第 {cycle+1}/{repeats} 轮循环完成", "✅"))
|
||||
|
||||
# === 停止搅拌器 ===
|
||||
debug_print("步骤7: 停止搅拌器...")
|
||||
debug_print("🔍 步骤7: 停止搅拌器...")
|
||||
|
||||
if stirrer_id:
|
||||
debug_print(f"停止搅拌器: {stirrer_id}")
|
||||
debug_print(f"🛑 停止搅拌器: {stirrer_id}")
|
||||
action_sequence.append(create_action_log(f"停止搅拌器: {stirrer_id}", "🛑"))
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "stop_stir",
|
||||
"action_kwargs": {"vessel": vessel}
|
||||
})
|
||||
else:
|
||||
action_sequence.append(create_action_log("跳过搅拌器停止", "⏭️"))
|
||||
|
||||
# === 最终等待 ===
|
||||
action_sequence.append(create_action_log("最终稳定等待...", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 10.0}
|
||||
})
|
||||
|
||||
# === 总结 ===
|
||||
total_time = (VACUUM_TIME + REFILL_TIME + 25) * repeats + 20
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print(f"抽真空充气协议生成完成")
|
||||
debug_print(f"总动作数: {len(action_sequence)}")
|
||||
debug_print(f"处理容器: {vessel}")
|
||||
debug_print(f"使用气体: {gas}")
|
||||
debug_print(f"重复次数: {repeats} (硬编码)")
|
||||
debug_print(f"🎉 抽真空充气协议生成完成")
|
||||
debug_print(f"📊 协议统计:")
|
||||
debug_print(f" 📋 总动作数: {len(action_sequence)}")
|
||||
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f} 分钟)")
|
||||
debug_print(f" 🥼 处理容器: {vessel}")
|
||||
debug_print(f" 💨 使用气体: {gas}")
|
||||
debug_print(f" 🔄 重复次数: {repeats}")
|
||||
debug_print("=" * 60)
|
||||
|
||||
# 添加完成日志
|
||||
summary_msg = f"抽真空充气协议完成: {vessel} (使用 {gas},{repeats} 次循环)"
|
||||
action_sequence.append(create_action_log(summary_msg, "🎉"))
|
||||
|
||||
return action_sequence
|
||||
|
||||
# === 便捷函数 ===
|
||||
|
||||
def generate_nitrogen_purge_protocol(G: nx.DiGraph, vessel: str, **kwargs) -> List[Dict[str, Any]]:
|
||||
"""生成氮气置换协议"""
|
||||
debug_print(f"💨 生成氮气置换协议: {vessel}")
|
||||
return generate_evacuateandrefill_protocol(G, vessel, "nitrogen", **kwargs)
|
||||
|
||||
def generate_argon_purge_protocol(G: nx.DiGraph, vessel: str, **kwargs) -> List[Dict[str, Any]]:
|
||||
"""生成氩气置换协议"""
|
||||
debug_print(f"💨 生成氩气置换协议: {vessel}")
|
||||
return generate_evacuateandrefill_protocol(G, vessel, "argon", **kwargs)
|
||||
|
||||
def generate_air_purge_protocol(G: nx.DiGraph, vessel: str, **kwargs) -> List[Dict[str, Any]]:
|
||||
"""生成空气置换协议"""
|
||||
debug_print(f"💨 生成空气置换协议: {vessel}")
|
||||
return generate_evacuateandrefill_protocol(G, vessel, "air", **kwargs)
|
||||
|
||||
def generate_inert_atmosphere_protocol(G: nx.DiGraph, vessel: str, gas: str = "nitrogen", **kwargs) -> List[Dict[str, Any]]:
|
||||
"""生成惰性气氛协议"""
|
||||
debug_print(f"🛡️ 生成惰性气氛协议: {vessel} (使用 {gas})")
|
||||
return generate_evacuateandrefill_protocol(G, vessel, gas, **kwargs)
|
||||
|
||||
# 测试函数
|
||||
def test_evacuateandrefill_protocol():
|
||||
"""测试抽真空充气协议"""
|
||||
debug_print("=== EVACUATE AND REFILL PROTOCOL 测试 ===")
|
||||
debug_print("测试完成")
|
||||
debug_print("=== 抽真空充气协议增强中文版测试 ===")
|
||||
debug_print("✅ 测试完成")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_evacuateandrefill_protocol()
|
||||
@@ -273,7 +273,6 @@ def generate_pump_protocol(
|
||||
|
||||
if not pump_backbone:
|
||||
debug_print("PUMP_TRANSFER: 没有泵骨架节点,可能是直接容器连接或只有电磁阀")
|
||||
# 🔧 对于气体传输,这是正常的,直接返回空序列
|
||||
return pump_action_sequence
|
||||
|
||||
if transfer_flowrate == 0:
|
||||
@@ -319,10 +318,31 @@ def generate_pump_protocol(
|
||||
volume_left = volume
|
||||
debug_print(f"PUMP_TRANSFER: 需要 {repeats} 次转移,单次最大体积 {min_transfer_volume} mL")
|
||||
|
||||
# 🆕 只在开头打印总体概览
|
||||
if repeats > 1:
|
||||
debug_print(f"🔄 分批转移概览: 总体积 {volume:.2f}mL,需要 {repeats} 次转移")
|
||||
logger.info(f"🔄 分批转移概览: 总体积 {volume:.2f}mL,需要 {repeats} 次转移")
|
||||
|
||||
# 🔧 创建一个自定义的wait动作,用于在执行时打印日志
|
||||
def create_progress_log_action(message: str) -> Dict[str, Any]:
|
||||
"""创建一个特殊的等待动作,在执行时打印进度日志"""
|
||||
return {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 0.1, # 很短的等待时间
|
||||
"progress_message": message # 自定义字段,用于进度日志
|
||||
}
|
||||
}
|
||||
|
||||
# 生成泵操作序列
|
||||
for i in range(repeats):
|
||||
current_volume = min(volume_left, min_transfer_volume)
|
||||
|
||||
# 🆕 在每次循环开始时添加进度日志
|
||||
if repeats > 1:
|
||||
start_message = f"🚀 准备开始第 {i+1}/{repeats} 次转移: {current_volume:.2f}mL ({from_vessel} → {to_vessel}) 🚰"
|
||||
pump_action_sequence.append(create_progress_log_action(start_message))
|
||||
|
||||
# 🔧 修复:安全地获取边数据
|
||||
def get_safe_edge_data(node_a, node_b, key):
|
||||
try:
|
||||
@@ -426,6 +446,426 @@ def generate_pump_protocol(
|
||||
])
|
||||
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 3}})
|
||||
|
||||
# 🆕 在每次循环结束时添加完成日志
|
||||
if repeats > 1:
|
||||
remaining_volume = volume_left - current_volume
|
||||
if remaining_volume > 0:
|
||||
end_message = f"✅ 第 {i+1}/{repeats} 次转移完成! 剩余 {remaining_volume:.2f}mL 待转移 ⏳"
|
||||
else:
|
||||
end_message = f"🎉 第 {i+1}/{repeats} 次转移完成! 全部 {volume:.2f}mL 转移完毕 ✨"
|
||||
|
||||
pump_action_sequence.append(create_progress_log_action(end_message))
|
||||
|
||||
volume_left -= current_volume
|
||||
|
||||
return pump_action_sequence
|
||||
|
||||
|
||||
def generate_pump_protocol_with_rinsing(
|
||||
G: nx.DiGraph,
|
||||
from_vessel: str,
|
||||
to_vessel: str,
|
||||
volume: float = 0.0,
|
||||
amount: str = "",
|
||||
time: float = 0.0, # 🔧 修复:统一使用 time
|
||||
viscous: bool = False,
|
||||
rinsing_solvent: str = "",
|
||||
rinsing_volume: float = 0.0,
|
||||
rinsing_repeats: int = 0,
|
||||
solid: bool = False,
|
||||
flowrate: float = 2.5,
|
||||
transfer_flowrate: float = 0.5,
|
||||
rate_spec: str = "",
|
||||
event: str = "",
|
||||
through: str = "",
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
原有的同步版本,添加防冲突机制
|
||||
"""
|
||||
|
||||
# 添加执行锁,防止并发调用
|
||||
import threading
|
||||
if not hasattr(generate_pump_protocol_with_rinsing, '_lock'):
|
||||
generate_pump_protocol_with_rinsing._lock = threading.Lock()
|
||||
|
||||
with generate_pump_protocol_with_rinsing._lock:
|
||||
debug_print("=" * 60)
|
||||
debug_print(f"PUMP_TRANSFER: 🚀 开始生成协议 (同步版本)")
|
||||
debug_print(f" 📍 路径: {from_vessel} -> {to_vessel}")
|
||||
debug_print(f" 🕐 时间戳: {time_module.time()}")
|
||||
debug_print(f" 🔒 获得执行锁")
|
||||
debug_print("=" * 60)
|
||||
|
||||
# 短暂延迟,避免快速重复调用
|
||||
time_module.sleep(0.01)
|
||||
|
||||
debug_print("🔍 步骤1: 开始体积处理...")
|
||||
|
||||
# 1. 处理体积参数
|
||||
final_volume = volume
|
||||
debug_print(f"📋 初始设置: final_volume = {final_volume}")
|
||||
|
||||
# 🔧 修复:如果volume为0(ROS2传入的空值),从容器读取实际体积
|
||||
if volume == 0.0:
|
||||
debug_print("🎯 检测到 volume=0.0,开始自动体积检测...")
|
||||
|
||||
# 直接从源容器读取实际体积
|
||||
actual_volume = get_vessel_liquid_volume(G, from_vessel)
|
||||
debug_print(f"📖 从容器 '{from_vessel}' 读取到体积: {actual_volume}mL")
|
||||
|
||||
if actual_volume > 0:
|
||||
final_volume = actual_volume
|
||||
debug_print(f"✅ 成功设置体积为: {final_volume}mL")
|
||||
else:
|
||||
final_volume = 10.0 # 如果读取失败,使用默认值
|
||||
logger.warning(f"⚠️ 无法从容器读取体积,使用默认值: {final_volume}mL")
|
||||
else:
|
||||
debug_print(f"📌 体积非零,直接使用: {final_volume}mL")
|
||||
|
||||
# 处理 amount 参数
|
||||
if amount and amount.strip():
|
||||
debug_print(f"🔍 检测到 amount 参数: '{amount}',开始解析...")
|
||||
parsed_volume = _parse_amount_to_volume(amount)
|
||||
debug_print(f"📖 从 amount 解析得到体积: {parsed_volume}mL")
|
||||
|
||||
if parsed_volume > 0:
|
||||
final_volume = parsed_volume
|
||||
debug_print(f"✅ 使用从 amount 解析的体积: {final_volume}mL")
|
||||
elif parsed_volume == 0.0 and amount.lower().strip() == "all":
|
||||
debug_print("🎯 检测到 amount='all',从容器读取全部体积...")
|
||||
actual_volume = get_vessel_liquid_volume(G, from_vessel)
|
||||
if actual_volume > 0:
|
||||
final_volume = actual_volume
|
||||
debug_print(f"✅ amount='all',设置体积为: {final_volume}mL")
|
||||
|
||||
# 最终体积验证
|
||||
debug_print(f"🔍 步骤2: 最终体积验证...")
|
||||
if final_volume <= 0:
|
||||
logger.error(f"❌ 体积无效: {final_volume}mL")
|
||||
final_volume = 10.0
|
||||
logger.warning(f"⚠️ 强制设置为默认值: {final_volume}mL")
|
||||
|
||||
debug_print(f"✅ 最终确定体积: {final_volume}mL")
|
||||
|
||||
# 2. 处理流速参数
|
||||
debug_print(f"🔍 步骤3: 处理流速参数...")
|
||||
debug_print(f" - 原始 flowrate: {flowrate}")
|
||||
debug_print(f" - 原始 transfer_flowrate: {transfer_flowrate}")
|
||||
|
||||
final_flowrate = flowrate if flowrate > 0 else 2.5
|
||||
final_transfer_flowrate = transfer_flowrate if transfer_flowrate > 0 else 0.5
|
||||
|
||||
if flowrate <= 0:
|
||||
logger.warning(f"⚠️ flowrate <= 0,修正为: {final_flowrate}mL/s")
|
||||
if transfer_flowrate <= 0:
|
||||
logger.warning(f"⚠️ transfer_flowrate <= 0,修正为: {final_transfer_flowrate}mL/s")
|
||||
|
||||
debug_print(f"✅ 修正后流速: flowrate={final_flowrate}mL/s, transfer_flowrate={final_transfer_flowrate}mL/s")
|
||||
|
||||
# 3. 根据时间计算流速
|
||||
if time > 0 and final_volume > 0:
|
||||
debug_print(f"🔍 步骤4: 根据时间计算流速...")
|
||||
calculated_flowrate = final_volume / time
|
||||
debug_print(f" - 计算得到流速: {calculated_flowrate}mL/s")
|
||||
|
||||
if flowrate <= 0 or flowrate == 2.5:
|
||||
final_flowrate = min(calculated_flowrate, 10.0)
|
||||
debug_print(f" - 调整 flowrate 为: {final_flowrate}mL/s")
|
||||
if transfer_flowrate <= 0 or transfer_flowrate == 0.5:
|
||||
final_transfer_flowrate = min(calculated_flowrate, 5.0)
|
||||
debug_print(f" - 调整 transfer_flowrate 为: {final_transfer_flowrate}mL/s")
|
||||
|
||||
# 4. 根据速度规格调整
|
||||
if rate_spec:
|
||||
debug_print(f"🔍 步骤5: 根据速度规格调整...")
|
||||
debug_print(f" - 速度规格: '{rate_spec}'")
|
||||
|
||||
if rate_spec == "dropwise":
|
||||
final_flowrate = min(final_flowrate, 0.1)
|
||||
final_transfer_flowrate = min(final_transfer_flowrate, 0.1)
|
||||
debug_print(f" - dropwise模式,流速调整为: {final_flowrate}mL/s")
|
||||
elif rate_spec == "slowly":
|
||||
final_flowrate = min(final_flowrate, 0.5)
|
||||
final_transfer_flowrate = min(final_transfer_flowrate, 0.3)
|
||||
debug_print(f" - slowly模式,流速调整为: {final_flowrate}mL/s")
|
||||
elif rate_spec == "quickly":
|
||||
final_flowrate = max(final_flowrate, 5.0)
|
||||
final_transfer_flowrate = max(final_transfer_flowrate, 2.0)
|
||||
debug_print(f" - quickly模式,流速调整为: {final_flowrate}mL/s")
|
||||
|
||||
try:
|
||||
# 🆕 修复:在这里调用带有循环日志的generate_pump_protocol_with_loop_logging函数
|
||||
pump_action_sequence = generate_pump_protocol_with_loop_logging(
|
||||
G, from_vessel, to_vessel, final_volume,
|
||||
final_flowrate, final_transfer_flowrate
|
||||
)
|
||||
|
||||
debug_print(f"🔓 释放执行锁")
|
||||
return pump_action_sequence
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 协议生成失败: {str(e)}")
|
||||
return [
|
||||
{
|
||||
"device_id": "system",
|
||||
"action_name": "log_message",
|
||||
"action_kwargs": {
|
||||
"message": f"❌ 协议生成失败: {str(e)}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def generate_pump_protocol_with_loop_logging(
|
||||
G: nx.DiGraph,
|
||||
from_vessel: str,
|
||||
to_vessel: str,
|
||||
volume: float,
|
||||
flowrate: float = 2.5,
|
||||
transfer_flowrate: float = 0.5,
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成泵操作的动作序列 - 带循环日志版本
|
||||
🔧 修复:正确处理包含电磁阀的路径,并在合适时机打印循环日志
|
||||
"""
|
||||
pump_action_sequence = []
|
||||
nodes = G.nodes(data=True)
|
||||
|
||||
# 验证输入参数
|
||||
if volume <= 0:
|
||||
logger.error(f"无效的体积参数: {volume}mL")
|
||||
return pump_action_sequence
|
||||
|
||||
if flowrate <= 0:
|
||||
flowrate = 2.5
|
||||
logger.warning(f"flowrate <= 0,使用默认值 {flowrate}mL/s")
|
||||
|
||||
if transfer_flowrate <= 0:
|
||||
transfer_flowrate = 0.5
|
||||
logger.warning(f"transfer_flowrate <= 0,使用默认值 {transfer_flowrate}mL/s")
|
||||
|
||||
# 验证容器存在
|
||||
if from_vessel not in G.nodes():
|
||||
logger.error(f"源容器 '{from_vessel}' 不存在")
|
||||
return pump_action_sequence
|
||||
|
||||
if to_vessel not in G.nodes():
|
||||
logger.error(f"目标容器 '{to_vessel}' 不存在")
|
||||
return pump_action_sequence
|
||||
|
||||
try:
|
||||
shortest_path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
|
||||
debug_print(f"PUMP_TRANSFER: 路径 {from_vessel} -> {to_vessel}: {shortest_path}")
|
||||
except nx.NetworkXNoPath:
|
||||
logger.error(f"无法找到从 '{from_vessel}' 到 '{to_vessel}' 的路径")
|
||||
return pump_action_sequence
|
||||
|
||||
# 🔧 关键修复:正确构建泵骨架,排除容器和电磁阀
|
||||
pump_backbone = []
|
||||
for node in shortest_path:
|
||||
# 跳过起始和结束容器
|
||||
if node == from_vessel or node == to_vessel:
|
||||
continue
|
||||
|
||||
# 跳过电磁阀(电磁阀不参与泵操作)
|
||||
node_data = G.nodes.get(node, {})
|
||||
node_class = node_data.get("class", "") or ""
|
||||
if ("solenoid" in node_class.lower() or "solenoid_valve" in node.lower()):
|
||||
debug_print(f"PUMP_TRANSFER: 跳过电磁阀 {node}")
|
||||
continue
|
||||
|
||||
# 只包含多通阀和泵
|
||||
if ("multiway" in node_class.lower() or "valve" in node_class.lower() or "pump" in node_class.lower()):
|
||||
pump_backbone.append(node)
|
||||
|
||||
debug_print(f"PUMP_TRANSFER: 过滤后的泵骨架: {pump_backbone}")
|
||||
|
||||
if not pump_backbone:
|
||||
debug_print("PUMP_TRANSFER: 没有泵骨架节点,可能是直接容器连接或只有电磁阀")
|
||||
return pump_action_sequence
|
||||
|
||||
if transfer_flowrate == 0:
|
||||
transfer_flowrate = flowrate
|
||||
|
||||
try:
|
||||
pumps_from_node, valve_from_node = build_pump_valve_maps(G, pump_backbone)
|
||||
except Exception as e:
|
||||
debug_print(f"PUMP_TRANSFER: 构建泵-阀门映射失败: {str(e)}")
|
||||
return pump_action_sequence
|
||||
|
||||
if not pumps_from_node:
|
||||
debug_print("PUMP_TRANSFER: 没有可用的泵映射")
|
||||
return pump_action_sequence
|
||||
|
||||
# 🔧 修复:安全地获取最小转移体积
|
||||
try:
|
||||
min_transfer_volumes = []
|
||||
for node in pump_backbone:
|
||||
if node in pumps_from_node:
|
||||
pump_node = pumps_from_node[node]
|
||||
if pump_node in nodes:
|
||||
pump_config = nodes[pump_node].get("config", {})
|
||||
max_volume = pump_config.get("max_volume")
|
||||
if max_volume is not None:
|
||||
min_transfer_volumes.append(max_volume)
|
||||
|
||||
if min_transfer_volumes:
|
||||
min_transfer_volume = min(min_transfer_volumes)
|
||||
else:
|
||||
min_transfer_volume = 25.0 # 默认值
|
||||
debug_print(f"PUMP_TRANSFER: 无法获取泵的最大体积,使用默认值: {min_transfer_volume}mL")
|
||||
except Exception as e:
|
||||
debug_print(f"PUMP_TRANSFER: 获取最小转移体积失败: {str(e)}")
|
||||
min_transfer_volume = 25.0 # 默认值
|
||||
|
||||
repeats = int(np.ceil(volume / min_transfer_volume))
|
||||
|
||||
if repeats > 1 and (from_vessel.startswith("pump") or to_vessel.startswith("pump")):
|
||||
logger.error("Cannot transfer volume larger than min_transfer_volume between two pumps.")
|
||||
return pump_action_sequence
|
||||
|
||||
volume_left = volume
|
||||
debug_print(f"PUMP_TRANSFER: 需要 {repeats} 次转移,单次最大体积 {min_transfer_volume} mL")
|
||||
|
||||
# 🆕 只在开头打印总体概览
|
||||
if repeats > 1:
|
||||
debug_print(f"🔄 分批转移概览: 总体积 {volume:.2f}mL,需要 {repeats} 次转移")
|
||||
logger.info(f"🔄 分批转移概览: 总体积 {volume:.2f}mL,需要 {repeats} 次转移")
|
||||
|
||||
# 🔧 创建一个自定义的wait动作,用于在执行时打印日志
|
||||
def create_progress_log_action(message: str) -> Dict[str, Any]:
|
||||
"""创建一个特殊的等待动作,在执行时打印进度日志"""
|
||||
return {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 0.1, # 很短的等待时间
|
||||
"progress_message": message # 自定义字段,用于进度日志
|
||||
}
|
||||
}
|
||||
|
||||
# 生成泵操作序列
|
||||
for i in range(repeats):
|
||||
current_volume = min(volume_left, min_transfer_volume)
|
||||
|
||||
# 🆕 在每次循环开始时添加进度日志
|
||||
if repeats > 1:
|
||||
start_message = f"🚀 准备开始第 {i+1}/{repeats} 次转移: {current_volume:.2f}mL ({from_vessel} → {to_vessel}) 🚰"
|
||||
pump_action_sequence.append(create_progress_log_action(start_message))
|
||||
|
||||
# 🔧 修复:安全地获取边数据
|
||||
def get_safe_edge_data(node_a, node_b, key):
|
||||
try:
|
||||
edge_data = G.get_edge_data(node_a, node_b)
|
||||
if edge_data and "port" in edge_data:
|
||||
port_data = edge_data["port"]
|
||||
if isinstance(port_data, dict) and key in port_data:
|
||||
return port_data[key]
|
||||
return "default"
|
||||
except Exception as e:
|
||||
debug_print(f"PUMP_TRANSFER: 获取边数据失败 {node_a}->{node_b}: {str(e)}")
|
||||
return "default"
|
||||
|
||||
# 从源容器吸液
|
||||
if not from_vessel.startswith("pump") and pump_backbone:
|
||||
first_pump_node = pump_backbone[0]
|
||||
if first_pump_node in valve_from_node and first_pump_node in pumps_from_node:
|
||||
port_command = get_safe_edge_data(first_pump_node, from_vessel, first_pump_node)
|
||||
pump_action_sequence.extend([
|
||||
{
|
||||
"device_id": valve_from_node[first_pump_node],
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {
|
||||
"command": port_command
|
||||
}
|
||||
},
|
||||
{
|
||||
"device_id": pumps_from_node[first_pump_node],
|
||||
"action_name": "set_position",
|
||||
"action_kwargs": {
|
||||
"position": float(current_volume),
|
||||
"max_velocity": transfer_flowrate
|
||||
}
|
||||
}
|
||||
])
|
||||
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 3}})
|
||||
|
||||
# 泵间转移
|
||||
for nodeA, nodeB in zip(pump_backbone[:-1], pump_backbone[1:]):
|
||||
if nodeA in valve_from_node and nodeB in valve_from_node and nodeA in pumps_from_node and nodeB in pumps_from_node:
|
||||
port_a = get_safe_edge_data(nodeA, nodeB, nodeA)
|
||||
port_b = get_safe_edge_data(nodeB, nodeA, nodeB)
|
||||
|
||||
pump_action_sequence.append([
|
||||
{
|
||||
"device_id": valve_from_node[nodeA],
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {
|
||||
"command": port_a
|
||||
}
|
||||
},
|
||||
{
|
||||
"device_id": valve_from_node[nodeB],
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {
|
||||
"command": port_b
|
||||
}
|
||||
}
|
||||
])
|
||||
pump_action_sequence.append([
|
||||
{
|
||||
"device_id": pumps_from_node[nodeA],
|
||||
"action_name": "set_position",
|
||||
"action_kwargs": {
|
||||
"position": 0.0,
|
||||
"max_velocity": transfer_flowrate
|
||||
}
|
||||
},
|
||||
{
|
||||
"device_id": pumps_from_node[nodeB],
|
||||
"action_name": "set_position",
|
||||
"action_kwargs": {
|
||||
"position": float(current_volume),
|
||||
"max_velocity": transfer_flowrate
|
||||
}
|
||||
}
|
||||
])
|
||||
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 3}})
|
||||
|
||||
# 排液到目标容器
|
||||
if not to_vessel.startswith("pump") and pump_backbone:
|
||||
last_pump_node = pump_backbone[-1]
|
||||
if last_pump_node in valve_from_node and last_pump_node in pumps_from_node:
|
||||
port_command = get_safe_edge_data(last_pump_node, to_vessel, last_pump_node)
|
||||
pump_action_sequence.extend([
|
||||
{
|
||||
"device_id": valve_from_node[last_pump_node],
|
||||
"action_name": "set_valve_position",
|
||||
"action_kwargs": {
|
||||
"command": port_command
|
||||
}
|
||||
},
|
||||
{
|
||||
"device_id": pumps_from_node[last_pump_node],
|
||||
"action_name": "set_position",
|
||||
"action_kwargs": {
|
||||
"position": 0.0,
|
||||
"max_velocity": flowrate
|
||||
}
|
||||
}
|
||||
])
|
||||
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 3}})
|
||||
|
||||
# 🆕 在每次循环结束时添加完成日志
|
||||
if repeats > 1:
|
||||
remaining_volume = volume_left - current_volume
|
||||
if remaining_volume > 0:
|
||||
end_message = f"✅ 第 {i+1}/{repeats} 次转移完成! 剩余 {remaining_volume:.2f}mL 待转移 ⏳"
|
||||
else:
|
||||
end_message = f"🎉 第 {i+1}/{repeats} 次转移完成! 全部 {volume:.2f}mL 转移完毕 ✨"
|
||||
|
||||
pump_action_sequence.append(create_progress_log_action(end_message))
|
||||
|
||||
volume_left -= current_volume
|
||||
|
||||
return pump_action_sequence
|
||||
@@ -891,58 +1331,386 @@ def generate_pump_protocol_with_rinsing(
|
||||
final_flowrate = max(final_flowrate, 5.0)
|
||||
final_transfer_flowrate = max(final_transfer_flowrate, 2.0)
|
||||
debug_print(f" - quickly模式,流速调整为: {final_flowrate}mL/s")
|
||||
|
||||
# # 5. 处理冲洗参数
|
||||
# debug_print(f"🔍 步骤6: 处理冲洗参数...")
|
||||
# final_rinsing_solvent = rinsing_solvent
|
||||
# final_rinsing_volume = rinsing_volume if rinsing_volume > 0 else 5.0
|
||||
# final_rinsing_repeats = rinsing_repeats if rinsing_repeats > 0 else 2
|
||||
|
||||
# if rinsing_volume <= 0:
|
||||
# logger.warning(f"⚠️ rinsing_volume <= 0,修正为: {final_rinsing_volume}mL")
|
||||
# if rinsing_repeats <= 0:
|
||||
# logger.warning(f"⚠️ rinsing_repeats <= 0,修正为: {final_rinsing_repeats}次")
|
||||
|
||||
# # 根据物理属性调整冲洗参数
|
||||
# if viscous or solid:
|
||||
# final_rinsing_repeats = max(final_rinsing_repeats, 3)
|
||||
# final_rinsing_volume = max(final_rinsing_volume, 10.0)
|
||||
# debug_print(f"🧪 粘稠/固体物质,调整冲洗参数:{final_rinsing_repeats}次,{final_rinsing_volume}mL")
|
||||
|
||||
# 参数总结
|
||||
debug_print("📊 最终参数总结:")
|
||||
debug_print(f" - 体积: {final_volume}mL")
|
||||
debug_print(f" - 流速: {final_flowrate}mL/s")
|
||||
debug_print(f" - 转移流速: {final_transfer_flowrate}mL/s")
|
||||
# debug_print(f" - 冲洗溶剂: '{final_rinsing_solvent}'")
|
||||
# debug_print(f" - 冲洗体积: {final_rinsing_volume}mL")
|
||||
# debug_print(f" - 冲洗次数: {final_rinsing_repeats}次")
|
||||
|
||||
# ========== 执行基础转移 ==========
|
||||
|
||||
debug_print("🔧 步骤7: 开始执行基础转移...")
|
||||
|
||||
try:
|
||||
debug_print(f" - 调用 generate_pump_protocol...")
|
||||
debug_print(f" - 参数: G, '{from_vessel}', '{to_vessel}', {final_volume}, {final_flowrate}, {final_transfer_flowrate}")
|
||||
|
||||
# # 5. 处理冲洗参数
|
||||
# debug_print(f"🔍 步骤6: 处理冲洗参数...")
|
||||
# final_rinsing_solvent = rinsing_solvent
|
||||
# final_rinsing_volume = rinsing_volume if rinsing_volume > 0 else 5.0
|
||||
# final_rinsing_repeats = rinsing_repeats if rinsing_repeats > 0 else 2
|
||||
pump_action_sequence = generate_pump_protocol(
|
||||
G, from_vessel, to_vessel, final_volume,
|
||||
final_flowrate, final_transfer_flowrate
|
||||
)
|
||||
|
||||
# if rinsing_volume <= 0:
|
||||
# logger.warning(f"⚠️ rinsing_volume <= 0,修正为: {final_rinsing_volume}mL")
|
||||
# if rinsing_repeats <= 0:
|
||||
# logger.warning(f"⚠️ rinsing_repeats <= 0,修正为: {final_rinsing_repeats}次")
|
||||
debug_print(f" - generate_pump_protocol 返回结果:")
|
||||
debug_print(f" - 动作序列长度: {len(pump_action_sequence)}")
|
||||
debug_print(f" - 动作序列是否为空: {len(pump_action_sequence) == 0}")
|
||||
|
||||
# # 根据物理属性调整冲洗参数
|
||||
# if viscous or solid:
|
||||
# final_rinsing_repeats = max(final_rinsing_repeats, 3)
|
||||
# final_rinsing_volume = max(final_rinsing_volume, 10.0)
|
||||
# debug_print(f"🧪 粘稠/固体物质,调整冲洗参数:{final_rinsing_repeats}次,{final_rinsing_volume}mL")
|
||||
|
||||
try:
|
||||
pump_action_sequence = generate_pump_protocol(
|
||||
G, from_vessel, to_vessel, final_volume,
|
||||
flowrate, transfer_flowrate
|
||||
)
|
||||
if not pump_action_sequence:
|
||||
debug_print("❌ 基础转移协议生成为空,可能是路径问题")
|
||||
debug_print(f" - 源容器存在: {from_vessel in G.nodes()}")
|
||||
debug_print(f" - 目标容器存在: {to_vessel in G.nodes()}")
|
||||
|
||||
# 为每个动作添加唯一标识
|
||||
# for i, action in enumerate(pump_action_sequence):
|
||||
# if isinstance(action, dict):
|
||||
# action['_protocol_id'] = protocol_id
|
||||
# action['_action_sequence'] = i
|
||||
# elif isinstance(action, list):
|
||||
# for j, sub_action in enumerate(action):
|
||||
# if isinstance(sub_action, dict):
|
||||
# sub_action['_protocol_id'] = protocol_id
|
||||
# sub_action['_action_sequence'] = f"{i}_{j}"
|
||||
#
|
||||
# debug_print(f"📊 协议 {protocol_id} 生成完成,共 {len(pump_action_sequence)} 个动作")
|
||||
debug_print(f"🔓 释放执行锁")
|
||||
return pump_action_sequence
|
||||
if from_vessel in G.nodes() and to_vessel in G.nodes():
|
||||
try:
|
||||
path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
|
||||
debug_print(f" - 路径存在: {path}")
|
||||
except Exception as path_error:
|
||||
debug_print(f" - 无法找到路径: {str(path_error)}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 协议生成失败: {str(e)}")
|
||||
return [
|
||||
{
|
||||
"device_id": "system",
|
||||
"action_name": "log_message",
|
||||
"action_kwargs": {
|
||||
"message": f"❌ 协议生成失败: {str(e)}"
|
||||
},
|
||||
'_protocol_id': protocol_id,
|
||||
'_action_sequence': 0
|
||||
"message": f"⚠️ 路径问题,无法转移: {final_volume}mL 从 {from_vessel} 到 {to_vessel}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
debug_print(f"✅ 基础转移生成了 {len(pump_action_sequence)} 个动作")
|
||||
|
||||
# 打印前几个动作用于调试
|
||||
if len(pump_action_sequence) > 0:
|
||||
debug_print("🔍 前几个动作预览:")
|
||||
for i, action in enumerate(pump_action_sequence[:3]):
|
||||
debug_print(f" 动作 {i+1}: {action}")
|
||||
if len(pump_action_sequence) > 3:
|
||||
debug_print(f" ... 还有 {len(pump_action_sequence) - 3} 个动作")
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"❌ 基础转移失败: {str(e)}")
|
||||
import traceback
|
||||
debug_print(f"详细错误: {traceback.format_exc()}")
|
||||
return [
|
||||
{
|
||||
"device_id": "system",
|
||||
"action_name": "log_message",
|
||||
"action_kwargs": {
|
||||
"message": f"❌ 转移失败: {final_volume}mL 从 {from_vessel} 到 {to_vessel}, 错误: {str(e)}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
# ========== 执行冲洗操作 ==========
|
||||
|
||||
# debug_print("🔧 步骤8: 检查冲洗操作...")
|
||||
|
||||
# if final_rinsing_solvent and final_rinsing_solvent.strip() and final_rinsing_repeats > 0:
|
||||
# debug_print(f"🧽 开始冲洗操作,溶剂: '{final_rinsing_solvent}'")
|
||||
|
||||
# try:
|
||||
# if final_rinsing_solvent.strip() != "air":
|
||||
# debug_print(" - 执行液体冲洗...")
|
||||
# rinsing_actions = _generate_rinsing_sequence(
|
||||
# G, from_vessel, to_vessel, final_rinsing_solvent,
|
||||
# final_rinsing_volume, final_rinsing_repeats,
|
||||
# final_flowrate, final_transfer_flowrate
|
||||
# )
|
||||
# pump_action_sequence.extend(rinsing_actions)
|
||||
# debug_print(f" - 添加了 {len(rinsing_actions)} 个冲洗动作")
|
||||
# else:
|
||||
# debug_print(" - 执行空气冲洗...")
|
||||
# air_rinsing_actions = _generate_air_rinsing_sequence(
|
||||
# G, from_vessel, to_vessel, final_rinsing_volume, final_rinsing_repeats,
|
||||
# final_flowrate, final_transfer_flowrate
|
||||
# )
|
||||
# pump_action_sequence.extend(air_rinsing_actions)
|
||||
# debug_print(f" - 添加了 {len(air_rinsing_actions)} 个空气冲洗动作")
|
||||
# except Exception as e:
|
||||
# debug_print(f"⚠️ 冲洗操作失败: {str(e)},跳过冲洗")
|
||||
# else:
|
||||
# debug_print(f"⏭️ 跳过冲洗操作")
|
||||
# debug_print(f" - 溶剂: '{final_rinsing_solvent}'")
|
||||
# debug_print(f" - 次数: {final_rinsing_repeats}")
|
||||
# debug_print(f" - 条件满足: {bool(final_rinsing_solvent and final_rinsing_solvent.strip() and final_rinsing_repeats > 0)}")
|
||||
|
||||
# ========== 最终结果 ==========
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print(f"🎉 PUMP_TRANSFER: 协议生成完成")
|
||||
debug_print(f" 📊 总动作数: {len(pump_action_sequence)}")
|
||||
debug_print(f" 📋 最终体积: {final_volume}mL")
|
||||
debug_print(f" 🚀 执行路径: {from_vessel} -> {to_vessel}")
|
||||
|
||||
# 最终验证
|
||||
if len(pump_action_sequence) == 0:
|
||||
debug_print("🚨 协议生成结果为空!这是异常情况")
|
||||
return [
|
||||
{
|
||||
"device_id": "system",
|
||||
"action_name": "log_message",
|
||||
"action_kwargs": {
|
||||
"message": f"🚨 协议生成失败: 无法生成任何动作序列"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
debug_print("=" * 60)
|
||||
return pump_action_sequence
|
||||
|
||||
|
||||
async def generate_pump_protocol_with_rinsing_async(
|
||||
G: nx.DiGraph,
|
||||
from_vessel: str,
|
||||
to_vessel: str,
|
||||
volume: float = 0.0,
|
||||
amount: str = "",
|
||||
time: float = 0.0,
|
||||
viscous: bool = False,
|
||||
rinsing_solvent: str = "",
|
||||
rinsing_volume: float = 0.0,
|
||||
rinsing_repeats: int = 0,
|
||||
solid: bool = False,
|
||||
flowrate: float = 2.5,
|
||||
transfer_flowrate: float = 0.5,
|
||||
rate_spec: str = "",
|
||||
event: str = "",
|
||||
through: str = "",
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
异步版本的泵转移协议生成器,避免并发问题
|
||||
"""
|
||||
debug_print("=" * 60)
|
||||
debug_print(f"PUMP_TRANSFER: 🚀 开始生成协议 (异步版本)")
|
||||
debug_print(f" 📍 路径: {from_vessel} -> {to_vessel}")
|
||||
debug_print(f" 🕐 时间戳: {time_module.time()}")
|
||||
debug_print("=" * 60)
|
||||
|
||||
# 添加唯一标识符
|
||||
protocol_id = f"pump_transfer_{int(time_module.time() * 1000000)}"
|
||||
debug_print(f"📋 协议ID: {protocol_id}")
|
||||
|
||||
# 调用原有的同步版本
|
||||
result = generate_pump_protocol_with_rinsing(
|
||||
G, from_vessel, to_vessel, volume, amount, time, viscous,
|
||||
rinsing_solvent, rinsing_volume, rinsing_repeats, solid,
|
||||
flowrate, transfer_flowrate, rate_spec, event, through, **kwargs
|
||||
)
|
||||
|
||||
# 为每个动作添加唯一标识
|
||||
for i, action in enumerate(result):
|
||||
if isinstance(action, dict):
|
||||
action['_protocol_id'] = protocol_id
|
||||
action['_action_sequence'] = i
|
||||
action['_timestamp'] = time_module.time()
|
||||
|
||||
debug_print(f"📊 协议 {protocol_id} 生成完成,共 {len(result)} 个动作")
|
||||
return result
|
||||
|
||||
# 保持原有的同步版本兼容性
|
||||
def generate_pump_protocol_with_rinsing(
|
||||
G: nx.DiGraph,
|
||||
from_vessel: str,
|
||||
to_vessel: str,
|
||||
volume: float = 0.0,
|
||||
amount: str = "",
|
||||
time: float = 0.0,
|
||||
viscous: bool = False,
|
||||
rinsing_solvent: str = "",
|
||||
rinsing_volume: float = 0.0,
|
||||
rinsing_repeats: int = 0,
|
||||
solid: bool = False,
|
||||
flowrate: float = 2.5,
|
||||
transfer_flowrate: float = 0.5,
|
||||
rate_spec: str = "",
|
||||
event: str = "",
|
||||
through: str = "",
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
原有的同步版本,添加防冲突机制
|
||||
"""
|
||||
|
||||
# 添加执行锁,防止并发调用
|
||||
import threading
|
||||
if not hasattr(generate_pump_protocol_with_rinsing, '_lock'):
|
||||
generate_pump_protocol_with_rinsing._lock = threading.Lock()
|
||||
|
||||
with generate_pump_protocol_with_rinsing._lock:
|
||||
debug_print("=" * 60)
|
||||
debug_print(f"PUMP_TRANSFER: 🚀 开始生成协议 (同步版本)")
|
||||
debug_print(f" 📍 路径: {from_vessel} -> {to_vessel}")
|
||||
debug_print(f" 🕐 时间戳: {time_module.time()}")
|
||||
debug_print(f" 🔒 获得执行锁")
|
||||
debug_print("=" * 60)
|
||||
|
||||
# 短暂延迟,避免快速重复调用
|
||||
time_module.sleep(0.01)
|
||||
|
||||
debug_print("🔍 步骤1: 开始体积处理...")
|
||||
|
||||
# 1. 处理体积参数
|
||||
final_volume = volume
|
||||
debug_print(f"📋 初始设置: final_volume = {final_volume}")
|
||||
|
||||
# 🔧 修复:如果volume为0(ROS2传入的空值),从容器读取实际体积
|
||||
if volume == 0.0:
|
||||
debug_print("🎯 检测到 volume=0.0,开始自动体积检测...")
|
||||
|
||||
# 直接从源容器读取实际体积
|
||||
actual_volume = get_vessel_liquid_volume(G, from_vessel)
|
||||
debug_print(f"📖 从容器 '{from_vessel}' 读取到体积: {actual_volume}mL")
|
||||
|
||||
if actual_volume > 0:
|
||||
final_volume = actual_volume
|
||||
debug_print(f"✅ 成功设置体积为: {final_volume}mL")
|
||||
else:
|
||||
final_volume = 10.0 # 如果读取失败,使用默认值
|
||||
logger.warning(f"⚠️ 无法从容器读取体积,使用默认值: {final_volume}mL")
|
||||
else:
|
||||
debug_print(f"📌 体积非零,直接使用: {final_volume}mL")
|
||||
|
||||
# 处理 amount 参数
|
||||
if amount and amount.strip():
|
||||
debug_print(f"🔍 检测到 amount 参数: '{amount}',开始解析...")
|
||||
parsed_volume = _parse_amount_to_volume(amount)
|
||||
debug_print(f"📖 从 amount 解析得到体积: {parsed_volume}mL")
|
||||
|
||||
if parsed_volume > 0:
|
||||
final_volume = parsed_volume
|
||||
debug_print(f"✅ 使用从 amount 解析的体积: {final_volume}mL")
|
||||
elif parsed_volume == 0.0 and amount.lower().strip() == "all":
|
||||
debug_print("🎯 检测到 amount='all',从容器读取全部体积...")
|
||||
actual_volume = get_vessel_liquid_volume(G, from_vessel)
|
||||
if actual_volume > 0:
|
||||
final_volume = actual_volume
|
||||
debug_print(f"✅ amount='all',设置体积为: {final_volume}mL")
|
||||
|
||||
# 最终体积验证
|
||||
debug_print(f"🔍 步骤2: 最终体积验证...")
|
||||
if final_volume <= 0:
|
||||
logger.error(f"❌ 体积无效: {final_volume}mL")
|
||||
final_volume = 10.0
|
||||
logger.warning(f"⚠️ 强制设置为默认值: {final_volume}mL")
|
||||
|
||||
debug_print(f"✅ 最终确定体积: {final_volume}mL")
|
||||
|
||||
# 2. 处理流速参数
|
||||
debug_print(f"🔍 步骤3: 处理流速参数...")
|
||||
debug_print(f" - 原始 flowrate: {flowrate}")
|
||||
debug_print(f" - 原始 transfer_flowrate: {transfer_flowrate}")
|
||||
|
||||
final_flowrate = flowrate if flowrate > 0 else 2.5
|
||||
final_transfer_flowrate = transfer_flowrate if transfer_flowrate > 0 else 0.5
|
||||
|
||||
if flowrate <= 0:
|
||||
logger.warning(f"⚠️ flowrate <= 0,修正为: {final_flowrate}mL/s")
|
||||
if transfer_flowrate <= 0:
|
||||
logger.warning(f"⚠️ transfer_flowrate <= 0,修正为: {final_transfer_flowrate}mL/s")
|
||||
|
||||
debug_print(f"✅ 修正后流速: flowrate={final_flowrate}mL/s, transfer_flowrate={final_transfer_flowrate}mL/s")
|
||||
|
||||
# 3. 根据时间计算流速
|
||||
if time > 0 and final_volume > 0:
|
||||
debug_print(f"🔍 步骤4: 根据时间计算流速...")
|
||||
calculated_flowrate = final_volume / time
|
||||
debug_print(f" - 计算得到流速: {calculated_flowrate}mL/s")
|
||||
|
||||
if flowrate <= 0 or flowrate == 2.5:
|
||||
final_flowrate = min(calculated_flowrate, 10.0)
|
||||
debug_print(f" - 调整 flowrate 为: {final_flowrate}mL/s")
|
||||
if transfer_flowrate <= 0 or transfer_flowrate == 0.5:
|
||||
final_transfer_flowrate = min(calculated_flowrate, 5.0)
|
||||
debug_print(f" - 调整 transfer_flowrate 为: {final_transfer_flowrate}mL/s")
|
||||
|
||||
# 4. 根据速度规格调整
|
||||
if rate_spec:
|
||||
debug_print(f"🔍 步骤5: 根据速度规格调整...")
|
||||
debug_print(f" - 速度规格: '{rate_spec}'")
|
||||
|
||||
if rate_spec == "dropwise":
|
||||
final_flowrate = min(final_flowrate, 0.1)
|
||||
final_transfer_flowrate = min(final_transfer_flowrate, 0.1)
|
||||
debug_print(f" - dropwise模式,流速调整为: {final_flowrate}mL/s")
|
||||
elif rate_spec == "slowly":
|
||||
final_flowrate = min(final_flowrate, 0.5)
|
||||
final_transfer_flowrate = min(final_transfer_flowrate, 0.3)
|
||||
debug_print(f" - slowly模式,流速调整为: {final_flowrate}mL/s")
|
||||
elif rate_spec == "quickly":
|
||||
final_flowrate = max(final_flowrate, 5.0)
|
||||
final_transfer_flowrate = max(final_transfer_flowrate, 2.0)
|
||||
debug_print(f" - quickly模式,流速调整为: {final_flowrate}mL/s")
|
||||
|
||||
# # 5. 处理冲洗参数
|
||||
# debug_print(f"🔍 步骤6: 处理冲洗参数...")
|
||||
# final_rinsing_solvent = rinsing_solvent
|
||||
# final_rinsing_volume = rinsing_volume if rinsing_volume > 0 else 5.0
|
||||
# final_rinsing_repeats = rinsing_repeats if rinsing_repeats > 0 else 2
|
||||
|
||||
# if rinsing_volume <= 0:
|
||||
# logger.warning(f"⚠️ rinsing_volume <= 0,修正为: {final_rinsing_volume}mL")
|
||||
# if rinsing_repeats <= 0:
|
||||
# logger.warning(f"⚠️ rinsing_repeats <= 0,修正为: {final_rinsing_repeats}次")
|
||||
|
||||
# # 根据物理属性调整冲洗参数
|
||||
# if viscous or solid:
|
||||
# final_rinsing_repeats = max(final_rinsing_repeats, 3)
|
||||
# final_rinsing_volume = max(final_rinsing_volume, 10.0)
|
||||
# debug_print(f"🧪 粘稠/固体物质,调整冲洗参数:{final_rinsing_repeats}次,{final_rinsing_volume}mL")
|
||||
|
||||
try:
|
||||
pump_action_sequence = generate_pump_protocol(
|
||||
G, from_vessel, to_vessel, final_volume,
|
||||
flowrate, transfer_flowrate
|
||||
)
|
||||
|
||||
# 为每个动作添加唯一标识
|
||||
# for i, action in enumerate(pump_action_sequence):
|
||||
# if isinstance(action, dict):
|
||||
# action['_protocol_id'] = protocol_id
|
||||
# action['_action_sequence'] = i
|
||||
# elif isinstance(action, list):
|
||||
# for j, sub_action in enumerate(action):
|
||||
# if isinstance(sub_action, dict):
|
||||
# sub_action['_protocol_id'] = protocol_id
|
||||
# sub_action['_action_sequence'] = f"{i}_{j}"
|
||||
#
|
||||
# debug_print(f"📊 协议 {protocol_id} 生成完成,共 {len(pump_action_sequence)} 个动作")
|
||||
debug_print(f"🔓 释放执行锁")
|
||||
return pump_action_sequence
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 协议生成失败: {str(e)}")
|
||||
return [
|
||||
{
|
||||
"device_id": "system",
|
||||
"action_name": "log_message",
|
||||
"action_kwargs": {
|
||||
"message": f"❌ 协议生成失败: {str(e)}"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
def _parse_amount_to_volume(amount: str) -> float:
|
||||
"""解析 amount 字符串为体积"""
|
||||
|
||||
@@ -1,15 +1,67 @@
|
||||
import networkx as nx
|
||||
import re
|
||||
import logging
|
||||
import sys
|
||||
from typing import List, Dict, Any, Union
|
||||
from .pump_protocol import generate_pump_protocol_with_rinsing
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 确保输出编码为UTF-8
|
||||
if hasattr(sys.stdout, 'reconfigure'):
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
sys.stderr.reconfigure(encoding='utf-8')
|
||||
except:
|
||||
pass
|
||||
|
||||
def debug_print(message):
|
||||
"""调试输出"""
|
||||
print(f"[SEPARATE] {message}", flush=True)
|
||||
logger.info(f"[SEPARATE] {message}")
|
||||
"""调试输出函数 - 支持中文"""
|
||||
try:
|
||||
# 确保消息是字符串格式
|
||||
safe_message = str(message)
|
||||
print(f"[分离协议] {safe_message}", flush=True)
|
||||
logger.info(f"[分离协议] {safe_message}")
|
||||
except UnicodeEncodeError:
|
||||
# 如果编码失败,尝试替换不支持的字符
|
||||
safe_message = str(message).encode('utf-8', errors='replace').decode('utf-8')
|
||||
print(f"[分离协议] {safe_message}", flush=True)
|
||||
logger.info(f"[分离协议] {safe_message}")
|
||||
except Exception as e:
|
||||
# 最后的安全措施
|
||||
fallback_message = f"日志输出错误: {repr(message)}"
|
||||
print(f"[分离协议] {fallback_message}", flush=True)
|
||||
logger.info(f"[分离协议] {fallback_message}")
|
||||
|
||||
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
|
||||
"""创建一个动作日志 - 支持中文和emoji"""
|
||||
try:
|
||||
full_message = f"{emoji} {message}"
|
||||
debug_print(full_message)
|
||||
logger.info(full_message)
|
||||
|
||||
return {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 0.1,
|
||||
"log_message": full_message,
|
||||
"progress_message": full_message
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
# 如果emoji有问题,使用纯文本
|
||||
safe_message = f"[日志] {message}"
|
||||
debug_print(safe_message)
|
||||
logger.info(safe_message)
|
||||
|
||||
return {
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {
|
||||
"time": 0.1,
|
||||
"log_message": safe_message,
|
||||
"progress_message": safe_message
|
||||
}
|
||||
}
|
||||
|
||||
def parse_volume_input(volume_input: Union[str, float]) -> float:
|
||||
"""
|
||||
@@ -22,52 +74,58 @@ def parse_volume_input(volume_input: Union[str, float]) -> float:
|
||||
float: 体积(毫升)
|
||||
"""
|
||||
if isinstance(volume_input, (int, float)):
|
||||
debug_print(f"📏 体积输入为数值: {volume_input}")
|
||||
return float(volume_input)
|
||||
|
||||
if not volume_input or not str(volume_input).strip():
|
||||
debug_print(f"⚠️ 体积输入为空,返回 0.0mL")
|
||||
return 0.0
|
||||
|
||||
volume_str = str(volume_input).lower().strip()
|
||||
debug_print(f"解析体积输入: '{volume_str}'")
|
||||
debug_print(f"🔍 解析体积输入: '{volume_str}'")
|
||||
|
||||
# 处理未知体积
|
||||
if volume_str in ['?', 'unknown', 'tbd', 'to be determined']:
|
||||
if volume_str in ['?', 'unknown', 'tbd', 'to be determined', '未知', '待定']:
|
||||
default_volume = 100.0 # 默认100mL
|
||||
debug_print(f"检测到未知体积,使用默认值: {default_volume}mL")
|
||||
debug_print(f"❓ 检测到未知体积,使用默认值: {default_volume}mL")
|
||||
return default_volume
|
||||
|
||||
# 移除空格并提取数字和单位
|
||||
volume_clean = re.sub(r'\s+', '', volume_str)
|
||||
|
||||
# 匹配数字和单位的正则表达式
|
||||
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter)?', volume_clean)
|
||||
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter|毫升|升|微升)?', volume_clean)
|
||||
|
||||
if not match:
|
||||
debug_print(f"⚠️ 无法解析体积: '{volume_str}',使用默认值100mL")
|
||||
debug_print(f"⚠️ 无法解析体积: '{volume_str}',使用默认值 100mL")
|
||||
return 100.0
|
||||
|
||||
value = float(match.group(1))
|
||||
unit = match.group(2) or 'ml' # 默认单位为毫升
|
||||
|
||||
# 转换为毫升
|
||||
if unit in ['l', 'liter']:
|
||||
if unit in ['l', 'liter', '升']:
|
||||
volume = value * 1000.0 # L -> mL
|
||||
elif unit in ['μl', 'ul', 'microliter']:
|
||||
debug_print(f"🔄 体积转换: {value}L -> {volume}mL")
|
||||
elif unit in ['μl', 'ul', 'microliter', '微升']:
|
||||
volume = value / 1000.0 # μL -> mL
|
||||
else: # ml, milliliter 或默认
|
||||
debug_print(f"🔄 体积转换: {value}μL -> {volume}mL")
|
||||
else: # ml, milliliter, 毫升 或默认
|
||||
volume = value # 已经是mL
|
||||
debug_print(f"✅ 体积已为毫升单位: {volume}mL")
|
||||
|
||||
debug_print(f"体积转换: {value}{unit} → {volume}mL")
|
||||
return volume
|
||||
|
||||
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
"""查找溶剂容器"""
|
||||
"""查找溶剂容器,支持多种匹配模式"""
|
||||
if not solvent or not solvent.strip():
|
||||
debug_print("⏭️ 未指定溶剂,跳过溶剂容器查找")
|
||||
return ""
|
||||
|
||||
debug_print(f"查找溶剂 '{solvent}' 的容器...")
|
||||
debug_print(f"🔍 正在查找溶剂 '{solvent}' 的容器...")
|
||||
|
||||
# 🔧 方法1:直接搜索 data.reagent_name 和 config.reagent
|
||||
debug_print(f"📋 方法1: 搜索试剂字段...")
|
||||
for node in G.nodes():
|
||||
node_data = G.nodes[node].get('data', {})
|
||||
node_type = G.nodes[node].get('type', '')
|
||||
@@ -80,16 +138,17 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
|
||||
# 精确匹配
|
||||
if reagent_name == solvent.lower() or config_reagent == solvent.lower():
|
||||
debug_print(f"✅ 通过reagent字段找到容器: {node}")
|
||||
debug_print(f"✅ 通过试剂字段精确匹配找到容器: {node}")
|
||||
return node
|
||||
|
||||
# 模糊匹配
|
||||
if (solvent.lower() in reagent_name and reagent_name) or \
|
||||
(solvent.lower() in config_reagent and config_reagent):
|
||||
debug_print(f"✅ 通过reagent字段模糊匹配到容器: {node}")
|
||||
debug_print(f"✅ 通过试剂字段模糊匹配找到容器: {node}")
|
||||
return node
|
||||
|
||||
# 🔧 方法2:常见的容器命名规则
|
||||
debug_print(f"📋 方法2: 使用命名规则...")
|
||||
solvent_clean = solvent.lower().replace(' ', '_').replace('-', '_')
|
||||
possible_names = [
|
||||
f"flask_{solvent_clean}",
|
||||
@@ -99,9 +158,14 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
f"{solvent_clean}_bottle",
|
||||
f"solvent_{solvent_clean}",
|
||||
f"reagent_{solvent_clean}",
|
||||
f"reagent_bottle_{solvent_clean}"
|
||||
f"reagent_bottle_{solvent_clean}",
|
||||
f"reagent_bottle_1", # 通用试剂瓶
|
||||
f"reagent_bottle_2",
|
||||
f"reagent_bottle_3"
|
||||
]
|
||||
|
||||
debug_print(f"🎯 尝试的容器名称: {possible_names[:5]}... (共 {len(possible_names)} 个)")
|
||||
|
||||
for name in possible_names:
|
||||
if name in G.nodes():
|
||||
node_type = G.nodes[name].get('type', '')
|
||||
@@ -110,53 +174,94 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
||||
return name
|
||||
|
||||
# 🔧 方法3:使用第一个试剂瓶作为备选
|
||||
debug_print(f"📋 方法3: 查找备用试剂瓶...")
|
||||
for node_id in G.nodes():
|
||||
node_data = G.nodes[node_id]
|
||||
if (node_data.get('type') == 'container' and
|
||||
('reagent' in node_id.lower() or 'bottle' in node_id.lower())):
|
||||
debug_print(f"⚠️ 未找到专用容器,使用备选容器: {node_id}")
|
||||
debug_print(f"⚠️ 未找到专用容器,使用备用容器: {node_id}")
|
||||
return node_id
|
||||
|
||||
debug_print(f"⚠️ 未找到溶剂 '{solvent}' 的容器")
|
||||
debug_print(f"❌ 无法找到溶剂 '{solvent}' 的容器")
|
||||
return ""
|
||||
|
||||
def find_separator_device(G: nx.DiGraph, vessel: str) -> str:
|
||||
"""查找分离器设备"""
|
||||
debug_print(f"查找容器 '{vessel}' 对应的分离器设备...")
|
||||
"""查找分离器设备,支持多种查找方式"""
|
||||
debug_print(f"🔍 正在查找容器 '{vessel}' 的分离器设备...")
|
||||
|
||||
# 方法1:查找连接到容器的分离器设备
|
||||
debug_print(f"📋 方法1: 检查连接的分离器...")
|
||||
separator_nodes = []
|
||||
for node in G.nodes():
|
||||
node_class = G.nodes[node].get('class', '').lower()
|
||||
if 'separator' in node_class:
|
||||
separator_nodes.append(node)
|
||||
debug_print(f"📋 发现分离器设备: {node}")
|
||||
|
||||
# 检查是否连接到目标容器
|
||||
if G.has_edge(node, vessel) or G.has_edge(vessel, node):
|
||||
debug_print(f"✅ 找到连接的分离器: {node}")
|
||||
return node
|
||||
|
||||
debug_print(f"📊 找到的分离器总数: {len(separator_nodes)}")
|
||||
|
||||
# 方法2:根据命名规则查找
|
||||
debug_print(f"📋 方法2: 使用命名规则...")
|
||||
possible_names = [
|
||||
f"{vessel}_controller",
|
||||
f"{vessel}_separator",
|
||||
vessel, # 容器本身可能就是分离器
|
||||
"separator_1",
|
||||
"virtual_separator"
|
||||
"virtual_separator",
|
||||
"liquid_handler_1", # 液体处理器也可能用于分离
|
||||
"controller_1"
|
||||
]
|
||||
|
||||
debug_print(f"🎯 尝试的分离器名称: {possible_names}")
|
||||
|
||||
for name in possible_names:
|
||||
if name in G.nodes():
|
||||
node_class = G.nodes[name].get('class', '').lower()
|
||||
if 'separator' in node_class:
|
||||
if 'separator' in node_class or 'controller' in node_class:
|
||||
debug_print(f"✅ 通过命名规则找到分离器: {name}")
|
||||
return name
|
||||
|
||||
# 方法3:查找第一个分离器设备
|
||||
for node in G.nodes():
|
||||
node_class = G.nodes[node].get('class', '').lower()
|
||||
if 'separator' in node_class:
|
||||
debug_print(f"⚠️ 使用第一个分离器设备: {node}")
|
||||
return node
|
||||
debug_print(f"📋 方法3: 使用第一个可用分离器...")
|
||||
if separator_nodes:
|
||||
debug_print(f"⚠️ 使用第一个分离器设备: {separator_nodes[0]}")
|
||||
return separator_nodes[0]
|
||||
|
||||
debug_print(f"⚠️ 未找到分离器设备")
|
||||
debug_print(f"❌ 未找到分离器设备")
|
||||
return ""
|
||||
|
||||
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
|
||||
"""查找连接到指定容器的搅拌器"""
|
||||
debug_print(f"🔍 正在查找与容器 {vessel} 连接的搅拌器...")
|
||||
|
||||
stirrer_nodes = []
|
||||
for node in G.nodes():
|
||||
node_data = G.nodes[node]
|
||||
node_class = node_data.get('class', '') or ''
|
||||
|
||||
if 'stirrer' in node_class.lower():
|
||||
stirrer_nodes.append(node)
|
||||
debug_print(f"📋 发现搅拌器: {node}")
|
||||
|
||||
debug_print(f"📊 找到的搅拌器总数: {len(stirrer_nodes)}")
|
||||
|
||||
# 检查哪个搅拌器与目标容器相连
|
||||
for stirrer in stirrer_nodes:
|
||||
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
|
||||
debug_print(f"✅ 找到连接的搅拌器: {stirrer}")
|
||||
return stirrer
|
||||
|
||||
# 如果没有连接的搅拌器,返回第一个可用的
|
||||
if stirrer_nodes:
|
||||
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个可用的: {stirrer_nodes[0]}")
|
||||
return stirrer_nodes[0]
|
||||
|
||||
debug_print("❌ 未找到搅拌器")
|
||||
return ""
|
||||
|
||||
def generate_separate_protocol(
|
||||
@@ -185,7 +290,7 @@ def generate_separate_protocol(
|
||||
**kwargs
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
生成分离操作的协议序列 - 修复版
|
||||
生成分离操作的协议序列 - 增强中文版
|
||||
|
||||
支持XDL参数格式:
|
||||
- vessel: 分离容器(必需)
|
||||
@@ -206,26 +311,31 @@ def generate_separate_protocol(
|
||||
"""
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print("开始生成分离协议 - 修复版")
|
||||
debug_print(f"原始参数:")
|
||||
debug_print(f" - vessel: '{vessel}'")
|
||||
debug_print(f" - purpose: '{purpose}'")
|
||||
debug_print(f" - product_phase: '{product_phase}'")
|
||||
debug_print(f" - solvent: '{solvent}'")
|
||||
debug_print(f" - volume: {volume} (类型: {type(volume)})")
|
||||
debug_print(f" - repeats: {repeats}")
|
||||
debug_print(f" - product_vessel: '{product_vessel}'")
|
||||
debug_print(f" - waste_vessel: '{waste_vessel}'")
|
||||
debug_print("🧪 开始生成分离协议 - 增强中文版")
|
||||
debug_print(f"📋 原始参数:")
|
||||
debug_print(f" 🥼 容器: '{vessel}'")
|
||||
debug_print(f" 🎯 分离目的: '{purpose}'")
|
||||
debug_print(f" 📊 产物相: '{product_phase}'")
|
||||
debug_print(f" 💧 溶剂: '{solvent}'")
|
||||
debug_print(f" 📏 体积: {volume} (类型: {type(volume)})")
|
||||
debug_print(f" 🔄 重复次数: {repeats}")
|
||||
debug_print(f" 🎯 产物容器: '{product_vessel}'")
|
||||
debug_print(f" 🗑️ 废液容器: '{waste_vessel}'")
|
||||
debug_print(f" 📦 其他参数: {kwargs}")
|
||||
debug_print("=" * 60)
|
||||
|
||||
action_sequence = []
|
||||
|
||||
# === 参数验证和标准化 ===
|
||||
debug_print("步骤1: 参数验证和标准化...")
|
||||
debug_print("🔍 步骤1: 参数验证和标准化...")
|
||||
action_sequence.append(create_action_log(f"开始分离操作 - 容器: {vessel}", "🎬"))
|
||||
action_sequence.append(create_action_log(f"分离目的: {purpose}", "🧪"))
|
||||
action_sequence.append(create_action_log(f"产物相: {product_phase}", "📊"))
|
||||
|
||||
# 统一容器参数
|
||||
final_vessel = vessel or separation_vessel
|
||||
if not final_vessel:
|
||||
debug_print("❌ 必须指定分离容器")
|
||||
raise ValueError("必须指定分离容器 (vessel 或 separation_vessel)")
|
||||
|
||||
final_to_vessel = to_vessel or product_vessel
|
||||
@@ -237,14 +347,18 @@ def generate_separate_protocol(
|
||||
# 🔧 修复:确保repeats至少为1
|
||||
if repeats <= 0:
|
||||
repeats = 1
|
||||
debug_print(f"⚠️ repeats参数 <= 0,自动设置为1")
|
||||
debug_print(f"⚠️ 重复次数参数 <= 0,自动设置为 1")
|
||||
|
||||
debug_print(f"标准化参数:")
|
||||
debug_print(f" - 分离容器: '{final_vessel}'")
|
||||
debug_print(f" - 产物容器: '{final_to_vessel}'")
|
||||
debug_print(f" - 废液容器: '{final_waste_vessel}'")
|
||||
debug_print(f" - 溶剂体积: {final_volume}mL")
|
||||
debug_print(f" - 重复次数: {repeats}")
|
||||
debug_print(f"🔧 标准化后的参数:")
|
||||
debug_print(f" 🥼 分离容器: '{final_vessel}'")
|
||||
debug_print(f" 🎯 产物容器: '{final_to_vessel}'")
|
||||
debug_print(f" 🗑️ 废液容器: '{final_waste_vessel}'")
|
||||
debug_print(f" 📏 溶剂体积: {final_volume}mL")
|
||||
debug_print(f" 🔄 重复次数: {repeats}")
|
||||
|
||||
action_sequence.append(create_action_log(f"分离容器: {final_vessel}", "🧪"))
|
||||
action_sequence.append(create_action_log(f"溶剂体积: {final_volume}mL", "📏"))
|
||||
action_sequence.append(create_action_log(f"重复次数: {repeats}", "🔄"))
|
||||
|
||||
# 验证必需参数
|
||||
if not purpose:
|
||||
@@ -254,66 +368,147 @@ def generate_separate_protocol(
|
||||
if purpose not in ["wash", "extract", "separate"]:
|
||||
debug_print(f"⚠️ 未知的分离目的 '{purpose}',使用默认值 'separate'")
|
||||
purpose = "separate"
|
||||
action_sequence.append(create_action_log(f"未知目的,使用: {purpose}", "⚠️"))
|
||||
if product_phase not in ["top", "bottom"]:
|
||||
debug_print(f"⚠️ 未知的产物相 '{product_phase}',使用默认值 'top'")
|
||||
product_phase = "top"
|
||||
action_sequence.append(create_action_log(f"未知相别,使用: {product_phase}", "⚠️"))
|
||||
|
||||
debug_print("✅ 参数验证通过")
|
||||
action_sequence.append(create_action_log("参数验证通过", "✅"))
|
||||
|
||||
# === 查找设备 ===
|
||||
debug_print("步骤2: 查找设备...")
|
||||
debug_print("🔍 步骤2: 查找设备...")
|
||||
action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
|
||||
|
||||
# 查找分离器设备
|
||||
separator_device = find_separator_device(G, final_vessel)
|
||||
if not separator_device:
|
||||
debug_print("⚠️ 未找到分离器设备,可能无法执行分离操作")
|
||||
if separator_device:
|
||||
action_sequence.append(create_action_log(f"找到分离器设备: {separator_device}", "🧪"))
|
||||
else:
|
||||
debug_print("⚠️ 未找到分离器设备,可能无法执行分离")
|
||||
action_sequence.append(create_action_log("未找到分离器设备", "⚠️"))
|
||||
|
||||
# 查找搅拌器
|
||||
stirrer_device = find_connected_stirrer(G, final_vessel)
|
||||
if stirrer_device:
|
||||
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_device}", "🌪️"))
|
||||
else:
|
||||
action_sequence.append(create_action_log("未找到搅拌器", "⚠️"))
|
||||
|
||||
# 查找溶剂容器(如果需要)
|
||||
solvent_vessel = ""
|
||||
if solvent and solvent.strip():
|
||||
solvent_vessel = find_solvent_vessel(G, solvent)
|
||||
if solvent_vessel:
|
||||
action_sequence.append(create_action_log(f"找到溶剂容器: {solvent_vessel}", "💧"))
|
||||
else:
|
||||
action_sequence.append(create_action_log(f"未找到溶剂容器: {solvent}", "⚠️"))
|
||||
|
||||
debug_print(f"设备映射:")
|
||||
debug_print(f" - 分离器设备: '{separator_device}'")
|
||||
debug_print(f" - 溶剂容器: '{solvent_vessel}'")
|
||||
debug_print(f"📊 设备配置:")
|
||||
debug_print(f" 🧪 分离器设备: '{separator_device}'")
|
||||
debug_print(f" 🌪️ 搅拌器设备: '{stirrer_device}'")
|
||||
debug_print(f" 💧 溶剂容器: '{solvent_vessel}'")
|
||||
|
||||
# === 执行分离流程 ===
|
||||
debug_print("步骤3: 执行分离流程...")
|
||||
debug_print("🔍 步骤3: 执行分离流程...")
|
||||
action_sequence.append(create_action_log("开始分离工作流程", "🎯"))
|
||||
|
||||
try:
|
||||
for repeat_idx in range(repeats):
|
||||
debug_print(f"3.{repeat_idx+1}: 第 {repeat_idx+1}/{repeats} 次分离")
|
||||
cycle_num = repeat_idx + 1
|
||||
debug_print(f"🔄 第{cycle_num}轮: 开始分离循环 {cycle_num}/{repeats}")
|
||||
action_sequence.append(create_action_log(f"分离循环 {cycle_num}/{repeats} 开始", "🔄"))
|
||||
|
||||
# 步骤3.1: 添加溶剂(如果需要)
|
||||
if solvent_vessel and final_volume > 0:
|
||||
debug_print(f"3.{repeat_idx+1}.1: 添加溶剂 {solvent} ({final_volume}mL)")
|
||||
debug_print(f"🔄 第{cycle_num}轮 步骤1: 添加溶剂 {solvent} ({final_volume}mL)")
|
||||
action_sequence.append(create_action_log(f"向分离容器添加 {final_volume}mL {solvent}", "💧"))
|
||||
|
||||
# 使用pump protocol添加溶剂
|
||||
pump_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=solvent_vessel,
|
||||
to_vessel=final_vessel,
|
||||
volume=final_volume,
|
||||
amount="",
|
||||
time=0.0,
|
||||
viscous=False,
|
||||
rinsing_solvent="",
|
||||
rinsing_volume=0.0,
|
||||
rinsing_repeats=0,
|
||||
solid=False,
|
||||
flowrate=2.5,
|
||||
transfer_flowrate=0.5,
|
||||
rate_spec="",
|
||||
event="",
|
||||
through="",
|
||||
**kwargs
|
||||
)
|
||||
action_sequence.extend(pump_actions)
|
||||
debug_print(f"✅ 溶剂添加完成,添加了 {len(pump_actions)} 个动作")
|
||||
try:
|
||||
# 使用pump protocol添加溶剂
|
||||
pump_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G,
|
||||
from_vessel=solvent_vessel,
|
||||
to_vessel=final_vessel,
|
||||
volume=final_volume,
|
||||
amount="",
|
||||
time=0.0,
|
||||
viscous=False,
|
||||
rinsing_solvent="",
|
||||
rinsing_volume=0.0,
|
||||
rinsing_repeats=0,
|
||||
solid=False,
|
||||
flowrate=2.5,
|
||||
transfer_flowrate=0.5,
|
||||
rate_spec="",
|
||||
event="",
|
||||
through="",
|
||||
**kwargs
|
||||
)
|
||||
action_sequence.extend(pump_actions)
|
||||
debug_print(f"✅ 溶剂添加完成,添加了 {len(pump_actions)} 个动作")
|
||||
action_sequence.append(create_action_log(f"溶剂转移完成 ({len(pump_actions)} 个操作)", "✅"))
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"❌ 溶剂添加失败: {str(e)}")
|
||||
action_sequence.append(create_action_log(f"溶剂添加失败: {str(e)}", "❌"))
|
||||
else:
|
||||
debug_print(f"🔄 第{cycle_num}轮 步骤1: 无需添加溶剂")
|
||||
action_sequence.append(create_action_log("无需添加溶剂", "⏭️"))
|
||||
|
||||
# 步骤3.2: 执行分离操作
|
||||
# 步骤3.2: 启动搅拌(如果有搅拌器)
|
||||
if stirrer_device and stir_time > 0:
|
||||
debug_print(f"🔄 第{cycle_num}轮 步骤2: 开始搅拌 ({stir_speed}rpm,持续 {stir_time}s)")
|
||||
action_sequence.append(create_action_log(f"开始搅拌: {stir_speed}rpm,持续 {stir_time}s", "🌪️"))
|
||||
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_device,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {
|
||||
"vessel": final_vessel,
|
||||
"stir_speed": stir_speed,
|
||||
"purpose": f"分离混合 - {purpose}"
|
||||
}
|
||||
})
|
||||
|
||||
# 搅拌等待
|
||||
stir_minutes = stir_time / 60
|
||||
action_sequence.append(create_action_log(f"搅拌中,持续 {stir_minutes:.1f} 分钟", "⏱️"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": stir_time}
|
||||
})
|
||||
|
||||
# 停止搅拌
|
||||
action_sequence.append(create_action_log("停止搅拌器", "🛑"))
|
||||
action_sequence.append({
|
||||
"device_id": stirrer_device,
|
||||
"action_name": "stop_stir",
|
||||
"action_kwargs": {"vessel": final_vessel}
|
||||
})
|
||||
|
||||
else:
|
||||
debug_print(f"🔄 第{cycle_num}轮 步骤2: 无需搅拌")
|
||||
action_sequence.append(create_action_log("无需搅拌", "⏭️"))
|
||||
|
||||
# 步骤3.3: 静置分层
|
||||
if settling_time > 0:
|
||||
debug_print(f"🔄 第{cycle_num}轮 步骤3: 静置分层 ({settling_time}s)")
|
||||
settling_minutes = settling_time / 60
|
||||
action_sequence.append(create_action_log(f"静置分层 ({settling_minutes:.1f} 分钟)", "⚖️"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": settling_time}
|
||||
})
|
||||
else:
|
||||
debug_print(f"🔄 第{cycle_num}轮 步骤3: 未指定静置时间")
|
||||
action_sequence.append(create_action_log("未指定静置时间", "⏭️"))
|
||||
|
||||
# 步骤3.4: 执行分离操作
|
||||
if separator_device:
|
||||
debug_print(f"3.{repeat_idx+1}.2: 执行分离操作")
|
||||
debug_print(f"🔄 第{cycle_num}轮 步骤4: 执行分离操作")
|
||||
action_sequence.append(create_action_log(f"执行分离: 收集{product_phase}相", "🧪"))
|
||||
|
||||
# 调用分离器设备的separate方法
|
||||
separate_action = {
|
||||
@@ -330,31 +525,44 @@ def generate_separate_protocol(
|
||||
"solvent_volume": final_volume,
|
||||
"through": through,
|
||||
"repeats": 1, # 每次调用只做一次分离
|
||||
"stir_time": stir_time,
|
||||
"stir_time": 0, # 已经在上面完成
|
||||
"stir_speed": stir_speed,
|
||||
"settling_time": settling_time
|
||||
"settling_time": 0 # 已经在上面完成
|
||||
}
|
||||
}
|
||||
action_sequence.append(separate_action)
|
||||
debug_print(f"✅ 分离操作添加完成")
|
||||
debug_print(f"✅ 分离操作已添加")
|
||||
action_sequence.append(create_action_log("分离操作完成", "✅"))
|
||||
|
||||
# 收集结果
|
||||
if final_to_vessel:
|
||||
action_sequence.append(create_action_log(f"产物 ({product_phase}相) 收集到: {final_to_vessel}", "📦"))
|
||||
if final_waste_vessel:
|
||||
action_sequence.append(create_action_log(f"废相收集到: {final_waste_vessel}", "🗑️"))
|
||||
|
||||
else:
|
||||
debug_print(f"3.{repeat_idx+1}.2: 无分离器设备,跳过分离操作")
|
||||
debug_print(f"🔄 第{cycle_num}轮 步骤4: 无分离器设备,跳过分离")
|
||||
action_sequence.append(create_action_log("无分离器设备可用", "❌"))
|
||||
# 添加等待时间模拟分离
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": stir_time + settling_time}
|
||||
"action_kwargs": {"time": 10.0}
|
||||
})
|
||||
|
||||
# 等待间隔(除了最后一次)
|
||||
# 循环间等待(除了最后一次)
|
||||
if repeat_idx < repeats - 1:
|
||||
debug_print(f"🔄 第{cycle_num}轮: 等待下一次循环...")
|
||||
action_sequence.append(create_action_log("等待下一次循环...", "⏳"))
|
||||
action_sequence.append({
|
||||
"action_name": "wait",
|
||||
"action_kwargs": {"time": 5}
|
||||
})
|
||||
else:
|
||||
action_sequence.append(create_action_log(f"分离循环 {cycle_num}/{repeats} 完成", "🌟"))
|
||||
|
||||
except Exception as e:
|
||||
debug_print(f"⚠️ 分离流程执行失败: {str(e)}")
|
||||
debug_print(f"❌ 分离工作流程执行失败: {str(e)}")
|
||||
action_sequence.append(create_action_log(f"分离工作流程失败: {str(e)}", "❌"))
|
||||
# 添加错误日志
|
||||
action_sequence.append({
|
||||
"device_id": "system",
|
||||
@@ -365,22 +573,31 @@ def generate_separate_protocol(
|
||||
})
|
||||
|
||||
# === 最终结果 ===
|
||||
total_time = (stir_time + settling_time + 15) * repeats # 估算总时间
|
||||
|
||||
debug_print("=" * 60)
|
||||
debug_print(f"✅ 分离协议生成完成")
|
||||
debug_print(f"📊 总动作数: {len(action_sequence)}")
|
||||
debug_print(f"📋 处理总结:")
|
||||
debug_print(f" - 分离容器: {final_vessel}")
|
||||
debug_print(f" - 分离目的: {purpose}")
|
||||
debug_print(f" - 产物相: {product_phase}")
|
||||
debug_print(f" - 重复次数: {repeats}")
|
||||
debug_print(f"🎉 分离协议生成完成")
|
||||
debug_print(f"📊 协议统计:")
|
||||
debug_print(f" 📋 总动作数: {len(action_sequence)}")
|
||||
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f} 分钟)")
|
||||
debug_print(f" 🥼 分离容器: {final_vessel}")
|
||||
debug_print(f" 🎯 分离目的: {purpose}")
|
||||
debug_print(f" 📊 产物相: {product_phase}")
|
||||
debug_print(f" 🔄 重复次数: {repeats}")
|
||||
if solvent:
|
||||
debug_print(f" - 溶剂: {solvent} ({final_volume}mL)")
|
||||
debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL)")
|
||||
if final_to_vessel:
|
||||
debug_print(f" - 产物容器: {final_to_vessel}")
|
||||
debug_print(f" 🎯 产物容器: {final_to_vessel}")
|
||||
if final_waste_vessel:
|
||||
debug_print(f" - 废液容器: {final_waste_vessel}")
|
||||
debug_print(f" 🗑️ 废液容器: {final_waste_vessel}")
|
||||
debug_print("=" * 60)
|
||||
|
||||
# 添加完成日志
|
||||
summary_msg = f"分离协议完成: {final_vessel} ({purpose},{repeats} 次循环)"
|
||||
if solvent:
|
||||
summary_msg += f",使用 {final_volume}mL {solvent}"
|
||||
action_sequence.append(create_action_log(summary_msg, "🎉"))
|
||||
|
||||
return action_sequence
|
||||
|
||||
# === 便捷函数 ===
|
||||
@@ -388,6 +605,7 @@ def generate_separate_protocol(
|
||||
def separate_phases_only(G: nx.DiGraph, vessel: str, product_phase: str = "top",
|
||||
product_vessel: str = "", waste_vessel: str = "") -> List[Dict[str, Any]]:
|
||||
"""仅进行相分离(不添加溶剂)"""
|
||||
debug_print(f"⚡ 快速相分离: {vessel} ({product_phase}相)")
|
||||
return generate_separate_protocol(
|
||||
G, vessel=vessel,
|
||||
purpose="separate",
|
||||
@@ -399,6 +617,7 @@ def separate_phases_only(G: nx.DiGraph, vessel: str, product_phase: str = "top",
|
||||
def wash_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
|
||||
product_phase: str = "top", repeats: int = 1) -> List[Dict[str, Any]]:
|
||||
"""用溶剂洗涤"""
|
||||
debug_print(f"🧽 用{solvent}洗涤: {vessel} ({repeats} 次)")
|
||||
return generate_separate_protocol(
|
||||
G, vessel=vessel,
|
||||
purpose="wash",
|
||||
@@ -411,6 +630,7 @@ def wash_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[st
|
||||
def extract_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
|
||||
product_phase: str = "bottom", repeats: int = 3) -> List[Dict[str, Any]]:
|
||||
"""用溶剂萃取"""
|
||||
debug_print(f"🧪 用{solvent}萃取: {vessel} ({repeats} 次)")
|
||||
return generate_separate_protocol(
|
||||
G, vessel=vessel,
|
||||
purpose="extract",
|
||||
@@ -423,6 +643,7 @@ def extract_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union
|
||||
def separate_aqueous_organic(G: nx.DiGraph, vessel: str, organic_phase: str = "top",
|
||||
product_vessel: str = "", waste_vessel: str = "") -> List[Dict[str, Any]]:
|
||||
"""水-有机相分离"""
|
||||
debug_print(f"💧 水-有机相分离: {vessel} (有机相: {organic_phase})")
|
||||
return generate_separate_protocol(
|
||||
G, vessel=vessel,
|
||||
purpose="separate",
|
||||
@@ -434,15 +655,16 @@ def separate_aqueous_organic(G: nx.DiGraph, vessel: str, organic_phase: str = "t
|
||||
# 测试函数
|
||||
def test_separate_protocol():
|
||||
"""测试分离协议的各种参数解析"""
|
||||
print("=== SEPARATE PROTOCOL 增强版测试 ===")
|
||||
debug_print("=== 分离协议增强中文版测试 ===")
|
||||
|
||||
# 测试体积解析
|
||||
volumes = ["200 mL", "?", 100.0, "1 L", "500 μL"]
|
||||
debug_print("🧪 测试体积解析...")
|
||||
volumes = ["200 mL", "?", 100.0, "1 L", "500 μL", "未知", "50毫升"]
|
||||
for vol in volumes:
|
||||
result = parse_volume_input(vol)
|
||||
print(f"体积解析: {vol} → {result}mL")
|
||||
debug_print(f"📊 体积解析结果: {vol} -> {result}mL")
|
||||
|
||||
print("✅ 测试完成")
|
||||
debug_print("✅ 测试完成")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_separate_protocol()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import traceback
|
||||
from typing import List, Sequence, Optional, Literal, Union, Iterator
|
||||
|
||||
import asyncio
|
||||
@@ -117,7 +118,7 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
pass # This mode is not verified.
|
||||
else:
|
||||
if len(asp_vols) != len(targets):
|
||||
raise ValueError("Length of `vols` must match `targets`.")
|
||||
raise ValueError(f"Length of `asp_vols` {len(asp_vols)} must match `targets` {len(targets)}.")
|
||||
tip = next(self.current_tip)
|
||||
await self.pick_up_tips(tip)
|
||||
|
||||
@@ -160,6 +161,7 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
await self.discard_tips()
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
raise RuntimeError(f"Liquid addition failed: {e}") from e
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
@@ -183,7 +185,7 @@ class LiquidHandlerAbstract(LiquidHandler):
|
||||
spread: Literal["wide", "tight", "custom"] = "wide",
|
||||
is_96_well: bool = False,
|
||||
mix_stage: Optional[Literal["none", "before", "after", "both"]] = "none",
|
||||
mix_times: Optional[List(int)] = None,
|
||||
mix_times: Optional[List[int]] = None,
|
||||
mix_vol: Optional[int] = None,
|
||||
mix_rate: Optional[int] = None,
|
||||
mix_liquid_height: Optional[float] = None,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@ class MoveitInterface:
|
||||
tf_buffer: Buffer
|
||||
tf_listener: TransformListener
|
||||
|
||||
def __init__(self, moveit_type, joint_poses, rotation=None, device_config=None):
|
||||
def __init__(self, moveit_type, joint_poses, rotation=None, device_config=None, **kwargs):
|
||||
self.device_config = device_config
|
||||
self.rotation = rotation
|
||||
self.data_config = json.load(
|
||||
|
||||
@@ -101,7 +101,7 @@ class VirtualMultiwayValve:
|
||||
self._target_position = pos
|
||||
|
||||
# 模拟阀门切换时间
|
||||
switch_time = abs(self._current_position - pos) * 0.5 # 每个位置0.1秒
|
||||
switch_time = abs(self._current_position - pos) * 0.5 # 每个位置0.5秒
|
||||
|
||||
if switch_time > 0:
|
||||
self.logger.info(f"⏱️ 阀门移动中... 预计用时: {switch_time:.1f}秒 🔄")
|
||||
@@ -172,32 +172,32 @@ class VirtualMultiwayValve:
|
||||
def is_at_position(self, position: int) -> bool:
|
||||
"""检查是否在指定位置 🎯"""
|
||||
result = self._current_position == position
|
||||
self.logger.debug(f"🎯 位置检查: 当前={self._current_position}, 目标={position}, 匹配={result}")
|
||||
# 删除debug日志:self.logger.debug(f"🎯 位置检查: 当前={self._current_position}, 目标={position}, 匹配={result}")
|
||||
return result
|
||||
|
||||
def is_at_pump_position(self) -> bool:
|
||||
"""检查是否在transfer pump位置 🚰"""
|
||||
result = self._current_position == 0
|
||||
pump_status = "是" if result else "否"
|
||||
self.logger.debug(f"🚰 泵位置检查: {pump_status} (当前位置: {self._current_position})")
|
||||
# 删除debug日志:pump_status = "是" if result else "否"
|
||||
# 删除debug日志:self.logger.debug(f"🚰 泵位置检查: {pump_status} (当前位置: {self._current_position})")
|
||||
return result
|
||||
|
||||
def is_at_port(self, port_number: int) -> bool:
|
||||
"""检查是否在指定端口位置 🔌"""
|
||||
result = self._current_position == port_number
|
||||
port_status = "是" if result else "否"
|
||||
self.logger.debug(f"🔌 端口{port_number}检查: {port_status} (当前位置: {self._current_position})")
|
||||
# 删除debug日志:port_status = "是" if result else "否"
|
||||
# 删除debug日志:self.logger.debug(f"🔌 端口{port_number}检查: {port_status} (当前位置: {self._current_position})")
|
||||
return result
|
||||
|
||||
def get_available_positions(self) -> list:
|
||||
"""获取可用位置列表 📋"""
|
||||
positions = list(range(0, self.max_positions + 1))
|
||||
self.logger.debug(f"📋 可用位置: {positions}")
|
||||
# 删除debug日志:self.logger.debug(f"📋 可用位置: {positions}")
|
||||
return positions
|
||||
|
||||
def get_available_ports(self) -> Dict[int, str]:
|
||||
"""获取可用端口映射 🗺️"""
|
||||
self.logger.debug(f"🗺️ 端口映射: {self.position_map}")
|
||||
# 删除debug日志:self.logger.debug(f"🗺️ 端口映射: {self.position_map}")
|
||||
return self.position_map.copy()
|
||||
|
||||
def reset(self):
|
||||
@@ -229,7 +229,7 @@ class VirtualMultiwayValve:
|
||||
else:
|
||||
flow_path = f"🔌 端口 {self._current_position} 已连接 ({current_port})"
|
||||
|
||||
self.logger.debug(f"🌊 当前流路: {flow_path}")
|
||||
# 删除debug日志:self.logger.debug(f"🌊 当前流路: {flow_path}")
|
||||
return flow_path
|
||||
|
||||
def get_info(self) -> dict:
|
||||
@@ -247,7 +247,7 @@ class VirtualMultiwayValve:
|
||||
"position_map": self.position_map
|
||||
}
|
||||
|
||||
self.logger.debug(f"📊 阀门信息: 位置={self._current_position}, 状态={self._status}, 端口={self.get_current_port()}")
|
||||
# 删除debug日志:self.logger.debug(f"📊 阀门信息: 位置={self._current_position}, 状态={self._status}, 端口={self.get_current_port()}")
|
||||
return info
|
||||
|
||||
def __str__(self):
|
||||
@@ -264,7 +264,7 @@ class VirtualMultiwayValve:
|
||||
Args:
|
||||
command: 目标位置 (0-8) 或位置字符串
|
||||
"""
|
||||
self.logger.debug(f"🎯 兼容性调用: set_valve_position({command})")
|
||||
# 删除debug日志:self.logger.debug(f"🎯 兼容性调用: set_valve_position({command})")
|
||||
return self.set_position(command)
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class VirtualPumpMode(Enum):
|
||||
|
||||
|
||||
class VirtualTransferPump:
|
||||
"""虚拟转移泵类 - 模拟泵的基本功能,无需实际硬件"""
|
||||
"""虚拟转移泵类 - 模拟泵的基本功能,无需实际硬件 🚰"""
|
||||
|
||||
def __init__(self, device_id: str = None, config: dict = None, **kwargs):
|
||||
"""
|
||||
@@ -42,20 +42,31 @@ class VirtualTransferPump:
|
||||
self._max_velocity = 5.0 # float
|
||||
self._current_volume = 0.0 # float
|
||||
|
||||
# 🚀 新增:快速模式设置 - 大幅缩短执行时间
|
||||
self._fast_mode = True # 是否启用快速模式
|
||||
self._fast_move_time = 1.0 # 快速移动时间(秒)
|
||||
self._fast_dispense_time = 1.0 # 快速喷射时间(秒)
|
||||
|
||||
self.logger = logging.getLogger(f"VirtualTransferPump.{self.device_id}")
|
||||
|
||||
print(f"🚰 === 虚拟转移泵 {self.device_id} 已创建 === ✨")
|
||||
print(f"💨 快速模式: {'启用' if self._fast_mode else '禁用'} | 移动时间: {self._fast_move_time}s | 喷射时间: {self._fast_dispense_time}s")
|
||||
print(f"📊 最大容量: {self.max_volume}mL | 端口: {self.port}")
|
||||
|
||||
async def initialize(self) -> bool:
|
||||
"""初始化虚拟泵"""
|
||||
self.logger.info(f"Initializing virtual pump {self.device_id}")
|
||||
"""初始化虚拟泵 🚀"""
|
||||
self.logger.info(f"🔧 初始化虚拟转移泵 {self.device_id} ✨")
|
||||
self._status = "Idle"
|
||||
self._position = 0.0
|
||||
self._current_volume = 0.0
|
||||
self.logger.info(f"✅ 转移泵 {self.device_id} 初始化完成 🚰")
|
||||
return True
|
||||
|
||||
async def cleanup(self) -> bool:
|
||||
"""清理虚拟泵"""
|
||||
self.logger.info(f"Cleaning up virtual pump {self.device_id}")
|
||||
"""清理虚拟泵 🧹"""
|
||||
self.logger.info(f"🧹 清理虚拟转移泵 {self.device_id} 🔚")
|
||||
self._status = "Idle"
|
||||
self.logger.info(f"✅ 转移泵 {self.device_id} 清理完成 💤")
|
||||
return True
|
||||
|
||||
# 基本属性
|
||||
@@ -65,12 +76,12 @@ class VirtualTransferPump:
|
||||
|
||||
@property
|
||||
def position(self) -> float:
|
||||
"""当前柱塞位置 (ml)"""
|
||||
"""当前柱塞位置 (ml) 📍"""
|
||||
return self._position
|
||||
|
||||
@property
|
||||
def current_volume(self) -> float:
|
||||
"""当前注射器中的体积 (ml)"""
|
||||
"""当前注射器中的体积 (ml) 💧"""
|
||||
return self._current_volume
|
||||
|
||||
@property
|
||||
@@ -82,22 +93,50 @@ class VirtualTransferPump:
|
||||
return self._transfer_rate
|
||||
|
||||
def set_max_velocity(self, velocity: float):
|
||||
"""设置最大速度 (ml/s)"""
|
||||
"""设置最大速度 (ml/s) 🌊"""
|
||||
self._max_velocity = max(0.1, min(50.0, velocity)) # 限制在合理范围内
|
||||
self.logger.info(f"Set max velocity to {self._max_velocity} ml/s")
|
||||
self.logger.info(f"🌊 设置最大速度为 {self._max_velocity} mL/s")
|
||||
|
||||
def get_status(self) -> str:
|
||||
"""获取泵状态"""
|
||||
"""获取泵状态 📋"""
|
||||
return self._status
|
||||
|
||||
async def _simulate_operation(self, duration: float):
|
||||
"""模拟操作延时"""
|
||||
"""模拟操作延时 ⏱️"""
|
||||
self._status = "Busy"
|
||||
await asyncio.sleep(duration)
|
||||
self._status = "Idle"
|
||||
|
||||
def _calculate_duration(self, volume: float, velocity: float = None) -> float:
|
||||
"""计算操作持续时间"""
|
||||
"""
|
||||
计算操作持续时间 ⏰
|
||||
🚀 快速模式:保留计算逻辑用于日志显示,但实际使用固定的快速时间
|
||||
"""
|
||||
if velocity is None:
|
||||
velocity = self._max_velocity
|
||||
|
||||
# 📊 计算理论时间(用于日志显示)
|
||||
theoretical_duration = abs(volume) / velocity
|
||||
|
||||
# 🚀 如果启用快速模式,使用固定的快速时间
|
||||
if self._fast_mode:
|
||||
# 根据操作类型选择快速时间
|
||||
if abs(volume) > 0.1: # 大于0.1mL的操作
|
||||
actual_duration = self._fast_move_time
|
||||
else: # 很小的操作
|
||||
actual_duration = 0.5
|
||||
|
||||
self.logger.debug(f"⚡ 快速模式: 理论时间 {theoretical_duration:.2f}s → 实际时间 {actual_duration:.2f}s")
|
||||
return actual_duration
|
||||
else:
|
||||
# 正常模式使用理论时间
|
||||
return theoretical_duration
|
||||
|
||||
def _calculate_display_duration(self, volume: float, velocity: float = None) -> float:
|
||||
"""
|
||||
计算显示用的持续时间(用于日志) 📊
|
||||
这个函数返回理论计算时间,用于日志显示
|
||||
"""
|
||||
if velocity is None:
|
||||
velocity = self._max_velocity
|
||||
return abs(volume) / velocity
|
||||
@@ -105,7 +144,7 @@ class VirtualTransferPump:
|
||||
# 新的set_position方法 - 专门用于SetPumpPosition动作
|
||||
async def set_position(self, position: float, max_velocity: float = None):
|
||||
"""
|
||||
移动到绝对位置 - 专门用于SetPumpPosition动作
|
||||
移动到绝对位置 - 专门用于SetPumpPosition动作 🎯
|
||||
|
||||
Args:
|
||||
position (float): 目标位置 (ml)
|
||||
@@ -122,56 +161,107 @@ class VirtualTransferPump:
|
||||
# 限制位置在有效范围内
|
||||
target_position = max(0.0, min(float(self.max_volume), target_position))
|
||||
|
||||
# 计算移动距离和时间
|
||||
# 计算移动距离
|
||||
volume_to_move = abs(target_position - self._position)
|
||||
duration = self._calculate_duration(volume_to_move, velocity)
|
||||
|
||||
self.logger.info(f"SET_POSITION: Moving to {target_position} ml (current: {self._position} ml), velocity: {velocity} ml/s")
|
||||
# 📊 计算显示用的时间(用于日志)
|
||||
display_duration = self._calculate_display_duration(volume_to_move, velocity)
|
||||
|
||||
# 模拟移动过程
|
||||
start_position = self._position
|
||||
steps = 10 if duration > 0.1 else 1 # 如果移动距离很小,只用1步
|
||||
step_duration = duration / steps if steps > 1 else duration
|
||||
# ⚡ 计算实际执行时间(快速模式)
|
||||
actual_duration = self._calculate_duration(volume_to_move, velocity)
|
||||
|
||||
for i in range(steps + 1):
|
||||
# 计算当前位置和进度
|
||||
progress = (i / steps) * 100 if steps > 0 else 100
|
||||
current_pos = start_position + (target_position - start_position) * (i / steps) if steps > 0 else target_position
|
||||
# 🎯 确定操作类型和emoji
|
||||
if target_position > self._position:
|
||||
operation_type = "吸液"
|
||||
operation_emoji = "📥"
|
||||
elif target_position < self._position:
|
||||
operation_type = "排液"
|
||||
operation_emoji = "📤"
|
||||
else:
|
||||
operation_type = "保持"
|
||||
operation_emoji = "📍"
|
||||
|
||||
self.logger.info(f"🎯 SET_POSITION: {operation_type} {operation_emoji}")
|
||||
self.logger.info(f" 📍 位置: {self._position:.2f}mL → {target_position:.2f}mL (移动 {volume_to_move:.2f}mL)")
|
||||
self.logger.info(f" 🌊 速度: {velocity:.2f} mL/s")
|
||||
self.logger.info(f" ⏰ 预计时间: {display_duration:.2f}s")
|
||||
|
||||
if self._fast_mode:
|
||||
self.logger.info(f" ⚡ 快速模式: 实际用时 {actual_duration:.2f}s")
|
||||
|
||||
# 🚀 模拟移动过程
|
||||
if volume_to_move > 0.01: # 只有当移动距离足够大时才显示进度
|
||||
start_position = self._position
|
||||
steps = 5 if actual_duration > 0.5 else 2 # 根据实际时间调整步数
|
||||
step_duration = actual_duration / steps
|
||||
|
||||
# 更新状态
|
||||
self._status = "Moving" if i < steps else "Idle"
|
||||
self._position = current_pos
|
||||
self._current_volume = current_pos
|
||||
self.logger.info(f"🚀 开始{operation_type}... {operation_emoji}")
|
||||
|
||||
# 等待一小步时间
|
||||
if i < steps and step_duration > 0:
|
||||
await asyncio.sleep(step_duration)
|
||||
for i in range(steps + 1):
|
||||
# 计算当前位置和进度
|
||||
progress = (i / steps) * 100 if steps > 0 else 100
|
||||
current_pos = start_position + (target_position - start_position) * (i / steps) if steps > 0 else target_position
|
||||
|
||||
# 更新状态
|
||||
if i < steps:
|
||||
self._status = f"{operation_type}中"
|
||||
status_emoji = "🔄"
|
||||
else:
|
||||
self._status = "Idle"
|
||||
status_emoji = "✅"
|
||||
|
||||
self._position = current_pos
|
||||
self._current_volume = current_pos
|
||||
|
||||
# 显示进度(每25%或最后一步)
|
||||
if i == 0:
|
||||
self.logger.debug(f" 🔄 {operation_type}开始: {progress:.0f}%")
|
||||
elif progress >= 50 and i == steps // 2:
|
||||
self.logger.debug(f" 🔄 {operation_type}进度: {progress:.0f}%")
|
||||
elif i == steps:
|
||||
self.logger.info(f" ✅ {operation_type}完成: {progress:.0f}% | 当前位置: {current_pos:.2f}mL")
|
||||
|
||||
# 等待一小步时间
|
||||
if i < steps and step_duration > 0:
|
||||
await asyncio.sleep(step_duration)
|
||||
else:
|
||||
# 移动距离很小,直接完成
|
||||
self._position = target_position
|
||||
self._current_volume = target_position
|
||||
self.logger.info(f" 📍 微调完成: {target_position:.2f}mL")
|
||||
|
||||
# 确保最终位置准确
|
||||
self._position = target_position
|
||||
self._current_volume = target_position
|
||||
self._status = "Idle"
|
||||
|
||||
self.logger.info(f"SET_POSITION: Reached position {self._position} ml, current volume: {self._current_volume} ml")
|
||||
# 📊 最终状态日志
|
||||
if volume_to_move > 0.01:
|
||||
self.logger.info(f"🎉 SET_POSITION 完成! 📍 最终位置: {self._position:.2f}mL | 💧 当前体积: {self._current_volume:.2f}mL")
|
||||
|
||||
# 返回符合action定义的结果
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"Successfully moved to position {self._position} ml"
|
||||
"message": f"✅ 成功移动到位置 {self._position:.2f}mL ({operation_type})",
|
||||
"final_position": self._position,
|
||||
"final_volume": self._current_volume,
|
||||
"operation_type": operation_type
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to set position: {str(e)}"
|
||||
error_msg = f"❌ 设置位置失败: {str(e)}"
|
||||
self.logger.error(error_msg)
|
||||
return {
|
||||
"success": False,
|
||||
"message": error_msg
|
||||
"message": error_msg,
|
||||
"final_position": self._position,
|
||||
"final_volume": self._current_volume
|
||||
}
|
||||
|
||||
# 其他泵操作方法
|
||||
async def pull_plunger(self, volume: float, velocity: float = None):
|
||||
"""
|
||||
拉取柱塞(吸液)
|
||||
拉取柱塞(吸液) 📥
|
||||
|
||||
Args:
|
||||
volume (float): 要拉取的体积 (ml)
|
||||
@@ -181,23 +271,29 @@ class VirtualTransferPump:
|
||||
actual_volume = new_position - self._position
|
||||
|
||||
if actual_volume <= 0:
|
||||
self.logger.warning("Cannot pull - already at maximum volume")
|
||||
self.logger.warning("⚠️ 无法吸液 - 已达到最大容量")
|
||||
return
|
||||
|
||||
duration = self._calculate_duration(actual_volume, velocity)
|
||||
display_duration = self._calculate_display_duration(actual_volume, velocity)
|
||||
actual_duration = self._calculate_duration(actual_volume, velocity)
|
||||
|
||||
self.logger.info(f"Pulling {actual_volume} ml (from {self._position} to {new_position})")
|
||||
self.logger.info(f"📥 开始吸液: {actual_volume:.2f}mL")
|
||||
self.logger.info(f" 📍 位置: {self._position:.2f}mL → {new_position:.2f}mL")
|
||||
self.logger.info(f" ⏰ 预计时间: {display_duration:.2f}s")
|
||||
|
||||
await self._simulate_operation(duration)
|
||||
if self._fast_mode:
|
||||
self.logger.info(f" ⚡ 快速模式: 实际用时 {actual_duration:.2f}s")
|
||||
|
||||
await self._simulate_operation(actual_duration)
|
||||
|
||||
self._position = new_position
|
||||
self._current_volume = new_position
|
||||
|
||||
self.logger.info(f"Pulled {actual_volume} ml, current volume: {self._current_volume} ml")
|
||||
self.logger.info(f"✅ 吸液完成: {actual_volume:.2f}mL | 💧 当前体积: {self._current_volume:.2f}mL")
|
||||
|
||||
async def push_plunger(self, volume: float, velocity: float = None):
|
||||
"""
|
||||
推出柱塞(排液)
|
||||
推出柱塞(排液) 📤
|
||||
|
||||
Args:
|
||||
volume (float): 要推出的体积 (ml)
|
||||
@@ -207,35 +303,44 @@ class VirtualTransferPump:
|
||||
actual_volume = self._position - new_position
|
||||
|
||||
if actual_volume <= 0:
|
||||
self.logger.warning("Cannot push - already at minimum volume")
|
||||
self.logger.warning("⚠️ 无法排液 - 已达到最小容量")
|
||||
return
|
||||
|
||||
duration = self._calculate_duration(actual_volume, velocity)
|
||||
display_duration = self._calculate_display_duration(actual_volume, velocity)
|
||||
actual_duration = self._calculate_duration(actual_volume, velocity)
|
||||
|
||||
self.logger.info(f"Pushing {actual_volume} ml (from {self._position} to {new_position})")
|
||||
self.logger.info(f"📤 开始排液: {actual_volume:.2f}mL")
|
||||
self.logger.info(f" 📍 位置: {self._position:.2f}mL → {new_position:.2f}mL")
|
||||
self.logger.info(f" ⏰ 预计时间: {display_duration:.2f}s")
|
||||
|
||||
await self._simulate_operation(duration)
|
||||
if self._fast_mode:
|
||||
self.logger.info(f" ⚡ 快速模式: 实际用时 {actual_duration:.2f}s")
|
||||
|
||||
await self._simulate_operation(actual_duration)
|
||||
|
||||
self._position = new_position
|
||||
self._current_volume = new_position
|
||||
|
||||
self.logger.info(f"Pushed {actual_volume} ml, current volume: {self._current_volume} ml")
|
||||
self.logger.info(f"✅ 排液完成: {actual_volume:.2f}mL | 💧 当前体积: {self._current_volume:.2f}mL")
|
||||
|
||||
# 便捷操作方法
|
||||
async def aspirate(self, volume: float, velocity: float = None):
|
||||
"""吸液操作"""
|
||||
"""吸液操作 📥"""
|
||||
await self.pull_plunger(volume, velocity)
|
||||
|
||||
async def dispense(self, volume: float, velocity: float = None):
|
||||
"""排液操作"""
|
||||
"""排液操作 📤"""
|
||||
await self.push_plunger(volume, velocity)
|
||||
|
||||
async def transfer(self, volume: float, aspirate_velocity: float = None, dispense_velocity: float = None):
|
||||
"""转移操作(先吸后排)"""
|
||||
"""转移操作(先吸后排) 🔄"""
|
||||
self.logger.info(f"🔄 开始转移操作: {volume:.2f}mL")
|
||||
|
||||
# 吸液
|
||||
await self.aspirate(volume, aspirate_velocity)
|
||||
|
||||
# 短暂停顿
|
||||
self.logger.debug("⏸️ 短暂停顿...")
|
||||
await asyncio.sleep(0.1)
|
||||
|
||||
# 排液
|
||||
|
||||
@@ -100,3 +100,4 @@ serial:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
|
||||
@@ -8,7 +8,7 @@ camera:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: destroy_node的参数schema
|
||||
description: 用于安全地关闭摄像头设备,释放摄像头资源,停止视频采集和发布服务。调用此函数将清理OpenCV摄像头连接并销毁ROS2节点。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -28,7 +28,7 @@ camera:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: timer_callback的参数schema
|
||||
description: 定时器回调函数的参数schema。此函数负责定期采集摄像头视频帧,将OpenCV格式的图像转换为ROS Image消息格式,并发布到指定的视频话题。默认以10Hz频率执行,确保视频流的连续性和实时性。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -44,7 +44,7 @@ camera:
|
||||
module: unilabos.ros.nodes.presets.camera:VideoPublisher
|
||||
status_types: {}
|
||||
type: ros2
|
||||
description: ''
|
||||
description: VideoPublisher摄像头设备节点,用于实时视频采集和流媒体发布。该设备通过OpenCV连接本地摄像头(如USB摄像头、内置摄像头等),定时采集视频帧并将其转换为ROS2的sensor_msgs/Image消息格式发布到视频话题。主要用于实验室自动化系统中的视觉监控、图像分析、实时观察等应用场景。支持可配置的摄像头索引、发布频率等参数。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -67,3 +67,4 @@ camera:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
|
||||
@@ -8,7 +8,7 @@ hplc.agilent:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: check_status的参数schema
|
||||
description: 检查安捷伦HPLC设备状态的函数。用于监控设备的运行状态、连接状态、错误信息等关键指标。该函数定期查询设备状态,确保系统稳定运行,及时发现和报告设备异常。适用于自动化流程中的设备监控、故障诊断、系统维护等场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -29,7 +29,7 @@ hplc.agilent:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: extract_data_from_txt的参数schema
|
||||
description: 从文本文件中提取分析数据的函数。用于解析安捷伦HPLC生成的结果文件,提取峰面积、保留时间、浓度等关键分析数据。支持多种文件格式的自动识别和数据结构化处理,为后续数据分析和报告生成提供标准化的数据格式。适用于批量数据处理、结果验证、质量控制等分析工作流程。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -55,7 +55,7 @@ hplc.agilent:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: start_sequence的参数schema
|
||||
description: 启动安捷伦HPLC分析序列的函数。用于执行预定义的分析方法序列,包括样品进样、色谱分离、检测等完整的分析流程。支持参数配置、资源分配、工作流程管理等功能,实现全自动的样品分析。适用于批量样品处理、标准化分析、质量检测等需要连续自动分析的应用场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -83,7 +83,7 @@ hplc.agilent:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: try_close_sub_device的参数schema
|
||||
description: 尝试关闭HPLC子设备的函数。用于安全地关闭泵、检测器、进样器等各个子模块,确保设备正常断开连接并保护硬件安全。该函数提供错误处理和状态确认机制,避免强制关闭可能造成的设备损坏。适用于设备维护、系统重启、紧急停机等需要安全关闭设备的场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -106,7 +106,7 @@ hplc.agilent:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: try_open_sub_device的参数schema
|
||||
description: 尝试打开HPLC子设备的函数。用于初始化和连接泵、检测器、进样器等各个子模块,建立设备通信并进行自检。该函数提供连接验证和错误恢复机制,确保子设备正常启动并准备就绪。适用于设备初始化、系统启动、设备重连等需要建立设备连接的场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -131,10 +131,9 @@ hplc.agilent:
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -143,7 +142,6 @@ hplc.agilent:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -152,7 +150,6 @@ hplc.agilent:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -179,7 +176,7 @@ hplc.agilent:
|
||||
status_text: str
|
||||
success: bool
|
||||
type: python
|
||||
description: HPLC device
|
||||
description: 安捷伦高效液相色谱(HPLC)分析设备,用于复杂化合物的分离、检测和定量分析。该设备通过UI自动化技术控制安捷伦ChemStation软件,实现全自动的样品分析流程。具备序列启动、设备状态监控、数据文件提取、结果处理等功能。支持多样品批量处理和实时状态反馈,适用于药物分析、环境检测、食品安全、化学研究等需要高精度色谱分析的实验室应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -218,6 +215,7 @@ hplc.agilent:
|
||||
- finish_status
|
||||
- data_file
|
||||
type: object
|
||||
version: 0.0.1
|
||||
raman_home_made:
|
||||
class:
|
||||
action_value_mappings:
|
||||
@@ -229,7 +227,7 @@ raman_home_made:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ccd_time的参数schema
|
||||
description: 设置CCD检测器积分时间的函数。用于配置拉曼光谱仪的信号采集时间,控制光谱数据的质量和信噪比。较长的积分时间可获得更高的信号强度和更好的光谱质量,但会增加测量时间。该函数允许根据样品特性和测量要求动态调整检测参数,优化测量效果。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -253,7 +251,7 @@ raman_home_made:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: laser_on_power的参数schema
|
||||
description: 设置激光器输出功率的函数。用于控制拉曼光谱仪激光器的功率输出,调节激光强度以适应不同样品的测量需求。适当的激光功率能够获得良好的拉曼信号同时避免样品损伤。该函数支持精确的功率控制,确保测量结果的稳定性和重现性。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -269,30 +267,6 @@ raman_home_made:
|
||||
title: laser_on_power参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-raman_cmd:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
command: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: raman_cmd的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: raman_cmd参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-raman_without_background:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -302,7 +276,7 @@ raman_home_made:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: raman_without_background的参数schema
|
||||
description: 执行无背景扣除的拉曼光谱测量函数。用于直接采集样品的拉曼光谱信号,不进行背景校正处理。该函数配置积分时间和激光功率参数,获取原始光谱数据用于后续的数据处理分析。适用于对光谱数据质量要求较高或需要自定义背景处理流程的测量场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -332,7 +306,7 @@ raman_home_made:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: raman_without_background_average的参数schema
|
||||
description: 执行多次平均的无背景拉曼光谱测量函数。通过多次测量取平均值来提高光谱数据的信噪比和测量精度,减少随机噪声影响。该函数支持自定义平均次数、积分时间、激光功率等参数,并可为样品指定名称便于数据管理。适用于对测量精度要求较高的定量分析和研究应用。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -367,10 +341,9 @@ raman_home_made:
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -379,7 +352,6 @@ raman_home_made:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -388,7 +360,6 @@ raman_home_made:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -407,7 +378,7 @@ raman_home_made:
|
||||
module: unilabos.devices.raman_uv.home_made_raman:RamanObj
|
||||
status_types: {}
|
||||
type: python
|
||||
description: Raman spectroscopy device
|
||||
description: 拉曼光谱分析设备,用于物质的分子结构和化学成分表征。该设备集成激光器和CCD检测器,通过串口通信控制激光功率和光谱采集。具备背景扣除、多次平均、自动数据处理等功能,支持高精度的拉曼光谱测量。适用于材料表征、化学分析、质量控制、研究开发等需要分子指纹识别和结构分析的实验应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -431,3 +402,4 @@ raman_home_made:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
|
||||
@@ -5,7 +5,7 @@ hotel.thermo_orbitor_rs2_hotel:
|
||||
status_types:
|
||||
rotation: String
|
||||
type: python
|
||||
description: Thermo Orbitor RS2 Hotel
|
||||
description: Thermo Orbitor RS2 Hotel容器设备,用于实验室样品的存储和管理。该设备通过HotelContainer类实现容器的旋转控制和状态监控,主要用于存储实验样品、试剂瓶或其他实验器具,支持旋转功能以便于样品的自动化存取。适用于需要有序存储和快速访问大量样品的实验室自动化场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -29,3 +29,4 @@ hotel.thermo_orbitor_rs2_hotel:
|
||||
model:
|
||||
mesh: thermo_orbitor_rs2_hotel
|
||||
type: device
|
||||
version: 0.0.1
|
||||
|
||||
@@ -15,16 +15,14 @@ laiyu_add_solid:
|
||||
result:
|
||||
actual_mass_mg: actual_mass_mg
|
||||
schema:
|
||||
description: ROS Action SolidDispenseAddPowderTube 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: SolidDispenseAddPowderTube_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
compound_mass:
|
||||
type: number
|
||||
@@ -41,7 +39,6 @@ laiyu_add_solid:
|
||||
title: SolidDispenseAddPowderTube_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
actual_mass_mg:
|
||||
type: number
|
||||
@@ -60,38 +57,6 @@ laiyu_add_solid:
|
||||
title: SolidDispenseAddPowderTube
|
||||
type: object
|
||||
type: SolidDispenseAddPowderTube
|
||||
auto-add_powder_tube:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
compound_mass: null
|
||||
powder_tube_number: null
|
||||
target_tube_position: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: add_powder_tube的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
compound_mass:
|
||||
type: string
|
||||
powder_tube_number:
|
||||
type: string
|
||||
target_tube_position:
|
||||
type: string
|
||||
required:
|
||||
- powder_tube_number
|
||||
- target_tube_position
|
||||
- compound_mass
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: add_powder_tube参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-calculate_crc:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -100,7 +65,7 @@ laiyu_add_solid:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: calculate_crc的参数schema
|
||||
description: Modbus CRC-16校验码计算函数。计算Modbus RTU通信协议所需的CRC-16校验码,确保数据传输的完整性和可靠性。该函数实现标准的CRC-16算法,用于构造完整的Modbus指令帧。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -116,154 +81,6 @@ laiyu_add_solid:
|
||||
title: calculate_crc参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-discharge:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
float_in: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: discharge的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
float_in:
|
||||
type: number
|
||||
required:
|
||||
- float_in
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: discharge参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-move_to_plate:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
string: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: move_to_plate的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
required:
|
||||
- string
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: move_to_plate参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-move_to_xyz:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
x: null
|
||||
y: null
|
||||
z: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: move_to_xyz的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
x:
|
||||
type: number
|
||||
y:
|
||||
type: number
|
||||
z:
|
||||
type: number
|
||||
required:
|
||||
- x
|
||||
- y
|
||||
- z
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: move_to_xyz参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-pick_powder_tube:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
int_input: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: pick_powder_tube的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
int_input:
|
||||
type: integer
|
||||
required:
|
||||
- int_input
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: pick_powder_tube参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-put_powder_tube:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
int_input: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: put_powder_tube的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
int_input:
|
||||
type: integer
|
||||
required:
|
||||
- int_input
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: put_powder_tube参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-reset:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: reset的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: reset参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-send_command:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -272,7 +89,7 @@ laiyu_add_solid:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: send_command的参数schema
|
||||
description: Modbus指令发送函数。构造完整的Modbus RTU指令帧(包含CRC校验),发送给分装设备并等待响应。该函数处理底层通信协议,确保指令的正确传输和响应接收,支持最长3分钟的响应等待时间。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -297,16 +114,14 @@ laiyu_add_solid:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action FloatSingleInput 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: FloatSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
float_in:
|
||||
type: number
|
||||
@@ -315,7 +130,6 @@ laiyu_add_solid:
|
||||
title: FloatSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -340,16 +154,14 @@ laiyu_add_solid:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action StrSingleInput 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: StrSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
@@ -358,7 +170,6 @@ laiyu_add_solid:
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -387,16 +198,14 @@ laiyu_add_solid:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action Point3DSeparateInput 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: Point3DSeparateInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
x:
|
||||
type: number
|
||||
@@ -411,7 +220,6 @@ laiyu_add_solid:
|
||||
title: Point3DSeparateInput_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -436,16 +244,14 @@ laiyu_add_solid:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action IntSingleInput 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: IntSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
int_input:
|
||||
maximum: 2147483647
|
||||
@@ -456,7 +262,6 @@ laiyu_add_solid:
|
||||
title: IntSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -481,16 +286,14 @@ laiyu_add_solid:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action IntSingleInput 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: IntSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
int_input:
|
||||
maximum: 2147483647
|
||||
@@ -501,7 +304,6 @@ laiyu_add_solid:
|
||||
title: IntSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -524,22 +326,19 @@ laiyu_add_solid:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action EmptyIn 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -556,7 +355,7 @@ laiyu_add_solid:
|
||||
status_types:
|
||||
status: str
|
||||
type: python
|
||||
description: Laiyu Add Solid
|
||||
description: 来渝固体粉末自动分装设备,用于实验室化学试剂的精确称量和分装。该设备通过Modbus RTU协议与控制系统通信,集成了精密天平、三轴运动平台、粉筒管理系统等组件。支持多种粉末试剂的自动拿取、精确称量、定点分装和归位操作。具备高精度称量、位置控制和批量处理能力,适用于化学合成、药物研发、材料制备等需要精确固体试剂配制的实验室应用场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -580,3 +379,4 @@ laiyu_add_solid:
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -117,30 +117,6 @@ moveit.arm_slider:
|
||||
title: moveit_task参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-pick_and_place:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
command: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: pick_and_place的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: pick_and_place参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-post_init:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -193,54 +169,6 @@ moveit.arm_slider:
|
||||
title: resource_manager参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_position:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
command: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: set_position的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: set_position参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_status:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
command: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: set_status的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: set_status参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-wait_for_resource_action:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -270,10 +198,9 @@ moveit.arm_slider:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -282,7 +209,6 @@ moveit.arm_slider:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -291,7 +217,6 @@ moveit.arm_slider:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -316,10 +241,9 @@ moveit.arm_slider:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -328,7 +252,6 @@ moveit.arm_slider:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -337,7 +260,6 @@ moveit.arm_slider:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -362,10 +284,9 @@ moveit.arm_slider:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -374,7 +295,6 @@ moveit.arm_slider:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -383,7 +303,6 @@ moveit.arm_slider:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -402,7 +321,7 @@ moveit.arm_slider:
|
||||
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
|
||||
status_types: {}
|
||||
type: python
|
||||
description: Arm with Slider
|
||||
description: 机械臂与滑块运动系统,基于MoveIt2运动规划框架的多自由度机械臂控制设备。该系统集成机械臂和线性滑块,通过ROS2和MoveIt2实现精确的轨迹规划和协调运动控制。支持笛卡尔空间和关节空间的运动规划、碰撞检测、逆运动学求解等功能。适用于复杂的pick-and-place操作、精密装配、多工位协作等需要高精度多轴协调运动的实验室自动化应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -427,6 +346,7 @@ moveit.arm_slider:
|
||||
model:
|
||||
mesh: arm_slider
|
||||
type: device
|
||||
version: 0.0.1
|
||||
moveit.toyo_xyz:
|
||||
class:
|
||||
action_value_mappings:
|
||||
@@ -546,30 +466,6 @@ moveit.toyo_xyz:
|
||||
title: moveit_task参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-pick_and_place:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
command: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: pick_and_place的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: pick_and_place参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-post_init:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -622,54 +518,6 @@ moveit.toyo_xyz:
|
||||
title: resource_manager参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_position:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
command: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: set_position的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: set_position参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_status:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
command: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: set_status的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: set_status参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-wait_for_resource_action:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -699,10 +547,9 @@ moveit.toyo_xyz:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -711,7 +558,6 @@ moveit.toyo_xyz:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -720,7 +566,6 @@ moveit.toyo_xyz:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -745,10 +590,9 @@ moveit.toyo_xyz:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -757,7 +601,6 @@ moveit.toyo_xyz:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -766,7 +609,6 @@ moveit.toyo_xyz:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -791,10 +633,9 @@ moveit.toyo_xyz:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -803,7 +644,6 @@ moveit.toyo_xyz:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -812,7 +652,6 @@ moveit.toyo_xyz:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -831,7 +670,7 @@ moveit.toyo_xyz:
|
||||
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
|
||||
status_types: {}
|
||||
type: python
|
||||
description: Toyo XYZ
|
||||
description: 东洋XYZ三轴运动平台,基于MoveIt2运动规划框架的精密定位设备。该设备通过ROS2和MoveIt2实现三维空间的精确运动控制,支持复杂轨迹规划、多点定位、速度控制等功能。具备高精度定位、平稳运动、实时轨迹监控等特性。适用于精密加工、样品定位、检测扫描、自动化装配等需要高精度三维运动控制的实验室和工业应用场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -856,3 +695,4 @@ moveit.toyo_xyz:
|
||||
model:
|
||||
mesh: toyo_xyz
|
||||
type: device
|
||||
version: 0.0.1
|
||||
|
||||
@@ -93,30 +93,6 @@ rotavap.one:
|
||||
title: set_rotate_time参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_timer:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
command: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: set_timer的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: set_timer参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
set_timer:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -127,10 +103,9 @@ rotavap.one:
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -139,7 +114,6 @@ rotavap.one:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -148,7 +122,6 @@ rotavap.one:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -167,7 +140,7 @@ rotavap.one:
|
||||
module: unilabos.devices.rotavap.rotavap_one:RotavapOne
|
||||
status_types: {}
|
||||
type: python
|
||||
description: Rotavap device
|
||||
description: 旋转蒸发仪设备,用于有机化学实验中的溶剂回收和浓缩操作。该设备通过串口通信控制,集成旋转和真空泵功能,支持定时控制和自动化操作。具备旋转速度调节、真空度控制、温度管理等功能,实现高效的溶剂蒸发和回收。适用于有机合成、天然产物提取、药物制备等需要溶剂去除和浓缩的实验室应用场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -185,6 +158,7 @@ rotavap.one:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
separator.homemade:
|
||||
class:
|
||||
action_value_mappings:
|
||||
@@ -208,38 +182,6 @@ separator.homemade:
|
||||
title: read_sensor_loop参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-stir:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
settling_time: 10
|
||||
stir_speed: 300
|
||||
stir_time: 10
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: stir的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
settling_time:
|
||||
default: 10
|
||||
type: number
|
||||
stir_speed:
|
||||
default: 300
|
||||
type: number
|
||||
stir_time:
|
||||
default: 10
|
||||
type: number
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: stir参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-valve_open:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -268,30 +210,6 @@ separator.homemade:
|
||||
title: valve_open参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-valve_open_cmd:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
command: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: valve_open_cmd的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: valve_open_cmd参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-write:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -335,10 +253,9 @@ separator.homemade:
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ROS Action Stir 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -347,7 +264,6 @@ separator.homemade:
|
||||
title: Stir_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
event:
|
||||
type: string
|
||||
@@ -374,7 +290,6 @@ separator.homemade:
|
||||
title: Stir_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
@@ -401,13 +316,12 @@ separator.homemade:
|
||||
goal_default:
|
||||
command: ''
|
||||
handles: []
|
||||
result":
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -416,7 +330,6 @@ separator.homemade:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -425,7 +338,6 @@ separator.homemade:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -444,7 +356,7 @@ separator.homemade:
|
||||
module: unilabos.devices.separator.homemade_grbl_conductivity:SeparatorController
|
||||
status_types: {}
|
||||
type: python
|
||||
description: Separator device with homemade grbl controller
|
||||
description: 液-液分离器设备,基于自制Grbl控制器的自动化分离系统。该设备集成搅拌、沉降、阀门控制和电导率传感器,通过串口通信实现精确的分离操作控制。支持自动搅拌、分层沉降、基于传感器反馈的智能分液等功能。适用于有机化学中的萃取分离、相分离、液-液提取等需要精确分离控制的实验应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -468,3 +380,4 @@ separator.homemade:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
|
||||
@@ -125,30 +125,6 @@ solenoid_valve:
|
||||
title: send_command参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_valve_position:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
position: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: set_valve_position的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
position:
|
||||
type: string
|
||||
required:
|
||||
- position
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: set_valve_position参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
set_valve_position:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -158,16 +134,14 @@ solenoid_valve:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action StrSingleInput 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: StrSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
@@ -176,7 +150,6 @@ solenoid_valve:
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -197,7 +170,7 @@ solenoid_valve:
|
||||
status: str
|
||||
valve_position: str
|
||||
type: python
|
||||
description: Solenoid valve
|
||||
description: 电磁阀控制设备,用于精确的流体路径控制和开关操作。该设备通过串口通信控制电磁阀的开关状态,支持远程操作和状态监测。具备快速响应、可靠密封、状态反馈等特性,广泛应用于流体输送、样品进样、路径切换等需要精确流体控制的实验室自动化应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -218,29 +191,10 @@ solenoid_valve:
|
||||
- status
|
||||
- valve_position
|
||||
type: object
|
||||
version: 0.0.1
|
||||
solenoid_valve.mock:
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-close:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: close的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: close参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-is_closed:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -281,26 +235,6 @@ solenoid_valve.mock:
|
||||
title: is_open参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-open:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: open的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: open参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_valve_position:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -332,22 +266,19 @@ solenoid_valve.mock:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action EmptyIn 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -367,22 +298,19 @@ solenoid_valve.mock:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action EmptyIn 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -400,7 +328,7 @@ solenoid_valve.mock:
|
||||
status: str
|
||||
valve_position: str
|
||||
type: python
|
||||
description: Mock solenoid valve
|
||||
description: 模拟电磁阀设备,用于系统测试和开发调试。该设备模拟真实电磁阀的开关操作和状态变化,提供与实际设备相同的控制接口和反馈机制。支持流体路径的虚拟控制,便于在没有实际硬件的情况下进行流体系统的集成测试和算法验证。适用于系统开发、流程调试和培训演示等场景。
|
||||
handles:
|
||||
- data_type: fluid
|
||||
handler_key: in
|
||||
@@ -431,6 +359,7 @@ solenoid_valve.mock:
|
||||
- status
|
||||
- valve_position
|
||||
type: object
|
||||
version: 0.0.1
|
||||
syringe_pump_with_valve.runze:
|
||||
class:
|
||||
action_value_mappings:
|
||||
@@ -825,7 +754,7 @@ syringe_pump_with_valve.runze:
|
||||
velocity_grade: String
|
||||
velocity_init: String
|
||||
type: python
|
||||
description: Runze Syringe pump with valve
|
||||
description: 润泽精密注射泵设备,集成阀门控制的高精度流体输送系统。该设备通过串口通信控制,支持多种运行模式和精确的体积控制。具备可变速度控制、精密定位、阀门切换、实时状态监控等功能。适用于微量液体输送、精密进样、流速控制、化学反应进料等需要高精度流体操作的实验室自动化应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -875,3 +804,4 @@ syringe_pump_with_valve.runze:
|
||||
- position
|
||||
- plunger_position
|
||||
type: object
|
||||
version: 0.0.1
|
||||
|
||||
@@ -11,7 +11,7 @@ agv.SEER:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: send的参数schema
|
||||
description: AGV底层通信命令发送函数。通过TCP socket连接向AGV发送底层控制命令,支持pose(位置)、status(状态)、nav(导航)等命令类型。用于获取AGV当前位置坐标、运行状态或发送导航指令。该函数封装了AGV的通信协议,将命令转换为十六进制数据包并处理响应解析。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -33,30 +33,6 @@ agv.SEER:
|
||||
title: send参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-send_nav_task:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
command: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: send_nav_task的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: send_nav_task参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
send_nav_task:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -67,10 +43,9 @@ agv.SEER:
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -79,7 +54,6 @@ agv.SEER:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -88,7 +62,6 @@ agv.SEER:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -109,7 +82,7 @@ agv.SEER:
|
||||
pose: list
|
||||
status: str
|
||||
type: python
|
||||
description: SEER AGV
|
||||
description: SEER AGV自动导引车设备,用于实验室内物料和设备的自主移动运输。该AGV通过TCP socket与导航系统通信,具备精确的定位和路径规划能力。支持实时位置监控、状态查询和导航任务执行,可在预设的实验室环境中自主移动至指定位置。适用于样品运输、设备转移、多工位协作等实验室自动化物流场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -130,3 +103,4 @@ agv.SEER:
|
||||
- pose
|
||||
- status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
|
||||
@@ -8,7 +8,7 @@ robotic_arm.UR:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: arm_init的参数schema
|
||||
description: 机械臂初始化函数。执行UR机械臂的完整初始化流程,包括上电、释放制动器、解除保护停止状态等。该函数确保机械臂从安全停止状态恢复到可操作状态,是机械臂使用前的必要步骤。初始化完成后机械臂将处于就绪状态,可以接收后续的运动指令。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -29,7 +29,7 @@ robotic_arm.UR:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: load_pose_data的参数schema
|
||||
description: 从JSON字符串加载位置数据函数。接收包含机械臂位置信息的JSON格式字符串,解析并存储位置数据供后续运动任务使用。位置数据通常包含多个预定义的工作位置坐标,用于实现精确的多点运动控制。适用于动态配置机械臂工作位置的场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -53,7 +53,7 @@ robotic_arm.UR:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: load_pose_file的参数schema
|
||||
description: 从文件加载位置数据函数。读取指定的JSON文件并加载其中的机械臂位置信息。该函数支持从外部配置文件中获取预设的工作位置,便于位置数据的管理和重用。适用于需要从固定配置文件中读取复杂位置序列的应用场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -69,30 +69,6 @@ robotic_arm.UR:
|
||||
title: load_pose_file参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-move_pos_task:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
command: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: move_pos_task的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: move_pos_task参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-reload_pose:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -100,7 +76,7 @@ robotic_arm.UR:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: reload_pose的参数schema
|
||||
description: 重新加载位置数据函数。重新读取并解析之前设置的位置文件,更新内存中的位置数据。该函数用于在位置文件被修改后刷新机械臂的位置配置,无需重新初始化整个系统。适用于动态更新机械臂工作位置的场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -123,10 +99,9 @@ robotic_arm.UR:
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -135,7 +110,6 @@ robotic_arm.UR:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -144,7 +118,6 @@ robotic_arm.UR:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -167,7 +140,7 @@ robotic_arm.UR:
|
||||
gripper_pose: float
|
||||
gripper_status: str
|
||||
type: python
|
||||
description: UR robotic arm
|
||||
description: Universal Robots机械臂设备,用于实验室精密操作和自动化作业。该设备集成了UR机械臂本体、Robotiq夹爪和RTDE通信接口,支持六自由度精确运动控制和力觉反馈。具备实时位置监控、状态反馈、轨迹规划等功能,可执行复杂的多点位运动任务。适用于样品抓取、精密装配、实验器具操作等需要高精度和高重复性的实验室自动化场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -197,3 +170,4 @@ robotic_arm.UR:
|
||||
- arm_status
|
||||
- gripper_status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
|
||||
@@ -51,7 +51,7 @@ gripper.misumi_rz:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: gripper_move的参数schema
|
||||
description: 夹爪抓取运动控制函数。控制夹爪的开合运动,支持位置、速度、力矩的精确设定。位置参数控制夹爪开合程度,速度参数控制运动快慢,力矩参数控制夹持强度。该函数提供安全的力控制,避免损坏被抓取物体,适用于各种形状和材质的物品抓取。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -80,7 +80,7 @@ gripper.misumi_rz:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: init_gripper的参数schema
|
||||
description: 夹爪初始化函数。执行Misumi RZ夹爪的完整初始化流程,包括Modbus通信建立、电机参数配置、传感器校准等。该函数确保夹爪系统从安全状态恢复到可操作状态,是夹爪使用前的必要步骤。初始化完成后夹爪将处于就绪状态,可接收抓取和旋转指令。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -169,7 +169,7 @@ gripper.misumi_rz:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: node_gripper_move的参数schema
|
||||
description: 节点夹爪移动任务函数。接收逗号分隔的命令字符串,解析位置、速度、力矩参数并执行夹爪抓取动作。该函数等待运动完成并返回执行结果,提供同步的运动控制接口。适用于需要可靠完成确认的精密抓取操作。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -193,7 +193,7 @@ gripper.misumi_rz:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: node_rotate_move的参数schema
|
||||
description: 节点旋转移动任务函数。接收逗号分隔的命令字符串,解析角度、速度、力矩参数并执行夹爪旋转动作。该函数等待旋转完成并返回执行结果,提供同步的旋转控制接口。适用于需要精确角度定位和完成确认的旋转操作。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -251,7 +251,7 @@ gripper.misumi_rz:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: rotate_move_abs的参数schema
|
||||
description: 夹爪绝对位置旋转控制函数。控制夹爪主轴旋转到指定的绝对角度位置,支持360度连续旋转。位置参数指定目标角度,速度参数控制旋转速率,力矩参数设定旋转阻力限制。该函数提供高精度的角度定位,适用于需要精确方向控制的操作场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -379,10 +379,9 @@ gripper.misumi_rz:
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -391,7 +390,6 @@ gripper.misumi_rz:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -400,7 +398,6 @@ gripper.misumi_rz:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -420,7 +417,7 @@ gripper.misumi_rz:
|
||||
status_types:
|
||||
status: str
|
||||
type: python
|
||||
description: Misumi RZ gripper
|
||||
description: Misumi RZ系列电子夹爪设备,集成旋转和抓取双重功能的精密夹爪系统。该设备通过Modbus RTU协议与控制系统通信,支持位置、速度、力矩的精确控制。具备高精度的位置反馈、实时状态监控和故障检测功能。适用于需要精密抓取和旋转操作的实验室自动化场景,如样品管理、精密装配、器件操作等应用。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -447,6 +444,7 @@ gripper.misumi_rz:
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
gripper.mock:
|
||||
class:
|
||||
action_value_mappings:
|
||||
@@ -461,7 +459,7 @@ gripper.mock:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: edit_id的参数schema
|
||||
description: 模拟夹爪资源ID编辑函数。用于测试和演示资源管理功能,模拟修改夹爪资源的标识信息。该函数接收工作流名称、参数和资源对象,模拟真实的资源更新过程并返回修改后的资源信息。适用于系统测试和开发调试场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -484,38 +482,6 @@ gripper.mock:
|
||||
title: edit_id参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-push_to:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
position: null
|
||||
torque: null
|
||||
velocity: 0.0
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: push_to的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
position:
|
||||
type: number
|
||||
torque:
|
||||
type: number
|
||||
velocity:
|
||||
default: 0.0
|
||||
type: number
|
||||
required:
|
||||
- position
|
||||
- torque
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: push_to参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
push_to:
|
||||
feedback:
|
||||
effort: torque
|
||||
@@ -532,10 +498,9 @@ gripper.mock:
|
||||
effort: torque
|
||||
position: position
|
||||
schema:
|
||||
description: ROS Action GripperCommand 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
effort:
|
||||
type: number
|
||||
@@ -553,7 +518,6 @@ gripper.mock:
|
||||
title: GripperCommand_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
properties:
|
||||
@@ -571,7 +535,6 @@ gripper.mock:
|
||||
title: GripperCommand_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
effort:
|
||||
type: number
|
||||
@@ -600,7 +563,7 @@ gripper.mock:
|
||||
torque: float
|
||||
velocity: float
|
||||
type: python
|
||||
description: Mock gripper
|
||||
description: 模拟夹爪设备,用于系统测试和开发调试。该设备模拟真实夹爪的位置、速度、力矩等物理特性,支持虚拟的抓取和移动操作。提供与真实夹爪相同的接口和状态反馈,便于在没有实际硬件的情况下进行系统集成测试和算法验证。适用于软件开发、系统调试和培训演示等场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -624,3 +587,4 @@ gripper.mock:
|
||||
- torque
|
||||
- status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
|
||||
@@ -8,7 +8,7 @@ linear_motion.grbl:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: initialize的参数schema
|
||||
description: CNC设备初始化函数。执行Grbl CNC的完整初始化流程,包括归零操作、轴校准和状态复位。该函数将所有轴移动到原点位置(0,0,0),确保设备处于已知的参考状态。初始化完成后设备进入空闲状态,可接收后续的运动指令。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -21,30 +21,6 @@ linear_motion.grbl:
|
||||
title: initialize参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-move_through_points:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
positions: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: move_through_points的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
positions:
|
||||
type: array
|
||||
required:
|
||||
- positions
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: move_through_points参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_position:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -53,7 +29,7 @@ linear_motion.grbl:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: set_position的参数schema
|
||||
description: CNC绝对位置设定函数。控制CNC设备移动到指定的三维坐标位置(x,y,z)。该函数支持安全限位检查,防止超出设备工作范围。移动过程中会监控设备状态,确保安全到达目标位置。适用于精确定位和轨迹控制操作。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -69,34 +45,6 @@ linear_motion.grbl:
|
||||
title: set_position参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_spindle_speed:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
max_velocity: 500
|
||||
spindle_speed: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: set_spindle_speed的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
max_velocity:
|
||||
default: 500
|
||||
type: number
|
||||
spindle_speed:
|
||||
type: number
|
||||
required:
|
||||
- spindle_speed
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: set_spindle_speed参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-stop_operation:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -104,7 +52,7 @@ linear_motion.grbl:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: stop_operation的参数schema
|
||||
description: CNC操作停止函数。立即停止当前正在执行的所有CNC运动,包括轴移动和主轴旋转。该函数用于紧急停止或任务中断,确保设备和工件的安全。停止后设备将保持当前位置,等待新的指令。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -166,10 +114,9 @@ linear_motion.grbl:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action NavigateThroughPoses 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
current_pose:
|
||||
properties:
|
||||
@@ -290,7 +237,6 @@ linear_motion.grbl:
|
||||
title: NavigateThroughPoses_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
behavior_tree:
|
||||
type: string
|
||||
@@ -371,7 +317,6 @@ linear_motion.grbl:
|
||||
title: NavigateThroughPoses_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
result:
|
||||
properties: {}
|
||||
@@ -401,10 +346,9 @@ linear_motion.grbl:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action SingleJointPosition 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
error:
|
||||
type: number
|
||||
@@ -444,7 +388,6 @@ linear_motion.grbl:
|
||||
title: SingleJointPosition_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
max_velocity:
|
||||
type: number
|
||||
@@ -472,7 +415,6 @@ linear_motion.grbl:
|
||||
title: SingleJointPosition_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: SingleJointPosition_Result
|
||||
@@ -488,7 +430,7 @@ linear_motion.grbl:
|
||||
spindle_speed: float
|
||||
status: str
|
||||
type: python
|
||||
description: Grbl CNC
|
||||
description: Grbl数控机床(CNC)设备,用于实验室精密加工和三轴定位操作。该设备基于Grbl固件,通过串口通信控制步进电机实现X、Y、Z三轴的精确运动。支持绝对定位、轨迹规划、主轴控制和实时状态监控。具备安全限位保护和运动平滑控制功能。适用于精密钻孔、铣削、雕刻、样品制备等需要高精度定位和加工的实验室应用场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -524,6 +466,7 @@ linear_motion.grbl:
|
||||
- position
|
||||
- spindle_speed
|
||||
type: object
|
||||
version: 0.0.1
|
||||
motor.iCL42:
|
||||
class:
|
||||
action_value_mappings:
|
||||
@@ -537,7 +480,7 @@ motor.iCL42:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: execute_run_motor的参数schema
|
||||
description: 步进电机执行运动函数。直接执行电机运动命令,包括位置设定、速度控制和路径规划。该函数处理底层的电机控制协议,消除警告信息,设置运动参数并启动电机运行。适用于需要直接控制电机运动的应用场景。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -566,7 +509,7 @@ motor.iCL42:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: init_device的参数schema
|
||||
description: iCL42电机设备初始化函数。建立与iCL42步进电机驱动器的串口通信连接,配置通信参数包括波特率、数据位、校验位等。该函数是电机使用前的必要步骤,确保驱动器处于可控状态并准备接收运动指令。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -589,7 +532,7 @@ motor.iCL42:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: run_motor的参数schema
|
||||
description: 步进电机运动控制函数。根据指定的运动模式、目标位置和速度参数控制电机运动。支持多种运动模式和精确的位置控制,自动处理运动轨迹规划和执行。该函数提供异步执行和状态反馈,确保运动的准确性和可靠性。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -621,10 +564,9 @@ motor.iCL42:
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -633,7 +575,6 @@ motor.iCL42:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -642,7 +583,6 @@ motor.iCL42:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -664,7 +604,7 @@ motor.iCL42:
|
||||
motor_position: int
|
||||
success: bool
|
||||
type: python
|
||||
description: iCL42 motor
|
||||
description: iCL42步进电机驱动器,用于实验室设备的精密线性运动控制。该设备通过串口通信控制iCL42型步进电机驱动器,支持多种运动模式和精确的位置、速度控制。具备位置反馈、运行状态监控和故障检测功能。适用于自动进样器、样品传送、精密定位平台等需要准确线性运动控制的实验室自动化设备。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -691,3 +631,4 @@ motor.iCL42:
|
||||
- is_executing_run
|
||||
- success
|
||||
type: object
|
||||
version: 0.0.1
|
||||
|
||||
@@ -285,7 +285,7 @@ lh_joint_publisher:
|
||||
module: unilabos.devices.ros_dev.liquid_handler_joint_publisher:LiquidHandlerJointPublisher
|
||||
status_types: {}
|
||||
type: ros2
|
||||
description: ''
|
||||
description: 液体处理器关节发布器,用于ROS2仿真系统中的液体处理设备运动控制。该节点通过发布关节状态驱动仿真模型中的机械臂运动,支持三维坐标到关节空间的逆运动学转换、多关节协调控制、资源跟踪和TF变换。具备精确的位置控制、速度调节、pick-and-place操作等功能。适用于液体处理系统的虚拟仿真、运动规划验证、系统集成测试等应用场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -309,3 +309,4 @@ lh_joint_publisher:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
|
||||
@@ -89,30 +89,6 @@ chiller:
|
||||
title: modbus_crc参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_temperature:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
command: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: set_temperature的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: set_temperature参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-stop:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -143,10 +119,9 @@ chiller:
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -155,7 +130,6 @@ chiller:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -164,7 +138,6 @@ chiller:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -183,7 +156,7 @@ chiller:
|
||||
module: unilabos.devices.temperature.chiller:Chiller
|
||||
status_types: {}
|
||||
type: python
|
||||
description: Chiller
|
||||
description: 实验室制冷设备,用于精确的温度控制和冷却操作。该设备通过Modbus RTU协议与控制系统通信,支持精确的温度设定和监控。具备快速降温、恒温控制和温度保持功能,广泛应用于需要低温环境的化学反应、样品保存、结晶操作等实验场景。提供稳定可靠的冷却性能,确保实验过程的温度精度。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -201,6 +174,7 @@ chiller:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
version: 0.0.1
|
||||
heaterstirrer.dalong:
|
||||
class:
|
||||
action_value_mappings:
|
||||
@@ -224,50 +198,6 @@ heaterstirrer.dalong:
|
||||
title: close参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-heatchill:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
purpose: reaction
|
||||
stir: true
|
||||
stir_speed: 300
|
||||
temp: null
|
||||
time: 3600
|
||||
vessel: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: heatchill的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
purpose:
|
||||
default: reaction
|
||||
type: string
|
||||
stir:
|
||||
default: true
|
||||
type: boolean
|
||||
stir_speed:
|
||||
default: 300
|
||||
type: number
|
||||
temp:
|
||||
type: number
|
||||
time:
|
||||
default: 3600
|
||||
type: number
|
||||
vessel:
|
||||
type: string
|
||||
required:
|
||||
- vessel
|
||||
- temp
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: heatchill参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_stir_speed:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -320,54 +250,6 @@ heaterstirrer.dalong:
|
||||
title: set_temp_inner参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_temp_target:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
temp: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: set_temp_target的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
temp:
|
||||
type: string
|
||||
required:
|
||||
- temp
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: set_temp_target参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_temp_warning:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
temp: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: set_temp_warning的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
temp:
|
||||
type: string
|
||||
required:
|
||||
- temp
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: set_temp_warning参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
heatchill:
|
||||
feedback:
|
||||
status: status
|
||||
@@ -391,10 +273,9 @@ heaterstirrer.dalong:
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ROS Action HeatChill 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -403,7 +284,6 @@ heaterstirrer.dalong:
|
||||
title: HeatChill_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
pressure:
|
||||
type: string
|
||||
@@ -439,7 +319,6 @@ heaterstirrer.dalong:
|
||||
title: HeatChill_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
@@ -468,10 +347,9 @@ heaterstirrer.dalong:
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -480,7 +358,6 @@ heaterstirrer.dalong:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -489,7 +366,6 @@ heaterstirrer.dalong:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -515,10 +391,9 @@ heaterstirrer.dalong:
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -527,7 +402,6 @@ heaterstirrer.dalong:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -536,7 +410,6 @@ heaterstirrer.dalong:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -560,7 +433,7 @@ heaterstirrer.dalong:
|
||||
temp_target: float
|
||||
temp_warning: float
|
||||
type: python
|
||||
description: DaLong heater stirrer
|
||||
description: 大龙加热搅拌器,集成加热和搅拌双重功能的实验室设备。该设备通过串口通信控制,支持精确的温度调节、搅拌速度控制和安全保护功能。具备实时温度监测、目标温度设定、安全温度报警等特性。适用于化学合成、样品制备、反应控制等需要同时进行加热和搅拌的实验操作,提供稳定均匀的反应环境。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -596,6 +469,7 @@ heaterstirrer.dalong:
|
||||
- temp_warning
|
||||
- temp_target
|
||||
type: object
|
||||
version: 0.0.1
|
||||
tempsensor:
|
||||
class:
|
||||
action_value_mappings:
|
||||
@@ -707,30 +581,6 @@ tempsensor:
|
||||
title: send_prototype_command参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_warning:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
command: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: set_warning的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
required:
|
||||
- command
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: set_warning参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
set_warning:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -741,10 +591,9 @@ tempsensor:
|
||||
result:
|
||||
success: success
|
||||
schema:
|
||||
description: ROS Action SendCmd 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
@@ -753,7 +602,6 @@ tempsensor:
|
||||
title: SendCmd_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
command:
|
||||
type: string
|
||||
@@ -762,7 +610,6 @@ tempsensor:
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -782,7 +629,7 @@ tempsensor:
|
||||
status_types:
|
||||
value: float
|
||||
type: python
|
||||
description: Temperature sensor
|
||||
description: 高精度温度传感器设备,用于实验室环境和设备的温度监测。该传感器通过Modbus RTU协议与控制系统通信,提供实时准确的温度数据。具备高精度测量、报警温度设定、数据稳定性好等特点。适用于反应器监控、环境温度监测、设备保护等需要精确温度测量的实验场景,为实验安全和数据可靠性提供保障。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -809,3 +656,4 @@ tempsensor:
|
||||
required:
|
||||
- value
|
||||
type: object
|
||||
version: 0.0.1
|
||||
|
||||
@@ -1,26 +1,6 @@
|
||||
gas_source.mock:
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-close:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: close的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: close参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-is_closed:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -61,50 +41,6 @@ gas_source.mock:
|
||||
title: is_open参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-open:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: open的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: open参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_status:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
string: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: set_status的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
required:
|
||||
- string
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: set_status参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
close:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -112,22 +48,19 @@ gas_source.mock:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action EmptyIn 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -147,22 +80,19 @@ gas_source.mock:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action EmptyIn 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -184,16 +114,14 @@ gas_source.mock:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action StrSingleInput 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: StrSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
@@ -202,7 +130,6 @@ gas_source.mock:
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -222,7 +149,7 @@ gas_source.mock:
|
||||
status_types:
|
||||
status: str
|
||||
type: python
|
||||
description: Mock gas source
|
||||
description: 模拟气体源设备,用于系统测试和开发调试。该设备模拟真实气体源的开关控制和状态监测功能,支持气体供应的启停操作。提供与真实气体源相同的接口和状态反馈,便于在没有实际硬件的情况下进行系统集成测试和算法验证。适用于气路系统调试、软件开发和实验流程验证等场景。
|
||||
handles:
|
||||
- data_key: fluid_out
|
||||
data_source: executor
|
||||
@@ -246,29 +173,10 @@ gas_source.mock:
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
vacuum_pump.mock:
|
||||
class:
|
||||
action_value_mappings:
|
||||
auto-close:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: close的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: close参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-is_closed:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -309,50 +217,6 @@ vacuum_pump.mock:
|
||||
title: is_open参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-open:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: open的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: open参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-set_status:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
string: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: set_status的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
required:
|
||||
- string
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: set_status参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
close:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -360,22 +224,19 @@ vacuum_pump.mock:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action EmptyIn 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -395,22 +256,19 @@ vacuum_pump.mock:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action EmptyIn 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -432,16 +290,14 @@ vacuum_pump.mock:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action StrSingleInput 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: StrSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
@@ -450,7 +306,6 @@ vacuum_pump.mock:
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -470,7 +325,7 @@ vacuum_pump.mock:
|
||||
status_types:
|
||||
status: str
|
||||
type: python
|
||||
description: Mock vacuum pump
|
||||
description: 模拟真空泵设备,用于系统测试和开发调试。该设备模拟真实真空泵的抽气功能和状态控制,支持真空系统的启停操作和状态监测。提供与真实真空泵相同的接口和控制逻辑,便于在没有实际硬件的情况下进行真空系统的集成测试。适用于真空工艺调试、软件开发和实验流程验证等场景。
|
||||
handles:
|
||||
- data_key: fluid_in
|
||||
data_source: handle
|
||||
@@ -494,3 +349,4 @@ vacuum_pump.mock:
|
||||
required:
|
||||
- status
|
||||
type: object
|
||||
version: 0.0.1
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,22 +8,19 @@ zhida_hplc:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action EmptyIn 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -36,26 +33,6 @@ zhida_hplc:
|
||||
title: EmptyIn
|
||||
type: object
|
||||
type: EmptyIn
|
||||
auto-abort:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default: {}
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: abort的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties: {}
|
||||
required: []
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: abort参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-close:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -63,7 +40,7 @@ zhida_hplc:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: close的参数schema
|
||||
description: HPLC设备连接关闭函数。安全地断开与智达HPLC设备的TCP socket连接,释放网络资源。该函数确保连接的正确关闭,避免网络资源泄露。通常在设备使用完毕或系统关闭时调用。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -83,7 +60,7 @@ zhida_hplc:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: connect的参数schema
|
||||
description: HPLC设备连接建立函数。与智达HPLC设备建立TCP socket通信连接,配置通信超时参数。该函数是设备使用前的必要步骤,建立成功后可进行状态查询、方法获取、任务启动等操作。连接失败时会抛出异常。
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
@@ -96,30 +73,6 @@ zhida_hplc:
|
||||
title: connect参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
auto-start:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
goal_default:
|
||||
text: null
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: start的参数schema
|
||||
properties:
|
||||
feedback: {}
|
||||
goal:
|
||||
properties:
|
||||
text:
|
||||
type: string
|
||||
required:
|
||||
- text
|
||||
type: object
|
||||
result: {}
|
||||
required:
|
||||
- goal
|
||||
title: start参数
|
||||
type: object
|
||||
type: UniLabJsonCommand
|
||||
get_methods:
|
||||
feedback: {}
|
||||
goal: {}
|
||||
@@ -127,22 +80,19 @@ zhida_hplc:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action EmptyIn 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties: {}
|
||||
required: []
|
||||
title: EmptyIn_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -164,16 +114,14 @@ zhida_hplc:
|
||||
handles: []
|
||||
result: {}
|
||||
schema:
|
||||
description: ROS Action StrSingleInput 的 JSON Schema
|
||||
description: ''
|
||||
properties:
|
||||
feedback:
|
||||
description: Action 反馈 - 执行过程中从服务器发送到客户端
|
||||
properties: {}
|
||||
required: []
|
||||
title: StrSingleInput_Feedback
|
||||
type: object
|
||||
goal:
|
||||
description: Action 目标 - 从客户端发送到服务器
|
||||
properties:
|
||||
string:
|
||||
type: string
|
||||
@@ -182,7 +130,6 @@ zhida_hplc:
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
result:
|
||||
description: Action 结果 - 完成后从服务器发送到客户端
|
||||
properties:
|
||||
return_info:
|
||||
type: string
|
||||
@@ -203,7 +150,7 @@ zhida_hplc:
|
||||
methods: dict
|
||||
status: dict
|
||||
type: python
|
||||
description: Zhida HPLC
|
||||
description: 智达高效液相色谱(HPLC)分析设备,用于实验室样品的分离、检测和定量分析。该设备通过TCP socket与HPLC控制系统通信,支持远程控制和状态监控。具备自动进样、梯度洗脱、多检测器数据采集等功能,可执行复杂的色谱分析方法。适用于化学分析、药物检测、环境监测、生物样品分析等需要高精度分离分析的实验室应用场景。
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema:
|
||||
@@ -230,3 +177,4 @@ zhida_hplc:
|
||||
- status
|
||||
- methods
|
||||
type: object
|
||||
version: 0.0.1
|
||||
|
||||
@@ -2,6 +2,8 @@ import copy
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
|
||||
@@ -63,7 +65,7 @@ class Registry:
|
||||
},
|
||||
"feedback": {},
|
||||
"result": {"success": "success"},
|
||||
"schema": ros_action_to_json_schema(self.ResourceCreateFromOuter),
|
||||
"schema": ros_action_to_json_schema(self.ResourceCreateFromOuter, '用于创建或更新物料资源,每次传入多个物料信息。'),
|
||||
"goal_default": yaml.safe_load(
|
||||
io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuter.Goal))
|
||||
),
|
||||
@@ -84,7 +86,7 @@ class Registry:
|
||||
},
|
||||
"feedback": {},
|
||||
"result": {"success": "success"},
|
||||
"schema": ros_action_to_json_schema(self.ResourceCreateFromOuterEasy),
|
||||
"schema": ros_action_to_json_schema(self.ResourceCreateFromOuterEasy, '用于创建或更新物料资源,每次传入一个物料信息。'),
|
||||
"goal_default": yaml.safe_load(
|
||||
io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuterEasy.Goal))
|
||||
),
|
||||
@@ -99,18 +101,25 @@ class Registry:
|
||||
}
|
||||
]
|
||||
},
|
||||
# todo: support nested keys, switch to non ros message schema
|
||||
"placeholder_keys": {
|
||||
"res_id": "unilabos_resources", # 将当前实验室的全部物料id作为下拉框可选择
|
||||
"device_id": "unilabos_devices", # 将当前实验室的全部设备id作为下拉框可选择
|
||||
"parent": "unilabos_devices", # 将当前实验室的全部设备id作为下拉框可选择
|
||||
},
|
||||
},
|
||||
"test_latency": {
|
||||
"type": self.EmptyIn,
|
||||
"goal": {},
|
||||
"feedback": {},
|
||||
"result": {"latency_ms": "latency_ms", "time_diff_ms": "time_diff_ms"},
|
||||
"schema": ros_action_to_json_schema(self.EmptyIn),
|
||||
"schema": ros_action_to_json_schema(self.EmptyIn, '用于测试延迟的动作,返回延迟时间和时间差。'),
|
||||
"goal_default": {},
|
||||
"handles": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
"version": "0.0.1",
|
||||
"icon": "icon_device.webp",
|
||||
"registry_type": "device",
|
||||
"handles": [],
|
||||
@@ -161,6 +170,54 @@ class Registry:
|
||||
else:
|
||||
logger.debug(f"[UniLab Registry] Res File-{i+1}/{len(files)} Not Valid YAML File: {file.absolute()}")
|
||||
|
||||
def _extract_class_docstrings(self, module_string: str) -> Dict[str, str]:
|
||||
"""
|
||||
从模块字符串中提取类和方法的docstring信息
|
||||
|
||||
Args:
|
||||
module_string: 模块字符串,格式为 "module.path:ClassName"
|
||||
|
||||
Returns:
|
||||
包含类和方法docstring信息的字典
|
||||
"""
|
||||
docstrings = {"class_docstring": "", "methods": {}}
|
||||
|
||||
if not module_string or ":" not in module_string:
|
||||
return docstrings
|
||||
|
||||
try:
|
||||
module_path, class_name = module_string.split(":", 1)
|
||||
|
||||
# 动态导入模块
|
||||
module = importlib.import_module(module_path)
|
||||
|
||||
# 获取类
|
||||
if hasattr(module, class_name):
|
||||
cls = getattr(module, class_name)
|
||||
|
||||
# 获取类的docstring
|
||||
class_doc = inspect.getdoc(cls)
|
||||
if class_doc:
|
||||
docstrings["class_docstring"] = class_doc.strip()
|
||||
|
||||
# 获取所有方法的docstring
|
||||
for method_name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
|
||||
method_doc = inspect.getdoc(method)
|
||||
if method_doc:
|
||||
docstrings["methods"][method_name] = method_doc.strip()
|
||||
|
||||
# 也获取属性方法的docstring
|
||||
for method_name, method in inspect.getmembers(cls, predicate=lambda x: isinstance(x, property)):
|
||||
if hasattr(method, "fget") and method.fget:
|
||||
method_doc = inspect.getdoc(method.fget)
|
||||
if method_doc:
|
||||
docstrings["methods"][method_name] = method_doc.strip()
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[UniLab Registry] 无法提取docstring信息,模块: {module_string}, 错误: {str(e)}")
|
||||
|
||||
return docstrings
|
||||
|
||||
def _replace_type_with_class(self, type_name: str, device_id: str, field_name: str) -> Any:
|
||||
"""
|
||||
将类型名称替换为实际的类对象
|
||||
@@ -274,15 +331,13 @@ class Registry:
|
||||
param_type = arg_info.get("type", "")
|
||||
param_default = arg_info.get("default")
|
||||
param_required = arg_info.get("required", True)
|
||||
schema["properties"][param_name] = self._generate_schema_from_info(
|
||||
param_name, param_type, param_default
|
||||
)
|
||||
schema["properties"][param_name] = self._generate_schema_from_info(param_name, param_type, param_default)
|
||||
if param_required:
|
||||
schema["required"].append(param_name)
|
||||
|
||||
return {
|
||||
"title": f"{method_name}参数",
|
||||
"description": f"{method_name}的参数schema",
|
||||
"description": f"",
|
||||
"type": "object",
|
||||
"properties": {"goal": schema, "feedback": {}, "result": {}},
|
||||
"required": ["goal"],
|
||||
@@ -313,6 +368,8 @@ class Registry:
|
||||
# 在添加到注册表前处理类型替换
|
||||
for device_id, device_config in data.items():
|
||||
# 添加文件路径信息 - 使用规范化的完整文件路径
|
||||
if "version" not in device_config:
|
||||
device_config["version"] = "0.0.1"
|
||||
if "description" not in device_config:
|
||||
device_config["description"] = ""
|
||||
if "icon" not in device_config:
|
||||
@@ -348,6 +405,14 @@ class Registry:
|
||||
sorted(device_config["class"]["status_types"].items())
|
||||
)
|
||||
if complete_registry:
|
||||
# 保存原有的description信息
|
||||
old_descriptions = {}
|
||||
for action_name, action_config in device_config["class"]["action_value_mappings"].items():
|
||||
if "description" in action_config.get("schema", {}):
|
||||
description = action_config["schema"]["description"]
|
||||
if len(description):
|
||||
old_descriptions[action_name] = action_config["schema"]["description"]
|
||||
|
||||
device_config["class"]["action_value_mappings"] = {
|
||||
k: v
|
||||
for k, v in device_config["class"]["action_value_mappings"].items()
|
||||
@@ -365,9 +430,15 @@ class Registry:
|
||||
"goal_default": {i["name"]: i["default"] for i in v["args"]},
|
||||
"handles": [],
|
||||
}
|
||||
# 不生成已配置action的动作
|
||||
for k, v in enhanced_info["action_methods"].items()
|
||||
if k not in device_config["class"]["action_value_mappings"]
|
||||
}
|
||||
)
|
||||
|
||||
# 恢复原有的description信息(auto开头的不修改)
|
||||
for action_name, description in old_descriptions.items():
|
||||
device_config["class"]["action_value_mappings"][action_name]["schema"]["description"] = description
|
||||
device_config["init_param_schema"] = {}
|
||||
device_config["init_param_schema"]["config"] = self._generate_unilab_json_command_schema(
|
||||
enhanced_info["init_params"], "__init__"
|
||||
@@ -471,6 +542,13 @@ class Registry:
|
||||
},
|
||||
**schema["properties"]["goal"]["properties"],
|
||||
}
|
||||
# 将 placeholder_keys 信息添加到 schema 中
|
||||
if "placeholder_keys" in action_config and action_config.get("schema", {}).get(
|
||||
"properties", {}
|
||||
).get("goal", {}):
|
||||
action_config["schema"]["properties"]["goal"]["_unilabos_placeholder_info"] = action_config[
|
||||
"placeholder_keys"
|
||||
]
|
||||
|
||||
msg = {"id": device_id, **device_info_copy}
|
||||
devices.append(msg)
|
||||
|
||||
@@ -132,7 +132,11 @@ _msg_converter: Dict[Type, Any] = {
|
||||
Bool: lambda x: Bool(data=bool(x)),
|
||||
str: str,
|
||||
String: lambda x: String(data=str(x)),
|
||||
Point: lambda x: Point(x=x.x, y=x.y, z=x.z) if not isinstance(x, dict) else Point(x=x.get("x", 0.0), y=x.get("y", 0.0), z=x.get("z", 0.0)),
|
||||
Point: lambda x: (
|
||||
Point(x=x.x, y=x.y, z=x.z)
|
||||
if not isinstance(x, dict)
|
||||
else Point(x=float(x.get("x", 0.0)), y=float(x.get("y", 0.0)), z=float(x.get("z", 0.0)))
|
||||
),
|
||||
Resource: lambda x: Resource(
|
||||
id=x.get("id", ""),
|
||||
name=x.get("name", ""),
|
||||
@@ -142,7 +146,13 @@ _msg_converter: Dict[Type, Any] = {
|
||||
type=x.get("type", ""),
|
||||
category=x.get("class", "") or x.get("type", ""),
|
||||
pose=(
|
||||
Pose(position=Point(x=float(x.get("position", {}).get("x", 0.0)), y=float(x.get("position", {}).get("y", 0.0)), z=float(x.get("position", {}).get("z", 0.0))))
|
||||
Pose(
|
||||
position=Point(
|
||||
x=float(x.get("position", {}).get("x", 0.0)),
|
||||
y=float(x.get("position", {}).get("y", 0.0)),
|
||||
z=float(x.get("position", {}).get("z", 0.0)),
|
||||
)
|
||||
)
|
||||
if x.get("position", None) is not None
|
||||
else Pose()
|
||||
),
|
||||
@@ -151,6 +161,7 @@ _msg_converter: Dict[Type, Any] = {
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def json_or_yaml_loads(data: str) -> Any:
|
||||
try:
|
||||
return json.loads(data)
|
||||
@@ -161,6 +172,7 @@ def json_or_yaml_loads(data: str) -> Any:
|
||||
pass
|
||||
raise e
|
||||
|
||||
|
||||
# ROS消息到Python转换器
|
||||
_msg_converter_back: Dict[Type, Any] = {
|
||||
float: float,
|
||||
@@ -571,30 +583,30 @@ from unilabos.utils.import_manager import ImportManager
|
||||
from unilabos.config.config import ROSConfig
|
||||
|
||||
basic_type_map = {
|
||||
'bool': {'type': 'boolean'},
|
||||
'int8': {'type': 'integer', 'minimum': -128, 'maximum': 127},
|
||||
'uint8': {'type': 'integer', 'minimum': 0, 'maximum': 255},
|
||||
'int16': {'type': 'integer', 'minimum': -32768, 'maximum': 32767},
|
||||
'uint16': {'type': 'integer', 'minimum': 0, 'maximum': 65535},
|
||||
'int32': {'type': 'integer', 'minimum': -2147483648, 'maximum': 2147483647},
|
||||
'uint32': {'type': 'integer', 'minimum': 0, 'maximum': 4294967295},
|
||||
'int64': {'type': 'integer'},
|
||||
'uint64': {'type': 'integer', 'minimum': 0},
|
||||
'double': {'type': 'number'},
|
||||
'float': {'type': 'number'},
|
||||
'float32': {'type': 'number'},
|
||||
'float64': {'type': 'number'},
|
||||
'string': {'type': 'string'},
|
||||
'boolean': {'type': 'boolean'},
|
||||
'char': {'type': 'string', 'maxLength': 1},
|
||||
'byte': {'type': 'integer', 'minimum': 0, 'maximum': 255},
|
||||
"bool": {"type": "boolean"},
|
||||
"int8": {"type": "integer", "minimum": -128, "maximum": 127},
|
||||
"uint8": {"type": "integer", "minimum": 0, "maximum": 255},
|
||||
"int16": {"type": "integer", "minimum": -32768, "maximum": 32767},
|
||||
"uint16": {"type": "integer", "minimum": 0, "maximum": 65535},
|
||||
"int32": {"type": "integer", "minimum": -2147483648, "maximum": 2147483647},
|
||||
"uint32": {"type": "integer", "minimum": 0, "maximum": 4294967295},
|
||||
"int64": {"type": "integer"},
|
||||
"uint64": {"type": "integer", "minimum": 0},
|
||||
"double": {"type": "number"},
|
||||
"float": {"type": "number"},
|
||||
"float32": {"type": "number"},
|
||||
"float64": {"type": "number"},
|
||||
"string": {"type": "string"},
|
||||
"boolean": {"type": "boolean"},
|
||||
"char": {"type": "string", "maxLength": 1},
|
||||
"byte": {"type": "integer", "minimum": 0, "maximum": 255},
|
||||
}
|
||||
|
||||
|
||||
def ros_field_type_to_json_schema(type_info: Type | str, slot_type: str=None) -> Dict[str, Any]:
|
||||
def ros_field_type_to_json_schema(type_info: Type | str, slot_type: str = None) -> Dict[str, Any]:
|
||||
"""
|
||||
将 ROS 字段类型转换为 JSON Schema 类型定义
|
||||
|
||||
|
||||
Args:
|
||||
type_info: ROS 类型
|
||||
slot_type: ROS 类型
|
||||
@@ -603,10 +615,7 @@ def ros_field_type_to_json_schema(type_info: Type | str, slot_type: str=None) ->
|
||||
对应的 JSON Schema 类型定义
|
||||
"""
|
||||
if isinstance(type_info, UnboundedSequence):
|
||||
return {
|
||||
'type': 'array',
|
||||
'items': ros_field_type_to_json_schema(type_info.value_type)
|
||||
}
|
||||
return {"type": "array", "items": ros_field_type_to_json_schema(type_info.value_type)}
|
||||
if isinstance(type_info, NamespacedType):
|
||||
cls_name = ".".join(type_info.namespaces) + ":" + type_info.name
|
||||
type_class = msg_converter_manager.get_class(cls_name)
|
||||
@@ -614,20 +623,20 @@ def ros_field_type_to_json_schema(type_info: Type | str, slot_type: str=None) ->
|
||||
elif isinstance(type_info, BasicType):
|
||||
return ros_field_type_to_json_schema(type_info.typename)
|
||||
elif isinstance(type_info, UnboundedString):
|
||||
return basic_type_map['string']
|
||||
return basic_type_map["string"]
|
||||
elif isinstance(type_info, str):
|
||||
if type_info in basic_type_map:
|
||||
return basic_type_map[type_info]
|
||||
|
||||
# 处理时间和持续时间类型
|
||||
if type_info in ('time', 'duration', 'builtin_interfaces/Time', 'builtin_interfaces/Duration'):
|
||||
if type_info in ("time", "duration", "builtin_interfaces/Time", "builtin_interfaces/Duration"):
|
||||
return {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'sec': {'type': 'integer', 'description': '秒'},
|
||||
'nanosec': {'type': 'integer', 'description': '纳秒'}
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sec": {"type": "integer", "description": "秒"},
|
||||
"nanosec": {"type": "integer", "description": "纳秒"},
|
||||
},
|
||||
'required': ['sec', 'nanosec']
|
||||
"required": ["sec", "nanosec"],
|
||||
}
|
||||
else:
|
||||
return ros_message_to_json_schema(type_info)
|
||||
@@ -638,9 +647,7 @@ def ros_field_type_to_json_schema(type_info: Type | str, slot_type: str=None) ->
|
||||
# 'type': 'array',
|
||||
# 'items': ros_field_type_to_json_schema(item_type)
|
||||
# }
|
||||
|
||||
|
||||
|
||||
# # 处理复杂类型(尝试加载并处理)
|
||||
# try:
|
||||
# # 如果它是一个完整的消息类型规范 (包名/msg/类型名)
|
||||
@@ -655,34 +662,31 @@ def ros_field_type_to_json_schema(type_info: Type | str, slot_type: str=None) ->
|
||||
# logger.debug(f"无法解析类型 {field_type}: {str(e)}")
|
||||
# return {'type': 'object', 'description': f'未知类型: {field_type}'}
|
||||
|
||||
|
||||
def ros_message_to_json_schema(msg_class: Any) -> Dict[str, Any]:
|
||||
"""
|
||||
将 ROS 消息类转换为 JSON Schema
|
||||
|
||||
|
||||
Args:
|
||||
msg_class: ROS 消息类
|
||||
|
||||
|
||||
Returns:
|
||||
对应的 JSON Schema 定义
|
||||
"""
|
||||
schema = {
|
||||
'type': 'object',
|
||||
'properties': {},
|
||||
'required': []
|
||||
}
|
||||
|
||||
schema = {"type": "object", "properties": {}, "required": []}
|
||||
|
||||
# 获取类名作为标题
|
||||
if hasattr(msg_class, '__name__'):
|
||||
schema['title'] = msg_class.__name__
|
||||
|
||||
if hasattr(msg_class, "__name__"):
|
||||
schema["title"] = msg_class.__name__
|
||||
|
||||
# 获取消息的字段和字段类型
|
||||
try:
|
||||
for ind, slot_info in enumerate(msg_class._fields_and_field_types.items()):
|
||||
slot_name, slot_type = slot_info
|
||||
type_info = msg_class.SLOT_TYPES[ind]
|
||||
field_schema = ros_field_type_to_json_schema(type_info, slot_type)
|
||||
schema['properties'][slot_name] = field_schema
|
||||
schema['required'].append(slot_name)
|
||||
schema["properties"][slot_name] = field_schema
|
||||
schema["required"].append(slot_name)
|
||||
# if hasattr(msg_class, 'get_fields_and_field_types'):
|
||||
# fields_and_types = msg_class.get_fields_and_field_types()
|
||||
#
|
||||
@@ -707,61 +711,66 @@ def ros_message_to_json_schema(msg_class: Any) -> Dict[str, Any]:
|
||||
# schema['required'].append(clean_name)
|
||||
except Exception as e:
|
||||
# 如果获取字段类型失败,添加错误信息
|
||||
schema['description'] = f"解析消息字段时出错: {str(e)}"
|
||||
schema["description"] = f"解析消息字段时出错: {str(e)}"
|
||||
logger.error(f"解析 {msg_class.__name__} 消息字段失败: {str(e)}")
|
||||
|
||||
|
||||
return schema
|
||||
|
||||
def ros_action_to_json_schema(action_class: Any) -> Dict[str, Any]:
|
||||
|
||||
def ros_action_to_json_schema(action_class: Any, description="") -> Dict[str, Any]:
|
||||
"""
|
||||
将 ROS Action 类转换为 JSON Schema
|
||||
|
||||
|
||||
Args:
|
||||
action_class: ROS Action 类
|
||||
|
||||
description: 描述
|
||||
|
||||
Returns:
|
||||
完整的 JSON Schema 定义
|
||||
"""
|
||||
if not hasattr(action_class, 'Goal') or not hasattr(action_class, 'Feedback') or not hasattr(action_class, 'Result'):
|
||||
if (
|
||||
not hasattr(action_class, "Goal")
|
||||
or not hasattr(action_class, "Feedback")
|
||||
or not hasattr(action_class, "Result")
|
||||
):
|
||||
raise ValueError(f"{action_class.__name__} 不是有效的 ROS Action 类")
|
||||
|
||||
|
||||
# 创建基础 schema
|
||||
schema = {
|
||||
'title': action_class.__name__,
|
||||
'description': f"ROS Action {action_class.__name__} 的 JSON Schema",
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'goal': {
|
||||
'description': 'Action 目标 - 从客户端发送到服务器',
|
||||
"title": action_class.__name__,
|
||||
"description": description,
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"goal": {
|
||||
# 'description': 'Action 目标 - 从客户端发送到服务器',
|
||||
**ros_message_to_json_schema(action_class.Goal)
|
||||
},
|
||||
'feedback': {
|
||||
'description': 'Action 反馈 - 执行过程中从服务器发送到客户端',
|
||||
"feedback": {
|
||||
# 'description': 'Action 反馈 - 执行过程中从服务器发送到客户端',
|
||||
**ros_message_to_json_schema(action_class.Feedback)
|
||||
},
|
||||
'result': {
|
||||
'description': 'Action 结果 - 完成后从服务器发送到客户端',
|
||||
"result": {
|
||||
# 'description': 'Action 结果 - 完成后从服务器发送到客户端',
|
||||
**ros_message_to_json_schema(action_class.Result)
|
||||
}
|
||||
},
|
||||
},
|
||||
'required': ['goal']
|
||||
"required": ["goal"],
|
||||
}
|
||||
|
||||
|
||||
return schema
|
||||
|
||||
|
||||
def convert_ros_action_to_jsonschema(
|
||||
action_name_or_type: Union[str, Type],
|
||||
output_file: Optional[str] = None,
|
||||
format: str = 'json'
|
||||
action_name_or_type: Union[str, Type], output_file: Optional[str] = None, format: str = "json"
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
将 ROS Action 类型转换为 JSON Schema,并可选地保存到文件
|
||||
|
||||
|
||||
Args:
|
||||
action_name_or_type: ROS Action 类型名称或类
|
||||
output_file: 可选,输出 JSON Schema 的文件路径
|
||||
format: 输出格式,'json' 或 'yaml'
|
||||
|
||||
|
||||
Returns:
|
||||
JSON Schema 定义(字典)
|
||||
"""
|
||||
@@ -771,21 +780,21 @@ def convert_ros_action_to_jsonschema(
|
||||
action_type = get_ros_type_by_msgname(action_name_or_type)
|
||||
else:
|
||||
action_type = action_name_or_type
|
||||
|
||||
|
||||
# 生成 JSON Schema
|
||||
schema = ros_action_to_json_schema(action_type)
|
||||
|
||||
|
||||
# 如果指定了输出文件,将 Schema 保存到文件
|
||||
if output_file:
|
||||
if format.lower() == 'json':
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
if format.lower() == "json":
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
json.dump(schema, f, indent=2, ensure_ascii=False)
|
||||
elif format.lower() == 'yaml':
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
elif format.lower() == "yaml":
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
yaml.safe_dump(schema, f, default_flow_style=False, allow_unicode=True)
|
||||
else:
|
||||
raise ValueError(f"不支持的格式: {format},请使用 'json' 或 'yaml'")
|
||||
|
||||
|
||||
return schema
|
||||
|
||||
|
||||
@@ -794,14 +803,14 @@ if __name__ == "__main__":
|
||||
# 示例:转换 NavigateToPose action
|
||||
try:
|
||||
from nav2_msgs.action import NavigateToPose
|
||||
|
||||
|
||||
# 转换为 JSON Schema 并打印
|
||||
schema = convert_ros_action_to_jsonschema(NavigateToPose)
|
||||
print(json.dumps(schema, indent=2, ensure_ascii=False))
|
||||
|
||||
|
||||
# 保存到文件
|
||||
# convert_ros_action_to_jsonschema(NavigateToPose, "navigate_to_pose_schema.json")
|
||||
|
||||
|
||||
# 或者使用字符串形式的 action 名称
|
||||
# schema = convert_ros_action_to_jsonschema("nav2_msgs/action/NavigateToPose")
|
||||
except ImportError:
|
||||
|
||||
@@ -307,7 +307,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
|
||||
# 创建动作服务
|
||||
if self.create_action_server:
|
||||
for action_name, action_value_mapping in self._action_value_mappings.items():
|
||||
if action_name.startswith("auto-"):
|
||||
if action_name.startswith("auto-") or str(action_value_mapping.get("type", "")).startswith("UniLabJsonCommand"):
|
||||
continue
|
||||
self.create_ros_action_server(action_name, action_value_mapping)
|
||||
|
||||
@@ -923,11 +923,18 @@ class ROS2DeviceNode:
|
||||
driver_class.__module__.startswith("pylabrobot")
|
||||
or driver_class.__name__ == "LiquidHandlerAbstract"
|
||||
or driver_class.__name__ == "LiquidHandlerBiomek"
|
||||
or driver_class.__name__ == "PRCXI9300Handler"
|
||||
)
|
||||
|
||||
# TODO: 要在创建之前预先请求服务器是否有当前id的物料,放到resource_tracker中,让pylabrobot进行创建
|
||||
# 创建设备类实例
|
||||
if use_pylabrobot_creator:
|
||||
# 先对pylabrobot的子资源进行加载,不然subclass无法认出
|
||||
# 在下方对于加载Deck等Resource要手动import
|
||||
# noinspection PyUnresolvedReferences
|
||||
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Deck
|
||||
# noinspection PyUnresolvedReferences
|
||||
from unilabos.devices.liquid_handling.prcxi.prcxi import PRCXI9300Container
|
||||
self._driver_creator = PyLabRobotCreator(
|
||||
driver_class, children=children, resource_tracker=self.resource_tracker
|
||||
)
|
||||
|
||||
@@ -459,7 +459,7 @@ class HostNode(BaseROS2DeviceNode):
|
||||
self.devices_instances[device_id] = d
|
||||
# noinspection PyProtectedMember
|
||||
for action_name, action_value_mapping in d._ros_node._action_value_mappings.items():
|
||||
if action_name.startswith("auto-"):
|
||||
if action_name.startswith("auto-") or str(action_value_mapping.get("type", "")).startswith("UniLabJsonCommand"):
|
||||
continue
|
||||
action_id = f"/devices/{device_id}/{action_name}"
|
||||
if action_id not in self._action_clients:
|
||||
@@ -603,8 +603,7 @@ class HostNode(BaseROS2DeviceNode):
|
||||
if action_name == "test_latency" and server_info is not None:
|
||||
self.server_latest_timestamp = server_info.get("send_timestamp", 0.0)
|
||||
if action_id not in self._action_clients:
|
||||
self.lab_logger().error(f"[Host Node] ActionClient {action_id} not found.")
|
||||
return
|
||||
raise ValueError(f"ActionClient {action_id} not found.")
|
||||
|
||||
action_client: ActionClient = self._action_clients[action_id]
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
|
||||
if d is not None and hasattr(d, "ros_node_instance"):
|
||||
node = d.ros_node_instance
|
||||
for action_name, action_mapping in node._action_value_mappings.items():
|
||||
if action_name.startswith("auto-"):
|
||||
if action_name.startswith("auto-") or str(action_mapping.get("type", "")).startswith("UniLabJsonCommand"):
|
||||
continue
|
||||
action_id = f"/devices/{device_id_abs}/{action_name}"
|
||||
if action_id not in self._action_clients:
|
||||
@@ -211,7 +211,7 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
|
||||
# 逐步执行工作流
|
||||
step_results = []
|
||||
for i, action in enumerate(protocol_steps):
|
||||
self.get_logger().info(f"Running step {i + 1}: {action}")
|
||||
# self.get_logger().info(f"Running step {i + 1}: {action}")
|
||||
if isinstance(action, dict):
|
||||
# 如果是单个动作,直接执行
|
||||
if action["action_name"] == "wait":
|
||||
|
||||
@@ -148,7 +148,7 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
|
||||
contain_model = not issubclass(target_type, Deck)
|
||||
resource, target_type = self._process_resource_mapping(resource, target_type)
|
||||
resource_instance: Resource = resource_ulab_to_plr(resource, contain_model)
|
||||
|
||||
states[prefix_path] = resource_instance.serialize_all_state()
|
||||
# 使用 prefix_path 作为 key 存储资源状态
|
||||
if to_dict:
|
||||
serialized = resource_instance.serialize()
|
||||
@@ -199,7 +199,7 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
|
||||
spect = inspect.signature(deserialize_method)
|
||||
spec_args = spect.parameters
|
||||
for param_name, param_value in data.copy().items():
|
||||
if "_resource_child_name" in param_value and "_resource_type" not in param_value:
|
||||
if isinstance(param_value, dict) and "_resource_child_name" in param_value and "_resource_type" not in param_value:
|
||||
arg_value = spec_args[param_name].annotation
|
||||
data[param_name]["_resource_type"] = self.device_cls.__module__ + ":" + arg_value
|
||||
logger.debug(f"自动补充 _resource_type: {data[param_name]['_resource_type']}")
|
||||
@@ -230,7 +230,7 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
|
||||
spect = inspect.signature(self.device_cls.__init__)
|
||||
spec_args = spect.parameters
|
||||
for param_name, param_value in data.copy().items():
|
||||
if "_resource_child_name" in param_value and "_resource_type" not in param_value:
|
||||
if isinstance(param_value, dict) and "_resource_child_name" in param_value and "_resource_type" not in param_value:
|
||||
arg_value = spec_args[param_name].annotation
|
||||
data[param_name]["_resource_type"] = self.device_cls.__module__ + ":" + arg_value
|
||||
logger.debug(f"自动补充 _resource_type: {data[param_name]['_resource_type']}")
|
||||
|
||||
@@ -148,7 +148,7 @@ def configure_logger():
|
||||
"""配置日志记录器"""
|
||||
# 获取根日志记录器
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.DEBUG) # 修改为DEBUG以显示所有级别
|
||||
root_logger.setLevel(logging.INFO) # 修改为DEBUG以显示所有级别
|
||||
|
||||
# 移除已存在的处理器
|
||||
for handler in root_logger.handlers[:]:
|
||||
@@ -156,7 +156,7 @@ def configure_logger():
|
||||
|
||||
# 创建控制台处理器
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.DEBUG) # 修改为DEBUG以显示所有级别
|
||||
console_handler.setLevel(logging.INFO) # 修改为DEBUG以显示所有级别
|
||||
|
||||
# 使用自定义的颜色格式化器
|
||||
color_formatter = ColoredFormatter()
|
||||
|
||||
Reference in New Issue
Block a user