mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-05 05:45:10 +00:00
修复了部分的protocol因为XDL更新导致的问题
但是pumptransfer,add,dissolve,separate还没修,后续还需要写virtual固体加料器
This commit is contained in:
@@ -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():
|
||||
"""测试抽真空充气协议"""
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
if __name__ == "__main__":
|
||||
test_filter_protocol()
|
||||
@@ -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()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
"""温和搅拌:低速长时间"""
|
||||
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()
|
||||
@@ -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
|
||||
# === 总结 ===
|
||||
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()
|
||||
@@ -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)"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user