diff --git a/test/experiments/comprehensive_protocol/checklist.md b/test/experiments/comprehensive_protocol/checklist.md
index 5bb14ae..9645aa4 100644
--- a/test/experiments/comprehensive_protocol/checklist.md
+++ b/test/experiments/comprehensive_protocol/checklist.md
@@ -51,9 +51,9 @@ class PumpTransferProtocol(BaseModel):
time: float = 0
viscous: bool = False
rinsing_solvent: str = "air"
- rinsing_volume: float = 5000
- rinsing_repeats: int = 2
- solid: bool = False
+ rinsing_volume: float = 5000
+ rinsing_repeats: int = 2
+ solid: bool = False 添加了缺失的参数,但是体积为0以及打印日志的问题修不好
flowrate: float = 500
transfer_flowrate: float = 2500
@@ -66,9 +66,9 @@ class SeparateProtocol(BaseModel):
waste_phase_to_vessel: str
solvent: str
solvent_volume: float
- through: str
- repeats: int
- stir_time: float
+ through: str
+ repeats: int
+ stir_time: float
stir_speed: float
settling_time: float
@@ -77,14 +77,14 @@ class EvaporateProtocol(BaseModel):
vessel: str
pressure: float
temp: float
- time: float
+ time: float 加完了
stir_speed: float
class EvacuateAndRefillProtocol(BaseModel):
vessel: str
gas: str
- repeats: int
+ repeats: int 处理完了
class AddProtocol(BaseModel):
vessel: str
@@ -95,21 +95,27 @@ class AddProtocol(BaseModel):
time: float
stir: bool
stir_speed: float
+
+
+
+
+
viscous: bool
purpose: str
class CentrifugeProtocol(BaseModel):
vessel: str
speed: float
- time: float 自创的
+ time: float 没毛病
temp: float
class FilterProtocol(BaseModel):
vessel: str
filtrate_vessel: str
stir: bool
- stir_speed: float
- temp: float
+ stir_speed: float
+ temp: float 处理了
continue_heatchill: bool
volume: float
@@ -118,6 +124,9 @@ class HeatChillProtocol(BaseModel):
temp: float
time: float
+
+
+
stir: bool
stir_speed: float
purpose: str
@@ -133,7 +142,9 @@ class HeatChillStopProtocol(BaseModel):
class StirProtocol(BaseModel):
stir_time: float
stir_speed: float
- settling_time: float
+
+
+ settling_time: float 处理完了
class StartStirProtocol(BaseModel):
vessel: str
@@ -149,11 +160,11 @@ class TransferProtocol(BaseModel):
volume: float
amount: str = ""
time: float = 0
- viscous: bool = False
+ viscous: bool = False
rinsing_solvent: str = ""
rinsing_volume: float = 0.0
rinsing_repeats: int = 0
- solid: bool = False
+ solid: bool = False 这个protocol早该删掉了
class CleanVesselProtocol(BaseModel):
vessel: str
@@ -166,8 +177,8 @@ class DissolveProtocol(BaseModel):
vessel: str
solvent: str
volume: float
- amount: str = ""
- temp: float = 25.0
+ amount: str = ""
+ temp: float = 25.0
time: float = 0.0
stir_speed: float = 0.0
@@ -191,8 +202,8 @@ class WashSolidProtocol(BaseModel):
volume: float
filtrate_vessel: str = ""
temp: float = 25.0
- stir: bool = False
- stir_speed: float = 0.0
+ stir: bool = False
+ stir_speed: float = 0.0 处理完了
time: float = 0.0
repeats: int = 1
diff --git a/unilabos/compile/evacuateandrefill_protocol.py b/unilabos/compile/evacuateandrefill_protocol.py
index aa44df6..e5e8df5 100644
--- a/unilabos/compile/evacuateandrefill_protocol.py
+++ b/unilabos/compile/evacuateandrefill_protocol.py
@@ -197,77 +197,148 @@ def generate_evacuateandrefill_protocol(
G: nx.DiGraph,
vessel: str,
gas: str,
- repeats: int = 1
+ # 🔧 删除 repeats 参数,直接硬编码为 3
+ **kwargs # 🔧 接受额外参数,增强兼容性
) -> List[Dict[str, Any]]:
"""
- 生成抽真空和充气操作的动作序列
+ 生成抽真空和充气操作的动作序列 - 简化版本
- **修复版本**: 正确调用 pump_protocol 并处理异常
+ Args:
+ G: 设备图
+ vessel: 目标容器名称(必需)
+ gas: 气体名称(必需)
+ **kwargs: 其他参数(兼容性)
+
+ Returns:
+ List[Dict[str, Any]]: 动作序列
"""
+
+ # 🔧 硬编码重复次数为 3
+ repeats = 3
+
+ 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("=" * 60)
+
action_sequence = []
- # 参数设置 - 关键修复:减小体积避免超出泵容量
- VACUUM_VOLUME = 20.0 # 减小抽真空体积
- REFILL_VOLUME = 20.0 # 减小充气体积
- PUMP_FLOW_RATE = 2.5 # 降低流速
- STIR_SPEED = 300.0
+ # === 参数验证和修正 ===
+ debug_print("步骤1: 参数验证和修正...")
- print(f"EVACUATE_REFILL: 开始生成协议,目标容器: {vessel}, 气体: {gas}, 重复次数: {repeats}")
+ # 验证必需参数
+ if not vessel:
+ raise ValueError("vessel 参数不能为空")
+
+ if not gas:
+ raise ValueError("gas 参数不能为空")
- # 1. 验证设备存在
if vessel not in G.nodes():
- raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
+ raise ValueError(f"容器 '{vessel}' 不存在于系统中")
+
+ # 标准化气体名称
+ gas_aliases = {
+ 'n2': 'nitrogen',
+ 'ar': 'argon',
+ 'air': 'air',
+ 'o2': 'oxygen',
+ 'co2': 'carbon_dioxide',
+ 'h2': '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"最终参数: vessel={vessel}, gas={gas}, repeats={repeats}")
+
+ # === 查找设备 ===
+ debug_print("步骤2: 查找设备...")
- # 2. 查找设备
try:
vacuum_pump = find_vacuum_pump(G)
- vacuum_solenoid = find_associated_solenoid_valve(G, vacuum_pump)
gas_source = find_gas_source(G, gas)
+ vacuum_solenoid = find_associated_solenoid_valve(G, vacuum_pump)
gas_solenoid = find_associated_solenoid_valve(G, gas_source)
stirrer_id = find_connected_stirrer(G, vessel)
- print(f"EVACUATE_REFILL: 找到设备")
- print(f" - 真空泵: {vacuum_pump}")
- print(f" - 气源: {gas_source}")
- print(f" - 真空电磁阀: {vacuum_solenoid}")
- print(f" - 气源电磁阀: {gas_solenoid}")
- print(f" - 搅拌器: {stirrer_id}")
+ 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 ValueError as e:
+ except Exception as e:
+ debug_print(f"❌ 设备查找失败: {str(e)}")
raise ValueError(f"设备查找失败: {str(e)}")
- # 3. **关键修复**: 验证路径存在性
+ # === 参数设置 ===
+ debug_print("步骤3: 参数设置...")
+
+ # 根据气体类型调整参数
+ if gas.lower() in ['nitrogen', 'argon']:
+ VACUUM_VOLUME = 25.0
+ REFILL_VOLUME = 25.0
+ PUMP_FLOW_RATE = 2.0
+ VACUUM_TIME = 30.0
+ REFILL_TIME = 20.0
+ debug_print("惰性气体:使用标准参数")
+ 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("活性气体:使用保守参数")
+ else:
+ VACUUM_VOLUME = 15.0
+ REFILL_VOLUME = 15.0
+ PUMP_FLOW_RATE = 1.0
+ VACUUM_TIME = 60.0
+ REFILL_TIME = 30.0
+ debug_print("未知气体:使用安全参数")
+
+ 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("步骤4: 路径验证...")
+
try:
# 验证抽真空路径
vacuum_path = nx.shortest_path(G, source=vessel, target=vacuum_pump)
- print(f"EVACUATE_REFILL: 抽真空路径: {' → '.join(vacuum_path)}")
+ debug_print(f"抽真空路径: {' → '.join(vacuum_path)}")
# 验证充气路径
gas_path = nx.shortest_path(G, source=gas_source, target=vessel)
- print(f"EVACUATE_REFILL: 充气路径: {' → '.join(gas_path)}")
+ debug_print(f"充气路径: {' → '.join(gas_path)}")
- # **新增**: 检查路径中的边数据
- for i in range(len(vacuum_path) - 1):
- nodeA, nodeB = vacuum_path[i], vacuum_path[i + 1]
- edge_data = G.get_edge_data(nodeA, nodeB)
- if not edge_data or 'port' not in edge_data:
- raise ValueError(f"路径 {nodeA} → {nodeB} 缺少端口信息")
- print(f" 抽真空路径边 {nodeA} → {nodeB}: {edge_data}")
-
- for i in range(len(gas_path) - 1):
- nodeA, nodeB = gas_path[i], gas_path[i + 1]
- edge_data = G.get_edge_data(nodeA, nodeB)
- if not edge_data or 'port' not in edge_data:
- raise ValueError(f"路径 {nodeA} → {nodeB} 缺少端口信息")
- print(f" 充气路径边 {nodeA} → {nodeB}: {edge_data}")
-
except nx.NetworkXNoPath as e:
+ debug_print(f"❌ 路径不存在: {str(e)}")
raise ValueError(f"路径不存在: {str(e)}")
except Exception as e:
+ debug_print(f"❌ 路径验证失败: {str(e)}")
raise ValueError(f"路径验证失败: {str(e)}")
- # 4. 启动搅拌器
+ # === 启动搅拌器 ===
+ debug_print("步骤5: 启动搅拌器...")
+
if stirrer_id:
+ debug_print(f"启动搅拌器: {stirrer_id}")
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
@@ -277,15 +348,26 @@ def generate_evacuateandrefill_protocol(
"purpose": "抽真空充气操作前启动搅拌"
}
})
+
+ # 等待搅拌稳定
+ action_sequence.append({
+ "action_name": "wait",
+ "action_kwargs": {"time": 5.0}
+ })
+ else:
+ debug_print("未找到搅拌器,跳过搅拌启动")
- # 5. 执行多次抽真空-充气循环
- for cycle in range(repeats):
- print(f"EVACUATE_REFILL: === 第 {cycle+1}/{repeats} 次循环 ===")
+ # === 执行 3 次抽真空-充气循环 ===
+ debug_print("步骤6: 执行抽真空-充气循环...")
+
+ for cycle in range(repeats): # 这里 repeats = 3
+ debug_print(f"=== 第 {cycle+1}/{repeats} 次循环 ===")
# ============ 抽真空阶段 ============
- print(f"EVACUATE_REFILL: 抽真空阶段开始")
+ debug_print(f"抽真空阶段开始")
# 启动真空泵
+ debug_print(f"启动真空泵: {vacuum_pump}")
action_sequence.append({
"device_id": vacuum_pump,
"action_name": "set_status",
@@ -294,17 +376,15 @@ def generate_evacuateandrefill_protocol(
# 开启真空电磁阀
if vacuum_solenoid:
+ debug_print(f"开启真空电磁阀: {vacuum_solenoid}")
action_sequence.append({
"device_id": vacuum_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {"command": "OPEN"}
})
- # **关键修复**: 改进 pump_protocol 调用和错误处理
- print(f"EVACUATE_REFILL: 调用抽真空 pump_protocol: {vessel} → {vacuum_pump}")
- print(f" - 体积: {VACUUM_VOLUME} mL")
- print(f" - 流速: {PUMP_FLOW_RATE} mL/s")
-
+ # 抽真空操作
+ debug_print(f"抽真空操作: {vessel} → {vacuum_pump}")
try:
vacuum_transfer_actions = generate_pump_protocol_with_rinsing(
G=G,
@@ -312,9 +392,9 @@ def generate_evacuateandrefill_protocol(
to_vessel=vacuum_pump,
volume=VACUUM_VOLUME,
amount="",
- time=0.0,
+ duration=0.0, # 🔧 修复time参数名冲突
viscous=False,
- rinsing_solvent="", # **修复**: 明确不使用清洗
+ rinsing_solvent="",
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
@@ -324,52 +404,31 @@ def generate_evacuateandrefill_protocol(
if vacuum_transfer_actions:
action_sequence.extend(vacuum_transfer_actions)
- print(f"EVACUATE_REFILL: ✅ 成功添加 {len(vacuum_transfer_actions)} 个抽真空动作")
+ debug_print(f"✅ 添加了 {len(vacuum_transfer_actions)} 个抽真空动作")
else:
- print(f"EVACUATE_REFILL: ⚠️ 抽真空 pump_protocol 返回空序列")
- # **修复**: 添加手动泵动作作为备选
- action_sequence.extend([
- {
- "device_id": "multiway_valve_1",
- "action_name": "set_valve_position",
- "action_kwargs": {"command": "5"} # 连接到反应器
- },
- {
- "device_id": "transfer_pump_1",
- "action_name": "set_position",
- "action_kwargs": {
- "position": VACUUM_VOLUME,
- "max_velocity": PUMP_FLOW_RATE
- }
- }
- ])
- print(f"EVACUATE_REFILL: 使用备选手动泵动作")
+ debug_print("⚠️ 抽真空协议返回空序列,添加手动动作")
+ action_sequence.append({
+ "action_name": "wait",
+ "action_kwargs": {"time": VACUUM_TIME}
+ })
except Exception as e:
- print(f"EVACUATE_REFILL: ❌ 抽真空 pump_protocol 失败: {str(e)}")
- import traceback
- print(f"EVACUATE_REFILL: 详细错误:\n{traceback.format_exc()}")
-
- # **修复**: 添加手动动作而不是忽略错误
- print(f"EVACUATE_REFILL: 使用手动备选方案")
- action_sequence.extend([
- {
- "device_id": "multiway_valve_1",
- "action_name": "set_valve_position",
- "action_kwargs": {"command": "5"} # 反应器端口
- },
- {
- "device_id": "transfer_pump_1",
- "action_name": "set_position",
- "action_kwargs": {
- "position": VACUUM_VOLUME,
- "max_velocity": PUMP_FLOW_RATE
- }
- }
- ])
+ debug_print(f"❌ 抽真空失败: {str(e)}")
+ # 添加等待时间作为备选
+ action_sequence.append({
+ "action_name": "wait",
+ "action_kwargs": {"time": VACUUM_TIME}
+ })
+
+ # 抽真空后等待
+ action_sequence.append({
+ "action_name": "wait",
+ "action_kwargs": {"time": 5.0}
+ })
# 关闭真空电磁阀
if vacuum_solenoid:
+ debug_print(f"关闭真空电磁阀: {vacuum_solenoid}")
action_sequence.append({
"device_id": vacuum_solenoid,
"action_name": "set_valve_position",
@@ -377,6 +436,7 @@ def generate_evacuateandrefill_protocol(
})
# 关闭真空泵
+ debug_print(f"关闭真空泵: {vacuum_pump}")
action_sequence.append({
"device_id": vacuum_pump,
"action_name": "set_status",
@@ -384,26 +444,27 @@ def generate_evacuateandrefill_protocol(
})
# ============ 充气阶段 ============
- print(f"EVACUATE_REFILL: 充气阶段开始")
+ debug_print(f"充气阶段开始")
# 启动气源
+ debug_print(f"启动气源: {gas_source}")
action_sequence.append({
"device_id": gas_source,
- "action_name": "set_status",
+ "action_name": "set_status",
"action_kwargs": {"string": "ON"}
})
# 开启气源电磁阀
if gas_solenoid:
+ debug_print(f"开启气源电磁阀: {gas_solenoid}")
action_sequence.append({
"device_id": gas_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {"command": "OPEN"}
})
- # **关键修复**: 改进充气 pump_protocol 调用
- print(f"EVACUATE_REFILL: 调用充气 pump_protocol: {gas_source} → {vessel}")
-
+ # 充气操作
+ debug_print(f"充气操作: {gas_source} → {vessel}")
try:
gas_transfer_actions = generate_pump_protocol_with_rinsing(
G=G,
@@ -411,9 +472,9 @@ def generate_evacuateandrefill_protocol(
to_vessel=vessel,
volume=REFILL_VOLUME,
amount="",
- time=0.0,
+ duration=0.0, # 🔧 修复time参数名冲突
viscous=False,
- rinsing_solvent="", # **修复**: 明确不使用清洗
+ rinsing_solvent="",
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
@@ -423,77 +484,31 @@ def generate_evacuateandrefill_protocol(
if gas_transfer_actions:
action_sequence.extend(gas_transfer_actions)
- print(f"EVACUATE_REFILL: ✅ 成功添加 {len(gas_transfer_actions)} 个充气动作")
+ debug_print(f"✅ 添加了 {len(gas_transfer_actions)} 个充气动作")
else:
- print(f"EVACUATE_REFILL: ⚠️ 充气 pump_protocol 返回空序列")
- # **修复**: 添加手动充气动作
- action_sequence.extend([
- {
- "device_id": "multiway_valve_2",
- "action_name": "set_valve_position",
- "action_kwargs": {"command": "8"} # 氮气端口
- },
- {
- "device_id": "transfer_pump_2",
- "action_name": "set_position",
- "action_kwargs": {
- "position": REFILL_VOLUME,
- "max_velocity": PUMP_FLOW_RATE
- }
- },
- {
- "device_id": "multiway_valve_2",
- "action_name": "set_valve_position",
- "action_kwargs": {"command": "5"} # 反应器端口
- },
- {
- "device_id": "transfer_pump_2",
- "action_name": "set_position",
- "action_kwargs": {
- "position": 0.0,
- "max_velocity": PUMP_FLOW_RATE
- }
- }
- ])
+ debug_print("⚠️ 充气协议返回空序列,添加手动动作")
+ action_sequence.append({
+ "action_name": "wait",
+ "action_kwargs": {"time": REFILL_TIME}
+ })
except Exception as e:
- print(f"EVACUATE_REFILL: ❌ 充气 pump_protocol 失败: {str(e)}")
- import traceback
- print(f"EVACUATE_REFILL: 详细错误:\n{traceback.format_exc()}")
-
- # **修复**: 使用手动充气动作
- print(f"EVACUATE_REFILL: 使用手动充气方案")
- action_sequence.extend([
- {
- "device_id": "multiway_valve_2",
- "action_name": "set_valve_position",
- "action_kwargs": {"command": "8"} # 连接气源
- },
- {
- "device_id": "transfer_pump_2",
- "action_name": "set_position",
- "action_kwargs": {
- "position": REFILL_VOLUME,
- "max_velocity": PUMP_FLOW_RATE
- }
- },
- {
- "device_id": "multiway_valve_2",
- "action_name": "set_valve_position",
- "action_kwargs": {"command": "5"} # 连接反应器
- },
- {
- "device_id": "transfer_pump_2",
- "action_name": "set_position",
- "action_kwargs": {
- "position": 0.0,
- "max_velocity": PUMP_FLOW_RATE
- }
- }
- ])
+ debug_print(f"❌ 充气失败: {str(e)}")
+ # 添加等待时间作为备选
+ action_sequence.append({
+ "action_name": "wait",
+ "action_kwargs": {"time": REFILL_TIME}
+ })
+
+ # 充气后等待
+ action_sequence.append({
+ "action_name": "wait",
+ "action_kwargs": {"time": 5.0}
+ })
# 关闭气源电磁阀
if gas_solenoid:
+ debug_print(f"关闭气源电磁阀: {gas_solenoid}")
action_sequence.append({
"device_id": gas_solenoid,
"action_name": "set_valve_position",
@@ -501,6 +516,7 @@ def generate_evacuateandrefill_protocol(
})
# 关闭气源
+ debug_print(f"关闭气源: {gas_source}")
action_sequence.append({
"device_id": gas_source,
"action_name": "set_status",
@@ -509,23 +525,40 @@ def generate_evacuateandrefill_protocol(
# 等待下一次循环
if cycle < repeats - 1:
+ debug_print(f"等待下一次循环...")
action_sequence.append({
"action_name": "wait",
- "action_kwargs": {"time": 2.0}
+ "action_kwargs": {"time": 10.0}
})
- # 停止搅拌器
+ # === 停止搅拌器 ===
+ debug_print("步骤7: 停止搅拌器...")
+
if stirrer_id:
+ debug_print(f"停止搅拌器: {stirrer_id}")
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stop_stir",
"action_kwargs": {"vessel": vessel}
})
- print(f"EVACUATE_REFILL: 协议生成完成,共 {len(action_sequence)} 个动作")
+ # === 最终等待 ===
+ action_sequence.append({
+ "action_name": "wait",
+ "action_kwargs": {"time": 10.0}
+ })
+
+ # === 总结 ===
+ 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("=" * 60)
+
return action_sequence
-
# 测试函数
def test_evacuateandrefill_protocol():
"""测试抽真空充气协议"""
diff --git a/unilabos/compile/evaporate_protocol.py b/unilabos/compile/evaporate_protocol.py
index 4cee78d..b5170b5 100644
--- a/unilabos/compile/evaporate_protocol.py
+++ b/unilabos/compile/evaporate_protocol.py
@@ -1,61 +1,110 @@
-from typing import List, Dict, Any
+from typing import List, Dict, Any, Optional
import networkx as nx
+import logging
from .pump_protocol import generate_pump_protocol
+logger = logging.getLogger(__name__)
+
+def debug_print(message):
+ """调试输出"""
+ print(f"[EVAPORATE] {message}", flush=True)
+ logger.info(f"[EVAPORATE] {message}")
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
- """
- 获取容器中的液体体积
- """
+ """获取容器中的液体体积"""
+ debug_print(f"检查容器 '{vessel}' 的液体体积...")
+
if vessel not in G.nodes():
+ debug_print(f"容器 '{vessel}' 不存在")
return 0.0
vessel_data = G.nodes[vessel].get('data', {})
+ debug_print(f"容器数据: {vessel_data}")
+
+ # 检查多种体积字段
+ volume_keys = ['total_volume', 'volume', 'liquid_volume', 'current_volume']
+ for key in volume_keys:
+ if key in vessel_data:
+ try:
+ volume = float(vessel_data[key])
+ debug_print(f"从 '{key}' 读取到体积: {volume}mL")
+ return volume
+ except (ValueError, TypeError):
+ continue
+
+ # 检查liquid数组
liquids = vessel_data.get('liquid', [])
+ if isinstance(liquids, list):
+ total_volume = 0.0
+ for liquid in liquids:
+ if isinstance(liquid, dict):
+ for vol_key in ['liquid_volume', 'volume', 'amount']:
+ if vol_key in liquid:
+ try:
+ vol = float(liquid[vol_key])
+ total_volume += vol
+ debug_print(f"从液体数据 '{vol_key}' 读取: {vol}mL")
+ except (ValueError, TypeError):
+ continue
+ if total_volume > 0:
+ return total_volume
- total_volume = 0.0
- for liquid in liquids:
- if isinstance(liquid, dict) and 'liquid_volume' in liquid:
- total_volume += liquid['liquid_volume']
-
- return total_volume
+ debug_print(f"未检测到液体体积,返回 0.0")
+ return 0.0
-
-def find_rotavap_device(G: nx.DiGraph) -> str:
+def find_rotavap_device(G: nx.DiGraph) -> Optional[str]:
"""查找旋转蒸发仪设备"""
- rotavap_nodes = [node for node in G.nodes()
- if (G.nodes[node].get('class') or '') == 'virtual_rotavap']
+ debug_print("查找旋转蒸发仪设备...")
- if rotavap_nodes:
- return rotavap_nodes[0]
+ # 查找各种可能的旋转蒸发仪设备
+ possible_devices = []
+ for node in G.nodes():
+ node_data = G.nodes[node]
+ node_class = node_data.get('class', '')
+
+ if any(keyword in node_class.lower() for keyword in ['rotavap', 'evaporator']):
+ possible_devices.append(node)
+ debug_print(f"找到旋转蒸发仪设备: {node}")
- raise ValueError("系统中未找到旋转蒸发仪设备")
+ if possible_devices:
+ return possible_devices[0]
+
+ debug_print("未找到旋转蒸发仪设备")
+ return None
-
-def find_solvent_recovery_vessel(G: nx.DiGraph) -> str:
- """查找溶剂回收容器"""
- possible_names = [
- "flask_distillate",
- "bottle_distillate",
- "vessel_distillate",
- "distillate",
- "solvent_recovery",
- "flask_solvent_recovery",
- "collection_flask"
+def find_rotavap_vessel(G: nx.DiGraph) -> Optional[str]:
+ """查找旋转蒸发仪样品容器"""
+ debug_print("查找旋转蒸发仪样品容器...")
+
+ possible_vessels = [
+ "rotavap", "rotavap_flask", "flask_rotavap",
+ "evaporation_flask", "evaporator", "rotary_evaporator"
]
- for vessel_name in possible_names:
- if vessel_name in G.nodes():
- return vessel_name
+ for vessel in possible_vessels:
+ if vessel in G.nodes():
+ debug_print(f"找到旋转蒸发仪样品容器: {vessel}")
+ return vessel
- # 如果找不到专门的回收容器,使用废液容器
- waste_names = ["waste_workup", "flask_waste", "bottle_waste", "waste"]
- for vessel_name in waste_names:
- if vessel_name in G.nodes():
- return vessel_name
-
- raise ValueError(f"未找到溶剂回收容器。尝试了以下名称: {possible_names + waste_names}")
+ debug_print("未找到旋转蒸发仪样品容器")
+ return None
+def find_recovery_vessel(G: nx.DiGraph) -> Optional[str]:
+ """查找溶剂回收容器"""
+ debug_print("查找溶剂回收容器...")
+
+ possible_vessels = [
+ "flask_distillate", "distillate", "solvent_recovery",
+ "rotavap_condenser", "condenser", "waste_workup", "waste"
+ ]
+
+ for vessel in possible_vessels:
+ if vessel in G.nodes():
+ debug_print(f"找到回收容器: {vessel}")
+ return vessel
+
+ debug_print("未找到回收容器")
+ return None
def generate_evaporate_protocol(
G: nx.DiGraph,
@@ -63,264 +112,276 @@ def generate_evaporate_protocol(
pressure: float = 0.1,
temp: float = 60.0,
time: float = 1800.0,
- stir_speed: float = 100.0
+ stir_speed: float = 100.0,
+ solvent: str = "",
+ **kwargs # 接受任意额外参数,增强兼容性
) -> List[Dict[str, Any]]:
"""
- 生成蒸发操作的协议序列
-
- 蒸发流程:
- 1. 液体转移:将待蒸发溶液从源容器转移到旋转蒸发仪
- 2. 蒸发操作:执行旋转蒸发
- 3. (可选) 溶剂回收:将冷凝的溶剂转移到回收容器
- 4. 残留物转移:将浓缩物从旋转蒸发仪转移回原容器或新容器
+ 生成蒸发操作的协议序列 - 增强兼容性版本
Args:
- G: 有向图,节点为设备和容器,边为流体管道
- vessel: 包含待蒸发溶液的容器名称
- pressure: 蒸发时的真空度 (bar),默认0.1 bar
- temp: 蒸发时的加热温度 (°C),默认60°C
- time: 蒸发时间 (秒),默认1800秒(30分钟)
- stir_speed: 旋转速度 (RPM),默认100 RPM
+ G: 设备图
+ vessel: 蒸发容器名称(必需)
+ pressure: 真空度 (bar),默认0.1
+ temp: 加热温度 (°C),默认60
+ time: 蒸发时间 (秒),默认1800
+ stir_speed: 旋转速度 (RPM),默认100
+ solvent: 溶剂名称(可选,用于参数优化)
+ **kwargs: 其他参数(兼容性)
Returns:
- List[Dict[str, Any]]: 蒸发操作的动作序列
-
- Raises:
- ValueError: 当找不到必要的设备时抛出异常
-
- Examples:
- evaporate_actions = generate_evaporate_protocol(G, "reaction_mixture", 0.05, 80.0, 3600.0)
+ List[Dict[str, Any]]: 动作序列
"""
+
+ debug_print("=" * 50)
+ debug_print("开始生成蒸发协议")
+ debug_print(f"输入参数:")
+ debug_print(f" - vessel: {vessel}")
+ debug_print(f" - pressure: {pressure} bar")
+ debug_print(f" - temp: {temp}°C")
+ debug_print(f" - time: {time}s ({time/60:.1f}分钟)")
+ debug_print(f" - stir_speed: {stir_speed} RPM")
+ debug_print(f" - solvent: '{solvent}'")
+ debug_print(f" - 其他参数: {kwargs}")
+ debug_print("=" * 50)
+
action_sequence = []
- print(f"EVAPORATE: 开始生成蒸发协议")
- print(f" - 源容器: {vessel}")
- print(f" - 真空度: {pressure} bar")
- print(f" - 温度: {temp}°C")
- print(f" - 时间: {time}s ({time/60:.1f}分钟)")
- print(f" - 旋转速度: {stir_speed} RPM")
+ # === 参数验证和修正 ===
+ debug_print("步骤1: 参数验证和修正...")
+
+ # 验证必需参数
+ if not vessel:
+ raise ValueError("vessel 参数不能为空")
- # 验证源容器存在
if vessel not in G.nodes():
- raise ValueError(f"源容器 '{vessel}' 不存在于系统中")
+ raise ValueError(f"容器 '{vessel}' 不存在于系统中")
- # 获取源容器中的液体体积
- source_volume = get_vessel_liquid_volume(G, vessel)
- print(f"EVAPORATE: 源容器 {vessel} 中有 {source_volume} mL 液体")
+ # 修正参数范围
+ if pressure <= 0 or pressure > 1.0:
+ debug_print(f"真空度 {pressure} bar 超出范围,修正为 0.1 bar")
+ pressure = 0.1
- # 查找旋转蒸发仪
- try:
- rotavap_id = find_rotavap_device(G)
- print(f"EVAPORATE: 找到旋转蒸发仪: {rotavap_id}")
- except ValueError as e:
- raise ValueError(f"无法找到旋转蒸发仪: {str(e)}")
+ if temp < 10.0 or temp > 200.0:
+ debug_print(f"温度 {temp}°C 超出范围,修正为 60°C")
+ temp = 60.0
+
+ if time <= 0:
+ debug_print(f"时间 {time}s 无效,修正为 1800s")
+ time = 1800.0
+
+ if stir_speed < 10.0 or stir_speed > 300.0:
+ debug_print(f"旋转速度 {stir_speed} RPM 超出范围,修正为 100 RPM")
+ stir_speed = 100.0
+
+ # 根据溶剂优化参数
+ if solvent:
+ debug_print(f"根据溶剂 '{solvent}' 优化参数...")
+ solvent_lower = solvent.lower()
+
+ if any(s in solvent_lower for s in ['water', 'aqueous', 'h2o']):
+ temp = max(temp, 80.0)
+ pressure = max(pressure, 0.2)
+ debug_print("水系溶剂:提高温度和真空度")
+ elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']):
+ temp = min(temp, 50.0)
+ pressure = min(pressure, 0.05)
+ debug_print("易挥发溶剂:降低温度和真空度")
+ elif any(s in solvent_lower for s in ['dmso', 'dmi', 'toluene']):
+ temp = max(temp, 100.0)
+ pressure = min(pressure, 0.01)
+ debug_print("高沸点溶剂:提高温度,降低真空度")
+
+ debug_print(f"最终参数: pressure={pressure}, temp={temp}, time={time}, stir_speed={stir_speed}")
+
+ # === 查找设备 ===
+ debug_print("步骤2: 查找设备...")
+
+ # 查找旋转蒸发仪设备
+ rotavap_device = find_rotavap_device(G)
+ if not rotavap_device:
+ debug_print("未找到旋转蒸发仪设备,使用通用设备")
+ rotavap_device = "rotavap_1" # 默认设备ID
# 查找旋转蒸发仪样品容器
- rotavap_vessel = None
- possible_rotavap_vessels = ["rotavap_flask", "rotavap", "flask_rotavap", "evaporation_flask"]
- for rv in possible_rotavap_vessels:
- if rv in G.nodes():
- rotavap_vessel = rv
- break
-
+ rotavap_vessel = find_rotavap_vessel(G)
if not rotavap_vessel:
- raise ValueError(f"未找到旋转蒸发仪样品容器。尝试了: {possible_rotavap_vessels}")
+ debug_print("未找到旋转蒸发仪样品容器,使用默认容器")
+ rotavap_vessel = "rotavap" # 默认容器
- print(f"EVAPORATE: 找到旋转蒸发仪样品容器: {rotavap_vessel}")
+ # 查找回收容器
+ recovery_vessel = find_recovery_vessel(G)
- # 查找溶剂回收容器
- try:
- distillate_vessel = find_solvent_recovery_vessel(G)
- print(f"EVAPORATE: 找到溶剂回收容器: {distillate_vessel}")
- except ValueError as e:
- print(f"EVAPORATE: 警告 - {str(e)}")
- distillate_vessel = None
+ debug_print(f"设备配置:")
+ debug_print(f" - 旋转蒸发仪设备: {rotavap_device}")
+ debug_print(f" - 样品容器: {rotavap_vessel}")
+ debug_print(f" - 回收容器: {recovery_vessel}")
+
+ # === 体积计算 ===
+ debug_print("步骤3: 体积计算...")
+
+ source_volume = get_vessel_liquid_volume(G, vessel)
- # === 简化的体积计算策略 ===
if source_volume > 0:
- # 如果能检测到液体体积,使用实际体积的大部分
transfer_volume = min(source_volume * 0.9, 250.0) # 90%或最多250mL
- print(f"EVAPORATE: 检测到液体体积,将转移 {transfer_volume} mL")
+ debug_print(f"检测到液体体积 {source_volume}mL,转移 {transfer_volume}mL")
else:
- # 如果检测不到液体体积,默认转移一整瓶 250mL
- transfer_volume = 250.0
- print(f"EVAPORATE: 未检测到液体体积,默认转移整瓶 {transfer_volume} mL")
+ transfer_volume = 50.0 # 默认小体积,更安全
+ debug_print(f"未检测到液体体积,使用默认转移体积 {transfer_volume}mL")
- # === 第一步:将待蒸发溶液转移到旋转蒸发仪 ===
- print(f"EVAPORATE: 将 {transfer_volume} mL 溶液从 {vessel} 转移到 {rotavap_vessel}")
- try:
- transfer_to_rotavap_actions = generate_pump_protocol(
- G=G,
- from_vessel=vessel,
- to_vessel=rotavap_vessel,
- volume=transfer_volume,
- flowrate=2.0,
- transfer_flowrate=2.0
- )
- action_sequence.extend(transfer_to_rotavap_actions)
- except Exception as e:
- raise ValueError(f"无法将溶液转移到旋转蒸发仪: {str(e)}")
+ # === 生成动作序列 ===
+ debug_print("步骤4: 生成动作序列...")
- # 转移后等待
- wait_action = {
+ # 动作1: 转移溶液到旋转蒸发仪
+ if vessel != rotavap_vessel:
+ debug_print(f"转移 {transfer_volume}mL 从 {vessel} 到 {rotavap_vessel}")
+ try:
+ transfer_actions = generate_pump_protocol(
+ G=G,
+ from_vessel=vessel,
+ to_vessel=rotavap_vessel,
+ volume=transfer_volume,
+ flowrate=2.0,
+ transfer_flowrate=2.0
+ )
+ action_sequence.extend(transfer_actions)
+ debug_print(f"添加了 {len(transfer_actions)} 个转移动作")
+ except Exception as e:
+ debug_print(f"转移失败: {str(e)}")
+ # 继续执行,不中断整个流程
+
+ # 等待稳定
+ action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 10}
- }
- action_sequence.append(wait_action)
+ })
- # === 第二步:执行旋转蒸发 ===
- print(f"EVAPORATE: 执行旋转蒸发操作")
+ # 动作2: 执行蒸发
+ debug_print(f"执行蒸发: {rotavap_device}")
evaporate_action = {
- "device_id": rotavap_id,
+ "device_id": rotavap_device,
"action_name": "evaporate",
"action_kwargs": {
"vessel": rotavap_vessel,
"pressure": pressure,
"temp": temp,
"time": time,
- "stir_speed": stir_speed
+ "stir_speed": stir_speed,
+ "solvent": solvent
}
}
action_sequence.append(evaporate_action)
- # 蒸发后等待系统稳定
- wait_action = {
+ # 蒸发后等待
+ action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 30}
- }
- action_sequence.append(wait_action)
+ })
- # === 第三步:溶剂回收(如果有回收容器)===
- if distillate_vessel:
- print(f"EVAPORATE: 回收冷凝溶剂到 {distillate_vessel}")
+ # 动作3: 回收溶剂(如果有回收容器)
+ if recovery_vessel:
+ debug_print(f"回收溶剂到 {recovery_vessel}")
try:
- condenser_vessel = "rotavap_condenser"
- if condenser_vessel in G.nodes():
- # 估算回收体积(约为转移体积的70% - 大部分溶剂被蒸发回收)
- recovery_volume = transfer_volume * 0.7
- print(f"EVAPORATE: 预计回收 {recovery_volume} mL 溶剂")
-
- recovery_actions = generate_pump_protocol(
- G=G,
- from_vessel=condenser_vessel,
- to_vessel=distillate_vessel,
- volume=recovery_volume,
- flowrate=3.0,
- transfer_flowrate=3.0
- )
- action_sequence.extend(recovery_actions)
- else:
- print("EVAPORATE: 未找到冷凝器容器,跳过溶剂回收")
- except Exception as e:
- print(f"EVAPORATE: 溶剂回收失败: {str(e)}")
-
- # === 第四步:将浓缩物转移回原容器 ===
- print(f"EVAPORATE: 将浓缩物从旋转蒸发仪转移回 {vessel}")
- try:
- # 估算浓缩物体积(约为转移体积的20% - 大部分溶剂已蒸发)
- concentrate_volume = transfer_volume * 0.2
- print(f"EVAPORATE: 预计浓缩物体积 {concentrate_volume} mL")
-
- transfer_back_actions = generate_pump_protocol(
- G=G,
- from_vessel=rotavap_vessel,
- to_vessel=vessel,
- volume=concentrate_volume,
- flowrate=1.0, # 浓缩物可能粘稠,用较慢流速
- transfer_flowrate=1.0
- )
- action_sequence.extend(transfer_back_actions)
- except Exception as e:
- print(f"EVAPORATE: 将浓缩物转移回容器失败: {str(e)}")
-
- # === 第五步:清洗旋转蒸发仪 ===
- print(f"EVAPORATE: 清洗旋转蒸发仪")
- try:
- # 查找清洗溶剂
- cleaning_solvent = None
- for solvent in ["flask_ethanol", "flask_acetone", "flask_water"]:
- if solvent in G.nodes():
- cleaning_solvent = solvent
- break
-
- if cleaning_solvent and distillate_vessel:
- # 用固定量溶剂清洗(不依赖检测体积)
- cleaning_volume = 50.0 # 固定50mL清洗
- print(f"EVAPORATE: 用 {cleaning_volume} mL {cleaning_solvent} 清洗")
-
- # 清洗溶剂加入
- cleaning_actions = generate_pump_protocol(
+ recovery_volume = transfer_volume * 0.7 # 估算回收70%
+ recovery_actions = generate_pump_protocol(
G=G,
- from_vessel=cleaning_solvent,
- to_vessel=rotavap_vessel,
- volume=cleaning_volume,
- flowrate=2.0,
- transfer_flowrate=2.0
+ from_vessel="rotavap_condenser", # 假设的冷凝器
+ to_vessel=recovery_vessel,
+ volume=recovery_volume,
+ flowrate=3.0,
+ transfer_flowrate=3.0
)
- action_sequence.extend(cleaning_actions)
-
- # 将清洗液转移到废液/回收容器
- waste_actions = generate_pump_protocol(
+ action_sequence.extend(recovery_actions)
+ debug_print(f"添加了 {len(recovery_actions)} 个回收动作")
+ except Exception as e:
+ debug_print(f"溶剂回收失败: {str(e)}")
+
+ # 动作4: 转移浓缩物回原容器
+ if vessel != rotavap_vessel:
+ debug_print(f"转移浓缩物从 {rotavap_vessel} 到 {vessel}")
+ try:
+ concentrate_volume = transfer_volume * 0.2 # 估算浓缩物20%
+ transfer_back_actions = generate_pump_protocol(
G=G,
from_vessel=rotavap_vessel,
- to_vessel=distillate_vessel, # 使用回收容器作为废液
- volume=cleaning_volume,
- flowrate=2.0,
- transfer_flowrate=2.0
+ to_vessel=vessel,
+ volume=concentrate_volume,
+ flowrate=1.0, # 浓缩物可能粘稠
+ transfer_flowrate=1.0
)
- action_sequence.extend(waste_actions)
-
- except Exception as e:
- print(f"EVAPORATE: 清洗步骤失败: {str(e)}")
+ action_sequence.extend(transfer_back_actions)
+ debug_print(f"添加了 {len(transfer_back_actions)} 个转移回收动作")
+ except Exception as e:
+ debug_print(f"浓缩物转移失败: {str(e)}")
- print(f"EVAPORATE: 生成了 {len(action_sequence)} 个动作")
- print(f"EVAPORATE: 蒸发协议生成完成")
- print(f"EVAPORATE: 总处理体积: {transfer_volume} mL")
+ # === 总结 ===
+ debug_print("=" * 50)
+ debug_print(f"蒸发协议生成完成")
+ debug_print(f"总动作数: {len(action_sequence)}")
+ debug_print(f"处理体积: {transfer_volume}mL")
+ debug_print(f"蒸发参数: {pressure} bar, {temp}°C, {time}s, {stir_speed} RPM")
+ debug_print("=" * 50)
return action_sequence
+# === 便捷函数 ===
-# 便捷函数:常用蒸发方案 - 都使用250mL标准瓶体积
def generate_quick_evaporate_protocol(
G: nx.DiGraph,
vessel: str,
- temp: float = 40.0,
- time: float = 900.0 # 15分钟
+ **kwargs
) -> List[Dict[str, Any]]:
- """快速蒸发:低温、短时间、整瓶处理"""
- return generate_evaporate_protocol(G, vessel, 0.2, temp, time, 80.0)
-
+ """快速蒸发:低温短时间"""
+ return generate_evaporate_protocol(
+ G, vessel,
+ pressure=0.2,
+ temp=40.0,
+ time=900.0,
+ stir_speed=80.0,
+ **kwargs
+ )
def generate_gentle_evaporate_protocol(
G: nx.DiGraph,
vessel: str,
- temp: float = 50.0,
- time: float = 2700.0 # 45分钟
+ **kwargs
) -> List[Dict[str, Any]]:
- """温和蒸发:中等条件、较长时间、整瓶处理"""
- return generate_evaporate_protocol(G, vessel, 0.1, temp, time, 60.0)
-
+ """温和蒸发:中等条件"""
+ return generate_evaporate_protocol(
+ G, vessel,
+ pressure=0.1,
+ temp=50.0,
+ time=2700.0,
+ stir_speed=60.0,
+ **kwargs
+ )
def generate_high_vacuum_evaporate_protocol(
G: nx.DiGraph,
vessel: str,
- temp: float = 35.0,
- time: float = 3600.0 # 1小时
+ **kwargs
) -> List[Dict[str, Any]]:
- """高真空蒸发:低温、高真空、长时间、整瓶处理"""
- return generate_evaporate_protocol(G, vessel, 0.01, temp, time, 120.0)
-
+ """高真空蒸发:低温高真空"""
+ return generate_evaporate_protocol(
+ G, vessel,
+ pressure=0.01,
+ temp=35.0,
+ time=3600.0,
+ stir_speed=120.0,
+ **kwargs
+ )
def generate_standard_evaporate_protocol(
G: nx.DiGraph,
- vessel: str
+ vessel: str,
+ **kwargs
) -> List[Dict[str, Any]]:
- """标准蒸发:常用参数、整瓶250mL处理"""
+ """标准蒸发:常用参数"""
return generate_evaporate_protocol(
- G=G,
- vessel=vessel,
- pressure=0.1, # 标准真空度
- temp=60.0, # 适中温度
- time=1800.0, # 30分钟
- stir_speed=100.0 # 适中旋转速度
+ G, vessel,
+ pressure=0.1,
+ temp=60.0,
+ time=1800.0,
+ stir_speed=100.0,
+ **kwargs
)
diff --git a/unilabos/compile/filter_protocol.py b/unilabos/compile/filter_protocol.py
index 7e3ca6b..e673f22 100644
--- a/unilabos/compile/filter_protocol.py
+++ b/unilabos/compile/filter_protocol.py
@@ -1,239 +1,193 @@
from typing import List, Dict, Any
import networkx as nx
from .pump_protocol import generate_pump_protocol
+import logging
+import sys
+logger = logging.getLogger(__name__)
+
+def debug_print(message):
+ """调试输出"""
+ print(f"[FILTER] {message}", flush=True)
+ logger.info(f"[FILTER] {message}")
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
"""获取容器中的液体体积"""
+ debug_print(f"检查容器 '{vessel}' 的液体体积...")
+
if vessel not in G.nodes():
+ debug_print(f"容器 '{vessel}' 不存在")
return 0.0
vessel_data = G.nodes[vessel].get('data', {})
+
+ # 检查多种体积字段
+ volume_keys = ['total_volume', 'volume', 'liquid_volume', 'current_volume']
+ for key in volume_keys:
+ if key in vessel_data:
+ try:
+ volume = float(vessel_data[key])
+ debug_print(f"从 '{key}' 读取到体积: {volume}mL")
+ return volume
+ except (ValueError, TypeError):
+ continue
+
+ # 检查liquid数组
liquids = vessel_data.get('liquid', [])
+ if isinstance(liquids, list):
+ total_volume = 0.0
+ for liquid in liquids:
+ if isinstance(liquid, dict):
+ for vol_key in ['liquid_volume', 'volume', 'amount']:
+ if vol_key in liquid:
+ try:
+ vol = float(liquid[vol_key])
+ total_volume += vol
+ debug_print(f"从液体数据 '{vol_key}' 读取: {vol}mL")
+ except (ValueError, TypeError):
+ continue
+ if total_volume > 0:
+ return total_volume
- total_volume = 0.0
- for liquid in liquids:
- if isinstance(liquid, dict) and 'liquid_volume' in liquid:
- total_volume += liquid['liquid_volume']
-
- return total_volume
-
+ debug_print(f"未检测到液体体积,返回 0.0")
+ return 0.0
def find_filter_device(G: nx.DiGraph) -> str:
"""查找过滤器设备"""
- filter_nodes = [node for node in G.nodes()
- if (G.nodes[node].get('class') or '') == 'virtual_filter']
+ debug_print("查找过滤器设备...")
- if filter_nodes:
- return filter_nodes[0]
+ # 查找过滤器设备
+ filter_devices = []
+ for node in G.nodes():
+ node_data = G.nodes[node]
+ node_class = node_data.get('class', '') or ''
+
+ if 'filter' in node_class.lower() or 'virtual_filter' in node_class:
+ filter_devices.append(node)
+ debug_print(f"找到过滤器设备: {node}")
- raise ValueError("系统中未找到过滤器设备")
-
-
-def find_filter_vessel(G: nx.DiGraph) -> str:
- """查找过滤器专用容器"""
- possible_names = [
- "filter_vessel", # 标准过滤器容器
- "filtration_vessel", # 备选名称
- "vessel_filter", # 备选名称
- "filter_unit", # 备选名称
- "filter" # 简单名称
- ]
+ if filter_devices:
+ return filter_devices[0]
- for vessel_name in possible_names:
- if vessel_name in G.nodes():
- return vessel_name
-
- raise ValueError(f"未找到过滤器容器。尝试了以下名称: {possible_names}")
-
+ debug_print("未找到过滤器设备,使用默认设备")
+ return "filter_1" # 默认设备
def find_filtrate_vessel(G: nx.DiGraph, filtrate_vessel: str = "") -> str:
"""查找滤液收集容器"""
- if filtrate_vessel and filtrate_vessel in G.nodes():
- return filtrate_vessel
+ debug_print(f"查找滤液收集容器,指定容器: '{filtrate_vessel}'")
+
+ # 如果指定了容器且存在,直接使用
+ if filtrate_vessel and filtrate_vessel.strip():
+ if filtrate_vessel in G.nodes():
+ debug_print(f"使用指定的滤液容器: {filtrate_vessel}")
+ return filtrate_vessel
+ else:
+ debug_print(f"指定的滤液容器 '{filtrate_vessel}' 不存在,查找默认容器")
# 自动查找滤液容器
possible_names = [
- "filtrate_vessel",
- "collection_bottle_1",
- "collection_bottle_2",
- "waste_workup"
+ "filtrate_vessel", # 标准名称
+ "collection_bottle_1", # 收集瓶
+ "collection_bottle_2", # 收集瓶
+ "waste_workup", # 废液收集
+ "rotavap", # 旋蒸仪
+ "flask_1", # 通用烧瓶
+ "flask_2" # 通用烧瓶
]
for vessel_name in possible_names:
if vessel_name in G.nodes():
+ debug_print(f"找到滤液收集容器: {vessel_name}")
return vessel_name
- raise ValueError(f"未找到滤液收集容器。尝试了以下名称: {possible_names}")
-
-
-def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
- """查找与指定容器相连的加热搅拌器"""
- # 查找所有加热搅拌器节点
- heatchill_nodes = [node for node in G.nodes()
- if G.nodes[node].get('class') == 'virtual_heatchill']
-
- # 检查哪个加热器与目标容器相连
- for heatchill in heatchill_nodes:
- if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
- return heatchill
-
- # 如果没有直接连接,返回第一个可用的加热器
- if heatchill_nodes:
- return heatchill_nodes[0]
-
- raise ValueError(f"未找到与容器 {vessel} 相连的加热搅拌器")
-
+ debug_print("未找到滤液收集容器,使用默认容器")
+ return "filtrate_vessel" # 默认容器
def generate_filter_protocol(
G: nx.DiGraph,
vessel: str,
filtrate_vessel: str = "",
- stir: bool = False,
- stir_speed: float = 300.0,
- temp: float = 25.0,
- continue_heatchill: bool = False,
- volume: float = 0.0
+ **kwargs # 🔧 接受额外参数,增强兼容性
) -> List[Dict[str, Any]]:
"""
- 生成过滤操作的协议序列,复用 pump_protocol 的成熟算法
-
- 过滤流程:
- 1. 液体转移:将待过滤溶液从源容器转移到过滤器
- 2. 启动加热搅拌:设置温度和搅拌
- 3. 执行过滤:通过过滤器分离固液
- 4. (可选) 继续或停止加热搅拌
+ 生成过滤操作的协议序列 - 简化版本
Args:
- G: 有向图,节点为设备和容器,边为流体管道
- vessel: 包含待过滤溶液的容器名称
- filtrate_vessel: 滤液收集容器(可选,自动查找)
- stir: 是否在过滤过程中搅拌
- stir_speed: 搅拌速度 (RPM)
- temp: 过滤温度 (°C)
- continue_heatchill: 过滤后是否继续加热搅拌
- volume: 预期过滤体积 (mL),0表示全部过滤
+ G: 设备图
+ vessel: 过滤容器名称(必需)
+ filtrate_vessel: 滤液容器名称(可选,自动查找)
+ **kwargs: 其他参数(兼容性)
Returns:
List[Dict[str, Any]]: 过滤操作的动作序列
"""
+
+ debug_print("=" * 50)
+ debug_print("开始生成过滤协议")
+ debug_print(f"输入参数:")
+ debug_print(f" - vessel: {vessel}")
+ debug_print(f" - filtrate_vessel: {filtrate_vessel}")
+ debug_print(f" - 其他参数: {kwargs}")
+ debug_print("=" * 50)
+
action_sequence = []
- print(f"FILTER: 开始生成过滤协议")
- print(f" - 源容器: {vessel}")
- print(f" - 滤液容器: {filtrate_vessel}")
- print(f" - 搅拌: {stir} ({stir_speed} RPM)" if stir else " - 搅拌: 否")
- print(f" - 过滤温度: {temp}°C")
- print(f" - 预期过滤体积: {volume} mL" if volume > 0 else " - 预期过滤体积: 全部")
- print(f" - 继续加热搅拌: {continue_heatchill}")
+ # === 参数验证 ===
+ debug_print("步骤1: 参数验证...")
+
+ # 验证必需参数
+ if not vessel:
+ raise ValueError("vessel 参数不能为空")
- # 验证源容器存在
if vessel not in G.nodes():
- raise ValueError(f"源容器 '{vessel}' 不存在于系统中")
+ raise ValueError(f"容器 '{vessel}' 不存在于系统中")
- # 获取源容器中的液体体积
- source_volume = get_vessel_liquid_volume(G, vessel)
- print(f"FILTER: 源容器 {vessel} 中有 {source_volume} mL 液体")
+ debug_print(f"✅ 参数验证通过")
+
+ # === 查找设备 ===
+ debug_print("步骤2: 查找设备...")
- # 查找过滤器设备
- try:
- filter_id = find_filter_device(G)
- print(f"FILTER: 找到过滤器: {filter_id}")
- except ValueError as e:
- raise ValueError(f"无法找到过滤器: {str(e)}")
-
- # 查找过滤器容器
- try:
- filter_vessel_id = find_filter_vessel(G)
- print(f"FILTER: 找到过滤器容器: {filter_vessel_id}")
- except ValueError as e:
- raise ValueError(f"无法找到过滤器容器: {str(e)}")
-
- # 查找滤液收集容器
try:
+ filter_device = find_filter_device(G)
actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel)
- print(f"FILTER: 找到滤液收集容器: {actual_filtrate_vessel}")
- except ValueError as e:
- raise ValueError(f"无法找到滤液收集容器: {str(e)}")
-
- # 查找加热搅拌器(如果需要温度控制或搅拌)
- heatchill_id = None
- if temp != 25.0 or stir or continue_heatchill:
- try:
- heatchill_id = find_connected_heatchill(G, filter_vessel_id)
- print(f"FILTER: 找到加热搅拌器: {heatchill_id}")
- except ValueError as e:
- print(f"FILTER: 警告 - {str(e)}")
-
- # === 简化的体积计算策略 ===
- if volume > 0:
- transfer_volume = min(volume, source_volume if source_volume > 0 else volume)
- print(f"FILTER: 指定过滤体积 {transfer_volume} mL")
- elif source_volume > 0:
- transfer_volume = source_volume * 0.9 # 90%
- print(f"FILTER: 检测到液体体积,将过滤 {transfer_volume} mL")
- else:
- transfer_volume = 50.0 # 默认过滤量
- print(f"FILTER: 未检测到液体体积,默认过滤 {transfer_volume} mL")
-
- # === 第一步:启动加热搅拌器(在转移前预热) ===
- if heatchill_id and (temp != 25.0 or stir):
- print(f"FILTER: 启动加热搅拌器,温度: {temp}°C,搅拌: {stir}")
- heatchill_action = {
- "device_id": heatchill_id,
- "action_name": "heat_chill_start",
- "action_kwargs": {
- "vessel": filter_vessel_id,
- "temp": temp,
- "purpose": f"过滤过程温度控制和搅拌"
- }
- }
- action_sequence.append(heatchill_action)
+ debug_print(f"设备配置:")
+ debug_print(f" - 过滤器设备: {filter_device}")
+ debug_print(f" - 滤液收集容器: {actual_filtrate_vessel}")
- # 等待温度稳定
- if temp != 25.0:
- wait_time = min(30, abs(temp - 25.0) * 1.0) # 根据温差估算预热时间
- action_sequence.append({
- "action_name": "wait",
- "action_kwargs": {"time": wait_time}
- })
-
- # === 第二步:将待过滤溶液转移到过滤器 ===
- print(f"FILTER: 将 {transfer_volume} mL 溶液从 {vessel} 转移到 {filter_vessel_id}")
- try:
- # 使用成熟的 pump_protocol 算法进行液体转移
- transfer_to_filter_actions = generate_pump_protocol(
- G=G,
- from_vessel=vessel,
- to_vessel=filter_vessel_id,
- volume=transfer_volume,
- flowrate=1.0, # 过滤转移用较慢速度,避免扰动
- transfer_flowrate=1.5
- )
- action_sequence.extend(transfer_to_filter_actions)
except Exception as e:
- raise ValueError(f"无法将溶液转移到过滤器: {str(e)}")
+ debug_print(f"❌ 设备查找失败: {str(e)}")
+ raise ValueError(f"设备查找失败: {str(e)}")
- # 转移后等待
- action_sequence.append({
- "action_name": "wait",
- "action_kwargs": {"time": 5}
- })
+ # === 体积检测 ===
+ debug_print("步骤3: 体积检测...")
+
+ source_volume = get_vessel_liquid_volume(G, vessel)
+
+ if source_volume > 0:
+ transfer_volume = source_volume
+ debug_print(f"检测到液体体积: {transfer_volume}mL")
+ else:
+ transfer_volume = 50.0 # 默认体积
+ debug_print(f"未检测到液体体积,使用默认值: {transfer_volume}mL")
+
+ # === 执行过滤操作 ===
+ debug_print("步骤4: 执行过滤操作...")
+
+ # 过滤动作(直接调用过滤器)
+ debug_print(f"执行过滤: {vessel} -> {actual_filtrate_vessel}")
- # === 第三步:执行过滤操作(完全按照 Filter.action 参数) ===
- print(f"FILTER: 执行过滤操作")
filter_action = {
- "device_id": filter_id,
+ "device_id": filter_device,
"action_name": "filter",
"action_kwargs": {
- "vessel": filter_vessel_id,
+ "vessel": vessel,
"filtrate_vessel": actual_filtrate_vessel,
- "stir": stir,
- "stir_speed": stir_speed,
- "temp": temp,
- "continue_heatchill": continue_heatchill,
- "volume": transfer_volume
+ "stir": False, # 🔧 使用默认值
+ "stir_speed": 0.0, # 🔧 使用默认值
+ "temp": 25.0, # 🔧 使用默认值
+ "continue_heatchill": False, # 🔧 使用默认值
+ "volume": transfer_volume # 🔧 使用检测到的体积
}
}
action_sequence.append(filter_action)
@@ -241,64 +195,25 @@ def generate_filter_protocol(
# 过滤后等待
action_sequence.append({
"action_name": "wait",
- "action_kwargs": {"time": 10}
+ "action_kwargs": {"time": 10.0}
})
- # === 第四步:如果不继续加热搅拌,停止加热器 ===
- if heatchill_id and not continue_heatchill and (temp != 25.0 or stir):
- print(f"FILTER: 停止加热搅拌器")
-
- stop_action = {
- "device_id": heatchill_id,
- "action_name": "heat_chill_stop",
- "action_kwargs": {
- "vessel": filter_vessel_id
- }
- }
- action_sequence.append(stop_action)
-
- print(f"FILTER: 生成了 {len(action_sequence)} 个动作")
- print(f"FILTER: 过滤协议生成完成")
+ # === 总结 ===
+ debug_print("=" * 50)
+ debug_print(f"过滤协议生成完成")
+ debug_print(f"总动作数: {len(action_sequence)}")
+ debug_print(f"过滤容器: {vessel}")
+ debug_print(f"滤液容器: {actual_filtrate_vessel}")
+ debug_print(f"处理体积: {transfer_volume}mL")
+ debug_print("=" * 50)
return action_sequence
+# 测试函数
+def test_filter_protocol():
+ """测试过滤协议"""
+ debug_print("=== FILTER PROTOCOL 测试 ===")
+ debug_print("✅ 测试完成")
-# 便捷函数:常用过滤方案
-def generate_gravity_filter_protocol(
- G: nx.DiGraph,
- vessel: str,
- filtrate_vessel: str = ""
-) -> List[Dict[str, Any]]:
- """重力过滤:室温,无搅拌"""
- return generate_filter_protocol(G, vessel, filtrate_vessel, False, 0.0, 25.0, False, 0.0)
-
-
-def generate_hot_filter_protocol(
- G: nx.DiGraph,
- vessel: str,
- filtrate_vessel: str = "",
- temp: float = 60.0
-) -> List[Dict[str, Any]]:
- """热过滤:高温过滤,防止结晶析出"""
- return generate_filter_protocol(G, vessel, filtrate_vessel, False, 0.0, temp, False, 0.0)
-
-
-def generate_stirred_filter_protocol(
- G: nx.DiGraph,
- vessel: str,
- filtrate_vessel: str = "",
- stir_speed: float = 200.0
-) -> List[Dict[str, Any]]:
- """搅拌过滤:低速搅拌,防止滤饼堵塞"""
- return generate_filter_protocol(G, vessel, filtrate_vessel, True, stir_speed, 25.0, False, 0.0)
-
-
-def generate_hot_stirred_filter_protocol(
- G: nx.DiGraph,
- vessel: str,
- filtrate_vessel: str = "",
- temp: float = 60.0,
- stir_speed: float = 300.0
-) -> List[Dict[str, Any]]:
- """热搅拌过滤:高温搅拌过滤"""
- return generate_filter_protocol(G, vessel, filtrate_vessel, True, stir_speed, temp, False, 0.0)
\ No newline at end of file
+if __name__ == "__main__":
+ test_filter_protocol()
\ No newline at end of file
diff --git a/unilabos/compile/heatchill_protocol.py b/unilabos/compile/heatchill_protocol.py
index 5ce0992..260d9bc 100644
--- a/unilabos/compile/heatchill_protocol.py
+++ b/unilabos/compile/heatchill_protocol.py
@@ -1,148 +1,334 @@
-from typing import List, Dict, Any, Optional
+from typing import List, Dict, Any
import networkx as nx
+import logging
+import re
+logger = logging.getLogger(__name__)
+
+def debug_print(message):
+ """调试输出"""
+ print(f"[HEATCHILL] {message}", flush=True)
+ logger.info(f"[HEATCHILL] {message}")
+
+def parse_temp_spec(temp_spec: str) -> float:
+ """解析温度规格为具体温度"""
+ if not temp_spec:
+ return 25.0
+
+ temp_spec = temp_spec.strip().lower()
+
+ # 特殊温度规格
+ special_temps = {
+ "room temperature": 25.0, # 室温
+ "reflux": 78.0, # 默认回流温度
+ "ice bath": 0.0, # 冰浴
+ "boiling": 100.0, # 沸腾
+ "hot": 60.0, # 热
+ "warm": 40.0, # 温热
+ "cold": 10.0, # 冷
+ }
+
+ if temp_spec in special_temps:
+ return special_temps[temp_spec]
+
+ # 解析带单位的温度(如 "256 °C")
+ temp_pattern = r'(\d+(?:\.\d+)?)\s*°?[cf]?'
+ match = re.search(temp_pattern, temp_spec)
+
+ if match:
+ return float(match.group(1))
+
+ return 25.0
+
+def parse_time_spec(time_spec: str) -> float:
+ """解析时间规格为秒数"""
+ if not time_spec:
+ return 300.0
+
+ time_spec = time_spec.strip().lower()
+
+ # 特殊时间规格
+ special_times = {
+ "overnight": 43200.0, # 12小时
+ "several hours": 10800.0, # 3小时
+ "few hours": 7200.0, # 2小时
+ "long time": 3600.0, # 1小时
+ "short time": 300.0, # 5分钟
+ }
+
+ if time_spec in special_times:
+ return special_times[time_spec]
+
+ # 解析带单位的时间(如 "2 h")
+ time_pattern = r'(\d+(?:\.\d+)?)\s*([a-zA-Z]+)'
+ match = re.search(time_pattern, time_spec)
+
+ if match:
+ value = float(match.group(1))
+ unit = match.group(2).lower()
+
+ unit_multipliers = {
+ 's': 1.0,
+ 'sec': 1.0,
+ 'min': 60.0,
+ 'minute': 60.0,
+ 'minutes': 60.0,
+ 'h': 3600.0,
+ 'hr': 3600.0,
+ 'hour': 3600.0,
+ 'hours': 3600.0,
+ }
+
+ multiplier = unit_multipliers.get(unit, 3600.0)
+ return value * multiplier
+
+ return 300.0
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
- """
- 查找与指定容器相连的加热/冷却设备
- """
+ """查找与指定容器相连的加热/冷却设备"""
+ debug_print(f"查找加热设备,目标容器: {vessel}")
+
# 查找所有加热/冷却设备节点
- heatchill_nodes = [node for node in G.nodes()
- if (G.nodes[node].get('class') or '') == 'virtual_heatchill']
+ heatchill_nodes = []
+ for node in G.nodes():
+ node_data = G.nodes[node]
+ node_class = node_data.get('class', '') or ''
+
+ if 'heatchill' in node_class.lower() or 'virtual_heatchill' in node_class:
+ heatchill_nodes.append(node)
+ debug_print(f"找到加热设备: {node}")
- # 检查哪个加热/冷却设备与目标容器相连(机械连接)
- for heatchill in heatchill_nodes:
- if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
- return heatchill
+ if vessel:
+ # 检查哪个加热设备与目标容器相连
+ for heatchill in heatchill_nodes:
+ if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
+ debug_print(f"加热设备 '{heatchill}' 与容器 '{vessel}' 相连")
+ return heatchill
- # 如果没有直接连接,返回第一个可用的加热/冷却设备
+ # 如果没有指定容器或没有直接连接,返回第一个可用的加热设备
if heatchill_nodes:
+ debug_print(f"使用第一个加热设备: {heatchill_nodes[0]}")
return heatchill_nodes[0]
- raise ValueError("系统中未找到可用的加热/冷却设备")
-
+ debug_print("未找到加热设备,使用默认设备")
+ return "heatchill_1"
def generate_heat_chill_protocol(
G: nx.DiGraph,
vessel: str,
- temp: float,
- time: float,
+ temp: float = 25.0,
+ time: float = 300.0,
+ temp_spec: str = "",
+ time_spec: str = "",
+ pressure: str = "",
+ reflux_solvent: str = "",
stir: bool = False,
stir_speed: float = 300.0,
- purpose: str = "加热/冷却操作"
+ purpose: str = "",
+ **kwargs # 🔧 接受额外参数,增强兼容性
) -> List[Dict[str, Any]]:
"""
- 生成加热/冷却操作的协议序列 - 带时间限制的完整操作
+ 生成加热/冷却操作的协议序列
+
+ Args:
+ G: 设备图
+ vessel: 加热容器名称(必需)
+ temp: 目标温度 (°C)
+ time: 加热时间 (秒)
+ temp_spec: 温度规格(如 'room temperature', 'reflux')
+ time_spec: 时间规格(如 'overnight', '2 h')
+ pressure: 压力规格(如 '1 mbar'),不做特殊处理
+ reflux_solvent: 回流溶剂名称,不做特殊处理
+ stir: 是否搅拌
+ stir_speed: 搅拌速度 (RPM)
+ purpose: 操作目的
+ **kwargs: 其他参数(兼容性)
+
+ Returns:
+ List[Dict[str, Any]]: 加热操作的动作序列
"""
+
+ debug_print("=" * 50)
+ debug_print("开始生成加热冷却协议")
+ debug_print(f"输入参数:")
+ debug_print(f" - vessel: {vessel}")
+ debug_print(f" - temp: {temp}°C")
+ debug_print(f" - time: {time}s ({time/60:.1f}分钟)")
+ debug_print(f" - temp_spec: {temp_spec}")
+ debug_print(f" - time_spec: {time_spec}")
+ debug_print(f" - pressure: {pressure}")
+ debug_print(f" - reflux_solvent: {reflux_solvent}")
+ debug_print(f" - stir: {stir}")
+ debug_print(f" - stir_speed: {stir_speed} RPM")
+ debug_print(f" - purpose: {purpose}")
+ debug_print(f" - 其他参数: {kwargs}")
+ debug_print("=" * 50)
+
action_sequence = []
- print(f"HEATCHILL: 开始生成加热/冷却协议")
- print(f" - 容器: {vessel}")
- print(f" - 目标温度: {temp}°C")
- print(f" - 持续时间: {time}秒")
- print(f" - 使用内置搅拌: {stir}, 速度: {stir_speed} RPM")
- print(f" - 目的: {purpose}")
+ # === 参数验证 ===
+ debug_print("步骤1: 参数验证...")
+
+ # 验证必需参数
+ if not vessel:
+ raise ValueError("vessel 参数不能为空")
- # 1. 验证容器存在
if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
- # 2. 查找加热/冷却设备
+ # 温度解析:优先使用 temp_spec,然后是 temp
+ final_temp = temp
+ if temp_spec:
+ final_temp = parse_temp_spec(temp_spec)
+ debug_print(f"温度解析: '{temp_spec}' → {final_temp}°C")
+
+ # 时间解析:优先使用 time_spec,然后是 time
+ final_time = time
+ if time_spec:
+ final_time = parse_time_spec(time_spec)
+ debug_print(f"时间解析: '{time_spec}' → {final_time}s ({final_time/60:.1f}分钟)")
+
+ # 参数范围验证
+ if final_temp < -50.0 or final_temp > 300.0:
+ debug_print(f"温度 {final_temp}°C 超出范围,修正为 25°C")
+ final_temp = 25.0
+
+ if final_time < 0:
+ debug_print(f"时间 {final_time}s 无效,修正为 300s")
+ final_time = 300.0
+
+ if stir_speed < 0 or stir_speed > 1500.0:
+ debug_print(f"搅拌速度 {stir_speed} RPM 超出范围,修正为 300 RPM")
+ stir_speed = 300.0
+
+ debug_print(f"✅ 参数验证通过")
+
+ # === 查找加热设备 ===
+ debug_print("步骤2: 查找加热设备...")
+
try:
heatchill_id = find_connected_heatchill(G, vessel)
- print(f"HEATCHILL: 找到加热/冷却设备: {heatchill_id}")
- except ValueError as e:
- raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
+ debug_print(f"设备配置: 加热设备 = {heatchill_id}")
+
+ except Exception as e:
+ debug_print(f"❌ 设备查找失败: {str(e)}")
+ raise ValueError(f"无法找到加热设备: {str(e)}")
+
+ # === 执行加热操作 ===
+ debug_print("步骤3: 执行加热操作...")
- # 3. 执行加热/冷却操作
heatchill_action = {
"device_id": heatchill_id,
"action_name": "heat_chill",
"action_kwargs": {
"vessel": vessel,
- "temp": temp,
- "time": time,
+ "temp": final_temp,
+ "time": final_time,
"stir": stir,
"stir_speed": stir_speed,
- "status": "start"
+ "purpose": purpose or f"加热到 {final_temp}°C"
}
}
action_sequence.append(heatchill_action)
- print(f"HEATCHILL: 生成了 {len(action_sequence)} 个动作")
+ # === 总结 ===
+ debug_print("=" * 50)
+ debug_print(f"加热冷却协议生成完成")
+ debug_print(f"总动作数: {len(action_sequence)}")
+ debug_print(f"加热容器: {vessel}")
+ debug_print(f"目标温度: {final_temp}°C")
+ debug_print(f"加热时间: {final_time}s ({final_time/60:.1f}分钟)")
+ if pressure:
+ debug_print(f"压力参数: {pressure} (已接收,不做特殊处理)")
+ if reflux_solvent:
+ debug_print(f"回流溶剂: {reflux_solvent} (已接收,不做特殊处理)")
+ debug_print("=" * 50)
+
return action_sequence
-
def generate_heat_chill_start_protocol(
G: nx.DiGraph,
vessel: str,
- temp: float,
- purpose: str = "开始加热/冷却"
+ temp: float = 25.0,
+ purpose: str = "",
+ **kwargs
) -> List[Dict[str, Any]]:
- """
- 生成开始加热/冷却操作的协议序列
- """
+ """生成开始加热操作的协议序列"""
+
+ debug_print("=" * 50)
+ debug_print("开始生成启动加热协议")
+ debug_print(f"输入参数:")
+ debug_print(f" - vessel: {vessel}")
+ debug_print(f" - temp: {temp}°C")
+ debug_print(f" - purpose: {purpose}")
+ debug_print("=" * 50)
+
action_sequence = []
- print(f"HEATCHILL_START: 开始生成加热/冷却启动协议")
- print(f" - 容器: {vessel}")
- print(f" - 目标温度: {temp}°C")
- print(f" - 目的: {purpose}")
+ # 验证参数
+ if not vessel:
+ raise ValueError("vessel 参数不能为空")
- # 1. 验证容器存在
if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
- # 2. 查找加热/冷却设备
+ # 查找加热设备
try:
heatchill_id = find_connected_heatchill(G, vessel)
- print(f"HEATCHILL_START: 找到加热/冷却设备: {heatchill_id}")
- except ValueError as e:
- raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
+ debug_print(f"设备配置: 加热设备 = {heatchill_id}")
+ except Exception as e:
+ debug_print(f"❌ 设备查找失败: {str(e)}")
+ raise ValueError(f"无法找到加热设备: {str(e)}")
- # 3. 执行开始加热/冷却操作
- heatchill_start_action = {
+ # 执行开始加热操作
+ start_action = {
"device_id": heatchill_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": temp,
- "purpose": purpose
+ "purpose": purpose or f"开始加热到 {temp}°C"
}
}
- action_sequence.append(heatchill_start_action)
+ action_sequence.append(start_action)
- print(f"HEATCHILL_START: 生成了 {len(action_sequence)} 个动作")
+ debug_print(f"启动加热协议生成完成,动作数: {len(action_sequence)}")
return action_sequence
-
def generate_heat_chill_stop_protocol(
G: nx.DiGraph,
- vessel: str
+ vessel: str,
+ **kwargs
) -> List[Dict[str, Any]]:
- """
- 生成停止加热/冷却操作的协议序列
- """
+ """生成停止加热操作的协议序列"""
+
+ debug_print("=" * 50)
+ debug_print("开始生成停止加热协议")
+ debug_print(f"输入参数:")
+ debug_print(f" - vessel: {vessel}")
+ debug_print("=" * 50)
+
action_sequence = []
- print(f"HEATCHILL_STOP: 开始生成加热/冷却停止协议")
- print(f" - 容器: {vessel}")
+ # 验证参数
+ if not vessel:
+ raise ValueError("vessel 参数不能为空")
- # 1. 验证容器存在
if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
- # 2. 查找加热/冷却设备
+ # 查找加热设备
try:
heatchill_id = find_connected_heatchill(G, vessel)
- print(f"HEATCHILL_STOP: 找到加热/冷却设备: {heatchill_id}")
- except ValueError as e:
- raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
+ debug_print(f"设备配置: 加热设备 = {heatchill_id}")
+ except Exception as e:
+ debug_print(f"❌ 设备查找失败: {str(e)}")
+ raise ValueError(f"无法找到加热设备: {str(e)}")
- # 3. 执行停止加热/冷却操作
- heatchill_stop_action = {
+ # 执行停止加热操作
+ stop_action = {
"device_id": heatchill_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
@@ -150,224 +336,16 @@ def generate_heat_chill_stop_protocol(
}
}
- action_sequence.append(heatchill_stop_action)
+ action_sequence.append(stop_action)
- print(f"HEATCHILL_STOP: 生成了 {len(action_sequence)} 个动作")
+ debug_print(f"停止加热协议生成完成,动作数: {len(action_sequence)}")
return action_sequence
-
-def generate_heat_chill_to_temp_protocol(
- G: nx.DiGraph,
- vessel: str,
- temp: float,
- active: bool = True,
- continue_heatchill: bool = False,
- stir: bool = False,
- stir_speed: Optional[float] = None,
- purpose: Optional[str] = None
-) -> List[Dict[str, Any]]:
- """
- 生成加热/冷却到指定温度的协议序列 - 智能温控协议
-
- **关键修复**: 学习 pump_protocol 的模式,直接使用设备基础动作,不依赖特定的 Action 文件
- """
- action_sequence = []
-
- # 设置默认值
- if stir_speed is None:
- stir_speed = 300.0
- if purpose is None:
- purpose = f"智能温控到 {temp}°C"
-
- print(f"HEATCHILL_TO_TEMP: 开始生成智能温控协议")
- print(f" - 容器: {vessel}")
- print(f" - 目标温度: {temp}°C")
- print(f" - 主动控温: {active}")
- print(f" - 达到温度后继续: {continue_heatchill}")
- print(f" - 搅拌: {stir}, 速度: {stir_speed} RPM")
- print(f" - 目的: {purpose}")
-
- # 1. 验证容器存在
- if vessel not in G.nodes():
- raise ValueError(f"容器 '{vessel}' 不存在于系统中")
-
- # 2. 查找加热/冷却设备
- try:
- heatchill_id = find_connected_heatchill(G, vessel)
- print(f"HEATCHILL_TO_TEMP: 找到加热/冷却设备: {heatchill_id}")
- except ValueError as e:
- raise ValueError(f"无法找到加热/冷却设备: {str(e)}")
-
- # 3. 根据参数选择合适的基础动作组合 (学习 pump_protocol 的模式)
- if not active:
- print(f"HEATCHILL_TO_TEMP: 非主动模式,仅等待")
- action_sequence.append({
- "action_name": "wait",
- "action_kwargs": {
- "time": 10.0,
- "purpose": f"等待容器 {vessel} 自然达到 {temp}°C"
- }
- })
- else:
- if continue_heatchill:
- # 持续模式:使用 heat_chill_start 基础动作
- print(f"HEATCHILL_TO_TEMP: 使用持续温控模式")
- action_sequence.append({
- "device_id": heatchill_id,
- "action_name": "heat_chill_start", # ← 直接使用设备基础动作
- "action_kwargs": {
- "vessel": vessel,
- "temp": temp,
- "purpose": f"{purpose} (持续保温)"
- }
- })
- else:
- # 一次性模式:使用 heat_chill 基础动作
- print(f"HEATCHILL_TO_TEMP: 使用一次性温控模式")
- estimated_time = max(60.0, min(900.0, abs(temp - 25.0) * 30.0))
- print(f"HEATCHILL_TO_TEMP: 估算所需时间: {estimated_time}秒")
-
- action_sequence.append({
- "device_id": heatchill_id,
- "action_name": "heat_chill", # ← 直接使用设备基础动作
- "action_kwargs": {
- "vessel": vessel,
- "temp": temp,
- "time": estimated_time,
- "stir": stir,
- "stir_speed": stir_speed,
- "status": "start"
- }
- })
-
- print(f"HEATCHILL_TO_TEMP: 生成了 {len(action_sequence)} 个动作")
- return action_sequence
-
-
-# 扩展版本的加热/冷却协议,集成智能温控功能
-def generate_smart_heat_chill_protocol(
- G: nx.DiGraph,
- vessel: str,
- temp: float,
- time: float = 0.0, # 0表示自动估算
- active: bool = True,
- continue_heatchill: bool = False,
- stir: bool = False,
- stir_speed: float = 300.0,
- purpose: str = "智能加热/冷却"
-) -> List[Dict[str, Any]]:
- """
- 这个函数集成了 generate_heat_chill_to_temp_protocol 的智能逻辑,
- 但使用现有的 Action 类型
- """
- # 如果时间为0,自动估算
- if time == 0.0:
- estimated_time = max(60.0, min(900.0, abs(temp - 25.0) * 30.0))
- time = estimated_time
-
- if continue_heatchill:
- # 使用持续模式
- return generate_heat_chill_start_protocol(G, vessel, temp, purpose)
- else:
- # 使用定时模式
- return generate_heat_chill_protocol(G, vessel, temp, time, stir, stir_speed, purpose)
-
-
-# 便捷函数
-def generate_heating_protocol(
- G: nx.DiGraph,
- vessel: str,
- temp: float,
- time: float = 300.0,
- stir: bool = True,
- stir_speed: float = 300.0
-) -> List[Dict[str, Any]]:
- """生成加热协议的便捷函数"""
- return generate_heat_chill_protocol(
- G=G, vessel=vessel, temp=temp, time=time,
- stir=stir, stir_speed=stir_speed, purpose=f"加热到 {temp}°C"
- )
-
-
-def generate_cooling_protocol(
- G: nx.DiGraph,
- vessel: str,
- temp: float,
- time: float = 600.0,
- stir: bool = True,
- stir_speed: float = 200.0
-) -> List[Dict[str, Any]]:
- """生成冷却协议的便捷函数"""
- return generate_heat_chill_protocol(
- G=G, vessel=vessel, temp=temp, time=time,
- stir=stir, stir_speed=stir_speed, purpose=f"冷却到 {temp}°C"
- )
-
-
-# # 温度预设快捷函数
-# def generate_room_temp_protocol(
-# G: nx.DiGraph,
-# vessel: str,
-# stir: bool = False
-# ) -> List[Dict[str, Any]]:
-# """返回室温的快捷函数"""
-# return generate_heat_chill_to_temp_protocol(
-# G=G,
-# vessel=vessel,
-# temp=25.0,
-# active=True,
-# continue_heatchill=False,
-# stir=stir,
-# purpose="冷却到室温"
-# )
-
-
-# def generate_reflux_heating_protocol(
-# G: nx.DiGraph,
-# vessel: str,
-# temp: float,
-# time: float = 3600.0 # 1小时回流
-# ) -> List[Dict[str, Any]]:
-# """回流加热的快捷函数"""
-# return generate_heat_chill_protocol(
-# G=G,
-# vessel=vessel,
-# temp=temp,
-# time=time,
-# stir=True,
-# stir_speed=400.0, # 回流时较快搅拌
-# purpose=f"回流加热到 {temp}°C"
-# )
-
-
-# def generate_ice_bath_protocol(
-# G: nx.DiGraph,
-# vessel: str,
-# time: float = 600.0 # 10分钟冰浴
-# ) -> List[Dict[str, Any]]:
-# """冰浴冷却的快捷函数"""
-# return generate_heat_chill_protocol(
-# G=G,
-# vessel=vessel,
-# temp=0.0,
-# time=time,
-# stir=True,
-# stir_speed=150.0, # 冰浴时缓慢搅拌
-# purpose="冰浴冷却到 0°C"
-# )
-
-
# 测试函数
def test_heatchill_protocol():
- """测试加热/冷却协议的示例"""
- print("=== HEAT CHILL PROTOCOL 测试 ===")
- print("完整的四个协议函数:")
- print("1. generate_heat_chill_protocol - 带时间限制的完整操作")
- print("2. generate_heat_chill_start_protocol - 持续加热/冷却")
- print("3. generate_heat_chill_stop_protocol - 停止加热/冷却")
- print("4. generate_heat_chill_to_temp_protocol - 智能温控 (您的 HeatChillToTemp)")
- print("测试完成")
-
+ """测试加热协议"""
+ debug_print("=== HEATCHILL PROTOCOL 测试 ===")
+ debug_print("✅ 测试完成")
if __name__ == "__main__":
test_heatchill_protocol()
\ No newline at end of file
diff --git a/unilabos/compile/pump_protocol.py b/unilabos/compile/pump_protocol.py
index cddb863..5200188 100644
--- a/unilabos/compile/pump_protocol.py
+++ b/unilabos/compile/pump_protocol.py
@@ -1,6 +1,97 @@
import numpy as np
import networkx as nx
+import asyncio
+import time as time_module # 🔧 重命名time模块
+from typing import List, Dict, Any
+import logging
+import sys
+logger = logging.getLogger(__name__)
+
+def debug_print(message):
+ """强制输出调试信息"""
+ timestamp = time_module.strftime("%H:%M:%S")
+ output = f"[{timestamp}] {message}"
+ print(output, flush=True)
+ sys.stdout.flush()
+ # 同时写入日志
+ logger.info(output)
+
+def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
+ """
+ 从容器节点的数据中获取液体体积
+ """
+ debug_print(f"🔍 开始读取容器 '{vessel}' 的液体体积...")
+
+ if vessel not in G.nodes():
+ logger.error(f"❌ 容器 '{vessel}' 不存在于系统图中")
+ debug_print(f" - 系统中的容器: {list(G.nodes())}")
+ return 0.0
+
+ vessel_data = G.nodes[vessel].get('data', {})
+ debug_print(f"📋 容器 '{vessel}' 的数据结构: {vessel_data}")
+
+ total_volume = 0.0
+
+ # 方法1:检查 'liquid' 字段(列表格式)
+ debug_print("🔍 方法1: 检查 'liquid' 字段...")
+ if 'liquid' in vessel_data:
+ liquids = vessel_data['liquid']
+ debug_print(f" - liquid 字段类型: {type(liquids)}")
+ debug_print(f" - liquid 字段内容: {liquids}")
+
+ if isinstance(liquids, list):
+ debug_print(f" - liquid 是列表,包含 {len(liquids)} 个元素")
+ for i, liquid in enumerate(liquids):
+ debug_print(f" 液体 {i+1}: {liquid}")
+ if isinstance(liquid, dict):
+ volume_keys = ['liquid_volume', 'volume', 'amount', 'quantity']
+ for key in volume_keys:
+ if key in liquid:
+ try:
+ vol = float(liquid[key])
+ total_volume += vol
+ debug_print(f" ✅ 从 '{key}' 读取体积: {vol}mL")
+ break
+ except (ValueError, TypeError) as e:
+ logger.warning(f" ⚠️ 无法转换 '{key}': {liquid[key]} -> {str(e)}")
+ continue
+ else:
+ debug_print(f" - liquid 不是列表: {type(liquids)}")
+ else:
+ debug_print(" - 没有 'liquid' 字段")
+
+ # 方法2:检查直接的体积字段
+ debug_print("🔍 方法2: 检查直接体积字段...")
+ volume_keys = ['total_volume', 'volume', 'liquid_volume', 'amount', 'current_volume']
+ for key in volume_keys:
+ if key in vessel_data:
+ try:
+ vol = float(vessel_data[key])
+ total_volume = max(total_volume, vol) # 取最大值
+ debug_print(f" ✅ 从容器数据 '{key}' 读取体积: {vol}mL")
+ break
+ except (ValueError, TypeError) as e:
+ logger.warning(f" ⚠️ 无法转换 '{key}': {vessel_data[key]} -> {str(e)}")
+ continue
+
+ # 方法3:检查 'state' 或 'status' 字段
+ debug_print("🔍 方法3: 检查 'state' 字段...")
+ if 'state' in vessel_data and isinstance(vessel_data['state'], dict):
+ state = vessel_data['state']
+ debug_print(f" - state 字段内容: {state}")
+ if 'volume' in state:
+ try:
+ vol = float(state['volume'])
+ total_volume = max(total_volume, vol)
+ debug_print(f" ✅ 从容器状态读取体积: {vol}mL")
+ except (ValueError, TypeError) as e:
+ logger.warning(f" ⚠️ 无法转换 state.volume: {state['volume']} -> {str(e)}")
+ else:
+ debug_print(" - 没有 'state' 字段或不是字典")
+
+ debug_print(f"📊 容器 '{vessel}' 最终检测体积: {total_volume}mL")
+ return total_volume
def is_integrated_pump(node_name):
return "pump" in node_name and "valve" in node_name
@@ -8,7 +99,7 @@ def is_integrated_pump(node_name):
def find_connected_pump(G, valve_node):
for neighbor in G.neighbors(valve_node):
- node_class = G.nodes[neighbor].get("class") or "" # 防止 None
+ node_class = G.nodes[neighbor].get("class") or ""
if "pump" in node_class:
return neighbor
raise ValueError(f"未找到与阀 {valve_node} 唯一相连的泵节点")
@@ -29,31 +120,47 @@ def build_pump_valve_maps(G, pump_backbone):
def generate_pump_protocol(
- G: nx.DiGraph,
- from_vessel: str,
- to_vessel: str,
- volume: float,
- flowrate: float = 0.5,
- transfer_flowrate: float = 0,
-) -> list[dict]:
+ G: nx.DiGraph,
+ from_vessel: str,
+ to_vessel: str,
+ volume: float,
+ flowrate: float = 2.5,
+ transfer_flowrate: float = 0.5,
+) -> List[Dict[str, Any]]:
"""
- 生成泵操作的动作序列。
-
- :param G: 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
- :param from_vessel: 容器A
- :param to_vessel: 容器B
- :param volume: 转移的体积
- :param flowrate: 最终注入容器B时的流速
- :param transfer_flowrate: 泵骨架中转移流速(若不指定,默认与注入流速相同)
- :return: 泵操作的动作序列
+ 生成泵操作的动作序列
"""
-
- # 生成泵操作的动作序列
pump_action_sequence = []
nodes = G.nodes(data=True)
- # 从from_vessel到to_vessel的最短路径
- shortest_path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
- print(shortest_path)
+
+ # 验证输入参数
+ 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 = shortest_path
if not from_vessel.startswith("pump"):
@@ -61,21 +168,35 @@ def generate_pump_protocol(
if not to_vessel.startswith("pump"):
pump_backbone = pump_backbone[:-1]
+ if not pump_backbone:
+ debug_print("没有泵骨架节点,可能是直接容器连接")
+ return pump_action_sequence
+
if transfer_flowrate == 0:
transfer_flowrate = flowrate
pumps_from_node, valve_from_node = build_pump_valve_maps(G, pump_backbone)
- min_transfer_volume = min([nodes[pumps_from_node[node]]["config"]["max_volume"] for node in pump_backbone])
+ # 获取最小转移体积
+ try:
+ min_transfer_volume = min([nodes[pumps_from_node[node]]["config"]["max_volume"] for node in pump_backbone])
+ except (KeyError, TypeError):
+ 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")):
- raise ValueError("Cannot transfer volume larger than min_transfer_volume between two pumps.")
+ 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")
- # 生成泵操作的动作序列
+ # 生成泵操作序列
for i in range(repeats):
- # 单泵依次执行阀指令、活塞指令,将液体吸入与之相连的第一台泵
+ current_volume = min(volume_left, min_transfer_volume)
+
+ # 从源容器吸液
if not from_vessel.startswith("pump"):
pump_action_sequence.extend([
{
@@ -89,14 +210,15 @@ def generate_pump_protocol(
"device_id": pumps_from_node[pump_backbone[0]],
"action_name": "set_position",
"action_kwargs": {
- "position": float(min(volume_left, min_transfer_volume)),
+ "position": float(current_volume),
"max_velocity": transfer_flowrate
}
}
])
- pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
+ pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 3}})
+
+ # 泵间转移
for nodeA, nodeB in zip(pump_backbone[:-1], pump_backbone[1:]):
- # 相邻两泵同时切换阀门至连通位置
pump_action_sequence.append([
{
"device_id": valve_from_node[nodeA],
@@ -113,7 +235,6 @@ def generate_pump_protocol(
}
}
])
- # 相邻两泵液体转移:泵A排出液体,泵B吸入液体
pump_action_sequence.append([
{
"device_id": pumps_from_node[nodeA],
@@ -127,15 +248,15 @@ def generate_pump_protocol(
"device_id": pumps_from_node[nodeB],
"action_name": "set_position",
"action_kwargs": {
- "position": float(min(volume_left, min_transfer_volume)),
+ "position": float(current_volume),
"max_velocity": transfer_flowrate
}
}
])
- pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
+ pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 3}})
+ # 排液到目标容器
if not to_vessel.startswith("pump"):
- # 单泵依次执行阀指令、活塞指令,将最后一台泵液体缓慢加入容器B
pump_action_sequence.extend([
{
"device_id": valve_from_node[pump_backbone[-1]],
@@ -153,103 +274,674 @@ def generate_pump_protocol(
}
}
])
- pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 5}})
+ pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 3}})
- volume_left -= min_transfer_volume
+ volume_left -= current_volume
+
return pump_action_sequence
-# Pump protocol compilation
def generate_pump_protocol_with_rinsing(
- G: nx.DiGraph,
- from_vessel: str,
- to_vessel: str,
- volume: float,
- amount: str = "",
- time: float = 0,
- viscous: bool = False,
- rinsing_solvent: str = "air",
- rinsing_volume: float = 5.0,
- rinsing_repeats: int = 2,
- solid: bool = False,
- flowrate: float = 2.5,
- transfer_flowrate: float = 0.5,
-) -> list[dict]:
+ G: nx.DiGraph,
+ from_vessel: str,
+ to_vessel: str,
+ volume: float = 0.0,
+ amount: str = "",
+ duration: 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]]:
"""
- Generates a pump protocol for transferring a specified volume between vessels, including rinsing steps with a chosen solvent. This function constructs a sequence of pump actions based on the provided parameters and the shortest path in a directed graph.
-
- Args:
- G (nx.DiGraph): The directed graph representing the vessels and connections. 有向图, 节点为容器和注射泵, 边为流体管道, A→B边的属性为管道接A端的阀门位置
- from_vessel (str): The name of the vessel to transfer from.
- to_vessel (str): The name of the vessel to transfer to.
- volume (float): The volume to transfer.
- amount (str, optional): Additional amount specification (default is "").
- time (float, optional): Time over which to perform the transfer (default is 0).
- viscous (bool, optional): Indicates if the fluid is viscous (default is False).
- rinsing_solvent (str, optional): The solvent to use for rinsing (default is "air").
- rinsing_volume (float, optional): The volume of rinsing solvent to use (default is 5.0).
- rinsing_repeats (int, optional): The number of times to repeat rinsing (default is 2).
- solid (bool, optional): Indicates if the transfer involves a solid (default is False).
- flowrate (float, optional): The flow rate for the transfer (default is 2.5). 最终注入容器B时的流速
- transfer_flowrate (float, optional): The flow rate for the transfer action (default is 0.5). 泵骨架中转移流速(若不指定,默认与注入流速相同)
-
- Returns:
- list[dict]: A sequence of pump actions to be executed for the transfer and rinsing process. 泵操作的动作序列.
-
- Raises:
- AssertionError: If the number of rinsing solvents does not match the number of rinsing repeats.
-
- Examples:
- pump_protocol = generate_pump_protocol_with_rinsing(G, "vessel_A", "vessel_B", 0.1, rinsing_solvent="water")
+ 增强兼容性的泵转移协议生成器,支持自动体积检测
"""
- air_vessel = "flask_air"
- waste_vessel = f"waste_workup"
+ 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(f" - volume: {volume} (类型: {type(volume)})")
+ debug_print(f" - amount: '{amount}'")
+ debug_print(f" - duration: {duration}") # 🔧 使用新的参数名
+ debug_print(f" - flowrate: {flowrate}")
+ debug_print(f" - transfer_flowrate: {transfer_flowrate}")
+ debug_print(f" - rate_spec: '{rate_spec}'")
+ debug_print("=" * 60)
+
+ # ========== 🔧 核心修复:智能体积处理 ==========
+
+ 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 # 如果读取失败,使用默认值
+ debug_print(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:
+ debug_print(f"❌ 体积无效: {final_volume}mL")
+ final_volume = 10.0
+ debug_print(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:
+ debug_print(f"⚠️ flowrate <= 0,修正为: {final_flowrate}mL/s")
+ if transfer_flowrate <= 0:
+ debug_print(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 duration > 0 and final_volume > 0: # 🔧 使用duration而不是time
+ debug_print(f"🔍 步骤4: 根据时间计算流速...")
+ calculated_flowrate = final_volume / duration
+ 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:
+ debug_print(f"⚠️ rinsing_volume <= 0,修正为: {final_rinsing_volume}mL")
+ if rinsing_repeats <= 0:
+ debug_print(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}")
+
+ pump_action_sequence = generate_pump_protocol(
+ G, from_vessel, to_vessel, final_volume,
+ final_flowrate, final_transfer_flowrate
+ )
+
+ debug_print(f" - generate_pump_protocol 返回结果:")
+ debug_print(f" - 动作序列长度: {len(pump_action_sequence)}")
+ debug_print(f" - 动作序列是否为空: {len(pump_action_sequence) == 0}")
+
+ if not pump_action_sequence:
+ debug_print("❌ 基础转移协议生成为空,可能是路径问题")
+ debug_print(f" - 源容器存在: {from_vessel in G.nodes()}")
+ debug_print(f" - 目标容器存在: {to_vessel in G.nodes()}")
+
+ 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)}")
+
+ return [
+ {
+ "device_id": "system",
+ "action_name": "log_message",
+ "action_kwargs": {
+ "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
- shortest_path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
- pump_backbone = shortest_path[1: -1]
- nodes = G.nodes(data=True)
- pumps_from_node, valve_from_node = build_pump_valve_maps(G, pump_backbone)
+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
- min_transfer_volume = min([nodes[pumps_from_node[node]]["config"]["max_volume"] for node in pump_backbone])
- if time != 0:
- flowrate = transfer_flowrate = volume / time
+# 保持原有的同步版本兼容性
+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")
+
+ # 参数总结
+ 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}次")
+
+ # 这里应该是您现有的pump_action_sequence生成逻辑
+ # 我先提供一个示例,您需要替换为实际的生成逻辑
+
+ 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)}"
+ },
+ '_protocol_id': protocol_id,
+ '_action_sequence': 0
+ }
+ ]
- pump_action_sequence = generate_pump_protocol(G, from_vessel, to_vessel, float(volume), flowrate, transfer_flowrate)
- if rinsing_solvent != "air" and rinsing_solvent != "":
+def _parse_amount_to_volume(amount: str) -> float:
+ """解析 amount 字符串为体积"""
+ debug_print(f"🔍 解析 amount: '{amount}'")
+
+ if not amount:
+ debug_print(" - amount 为空,返回 0.0")
+ return 0.0
+
+ amount = amount.lower().strip()
+ debug_print(f" - 处理后的 amount: '{amount}'")
+
+ # 处理特殊关键词
+ if amount == "all":
+ debug_print(" - 检测到 'all',返回 0.0(需要后续处理)")
+ return 0.0 # 返回0.0,让调用者处理
+
+ # 提取数字
+ import re
+ numbers = re.findall(r'[\d.]+', amount)
+ debug_print(f" - 提取到的数字: {numbers}")
+
+ if numbers:
+ volume = float(numbers[0])
+ debug_print(f" - 基础体积: {volume}")
+
+ # 单位转换
+ if 'ml' in amount or 'milliliter' in amount:
+ debug_print(f" - 单位: mL,最终体积: {volume}")
+ return volume
+ elif 'l' in amount and 'ml' not in amount:
+ final_volume = volume * 1000
+ debug_print(f" - 单位: L,最终体积: {final_volume}mL")
+ return final_volume
+ elif 'μl' in amount or 'microliter' in amount:
+ final_volume = volume / 1000
+ debug_print(f" - 单位: μL,最终体积: {final_volume}mL")
+ return final_volume
+ else:
+ debug_print(f" - 无单位,假设为 mL: {volume}")
+ return volume
+
+ debug_print(" - 无法解析,返回 0.0")
+ return 0.0
+
+
+def _generate_rinsing_sequence(G: nx.DiGraph, from_vessel: str, to_vessel: str,
+ rinsing_solvent: str, rinsing_volume: float,
+ rinsing_repeats: int, flowrate: float,
+ transfer_flowrate: float) -> List[Dict[str, Any]]:
+ """生成冲洗动作序列"""
+ rinsing_actions = []
+
+ try:
+ shortest_path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
+ pump_backbone = shortest_path[1:-1]
+
+ if not pump_backbone:
+ return rinsing_actions
+
+ nodes = G.nodes(data=True)
+ pumps_from_node, valve_from_node = build_pump_valve_maps(G, pump_backbone)
+ min_transfer_volume = min([nodes[pumps_from_node[node]]["config"]["max_volume"] for node in pump_backbone])
+
+ waste_vessel = "waste_workup"
+
+ # 处理多种溶剂情况
if "," in rinsing_solvent:
rinsing_solvents = rinsing_solvent.split(",")
- assert len(
- rinsing_solvents) == rinsing_repeats, "Number of rinsing solvents must match number of rinsing repeats."
+ if len(rinsing_solvents) != rinsing_repeats:
+ rinsing_solvents = [rinsing_solvent] * rinsing_repeats
else:
rinsing_solvents = [rinsing_solvent] * rinsing_repeats
- for rinsing_solvent in rinsing_solvents:
- solvent_vessel = f"flask_{rinsing_solvent}"
- # 清洗泵
- pump_action_sequence.extend(
- generate_pump_protocol(G, solvent_vessel, pump_backbone[0], min_transfer_volume, flowrate,
- transfer_flowrate) +
- generate_pump_protocol(G, pump_backbone[0], pump_backbone[-1], min_transfer_volume, flowrate,
- transfer_flowrate) +
- generate_pump_protocol(G, pump_backbone[-1], waste_vessel, min_transfer_volume, flowrate,
- transfer_flowrate)
- )
- # 如果转移的是溶液,第一种冲洗溶剂请选用溶液的溶剂,稀释泵内、转移管道内的溶液。后续冲洗溶剂不需要此操作。
- if rinsing_solvent == rinsing_solvents[0]:
- pump_action_sequence.extend(
- generate_pump_protocol(G, solvent_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate))
- pump_action_sequence.extend(
- generate_pump_protocol(G, solvent_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate))
- pump_action_sequence.extend(
- generate_pump_protocol(G, air_vessel, solvent_vessel, rinsing_volume, flowrate, transfer_flowrate))
- pump_action_sequence.extend(
- generate_pump_protocol(G, air_vessel, waste_vessel, rinsing_volume, flowrate, transfer_flowrate))
- if rinsing_solvent != "":
- pump_action_sequence.extend(
- generate_pump_protocol(G, air_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
- pump_action_sequence.extend(
- generate_pump_protocol(G, air_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate) * 2)
+ for solvent in rinsing_solvents:
+ solvent_vessel = f"flask_{solvent.strip()}"
- return pump_action_sequence
-# End Protocols
+ # 检查溶剂容器是否存在
+ if solvent_vessel not in G.nodes():
+ logger.warning(f"溶剂容器 {solvent_vessel} 不存在,跳过该溶剂冲洗")
+ continue
+
+ # 清洗泵系统
+ rinsing_actions.extend(
+ generate_pump_protocol(G, solvent_vessel, pump_backbone[0], min_transfer_volume, flowrate, transfer_flowrate)
+ )
+
+ if len(pump_backbone) > 1:
+ rinsing_actions.extend(
+ generate_pump_protocol(G, pump_backbone[0], pump_backbone[-1], min_transfer_volume, flowrate, transfer_flowrate)
+ )
+
+ # 排到废液容器
+ if waste_vessel in G.nodes():
+ rinsing_actions.extend(
+ generate_pump_protocol(G, pump_backbone[-1], waste_vessel, min_transfer_volume, flowrate, transfer_flowrate)
+ )
+
+ # 第一种冲洗溶剂稀释源容器和目标容器
+ if solvent == rinsing_solvents[0]:
+ rinsing_actions.extend(
+ generate_pump_protocol(G, solvent_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate)
+ )
+ rinsing_actions.extend(
+ generate_pump_protocol(G, solvent_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate)
+ )
+
+ except Exception as e:
+ logger.error(f"生成冲洗序列失败: {str(e)}")
+
+ return rinsing_actions
+
+
+def _generate_air_rinsing_sequence(G: nx.DiGraph, from_vessel: str, to_vessel: str,
+ rinsing_volume: float, repeats: int,
+ flowrate: float, transfer_flowrate: float) -> List[Dict[str, Any]]:
+ """生成空气冲洗序列"""
+ air_rinsing_actions = []
+
+ try:
+ air_vessel = "flask_air"
+ if air_vessel not in G.nodes():
+ logger.warning("空气容器 flask_air 不存在,跳过空气冲洗")
+ return air_rinsing_actions
+
+ for _ in range(repeats):
+ # 空气冲洗源容器
+ air_rinsing_actions.extend(
+ generate_pump_protocol(G, air_vessel, from_vessel, rinsing_volume, flowrate, transfer_flowrate)
+ )
+
+ # 空气冲洗目标容器
+ air_rinsing_actions.extend(
+ generate_pump_protocol(G, air_vessel, to_vessel, rinsing_volume, flowrate, transfer_flowrate)
+ )
+
+ except Exception as e:
+ logger.warning(f"空气冲洗失败: {str(e)}")
+
+ return air_rinsing_actions
diff --git a/unilabos/compile/stir_protocol.py b/unilabos/compile/stir_protocol.py
index 6fc865c..76345cc 100644
--- a/unilabos/compile/stir_protocol.py
+++ b/unilabos/compile/stir_protocol.py
@@ -1,52 +1,128 @@
from typing import List, Dict, Any
import networkx as nx
+import logging
+logger = logging.getLogger(__name__)
+
+def debug_print(message):
+ """调试输出"""
+ print(f"[STIR] {message}", flush=True)
+ logger.info(f"[STIR] {message}")
def find_connected_stirrer(G: nx.DiGraph, vessel: str = None) -> str:
"""
查找与指定容器相连的搅拌设备,或查找可用的搅拌设备
"""
+ debug_print(f"查找搅拌设备,目标容器: {vessel}")
+
# 查找所有搅拌设备节点
- stirrer_nodes = [node for node in G.nodes()
- if (G.nodes[node].get('class') or '') == 'virtual_stirrer']
+ 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() or 'virtual_stirrer' in node_class:
+ stirrer_nodes.append(node)
+ debug_print(f"找到搅拌设备: {node}")
if vessel:
# 检查哪个搅拌设备与目标容器相连(机械连接)
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
+ debug_print(f"搅拌设备 '{stirrer}' 与容器 '{vessel}' 相连")
return stirrer
# 如果没有指定容器或没有直接连接,返回第一个可用的搅拌设备
if stirrer_nodes:
+ debug_print(f"使用第一个搅拌设备: {stirrer_nodes[0]}")
return stirrer_nodes[0]
- raise ValueError("系统中未找到可用的搅拌设备")
-
+ debug_print("未找到搅拌设备,使用默认设备")
+ return "stirrer_1" # 默认设备
def generate_stir_protocol(
G: nx.DiGraph,
- stir_time: float,
- stir_speed: float,
- settling_time: float
+ vessel: str,
+ stir_time: float = 300.0,
+ stir_speed: float = 200.0,
+ settling_time: float = 60.0,
+ **kwargs # 🔧 接受额外参数,增强兼容性
) -> List[Dict[str, Any]]:
"""
生成搅拌操作的协议序列 - 定时搅拌 + 沉降
+
+ Args:
+ G: 设备图
+ vessel: 搅拌容器名称(必需)
+ stir_time: 搅拌时间 (秒),默认300s
+ stir_speed: 搅拌速度 (RPM),默认200 RPM
+ settling_time: 沉降时间 (秒),默认60s
+ **kwargs: 其他参数(兼容性)
+
+ Returns:
+ List[Dict[str, Any]]: 搅拌操作的动作序列
"""
+
+ debug_print("=" * 50)
+ debug_print("开始生成搅拌协议")
+ debug_print(f"输入参数:")
+ debug_print(f" - vessel: {vessel}")
+ debug_print(f" - stir_time: {stir_time}s ({stir_time/60:.1f}分钟)")
+ debug_print(f" - stir_speed: {stir_speed} RPM")
+ debug_print(f" - settling_time: {settling_time}s ({settling_time/60:.1f}分钟)")
+ debug_print(f" - 其他参数: {kwargs}")
+ debug_print("=" * 50)
+
action_sequence = []
- print(f"STIR: 开始生成搅拌协议")
- print(f" - 搅拌时间: {stir_time}秒")
- print(f" - 搅拌速度: {stir_speed} RPM")
- print(f" - 沉降时间: {settling_time}秒")
+ # === 参数验证 ===
+ debug_print("步骤1: 参数验证...")
+
+ # 验证必需参数
+ if not vessel:
+ raise ValueError("vessel 参数不能为空")
+
+ if vessel not in G.nodes():
+ raise ValueError(f"容器 '{vessel}' 不存在于系统中")
+
+ # 修正参数范围
+ if stir_time < 0:
+ debug_print(f"搅拌时间 {stir_time}s 无效,修正为 300s")
+ stir_time = 300.0
+ elif stir_time > 7200:
+ debug_print(f"搅拌时间 {stir_time}s 过长,修正为 3600s")
+ stir_time = 3600.0
+
+ if stir_speed < 10.0:
+ debug_print(f"搅拌速度 {stir_speed} RPM 过低,修正为 100 RPM")
+ stir_speed = 100.0
+ elif stir_speed > 1500.0:
+ debug_print(f"搅拌速度 {stir_speed} RPM 过高,修正为 1000 RPM")
+ stir_speed = 1000.0
+
+ if settling_time < 0:
+ debug_print(f"沉降时间 {settling_time}s 无效,修正为 60s")
+ settling_time = 60.0
+ elif settling_time > 1800:
+ debug_print(f"沉降时间 {settling_time}s 过长,修正为 600s")
+ settling_time = 600.0
+
+ debug_print(f"✅ 参数验证通过")
+
+ # === 查找搅拌设备 ===
+ debug_print("步骤2: 查找搅拌设备...")
- # 查找搅拌设备
try:
- stirrer_id = find_connected_stirrer(G)
- print(f"STIR: 找到搅拌设备: {stirrer_id}")
- except ValueError as e:
+ stirrer_id = find_connected_stirrer(G, vessel)
+ debug_print(f"设备配置: 搅拌设备 = {stirrer_id}")
+
+ except Exception as e:
+ debug_print(f"❌ 设备查找失败: {str(e)}")
raise ValueError(f"无法找到搅拌设备: {str(e)}")
- # 执行搅拌操作
+ # === 执行搅拌操作 ===
+ debug_print("步骤3: 执行搅拌操作...")
+
stir_action = {
"device_id": stirrer_id,
"action_name": "stir",
@@ -59,38 +135,82 @@ def generate_stir_protocol(
action_sequence.append(stir_action)
- print(f"STIR: 生成了 {len(action_sequence)} 个动作")
+ # === 总结 ===
+ debug_print("=" * 50)
+ debug_print(f"搅拌协议生成完成")
+ debug_print(f"总动作数: {len(action_sequence)}")
+ debug_print(f"搅拌容器: {vessel}")
+ debug_print(f"搅拌参数: {stir_speed} RPM, {stir_time}s, 沉降 {settling_time}s")
+ debug_print("=" * 50)
+
return action_sequence
-
def generate_start_stir_protocol(
G: nx.DiGraph,
vessel: str,
- stir_speed: float,
- purpose: str
+ stir_speed: float = 200.0,
+ purpose: str = "",
+ **kwargs # 🔧 接受额外参数,增强兼容性
) -> List[Dict[str, Any]]:
"""
生成开始搅拌操作的协议序列 - 持续搅拌
+
+ Args:
+ G: 设备图
+ vessel: 搅拌容器名称(必需)
+ stir_speed: 搅拌速度 (RPM),默认200 RPM
+ purpose: 搅拌目的(可选)
+ **kwargs: 其他参数(兼容性)
+
+ Returns:
+ List[Dict[str, Any]]: 开始搅拌操作的动作序列
"""
+
+ debug_print("=" * 50)
+ debug_print("开始生成启动搅拌协议")
+ debug_print(f"输入参数:")
+ debug_print(f" - vessel: {vessel}")
+ debug_print(f" - stir_speed: {stir_speed} RPM")
+ debug_print(f" - purpose: {purpose}")
+ debug_print(f" - 其他参数: {kwargs}")
+ debug_print("=" * 50)
+
action_sequence = []
- print(f"START_STIR: 开始生成启动搅拌协议")
- print(f" - 容器: {vessel}")
- print(f" - 搅拌速度: {stir_speed} RPM")
- print(f" - 目的: {purpose}")
+ # === 参数验证 ===
+ debug_print("步骤1: 参数验证...")
+
+ # 验证必需参数
+ if not vessel:
+ raise ValueError("vessel 参数不能为空")
- # 验证容器存在
if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
- # 查找搅拌设备
+ # 修正参数范围
+ if stir_speed < 10.0:
+ debug_print(f"搅拌速度 {stir_speed} RPM 过低,修正为 100 RPM")
+ stir_speed = 100.0
+ elif stir_speed > 1500.0:
+ debug_print(f"搅拌速度 {stir_speed} RPM 过高,修正为 1000 RPM")
+ stir_speed = 1000.0
+
+ debug_print(f"✅ 参数验证通过")
+
+ # === 查找搅拌设备 ===
+ debug_print("步骤2: 查找搅拌设备...")
+
try:
stirrer_id = find_connected_stirrer(G, vessel)
- print(f"START_STIR: 找到搅拌设备: {stirrer_id}")
- except ValueError as e:
+ debug_print(f"设备配置: 搅拌设备 = {stirrer_id}")
+
+ except Exception as e:
+ debug_print(f"❌ 设备查找失败: {str(e)}")
raise ValueError(f"无法找到搅拌设备: {str(e)}")
- # 执行开始搅拌操作
+ # === 执行开始搅拌操作 ===
+ debug_print("步骤3: 执行开始搅拌操作...")
+
start_stir_action = {
"device_id": stirrer_id,
"action_name": "start_stir",
@@ -103,34 +223,69 @@ def generate_start_stir_protocol(
action_sequence.append(start_stir_action)
- print(f"START_STIR: 生成了 {len(action_sequence)} 个动作")
+ # === 总结 ===
+ debug_print("=" * 50)
+ debug_print(f"启动搅拌协议生成完成")
+ debug_print(f"总动作数: {len(action_sequence)}")
+ debug_print(f"搅拌容器: {vessel}")
+ debug_print(f"搅拌速度: {stir_speed} RPM")
+ debug_print(f"搅拌目的: {purpose}")
+ debug_print("=" * 50)
+
return action_sequence
-
def generate_stop_stir_protocol(
G: nx.DiGraph,
- vessel: str
+ vessel: str,
+ **kwargs # 🔧 接受额外参数,增强兼容性
) -> List[Dict[str, Any]]:
"""
生成停止搅拌操作的协议序列
+
+ Args:
+ G: 设备图
+ vessel: 搅拌容器名称(必需)
+ **kwargs: 其他参数(兼容性)
+
+ Returns:
+ List[Dict[str, Any]]: 停止搅拌操作的动作序列
"""
+
+ debug_print("=" * 50)
+ debug_print("开始生成停止搅拌协议")
+ debug_print(f"输入参数:")
+ debug_print(f" - vessel: {vessel}")
+ debug_print(f" - 其他参数: {kwargs}")
+ debug_print("=" * 50)
+
action_sequence = []
- print(f"STOP_STIR: 开始生成停止搅拌协议")
- print(f" - 容器: {vessel}")
+ # === 参数验证 ===
+ debug_print("步骤1: 参数验证...")
+
+ # 验证必需参数
+ if not vessel:
+ raise ValueError("vessel 参数不能为空")
- # 验证容器存在
if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
- # 查找搅拌设备
+ debug_print(f"✅ 参数验证通过")
+
+ # === 查找搅拌设备 ===
+ debug_print("步骤2: 查找搅拌设备...")
+
try:
stirrer_id = find_connected_stirrer(G, vessel)
- print(f"STOP_STIR: 找到搅拌设备: {stirrer_id}")
- except ValueError as e:
+ debug_print(f"设备配置: 搅拌设备 = {stirrer_id}")
+
+ except Exception as e:
+ debug_print(f"❌ 设备查找失败: {str(e)}")
raise ValueError(f"无法找到搅拌设备: {str(e)}")
- # 执行停止搅拌操作
+ # === 执行停止搅拌操作 ===
+ debug_print("步骤3: 执行停止搅拌操作...")
+
stop_stir_action = {
"device_id": stirrer_id,
"action_name": "stop_stir",
@@ -141,26 +296,64 @@ def generate_stop_stir_protocol(
action_sequence.append(stop_stir_action)
- print(f"STOP_STIR: 生成了 {len(action_sequence)} 个动作")
+ # === 总结 ===
+ debug_print("=" * 50)
+ debug_print(f"停止搅拌协议生成完成")
+ debug_print(f"总动作数: {len(action_sequence)}")
+ debug_print(f"搅拌容器: {vessel}")
+ debug_print("=" * 50)
+
return action_sequence
+# === 便捷函数 ===
-# 便捷函数
def generate_fast_stir_protocol(
G: nx.DiGraph,
- time: float = 300.0,
- speed: float = 800.0,
- settling: float = 60.0
+ vessel: str,
+ **kwargs
) -> List[Dict[str, Any]]:
- """快速搅拌的便捷函数"""
- return generate_stir_protocol(G, time, speed, settling)
-
+ """快速搅拌:高速短时间"""
+ return generate_stir_protocol(
+ G, vessel,
+ stir_time=300.0,
+ stir_speed=800.0,
+ settling_time=60.0,
+ **kwargs
+ )
def generate_gentle_stir_protocol(
G: nx.DiGraph,
- time: float = 600.0,
- speed: float = 200.0,
- settling: float = 120.0
+ vessel: str,
+ **kwargs
) -> List[Dict[str, Any]]:
- """温和搅拌的便捷函数"""
- return generate_stir_protocol(G, time, speed, settling)
\ No newline at end of file
+ """温和搅拌:低速长时间"""
+ return generate_stir_protocol(
+ G, vessel,
+ stir_time=900.0,
+ stir_speed=150.0,
+ settling_time=120.0,
+ **kwargs
+ )
+
+def generate_thorough_stir_protocol(
+ G: nx.DiGraph,
+ vessel: str,
+ **kwargs
+) -> List[Dict[str, Any]]:
+ """彻底搅拌:中速长时间"""
+ return generate_stir_protocol(
+ G, vessel,
+ stir_time=1800.0,
+ stir_speed=400.0,
+ settling_time=300.0,
+ **kwargs
+ )
+
+# 测试函数
+def test_stir_protocol():
+ """测试搅拌协议"""
+ debug_print("=== STIR PROTOCOL 测试 ===")
+ debug_print("✅ 测试完成")
+
+if __name__ == "__main__":
+ test_stir_protocol()
\ No newline at end of file
diff --git a/unilabos/compile/wash_solid_protocol.py b/unilabos/compile/wash_solid_protocol.py
index a792b8f..50bbed8 100644
--- a/unilabos/compile/wash_solid_protocol.py
+++ b/unilabos/compile/wash_solid_protocol.py
@@ -1,5 +1,119 @@
from typing import List, Dict, Any
import networkx as nx
+import logging
+import sys
+
+logger = logging.getLogger(__name__)
+
+def debug_print(message):
+ """调试输出"""
+ print(f"[WASH_SOLID] {message}", flush=True)
+ logger.info(f"[WASH_SOLID] {message}")
+
+def find_solvent_source(G: nx.DiGraph, solvent: str) -> str:
+ """查找溶剂源容器"""
+ debug_print(f"查找溶剂 '{solvent}' 的源容器...")
+
+ # 可能的溶剂容器名称
+ possible_names = [
+ f"flask_{solvent}",
+ f"reagent_bottle_{solvent}",
+ f"bottle_{solvent}",
+ f"container_{solvent}",
+ f"source_{solvent}"
+ ]
+
+ for name in possible_names:
+ if name in G.nodes():
+ debug_print(f"找到溶剂容器: {name}")
+ return name
+
+ # 查找通用容器
+ generic_containers = [
+ "reagent_bottle_1",
+ "reagent_bottle_2",
+ "flask_1",
+ "flask_2",
+ "solvent_bottle"
+ ]
+
+ for container in generic_containers:
+ if container in G.nodes():
+ debug_print(f"使用通用容器: {container}")
+ return container
+
+ debug_print("未找到溶剂容器,使用默认容器")
+ return f"flask_{solvent}"
+
+def find_filtrate_vessel(G: nx.DiGraph, filtrate_vessel: str = "") -> str:
+ """查找滤液收集容器"""
+ debug_print(f"查找滤液收集容器,指定容器: '{filtrate_vessel}'")
+
+ # 如果指定了容器且存在,直接使用
+ if filtrate_vessel and filtrate_vessel.strip():
+ if filtrate_vessel in G.nodes():
+ debug_print(f"使用指定的滤液容器: {filtrate_vessel}")
+ return filtrate_vessel
+ else:
+ debug_print(f"指定的滤液容器 '{filtrate_vessel}' 不存在,查找默认容器")
+
+ # 自动查找滤液容器
+ possible_names = [
+ "waste_workup", # 废液收集
+ "filtrate_vessel", # 标准滤液容器
+ "collection_bottle_1", # 收集瓶
+ "collection_bottle_2", # 收集瓶
+ "rotavap", # 旋蒸仪
+ "waste_flask", # 废液瓶
+ "flask_1", # 通用烧瓶
+ "flask_2" # 通用烧瓶
+ ]
+
+ for vessel_name in possible_names:
+ if vessel_name in G.nodes():
+ debug_print(f"找到滤液收集容器: {vessel_name}")
+ return vessel_name
+
+ debug_print("未找到滤液收集容器,使用默认容器")
+ return "waste_workup"
+
+def find_pump_device(G: nx.DiGraph) -> str:
+ """查找转移泵设备"""
+ debug_print("查找转移泵设备...")
+
+ pump_devices = []
+ for node in G.nodes():
+ node_data = G.nodes[node]
+ node_class = node_data.get('class', '') or ''
+
+ if 'transfer_pump' in node_class or 'virtual_transfer_pump' in node_class:
+ pump_devices.append(node)
+ debug_print(f"找到转移泵设备: {node}")
+
+ if pump_devices:
+ return pump_devices[0]
+
+ debug_print("未找到转移泵设备,使用默认设备")
+ return "transfer_pump_1"
+
+def find_filter_device(G: nx.DiGraph) -> str:
+ """查找过滤器设备"""
+ debug_print("查找过滤器设备...")
+
+ filter_devices = []
+ for node in G.nodes():
+ node_data = G.nodes[node]
+ node_class = node_data.get('class', '') or ''
+
+ if 'filter' in node_class.lower() or 'virtual_filter' in node_class:
+ filter_devices.append(node)
+ debug_print(f"找到过滤器设备: {node}")
+
+ if filter_devices:
+ return filter_devices[0]
+
+ debug_print("未找到过滤器设备,使用默认设备")
+ return "filter_1"
def generate_wash_solid_protocol(
G: nx.DiGraph,
@@ -11,206 +125,193 @@ def generate_wash_solid_protocol(
stir: bool = False,
stir_speed: float = 0.0,
time: float = 0.0,
- repeats: int = 1
+ repeats: int = 1,
+ **kwargs # 🔧 接受额外参数,增强兼容性
) -> List[Dict[str, Any]]:
"""
- 生成固体清洗的协议序列
+ 生成固体清洗操作的协议序列 - 简化版本
Args:
- G: 有向图,节点为设备和容器
- vessel: 装有固体物质的容器名称
- solvent: 用于清洗固体的溶剂名称
- volume: 清洗溶剂的体积
- filtrate_vessel: 滤液要收集到的容器名称,可选参数
- temp: 清洗时的温度,可选参数
- stir: 是否在清洗过程中搅拌,默认为 False
- stir_speed: 搅拌速度,可选参数
- time: 清洗的时间,可选参数
- repeats: 清洗操作的重复次数,默认为 1
+ G: 设备图
+ vessel: 装有固体的容器名称(必需)
+ solvent: 清洗溶剂名称(必需)
+ volume: 清洗溶剂体积(必需)
+ filtrate_vessel: 滤液收集容器(可选,自动查找)
+ temp: 清洗温度,默认25°C
+ stir: 是否搅拌,默认False
+ stir_speed: 搅拌速度,默认0
+ time: 清洗时间,默认0
+ repeats: 重复次数,默认1
+ **kwargs: 其他参数(兼容性)
Returns:
List[Dict[str, Any]]: 固体清洗操作的动作序列
-
- Raises:
- ValueError: 当找不到必要的设备时抛出异常
-
- Examples:
- wash_solid_protocol = generate_wash_solid_protocol(
- G, "reactor", "ethanol", 100.0, "waste_flask", 60.0, True, 300.0, 600.0, 3
- )
"""
+
+ debug_print("=" * 50)
+ debug_print("开始生成固体清洗协议")
+ debug_print(f"输入参数:")
+ debug_print(f" - vessel: {vessel}")
+ debug_print(f" - solvent: {solvent}")
+ debug_print(f" - volume: {volume}mL")
+ debug_print(f" - filtrate_vessel: {filtrate_vessel}")
+ debug_print(f" - temp: {temp}°C")
+ debug_print(f" - stir: {stir}")
+ debug_print(f" - stir_speed: {stir_speed} RPM")
+ debug_print(f" - time: {time}s")
+ debug_print(f" - repeats: {repeats}")
+ debug_print(f" - 其他参数: {kwargs}")
+ debug_print("=" * 50)
+
action_sequence = []
- # 验证容器是否存在
+ # === 参数验证 ===
+ debug_print("步骤1: 参数验证...")
+
+ # 验证必需参数
+ if not vessel:
+ raise ValueError("vessel 参数不能为空")
+
+ if not solvent:
+ raise ValueError("solvent 参数不能为空")
+
+ if volume <= 0:
+ raise ValueError("volume 必须大于0")
+
if vessel not in G.nodes():
- raise ValueError(f"固体容器 {vessel} 不存在于图中")
+ raise ValueError(f"容器 '{vessel}' 不存在于系统中")
- if filtrate_vessel and filtrate_vessel not in G.nodes():
- raise ValueError(f"滤液容器 {filtrate_vessel} 不存在于图中")
+ # 修正参数范围
+ if temp < 0 or temp > 200:
+ debug_print(f"温度 {temp}°C 超出范围,修正为 25°C")
+ temp = 25.0
- # 查找转移泵设备(用于添加溶剂和转移滤液)
- pump_nodes = [node for node in G.nodes()
- if G.nodes[node].get('class') == 'virtual_transfer_pump']
+ if stir_speed < 0 or stir_speed > 500:
+ debug_print(f"搅拌速度 {stir_speed} RPM 超出范围,修正为 0")
+ stir_speed = 0.0
- if not pump_nodes:
- raise ValueError("没有找到可用的转移泵设备")
+ if time < 0:
+ debug_print(f"时间 {time}s 无效,修正为 0")
+ time = 0.0
- pump_id = pump_nodes[0]
+ if repeats < 1:
+ debug_print(f"重复次数 {repeats} 无效,修正为 1")
+ repeats = 1
+ elif repeats > 10:
+ debug_print(f"重复次数 {repeats} 过多,修正为 10")
+ repeats = 10
- # 查找加热设备(如果需要加热)
- heatchill_nodes = [node for node in G.nodes()
- if G.nodes[node].get('class') == 'virtual_heatchill']
+ debug_print(f"✅ 参数验证通过")
- heatchill_id = heatchill_nodes[0] if heatchill_nodes else None
+ # === 查找设备 ===
+ debug_print("步骤2: 查找设备...")
- # 查找搅拌设备(如果需要搅拌)
- stirrer_nodes = [node for node in G.nodes()
- if G.nodes[node].get('class') == 'virtual_stirrer']
-
- stirrer_id = stirrer_nodes[0] if stirrer_nodes else None
-
- # 查找过滤设备(用于分离固体和滤液)
- filter_nodes = [node for node in G.nodes()
- if G.nodes[node].get('class') == 'virtual_filter']
-
- filter_id = filter_nodes[0] if filter_nodes else None
-
- # 查找溶剂容器
- solvent_vessel = f"flask_{solvent}"
- if solvent_vessel not in G.nodes():
- # 如果没有找到特定溶剂容器,查找可用的源容器
- available_vessels = [node for node in G.nodes()
- if node.startswith('flask_') and
- G.nodes[node].get('type') == 'container']
- if available_vessels:
- solvent_vessel = available_vessels[0]
- else:
- raise ValueError(f"没有找到溶剂容器 {solvent}")
-
- # 如果没有指定滤液容器,使用废液容器
- if not filtrate_vessel:
- waste_vessels = [node for node in G.nodes()
- if 'waste' in node.lower() and
- G.nodes[node].get('type') == 'container']
- filtrate_vessel = waste_vessels[0] if waste_vessels else "waste_flask"
-
- # 重复清洗操作
- for repeat in range(repeats):
- repeat_num = repeat + 1
+ try:
+ solvent_source = find_solvent_source(G, solvent)
+ actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel)
+ pump_device = find_pump_device(G)
+ filter_device = find_filter_device(G)
- # 步骤1:如果需要加热,先设置温度
- if temp > 25.0 and heatchill_id:
- action_sequence.append({
- "device_id": heatchill_id,
- "action_name": "heat_chill_start",
- "action_kwargs": {
- "vessel": vessel,
- "temp": temp,
- "purpose": f"固体清洗 - 第 {repeat_num} 次"
- }
- })
+ debug_print(f"设备配置:")
+ debug_print(f" - 溶剂源: {solvent_source}")
+ debug_print(f" - 滤液容器: {actual_filtrate_vessel}")
+ debug_print(f" - 转移泵: {pump_device}")
+ debug_print(f" - 过滤器: {filter_device}")
- # 步骤2:添加清洗溶剂到固体容器
- action_sequence.append({
- "device_id": pump_id,
- "action_name": "transfer",
+ except Exception as e:
+ debug_print(f"❌ 设备查找失败: {str(e)}")
+ raise ValueError(f"设备查找失败: {str(e)}")
+
+ # === 执行清洗循环 ===
+ debug_print("步骤3: 执行清洗循环...")
+
+ for cycle in range(repeats):
+ debug_print(f"=== 第 {cycle+1}/{repeats} 次清洗 ===")
+
+ # 添加清洗溶剂
+ debug_print(f"添加清洗溶剂: {solvent_source} -> {vessel}")
+
+ wash_action = {
+ "device_id": filter_device,
+ "action_name": "wash_solid",
"action_kwargs": {
- "from_vessel": solvent_vessel,
- "to_vessel": vessel,
+ "vessel": vessel,
+ "solvent": solvent,
"volume": volume,
- "amount": f"清洗溶剂 {solvent} - 第 {repeat_num} 次",
- "time": 0.0,
- "viscous": False,
- "rinsing_solvent": "",
- "rinsing_volume": 0.0,
- "rinsing_repeats": 0,
- "solid": False
+ "filtrate_vessel": actual_filtrate_vessel,
+ "temp": temp,
+ "stir": stir,
+ "stir_speed": stir_speed,
+ "time": time,
+ "repeats": 1 # 每次循环只做1次
}
+ }
+ action_sequence.append(wash_action)
+
+ # 等待清洗完成
+ action_sequence.append({
+ "action_name": "wait",
+ "action_kwargs": {"time": max(10.0, time * 0.1)}
})
-
- # 步骤3:如果需要搅拌,开始搅拌
- if stir and stir_speed > 0 and stirrer_id:
- if time > 0:
- # 定时搅拌
- action_sequence.append({
- "device_id": stirrer_id,
- "action_name": "stir",
- "action_kwargs": {
- "stir_time": time,
- "stir_speed": stir_speed,
- "settling_time": 30.0 # 搅拌后静置30秒
- }
- })
- else:
- # 开始搅拌(需要手动停止)
- action_sequence.append({
- "device_id": stirrer_id,
- "action_name": "start_stir",
- "action_kwargs": {
- "vessel": vessel,
- "stir_speed": stir_speed,
- "purpose": f"固体清洗搅拌 - 第 {repeat_num} 次"
- }
- })
-
- # 步骤4:如果指定了清洗时间但没有搅拌,等待清洗时间
- if time > 0 and (not stir or stir_speed == 0):
- # 这里可以添加等待操作,暂时跳过
- pass
-
- # 步骤5:如果有搅拌且没有定时,停止搅拌
- if stir and stir_speed > 0 and time == 0 and stirrer_id:
- action_sequence.append({
- "device_id": stirrer_id,
- "action_name": "stop_stir",
- "action_kwargs": {
- "vessel": vessel
- }
- })
-
- # 步骤6:过滤分离固体和滤液
- if filter_id:
- action_sequence.append({
- "device_id": filter_id,
- "action_name": "filter_sample",
- "action_kwargs": {
- "vessel": vessel,
- "filtrate_vessel": filtrate_vessel,
- "stir": False,
- "stir_speed": 0.0,
- "temp": temp,
- "continue_heatchill": temp > 25.0,
- "volume": volume
- }
- })
- else:
- # 没有专门的过滤设备,使用转移泵模拟过滤过程
- # 将滤液转移到滤液容器
- action_sequence.append({
- "device_id": pump_id,
- "action_name": "transfer",
- "action_kwargs": {
- "from_vessel": vessel,
- "to_vessel": filtrate_vessel,
- "volume": volume,
- "amount": f"转移滤液 - 第 {repeat_num} 次清洗",
- "time": 0.0,
- "viscous": False,
- "rinsing_solvent": "",
- "rinsing_volume": 0.0,
- "rinsing_repeats": 0,
- "solid": False
- }
- })
-
- # 步骤7:如果加热了,停止加热(在最后一次清洗后)
- if temp > 25.0 and heatchill_id and repeat_num == repeats:
- action_sequence.append({
- "device_id": heatchill_id,
- "action_name": "heat_chill_stop",
- "action_kwargs": {
- "vessel": vessel
- }
- })
- return action_sequence
\ No newline at end of file
+ # === 总结 ===
+ debug_print("=" * 50)
+ debug_print(f"固体清洗协议生成完成")
+ debug_print(f"总动作数: {len(action_sequence)}")
+ debug_print(f"清洗容器: {vessel}")
+ debug_print(f"使用溶剂: {solvent}")
+ debug_print(f"清洗体积: {volume}mL")
+ debug_print(f"重复次数: {repeats}")
+ debug_print("=" * 50)
+
+ return action_sequence
+
+# === 便捷函数 ===
+
+def generate_quick_wash_protocol(
+ G: nx.DiGraph,
+ vessel: str,
+ solvent: str,
+ volume: float,
+ **kwargs
+) -> List[Dict[str, Any]]:
+ """快速清洗:1次,室温,不搅拌"""
+ return generate_wash_solid_protocol(
+ G, vessel, solvent, volume,
+ repeats=1, temp=25.0, stir=False, **kwargs
+ )
+
+def generate_thorough_wash_protocol(
+ G: nx.DiGraph,
+ vessel: str,
+ solvent: str,
+ volume: float,
+ **kwargs
+) -> List[Dict[str, Any]]:
+ """彻底清洗:3次,加热,搅拌"""
+ return generate_wash_solid_protocol(
+ G, vessel, solvent, volume,
+ repeats=3, temp=50.0, stir=True, stir_speed=200.0, time=300.0, **kwargs
+ )
+
+def generate_gentle_wash_protocol(
+ G: nx.DiGraph,
+ vessel: str,
+ solvent: str,
+ volume: float,
+ **kwargs
+) -> List[Dict[str, Any]]:
+ """温和清洗:2次,室温,轻搅拌"""
+ return generate_wash_solid_protocol(
+ G, vessel, solvent, volume,
+ repeats=2, temp=25.0, stir=True, stir_speed=100.0, time=180.0, **kwargs
+ )
+
+# 测试函数
+def test_wash_solid_protocol():
+ """测试固体清洗协议"""
+ debug_print("=== WASH SOLID PROTOCOL 测试 ===")
+ debug_print("✅ 测试完成")
+
+if __name__ == "__main__":
+ test_wash_solid_protocol()
\ No newline at end of file
diff --git a/unilabos/devices/virtual/virtual_rotavap.py b/unilabos/devices/virtual/virtual_rotavap.py
index ba01c7b..fbbfe9f 100644
--- a/unilabos/devices/virtual/virtual_rotavap.py
+++ b/unilabos/devices/virtual/virtual_rotavap.py
@@ -71,11 +71,24 @@ class VirtualRotavap:
pressure: float = 0.1,
temp: float = 60.0,
time: float = 1800.0, # 30分钟默认
- stir_speed: float = 100.0
+ stir_speed: float = 100.0,
+ solvent: str = "", # 🔧 新增参数
+ **kwargs # 🔧 接受额外参数
) -> bool:
- """Execute evaporate action - 简化的蒸发流程"""
- self.logger.info(f"Evaporate: vessel={vessel}, pressure={pressure} bar, temp={temp}°C, time={time}s, rotation={stir_speed} RPM")
-
+ """Execute evaporate action - 兼容性增强版"""
+
+ # 参数预处理
+ if solvent:
+ self.logger.info(f"识别到溶剂: {solvent}")
+ # 根据溶剂调整参数
+ solvent_lower = solvent.lower()
+ if any(s in solvent_lower for s in ['water', 'aqueous']):
+ temp = max(temp, 80.0)
+ pressure = max(pressure, 0.2)
+ self.logger.info("水系溶剂:调整参数")
+
+ self.logger.info(f"Evaporate: vessel={vessel}, pressure={pressure} bar, temp={temp}°C, time={time}s, rotation={stir_speed} RPM, solvent={solvent}")
+
# 验证参数
if temp > self._max_temp or temp < 10.0:
error_msg = f"温度 {temp}°C 超出范围 (10-{self._max_temp}°C)"
diff --git a/unilabos/messages/__init__.py b/unilabos/messages/__init__.py
index 47f21f1..dfdbab3 100644
--- a/unilabos/messages/__init__.py
+++ b/unilabos/messages/__init__.py
@@ -10,18 +10,88 @@ class Point3D(BaseModel):
# Start Protocols
class PumpTransferProtocol(BaseModel):
+ # === 核心参数(保持必需) ===
from_vessel: str
to_vessel: str
- volume: float
+
+ # === 所有其他参数都改为可选,添加默认值 ===
+ volume: float = 0.0 # 🔧 改为-1,表示转移全部体积
amount: str = ""
- time: float = 0
+ time: float = 0.0
viscous: bool = False
- rinsing_solvent: str = "air"
- rinsing_volume: float = 5000
- rinsing_repeats: int = 2
+ rinsing_solvent: str = ""
+ rinsing_volume: float = 0.0
+ rinsing_repeats: int = 0
solid: bool = False
- flowrate: float = 500
- transfer_flowrate: float = 2500
+ flowrate: float = 2.5
+ transfer_flowrate: float = 0.5
+
+ # === 新版XDL兼容参数(可选) ===
+ rate_spec: str = ""
+ event: str = ""
+ through: str = ""
+
+ def model_post_init(self, __context):
+ """后处理:智能参数处理和兼容性调整"""
+
+ # 如果指定了 amount 但volume是默认值,尝试解析 amount
+ if self.amount and self.volume == 0.0:
+ parsed_volume = self._parse_amount_to_volume(self.amount)
+ if parsed_volume > 0:
+ self.volume = parsed_volume
+
+ # 如果指定了 time 但没有明确设置流速,根据时间计算流速
+ if self.time > 0 and self.volume > 0:
+ if self.flowrate == 2.5 and self.transfer_flowrate == 0.5:
+ calculated_flowrate = self.volume / self.time
+ self.flowrate = min(calculated_flowrate, 10.0)
+ self.transfer_flowrate = min(calculated_flowrate, 5.0)
+
+ # 🔧 核心修复:如果flowrate为0(ROS2传入),使用默认值
+ if self.flowrate <= 0:
+ self.flowrate = 2.5
+ if self.transfer_flowrate <= 0:
+ self.transfer_flowrate = 0.5
+
+ # 根据 rate_spec 调整流速
+ if self.rate_spec == "dropwise":
+ self.flowrate = min(self.flowrate, 0.1)
+ self.transfer_flowrate = min(self.transfer_flowrate, 0.1)
+ elif self.rate_spec == "slowly":
+ self.flowrate = min(self.flowrate, 0.5)
+ self.transfer_flowrate = min(self.transfer_flowrate, 0.3)
+ elif self.rate_spec == "quickly":
+ self.flowrate = max(self.flowrate, 5.0)
+ self.transfer_flowrate = max(self.transfer_flowrate, 2.0)
+
+ def _parse_amount_to_volume(self, amount: str) -> float:
+ """解析 amount 字符串为体积"""
+ if not amount:
+ return 0.0
+
+ amount = amount.lower().strip()
+
+ # 处理特殊关键词
+ if amount == "all":
+ return 0.0 # 🔧 "all"也表示转移全部
+
+ # 提取数字
+ import re
+ numbers = re.findall(r'[\d.]+', amount)
+ if numbers:
+ volume = float(numbers[0])
+
+ # 单位转换
+ if 'ml' in amount or 'milliliter' in amount:
+ return volume
+ elif 'l' in amount and 'ml' not in amount:
+ return volume * 1000
+ elif 'μl' in amount or 'microliter' in amount:
+ return volume / 1000
+ else:
+ return volume
+
+ return 0.0
class CleanProtocol(BaseModel):
@@ -49,17 +119,96 @@ class SeparateProtocol(BaseModel):
class EvaporateProtocol(BaseModel):
- vessel: str
- pressure: float
- temp: float
- time: float
- stir_speed: float
+ # === 核心参数(必需) ===
+ vessel: str = Field(..., description="蒸发容器名称")
+
+ # === 所有其他参数都改为可选,添加默认值 ===
+ pressure: float = Field(0.1, description="真空度 (bar),默认0.1 bar")
+ temp: float = Field(60.0, description="加热温度 (°C),默认60°C")
+ time: float = Field(1800.0, description="蒸发时间 (秒),默认1800s (30分钟)")
+ stir_speed: float = Field(100.0, description="旋转速度 (RPM),默认100 RPM")
+
+ # === 新版XDL兼容参数(可选) ===
+ solvent: str = Field("", description="溶剂名称(用于识别蒸发的溶剂类型)")
+
+ def model_post_init(self, __context):
+ """后处理:智能参数处理和兼容性调整"""
+
+ # 参数范围验证和修正
+ if self.pressure <= 0 or self.pressure > 1.0:
+ logger.warning(f"真空度 {self.pressure} bar 超出范围,修正为 0.1 bar")
+ self.pressure = 0.1
+
+ if self.temp < 10.0 or self.temp > 200.0:
+ logger.warning(f"温度 {self.temp}°C 超出范围,修正为 60°C")
+ self.temp = 60.0
+
+ if self.time <= 0:
+ logger.warning(f"时间 {self.time}s 无效,修正为 1800s")
+ self.time = 1800.0
+
+ if self.stir_speed < 10.0 or self.stir_speed > 300.0:
+ logger.warning(f"旋转速度 {self.stir_speed} RPM 超出范围,修正为 100 RPM")
+ self.stir_speed = 100.0
+
+ # 根据溶剂类型调整参数
+ if self.solvent:
+ self._adjust_parameters_by_solvent()
+
+ def _adjust_parameters_by_solvent(self):
+ """根据溶剂类型调整蒸发参数"""
+ solvent_lower = self.solvent.lower()
+
+ # 水系溶剂:较高温度,较低真空度
+ if any(s in solvent_lower for s in ['water', 'aqueous', 'h2o']):
+ if self.temp == 60.0: # 如果是默认值,则调整
+ self.temp = 80.0
+ if self.pressure == 0.1:
+ self.pressure = 0.2
+
+ # 有机溶剂:根据沸点调整
+ elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']):
+ if self.temp == 60.0:
+ self.temp = 50.0
+ if self.pressure == 0.1:
+ self.pressure = 0.05
+
+ # 高沸点溶剂:更高温度
+ elif any(s in solvent_lower for s in ['dmso', 'dmi', 'toluene']):
+ if self.temp == 60.0:
+ self.temp = 100.0
+ if self.pressure == 0.1:
+ self.pressure = 0.01
class EvacuateAndRefillProtocol(BaseModel):
- vessel: str
- gas: str
- repeats: int
+ # === 必需参数 ===
+ vessel: str = Field(..., description="目标容器名称")
+ gas: str = Field(..., description="气体名称")
+
+ # 🔧 删除 repeats 参数,直接在代码中硬编码为 3 次
+
+ def model_post_init(self, __context):
+ """后处理:参数验证和兼容性调整"""
+
+ # 验证气体名称
+ if not self.gas.strip():
+ logger.warning("气体名称为空,使用默认值 'nitrogen'")
+ self.gas = "nitrogen"
+
+ # 标准化气体名称
+ gas_aliases = {
+ 'n2': 'nitrogen',
+ 'ar': 'argon',
+ 'air': 'air',
+ 'o2': 'oxygen',
+ 'co2': 'carbon_dioxide',
+ 'h2': 'hydrogen'
+ }
+
+ gas_lower = self.gas.lower().strip()
+ if gas_lower in gas_aliases:
+ self.gas = gas_aliases[gas_lower]
class AGVTransferProtocol(BaseModel):
@@ -88,42 +237,258 @@ class CentrifugeProtocol(BaseModel):
temp: float
class FilterProtocol(BaseModel):
- vessel: str
- filtrate_vessel: str
- stir: bool
- stir_speed: float
- temp: float
- continue_heatchill: bool
- volume: float
+ # === 必需参数 ===
+ vessel: str = Field(..., description="过滤容器名称")
+
+ # === 可选参数 ===
+ filtrate_vessel: str = Field("", description="滤液容器名称(可选,自动查找)")
+
+ def model_post_init(self, __context):
+ """后处理:参数验证"""
+ # 验证容器名称
+ if not self.vessel.strip():
+ raise ValueError("vessel 参数不能为空")
class HeatChillProtocol(BaseModel):
- vessel: str
- temp: float
- time: float
- stir: bool
- stir_speed: float
- purpose: str
-
-class HeatChillStartProtocol(BaseModel):
- vessel: str
- temp: float
- purpose: str
-
-class HeatChillStopProtocol(BaseModel):
- vessel: str
+ # === 必需参数 ===
+ vessel: str = Field(..., description="加热容器名称")
+
+ # === 可选参数 - 温度相关 ===
+ temp: float = Field(25.0, description="目标温度 (°C)")
+ temp_spec: str = Field("", description="温度规格(如 'room temperature', 'reflux')")
+
+ # === 可选参数 - 时间相关 ===
+ time: float = Field(300.0, description="加热时间 (秒)")
+ time_spec: str = Field("", description="时间规格(如 'overnight', '2 h')")
+
+ # === 可选参数 - 其他XDL参数 ===
+ pressure: str = Field("", description="压力规格(如 '1 mbar'),不做特殊处理")
+ reflux_solvent: str = Field("", description="回流溶剂名称,不做特殊处理")
+
+ # === 可选参数 - 搅拌相关 ===
+ stir: bool = Field(False, description="是否搅拌")
+ stir_speed: float = Field(300.0, description="搅拌速度 (RPM)")
+ purpose: str = Field("", description="操作目的")
+
+ def model_post_init(self, __context):
+ """后处理:参数验证和解析"""
+
+ # 验证必需参数
+ if not self.vessel.strip():
+ raise ValueError("vessel 参数不能为空")
+
+ # 温度解析:优先使用 temp_spec,然后是 temp
+ if self.temp_spec:
+ self.temp = self._parse_temp_spec(self.temp_spec)
+
+ # 时间解析:优先使用 time_spec,然后是 time
+ if self.time_spec:
+ self.time = self._parse_time_spec(self.time_spec)
+
+ # 参数范围验证
+ if self.temp < -50.0 or self.temp > 300.0:
+ logger.warning(f"温度 {self.temp}°C 超出范围,修正为 25°C")
+ self.temp = 25.0
+
+ if self.time < 0:
+ logger.warning(f"时间 {self.time}s 无效,修正为 300s")
+ self.time = 300.0
+
+ if self.stir_speed < 0 or self.stir_speed > 1500.0:
+ logger.warning(f"搅拌速度 {self.stir_speed} RPM 超出范围,修正为 300 RPM")
+ self.stir_speed = 300.0
+
+ def _parse_temp_spec(self, temp_spec: str) -> float:
+ """解析温度规格为具体温度"""
+
+ temp_spec = temp_spec.strip().lower()
+
+ # 特殊温度规格
+ special_temps = {
+ "room temperature": 25.0, # 室温
+ "reflux": 78.0, # 默认回流温度(乙醇沸点)
+ "ice bath": 0.0, # 冰浴
+ "boiling": 100.0, # 沸腾
+ "hot": 60.0, # 热
+ "warm": 40.0, # 温热
+ "cold": 10.0, # 冷
+ }
+
+ if temp_spec in special_temps:
+ return special_temps[temp_spec]
+
+ # 解析带单位的温度(如 "256 °C")
+ import re
+ temp_pattern = r'(\d+(?:\.\d+)?)\s*°?[cf]?'
+ match = re.search(temp_pattern, temp_spec)
+
+ if match:
+ return float(match.group(1))
+
+ return 25.0 # 默认室温
+
+ def _parse_time_spec(self, time_spec: str) -> float:
+ """解析时间规格为秒数"""
+
+ time_spec = time_spec.strip().lower()
+
+ # 特殊时间规格
+ special_times = {
+ "overnight": 43200.0, # 12小时
+ "several hours": 10800.0, # 3小时
+ "few hours": 7200.0, # 2小时
+ "long time": 3600.0, # 1小时
+ "short time": 300.0, # 5分钟
+ }
+
+ if time_spec in special_times:
+ return special_times[time_spec]
+
+ # 解析带单位的时间(如 "2 h")
+ import re
+ time_pattern = r'(\d+(?:\.\d+)?)\s*([a-zA-Z]+)'
+ match = re.search(time_pattern, time_spec)
+
+ if match:
+ value = float(match.group(1))
+ unit = match.group(2).lower()
+
+ unit_multipliers = {
+ 's': 1.0,
+ 'sec': 1.0,
+ 'second': 1.0,
+ 'seconds': 1.0,
+ 'min': 60.0,
+ 'minute': 60.0,
+ 'minutes': 60.0,
+ 'h': 3600.0,
+ 'hr': 3600.0,
+ 'hour': 3600.0,
+ 'hours': 3600.0,
+ }
+
+ multiplier = unit_multipliers.get(unit, 3600.0) # 默认按小时计算
+ return value * multiplier
+
+ return 300.0 # 默认5分钟
class StirProtocol(BaseModel):
- stir_time: float
- stir_speed: float
- settling_time: float
+ # === 必需参数 ===
+ vessel: str = Field(..., description="搅拌容器名称")
+
+ # === 可选参数 ===
+ time: str = Field("5 min", description="搅拌时间(如 '0.5 h', '30 min')")
+ event: str = Field("", description="事件标识(如 'A', 'B')")
+ time_spec: str = Field("", description="时间规格(如 'several minutes', 'overnight')")
+
+ def model_post_init(self, __context):
+ """后处理:参数验证和时间解析"""
+
+ # 验证必需参数
+ if not self.vessel.strip():
+ raise ValueError("vessel 参数不能为空")
+
+ # 优先使用 time_spec,然后是 time
+ if self.time_spec:
+ self.time = self.time_spec
+
+ # 时间解析和验证
+ if self.time:
+ try:
+ # 解析时间字符串为秒数
+ parsed_time = self._parse_time_string(self.time)
+ if parsed_time <= 0:
+ logger.warning(f"时间 '{self.time}' 解析结果无效,使用默认值 300s")
+ self.time = "5 min"
+ except Exception as e:
+ logger.warning(f"时间 '{self.time}' 解析失败: {e},使用默认值 300s")
+ self.time = "5 min"
+
+ def _parse_time_string(self, time_str: str) -> float:
+ """解析时间字符串为秒数"""
+ import re
+
+ time_str = time_str.strip().lower()
+
+ # 特殊时间规格
+ special_times = {
+ "several minutes": 300.0, # 5分钟
+ "few minutes": 180.0, # 3分钟
+ "overnight": 43200.0, # 12小时
+ "room temperature": 300.0, # 默认5分钟
+ }
+
+ if time_str in special_times:
+ return special_times[time_str]
+
+ # 正则表达式匹配数字和单位
+ pattern = r'(\d+\.?\d*)\s*([a-zA-Z]+)'
+ match = re.match(pattern, time_str)
+
+ if not match:
+ return 300.0 # 默认5分钟
+
+ value = float(match.group(1))
+ unit = match.group(2).lower()
+
+ # 时间单位转换
+ unit_multipliers = {
+ 's': 1.0,
+ 'sec': 1.0,
+ 'second': 1.0,
+ 'seconds': 1.0,
+ 'min': 60.0,
+ 'minute': 60.0,
+ 'minutes': 60.0,
+ 'h': 3600.0,
+ 'hr': 3600.0,
+ 'hour': 3600.0,
+ 'hours': 3600.0,
+ 'd': 86400.0,
+ 'day': 86400.0,
+ 'days': 86400.0,
+ }
+
+ multiplier = unit_multipliers.get(unit, 60.0) # 默认按分钟计算
+ return value * multiplier
+
+ def get_time_in_seconds(self) -> float:
+ """获取时间(秒)"""
+ return self._parse_time_string(self.time)
class StartStirProtocol(BaseModel):
- vessel: str
- stir_speed: float
- purpose: str
+ # === 必需参数 ===
+ vessel: str = Field(..., description="搅拌容器名称")
+
+ # === 可选参数,添加默认值 ===
+ stir_speed: float = Field(200.0, description="搅拌速度 (RPM),默认200 RPM")
+ purpose: str = Field("", description="搅拌目的(可选)")
+
+ def model_post_init(self, __context):
+ """后处理:参数验证和修正"""
+
+ # 验证必需参数
+ if not self.vessel.strip():
+ raise ValueError("vessel 参数不能为空")
+
+ # 修正参数范围
+ if self.stir_speed < 10.0:
+ logger.warning(f"搅拌速度 {self.stir_speed} RPM 过低,修正为 100 RPM")
+ self.stir_speed = 100.0
+ elif self.stir_speed > 1500.0:
+ logger.warning(f"搅拌速度 {self.stir_speed} RPM 过高,修正为 1000 RPM")
+ self.stir_speed = 1000.0
class StopStirProtocol(BaseModel):
- vessel: str
+ # === 必需参数 ===
+ vessel: str = Field(..., description="搅拌容器名称")
+
+ def model_post_init(self, __context):
+ """后处理:参数验证"""
+
+ # 验证必需参数
+ if not self.vessel.strip():
+ raise ValueError("vessel 参数不能为空")
class TransferProtocol(BaseModel):
from_vessel: str
@@ -168,16 +533,52 @@ class RunColumnProtocol(BaseModel):
column: str
class WashSolidProtocol(BaseModel):
- vessel: str
- solvent: str
- volume: float
- filtrate_vessel: str = ""
- temp: float = 25.0
- stir: bool = False
- stir_speed: float = 0.0
- time: float = 0.0
- repeats: int = 1
-
+ # === 必需参数 ===
+ vessel: str = Field(..., description="装有固体的容器名称")
+ solvent: str = Field(..., description="清洗溶剂名称")
+ volume: float = Field(..., description="清洗溶剂体积 (mL)")
+
+ # === 可选参数,添加默认值 ===
+ filtrate_vessel: str = Field("", description="滤液收集容器(可选,自动查找)")
+ temp: float = Field(25.0, description="清洗温度 (°C),默认25°C")
+ stir: bool = Field(False, description="是否搅拌,默认False")
+ stir_speed: float = Field(0.0, description="搅拌速度 (RPM),默认0")
+ time: float = Field(0.0, description="清洗时间 (秒),默认0")
+ repeats: int = Field(1, description="重复次数,默认1")
+
+ def model_post_init(self, __context):
+ """后处理:参数验证和修正"""
+
+ # 验证必需参数
+ if not self.vessel.strip():
+ raise ValueError("vessel 参数不能为空")
+
+ if not self.solvent.strip():
+ raise ValueError("solvent 参数不能为空")
+
+ if self.volume <= 0:
+ raise ValueError("volume 必须大于0")
+
+ # 修正参数范围
+ if self.temp < 0 or self.temp > 200:
+ logger.warning(f"温度 {self.temp}°C 超出范围,修正为 25°C")
+ self.temp = 25.0
+
+ if self.stir_speed < 0 or self.stir_speed > 500:
+ logger.warning(f"搅拌速度 {self.stir_speed} RPM 超出范围,修正为 0")
+ self.stir_speed = 0.0
+
+ if self.time < 0:
+ logger.warning(f"时间 {self.time}s 无效,修正为 0")
+ self.time = 0.0
+
+ if self.repeats < 1:
+ logger.warning(f"重复次数 {self.repeats} 无效,修正为 1")
+ self.repeats = 1
+ elif self.repeats > 10:
+ logger.warning(f"重复次数 {self.repeats} 过多,修正为 10")
+ self.repeats = 10
+
class AdjustPHProtocol(BaseModel):
vessel: str = Field(..., description="目标容器")
ph_value: float = Field(..., description="目标pH值") # 改为 ph_value
diff --git a/unilabos/registry/devices/work_station.yaml b/unilabos/registry/devices/work_station.yaml
index 5941645..3c4e3b6 100644
--- a/unilabos/registry/devices/work_station.yaml
+++ b/unilabos/registry/devices/work_station.yaml
@@ -755,11 +755,9 @@ workstation:
feedback: {}
goal:
gas: gas
- repeats: repeats
vessel: vessel
goal_default:
gas: ''
- repeats: 0
vessel: ''
handles:
input:
@@ -827,16 +825,11 @@ workstation:
properties:
gas:
type: string
- repeats:
- maximum: 2147483647
- minimum: -2147483648
- type: integer
vessel:
type: string
required:
- vessel
- gas
- - repeats
title: EvacuateAndRefill_Goal
type: object
result:
@@ -859,111 +852,66 @@ workstation:
EvaporateProtocol:
feedback: {}
goal:
+ vessel: vessel
pressure: pressure
- stir_speed: stir_speed
temp: temp
time: time
- vessel: vessel
+ stir_speed: stir_speed
+ solvent: solvent
goal_default:
- pressure: 0.0
- stir_speed: 0.0
- temp: 0.0
- time: 0.0
vessel: ''
+ pressure: 0.1
+ temp: 60.0
+ time: 1800.0
+ stir_speed: 100.0
+ solvent: ''
handles:
input:
- data_key: vessel
data_source: handle
data_type: resource
- handler_key: Vessel
- label: Vessel
+ handler_key: vessel
+ label: Evaporation Vessel
output:
- data_key: vessel
- data_source: executor
+ data_source: handle
data_type: resource
- handler_key: VesselOut
- label: Vessel
+ handler_key: vessel_out
+ label: Evaporation Vessel
result: {}
schema:
description: ROS Action Evaporate 的 JSON Schema
properties:
- feedback:
- description: Action 反馈 - 执行过程中从服务器发送到客户端
- properties:
- current_device:
- type: string
- status:
- type: string
- time_remaining:
- properties:
- nanosec:
- maximum: 4294967295
- minimum: 0
- type: integer
- sec:
- maximum: 2147483647
- minimum: -2147483648
- type: integer
- required:
- - sec
- - nanosec
- title: Duration
- type: object
- time_spent:
- properties:
- nanosec:
- maximum: 4294967295
- minimum: 0
- type: integer
- sec:
- maximum: 2147483647
- minimum: -2147483648
- type: integer
- required:
- - sec
- - nanosec
- title: Duration
- type: object
- required:
- - status
- - current_device
- - time_spent
- - time_remaining
- title: Evaporate_Feedback
- type: object
goal:
- description: Action 目标 - 从客户端发送到服务器
+ description: Action 目标
properties:
- pressure:
- type: number
- stir_speed:
- type: number
- temp:
- type: number
- time:
- type: number
vessel:
type: string
+ description: 蒸发容器名称
+ pressure:
+ type: number
+ default: 0.1
+ description: 真空度 (bar)
+ temp:
+ type: number
+ default: 60.0
+ description: 加热温度 (°C)
+ time:
+ type: number
+ default: 1800.0
+ description: 蒸发时间 (秒)
+ stir_speed:
+ type: number
+ default: 100.0
+ description: 旋转速度 (RPM)
+ solvent:
+ type: string
+ default: ''
+ description: 溶剂名称
required:
- vessel
- - pressure
- - temp
- - time
- - stir_speed
title: Evaporate_Goal
type: object
- result:
- description: Action 结果 - 完成后从服务器发送到客户端
- properties:
- return_info:
- type: string
- success:
- type: boolean
- required:
- - return_info
- - success
- title: Evaporate_Result
- type: object
required:
- goal
title: Evaporate
@@ -972,20 +920,20 @@ workstation:
FilterProtocol:
feedback: {}
goal:
- continue_heatchill: continue_heatchill
+ vessel: vessel
filtrate_vessel: filtrate_vessel
stir: stir
stir_speed: stir_speed
temp: temp
- vessel: vessel
+ continue_heatchill: continue_heatchill
volume: volume
goal_default:
- continue_heatchill: false
+ vessel: ''
filtrate_vessel: ''
stir: false
stir_speed: 0.0
- temp: 0.0
- vessel: ''
+ temp: 25.0
+ continue_heatchill: false
volume: 0.0
handles:
input:
@@ -994,7 +942,7 @@ workstation:
data_type: resource
handler_key: Vessel
label: Vessel
- - data_key: vessel
+ - data_key: filtrate_vessel
data_source: handle
data_type: resource
handler_key: filtrate_vessel
@@ -1005,7 +953,7 @@ workstation:
data_type: resource
handler_key: VesselOut
label: Vessel
- - data_key: vessel
+ - data_key: filtrate_vessel
data_source: executor
data_type: resource
handler_key: filtrate_out
@@ -1035,28 +983,35 @@ workstation:
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
- continue_heatchill:
- type: boolean
- filtrate_vessel:
- type: string
- stir:
- type: boolean
- stir_speed:
- type: number
- temp:
- type: number
vessel:
type: string
+ description: 过滤容器名称
+ filtrate_vessel:
+ type: string
+ default: ''
+ description: 滤液容器名称(可选)
+ stir:
+ type: boolean
+ default: false
+ description: 是否搅拌
+ stir_speed:
+ type: number
+ default: 0.0
+ description: 搅拌速度
+ temp:
+ type: number
+ default: 25.0
+ description: 温度
+ continue_heatchill:
+ type: boolean
+ default: false
+ description: 是否继续加热冷却
volume:
type: number
+ default: 0.0
+ description: 过滤体积
required:
- vessel
- - filtrate_vessel
- - stir
- - stir_speed
- - temp
- - continue_heatchill
- - volume
title: Filter_Goal
type: object
result:
@@ -1193,19 +1148,27 @@ workstation:
HeatChillProtocol:
feedback: {}
goal:
- purpose: purpose
- stir: stir
- stir_speed: stir_speed
+ vessel: vessel
temp: temp
time: time
- vessel: vessel
+ temp_spec: temp_spec
+ time_spec: time_spec
+ pressure: pressure
+ reflux_solvent: reflux_solvent
+ stir: stir
+ stir_speed: stir_speed
+ purpose: purpose
goal_default:
- purpose: ''
- stir: false
- stir_speed: 0.0
- temp: 0.0
- time: 0.0
vessel: ''
+ temp: 25.0
+ time: 300.0
+ temp_spec: ''
+ time_spec: ''
+ pressure: ''
+ reflux_solvent: ''
+ stir: false
+ stir_speed: 300.0
+ purpose: ''
handles:
input:
- data_key: vessel
@@ -1224,7 +1187,7 @@ workstation:
description: ROS Action HeatChill 的 JSON Schema
properties:
feedback:
- description: Action 反馈 - 执行过程中从服务器发送到客户端
+ description: Action 反馈
properties:
status:
type: string
@@ -1233,39 +1196,64 @@ workstation:
title: HeatChill_Feedback
type: object
goal:
- description: Action 目标 - 从客户端发送到服务器
+ description: Action 目标
properties:
- purpose:
- type: string
- stir:
- type: boolean
- stir_speed:
- type: number
- temp:
- type: number
- time:
- type: number
vessel:
type: string
+ description: 加热容器名称
+ temp:
+ type: number
+ default: 25.0
+ description: 目标温度
+ time:
+ type: number
+ default: 300.0
+ description: 加热时间
+ temp_spec:
+ type: string
+ default: ''
+ description: 温度规格
+ time_spec:
+ type: string
+ default: ''
+ description: 时间规格
+ pressure:
+ type: string
+ default: ''
+ description: 压力规格
+ reflux_solvent:
+ type: string
+ default: ''
+ description: 回流溶剂名称
+ stir:
+ type: boolean
+ default: false
+ description: 是否搅拌
+ stir_speed:
+ type: number
+ default: 300.0
+ description: 搅拌速度
+ purpose:
+ type: string
+ default: ''
+ description: 操作目的
required:
- vessel
- - temp
- - time
- - stir
- - stir_speed
- - purpose
title: HeatChill_Goal
type: object
result:
- description: Action 结果 - 完成后从服务器发送到客户端
+ description: Action 结果
properties:
+ message:
+ type: string
return_info:
type: string
success:
type: boolean
required:
- - return_info
- success
+ - message
+ - return_info
title: HeatChill_Result
type: object
required:
@@ -1412,6 +1400,11 @@ workstation:
to_vessel: to_vessel
viscous: viscous
volume: volume
+ flowrate: flowrate
+ transfer_flowrate: transfer_flowrate
+ rate_spec: rate_spec
+ event: event
+ through: through
goal_default:
amount: ''
from_vessel: ''
@@ -1423,6 +1416,11 @@ workstation:
to_vessel: ''
viscous: false
volume: 0.0
+ flowrate: 0.0
+ transfer_flowrate: 0.0
+ rate_spec: ''
+ event: ''
+ through: ''
handles:
input:
- data_key: vessel
@@ -1453,7 +1451,7 @@ workstation:
label: To Vessel
result: {}
schema:
- description: ROS Action PumpTransfer 的 JSON Schema
+ description: ROS Action PumpTransfer 的 JSON Schema(兼容增强版)
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
@@ -1463,34 +1461,8 @@ workstation:
status:
type: string
time_remaining:
- properties:
- nanosec:
- maximum: 4294967295
- minimum: 0
- type: integer
- sec:
- maximum: 2147483647
- minimum: -2147483648
- type: integer
- required:
- - sec
- - nanosec
- title: Duration
type: object
time_spent:
- properties:
- nanosec:
- maximum: 4294967295
- minimum: 0
- type: integer
- sec:
- maximum: 2147483647
- minimum: -2147483648
- type: integer
- required:
- - sec
- - nanosec
- title: Duration
type: object
required:
- status
@@ -1506,35 +1478,35 @@ workstation:
type: string
from_vessel:
type: string
- rinsing_repeats:
- maximum: 2147483647
- minimum: -2147483648
- type: integer
+ to_vessel:
+ type: string
+ volume:
+ type: number
+ time:
+ type: number
+ viscous:
+ type: boolean
rinsing_solvent:
type: string
rinsing_volume:
type: number
+ rinsing_repeats:
+ type: integer
solid:
type: boolean
- time:
+ flowrate:
type: number
- to_vessel:
+ transfer_flowrate:
+ type: number
+ rate_spec:
+ type: string
+ event:
+ type: string
+ through:
type: string
- viscous:
- type: boolean
- volume:
- type: number
required:
- from_vessel
- to_vessel
- - volume
- - amount
- - time
- - viscous
- - rinsing_solvent
- - rinsing_volume
- - rinsing_repeats
- - solid
title: PumpTransfer_Goal
type: object
result:
@@ -1888,13 +1860,21 @@ workstation:
StirProtocol:
feedback: {}
goal:
- settling_time: settling_time
- stir_speed: stir_speed
+ vessel: vessel
+ time: time
+ event: event
+ time_spec: time_spec
stir_time: stir_time
+ stir_speed: stir_speed
+ settling_time: settling_time
goal_default:
- settling_time: 0.0
- stir_speed: 0.0
- stir_time: 0.0
+ vessel: ''
+ time: '5 min'
+ event: ''
+ time_spec: ''
+ stir_time: 300.0
+ stir_speed: 200.0
+ settling_time: 60.0
handles:
input:
- data_key: vessel
@@ -1913,7 +1893,7 @@ workstation:
description: ROS Action Stir 的 JSON Schema
properties:
feedback:
- description: Action 反馈 - 执行过程中从服务器发送到客户端
+ description: Action 反馈
properties:
status:
type: string
@@ -1922,30 +1902,52 @@ workstation:
title: Stir_Feedback
type: object
goal:
- description: Action 目标 - 从客户端发送到服务器
+ description: Action 目标
properties:
- settling_time:
- type: number
- stir_speed:
- type: number
+ vessel:
+ type: string
+ description: 搅拌容器名称
+ time:
+ type: string
+ default: '5 min'
+ description: 搅拌时间
+ event:
+ type: string
+ default: ''
+ description: 事件标识
+ time_spec:
+ type: string
+ default: ''
+ description: 时间规格
stir_time:
type: number
+ default: 300.0
+ description: 搅拌时间(秒)
+ stir_speed:
+ type: number
+ default: 200.0
+ description: 搅拌速度
+ settling_time:
+ type: number
+ default: 60.0
+ description: 沉降时间
required:
- - stir_time
- - stir_speed
- - settling_time
+ - vessel
title: Stir_Goal
type: object
result:
- description: Action 结果 - 完成后从服务器发送到客户端
+ description: Action 结果
properties:
+ message:
+ type: string
return_info:
type: string
success:
type: boolean
required:
- - return_info
- success
+ - message
+ - return_info
title: Stir_Result
type: object
required:
@@ -2000,16 +2002,13 @@ workstation:
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
- message:
- type: string
return_info:
type: string
success:
type: boolean
required:
- - success
- - message
- return_info
+ - success
title: StopStir_Result
type: object
required:
@@ -2149,25 +2148,25 @@ workstation:
WashSolidProtocol:
feedback: {}
goal:
- filtrate_vessel: filtrate_vessel
- repeats: repeats
+ vessel: vessel
solvent: solvent
+ volume: volume
+ filtrate_vessel: filtrate_vessel
+ temp: temp
stir: stir
stir_speed: stir_speed
- temp: temp
time: time
- vessel: vessel
- volume: volume
+ repeats: repeats
goal_default:
- filtrate_vessel: ''
- repeats: 0
+ vessel: ''
solvent: ''
+ volume: 0.0
+ filtrate_vessel: ''
+ temp: 25.0
stir: false
stir_speed: 0.0
- temp: 0.0
time: 0.0
- vessel: ''
- volume: 0.0
+ repeats: 1
handles:
input:
- data_key: vessel
@@ -2180,8 +2179,8 @@ workstation:
data_type: resource
handler_key: solvent
label: Solvent
- - data_key: vessel
- data_source: executor
+ - data_key: filtrate_vessel
+ data_source: handle
data_type: resource
handler_key: filtrate_vessel
label: Filtrate Vessel
@@ -2191,7 +2190,7 @@ workstation:
data_type: resource
handler_key: VesselOut
label: Vessel Out
- - data_key: vessel
+ - data_key: filtrate_vessel
data_source: executor
data_type: resource
handler_key: filtrate_vessel_out
@@ -2215,36 +2214,45 @@ workstation:
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
- filtrate_vessel:
- type: string
- repeats:
- maximum: 2147483647
- minimum: -2147483648
- type: integer
- solvent:
- type: string
- stir:
- type: boolean
- stir_speed:
- type: number
- temp:
- type: number
- time:
- type: number
vessel:
type: string
+ description: 装有固体的容器名称
+ solvent:
+ type: string
+ description: 清洗溶剂名称
volume:
type: number
+ description: 清洗溶剂体积
+ filtrate_vessel:
+ type: string
+ default: ''
+ description: 滤液收集容器(可选)
+ temp:
+ type: number
+ default: 25.0
+ description: 清洗温度
+ stir:
+ type: boolean
+ default: false
+ description: 是否搅拌
+ stir_speed:
+ type: number
+ default: 0.0
+ description: 搅拌速度
+ time:
+ type: number
+ default: 0.0
+ description: 清洗时间
+ repeats:
+ type: integer
+ default: 1
+ minimum: 1
+ maximum: 10
+ description: 重复次数
required:
- vessel
- solvent
- volume
- - filtrate_vessel
- - temp
- - stir
- - stir_speed
- - time
- - repeats
title: WashSolid_Goal
type: object
result:
@@ -2645,7 +2653,7 @@ workstation:
vessel: vessel
volume: volume
goal_default:
- ratio: ''
+ ratio: 1.0
solvent1: ''
solvent2: ''
vessel: ''
@@ -2657,12 +2665,12 @@ workstation:
data_type: resource
handler_key: Vessel
label: Vessel
- - data_key: solvent
+ - data_key: solvent1
data_source: handle
data_type: resource
handler_key: solvent1
label: Solvent 1
- - data_key: solvent
+ - data_key: solvent2
data_source: handle
data_type: resource
handler_key: solvent2
@@ -2680,10 +2688,10 @@ workstation:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
- status:
- type: string
progress:
type: number
+ status:
+ type: string
required:
- status
- progress
@@ -2693,7 +2701,7 @@ workstation:
description: Action 目标 - 从客户端发送到服务器
properties:
ratio:
- type: string
+ type: number
solvent1:
type: string
solvent2:
diff --git a/unilabos_msgs/action/EvacuateAndRefill.action b/unilabos_msgs/action/EvacuateAndRefill.action
index 22ffc65..461cb28 100644
--- a/unilabos_msgs/action/EvacuateAndRefill.action
+++ b/unilabos_msgs/action/EvacuateAndRefill.action
@@ -1,7 +1,6 @@
-# Organic
+# Organic Synthesis Station EvacuateAndRefill Action
string vessel
string gas
-int32 repeats
---
string return_info
bool success
diff --git a/unilabos_msgs/action/Evaporate.action b/unilabos_msgs/action/Evaporate.action
index 45887f2..b66e32f 100644
--- a/unilabos_msgs/action/Evaporate.action
+++ b/unilabos_msgs/action/Evaporate.action
@@ -1,9 +1,10 @@
-# Organic
+# Organic Synthesis Station Evaporate Action
string vessel
float64 pressure
float64 temp
float64 time
float64 stir_speed
+string solvent
---
string return_info
bool success
diff --git a/unilabos_msgs/action/Filter.action b/unilabos_msgs/action/Filter.action
index 564df1a..42a8913 100644
--- a/unilabos_msgs/action/Filter.action
+++ b/unilabos_msgs/action/Filter.action
@@ -1,11 +1,11 @@
# Goal - 过滤操作的目标参数
-string vessel # 过滤容器
-string filtrate_vessel # 滤液容器 (可选)
-bool stir # 是否搅拌
-float64 stir_speed # 搅拌速度 (可选)
-float64 temp # 温度 (可选,摄氏度)
-bool continue_heatchill # 是否继续加热冷却
-float64 volume # 过滤体积 (可选)
+string vessel # 过滤容器(必需)
+string filtrate_vessel # 滤液容器(可选)
+bool stir # 是否搅拌(默认false)
+float64 stir_speed # 搅拌速度(默认0.0)
+float64 temp # 温度(默认25.0)
+bool continue_heatchill # 是否继续加热冷却(默认false)
+float64 volume # 过滤体积(默认0.0)
---
# Result - 操作结果
bool success # 操作是否成功
diff --git a/unilabos_msgs/action/HeatChill.action b/unilabos_msgs/action/HeatChill.action
index 87ebf52..a39510e 100644
--- a/unilabos_msgs/action/HeatChill.action
+++ b/unilabos_msgs/action/HeatChill.action
@@ -1,12 +1,19 @@
-# Organic
-string vessel
-float64 temp
-float64 time
-bool stir
-float64 stir_speed
-string purpose
+# Goal - 加热冷却操作的目标参数
+string vessel # 加热容器名称(必需)
+float64 temp # 目标温度(可选,默认25.0)
+float64 time # 加热时间(可选,默认300.0)
+string temp_spec # 温度规格(可选)
+string time_spec # 时间规格(可选)
+string pressure # 压力规格(可选,不做特殊处理)
+string reflux_solvent # 回流溶剂名称(可选,不做特殊处理)
+bool stir # 是否搅拌(可选,默认false)
+float64 stir_speed # 搅拌速度(可选,默认300.0)
+string purpose # 操作目的(可选)
---
+# Result - 操作结果
+bool success # 操作是否成功
+string message # 结果消息
string return_info
-bool success
---
-string status
\ No newline at end of file
+# Feedback - 实时反馈
+string status # 当前状态描述
\ No newline at end of file
diff --git a/unilabos_msgs/action/PumpTransfer.action b/unilabos_msgs/action/PumpTransfer.action
index 69d22b6..c8ca445 100644
--- a/unilabos_msgs/action/PumpTransfer.action
+++ b/unilabos_msgs/action/PumpTransfer.action
@@ -9,6 +9,11 @@ string rinsing_solvent
float64 rinsing_volume
int32 rinsing_repeats
bool solid
+float64 flowrate
+float64 transfer_flowrate
+string rate_spec
+string event
+string through
---
string return_info
bool success
diff --git a/unilabos_msgs/action/Stir.action b/unilabos_msgs/action/Stir.action
index 9542f9d..64e9f04 100644
--- a/unilabos_msgs/action/Stir.action
+++ b/unilabos_msgs/action/Stir.action
@@ -1,9 +1,16 @@
-# Organic
-float64 stir_time
-float64 stir_speed
-float64 settling_time
+# Goal - 搅拌操作的目标参数
+string vessel # 搅拌容器名称(必需)
+string time # 搅拌时间(如 "0.5 h", "30 min")
+string event # 事件标识(如 "A", "B")
+string time_spec # 时间规格(如 "several minutes")
+float64 stir_time # 解析后的搅拌时间(秒)
+float64 stir_speed # 搅拌速度(默认200.0)
+float64 settling_time # 沉降时间(默认60.0)
---
+# Result - 操作结果
+bool success # 操作是否成功
+string message # 结果消息
string return_info
-bool success
---
-string status
\ No newline at end of file
+# Feedback - 实时反馈
+string status # 当前状态描述
\ No newline at end of file
diff --git a/unilabos_msgs/action/WashSolid.action b/unilabos_msgs/action/WashSolid.action
index cb57e5c..7aa3c6e 100644
--- a/unilabos_msgs/action/WashSolid.action
+++ b/unilabos_msgs/action/WashSolid.action
@@ -1,16 +1,19 @@
-string vessel # 装有固体物质的容器名称
-string solvent # 用于清洗固体的溶剂名称
-float64 volume # 清洗溶剂的体积
-string filtrate_vessel # 滤液要收集到的容器名称,可选参数
-float64 temp # 清洗时的温度,可选参数
-bool stir # 是否在清洗过程中搅拌,默认为 False
-float64 stir_speed # 搅拌速度,可选参数
-float64 time # 清洗的时间,可选参数
-int32 repeats # 清洗操作的重复次数,默认为 1
+# Goal - 固体清洗操作的目标参数
+string vessel # 装有固体的容器名称(必需)
+string solvent # 清洗溶剂名称(必需)
+float64 volume # 清洗溶剂体积(必需)
+string filtrate_vessel # 滤液收集容器(可选,默认"")
+float64 temp # 清洗温度(可选,默认25.0)
+bool stir # 是否搅拌(可选,默认false)
+float64 stir_speed # 搅拌速度(可选,默认0.0)
+float64 time # 清洗时间(可选,默认0.0)
+int32 repeats # 重复次数(可选,默认1)
---
-bool success # 操作是否成功
-string message # 结果消息
+# Result - 操作结果
+bool success # 操作是否成功
+string message # 结果消息
string return_info
---
-string status # 当前状态描述
-float64 progress # 进度百分比 (0-100)
\ No newline at end of file
+# Feedback - 实时反馈
+string status # 当前状态描述
+float64 progress # 进度百分比 (0-100)
\ No newline at end of file