修复了部分的protocol因为XDL更新导致的问题

但是pumptransfer,add,dissolve,separate还没修,后续还需要写virtual固体加料器
This commit is contained in:
KCFeng425
2025-07-06 19:21:53 +08:00
parent bef44b2293
commit 4c6e437eb1
18 changed files with 3034 additions and 1606 deletions

View File

@@ -51,9 +51,9 @@ class PumpTransferProtocol(BaseModel):
time: float = 0 time: float = 0
viscous: bool = False viscous: bool = False
rinsing_solvent: str = "air" <Transfer from_vessel="main_reactor" to_vessel="rotavap"/> rinsing_solvent: str = "air" <Transfer from_vessel="main_reactor" to_vessel="rotavap"/>
rinsing_volume: float = 5000 rinsing_volume: float = 5000 <Transfer event="A" from_vessel="reactor" rate_spec="dropwise" to_vessel="main_reactor"/>
rinsing_repeats: int = 2 rinsing_repeats: int = 2 <Transfer from_vessel="separator" through="cartridge" to_vessel="rotavap"/>
solid: bool = False solid: bool = False 添加了缺失的参数但是体积为0以及打印日志的问题修不好
flowrate: float = 500 flowrate: float = 500
transfer_flowrate: float = 2500 transfer_flowrate: float = 2500
@@ -66,9 +66,9 @@ class SeparateProtocol(BaseModel):
waste_phase_to_vessel: str waste_phase_to_vessel: str
solvent: str solvent: str
solvent_volume: float <Separate product_phase="bottom" purpose="wash" solvent="water" vessel="separator" volume="?"/> solvent_volume: float <Separate product_phase="bottom" purpose="wash" solvent="water" vessel="separator" volume="?"/>
through: str through: str <Separate product_phase="top" purpose="separate" vessel="separator"/>
repeats: int repeats: int <Separate product_phase="bottom" purpose="extract" repeats="3" solvent="CH2Cl2" vessel="separator" volume="?"/>
stir_time: float stir_time: float<Separate product_phase="top" product_vessel="flask" purpose="separate" vessel="separator" waste_vessel="separator"/>
stir_speed: float stir_speed: float
settling_time: float settling_time: float
@@ -77,14 +77,14 @@ class EvaporateProtocol(BaseModel):
vessel: str vessel: str
pressure: float pressure: float
temp: float <Evaporate solvent="ethanol" vessel="rotavap"/> temp: float <Evaporate solvent="ethanol" vessel="rotavap"/>
time: float time: float 加完了
stir_speed: float stir_speed: float
class EvacuateAndRefillProtocol(BaseModel): class EvacuateAndRefillProtocol(BaseModel):
vessel: str vessel: str
gas: str <EvacuateAndRefill gas="nitrogen" vessel="main_reactor"/> gas: str <EvacuateAndRefill gas="nitrogen" vessel="main_reactor"/>
repeats: int repeats: int 处理完了
class AddProtocol(BaseModel): class AddProtocol(BaseModel):
vessel: str vessel: str
@@ -95,21 +95,27 @@ class AddProtocol(BaseModel):
time: float time: float
stir: bool stir: bool
stir_speed: float <Add reagent="ethanol" vessel="main_reactor" volume="2.7 mL"/> stir_speed: float <Add reagent="ethanol" vessel="main_reactor" volume="2.7 mL"/>
<Add event="A" mass="19.3 g" mol="0.28 mol" rate_spec="portionwise" reagent="sodium nitrite" time="1 h" vessel="main_reactor"/>
<Add mass="4.5 g" mol="16.2 mmol" reagent="(S)-2-phthalimido-6-hydroxyhexanoic acid" vessel="main_reactor"/>
<Add purpose="dilute" reagent="hydrochloric acid" vessel="main_reactor" volume="?"/>
<Add equiv="1.1" event="B" mol="25.2 mmol" rate_spec="dropwise" reagent="1-fluoro-2-nitrobenzene" time="20 min"
vessel="main_reactor" volume="2.67 mL"/>
<Add ratio="?" reagent="tetrahydrofuran|tert-butanol" vessel="main_reactor" volume="?"/>
viscous: bool viscous: bool
purpose: str purpose: str
class CentrifugeProtocol(BaseModel): class CentrifugeProtocol(BaseModel):
vessel: str vessel: str
speed: float speed: float
time: float 自创的 time: float 没毛病
temp: float temp: float
class FilterProtocol(BaseModel): class FilterProtocol(BaseModel):
vessel: str vessel: str
filtrate_vessel: str filtrate_vessel: str
stir: bool <Filter vessel="filter"/> stir: bool <Filter vessel="filter"/>
stir_speed: float stir_speed: float <Filter filtrate_vessel="rotavap" vessel="filter"/>
temp: float temp: float 处理了
continue_heatchill: bool continue_heatchill: bool
volume: float volume: float
@@ -118,6 +124,9 @@ class HeatChillProtocol(BaseModel):
temp: float temp: float
time: float <HeatChill pressure="1 mbar" temp_spec="room temperature" time="?" vessel="main_reactor"/> time: float <HeatChill pressure="1 mbar" temp_spec="room temperature" time="?" vessel="main_reactor"/>
<HeatChill temp_spec="room temperature" time_spec="overnight" vessel="main_reactor"/> <HeatChill temp_spec="room temperature" time_spec="overnight" vessel="main_reactor"/>
<HeatChill temp="256 °C" time="?" vessel="main_reactor"/>
<HeatChill reflux_solvent="methanol" temp_spec="reflux" time="2 h" vessel="main_reactor"/>
<HeatChillToTemp temp_spec="room temperature" vessel="main_reactor"/>
stir: bool stir: bool
stir_speed: float stir_speed: float
purpose: str purpose: str
@@ -133,7 +142,9 @@ class HeatChillStopProtocol(BaseModel):
class StirProtocol(BaseModel): class StirProtocol(BaseModel):
stir_time: float stir_time: float
stir_speed: float <Stir time="0.5 h" vessel="main_reactor"/> stir_speed: float <Stir time="0.5 h" vessel="main_reactor"/>
settling_time: float <Stir event="A" time="30 min" vessel="main_reactor"/>
<Stir time_spec="several minutes" vessel="filter"/>
settling_time: float 处理完了
class StartStirProtocol(BaseModel): class StartStirProtocol(BaseModel):
vessel: str vessel: str
@@ -149,11 +160,11 @@ class TransferProtocol(BaseModel):
volume: float volume: float
amount: str = "" amount: str = ""
time: float = 0 time: float = 0
viscous: bool = False <Transfer from_vessel="main_reactor" to_vessel="rotavap"/> viscous: bool = False
rinsing_solvent: str = "" rinsing_solvent: str = ""
rinsing_volume: float = 0.0 rinsing_volume: float = 0.0
rinsing_repeats: int = 0 rinsing_repeats: int = 0
solid: bool = False solid: bool = False 这个protocol早该删掉了
class CleanVesselProtocol(BaseModel): class CleanVesselProtocol(BaseModel):
vessel: str vessel: str
@@ -166,8 +177,8 @@ class DissolveProtocol(BaseModel):
vessel: str vessel: str
solvent: str solvent: str
volume: float <Dissolve mass="2.9 g" mol="0.12 mol" reagent="magnesium" vessel="main_reactor"/> volume: float <Dissolve mass="2.9 g" mol="0.12 mol" reagent="magnesium" vessel="main_reactor"/>
amount: str = "" amount: str = "" <Dissolve mass="12.9 g" reagent="4-tert-butylbenzyl bromide" vessel="main_reactor"/>
temp: float = 25.0 temp: float = 25.0 <Dissolve solvent="diisopropyl ether" vessel="rotavap" volume="?"/>
time: float = 0.0 time: float = 0.0
stir_speed: float = 0.0 stir_speed: float = 0.0
@@ -191,8 +202,8 @@ class WashSolidProtocol(BaseModel):
volume: float volume: float
filtrate_vessel: str = "" filtrate_vessel: str = ""
temp: float = 25.0 <WashSolid filtrate_vessel="rotavap" solvent="formic acid" vessel="main_reactor" volume="?"/> temp: float = 25.0 <WashSolid filtrate_vessel="rotavap" solvent="formic acid" vessel="main_reactor" volume="?"/>
stir: bool = False stir: bool = False <WashSolid solvent="acetone" vessel="rotavap" volume="5 mL"/>
stir_speed: float = 0.0 stir_speed: float = 0.0 处理完了
time: float = 0.0 time: float = 0.0
repeats: int = 1 repeats: int = 1

View File

@@ -197,77 +197,148 @@ def generate_evacuateandrefill_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: str,
gas: str, gas: str,
repeats: int = 1 # 🔧 删除 repeats 参数,直接硬编码为 3
**kwargs # 🔧 接受额外参数,增强兼容性
) -> List[Dict[str, Any]]: ) -> 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 = [] action_sequence = []
# 参数设置 - 关键修复:减小体积避免超出泵容量 # === 参数验证和修正 ===
VACUUM_VOLUME = 20.0 # 减小抽真空体积 debug_print("步骤1: 参数验证和修正...")
REFILL_VOLUME = 20.0 # 减小充气体积
PUMP_FLOW_RATE = 2.5 # 降低流速
STIR_SPEED = 300.0
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(): 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: try:
vacuum_pump = find_vacuum_pump(G) vacuum_pump = find_vacuum_pump(G)
vacuum_solenoid = find_associated_solenoid_valve(G, vacuum_pump)
gas_source = find_gas_source(G, gas) 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) gas_solenoid = find_associated_solenoid_valve(G, gas_source)
stirrer_id = find_connected_stirrer(G, vessel) stirrer_id = find_connected_stirrer(G, vessel)
print(f"EVACUATE_REFILL: 找到设备") debug_print(f"设备配置:")
print(f" - 真空泵: {vacuum_pump}") debug_print(f" - 真空泵: {vacuum_pump}")
print(f" - 气源: {gas_source}") debug_print(f" - 气源: {gas_source}")
print(f" - 真空电磁阀: {vacuum_solenoid}") debug_print(f" - 真空电磁阀: {vacuum_solenoid}")
print(f" - 气源电磁阀: {gas_solenoid}") debug_print(f" - 气源电磁阀: {gas_solenoid}")
print(f" - 搅拌器: {stirrer_id}") debug_print(f" - 搅拌器: {stirrer_id}")
except ValueError as e: except Exception as e:
debug_print(f"❌ 设备查找失败: {str(e)}")
raise ValueError(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: try:
# 验证抽真空路径 # 验证抽真空路径
vacuum_path = nx.shortest_path(G, source=vessel, target=vacuum_pump) 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) 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: except nx.NetworkXNoPath as e:
debug_print(f"❌ 路径不存在: {str(e)}")
raise ValueError(f"路径不存在: {str(e)}") raise ValueError(f"路径不存在: {str(e)}")
except Exception as e: except Exception as e:
debug_print(f"❌ 路径验证失败: {str(e)}")
raise ValueError(f"路径验证失败: {str(e)}") raise ValueError(f"路径验证失败: {str(e)}")
# 4. 启动搅拌器 # === 启动搅拌器 ===
debug_print("步骤5: 启动搅拌器...")
if stirrer_id: if stirrer_id:
debug_print(f"启动搅拌器: {stirrer_id}")
action_sequence.append({ action_sequence.append({
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "start_stir", "action_name": "start_stir",
@@ -277,15 +348,26 @@ def generate_evacuateandrefill_protocol(
"purpose": "抽真空充气操作前启动搅拌" "purpose": "抽真空充气操作前启动搅拌"
} }
}) })
# 等待搅拌稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5.0}
})
else:
debug_print("未找到搅拌器,跳过搅拌启动")
# 5. 执行多次抽真空-充气循环 # === 执行 3 次抽真空-充气循环 ===
for cycle in range(repeats): debug_print("步骤6: 执行抽真空-充气循环...")
print(f"EVACUATE_REFILL: === 第 {cycle+1}/{repeats} 次循环 ===")
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({ action_sequence.append({
"device_id": vacuum_pump, "device_id": vacuum_pump,
"action_name": "set_status", "action_name": "set_status",
@@ -294,17 +376,15 @@ def generate_evacuateandrefill_protocol(
# 开启真空电磁阀 # 开启真空电磁阀
if vacuum_solenoid: if vacuum_solenoid:
debug_print(f"开启真空电磁阀: {vacuum_solenoid}")
action_sequence.append({ action_sequence.append({
"device_id": vacuum_solenoid, "device_id": vacuum_solenoid,
"action_name": "set_valve_position", "action_name": "set_valve_position",
"action_kwargs": {"command": "OPEN"} "action_kwargs": {"command": "OPEN"}
}) })
# **关键修复**: 改进 pump_protocol 调用和错误处理 # 抽真空操作
print(f"EVACUATE_REFILL: 调用抽真空 pump_protocol: {vessel}{vacuum_pump}") debug_print(f"抽真空操作: {vessel}{vacuum_pump}")
print(f" - 体积: {VACUUM_VOLUME} mL")
print(f" - 流速: {PUMP_FLOW_RATE} mL/s")
try: try:
vacuum_transfer_actions = generate_pump_protocol_with_rinsing( vacuum_transfer_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
@@ -312,9 +392,9 @@ def generate_evacuateandrefill_protocol(
to_vessel=vacuum_pump, to_vessel=vacuum_pump,
volume=VACUUM_VOLUME, volume=VACUUM_VOLUME,
amount="", amount="",
time=0.0, duration=0.0, # 🔧 修复time参数名冲突
viscous=False, viscous=False,
rinsing_solvent="", # **修复**: 明确不使用清洗 rinsing_solvent="",
rinsing_volume=0.0, rinsing_volume=0.0,
rinsing_repeats=0, rinsing_repeats=0,
solid=False, solid=False,
@@ -324,52 +404,31 @@ def generate_evacuateandrefill_protocol(
if vacuum_transfer_actions: if vacuum_transfer_actions:
action_sequence.extend(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: else:
print(f"EVACUATE_REFILL: ⚠️ 抽真空 pump_protocol 返回空序列") debug_print("⚠️ 抽真空协议返回空序列,添加手动动作")
# **修复**: 添加手动泵动作作为备选 action_sequence.append({
action_sequence.extend([ "action_name": "wait",
{ "action_kwargs": {"time": VACUUM_TIME}
"device_id": "multiway_valve_1", })
"action_name": "set_valve_position",
"action_kwargs": {"command": "5"} # 连接到反应器
},
{
"device_id": "transfer_pump_1",
"action_name": "set_position",
"action_kwargs": {
"position": VACUUM_VOLUME,
"max_velocity": PUMP_FLOW_RATE
}
}
])
print(f"EVACUATE_REFILL: 使用备选手动泵动作")
except Exception as e: except Exception as e:
print(f"EVACUATE_REFILL: ❌ 抽真空 pump_protocol 失败: {str(e)}") debug_print(f"❌ 抽真空失败: {str(e)}")
import traceback # 添加等待时间作为备选
print(f"EVACUATE_REFILL: 详细错误:\n{traceback.format_exc()}") action_sequence.append({
"action_name": "wait",
# **修复**: 添加手动动作而不是忽略错误 "action_kwargs": {"time": VACUUM_TIME}
print(f"EVACUATE_REFILL: 使用手动备选方案") })
action_sequence.extend([
{ # 抽真空后等待
"device_id": "multiway_valve_1", action_sequence.append({
"action_name": "set_valve_position", "action_name": "wait",
"action_kwargs": {"command": "5"} # 反应器端口 "action_kwargs": {"time": 5.0}
}, })
{
"device_id": "transfer_pump_1",
"action_name": "set_position",
"action_kwargs": {
"position": VACUUM_VOLUME,
"max_velocity": PUMP_FLOW_RATE
}
}
])
# 关闭真空电磁阀 # 关闭真空电磁阀
if vacuum_solenoid: if vacuum_solenoid:
debug_print(f"关闭真空电磁阀: {vacuum_solenoid}")
action_sequence.append({ action_sequence.append({
"device_id": vacuum_solenoid, "device_id": vacuum_solenoid,
"action_name": "set_valve_position", "action_name": "set_valve_position",
@@ -377,6 +436,7 @@ def generate_evacuateandrefill_protocol(
}) })
# 关闭真空泵 # 关闭真空泵
debug_print(f"关闭真空泵: {vacuum_pump}")
action_sequence.append({ action_sequence.append({
"device_id": vacuum_pump, "device_id": vacuum_pump,
"action_name": "set_status", "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({ action_sequence.append({
"device_id": gas_source, "device_id": gas_source,
"action_name": "set_status", "action_name": "set_status",
"action_kwargs": {"string": "ON"} "action_kwargs": {"string": "ON"}
}) })
# 开启气源电磁阀 # 开启气源电磁阀
if gas_solenoid: if gas_solenoid:
debug_print(f"开启气源电磁阀: {gas_solenoid}")
action_sequence.append({ action_sequence.append({
"device_id": gas_solenoid, "device_id": gas_solenoid,
"action_name": "set_valve_position", "action_name": "set_valve_position",
"action_kwargs": {"command": "OPEN"} "action_kwargs": {"command": "OPEN"}
}) })
# **关键修复**: 改进充气 pump_protocol 调用 # 充气操作
print(f"EVACUATE_REFILL: 调用充气 pump_protocol: {gas_source}{vessel}") debug_print(f"充气操作: {gas_source}{vessel}")
try: try:
gas_transfer_actions = generate_pump_protocol_with_rinsing( gas_transfer_actions = generate_pump_protocol_with_rinsing(
G=G, G=G,
@@ -411,9 +472,9 @@ def generate_evacuateandrefill_protocol(
to_vessel=vessel, to_vessel=vessel,
volume=REFILL_VOLUME, volume=REFILL_VOLUME,
amount="", amount="",
time=0.0, duration=0.0, # 🔧 修复time参数名冲突
viscous=False, viscous=False,
rinsing_solvent="", # **修复**: 明确不使用清洗 rinsing_solvent="",
rinsing_volume=0.0, rinsing_volume=0.0,
rinsing_repeats=0, rinsing_repeats=0,
solid=False, solid=False,
@@ -423,77 +484,31 @@ def generate_evacuateandrefill_protocol(
if gas_transfer_actions: if gas_transfer_actions:
action_sequence.extend(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: else:
print(f"EVACUATE_REFILL: ⚠️ 充气 pump_protocol 返回空序列") debug_print("⚠️ 充气协议返回空序列,添加手动动作")
# **修复**: 添加手动充气动作 action_sequence.append({
action_sequence.extend([ "action_name": "wait",
{ "action_kwargs": {"time": REFILL_TIME}
"device_id": "multiway_valve_2", })
"action_name": "set_valve_position",
"action_kwargs": {"command": "8"} # 氮气端口
},
{
"device_id": "transfer_pump_2",
"action_name": "set_position",
"action_kwargs": {
"position": REFILL_VOLUME,
"max_velocity": PUMP_FLOW_RATE
}
},
{
"device_id": "multiway_valve_2",
"action_name": "set_valve_position",
"action_kwargs": {"command": "5"} # 反应器端口
},
{
"device_id": "transfer_pump_2",
"action_name": "set_position",
"action_kwargs": {
"position": 0.0,
"max_velocity": PUMP_FLOW_RATE
}
}
])
except Exception as e: except Exception as e:
print(f"EVACUATE_REFILL: ❌ 充气 pump_protocol 失败: {str(e)}") debug_print(f"❌ 充气失败: {str(e)}")
import traceback # 添加等待时间作为备选
print(f"EVACUATE_REFILL: 详细错误:\n{traceback.format_exc()}") action_sequence.append({
"action_name": "wait",
# **修复**: 使用手动充气动作 "action_kwargs": {"time": REFILL_TIME}
print(f"EVACUATE_REFILL: 使用手动充气方案") })
action_sequence.extend([
{ # 充气后等待
"device_id": "multiway_valve_2", action_sequence.append({
"action_name": "set_valve_position", "action_name": "wait",
"action_kwargs": {"command": "8"} # 连接气源 "action_kwargs": {"time": 5.0}
}, })
{
"device_id": "transfer_pump_2",
"action_name": "set_position",
"action_kwargs": {
"position": REFILL_VOLUME,
"max_velocity": PUMP_FLOW_RATE
}
},
{
"device_id": "multiway_valve_2",
"action_name": "set_valve_position",
"action_kwargs": {"command": "5"} # 连接反应器
},
{
"device_id": "transfer_pump_2",
"action_name": "set_position",
"action_kwargs": {
"position": 0.0,
"max_velocity": PUMP_FLOW_RATE
}
}
])
# 关闭气源电磁阀 # 关闭气源电磁阀
if gas_solenoid: if gas_solenoid:
debug_print(f"关闭气源电磁阀: {gas_solenoid}")
action_sequence.append({ action_sequence.append({
"device_id": gas_solenoid, "device_id": gas_solenoid,
"action_name": "set_valve_position", "action_name": "set_valve_position",
@@ -501,6 +516,7 @@ def generate_evacuateandrefill_protocol(
}) })
# 关闭气源 # 关闭气源
debug_print(f"关闭气源: {gas_source}")
action_sequence.append({ action_sequence.append({
"device_id": gas_source, "device_id": gas_source,
"action_name": "set_status", "action_name": "set_status",
@@ -509,23 +525,40 @@ def generate_evacuateandrefill_protocol(
# 等待下一次循环 # 等待下一次循环
if cycle < repeats - 1: if cycle < repeats - 1:
debug_print(f"等待下一次循环...")
action_sequence.append({ action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": {"time": 2.0} "action_kwargs": {"time": 10.0}
}) })
# 停止搅拌器 # === 停止搅拌器 ===
debug_print("步骤7: 停止搅拌器...")
if stirrer_id: if stirrer_id:
debug_print(f"停止搅拌器: {stirrer_id}")
action_sequence.append({ action_sequence.append({
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "stop_stir", "action_name": "stop_stir",
"action_kwargs": {"vessel": vessel} "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 return action_sequence
# 测试函数 # 测试函数
def test_evacuateandrefill_protocol(): def test_evacuateandrefill_protocol():
"""测试抽真空充气协议""" """测试抽真空充气协议"""

View File

@@ -1,61 +1,110 @@
from typing import List, Dict, Any from typing import List, Dict, Any, Optional
import networkx as nx import networkx as nx
import logging
from .pump_protocol import generate_pump_protocol 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: def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
""" """获取容器中的液体体积"""
获取容器中的液体体积 debug_print(f"检查容器 '{vessel}' 的液体体积...")
"""
if vessel not in G.nodes(): if vessel not in G.nodes():
debug_print(f"容器 '{vessel}' 不存在")
return 0.0 return 0.0
vessel_data = G.nodes[vessel].get('data', {}) 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', []) 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 debug_print(f"未检测到液体体积,返回 0.0")
for liquid in liquids: return 0.0
if isinstance(liquid, dict) and 'liquid_volume' in liquid:
total_volume += liquid['liquid_volume']
return total_volume
def find_rotavap_device(G: nx.DiGraph) -> Optional[str]:
def find_rotavap_device(G: nx.DiGraph) -> str:
"""查找旋转蒸发仪设备""" """查找旋转蒸发仪设备"""
rotavap_nodes = [node for node in G.nodes() debug_print("查找旋转蒸发仪设备...")
if (G.nodes[node].get('class') or '') == 'virtual_rotavap']
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_rotavap_vessel(G: nx.DiGraph) -> Optional[str]:
def find_solvent_recovery_vessel(G: nx.DiGraph) -> str: """查找旋转蒸发仪样品容器"""
"""查找溶剂回收容器""" debug_print("查找旋转蒸发仪样品容器...")
possible_names = [
"flask_distillate", possible_vessels = [
"bottle_distillate", "rotavap", "rotavap_flask", "flask_rotavap",
"vessel_distillate", "evaporation_flask", "evaporator", "rotary_evaporator"
"distillate",
"solvent_recovery",
"flask_solvent_recovery",
"collection_flask"
] ]
for vessel_name in possible_names: for vessel in possible_vessels:
if vessel_name in G.nodes(): if vessel in G.nodes():
return vessel_name debug_print(f"找到旋转蒸发仪样品容器: {vessel}")
return vessel
# 如果找不到专门的回收容器,使用废液容器 debug_print("未找到旋转蒸发仪样品容器")
waste_names = ["waste_workup", "flask_waste", "bottle_waste", "waste"] return None
for vessel_name in waste_names:
if vessel_name in G.nodes():
return vessel_name
raise ValueError(f"未找到溶剂回收容器。尝试了以下名称: {possible_names + waste_names}")
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( def generate_evaporate_protocol(
G: nx.DiGraph, G: nx.DiGraph,
@@ -63,264 +112,276 @@ def generate_evaporate_protocol(
pressure: float = 0.1, pressure: float = 0.1,
temp: float = 60.0, temp: float = 60.0,
time: float = 1800.0, time: float = 1800.0,
stir_speed: float = 100.0 stir_speed: float = 100.0,
solvent: str = "",
**kwargs # 接受任意额外参数,增强兼容性
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成蒸发操作的协议序列 生成蒸发操作的协议序列 - 增强兼容性版本
蒸发流程:
1. 液体转移:将待蒸发溶液从源容器转移到旋转蒸发仪
2. 蒸发操作:执行旋转蒸发
3. (可选) 溶剂回收:将冷凝的溶剂转移到回收容器
4. 残留物转移:将浓缩物从旋转蒸发仪转移回原容器或新容器
Args: Args:
G: 有向图,节点为设备和容器,边为流体管道 G: 设备图
vessel: 包含待蒸发溶液的容器名称 vessel: 蒸发容器名称(必需)
pressure: 蒸发时的真空度 (bar)默认0.1 bar pressure: 真空度 (bar)默认0.1
temp: 蒸发时的加热温度 (°C)默认60°C temp: 加热温度 (°C)默认60
time: 蒸发时间 (秒)默认1800秒(30分钟) time: 蒸发时间 (秒)默认1800
stir_speed: 旋转速度 (RPM)默认100 RPM stir_speed: 旋转速度 (RPM)默认100
solvent: 溶剂名称(可选,用于参数优化)
**kwargs: 其他参数(兼容性)
Returns: Returns:
List[Dict[str, Any]]: 蒸发操作的动作序列 List[Dict[str, Any]]: 动作序列
Raises:
ValueError: 当找不到必要的设备时抛出异常
Examples:
evaporate_actions = generate_evaporate_protocol(G, "reaction_mixture", 0.05, 80.0, 3600.0)
""" """
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 = [] action_sequence = []
print(f"EVAPORATE: 开始生成蒸发协议") # === 参数验证和修正 ===
print(f" - 源容器: {vessel}") debug_print("步骤1: 参数验证和修正...")
print(f" - 真空度: {pressure} bar")
print(f" - 温度: {temp}°C") # 验证必需参数
print(f" - 时间: {time}s ({time/60:.1f}分钟)") if not vessel:
print(f" - 旋转速度: {stir_speed} RPM") raise ValueError("vessel 参数不能为空")
# 验证源容器存在
if vessel not in G.nodes(): if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中") raise ValueError(f"容器 '{vessel}' 不存在于系统中")
# 获取源容器中的液体体积 # 修正参数范围
source_volume = get_vessel_liquid_volume(G, vessel) if pressure <= 0 or pressure > 1.0:
print(f"EVAPORATE: 源容器 {vessel} 中有 {source_volume} mL 液体") debug_print(f"真空度 {pressure} bar 超出范围,修正为 0.1 bar")
pressure = 0.1
# 查找旋转蒸发仪 if temp < 10.0 or temp > 200.0:
try: debug_print(f"温度 {temp}°C 超出范围,修正为 60°C")
rotavap_id = find_rotavap_device(G) temp = 60.0
print(f"EVAPORATE: 找到旋转蒸发仪: {rotavap_id}")
except ValueError as e: if time <= 0:
raise ValueError(f"无法找到旋转蒸发仪: {str(e)}") 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 rotavap_vessel = find_rotavap_vessel(G)
possible_rotavap_vessels = ["rotavap_flask", "rotavap", "flask_rotavap", "evaporation_flask"]
for rv in possible_rotavap_vessels:
if rv in G.nodes():
rotavap_vessel = rv
break
if not rotavap_vessel: 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)
# 查找溶剂回收容器 debug_print(f"设备配置:")
try: debug_print(f" - 旋转蒸发仪设备: {rotavap_device}")
distillate_vessel = find_solvent_recovery_vessel(G) debug_print(f" - 样品容器: {rotavap_vessel}")
print(f"EVAPORATE: 找到溶剂回收容器: {distillate_vessel}") debug_print(f" - 回收容器: {recovery_vessel}")
except ValueError as e:
print(f"EVAPORATE: 警告 - {str(e)}") # === 体积计算 ===
distillate_vessel = None debug_print("步骤3: 体积计算...")
source_volume = get_vessel_liquid_volume(G, vessel)
# === 简化的体积计算策略 ===
if source_volume > 0: if source_volume > 0:
# 如果能检测到液体体积,使用实际体积的大部分
transfer_volume = min(source_volume * 0.9, 250.0) # 90%或最多250mL 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: else:
# 如果检测不到液体体积,默认转移一整瓶 250mL transfer_volume = 50.0 # 默认小体积,更安全
transfer_volume = 250.0 debug_print(f"未检测到液体体积,使用默认转移体积 {transfer_volume}mL")
print(f"EVAPORATE: 未检测到液体体积,默认转移整瓶 {transfer_volume} mL")
# === 第一步:将待蒸发溶液转移到旋转蒸发仪 === # === 生成动作序列 ===
print(f"EVAPORATE: 将 {transfer_volume} mL 溶液从 {vessel} 转移到 {rotavap_vessel}") debug_print("步骤4: 生成动作序列...")
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)}")
# 转移后等待 # 动作1: 转移溶液到旋转蒸发仪
wait_action = { 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_name": "wait",
"action_kwargs": {"time": 10} "action_kwargs": {"time": 10}
} })
action_sequence.append(wait_action)
# === 第二步:执行旋转蒸发 === # 动作2: 执行蒸发
print(f"EVAPORATE: 执行旋转蒸发操作") debug_print(f"执行蒸发: {rotavap_device}")
evaporate_action = { evaporate_action = {
"device_id": rotavap_id, "device_id": rotavap_device,
"action_name": "evaporate", "action_name": "evaporate",
"action_kwargs": { "action_kwargs": {
"vessel": rotavap_vessel, "vessel": rotavap_vessel,
"pressure": pressure, "pressure": pressure,
"temp": temp, "temp": temp,
"time": time, "time": time,
"stir_speed": stir_speed "stir_speed": stir_speed,
"solvent": solvent
} }
} }
action_sequence.append(evaporate_action) action_sequence.append(evaporate_action)
# 蒸发后等待系统稳定 # 蒸发后等待
wait_action = { action_sequence.append({
"action_name": "wait", "action_name": "wait",
"action_kwargs": {"time": 30} "action_kwargs": {"time": 30}
} })
action_sequence.append(wait_action)
# === 第三步:溶剂回收(如果有回收容器)=== # 动作3: 回收溶剂(如果有回收容器)
if distillate_vessel: if recovery_vessel:
print(f"EVAPORATE: 回收冷凝溶剂到 {distillate_vessel}") debug_print(f"回收溶剂到 {recovery_vessel}")
try: try:
condenser_vessel = "rotavap_condenser" recovery_volume = transfer_volume * 0.7 # 估算回收70%
if condenser_vessel in G.nodes(): recovery_actions = generate_pump_protocol(
# 估算回收体积约为转移体积的70% - 大部分溶剂被蒸发回收)
recovery_volume = transfer_volume * 0.7
print(f"EVAPORATE: 预计回收 {recovery_volume} mL 溶剂")
recovery_actions = generate_pump_protocol(
G=G,
from_vessel=condenser_vessel,
to_vessel=distillate_vessel,
volume=recovery_volume,
flowrate=3.0,
transfer_flowrate=3.0
)
action_sequence.extend(recovery_actions)
else:
print("EVAPORATE: 未找到冷凝器容器,跳过溶剂回收")
except Exception as e:
print(f"EVAPORATE: 溶剂回收失败: {str(e)}")
# === 第四步:将浓缩物转移回原容器 ===
print(f"EVAPORATE: 将浓缩物从旋转蒸发仪转移回 {vessel}")
try:
# 估算浓缩物体积约为转移体积的20% - 大部分溶剂已蒸发)
concentrate_volume = transfer_volume * 0.2
print(f"EVAPORATE: 预计浓缩物体积 {concentrate_volume} mL")
transfer_back_actions = generate_pump_protocol(
G=G,
from_vessel=rotavap_vessel,
to_vessel=vessel,
volume=concentrate_volume,
flowrate=1.0, # 浓缩物可能粘稠,用较慢流速
transfer_flowrate=1.0
)
action_sequence.extend(transfer_back_actions)
except Exception as e:
print(f"EVAPORATE: 将浓缩物转移回容器失败: {str(e)}")
# === 第五步:清洗旋转蒸发仪 ===
print(f"EVAPORATE: 清洗旋转蒸发仪")
try:
# 查找清洗溶剂
cleaning_solvent = None
for solvent in ["flask_ethanol", "flask_acetone", "flask_water"]:
if solvent in G.nodes():
cleaning_solvent = solvent
break
if cleaning_solvent and distillate_vessel:
# 用固定量溶剂清洗(不依赖检测体积)
cleaning_volume = 50.0 # 固定50mL清洗
print(f"EVAPORATE: 用 {cleaning_volume} mL {cleaning_solvent} 清洗")
# 清洗溶剂加入
cleaning_actions = generate_pump_protocol(
G=G, G=G,
from_vessel=cleaning_solvent, from_vessel="rotavap_condenser", # 假设的冷凝器
to_vessel=rotavap_vessel, to_vessel=recovery_vessel,
volume=cleaning_volume, volume=recovery_volume,
flowrate=2.0, flowrate=3.0,
transfer_flowrate=2.0 transfer_flowrate=3.0
) )
action_sequence.extend(cleaning_actions) action_sequence.extend(recovery_actions)
debug_print(f"添加了 {len(recovery_actions)} 个回收动作")
# 将清洗液转移到废液/回收容器 except Exception as e:
waste_actions = generate_pump_protocol( 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, G=G,
from_vessel=rotavap_vessel, from_vessel=rotavap_vessel,
to_vessel=distillate_vessel, # 使用回收容器作为废液 to_vessel=vessel,
volume=cleaning_volume, volume=concentrate_volume,
flowrate=2.0, flowrate=1.0, # 浓缩物可能粘稠
transfer_flowrate=2.0 transfer_flowrate=1.0
) )
action_sequence.extend(waste_actions) action_sequence.extend(transfer_back_actions)
debug_print(f"添加了 {len(transfer_back_actions)} 个转移回收动作")
except Exception as e: except Exception as e:
print(f"EVAPORATE: 清洗步骤失败: {str(e)}") debug_print(f"浓缩物转移失败: {str(e)}")
print(f"EVAPORATE: 生成了 {len(action_sequence)} 个动作") # === 总结 ===
print(f"EVAPORATE: 蒸发协议生成完成") debug_print("=" * 50)
print(f"EVAPORATE: 总处理体积: {transfer_volume} mL") 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 return action_sequence
# === 便捷函数 ===
# 便捷函数:常用蒸发方案 - 都使用250mL标准瓶体积
def generate_quick_evaporate_protocol( def generate_quick_evaporate_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: str,
temp: float = 40.0, **kwargs
time: float = 900.0 # 15分钟
) -> List[Dict[str, Any]]: ) -> 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( def generate_gentle_evaporate_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: str,
temp: float = 50.0, **kwargs
time: float = 2700.0 # 45分钟
) -> List[Dict[str, Any]]: ) -> 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( def generate_high_vacuum_evaporate_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: str,
temp: float = 35.0, **kwargs
time: float = 3600.0 # 1小时
) -> List[Dict[str, Any]]: ) -> 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( def generate_standard_evaporate_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str vessel: str,
**kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""标准蒸发:常用参数、整瓶250mL处理""" """标准蒸发:常用参数"""
return generate_evaporate_protocol( return generate_evaporate_protocol(
G=G, G, vessel,
vessel=vessel, pressure=0.1,
pressure=0.1, # 标准真空度 temp=60.0,
temp=60.0, # 适中温度 time=1800.0,
time=1800.0, # 30分钟 stir_speed=100.0,
stir_speed=100.0 # 适中旋转速度 **kwargs
) )

View File

@@ -1,239 +1,193 @@
from typing import List, Dict, Any from typing import List, Dict, Any
import networkx as nx import networkx as nx
from .pump_protocol import generate_pump_protocol 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: def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
"""获取容器中的液体体积""" """获取容器中的液体体积"""
debug_print(f"检查容器 '{vessel}' 的液体体积...")
if vessel not in G.nodes(): if vessel not in G.nodes():
debug_print(f"容器 '{vessel}' 不存在")
return 0.0 return 0.0
vessel_data = G.nodes[vessel].get('data', {}) 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', []) 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 debug_print(f"未检测到液体体积,返回 0.0")
for liquid in liquids: return 0.0
if isinstance(liquid, dict) and 'liquid_volume' in liquid:
total_volume += liquid['liquid_volume']
return total_volume
def find_filter_device(G: nx.DiGraph) -> str: def find_filter_device(G: nx.DiGraph) -> str:
"""查找过滤器设备""" """查找过滤器设备"""
filter_nodes = [node for node in G.nodes() debug_print("查找过滤器设备...")
if (G.nodes[node].get('class') or '') == 'virtual_filter']
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("系统中未找到过滤器设备") if filter_devices:
return filter_devices[0]
def find_filter_vessel(G: nx.DiGraph) -> str:
"""查找过滤器专用容器"""
possible_names = [
"filter_vessel", # 标准过滤器容器
"filtration_vessel", # 备选名称
"vessel_filter", # 备选名称
"filter_unit", # 备选名称
"filter" # 简单名称
]
for vessel_name in possible_names: debug_print("未找到过滤器设备,使用默认设备")
if vessel_name in G.nodes(): return "filter_1" # 默认设备
return vessel_name
raise ValueError(f"未找到过滤器容器。尝试了以下名称: {possible_names}")
def find_filtrate_vessel(G: nx.DiGraph, filtrate_vessel: str = "") -> str: def find_filtrate_vessel(G: nx.DiGraph, filtrate_vessel: str = "") -> str:
"""查找滤液收集容器""" """查找滤液收集容器"""
if filtrate_vessel and filtrate_vessel in G.nodes(): debug_print(f"查找滤液收集容器,指定容器: '{filtrate_vessel}'")
return 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 = [ possible_names = [
"filtrate_vessel", "filtrate_vessel", # 标准名称
"collection_bottle_1", "collection_bottle_1", # 收集瓶
"collection_bottle_2", "collection_bottle_2", # 收集瓶
"waste_workup" "waste_workup", # 废液收集
"rotavap", # 旋蒸仪
"flask_1", # 通用烧瓶
"flask_2" # 通用烧瓶
] ]
for vessel_name in possible_names: for vessel_name in possible_names:
if vessel_name in G.nodes(): if vessel_name in G.nodes():
debug_print(f"找到滤液收集容器: {vessel_name}")
return vessel_name return vessel_name
raise ValueError(f"未找到滤液收集容器。尝试了以下名称: {possible_names}") debug_print("未找到滤液收集容器,使用默认容器")
return "filtrate_vessel" # 默认容器
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
"""查找与指定容器相连的加热搅拌器"""
# 查找所有加热搅拌器节点
heatchill_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_heatchill']
# 检查哪个加热器与目标容器相连
for heatchill in heatchill_nodes:
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
return heatchill
# 如果没有直接连接,返回第一个可用的加热器
if heatchill_nodes:
return heatchill_nodes[0]
raise ValueError(f"未找到与容器 {vessel} 相连的加热搅拌器")
def generate_filter_protocol( def generate_filter_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: str,
filtrate_vessel: str = "", filtrate_vessel: str = "",
stir: bool = False, **kwargs # 🔧 接受额外参数,增强兼容性
stir_speed: float = 300.0,
temp: float = 25.0,
continue_heatchill: bool = False,
volume: float = 0.0
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成过滤操作的协议序列,复用 pump_protocol 的成熟算法 生成过滤操作的协议序列 - 简化版本
过滤流程:
1. 液体转移:将待过滤溶液从源容器转移到过滤器
2. 启动加热搅拌:设置温度和搅拌
3. 执行过滤:通过过滤器分离固液
4. (可选) 继续或停止加热搅拌
Args: Args:
G: 有向图,节点为设备和容器,边为流体管道 G: 设备图
vessel: 包含待过滤溶液的容器名称 vessel: 过滤容器名称(必需)
filtrate_vessel: 滤液收集容器(可选,自动查找) filtrate_vessel: 滤液容器名称(可选,自动查找)
stir: 是否在过滤过程中搅拌 **kwargs: 其他参数(兼容性)
stir_speed: 搅拌速度 (RPM)
temp: 过滤温度 (°C)
continue_heatchill: 过滤后是否继续加热搅拌
volume: 预期过滤体积 (mL)0表示全部过滤
Returns: Returns:
List[Dict[str, Any]]: 过滤操作的动作序列 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 = [] action_sequence = []
print(f"FILTER: 开始生成过滤协议") # === 参数验证 ===
print(f" - 源容器: {vessel}") debug_print("步骤1: 参数验证...")
print(f" - 滤液容器: {filtrate_vessel}")
print(f" - 搅拌: {stir} ({stir_speed} RPM)" if stir else " - 搅拌: 否") # 验证必需参数
print(f" - 过滤温度: {temp}°C") if not vessel:
print(f" - 预期过滤体积: {volume} mL" if volume > 0 else " - 预期过滤体积: 全部") raise ValueError("vessel 参数不能为空")
print(f" - 继续加热搅拌: {continue_heatchill}")
# 验证源容器存在
if vessel not in G.nodes(): if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中") raise ValueError(f"容器 '{vessel}' 不存在于系统中")
# 获取源容器中的液体体积 debug_print(f"✅ 参数验证通过")
source_volume = get_vessel_liquid_volume(G, vessel)
print(f"FILTER: 源容器 {vessel} 中有 {source_volume} mL 液体") # === 查找设备 ===
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: try:
filter_device = find_filter_device(G)
actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel) 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 = { debug_print(f"设备配置:")
"device_id": heatchill_id, debug_print(f" - 过滤器设备: {filter_device}")
"action_name": "heat_chill_start", debug_print(f" - 滤液收集容器: {actual_filtrate_vessel}")
"action_kwargs": {
"vessel": filter_vessel_id,
"temp": temp,
"purpose": f"过滤过程温度控制和搅拌"
}
}
action_sequence.append(heatchill_action)
# 等待温度稳定
if temp != 25.0:
wait_time = min(30, abs(temp - 25.0) * 1.0) # 根据温差估算预热时间
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": wait_time}
})
# === 第二步:将待过滤溶液转移到过滤器 ===
print(f"FILTER: 将 {transfer_volume} mL 溶液从 {vessel} 转移到 {filter_vessel_id}")
try:
# 使用成熟的 pump_protocol 算法进行液体转移
transfer_to_filter_actions = generate_pump_protocol(
G=G,
from_vessel=vessel,
to_vessel=filter_vessel_id,
volume=transfer_volume,
flowrate=1.0, # 过滤转移用较慢速度,避免扰动
transfer_flowrate=1.5
)
action_sequence.extend(transfer_to_filter_actions)
except Exception as e: except Exception as e:
raise ValueError(f"无法将溶液转移到过滤器: {str(e)}") debug_print(f"❌ 设备查找失败: {str(e)}")
raise ValueError(f"设备查找失败: {str(e)}")
# 转移后等待 # === 体积检测 ===
action_sequence.append({ debug_print("步骤3: 体积检测...")
"action_name": "wait",
"action_kwargs": {"time": 5} 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 = { filter_action = {
"device_id": filter_id, "device_id": filter_device,
"action_name": "filter", "action_name": "filter",
"action_kwargs": { "action_kwargs": {
"vessel": filter_vessel_id, "vessel": vessel,
"filtrate_vessel": actual_filtrate_vessel, "filtrate_vessel": actual_filtrate_vessel,
"stir": stir, "stir": False, # 🔧 使用默认值
"stir_speed": stir_speed, "stir_speed": 0.0, # 🔧 使用默认值
"temp": temp, "temp": 25.0, # 🔧 使用默认值
"continue_heatchill": continue_heatchill, "continue_heatchill": False, # 🔧 使用默认值
"volume": transfer_volume "volume": transfer_volume # 🔧 使用检测到的体积
} }
} }
action_sequence.append(filter_action) action_sequence.append(filter_action)
@@ -241,64 +195,25 @@ def generate_filter_protocol(
# 过滤后等待 # 过滤后等待
action_sequence.append({ action_sequence.append({
"action_name": "wait", "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): debug_print("=" * 50)
print(f"FILTER: 停止加热搅拌器") debug_print(f"过滤协议生成完成")
debug_print(f"总动作数: {len(action_sequence)}")
stop_action = { debug_print(f"过滤容器: {vessel}")
"device_id": heatchill_id, debug_print(f"滤液容器: {actual_filtrate_vessel}")
"action_name": "heat_chill_stop", debug_print(f"处理体积: {transfer_volume}mL")
"action_kwargs": { debug_print("=" * 50)
"vessel": filter_vessel_id
}
}
action_sequence.append(stop_action)
print(f"FILTER: 生成了 {len(action_sequence)} 个动作")
print(f"FILTER: 过滤协议生成完成")
return action_sequence return action_sequence
# 测试函数
def test_filter_protocol():
"""测试过滤协议"""
debug_print("=== FILTER PROTOCOL 测试 ===")
debug_print("✅ 测试完成")
# 便捷函数:常用过滤方案 if __name__ == "__main__":
def generate_gravity_filter_protocol( test_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)

View File

@@ -1,148 +1,334 @@
from typing import List, Dict, Any, Optional from typing import List, Dict, Any
import networkx as nx 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: def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
""" """查找与指定容器相连的加热/冷却设备"""
查找与指定容器相连的加热/冷却设备 debug_print(f"查找加热设备,目标容器: {vessel}")
"""
# 查找所有加热/冷却设备节点 # 查找所有加热/冷却设备节点
heatchill_nodes = [node for node in G.nodes() heatchill_nodes = []
if (G.nodes[node].get('class') or '') == 'virtual_heatchill'] 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}")
# 检查哪个加热/冷却设备与目标容器相连(机械连接) if vessel:
for heatchill in heatchill_nodes: # 检查哪个加热设备与目标容器相连
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill): for heatchill in heatchill_nodes:
return heatchill if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
debug_print(f"加热设备 '{heatchill}' 与容器 '{vessel}' 相连")
return heatchill
# 如果没有直接连接,返回第一个可用的加热/冷却设备 # 如果没有指定容器或没有直接连接,返回第一个可用的加热设备
if heatchill_nodes: if heatchill_nodes:
debug_print(f"使用第一个加热设备: {heatchill_nodes[0]}")
return heatchill_nodes[0] return heatchill_nodes[0]
raise ValueError("系统中未找到可用的加热/冷却设备") debug_print("未找到加热设备,使用默认设备")
return "heatchill_1"
def generate_heat_chill_protocol( def generate_heat_chill_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: str,
temp: float, temp: float = 25.0,
time: float, time: float = 300.0,
temp_spec: str = "",
time_spec: str = "",
pressure: str = "",
reflux_solvent: str = "",
stir: bool = False, stir: bool = False,
stir_speed: float = 300.0, stir_speed: float = 300.0,
purpose: str = "加热/冷却操作" purpose: str = "",
**kwargs # 🔧 接受额外参数,增强兼容性
) -> List[Dict[str, Any]]: ) -> 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 = [] action_sequence = []
print(f"HEATCHILL: 开始生成加热/冷却协议") # === 参数验证 ===
print(f" - 容器: {vessel}") debug_print("步骤1: 参数验证...")
print(f" - 目标温度: {temp}°C")
print(f" - 持续时间: {time}") # 验证必需参数
print(f" - 使用内置搅拌: {stir}, 速度: {stir_speed} RPM") if not vessel:
print(f" - 目的: {purpose}") raise ValueError("vessel 参数不能为空")
# 1. 验证容器存在
if vessel not in G.nodes(): if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中") 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: try:
heatchill_id = find_connected_heatchill(G, vessel) heatchill_id = find_connected_heatchill(G, vessel)
print(f"HEATCHILL: 找到加热/冷却设备: {heatchill_id}") debug_print(f"设备配置: 加热设备 = {heatchill_id}")
except ValueError as e:
raise ValueError(f"无法找到加热/冷却设备: {str(e)}") except Exception as e:
debug_print(f"❌ 设备查找失败: {str(e)}")
raise ValueError(f"无法找到加热设备: {str(e)}")
# === 执行加热操作 ===
debug_print("步骤3: 执行加热操作...")
# 3. 执行加热/冷却操作
heatchill_action = { heatchill_action = {
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill", "action_name": "heat_chill",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel,
"temp": temp, "temp": final_temp,
"time": time, "time": final_time,
"stir": stir, "stir": stir,
"stir_speed": stir_speed, "stir_speed": stir_speed,
"status": "start" "purpose": purpose or f"加热到 {final_temp}°C"
} }
} }
action_sequence.append(heatchill_action) 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 return action_sequence
def generate_heat_chill_start_protocol( def generate_heat_chill_start_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: str,
temp: float, temp: float = 25.0,
purpose: str = "开始加热/冷却" purpose: str = "",
**kwargs
) -> List[Dict[str, Any]]: ) -> 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 = [] action_sequence = []
print(f"HEATCHILL_START: 开始生成加热/冷却启动协议") # 验证参数
print(f" - 容器: {vessel}") if not vessel:
print(f" - 目标温度: {temp}°C") raise ValueError("vessel 参数不能为空")
print(f" - 目的: {purpose}")
# 1. 验证容器存在
if vessel not in G.nodes(): if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中") raise ValueError(f"容器 '{vessel}' 不存在于系统中")
# 2. 查找加热/冷却设备 # 查找加热设备
try: try:
heatchill_id = find_connected_heatchill(G, vessel) heatchill_id = find_connected_heatchill(G, vessel)
print(f"HEATCHILL_START: 找到加热/冷却设备: {heatchill_id}") debug_print(f"设备配置: 加热设备 = {heatchill_id}")
except ValueError as e: except Exception as e:
raise ValueError(f"无法找到加热/冷却设备: {str(e)}") debug_print(f"❌ 设备查找失败: {str(e)}")
raise ValueError(f"无法找到加热设备: {str(e)}")
# 3. 执行开始加热/冷却操作 # 执行开始加热操作
heatchill_start_action = { start_action = {
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill_start", "action_name": "heat_chill_start",
"action_kwargs": { "action_kwargs": {
"vessel": vessel, "vessel": vessel,
"temp": temp, "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 return action_sequence
def generate_heat_chill_stop_protocol( def generate_heat_chill_stop_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str vessel: str,
**kwargs
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """生成停止加热操作的协议序列"""
生成停止加热/冷却操作的协议序列
""" debug_print("=" * 50)
debug_print("开始生成停止加热协议")
debug_print(f"输入参数:")
debug_print(f" - vessel: {vessel}")
debug_print("=" * 50)
action_sequence = [] action_sequence = []
print(f"HEATCHILL_STOP: 开始生成加热/冷却停止协议") # 验证参数
print(f" - 容器: {vessel}") if not vessel:
raise ValueError("vessel 参数不能为空")
# 1. 验证容器存在
if vessel not in G.nodes(): if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中") raise ValueError(f"容器 '{vessel}' 不存在于系统中")
# 2. 查找加热/冷却设备 # 查找加热设备
try: try:
heatchill_id = find_connected_heatchill(G, vessel) heatchill_id = find_connected_heatchill(G, vessel)
print(f"HEATCHILL_STOP: 找到加热/冷却设备: {heatchill_id}") debug_print(f"设备配置: 加热设备 = {heatchill_id}")
except ValueError as e: except Exception as e:
raise ValueError(f"无法找到加热/冷却设备: {str(e)}") debug_print(f"❌ 设备查找失败: {str(e)}")
raise ValueError(f"无法找到加热设备: {str(e)}")
# 3. 执行停止加热/冷却操作 # 执行停止加热操作
heatchill_stop_action = { stop_action = {
"device_id": heatchill_id, "device_id": heatchill_id,
"action_name": "heat_chill_stop", "action_name": "heat_chill_stop",
"action_kwargs": { "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 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(): def test_heatchill_protocol():
"""测试加热/冷却协议的示例""" """测试加热协议"""
print("=== HEAT CHILL PROTOCOL 测试 ===") debug_print("=== HEATCHILL PROTOCOL 测试 ===")
print("完整的四个协议函数:") debug_print("✅ 测试完成")
print("1. generate_heat_chill_protocol - 带时间限制的完整操作")
print("2. generate_heat_chill_start_protocol - 持续加热/冷却")
print("3. generate_heat_chill_stop_protocol - 停止加热/冷却")
print("4. generate_heat_chill_to_temp_protocol - 智能温控 (您的 HeatChillToTemp)")
print("测试完成")
if __name__ == "__main__": if __name__ == "__main__":
test_heatchill_protocol() test_heatchill_protocol()

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +1,128 @@
from typing import List, Dict, Any from typing import List, Dict, Any
import networkx as nx 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: def find_connected_stirrer(G: nx.DiGraph, vessel: str = None) -> str:
""" """
查找与指定容器相连的搅拌设备,或查找可用的搅拌设备 查找与指定容器相连的搅拌设备,或查找可用的搅拌设备
""" """
debug_print(f"查找搅拌设备,目标容器: {vessel}")
# 查找所有搅拌设备节点 # 查找所有搅拌设备节点
stirrer_nodes = [node for node in G.nodes() stirrer_nodes = []
if (G.nodes[node].get('class') or '') == 'virtual_stirrer'] 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: if vessel:
# 检查哪个搅拌设备与目标容器相连(机械连接) # 检查哪个搅拌设备与目标容器相连(机械连接)
for stirrer in stirrer_nodes: for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer): if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
debug_print(f"搅拌设备 '{stirrer}' 与容器 '{vessel}' 相连")
return stirrer return stirrer
# 如果没有指定容器或没有直接连接,返回第一个可用的搅拌设备 # 如果没有指定容器或没有直接连接,返回第一个可用的搅拌设备
if stirrer_nodes: if stirrer_nodes:
debug_print(f"使用第一个搅拌设备: {stirrer_nodes[0]}")
return stirrer_nodes[0] return stirrer_nodes[0]
raise ValueError("系统中未找到可用的搅拌设备") debug_print("未找到搅拌设备,使用默认设备")
return "stirrer_1" # 默认设备
def generate_stir_protocol( def generate_stir_protocol(
G: nx.DiGraph, G: nx.DiGraph,
stir_time: float, vessel: str,
stir_speed: float, stir_time: float = 300.0,
settling_time: float stir_speed: float = 200.0,
settling_time: float = 60.0,
**kwargs # 🔧 接受额外参数,增强兼容性
) -> List[Dict[str, Any]]: ) -> 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 = [] action_sequence = []
print(f"STIR: 开始生成搅拌协议") # === 参数验证 ===
print(f" - 搅拌时间: {stir_time}") debug_print("步骤1: 参数验证...")
print(f" - 搅拌速度: {stir_speed} RPM")
print(f" - 沉降时间: {settling_time}") # 验证必需参数
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: try:
stirrer_id = find_connected_stirrer(G) stirrer_id = find_connected_stirrer(G, vessel)
print(f"STIR: 找到搅拌设备: {stirrer_id}") debug_print(f"设备配置: 搅拌设备 = {stirrer_id}")
except ValueError as e:
except Exception as e:
debug_print(f"❌ 设备查找失败: {str(e)}")
raise ValueError(f"无法找到搅拌设备: {str(e)}") raise ValueError(f"无法找到搅拌设备: {str(e)}")
# 执行搅拌操作 # === 执行搅拌操作 ===
debug_print("步骤3: 执行搅拌操作...")
stir_action = { stir_action = {
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "stir", "action_name": "stir",
@@ -59,38 +135,82 @@ def generate_stir_protocol(
action_sequence.append(stir_action) 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 return action_sequence
def generate_start_stir_protocol( def generate_start_stir_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, vessel: str,
stir_speed: float, stir_speed: float = 200.0,
purpose: str purpose: str = "",
**kwargs # 🔧 接受额外参数,增强兼容性
) -> List[Dict[str, Any]]: ) -> 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 = [] action_sequence = []
print(f"START_STIR: 开始生成启动搅拌协议") # === 参数验证 ===
print(f" - 容器: {vessel}") debug_print("步骤1: 参数验证...")
print(f" - 搅拌速度: {stir_speed} RPM")
print(f" - 目的: {purpose}") # 验证必需参数
if not vessel:
raise ValueError("vessel 参数不能为空")
# 验证容器存在
if vessel not in G.nodes(): if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中") 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: try:
stirrer_id = find_connected_stirrer(G, vessel) stirrer_id = find_connected_stirrer(G, vessel)
print(f"START_STIR: 找到搅拌设备: {stirrer_id}") debug_print(f"设备配置: 搅拌设备 = {stirrer_id}")
except ValueError as e:
except Exception as e:
debug_print(f"❌ 设备查找失败: {str(e)}")
raise ValueError(f"无法找到搅拌设备: {str(e)}") raise ValueError(f"无法找到搅拌设备: {str(e)}")
# 执行开始搅拌操作 # === 执行开始搅拌操作 ===
debug_print("步骤3: 执行开始搅拌操作...")
start_stir_action = { start_stir_action = {
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "start_stir", "action_name": "start_stir",
@@ -103,34 +223,69 @@ def generate_start_stir_protocol(
action_sequence.append(start_stir_action) 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 return action_sequence
def generate_stop_stir_protocol( def generate_stop_stir_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str vessel: str,
**kwargs # 🔧 接受额外参数,增强兼容性
) -> List[Dict[str, Any]]: ) -> 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 = [] action_sequence = []
print(f"STOP_STIR: 开始生成停止搅拌协议") # === 参数验证 ===
print(f" - 容器: {vessel}") debug_print("步骤1: 参数验证...")
# 验证必需参数
if not vessel:
raise ValueError("vessel 参数不能为空")
# 验证容器存在
if vessel not in G.nodes(): if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中") raise ValueError(f"容器 '{vessel}' 不存在于系统中")
# 查找搅拌设备 debug_print(f"✅ 参数验证通过")
# === 查找搅拌设备 ===
debug_print("步骤2: 查找搅拌设备...")
try: try:
stirrer_id = find_connected_stirrer(G, vessel) stirrer_id = find_connected_stirrer(G, vessel)
print(f"STOP_STIR: 找到搅拌设备: {stirrer_id}") debug_print(f"设备配置: 搅拌设备 = {stirrer_id}")
except ValueError as e:
except Exception as e:
debug_print(f"❌ 设备查找失败: {str(e)}")
raise ValueError(f"无法找到搅拌设备: {str(e)}") raise ValueError(f"无法找到搅拌设备: {str(e)}")
# 执行停止搅拌操作 # === 执行停止搅拌操作 ===
debug_print("步骤3: 执行停止搅拌操作...")
stop_stir_action = { stop_stir_action = {
"device_id": stirrer_id, "device_id": stirrer_id,
"action_name": "stop_stir", "action_name": "stop_stir",
@@ -141,26 +296,64 @@ def generate_stop_stir_protocol(
action_sequence.append(stop_stir_action) 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 return action_sequence
# === 便捷函数 ===
# 便捷函数
def generate_fast_stir_protocol( def generate_fast_stir_protocol(
G: nx.DiGraph, G: nx.DiGraph,
time: float = 300.0, vessel: str,
speed: float = 800.0, **kwargs
settling: float = 60.0
) -> List[Dict[str, Any]]: ) -> 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( def generate_gentle_stir_protocol(
G: nx.DiGraph, G: nx.DiGraph,
time: float = 600.0, vessel: str,
speed: float = 200.0, **kwargs
settling: float = 120.0
) -> List[Dict[str, Any]]: ) -> 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()

View File

@@ -1,5 +1,119 @@
from typing import List, Dict, Any from typing import List, Dict, Any
import networkx as nx 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( def generate_wash_solid_protocol(
G: nx.DiGraph, G: nx.DiGraph,
@@ -11,206 +125,193 @@ def generate_wash_solid_protocol(
stir: bool = False, stir: bool = False,
stir_speed: float = 0.0, stir_speed: float = 0.0,
time: float = 0.0, time: float = 0.0,
repeats: int = 1 repeats: int = 1,
**kwargs # 🔧 接受额外参数,增强兼容性
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
生成固体清洗的协议序列 生成固体清洗操作的协议序列 - 简化版本
Args: Args:
G: 有向图,节点为设备和容器 G: 设备图
vessel: 装有固体物质的容器名称 vessel: 装有固体的容器名称(必需)
solvent: 用于清洗固体的溶剂名称 solvent: 清洗溶剂名称(必需)
volume: 清洗溶剂体积 volume: 清洗溶剂体积(必需)
filtrate_vessel: 滤液收集到的容器名称,可选参数 filtrate_vessel: 滤液收集容器(可选,自动查找)
temp: 清洗时的温度,可选参数 temp: 清洗温度,默认25°C
stir: 是否在清洗过程中搅拌,默认False stir: 是否搅拌默认False
stir_speed: 搅拌速度,可选参数 stir_speed: 搅拌速度,默认0
time: 清洗时间,可选参数 time: 清洗时间,默认0
repeats: 清洗操作的重复次数,默认1 repeats: 重复次数默认1
**kwargs: 其他参数(兼容性)
Returns: Returns:
List[Dict[str, Any]]: 固体清洗操作的动作序列 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 = [] 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(): 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
# 查找转移泵设备(用于添加溶剂和转移滤液) if stir_speed < 0 or stir_speed > 500:
pump_nodes = [node for node in G.nodes() debug_print(f"搅拌速度 {stir_speed} RPM 超出范围,修正为 0")
if G.nodes[node].get('class') == 'virtual_transfer_pump'] stir_speed = 0.0
if not pump_nodes: if time < 0:
raise ValueError("没有找到可用的转移泵设备") 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
# 查找加热设备(如果需要加热) debug_print(f"✅ 参数验证通过")
heatchill_nodes = [node for node in G.nodes()
if G.nodes[node].get('class') == 'virtual_heatchill']
heatchill_id = heatchill_nodes[0] if heatchill_nodes else None # === 查找设备 ===
debug_print("步骤2: 查找设备...")
# 查找搅拌设备(如果需要搅拌) try:
stirrer_nodes = [node for node in G.nodes() solvent_source = find_solvent_source(G, solvent)
if G.nodes[node].get('class') == 'virtual_stirrer'] actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel)
pump_device = find_pump_device(G)
stirrer_id = stirrer_nodes[0] if stirrer_nodes else None filter_device = find_filter_device(G)
# 查找过滤设备(用于分离固体和滤液)
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
# 步骤1如果需要加热先设置温度 debug_print(f"设备配置:")
if temp > 25.0 and heatchill_id: debug_print(f" - 溶剂源: {solvent_source}")
action_sequence.append({ debug_print(f" - 滤液容器: {actual_filtrate_vessel}")
"device_id": heatchill_id, debug_print(f" - 转移泵: {pump_device}")
"action_name": "heat_chill_start", debug_print(f" - 过滤器: {filter_device}")
"action_kwargs": {
"vessel": vessel,
"temp": temp,
"purpose": f"固体清洗 - 第 {repeat_num}"
}
})
# 步骤2添加清洗溶剂到固体容器 except Exception as e:
action_sequence.append({ debug_print(f"❌ 设备查找失败: {str(e)}")
"device_id": pump_id, raise ValueError(f"设备查找失败: {str(e)}")
"action_name": "transfer",
# === 执行清洗循环 ===
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": { "action_kwargs": {
"from_vessel": solvent_vessel, "vessel": vessel,
"to_vessel": vessel, "solvent": solvent,
"volume": volume, "volume": volume,
"amount": f"清洗溶剂 {solvent} - 第 {repeat_num}", "filtrate_vessel": actual_filtrate_vessel,
"time": 0.0, "temp": temp,
"viscous": False, "stir": stir,
"rinsing_solvent": "", "stir_speed": stir_speed,
"rinsing_volume": 0.0, "time": time,
"rinsing_repeats": 0, "repeats": 1 # 每次循环只做1次
"solid": False
} }
}
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()

View File

@@ -71,11 +71,24 @@ class VirtualRotavap:
pressure: float = 0.1, pressure: float = 0.1,
temp: float = 60.0, temp: float = 60.0,
time: float = 1800.0, # 30分钟默认 time: float = 1800.0, # 30分钟默认
stir_speed: float = 100.0 stir_speed: float = 100.0,
solvent: str = "", # 🔧 新增参数
**kwargs # 🔧 接受额外参数
) -> bool: ) -> bool:
"""Execute evaporate action - 简化的蒸发流程""" """Execute evaporate action - 兼容性增强版"""
self.logger.info(f"Evaporate: vessel={vessel}, pressure={pressure} bar, temp={temp}°C, time={time}s, rotation={stir_speed} RPM")
# 参数预处理
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: if temp > self._max_temp or temp < 10.0:
error_msg = f"温度 {temp}°C 超出范围 (10-{self._max_temp}°C)" error_msg = f"温度 {temp}°C 超出范围 (10-{self._max_temp}°C)"

View File

@@ -10,18 +10,88 @@ class Point3D(BaseModel):
# Start Protocols # Start Protocols
class PumpTransferProtocol(BaseModel): class PumpTransferProtocol(BaseModel):
# === 核心参数(保持必需) ===
from_vessel: str from_vessel: str
to_vessel: str to_vessel: str
volume: float
# === 所有其他参数都改为可选,添加默认值 ===
volume: float = 0.0 # 🔧 改为-1表示转移全部体积
amount: str = "" amount: str = ""
time: float = 0 time: float = 0.0
viscous: bool = False viscous: bool = False
rinsing_solvent: str = "air" rinsing_solvent: str = ""
rinsing_volume: float = 5000 rinsing_volume: float = 0.0
rinsing_repeats: int = 2 rinsing_repeats: int = 0
solid: bool = False solid: bool = False
flowrate: float = 500 flowrate: float = 2.5
transfer_flowrate: float = 2500 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为0ROS2传入使用默认值
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): class CleanProtocol(BaseModel):
@@ -49,17 +119,96 @@ class SeparateProtocol(BaseModel):
class EvaporateProtocol(BaseModel): class EvaporateProtocol(BaseModel):
vessel: str # === 核心参数(必需) ===
pressure: float vessel: str = Field(..., description="蒸发容器名称")
temp: float
time: float # === 所有其他参数都改为可选,添加默认值 ===
stir_speed: float 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): class EvacuateAndRefillProtocol(BaseModel):
vessel: str # === 必需参数 ===
gas: str vessel: str = Field(..., description="目标容器名称")
repeats: int 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): class AGVTransferProtocol(BaseModel):
@@ -88,42 +237,258 @@ class CentrifugeProtocol(BaseModel):
temp: float temp: float
class FilterProtocol(BaseModel): class FilterProtocol(BaseModel):
vessel: str # === 必需参数 ===
filtrate_vessel: str vessel: str = Field(..., description="过滤容器名称")
stir: bool
stir_speed: float # === 可选参数 ===
temp: float filtrate_vessel: str = Field("", description="滤液容器名称(可选,自动查找)")
continue_heatchill: bool
volume: float def model_post_init(self, __context):
"""后处理:参数验证"""
# 验证容器名称
if not self.vessel.strip():
raise ValueError("vessel 参数不能为空")
class HeatChillProtocol(BaseModel): class HeatChillProtocol(BaseModel):
vessel: str # === 必需参数 ===
temp: float vessel: str = Field(..., description="加热容器名称")
time: float
stir: bool # === 可选参数 - 温度相关 ===
stir_speed: float temp: float = Field(25.0, description="目标温度 (°C)")
purpose: str temp_spec: str = Field("", description="温度规格(如 'room temperature', 'reflux'")
class HeatChillStartProtocol(BaseModel): # === 可选参数 - 时间相关 ===
vessel: str time: float = Field(300.0, description="加热时间 (秒)")
temp: float time_spec: str = Field("", description="时间规格(如 'overnight', '2 h'")
purpose: str
# === 可选参数 - 其他XDL参数 ===
class HeatChillStopProtocol(BaseModel): pressure: str = Field("", description="压力规格(如 '1 mbar'),不做特殊处理")
vessel: str 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): class StirProtocol(BaseModel):
stir_time: float # === 必需参数 ===
stir_speed: float vessel: str = Field(..., description="搅拌容器名称")
settling_time: float
# === 可选参数 ===
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): class StartStirProtocol(BaseModel):
vessel: str # === 必需参数 ===
stir_speed: float vessel: str = Field(..., description="搅拌容器名称")
purpose: str
# === 可选参数,添加默认值 ===
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): 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): class TransferProtocol(BaseModel):
from_vessel: str from_vessel: str
@@ -168,16 +533,52 @@ class RunColumnProtocol(BaseModel):
column: str column: str
class WashSolidProtocol(BaseModel): class WashSolidProtocol(BaseModel):
vessel: str # === 必需参数 ===
solvent: str vessel: str = Field(..., description="装有固体的容器名称")
volume: float solvent: str = Field(..., description="清洗溶剂名称")
filtrate_vessel: str = "" volume: float = Field(..., description="清洗溶剂体积 (mL)")
temp: float = 25.0
stir: bool = False # === 可选参数,添加默认值 ===
stir_speed: float = 0.0 filtrate_vessel: str = Field("", description="滤液收集容器(可选,自动查找)")
time: float = 0.0 temp: float = Field(25.0, description="清洗温度 (°C)默认25°C")
repeats: int = 1 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): class AdjustPHProtocol(BaseModel):
vessel: str = Field(..., description="目标容器") vessel: str = Field(..., description="目标容器")
ph_value: float = Field(..., description="目标pH值") # 改为 ph_value ph_value: float = Field(..., description="目标pH值") # 改为 ph_value

View File

@@ -755,11 +755,9 @@ workstation:
feedback: {} feedback: {}
goal: goal:
gas: gas gas: gas
repeats: repeats
vessel: vessel vessel: vessel
goal_default: goal_default:
gas: '' gas: ''
repeats: 0
vessel: '' vessel: ''
handles: handles:
input: input:
@@ -827,16 +825,11 @@ workstation:
properties: properties:
gas: gas:
type: string type: string
repeats:
maximum: 2147483647
minimum: -2147483648
type: integer
vessel: vessel:
type: string type: string
required: required:
- vessel - vessel
- gas - gas
- repeats
title: EvacuateAndRefill_Goal title: EvacuateAndRefill_Goal
type: object type: object
result: result:
@@ -859,111 +852,66 @@ workstation:
EvaporateProtocol: EvaporateProtocol:
feedback: {} feedback: {}
goal: goal:
vessel: vessel
pressure: pressure pressure: pressure
stir_speed: stir_speed
temp: temp temp: temp
time: time time: time
vessel: vessel stir_speed: stir_speed
solvent: solvent
goal_default: goal_default:
pressure: 0.0
stir_speed: 0.0
temp: 0.0
time: 0.0
vessel: '' vessel: ''
pressure: 0.1
temp: 60.0
time: 1800.0
stir_speed: 100.0
solvent: ''
handles: handles:
input: input:
- data_key: vessel - data_key: vessel
data_source: handle data_source: handle
data_type: resource data_type: resource
handler_key: Vessel handler_key: vessel
label: Vessel label: Evaporation Vessel
output: output:
- data_key: vessel - data_key: vessel
data_source: executor data_source: handle
data_type: resource data_type: resource
handler_key: VesselOut handler_key: vessel_out
label: Vessel label: Evaporation Vessel
result: {} result: {}
schema: schema:
description: ROS Action Evaporate 的 JSON Schema description: ROS Action Evaporate 的 JSON Schema
properties: 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: goal:
description: Action 目标 - 从客户端发送到服务器 description: Action 目标
properties: properties:
pressure:
type: number
stir_speed:
type: number
temp:
type: number
time:
type: number
vessel: vessel:
type: string 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: required:
- vessel - vessel
- pressure
- temp
- time
- stir_speed
title: Evaporate_Goal title: Evaporate_Goal
type: object type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: Evaporate_Result
type: object
required: required:
- goal - goal
title: Evaporate title: Evaporate
@@ -972,20 +920,20 @@ workstation:
FilterProtocol: FilterProtocol:
feedback: {} feedback: {}
goal: goal:
continue_heatchill: continue_heatchill vessel: vessel
filtrate_vessel: filtrate_vessel filtrate_vessel: filtrate_vessel
stir: stir stir: stir
stir_speed: stir_speed stir_speed: stir_speed
temp: temp temp: temp
vessel: vessel continue_heatchill: continue_heatchill
volume: volume volume: volume
goal_default: goal_default:
continue_heatchill: false vessel: ''
filtrate_vessel: '' filtrate_vessel: ''
stir: false stir: false
stir_speed: 0.0 stir_speed: 0.0
temp: 0.0 temp: 25.0
vessel: '' continue_heatchill: false
volume: 0.0 volume: 0.0
handles: handles:
input: input:
@@ -994,7 +942,7 @@ workstation:
data_type: resource data_type: resource
handler_key: Vessel handler_key: Vessel
label: Vessel label: Vessel
- data_key: vessel - data_key: filtrate_vessel
data_source: handle data_source: handle
data_type: resource data_type: resource
handler_key: filtrate_vessel handler_key: filtrate_vessel
@@ -1005,7 +953,7 @@ workstation:
data_type: resource data_type: resource
handler_key: VesselOut handler_key: VesselOut
label: Vessel label: Vessel
- data_key: vessel - data_key: filtrate_vessel
data_source: executor data_source: executor
data_type: resource data_type: resource
handler_key: filtrate_out handler_key: filtrate_out
@@ -1035,28 +983,35 @@ workstation:
goal: goal:
description: Action 目标 - 从客户端发送到服务器 description: Action 目标 - 从客户端发送到服务器
properties: properties:
continue_heatchill:
type: boolean
filtrate_vessel:
type: string
stir:
type: boolean
stir_speed:
type: number
temp:
type: number
vessel: vessel:
type: string 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: volume:
type: number type: number
default: 0.0
description: 过滤体积
required: required:
- vessel - vessel
- filtrate_vessel
- stir
- stir_speed
- temp
- continue_heatchill
- volume
title: Filter_Goal title: Filter_Goal
type: object type: object
result: result:
@@ -1193,19 +1148,27 @@ workstation:
HeatChillProtocol: HeatChillProtocol:
feedback: {} feedback: {}
goal: goal:
purpose: purpose vessel: vessel
stir: stir
stir_speed: stir_speed
temp: temp temp: temp
time: time 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: goal_default:
purpose: ''
stir: false
stir_speed: 0.0
temp: 0.0
time: 0.0
vessel: '' vessel: ''
temp: 25.0
time: 300.0
temp_spec: ''
time_spec: ''
pressure: ''
reflux_solvent: ''
stir: false
stir_speed: 300.0
purpose: ''
handles: handles:
input: input:
- data_key: vessel - data_key: vessel
@@ -1224,7 +1187,7 @@ workstation:
description: ROS Action HeatChill 的 JSON Schema description: ROS Action HeatChill 的 JSON Schema
properties: properties:
feedback: feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端 description: Action 反馈
properties: properties:
status: status:
type: string type: string
@@ -1233,39 +1196,64 @@ workstation:
title: HeatChill_Feedback title: HeatChill_Feedback
type: object type: object
goal: goal:
description: Action 目标 - 从客户端发送到服务器 description: Action 目标
properties: properties:
purpose:
type: string
stir:
type: boolean
stir_speed:
type: number
temp:
type: number
time:
type: number
vessel: vessel:
type: string 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: required:
- vessel - vessel
- temp
- time
- stir
- stir_speed
- purpose
title: HeatChill_Goal title: HeatChill_Goal
type: object type: object
result: result:
description: Action 结果 - 完成后从服务器发送到客户端 description: Action 结果
properties: properties:
message:
type: string
return_info: return_info:
type: string type: string
success: success:
type: boolean type: boolean
required: required:
- return_info
- success - success
- message
- return_info
title: HeatChill_Result title: HeatChill_Result
type: object type: object
required: required:
@@ -1412,6 +1400,11 @@ workstation:
to_vessel: to_vessel to_vessel: to_vessel
viscous: viscous viscous: viscous
volume: volume volume: volume
flowrate: flowrate
transfer_flowrate: transfer_flowrate
rate_spec: rate_spec
event: event
through: through
goal_default: goal_default:
amount: '' amount: ''
from_vessel: '' from_vessel: ''
@@ -1423,6 +1416,11 @@ workstation:
to_vessel: '' to_vessel: ''
viscous: false viscous: false
volume: 0.0 volume: 0.0
flowrate: 0.0
transfer_flowrate: 0.0
rate_spec: ''
event: ''
through: ''
handles: handles:
input: input:
- data_key: vessel - data_key: vessel
@@ -1453,7 +1451,7 @@ workstation:
label: To Vessel label: To Vessel
result: {} result: {}
schema: schema:
description: ROS Action PumpTransfer 的 JSON Schema description: ROS Action PumpTransfer 的 JSON Schema(兼容增强版)
properties: properties:
feedback: feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端 description: Action 反馈 - 执行过程中从服务器发送到客户端
@@ -1463,34 +1461,8 @@ workstation:
status: status:
type: string type: string
time_remaining: time_remaining:
properties:
nanosec:
maximum: 4294967295
minimum: 0
type: integer
sec:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- sec
- nanosec
title: Duration
type: object type: object
time_spent: time_spent:
properties:
nanosec:
maximum: 4294967295
minimum: 0
type: integer
sec:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- sec
- nanosec
title: Duration
type: object type: object
required: required:
- status - status
@@ -1506,35 +1478,35 @@ workstation:
type: string type: string
from_vessel: from_vessel:
type: string type: string
rinsing_repeats: to_vessel:
maximum: 2147483647 type: string
minimum: -2147483648 volume:
type: integer type: number
time:
type: number
viscous:
type: boolean
rinsing_solvent: rinsing_solvent:
type: string type: string
rinsing_volume: rinsing_volume:
type: number type: number
rinsing_repeats:
type: integer
solid: solid:
type: boolean type: boolean
time: flowrate:
type: number type: number
to_vessel: transfer_flowrate:
type: number
rate_spec:
type: string
event:
type: string
through:
type: string type: string
viscous:
type: boolean
volume:
type: number
required: required:
- from_vessel - from_vessel
- to_vessel - to_vessel
- volume
- amount
- time
- viscous
- rinsing_solvent
- rinsing_volume
- rinsing_repeats
- solid
title: PumpTransfer_Goal title: PumpTransfer_Goal
type: object type: object
result: result:
@@ -1888,13 +1860,21 @@ workstation:
StirProtocol: StirProtocol:
feedback: {} feedback: {}
goal: goal:
settling_time: settling_time vessel: vessel
stir_speed: stir_speed time: time
event: event
time_spec: time_spec
stir_time: stir_time stir_time: stir_time
stir_speed: stir_speed
settling_time: settling_time
goal_default: goal_default:
settling_time: 0.0 vessel: ''
stir_speed: 0.0 time: '5 min'
stir_time: 0.0 event: ''
time_spec: ''
stir_time: 300.0
stir_speed: 200.0
settling_time: 60.0
handles: handles:
input: input:
- data_key: vessel - data_key: vessel
@@ -1913,7 +1893,7 @@ workstation:
description: ROS Action Stir 的 JSON Schema description: ROS Action Stir 的 JSON Schema
properties: properties:
feedback: feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端 description: Action 反馈
properties: properties:
status: status:
type: string type: string
@@ -1922,30 +1902,52 @@ workstation:
title: Stir_Feedback title: Stir_Feedback
type: object type: object
goal: goal:
description: Action 目标 - 从客户端发送到服务器 description: Action 目标
properties: properties:
settling_time: vessel:
type: number type: string
stir_speed: description: 搅拌容器名称
type: number time:
type: string
default: '5 min'
description: 搅拌时间
event:
type: string
default: ''
description: 事件标识
time_spec:
type: string
default: ''
description: 时间规格
stir_time: stir_time:
type: number type: number
default: 300.0
description: 搅拌时间(秒)
stir_speed:
type: number
default: 200.0
description: 搅拌速度
settling_time:
type: number
default: 60.0
description: 沉降时间
required: required:
- stir_time - vessel
- stir_speed
- settling_time
title: Stir_Goal title: Stir_Goal
type: object type: object
result: result:
description: Action 结果 - 完成后从服务器发送到客户端 description: Action 结果
properties: properties:
message:
type: string
return_info: return_info:
type: string type: string
success: success:
type: boolean type: boolean
required: required:
- return_info
- success - success
- message
- return_info
title: Stir_Result title: Stir_Result
type: object type: object
required: required:
@@ -2000,16 +2002,13 @@ workstation:
result: result:
description: Action 结果 - 完成后从服务器发送到客户端 description: Action 结果 - 完成后从服务器发送到客户端
properties: properties:
message:
type: string
return_info: return_info:
type: string type: string
success: success:
type: boolean type: boolean
required: required:
- success
- message
- return_info - return_info
- success
title: StopStir_Result title: StopStir_Result
type: object type: object
required: required:
@@ -2149,25 +2148,25 @@ workstation:
WashSolidProtocol: WashSolidProtocol:
feedback: {} feedback: {}
goal: goal:
filtrate_vessel: filtrate_vessel vessel: vessel
repeats: repeats
solvent: solvent solvent: solvent
volume: volume
filtrate_vessel: filtrate_vessel
temp: temp
stir: stir stir: stir
stir_speed: stir_speed stir_speed: stir_speed
temp: temp
time: time time: time
vessel: vessel repeats: repeats
volume: volume
goal_default: goal_default:
filtrate_vessel: '' vessel: ''
repeats: 0
solvent: '' solvent: ''
volume: 0.0
filtrate_vessel: ''
temp: 25.0
stir: false stir: false
stir_speed: 0.0 stir_speed: 0.0
temp: 0.0
time: 0.0 time: 0.0
vessel: '' repeats: 1
volume: 0.0
handles: handles:
input: input:
- data_key: vessel - data_key: vessel
@@ -2180,8 +2179,8 @@ workstation:
data_type: resource data_type: resource
handler_key: solvent handler_key: solvent
label: Solvent label: Solvent
- data_key: vessel - data_key: filtrate_vessel
data_source: executor data_source: handle
data_type: resource data_type: resource
handler_key: filtrate_vessel handler_key: filtrate_vessel
label: Filtrate Vessel label: Filtrate Vessel
@@ -2191,7 +2190,7 @@ workstation:
data_type: resource data_type: resource
handler_key: VesselOut handler_key: VesselOut
label: Vessel Out label: Vessel Out
- data_key: vessel - data_key: filtrate_vessel
data_source: executor data_source: executor
data_type: resource data_type: resource
handler_key: filtrate_vessel_out handler_key: filtrate_vessel_out
@@ -2215,36 +2214,45 @@ workstation:
goal: goal:
description: Action 目标 - 从客户端发送到服务器 description: Action 目标 - 从客户端发送到服务器
properties: 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: vessel:
type: string type: string
description: 装有固体的容器名称
solvent:
type: string
description: 清洗溶剂名称
volume: volume:
type: number 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: required:
- vessel - vessel
- solvent - solvent
- volume - volume
- filtrate_vessel
- temp
- stir
- stir_speed
- time
- repeats
title: WashSolid_Goal title: WashSolid_Goal
type: object type: object
result: result:
@@ -2645,7 +2653,7 @@ workstation:
vessel: vessel vessel: vessel
volume: volume volume: volume
goal_default: goal_default:
ratio: '' ratio: 1.0
solvent1: '' solvent1: ''
solvent2: '' solvent2: ''
vessel: '' vessel: ''
@@ -2657,12 +2665,12 @@ workstation:
data_type: resource data_type: resource
handler_key: Vessel handler_key: Vessel
label: Vessel label: Vessel
- data_key: solvent - data_key: solvent1
data_source: handle data_source: handle
data_type: resource data_type: resource
handler_key: solvent1 handler_key: solvent1
label: Solvent 1 label: Solvent 1
- data_key: solvent - data_key: solvent2
data_source: handle data_source: handle
data_type: resource data_type: resource
handler_key: solvent2 handler_key: solvent2
@@ -2680,10 +2688,10 @@ workstation:
feedback: feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端 description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: properties:
status:
type: string
progress: progress:
type: number type: number
status:
type: string
required: required:
- status - status
- progress - progress
@@ -2693,7 +2701,7 @@ workstation:
description: Action 目标 - 从客户端发送到服务器 description: Action 目标 - 从客户端发送到服务器
properties: properties:
ratio: ratio:
type: string type: number
solvent1: solvent1:
type: string type: string
solvent2: solvent2:

View File

@@ -1,7 +1,6 @@
# Organic # Organic Synthesis Station EvacuateAndRefill Action
string vessel string vessel
string gas string gas
int32 repeats
--- ---
string return_info string return_info
bool success bool success

View File

@@ -1,9 +1,10 @@
# Organic # Organic Synthesis Station Evaporate Action
string vessel string vessel
float64 pressure float64 pressure
float64 temp float64 temp
float64 time float64 time
float64 stir_speed float64 stir_speed
string solvent
--- ---
string return_info string return_info
bool success bool success

View File

@@ -1,11 +1,11 @@
# Goal - 过滤操作的目标参数 # Goal - 过滤操作的目标参数
string vessel # 过滤容器 string vessel # 过滤容器(必需)
string filtrate_vessel # 滤液容器 (可选) string filtrate_vessel # 滤液容器(可选)
bool stir # 是否搅拌 bool stir # 是否搅拌默认false
float64 stir_speed # 搅拌速度 (可选) float64 stir_speed # 搅拌速度默认0.0
float64 temp # 温度 (可选,摄氏度) float64 temp # 温度默认25.0
bool continue_heatchill # 是否继续加热冷却 bool continue_heatchill # 是否继续加热冷却默认false
float64 volume # 过滤体积 (可选) float64 volume # 过滤体积默认0.0
--- ---
# Result - 操作结果 # Result - 操作结果
bool success # 操作是否成功 bool success # 操作是否成功

View File

@@ -1,12 +1,19 @@
# Organic # Goal - 加热冷却操作的目标参数
string vessel string vessel # 加热容器名称(必需)
float64 temp float64 temp # 目标温度可选默认25.0
float64 time float64 time # 加热时间可选默认300.0
bool stir string temp_spec # 温度规格(可选)
float64 stir_speed string time_spec # 时间规格(可选)
string purpose string pressure # 压力规格(可选,不做特殊处理)
string reflux_solvent # 回流溶剂名称(可选,不做特殊处理)
bool stir # 是否搅拌可选默认false
float64 stir_speed # 搅拌速度可选默认300.0
string purpose # 操作目的(可选)
--- ---
# Result - 操作结果
bool success # 操作是否成功
string message # 结果消息
string return_info string return_info
bool success
--- ---
string status # Feedback - 实时反馈
string status # 当前状态描述

View File

@@ -9,6 +9,11 @@ string rinsing_solvent
float64 rinsing_volume float64 rinsing_volume
int32 rinsing_repeats int32 rinsing_repeats
bool solid bool solid
float64 flowrate
float64 transfer_flowrate
string rate_spec
string event
string through
--- ---
string return_info string return_info
bool success bool success

View File

@@ -1,9 +1,16 @@
# Organic # Goal - 搅拌操作的目标参数
float64 stir_time string vessel # 搅拌容器名称(必需)
float64 stir_speed string time # 搅拌时间(如 "0.5 h", "30 min"
float64 settling_time string event # 事件标识(如 "A", "B"
string time_spec # 时间规格(如 "several minutes"
float64 stir_time # 解析后的搅拌时间(秒)
float64 stir_speed # 搅拌速度默认200.0
float64 settling_time # 沉降时间默认60.0
--- ---
# Result - 操作结果
bool success # 操作是否成功
string message # 结果消息
string return_info string return_info
bool success
--- ---
string status # Feedback - 实时反馈
string status # 当前状态描述

View File

@@ -1,16 +1,19 @@
string vessel # 装有固体物质的容器名称 # Goal - 固体清洗操作的目标参数
string solvent # 用于清洗固体的溶剂名称 string vessel # 装有固体的容器名称(必需)
float64 volume # 清洗溶剂的体积 string solvent # 清洗溶剂名称(必需)
string filtrate_vessel # 滤液要收集到的容器名称,可选参数 float64 volume # 清洗溶剂体积(必需)
float64 temp # 清洗时的温度,可选参数 string filtrate_vessel # 滤液收集容器(可选,默认""
bool stir # 是否在清洗过程中搅拌,默认为 False float64 temp # 清洗温度可选默认25.0
float64 stir_speed # 搅拌速度,可选参数 bool stir # 是否搅拌可选默认false
float64 time # 清洗的时间,可选参数 float64 stir_speed # 搅拌速度可选默认0.0
int32 repeats # 清洗操作的重复次数,默认为 1 float64 time # 清洗时间可选默认0.0
int32 repeats # 重复次数可选默认1
--- ---
bool success # 操作是否成功 # Result - 操作结果
string message # 结果消息 bool success # 操作是否成功
string message # 结果消息
string return_info string return_info
--- ---
string status # 当前状态描述 # Feedback - 实时反馈
float64 progress # 进度百分比 (0-100) string status # 当前状态描述
float64 progress # 进度百分比 (0-100)