补充了剩下的几个protocol

This commit is contained in:
KCFeng425
2025-07-16 10:38:12 +08:00
parent ac294194e6
commit ed3b22a738
10 changed files with 2229 additions and 536 deletions

View File

@@ -22,18 +22,20 @@ def parse_volume_input(volume_input: Union[str, float]) -> float:
float: 体积(毫升)
"""
if isinstance(volume_input, (int, float)):
debug_print(f"📏 体积输入为数值: {volume_input}")
return float(volume_input)
if not volume_input or not str(volume_input).strip():
debug_print(f"⚠️ 体积输入为空返回0.0mL")
return 0.0
volume_str = str(volume_input).lower().strip()
debug_print(f"解析体积输入: '{volume_str}'")
debug_print(f"🔍 解析体积输入: '{volume_str}'")
# 处理未知体积
if volume_str in ['?', 'unknown', 'tbd', 'to be determined']:
default_volume = 10.0 # 默认10mL
debug_print(f"检测到未知体积,使用默认值: {default_volume}mL")
debug_print(f"检测到未知体积,使用默认值: {default_volume}mL 🎯")
return default_volume
# 移除空格并提取数字和单位
@@ -43,7 +45,7 @@ def parse_volume_input(volume_input: Union[str, float]) -> float:
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter)?', volume_clean)
if not match:
debug_print(f"⚠️ 无法解析体积: '{volume_str}'使用默认值10mL")
debug_print(f" 无法解析体积: '{volume_str}'使用默认值10mL")
return 10.0
value = float(match.group(1))
@@ -52,12 +54,14 @@ def parse_volume_input(volume_input: Union[str, float]) -> float:
# 转换为毫升
if unit in ['l', 'liter']:
volume = value * 1000.0 # L -> mL
debug_print(f"🔄 体积转换: {value}L → {volume}mL")
elif unit in ['μl', 'ul', 'microliter']:
volume = value / 1000.0 # μL -> mL
debug_print(f"🔄 体积转换: {value}μL → {volume}mL")
else: # ml, milliliter 或默认
volume = value # 已经是mL
debug_print(f"✅ 体积已为mL: {volume}mL")
debug_print(f"体积转换: {value}{unit}{volume}mL")
return volume
def parse_mass_input(mass_input: Union[str, float]) -> float:
@@ -71,13 +75,15 @@ def parse_mass_input(mass_input: Union[str, float]) -> float:
float: 质量(克)
"""
if isinstance(mass_input, (int, float)):
debug_print(f"⚖️ 质量输入为数值: {mass_input}g")
return float(mass_input)
if not mass_input or not str(mass_input).strip():
debug_print(f"⚠️ 质量输入为空返回0.0g")
return 0.0
mass_str = str(mass_input).lower().strip()
debug_print(f"解析质量输入: '{mass_str}'")
debug_print(f"🔍 解析质量输入: '{mass_str}'")
# 移除空格并提取数字和单位
mass_clean = re.sub(r'\s+', '', mass_str)
@@ -86,7 +92,7 @@ def parse_mass_input(mass_input: Union[str, float]) -> float:
match = re.match(r'([0-9]*\.?[0-9]+)\s*(g|mg|kg|gram|milligram|kilogram)?', mass_clean)
if not match:
debug_print(f"⚠️ 无法解析质量: '{mass_str}'返回0.0g")
debug_print(f" 无法解析质量: '{mass_str}'返回0.0g")
return 0.0
value = float(match.group(1))
@@ -95,12 +101,14 @@ def parse_mass_input(mass_input: Union[str, float]) -> float:
# 转换为克
if unit in ['mg', 'milligram']:
mass = value / 1000.0 # mg -> g
debug_print(f"🔄 质量转换: {value}mg → {mass}g")
elif unit in ['kg', 'kilogram']:
mass = value * 1000.0 # kg -> g
debug_print(f"🔄 质量转换: {value}kg → {mass}g")
else: # g, gram 或默认
mass = value # 已经是g
debug_print(f"✅ 质量已为g: {mass}g")
debug_print(f"质量转换: {value}{unit}{mass}g")
return mass
def parse_time_input(time_input: Union[str, float]) -> float:
@@ -114,18 +122,20 @@ def parse_time_input(time_input: Union[str, float]) -> float:
float: 时间(秒)
"""
if isinstance(time_input, (int, float)):
debug_print(f"⏱️ 时间输入为数值: {time_input}")
return float(time_input)
if not time_input or not str(time_input).strip():
debug_print(f"⚠️ 时间输入为空返回0秒")
return 0.0
time_str = str(time_input).lower().strip()
debug_print(f"解析时间输入: '{time_str}'")
debug_print(f"🔍 解析时间输入: '{time_str}'")
# 处理未知时间
if time_str in ['?', 'unknown', 'tbd']:
default_time = 60.0 # 默认1分钟
debug_print(f"检测到未知时间,使用默认值: {default_time}s")
debug_print(f"检测到未知时间,使用默认值: {default_time}s (1分钟) ⏰")
return default_time
# 移除空格并提取数字和单位
@@ -135,7 +145,7 @@ def parse_time_input(time_input: Union[str, float]) -> float:
match = re.match(r'([0-9]*\.?[0-9]+)\s*(s|sec|second|min|minute|h|hr|hour|d|day)?', time_clean)
if not match:
debug_print(f"⚠️ 无法解析时间: '{time_str}'返回0s")
debug_print(f" 无法解析时间: '{time_str}'返回0s")
return 0.0
value = float(match.group(1))
@@ -144,21 +154,25 @@ def parse_time_input(time_input: Union[str, float]) -> float:
# 转换为秒
if unit in ['min', 'minute']:
time_sec = value * 60.0 # min -> s
debug_print(f"🔄 时间转换: {value}分钟 → {time_sec}")
elif unit in ['h', 'hr', 'hour']:
time_sec = value * 3600.0 # h -> s
debug_print(f"🔄 时间转换: {value}小时 → {time_sec}")
elif unit in ['d', 'day']:
time_sec = value * 86400.0 # d -> s
debug_print(f"🔄 时间转换: {value}天 → {time_sec}")
else: # s, sec, second 或默认
time_sec = value # 已经是s
debug_print(f"✅ 时间已为秒: {time_sec}")
debug_print(f"时间转换: {value}{unit}{time_sec}s")
return time_sec
def find_reagent_vessel(G: nx.DiGraph, reagent: str) -> str:
"""增强版试剂容器查找,支持固体和液体"""
debug_print(f"查找试剂 '{reagent}' 的容器...")
debug_print(f"🔍 开始查找试剂 '{reagent}' 的容器...")
# 🔧 方法1直接搜索 data.reagent_name 和 config.reagent
debug_print(f"📋 方法1: 搜索reagent字段...")
for node in G.nodes():
node_data = G.nodes[node].get('data', {})
node_type = G.nodes[node].get('type', '')
@@ -171,16 +185,17 @@ def find_reagent_vessel(G: nx.DiGraph, reagent: str) -> str:
# 精确匹配
if reagent_name == reagent.lower() or config_reagent == reagent.lower():
debug_print(f"✅ 通过reagent字段到容器: {node}")
debug_print(f"✅ 通过reagent字段精确匹配到容器: {node} 🎯")
return node
# 模糊匹配
if (reagent.lower() in reagent_name and reagent_name) or \
(reagent.lower() in config_reagent and config_reagent):
debug_print(f"✅ 通过reagent字段模糊匹配到容器: {node}")
debug_print(f"✅ 通过reagent字段模糊匹配到容器: {node} 🔍")
return node
# 🔧 方法2常见的容器命名规则
debug_print(f"📋 方法2: 使用命名规则查找...")
reagent_clean = reagent.lower().replace(' ', '_').replace('-', '_')
possible_names = [
reagent_clean,
@@ -197,20 +212,23 @@ def find_reagent_vessel(G: nx.DiGraph, reagent: str) -> str:
f"reagent_bottle_3"
]
debug_print(f"🔍 尝试的容器名称: {possible_names[:5]}... (共{len(possible_names)}个)")
for name in possible_names:
if name in G.nodes():
node_type = G.nodes[name].get('type', '')
if node_type == 'container':
debug_print(f"✅ 通过命名规则找到容器: {name}")
debug_print(f"✅ 通过命名规则找到容器: {name} 📝")
return name
# 🔧 方法3节点名称模糊匹配
debug_print(f"📋 方法3: 节点名称模糊匹配...")
for node_id in G.nodes():
node_data = G.nodes[node_id]
if node_data.get('type') == 'container':
# 检查节点名称是否包含试剂名称
if reagent_clean in node_id.lower():
debug_print(f"✅ 通过节点名称模糊匹配到容器: {node_id}")
debug_print(f"✅ 通过节点名称模糊匹配到容器: {node_id} 🔍")
return node_id
# 检查液体类型匹配
@@ -220,51 +238,77 @@ def find_reagent_vessel(G: nx.DiGraph, reagent: str) -> str:
if isinstance(liquid, dict):
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
if liquid_type.lower() == reagent.lower():
debug_print(f"✅ 通过液体类型匹配到容器: {node_id}")
debug_print(f"✅ 通过液体类型匹配到容器: {node_id} 💧")
return node_id
# 🔧 方法4使用第一个试剂瓶作为备选
debug_print(f"📋 方法4: 查找备选试剂瓶...")
for node_id in G.nodes():
node_data = G.nodes[node_id]
if (node_data.get('type') == 'container' and
('reagent' in node_id.lower() or 'bottle' in node_id.lower())):
debug_print(f"⚠️ 未找到专用容器,使用备选试剂瓶: {node_id}")
debug_print(f"⚠️ 未找到专用容器,使用备选试剂瓶: {node_id} 🔄")
return node_id
debug_print(f"❌ 所有方法都失败了,无法找到容器!")
raise ValueError(f"找不到试剂 '{reagent}' 对应的容器")
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
"""查找连接到指定容器的搅拌器"""
debug_print(f"🔍 查找连接到容器 '{vessel}' 的搅拌器...")
stirrer_nodes = []
for node in G.nodes():
node_class = G.nodes[node].get('class', '').lower()
if 'stirrer' in node_class:
stirrer_nodes.append(node)
debug_print(f"📋 发现搅拌器: {node}")
debug_print(f"📊 共找到 {len(stirrer_nodes)} 个搅拌器")
# 查找连接到容器的搅拌器
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
debug_print(f"找到连接的搅拌器: {stirrer}")
debug_print(f"找到连接的搅拌器: {stirrer} 🔗")
return stirrer
# 返回第一个搅拌器
if stirrer_nodes:
debug_print(f"使用第一个搅拌器: {stirrer_nodes[0]}")
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个: {stirrer_nodes[0]} 🔄")
return stirrer_nodes[0]
debug_print(f"❌ 未找到任何搅拌器")
return ""
def find_solid_dispenser(G: nx.DiGraph) -> str:
"""查找固体加样器"""
debug_print(f"🔍 查找固体加样器...")
for node in G.nodes():
node_class = G.nodes[node].get('class', '').lower()
if 'solid_dispenser' in node_class or 'dispenser' in node_class:
debug_print(f"找到固体加样器: {node}")
debug_print(f"找到固体加样器: {node} 🥄")
return node
debug_print("⚠️ 未找到固体加样器")
debug_print(f" 未找到固体加样器")
return ""
# 🆕 创建进度日志动作
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
"""创建一个动作日志"""
full_message = f"{emoji} {message}"
debug_print(full_message)
logger.info(full_message)
print(f"[ACTION] {full_message}", flush=True)
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1,
"log_message": full_message
}
}
def generate_add_protocol(
G: nx.DiGraph,
vessel: str,
@@ -301,51 +345,58 @@ def generate_add_protocol(
"""
debug_print("=" * 60)
debug_print("开始生成添加试剂协议")
debug_print(f"原始参数:")
debug_print(f" - vessel: '{vessel}'")
debug_print(f" - reagent: '{reagent}'")
debug_print(f" - volume: {volume} (类型: {type(volume)})")
debug_print(f" - mass: {mass} (类型: {type(mass)})")
debug_print(f" - time: {time} (类型: {type(time)})")
debug_print(f" - mol: '{mol}'")
debug_print(f" - event: '{event}'")
debug_print(f" - rate_spec: '{rate_spec}'")
debug_print("🚀 开始生成添加试剂协议")
debug_print(f"📋 原始参数:")
debug_print(f" 🥼 vessel: '{vessel}'")
debug_print(f" 🧪 reagent: '{reagent}'")
debug_print(f" 📏 volume: {volume} (类型: {type(volume)})")
debug_print(f" ⚖️ mass: {mass} (类型: {type(mass)})")
debug_print(f" ⏱️ time: {time} (类型: {type(time)})")
debug_print(f" 🧬 mol: '{mol}'")
debug_print(f" 🎯 event: '{event}'")
debug_print(f" rate_spec: '{rate_spec}'")
debug_print(f" 🌪️ stir: {stir}")
debug_print(f" 🔄 stir_speed: {stir_speed} rpm")
debug_print("=" * 60)
action_sequence = []
# === 参数验证 ===
debug_print("步骤1: 参数验证...")
debug_print("🔍 步骤1: 参数验证...")
action_sequence.append(create_action_log(f"开始添加试剂 '{reagent}' 到容器 '{vessel}'", "🎬"))
if not vessel:
debug_print("❌ vessel 参数不能为空")
raise ValueError("vessel 参数不能为空")
if not reagent:
debug_print("❌ reagent 参数不能为空")
raise ValueError("reagent 参数不能为空")
if vessel not in G.nodes():
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中")
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
debug_print("✅ 基本参数验证通过")
# === 🔧 关键修复:参数解析 ===
debug_print("步骤2: 参数解析...")
debug_print("🔍 步骤2: 参数解析...")
action_sequence.append(create_action_log("正在解析添加参数...", "🔍"))
# 解析各种参数为数值
final_volume = parse_volume_input(volume)
final_mass = parse_mass_input(mass)
final_time = parse_time_input(time)
debug_print(f"解析结果:")
debug_print(f" - 体积: {final_volume}mL")
debug_print(f" - 质量: {final_mass}g")
debug_print(f" - 时间: {final_time}s")
debug_print(f" - 摩尔: '{mol}'")
debug_print(f" - 事件: '{event}'")
debug_print(f" - 速率: '{rate_spec}'")
debug_print(f"📊 解析结果:")
debug_print(f" 📏 体积: {final_volume}mL")
debug_print(f" ⚖️ 质量: {final_mass}g")
debug_print(f" ⏱️ 时间: {final_time}s")
debug_print(f" 🧬 摩尔: '{mol}'")
debug_print(f" 🎯 事件: '{event}'")
debug_print(f" 速率: '{rate_spec}'")
# === 判断添加类型 ===
debug_print("步骤3: 判断添加类型...")
debug_print("🔍 步骤3: 判断添加类型...")
# 🔧 修复:现在使用解析后的数值进行比较
is_solid = (final_mass > 0 or (mol and mol.strip() != ""))
@@ -357,22 +408,34 @@ def generate_add_protocol(
final_volume = 10.0
debug_print("⚠️ 未指定体积或质量默认为10mL液体")
debug_print(f"添加类型: {'固体' if is_solid else '液体'}")
add_type = "固体" if is_solid else "液体"
add_emoji = "🧂" if is_solid else "💧"
debug_print(f"📋 添加类型: {add_type} {add_emoji}")
action_sequence.append(create_action_log(f"确定添加类型: {add_type} {add_emoji}", "📋"))
# === 执行添加流程 ===
debug_print("步骤4: 执行添加流程...")
debug_print("🔍 步骤4: 执行添加流程...")
try:
if is_solid:
# === 固体添加路径 ===
debug_print(f"使用固体添加路径")
debug_print(f"🧂 使用固体添加路径")
action_sequence.append(create_action_log("开始固体试剂添加流程", "🧂"))
solid_dispenser = find_solid_dispenser(G)
if solid_dispenser:
action_sequence.append(create_action_log(f"找到固体加样器: {solid_dispenser}", "🥄"))
# 启动搅拌
if stir:
debug_print("🌪️ 准备启动搅拌...")
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed} rpm)", "🔄"))
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
@@ -383,6 +446,7 @@ def generate_add_protocol(
}
})
# 等待搅拌稳定
action_sequence.append(create_action_log("等待搅拌稳定...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 3}
@@ -399,19 +463,27 @@ def generate_add_protocol(
if final_mass > 0:
add_kwargs["mass"] = str(final_mass)
action_sequence.append(create_action_log(f"准备添加固体: {final_mass}g", "⚖️"))
if mol and mol.strip():
add_kwargs["mol"] = mol
action_sequence.append(create_action_log(f"按摩尔数添加: {mol}", "🧬"))
if equiv and equiv.strip():
add_kwargs["equiv"] = equiv
action_sequence.append(create_action_log(f"当量: {equiv}", "🔢"))
action_sequence.append(create_action_log("开始固体加样操作", "🥄"))
action_sequence.append({
"device_id": solid_dispenser,
"action_name": "add_solid",
"action_kwargs": add_kwargs
})
action_sequence.append(create_action_log("固体加样完成", ""))
# 添加后等待
if final_time > 0:
wait_minutes = final_time / 60
action_sequence.append(create_action_log(f"等待反应进行 ({wait_minutes:.1f}分钟)", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": final_time}
@@ -419,19 +491,28 @@ def generate_add_protocol(
debug_print(f"✅ 固体添加完成")
else:
debug_print("⚠️ 未找到固体加样器,跳过固体添加")
debug_print(" 未找到固体加样器,跳过固体添加")
action_sequence.append(create_action_log("未找到固体加样器,无法添加固体", ""))
else:
# === 液体添加路径 ===
debug_print(f"使用液体添加路径")
debug_print(f"💧 使用液体添加路径")
action_sequence.append(create_action_log("开始液体试剂添加流程", "💧"))
# 查找试剂容器
action_sequence.append(create_action_log("正在查找试剂容器...", "🔍"))
reagent_vessel = find_reagent_vessel(G, reagent)
action_sequence.append(create_action_log(f"找到试剂容器: {reagent_vessel}", "🧪"))
# 启动搅拌
if stir:
debug_print("🌪️ 准备启动搅拌...")
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed} rpm)", "🔄"))
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
@@ -442,6 +523,7 @@ def generate_add_protocol(
}
})
# 等待搅拌稳定
action_sequence.append(create_action_log("等待搅拌稳定...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
@@ -451,18 +533,23 @@ def generate_add_protocol(
if final_time > 0:
flowrate = final_volume / final_time * 60 # mL/min
transfer_flowrate = flowrate
debug_print(f"⚡ 根据时间计算流速: {flowrate:.2f} mL/min")
else:
if rate_spec == "dropwise":
flowrate = 0.5 # 滴加,很慢
transfer_flowrate = 0.2
debug_print(f"💧 滴加模式,流速: {flowrate} mL/min")
elif viscous:
flowrate = 1.0 # 粘性液体
transfer_flowrate = 0.3
debug_print(f"🍯 粘性液体,流速: {flowrate} mL/min")
else:
flowrate = 2.5 # 正常流速
transfer_flowrate = 0.5
debug_print(f"⚡ 正常流速: {flowrate} mL/min")
debug_print(f"流速设置: {flowrate} mL/min")
action_sequence.append(create_action_log(f"设置流速: {flowrate:.2f} mL/min", ""))
action_sequence.append(create_action_log(f"开始转移 {final_volume}mL 液体", "🚰"))
# 调用pump protocol
pump_actions = generate_pump_protocol_with_rinsing(
@@ -486,9 +573,11 @@ def generate_add_protocol(
)
action_sequence.extend(pump_actions)
debug_print(f"✅ 液体转移完成,添加了 {len(pump_actions)} 个动作")
action_sequence.append(create_action_log(f"液体转移完成 ({len(pump_actions)} 个操作)", ""))
except Exception as e:
debug_print(f"⚠️ 试剂添加失败: {str(e)}")
debug_print(f" 试剂添加失败: {str(e)}")
action_sequence.append(create_action_log(f"试剂添加失败: {str(e)}", ""))
# 添加错误日志
action_sequence.append({
"device_id": "system",
@@ -500,19 +589,28 @@ def generate_add_protocol(
# === 最终结果 ===
debug_print("=" * 60)
debug_print(f" 添加试剂协议生成完成")
debug_print(f"🎉 添加试剂协议生成完成")
debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"📋 处理总结:")
debug_print(f" - 试剂: {reagent}")
debug_print(f" - 添加类型: {'固体' if is_solid else '液体'}")
debug_print(f" - 目标容器: {vessel}")
debug_print(f" 🧪 试剂: {reagent}")
debug_print(f" {add_emoji} 添加类型: {add_type}")
debug_print(f" 🥼 目标容器: {vessel}")
if is_liquid:
debug_print(f" - 体积: {final_volume}mL")
debug_print(f" 📏 体积: {final_volume}mL")
if is_solid:
debug_print(f" - 质量: {final_mass}g")
debug_print(f" - 摩尔: {mol}")
debug_print(f" ⚖️ 质量: {final_mass}g")
debug_print(f" 🧬 摩尔: {mol}")
debug_print("=" * 60)
# 添加完成日志
summary_msg = f"试剂添加协议完成: {reagent}{vessel}"
if is_liquid:
summary_msg += f" ({final_volume}mL)"
if is_solid:
summary_msg += f" ({final_mass}g)"
action_sequence.append(create_action_log(summary_msg, "🎉"))
return action_sequence
# === 便捷函数 ===
@@ -520,6 +618,7 @@ def generate_add_protocol(
def add_liquid_volume(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[str, float],
time: Union[str, float] = 0.0, rate_spec: str = "") -> List[Dict[str, Any]]:
"""添加指定体积的液体试剂"""
debug_print(f"💧 快速添加液体: {reagent} ({volume}) → {vessel}")
return generate_add_protocol(
G, vessel, reagent,
volume=volume,
@@ -530,6 +629,7 @@ def add_liquid_volume(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[st
def add_solid_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float],
event: str = "") -> List[Dict[str, Any]]:
"""添加指定质量的固体试剂"""
debug_print(f"🧂 快速添加固体: {reagent} ({mass}) → {vessel}")
return generate_add_protocol(
G, vessel, reagent,
mass=mass,
@@ -539,6 +639,7 @@ def add_solid_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, fl
def add_solid_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
event: str = "") -> List[Dict[str, Any]]:
"""按摩尔数添加固体试剂"""
debug_print(f"🧬 按摩尔数添加固体: {reagent} ({mol}) → {vessel}")
return generate_add_protocol(
G, vessel, reagent,
mol=mol,
@@ -548,6 +649,7 @@ def add_solid_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
def add_dropwise_liquid(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[str, float],
time: Union[str, float] = "20 min", event: str = "") -> List[Dict[str, Any]]:
"""滴加液体试剂"""
debug_print(f"💧 滴加液体: {reagent} ({volume}) → {vessel} (用时: {time})")
return generate_add_protocol(
G, vessel, reagent,
volume=volume,
@@ -559,6 +661,7 @@ def add_dropwise_liquid(G: nx.DiGraph, vessel: str, reagent: str, volume: Union[
def add_portionwise_solid(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float],
time: Union[str, float] = "1 h", event: str = "") -> List[Dict[str, Any]]:
"""分批添加固体试剂"""
debug_print(f"🧂 分批添加固体: {reagent} ({mass}) → {vessel} (用时: {time})")
return generate_add_protocol(
G, vessel, reagent,
mass=mass,
@@ -573,22 +676,25 @@ def test_add_protocol():
print("=== ADD PROTOCOL 增强版测试 ===")
# 测试体积解析
debug_print("🧪 测试体积解析...")
volumes = ["2.7 mL", "2.67 mL", "?", 10.0, "1 L", "500 μL"]
for vol in volumes:
result = parse_volume_input(vol)
print(f"体积解析: {vol}{result}mL")
print(f"📏 体积解析: {vol}{result}mL")
# 测试质量解析
debug_print("⚖️ 测试质量解析...")
masses = ["19.3 g", "4.5 g", 2.5, "500 mg", "1 kg"]
for mass in masses:
result = parse_mass_input(mass)
print(f"质量解析: {mass}{result}g")
print(f"⚖️ 质量解析: {mass}{result}g")
# 测试时间解析
debug_print("⏱️ 测试时间解析...")
times = ["1 h", "20 min", "30 s", 60.0, "?"]
for time in times:
result = parse_time_input(time)
print(f"时间解析: {time}{result}s")
print(f"⏱️ 时间解析: {time}{result}s")
print("✅ 测试完成")

View File

@@ -1,7 +1,30 @@
import networkx as nx
import logging
from typing import List, Dict, Any
from .pump_protocol import generate_pump_protocol_with_rinsing
logger = logging.getLogger(__name__)
def debug_print(message):
"""调试输出"""
print(f"[ADJUST_PH] {message}", flush=True)
logger.info(f"[ADJUST_PH] {message}")
# 🆕 创建进度日志动作
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
"""创建一个动作日志"""
full_message = f"{emoji} {message}"
debug_print(full_message)
logger.info(full_message)
print(f"[ACTION] {full_message}", flush=True)
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1,
"log_message": full_message
}
}
def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
"""
@@ -14,7 +37,7 @@ def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
Returns:
str: 试剂容器ID
"""
print(f"ADJUST_PH: 正在查找试剂 '{reagent}' 的容器...")
debug_print(f"🔍 正在查找试剂 '{reagent}' 的容器...")
# 常见酸碱试剂的别名映射
reagent_aliases = {
@@ -29,11 +52,16 @@ def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
# 构建搜索名称列表
search_names = [reagent.lower()]
debug_print(f"📋 基础搜索名称: {reagent.lower()}")
# 添加别名
for base_name, aliases in reagent_aliases.items():
if reagent.lower() in base_name.lower() or base_name.lower() in reagent.lower():
search_names.extend([alias.lower() for alias in aliases])
debug_print(f"🔗 添加别名: {aliases}")
break
debug_print(f"📝 完整搜索列表: {search_names}")
# 构建可能的容器名称
possible_names = []
@@ -49,13 +77,17 @@ def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
name_clean
])
debug_print(f"🎯 可能的容器名称 (前5个): {possible_names[:5]}... (共{len(possible_names)}个)")
# 第一步:通过容器名称匹配
debug_print(f"📋 方法1: 精确名称匹配...")
for vessel_name in possible_names:
if vessel_name in G.nodes():
print(f"ADJUST_PH: 通过名称匹配找到容器: {vessel_name}")
debug_print(f" 通过名称匹配找到容器: {vessel_name} 🎯")
return vessel_name
# 第二步:通过模糊匹配
debug_print(f"📋 方法2: 模糊名称匹配...")
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
node_name = G.nodes[node_id].get('name', '').lower()
@@ -63,10 +95,11 @@ def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
# 检查是否包含任何搜索名称
for search_name in search_names:
if search_name in node_id.lower() or search_name in node_name:
print(f"ADJUST_PH: 通过模糊匹配找到容器: {node_id}")
debug_print(f" 通过模糊匹配找到容器: {node_id} 🔍")
return node_id
# 第三步:通过液体类型匹配
debug_print(f"📋 方法3: 液体类型匹配...")
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
vessel_data = G.nodes[node_id].get('data', {})
@@ -79,10 +112,11 @@ def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
for search_name in search_names:
if search_name in liquid_type or search_name in reagent_name:
print(f"ADJUST_PH: 通过液体类型匹配找到容器: {node_id}")
debug_print(f" 通过液体类型匹配找到容器: {node_id} 💧")
return node_id
# 列出可用容器帮助调试
debug_print(f"📊 列出可用容器帮助调试...")
available_containers = []
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
@@ -98,67 +132,92 @@ def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
'reagent_name': vessel_data.get('reagent_name', '')
})
print(f"ADJUST_PH: 可用容器列表:")
debug_print(f"📋 可用容器列表:")
for container in available_containers:
print(f" - {container['id']}: {container['name']}")
print(f" 液体: {container['liquids']}")
print(f" 试剂: {container['reagent_name']}")
debug_print(f" - 🧪 {container['id']}: {container['name']}")
debug_print(f" 💧 液体: {container['liquids']}")
debug_print(f" 🏷️ 试剂: {container['reagent_name']}")
raise ValueError(f"找不到试剂 '{reagent}' 对应的容器。尝试了: {possible_names}")
debug_print(f"❌ 所有匹配方法都失败了")
raise ValueError(f"找不到试剂 '{reagent}' 对应的容器。尝试了: {possible_names[:10]}...")
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
"""查找与容器相连的搅拌器"""
debug_print(f"🔍 查找连接到容器 '{vessel}' 的搅拌器...")
stirrer_nodes = [node for node in G.nodes()
if (G.nodes[node].get('class') or '') == 'virtual_stirrer']
debug_print(f"📊 发现 {len(stirrer_nodes)} 个搅拌器: {stirrer_nodes}")
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
debug_print(f"✅ 找到连接的搅拌器: {stirrer} 🔗")
return stirrer
return stirrer_nodes[0] if stirrer_nodes else None
if stirrer_nodes:
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个: {stirrer_nodes[0]} 🔄")
return stirrer_nodes[0]
debug_print(f"❌ 未找到任何搅拌器")
return None
def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume: float = 100.0) -> float: # 改为 target_ph_value
def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume: float = 100.0) -> float:
"""
估算需要的试剂体积来调节pH
Args:
target_ph_value: 目标pH值 # 改为 target_ph_value
target_ph_value: 目标pH值
reagent: 试剂名称
vessel_volume: 容器体积 (mL)
Returns:
float: 估算的试剂体积 (mL)
"""
debug_print(f"🧮 计算试剂体积...")
debug_print(f" 📍 目标pH: {target_ph_value}")
debug_print(f" 🧪 试剂: {reagent}")
debug_print(f" 📏 容器体积: {vessel_volume}mL")
# 简化的pH调节体积估算实际应用中需要更精确的计算
if "acid" in reagent.lower() or "hcl" in reagent.lower():
debug_print(f"🍋 检测到酸性试剂")
# 酸性试剂pH越低需要的体积越大
if target_ph_value < 3: # 改为 target_ph_value
return vessel_volume * 0.05 # 5%
elif target_ph_value < 5: # 改为 target_ph_value
return vessel_volume * 0.02 # 2%
if target_ph_value < 3:
volume = vessel_volume * 0.05 # 5%
debug_print(f" 💪 强酸性 (pH<3): 使用 5% 体积")
elif target_ph_value < 5:
volume = vessel_volume * 0.02 # 2%
debug_print(f" 🔸 中酸性 (pH<5): 使用 2% 体积")
else:
return vessel_volume * 0.01 # 1%
volume = vessel_volume * 0.01 # 1%
debug_print(f" 🔹 弱酸性 (pH≥5): 使用 1% 体积")
elif "hydroxide" in reagent.lower() or "naoh" in reagent.lower():
debug_print(f"🧂 检测到碱性试剂")
# 碱性试剂pH越高需要的体积越大
if target_ph_value > 11: # 改为 target_ph_value
return vessel_volume * 0.05 # 5%
elif target_ph_value > 9: # 改为 target_ph_value
return vessel_volume * 0.02 # 2%
if target_ph_value > 11:
volume = vessel_volume * 0.05 # 5%
debug_print(f" 💪 强碱性 (pH>11): 使用 5% 体积")
elif target_ph_value > 9:
volume = vessel_volume * 0.02 # 2%
debug_print(f" 🔸 中碱性 (pH>9): 使用 2% 体积")
else:
return vessel_volume * 0.01 # 1%
volume = vessel_volume * 0.01 # 1%
debug_print(f" 🔹 弱碱性 (pH≤9): 使用 1% 体积")
else:
# 未知试剂,使用默认值
return vessel_volume * 0.01
volume = vessel_volume * 0.01
debug_print(f"❓ 未知试剂类型,使用默认 1% 体积")
debug_print(f"📊 计算结果: {volume:.2f}mL")
return volume
def generate_adjust_ph_protocol(
G: nx.DiGraph,
vessel: str,
ph_value: float, # 改为 ph_value
ph_value: float,
reagent: str,
**kwargs
) -> List[Dict[str, Any]]:
@@ -168,13 +227,23 @@ def generate_adjust_ph_protocol(
Args:
G: 有向图,节点为容器和设备
vessel: 目标容器需要调节pH的容器
ph_value: 目标pH值从XDL传入 # 改为 ph_value
ph_value: 目标pH值从XDL传入
reagent: 酸碱试剂名称从XDL传入
**kwargs: 其他可选参数,使用默认值
Returns:
List[Dict[str, Any]]: 动作序列
"""
debug_print("=" * 60)
debug_print("🧪 开始生成pH调节协议")
debug_print(f"📋 原始参数:")
debug_print(f" 🥼 vessel: '{vessel}'")
debug_print(f" 📊 ph_value: {ph_value}")
debug_print(f" 🧪 reagent: '{reagent}'")
debug_print(f" 📦 kwargs: {kwargs}")
debug_print("=" * 60)
action_sequence = []
# 从kwargs中获取可选参数如果没有则使用默认值
@@ -184,48 +253,84 @@ def generate_adjust_ph_protocol(
stir_time = kwargs.get('stir_time', 60.0) # 默认搅拌时间
settling_time = kwargs.get('settling_time', 30.0) # 默认平衡时间
print(f"ADJUST_PH: 开始生成pH调节协议")
print(f" - 目标容器: {vessel}")
print(f" - 目标pH: {ph_value}") # 改为 ph_value
print(f" - 试剂: {reagent}")
print(f" - 使用默认参数: 体积=自动估算, 搅拌=True, 搅拌速度=300RPM")
debug_print(f"🔧 处理后的参数:")
debug_print(f" 📏 volume: {volume}mL (0.0表示自动估算)")
debug_print(f" 🌪️ stir: {stir}")
debug_print(f" 🔄 stir_speed: {stir_speed}rpm")
debug_print(f" ⏱️ stir_time: {stir_time}s")
debug_print(f" ⏳ settling_time: {settling_time}s")
# 开始处理
action_sequence.append(create_action_log(f"开始调节pH至 {ph_value}", "🧪"))
action_sequence.append(create_action_log(f"目标容器: {vessel}", "🥼"))
action_sequence.append(create_action_log(f"使用试剂: {reagent}", "⚗️"))
# 1. 验证目标容器存在
debug_print(f"🔍 步骤1: 验证目标容器...")
if vessel not in G.nodes():
debug_print(f"❌ 目标容器 '{vessel}' 不存在于系统中")
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
debug_print(f"✅ 目标容器验证通过")
action_sequence.append(create_action_log("目标容器验证通过", ""))
# 2. 查找酸碱试剂容器
debug_print(f"🔍 步骤2: 查找试剂容器...")
action_sequence.append(create_action_log("正在查找试剂容器...", "🔍"))
try:
reagent_vessel = find_acid_base_vessel(G, reagent)
print(f"ADJUST_PH: 找到试剂容器: {reagent_vessel}")
debug_print(f" 找到试剂容器: {reagent_vessel}")
action_sequence.append(create_action_log(f"找到试剂容器: {reagent_vessel}", "🧪"))
except ValueError as e:
debug_print(f"❌ 无法找到试剂容器: {str(e)}")
action_sequence.append(create_action_log(f"试剂容器查找失败: {str(e)}", ""))
raise ValueError(f"无法找到试剂 '{reagent}': {str(e)}")
# 3. 如果未指定体积,自动估算
# 3. 体积估算
debug_print(f"🔍 步骤3: 体积处理...")
if volume <= 0:
action_sequence.append(create_action_log("开始自动估算试剂体积", "🧮"))
# 获取目标容器的体积信息
vessel_data = G.nodes[vessel].get('data', {})
vessel_volume = vessel_data.get('max_volume', 100.0) # 默认100mL
debug_print(f"📏 容器最大体积: {vessel_volume}mL")
estimated_volume = calculate_reagent_volume(ph_value, reagent, vessel_volume) # 改为 ph_value
estimated_volume = calculate_reagent_volume(ph_value, reagent, vessel_volume)
volume = estimated_volume
print(f"ADJUST_PH: 自动估算试剂体积: {volume:.2f} mL")
debug_print(f" 自动估算试剂体积: {volume:.2f} mL")
action_sequence.append(create_action_log(f"估算试剂体积: {volume:.2f}mL", "📊"))
else:
debug_print(f"📏 使用指定体积: {volume}mL")
action_sequence.append(create_action_log(f"使用指定体积: {volume}mL", "📏"))
# 4. 验证路径存在
debug_print(f"🔍 步骤4: 路径验证...")
action_sequence.append(create_action_log("验证转移路径...", "🛤️"))
try:
path = nx.shortest_path(G, source=reagent_vessel, target=vessel)
print(f"ADJUST_PH: 找到路径: {''.join(path)}")
debug_print(f" 找到路径: {''.join(path)}")
action_sequence.append(create_action_log(f"找到转移路径: {''.join(path)}", "🛤️"))
except nx.NetworkXNoPath:
debug_print(f"❌ 无法找到转移路径")
action_sequence.append(create_action_log("转移路径不存在", ""))
raise ValueError(f"从试剂容器 '{reagent_vessel}' 到目标容器 '{vessel}' 没有可用路径")
# 5. 先启动搅拌(如果需要)
# 5. 搅拌器设置
debug_print(f"🔍 步骤5: 搅拌器设置...")
stirrer_id = None
if stir:
action_sequence.append(create_action_log("准备启动搅拌器", "🌪️"))
try:
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
print(f"ADJUST_PH: 找到搅拌器 {stirrer_id},启动搅拌")
debug_print(f" 找到搅拌器 {stirrer_id},启动搅拌")
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed}rpm)", "🔄"))
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
@@ -237,23 +342,34 @@ def generate_adjust_ph_protocol(
})
# 等待搅拌稳定
action_sequence.append(create_action_log("等待搅拌稳定...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
})
else:
print(f"ADJUST_PH: 警告 - 未找到搅拌器,继续执行")
debug_print(f"⚠️ 未找到搅拌器,继续执行")
action_sequence.append(create_action_log("未找到搅拌器,跳过搅拌", "⚠️"))
except Exception as e:
print(f"ADJUST_PH: 搅拌器配置出错: {str(e)}")
debug_print(f" 搅拌器配置出错: {str(e)}")
action_sequence.append(create_action_log(f"搅拌器配置失败: {str(e)}", ""))
else:
debug_print(f"📋 跳过搅拌设置")
action_sequence.append(create_action_log("跳过搅拌设置", "⏭️"))
# 6. 缓慢添加试剂 - 使用pump_protocol
print(f"ADJUST_PH: 开始添加试剂 {volume:.2f} mL")
# 6. 试剂添加
debug_print(f"🔍 步骤6: 试剂添加...")
action_sequence.append(create_action_log(f"开始添加试剂 {volume:.2f}mL", "🚰"))
# 计算添加时间pH调节需要缓慢添加
addition_time = max(30.0, volume * 2.0) # 至少30秒每mL需要2秒
debug_print(f"⏱️ 计算添加时间: {addition_time}s (缓慢注入)")
action_sequence.append(create_action_log(f"设置添加时间: {addition_time:.0f}s (缓慢注入)", "⏱️"))
try:
action_sequence.append(create_action_log("调用泵协议进行试剂转移", "🔄"))
pump_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=reagent_vessel,
@@ -266,17 +382,24 @@ def generate_adjust_ph_protocol(
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=0.5 # 缓慢注入
flowrate=0.5, # 缓慢注入
transfer_flowrate=0.3
)
action_sequence.extend(pump_actions)
debug_print(f"✅ 泵协议生成完成,添加了 {len(pump_actions)} 个动作")
action_sequence.append(create_action_log(f"试剂转移完成 ({len(pump_actions)} 个操作)", ""))
except Exception as e:
debug_print(f"❌ 生成泵协议时出错: {str(e)}")
action_sequence.append(create_action_log(f"泵协议生成失败: {str(e)}", ""))
raise ValueError(f"生成泵协议时出错: {str(e)}")
# 7. 持续搅拌以混合和平衡
# 7. 混合搅拌
if stir and stirrer_id:
print(f"ADJUST_PH: 持续搅拌 {stir_time} 秒以混合试剂")
debug_print(f"🔍 步骤7: 混合搅拌...")
action_sequence.append(create_action_log(f"开始混合搅拌 {stir_time:.0f}s", "🌀"))
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stir",
@@ -284,25 +407,47 @@ def generate_adjust_ph_protocol(
"stir_time": stir_time,
"stir_speed": stir_speed,
"settling_time": settling_time,
"purpose": f"pH调节: 混合试剂目标pH={ph_value}" # 改为 ph_value
"purpose": f"pH调节: 混合试剂目标pH={ph_value}"
}
})
debug_print(f"✅ 混合搅拌设置完成")
else:
debug_print(f"⏭️ 跳过混合搅拌")
action_sequence.append(create_action_log("跳过混合搅拌", "⏭️"))
# 8. 等待平衡
debug_print(f"🔍 步骤8: 反应平衡...")
action_sequence.append(create_action_log(f"等待pH平衡 {settling_time:.0f}s", "⚖️"))
# 8. 等待反应平衡
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": settling_time,
"description": f"等待pH平衡到目标值 {ph_value}" # 改为 ph_value
"description": f"等待pH平衡到目标值 {ph_value}"
}
})
print(f"ADJUST_PH: 协议生成完成,共 {len(action_sequence)} 个动作")
print(f"ADJUST_PH: 预计总时间: {addition_time + stir_time + settling_time:.0f}")
# 9. 完成总结
total_time = addition_time + stir_time + settling_time
debug_print("=" * 60)
debug_print(f"🎉 pH调节协议生成完成")
debug_print(f"📊 协议统计:")
debug_print(f" 📋 总动作数: {len(action_sequence)}")
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f}分钟)")
debug_print(f" 🧪 试剂: {reagent}")
debug_print(f" 📏 体积: {volume:.2f}mL")
debug_print(f" 📊 目标pH: {ph_value}")
debug_print(f" 🥼 目标容器: {vessel}")
debug_print("=" * 60)
# 添加完成日志
summary_msg = f"pH调节协议完成: {vessel} → pH {ph_value} (使用 {volume:.2f}mL {reagent})"
action_sequence.append(create_action_log(summary_msg, "🎉"))
return action_sequence
def generate_adjust_ph_protocol_stepwise(
G: nx.DiGraph,
vessel: str,
@@ -317,7 +462,7 @@ def generate_adjust_ph_protocol_stepwise(
Args:
G: 网络图
vessel: 目标容器
pH: 目标pH值
ph_value: 目标pH值
reagent: 酸碱试剂
max_volume: 最大试剂体积
steps: 分步数量
@@ -325,15 +470,28 @@ def generate_adjust_ph_protocol_stepwise(
Returns:
List[Dict[str, Any]]: 动作序列
"""
action_sequence = []
debug_print("=" * 60)
debug_print(f"🔄 开始分步pH调节")
debug_print(f"📋 分步参数:")
debug_print(f" 🥼 vessel: {vessel}")
debug_print(f" 📊 ph_value: {ph_value}")
debug_print(f" 🧪 reagent: {reagent}")
debug_print(f" 📏 max_volume: {max_volume}mL")
debug_print(f" 🔢 steps: {steps}")
debug_print("=" * 60)
print(f"ADJUST_PH: 开始分步pH调节{steps}步)")
action_sequence = []
# 每步添加的体积
step_volume = max_volume / steps
debug_print(f"📊 每步体积: {step_volume:.2f}mL")
action_sequence.append(create_action_log(f"开始分步pH调节 ({steps}步)", "🔄"))
action_sequence.append(create_action_log(f"每步添加: {step_volume:.2f}mL", "📏"))
for i in range(steps):
print(f"ADJUST_PH: {i+1}/{steps} 步,添加 {step_volume} mL")
debug_print(f"🔄 执行{i+1}/{steps} 步,添加 {step_volume:.2f}mL")
action_sequence.append(create_action_log(f"{i+1}/{steps} 步开始", "🚀"))
# 生成单步协议
step_actions = generate_adjust_ph_protocol(
@@ -349,9 +507,13 @@ def generate_adjust_ph_protocol_stepwise(
)
action_sequence.extend(step_actions)
debug_print(f"✅ 第 {i+1}/{steps} 步完成,添加了 {len(step_actions)} 个动作")
action_sequence.append(create_action_log(f"{i+1}/{steps} 步完成", ""))
# 步骤间等待
if i < steps - 1:
debug_print(f"⏳ 步骤间等待30s")
action_sequence.append(create_action_log("步骤间等待...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
@@ -360,10 +522,11 @@ def generate_adjust_ph_protocol_stepwise(
}
})
print(f"ADJUST_PH: 分步pH调节完成")
debug_print(f"🎉 分步pH调节完成{len(action_sequence)} 个动作")
action_sequence.append(create_action_log("分步pH调节全部完成", "🎉"))
return action_sequence
# 便捷函数常用pH调节
def generate_acidify_protocol(
G: nx.DiGraph,
@@ -372,11 +535,11 @@ def generate_acidify_protocol(
acid: str = "hydrochloric acid"
) -> List[Dict[str, Any]]:
"""酸化协议"""
debug_print(f"🍋 生成酸化协议: {vessel} → pH {target_ph} (使用 {acid})")
return generate_adjust_ph_protocol(
G, vessel, target_ph, acid, 0.0, True, 300.0, 120.0, 60.0
G, vessel, target_ph, acid
)
def generate_basify_protocol(
G: nx.DiGraph,
vessel: str,
@@ -384,28 +547,42 @@ def generate_basify_protocol(
base: str = "sodium hydroxide"
) -> List[Dict[str, Any]]:
"""碱化协议"""
debug_print(f"🧂 生成碱化协议: {vessel} → pH {target_ph} (使用 {base})")
return generate_adjust_ph_protocol(
G, vessel, target_ph, base, 0.0, True, 300.0, 120.0, 60.0
G, vessel, target_ph, base
)
def generate_neutralize_protocol(
G: nx.DiGraph,
vessel: str,
reagent: str = "sodium hydroxide"
) -> List[Dict[str, Any]]:
"""中和协议pH=7"""
debug_print(f"⚖️ 生成中和协议: {vessel} → pH 7.0 (使用 {reagent})")
return generate_adjust_ph_protocol(
G, vessel, 7.0, reagent, 0.0, True, 350.0, 180.0, 90.0
G, vessel, 7.0, reagent
)
# 测试函数
def test_adjust_ph_protocol():
"""测试pH调节协议"""
print("=== ADJUST PH PROTOCOL 测试 ===")
print("测试完成")
debug_print("=== ADJUST PH PROTOCOL 增强版测试 ===")
# 测试体积计算
debug_print("🧮 测试体积计算...")
test_cases = [
(2.0, "hydrochloric acid", 100.0),
(4.0, "hydrochloric acid", 100.0),
(12.0, "sodium hydroxide", 100.0),
(10.0, "sodium hydroxide", 100.0),
(7.0, "unknown reagent", 100.0)
]
for ph, reagent, volume in test_cases:
result = calculate_reagent_volume(ph, reagent, volume)
debug_print(f"📊 {reagent} → pH {ph}: {result:.2f}mL")
debug_print("✅ 测试完成")
if __name__ == "__main__":
test_adjust_ph_protocol()

View File

@@ -11,6 +11,22 @@ def debug_print(message):
print(f"[DISSOLVE] {message}", flush=True)
logger.info(f"[DISSOLVE] {message}")
# 🆕 创建进度日志动作
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
"""创建一个动作日志"""
full_message = f"{emoji} {message}"
debug_print(full_message)
logger.info(full_message)
print(f"[ACTION] {full_message}", flush=True)
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1,
"log_message": full_message
}
}
def parse_volume_input(volume_input: Union[str, float]) -> float:
"""
解析体积输入,支持带单位的字符串
@@ -22,18 +38,20 @@ def parse_volume_input(volume_input: Union[str, float]) -> float:
float: 体积(毫升)
"""
if isinstance(volume_input, (int, float)):
debug_print(f"📏 体积输入为数值: {volume_input}")
return float(volume_input)
if not volume_input or not str(volume_input).strip():
debug_print(f"⚠️ 体积输入为空返回0.0mL")
return 0.0
volume_str = str(volume_input).lower().strip()
debug_print(f"解析体积输入: '{volume_str}'")
debug_print(f"🔍 解析体积输入: '{volume_str}'")
# 处理未知体积
if volume_str in ['?', 'unknown', 'tbd', 'to be determined']:
default_volume = 50.0 # 默认50mL
debug_print(f"检测到未知体积,使用默认值: {default_volume}mL")
debug_print(f"检测到未知体积,使用默认值: {default_volume}mL 🎯")
return default_volume
# 移除空格并提取数字和单位
@@ -43,7 +61,7 @@ def parse_volume_input(volume_input: Union[str, float]) -> float:
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter)?', volume_clean)
if not match:
debug_print(f"⚠️ 无法解析体积: '{volume_str}'使用默认值50mL")
debug_print(f" 无法解析体积: '{volume_str}'使用默认值50mL")
return 50.0
value = float(match.group(1))
@@ -52,12 +70,14 @@ def parse_volume_input(volume_input: Union[str, float]) -> float:
# 转换为毫升
if unit in ['l', 'liter']:
volume = value * 1000.0 # L -> mL
debug_print(f"🔄 体积转换: {value}L → {volume}mL")
elif unit in ['μl', 'ul', 'microliter']:
volume = value / 1000.0 # μL -> mL
debug_print(f"🔄 体积转换: {value}μL → {volume}mL")
else: # ml, milliliter 或默认
volume = value # 已经是mL
debug_print(f"✅ 体积已为mL: {volume}mL")
debug_print(f"体积转换: {value}{unit}{volume}mL")
return volume
def parse_mass_input(mass_input: Union[str, float]) -> float:
@@ -71,18 +91,20 @@ def parse_mass_input(mass_input: Union[str, float]) -> float:
float: 质量(克)
"""
if isinstance(mass_input, (int, float)):
debug_print(f"⚖️ 质量输入为数值: {mass_input}g")
return float(mass_input)
if not mass_input or not str(mass_input).strip():
debug_print(f"⚠️ 质量输入为空返回0.0g")
return 0.0
mass_str = str(mass_input).lower().strip()
debug_print(f"解析质量输入: '{mass_str}'")
debug_print(f"🔍 解析质量输入: '{mass_str}'")
# 处理未知质量
if mass_str in ['?', 'unknown', 'tbd', 'to be determined']:
default_mass = 1.0 # 默认1g
debug_print(f"检测到未知质量,使用默认值: {default_mass}g")
debug_print(f"检测到未知质量,使用默认值: {default_mass}g 🎯")
return default_mass
# 移除空格并提取数字和单位
@@ -92,7 +114,7 @@ def parse_mass_input(mass_input: Union[str, float]) -> float:
match = re.match(r'([0-9]*\.?[0-9]+)\s*(g|mg|kg|gram|milligram|kilogram)?', mass_clean)
if not match:
debug_print(f"⚠️ 无法解析质量: '{mass_str}'返回0.0g")
debug_print(f" 无法解析质量: '{mass_str}'返回0.0g")
return 0.0
value = float(match.group(1))
@@ -101,12 +123,14 @@ def parse_mass_input(mass_input: Union[str, float]) -> float:
# 转换为克
if unit in ['mg', 'milligram']:
mass = value / 1000.0 # mg -> g
debug_print(f"🔄 质量转换: {value}mg → {mass}g")
elif unit in ['kg', 'kilogram']:
mass = value * 1000.0 # kg -> g
debug_print(f"🔄 质量转换: {value}kg → {mass}g")
else: # g, gram 或默认
mass = value # 已经是g
debug_print(f"✅ 质量已为g: {mass}g")
debug_print(f"质量转换: {value}{unit}{mass}g")
return mass
def parse_time_input(time_input: Union[str, float]) -> float:
@@ -120,18 +144,20 @@ def parse_time_input(time_input: Union[str, float]) -> float:
float: 时间(秒)
"""
if isinstance(time_input, (int, float)):
debug_print(f"⏱️ 时间输入为数值: {time_input}")
return float(time_input)
if not time_input or not str(time_input).strip():
debug_print(f"⚠️ 时间输入为空返回0秒")
return 0.0
time_str = str(time_input).lower().strip()
debug_print(f"解析时间输入: '{time_str}'")
debug_print(f"🔍 解析时间输入: '{time_str}'")
# 处理未知时间
if time_str in ['?', 'unknown', 'tbd']:
default_time = 600.0 # 默认10分钟
debug_print(f"检测到未知时间,使用默认值: {default_time}s")
debug_print(f"检测到未知时间,使用默认值: {default_time}s (10分钟) ⏰")
return default_time
# 移除空格并提取数字和单位
@@ -141,7 +167,7 @@ def parse_time_input(time_input: Union[str, float]) -> float:
match = re.match(r'([0-9]*\.?[0-9]+)\s*(s|sec|second|min|minute|h|hr|hour|d|day)?', time_clean)
if not match:
debug_print(f"⚠️ 无法解析时间: '{time_str}'返回0s")
debug_print(f" 无法解析时间: '{time_str}'返回0s")
return 0.0
value = float(match.group(1))
@@ -150,14 +176,17 @@ def parse_time_input(time_input: Union[str, float]) -> float:
# 转换为秒
if unit in ['min', 'minute']:
time_sec = value * 60.0 # min -> s
debug_print(f"🔄 时间转换: {value}分钟 → {time_sec}")
elif unit in ['h', 'hr', 'hour']:
time_sec = value * 3600.0 # h -> s
debug_print(f"🔄 时间转换: {value}小时 → {time_sec}")
elif unit in ['d', 'day']:
time_sec = value * 86400.0 # d -> s
debug_print(f"🔄 时间转换: {value}天 → {time_sec}")
else: # s, sec, second 或默认
time_sec = value # 已经是s
debug_print(f"✅ 时间已为秒: {time_sec}")
debug_print(f"时间转换: {value}{unit}{time_sec}s")
return time_sec
def parse_temperature_input(temp_input: Union[str, float]) -> float:
@@ -171,13 +200,15 @@ def parse_temperature_input(temp_input: Union[str, float]) -> float:
float: 温度(摄氏度)
"""
if isinstance(temp_input, (int, float)):
debug_print(f"🌡️ 温度输入为数值: {temp_input}°C")
return float(temp_input)
if not temp_input or not str(temp_input).strip():
debug_print(f"⚠️ 温度输入为空使用默认室温25°C")
return 25.0 # 默认室温
temp_str = str(temp_input).lower().strip()
debug_print(f"解析温度输入: '{temp_str}'")
debug_print(f"🔍 解析温度输入: '{temp_str}'")
# 处理特殊温度描述
temp_aliases = {
@@ -193,7 +224,7 @@ def parse_temperature_input(temp_input: Union[str, float]) -> float:
if temp_str in temp_aliases:
result = temp_aliases[temp_str]
debug_print(f"温度别名解析: '{temp_str}'{result}°C")
debug_print(f"🏷️ 温度别名解析: '{temp_str}'{result}°C")
return result
# 移除空格并提取数字和单位
@@ -203,7 +234,7 @@ def parse_temperature_input(temp_input: Union[str, float]) -> float:
match = re.match(r'([0-9]*\.?[0-9]+)\s*(°c|c|celsius|°f|f|fahrenheit|k|kelvin)?', temp_clean)
if not match:
debug_print(f"⚠️ 无法解析温度: '{temp_str}'使用默认值25°C")
debug_print(f" 无法解析温度: '{temp_str}'使用默认值25°C")
return 25.0
value = float(match.group(1))
@@ -212,19 +243,22 @@ def parse_temperature_input(temp_input: Union[str, float]) -> float:
# 转换为摄氏度
if unit in ['°f', 'f', 'fahrenheit']:
temp_c = (value - 32) * 5/9 # F -> C
debug_print(f"🔄 温度转换: {value}°F → {temp_c:.1f}°C")
elif unit in ['k', 'kelvin']:
temp_c = value - 273.15 # K -> C
debug_print(f"🔄 温度转换: {value}K → {temp_c:.1f}°C")
else: # °c, c, celsius 或默认
temp_c = value # 已经是C
debug_print(f"✅ 温度已为°C: {temp_c}°C")
debug_print(f"温度转换: {value}{unit}{temp_c}°C")
return temp_c
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
"""增强版溶剂容器查找"""
debug_print(f"查找溶剂 '{solvent}' 的容器...")
"""增强版溶剂容器查找,支持多种匹配模式"""
debug_print(f"🔍 开始查找溶剂 '{solvent}' 的容器...")
# 🔧 方法1直接搜索 data.reagent_name 和 config.reagent
debug_print(f"📋 方法1: 搜索reagent字段...")
for node in G.nodes():
node_data = G.nodes[node].get('data', {})
node_type = G.nodes[node].get('type', '')
@@ -237,18 +271,20 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
# 精确匹配
if reagent_name == solvent.lower() or config_reagent == solvent.lower():
debug_print(f"✅ 通过reagent字段到容器: {node}")
debug_print(f"✅ 通过reagent字段精确匹配到容器: {node} 🎯")
return node
# 模糊匹配
if (solvent.lower() in reagent_name and reagent_name) or \
(solvent.lower() in config_reagent and config_reagent):
debug_print(f"✅ 通过reagent字段模糊匹配到容器: {node}")
debug_print(f"✅ 通过reagent字段模糊匹配到容器: {node} 🔍")
return node
# 🔧 方法2常见的容器命名规则
debug_print(f"📋 方法2: 使用命名规则查找...")
solvent_clean = solvent.lower().replace(' ', '_').replace('-', '_')
possible_names = [
solvent_clean,
f"flask_{solvent_clean}",
f"bottle_{solvent_clean}",
f"vessel_{solvent_clean}",
@@ -256,77 +292,118 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
f"{solvent_clean}_bottle",
f"solvent_{solvent_clean}",
f"reagent_{solvent_clean}",
f"reagent_bottle_{solvent_clean}"
f"reagent_bottle_{solvent_clean}",
f"reagent_bottle_1", # 通用试剂瓶
f"reagent_bottle_2",
f"reagent_bottle_3"
]
debug_print(f"🔍 尝试的容器名称: {possible_names[:5]}... (共{len(possible_names)}个)")
for name in possible_names:
if name in G.nodes():
node_type = G.nodes[name].get('type', '')
if node_type == 'container':
debug_print(f"✅ 通过命名规则找到容器: {name}")
debug_print(f"✅ 通过命名规则找到容器: {name} 📝")
return name
# 🔧 方法3使用第一个试剂瓶作为备选
# 🔧 方法3节点名称模糊匹配
debug_print(f"📋 方法3: 节点名称模糊匹配...")
for node_id in G.nodes():
node_data = G.nodes[node_id]
if node_data.get('type') == 'container':
# 检查节点名称是否包含溶剂名称
if solvent_clean in node_id.lower():
debug_print(f"✅ 通过节点名称模糊匹配到容器: {node_id} 🔍")
return node_id
# 检查液体类型匹配
vessel_data = node_data.get('data', {})
liquids = vessel_data.get('liquid', [])
for liquid in liquids:
if isinstance(liquid, dict):
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
if liquid_type.lower() == solvent.lower():
debug_print(f"✅ 通过液体类型匹配到容器: {node_id} 💧")
return node_id
# 🔧 方法4使用第一个试剂瓶作为备选
debug_print(f"📋 方法4: 查找备选试剂瓶...")
for node_id in G.nodes():
node_data = G.nodes[node_id]
if (node_data.get('type') == 'container' and
('reagent' in node_id.lower() or 'bottle' in node_id.lower() or 'flask' in node_id.lower())):
debug_print(f"⚠️ 未找到专用容器,使用备选容器: {node_id}")
debug_print(f"⚠️ 未找到专用容器,使用备选试剂瓶: {node_id} 🔄")
return node_id
debug_print(f"❌ 所有方法都失败了,无法找到容器!")
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器")
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
"""查找连接到指定容器的加热搅拌器"""
debug_print(f"🔍 查找连接到容器 '{vessel}' 的加热搅拌器...")
heatchill_nodes = []
for node in G.nodes():
node_class = G.nodes[node].get('class', '').lower()
if 'heatchill' in node_class:
heatchill_nodes.append(node)
debug_print(f"📋 发现加热搅拌器: {node}")
debug_print(f"📊 共找到 {len(heatchill_nodes)} 个加热搅拌器")
# 查找连接到容器的加热器
for heatchill in heatchill_nodes:
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
debug_print(f"找到连接的加热器: {heatchill}")
debug_print(f"找到连接的加热搅拌器: {heatchill} 🔗")
return heatchill
# 返回第一个加热器
if heatchill_nodes:
debug_print(f"使用第一个加热器: {heatchill_nodes[0]}")
debug_print(f"⚠️ 未找到直接连接的加热搅拌器,使用第一个: {heatchill_nodes[0]} 🔄")
return heatchill_nodes[0]
debug_print(f"❌ 未找到任何加热搅拌器")
return ""
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
"""查找连接到指定容器的搅拌器"""
debug_print(f"🔍 查找连接到容器 '{vessel}' 的搅拌器...")
stirrer_nodes = []
for node in G.nodes():
node_class = G.nodes[node].get('class', '').lower()
if 'stirrer' in node_class:
stirrer_nodes.append(node)
debug_print(f"📋 发现搅拌器: {node}")
debug_print(f"📊 共找到 {len(stirrer_nodes)} 个搅拌器")
# 查找连接到容器的搅拌器
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
debug_print(f"找到连接的搅拌器: {stirrer}")
debug_print(f"找到连接的搅拌器: {stirrer} 🔗")
return stirrer
# 返回第一个搅拌器
if stirrer_nodes:
debug_print(f"使用第一个搅拌器: {stirrer_nodes[0]}")
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个: {stirrer_nodes[0]} 🔄")
return stirrer_nodes[0]
debug_print(f"❌ 未找到任何搅拌器")
return ""
def find_solid_dispenser(G: nx.DiGraph) -> str:
"""查找固体加样器"""
debug_print(f"🔍 查找固体加样器...")
for node in G.nodes():
node_class = G.nodes[node].get('class', '').lower()
if 'solid_dispenser' in node_class or 'dispenser' in node_class:
debug_print(f"找到固体加样器: {node}")
debug_print(f"找到固体加样器: {node} 🥄")
return node
debug_print("⚠️ 未找到固体加样器")
debug_print(f" 未找到固体加样器")
return ""
def generate_dissolve_protocol(
@@ -347,12 +424,13 @@ def generate_dissolve_protocol(
**kwargs # 🔧 关键接受所有其他参数防止unexpected keyword错误
) -> List[Dict[str, Any]]:
"""
生成溶解操作的协议序列 - 修复
生成溶解操作的协议序列 - 增强
🔧 修复要点:
1. 添加action文件中的所有参数mass, mol, reagent, event
2. 使用 **kwargs 接受所有额外参数,防止 unexpected keyword argument 错误
3. 支持固体溶解和液体溶解两种模式
4. 添加详细的emoji日志系统
支持两种溶解模式:
1. 液体溶解:指定 solvent + volume使用pump protocol转移溶剂
@@ -367,35 +445,40 @@ def generate_dissolve_protocol(
"""
debug_print("=" * 60)
debug_print("开始生成溶解协议 - 修复版")
debug_print(f"原始参数:")
debug_print(f" - vessel: '{vessel}'")
debug_print(f" - solvent: '{solvent}'")
debug_print(f" - volume: {volume} (类型: {type(volume)})")
debug_print(f" - mass: {mass} (类型: {type(mass)})")
debug_print(f" - temp: {temp} (类型: {type(temp)})")
debug_print(f" - time: {time} (类型: {type(time)})")
debug_print(f" - reagent: '{reagent}'")
debug_print(f" - mol: '{mol}'")
debug_print(f" - event: '{event}'")
debug_print(f" - kwargs: {kwargs}") # 显示额外参数
debug_print("🧪 开始生成溶解协议")
debug_print(f"📋 原始参数:")
debug_print(f" 🥼 vessel: '{vessel}'")
debug_print(f" 💧 solvent: '{solvent}'")
debug_print(f" 📏 volume: {volume} (类型: {type(volume)})")
debug_print(f" ⚖️ mass: {mass} (类型: {type(mass)})")
debug_print(f" 🌡️ temp: {temp} (类型: {type(temp)})")
debug_print(f" ⏱️ time: {time} (类型: {type(time)})")
debug_print(f" 🧪 reagent: '{reagent}'")
debug_print(f" 🧬 mol: '{mol}'")
debug_print(f" 🎯 event: '{event}'")
debug_print(f" 📦 kwargs: {kwargs}") # 显示额外参数
debug_print("=" * 60)
action_sequence = []
# === 参数验证 ===
debug_print("步骤1: 参数验证...")
debug_print("🔍 步骤1: 参数验证...")
action_sequence.append(create_action_log(f"开始溶解操作 - 容器: {vessel}", "🎬"))
if not vessel:
debug_print("❌ vessel 参数不能为空")
raise ValueError("vessel 参数不能为空")
if vessel not in G.nodes():
debug_print(f"❌ 容器 '{vessel}' 不存在于系统中")
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
debug_print("✅ 基本参数验证通过")
action_sequence.append(create_action_log("参数验证通过", ""))
# === 🔧 关键修复:参数解析 ===
debug_print("步骤2: 参数解析...")
debug_print("🔍 步骤2: 参数解析...")
action_sequence.append(create_action_log("正在解析溶解参数...", "🔍"))
# 解析各种参数为数值
final_volume = parse_volume_input(volume)
@@ -403,17 +486,18 @@ def generate_dissolve_protocol(
final_temp = parse_temperature_input(temp)
final_time = parse_time_input(time)
debug_print(f"解析结果:")
debug_print(f" - 体积: {final_volume}mL")
debug_print(f" - 质量: {final_mass}g")
debug_print(f" - 温度: {final_temp}°C")
debug_print(f" - 时间: {final_time}s")
debug_print(f" - 试剂: '{reagent}'")
debug_print(f" - 摩尔: '{mol}'")
debug_print(f" - 事件: '{event}'")
debug_print(f"📊 解析结果:")
debug_print(f" 📏 体积: {final_volume}mL")
debug_print(f" ⚖️ 质量: {final_mass}g")
debug_print(f" 🌡️ 温度: {final_temp}°C")
debug_print(f" ⏱️ 时间: {final_time}s")
debug_print(f" 🧪 试剂: '{reagent}'")
debug_print(f" 🧬 摩尔: '{mol}'")
debug_print(f" 🎯 事件: '{event}'")
# === 判断溶解类型 ===
debug_print("步骤3: 判断溶解类型...")
debug_print("🔍 步骤3: 判断溶解类型...")
action_sequence.append(create_action_log("正在判断溶解类型...", "🔍"))
# 判断是固体溶解还是液体溶解
is_solid_dissolve = (final_mass > 0 or (mol and mol.strip() != "") or (reagent and reagent.strip() != ""))
@@ -427,10 +511,15 @@ def generate_dissolve_protocol(
solvent = "water" # 默认溶剂
debug_print("⚠️ 未明确指定溶解参数默认为50mL水溶解")
debug_print(f"溶解类型: {'固体溶解' if is_solid_dissolve else '液体溶解'}")
dissolve_type = "固体溶解" if is_solid_dissolve else "液体溶解"
dissolve_emoji = "🧂" if is_solid_dissolve else "💧"
debug_print(f"📋 溶解类型: {dissolve_type} {dissolve_emoji}")
action_sequence.append(create_action_log(f"确定溶解类型: {dissolve_type} {dissolve_emoji}", "📋"))
# === 查找设备 ===
debug_print("步骤4: 查找设备...")
debug_print("🔍 步骤4: 查找设备...")
action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
# 查找加热搅拌器
heatchill_id = find_connected_heatchill(G, vessel)
@@ -439,21 +528,31 @@ def generate_dissolve_protocol(
# 优先使用加热搅拌器,否则使用独立搅拌器
stir_device_id = heatchill_id or stirrer_id
debug_print(f"设备映射:")
debug_print(f" - 加热器: '{heatchill_id}'")
debug_print(f" - 搅拌器: '{stirrer_id}'")
debug_print(f" - 使用设备: '{stir_device_id}'")
debug_print(f"📊 设备映射:")
debug_print(f" 🔥 加热器: '{heatchill_id}'")
debug_print(f" 🌪️ 搅拌器: '{stirrer_id}'")
debug_print(f" 🎯 使用设备: '{stir_device_id}'")
if heatchill_id:
action_sequence.append(create_action_log(f"找到加热搅拌器: {heatchill_id}", "🔥"))
elif stirrer_id:
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_id}", "🌪️"))
else:
action_sequence.append(create_action_log("未找到搅拌设备,将跳过搅拌", "⚠️"))
# === 执行溶解流程 ===
debug_print("步骤5: 执行溶解流程...")
debug_print("🔍 步骤5: 执行溶解流程...")
try:
# 步骤5.1: 启动加热搅拌(如果需要)
if stir_device_id and (final_temp > 25.0 or final_time > 0 or stir_speed > 0):
debug_print(f"5.1: 启动加热搅拌,温度: {final_temp}°C")
debug_print(f"🔍 5.1: 启动加热搅拌,温度: {final_temp}°C")
action_sequence.append(create_action_log(f"准备加热搅拌 (目标温度: {final_temp}°C)", "🔥"))
if heatchill_id and (final_temp > 25.0 or final_time > 0):
# 使用加热搅拌器
action_sequence.append(create_action_log(f"启动加热搅拌器 {heatchill_id}", "🔥"))
heatchill_action = {
"device_id": heatchill_id,
"action_name": "heat_chill_start",
@@ -468,6 +567,7 @@ def generate_dissolve_protocol(
# 等待温度稳定
if final_temp > 25.0:
wait_time = min(60, abs(final_temp - 25.0) * 1.5)
action_sequence.append(create_action_log(f"等待温度稳定 ({wait_time:.0f}秒)", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": wait_time}
@@ -475,6 +575,8 @@ def generate_dissolve_protocol(
elif stirrer_id:
# 使用独立搅拌器
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {stir_speed}rpm)", "🌪️"))
stir_action = {
"device_id": stirrer_id,
"action_name": "start_stir",
@@ -487,6 +589,7 @@ def generate_dissolve_protocol(
action_sequence.append(stir_action)
# 等待搅拌稳定
action_sequence.append(create_action_log("等待搅拌稳定...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
@@ -494,10 +597,13 @@ def generate_dissolve_protocol(
if is_solid_dissolve:
# === 固体溶解路径 ===
debug_print(f"5.2: 使用固体溶解路径")
debug_print(f"🔍 5.2: 使用固体溶解路径")
action_sequence.append(create_action_log("开始固体溶解流程", "🧂"))
solid_dispenser = find_solid_dispenser(G)
if solid_dispenser:
action_sequence.append(create_action_log(f"找到固体加样器: {solid_dispenser}", "🥄"))
# 固体加样
add_kwargs = {
"vessel": vessel,
@@ -508,9 +614,12 @@ def generate_dissolve_protocol(
if final_mass > 0:
add_kwargs["mass"] = str(final_mass)
action_sequence.append(create_action_log(f"准备添加固体: {final_mass}g", "⚖️"))
if mol and mol.strip():
add_kwargs["mol"] = mol
action_sequence.append(create_action_log(f"按摩尔数添加: {mol}", "🧬"))
action_sequence.append(create_action_log("开始固体加样操作", "🥄"))
action_sequence.append({
"device_id": solid_dispenser,
"action_name": "add_solid",
@@ -518,18 +627,24 @@ def generate_dissolve_protocol(
})
debug_print(f"✅ 固体加样完成")
action_sequence.append(create_action_log("固体加样完成", ""))
else:
debug_print("⚠️ 未找到固体加样器,跳过固体添加")
action_sequence.append(create_action_log("未找到固体加样器,无法添加固体", ""))
elif is_liquid_dissolve:
# === 液体溶解路径 ===
debug_print(f"5.3: 使用液体溶解路径")
debug_print(f"🔍 5.3: 使用液体溶解路径")
action_sequence.append(create_action_log("开始液体溶解流程", "💧"))
# 查找溶剂容器
action_sequence.append(create_action_log("正在查找溶剂容器...", "🔍"))
try:
solvent_vessel = find_solvent_vessel(G, solvent)
action_sequence.append(create_action_log(f"找到溶剂容器: {solvent_vessel}", "🧪"))
except ValueError as e:
debug_print(f"⚠️ {str(e)},跳过溶剂添加")
action_sequence.append(create_action_log(f"溶剂容器查找失败: {str(e)}", ""))
solvent_vessel = None
if solvent_vessel:
@@ -537,6 +652,9 @@ def generate_dissolve_protocol(
flowrate = 1.0 # 较慢的注入速度
transfer_flowrate = 0.5 # 较慢的转移速度
action_sequence.append(create_action_log(f"设置流速: {flowrate}mL/min (缓慢注入)", ""))
action_sequence.append(create_action_log(f"开始转移 {final_volume}mL {solvent}", "🚰"))
# 调用pump protocol
pump_actions = generate_pump_protocol_with_rinsing(
G=G,
@@ -559,8 +677,10 @@ def generate_dissolve_protocol(
)
action_sequence.extend(pump_actions)
debug_print(f"✅ 溶剂转移完成,添加了 {len(pump_actions)} 个动作")
action_sequence.append(create_action_log(f"溶剂转移完成 ({len(pump_actions)} 个操作)", ""))
# 溶剂添加后等待
action_sequence.append(create_action_log("溶剂添加后短暂等待...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
@@ -568,10 +688,14 @@ def generate_dissolve_protocol(
# 步骤5.4: 等待溶解完成
if final_time > 0:
debug_print(f"5.4: 等待溶解完成 - {final_time}s")
debug_print(f"🔍 5.4: 等待溶解完成 - {final_time}s")
wait_minutes = final_time / 60
action_sequence.append(create_action_log(f"开始溶解等待 ({wait_minutes:.1f}分钟)", ""))
if heatchill_id:
# 使用定时加热搅拌
action_sequence.append(create_action_log(f"使用加热搅拌器进行定时溶解", "🔥"))
dissolve_action = {
"device_id": heatchill_id,
"action_name": "heat_chill",
@@ -588,6 +712,8 @@ def generate_dissolve_protocol(
elif stirrer_id:
# 使用定时搅拌
action_sequence.append(create_action_log(f"使用搅拌器进行定时溶解", "🌪️"))
stir_action = {
"device_id": stirrer_id,
"action_name": "stir",
@@ -603,6 +729,7 @@ def generate_dissolve_protocol(
else:
# 简单等待
action_sequence.append(create_action_log(f"简单等待溶解完成", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": final_time}
@@ -610,7 +737,8 @@ def generate_dissolve_protocol(
# 步骤5.5: 停止加热搅拌(如果需要)
if heatchill_id and final_time == 0 and final_temp > 25.0:
debug_print(f"5.5: 停止加热器")
debug_print(f"🔍 5.5: 停止加热器")
action_sequence.append(create_action_log("停止加热搅拌器", "🛑"))
stop_action = {
"device_id": heatchill_id,
@@ -622,7 +750,8 @@ def generate_dissolve_protocol(
action_sequence.append(stop_action)
except Exception as e:
debug_print(f"⚠️ 溶解流程执行失败: {str(e)}")
debug_print(f" 溶解流程执行失败: {str(e)}")
action_sequence.append(create_action_log(f"溶解流程失败: {str(e)}", ""))
# 添加错误日志
action_sequence.append({
"device_id": "system",
@@ -634,21 +763,30 @@ def generate_dissolve_protocol(
# === 最终结果 ===
debug_print("=" * 60)
debug_print(f" 溶解协议生成完成")
debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"📋 处理总结:")
debug_print(f" - 容器: {vessel}")
debug_print(f" - 溶解类型: {'固体溶解' if is_solid_dissolve else '液体溶解'}")
debug_print(f"🎉 溶解协议生成完成")
debug_print(f"📊 协议统计:")
debug_print(f" 📋 总动作数: {len(action_sequence)}")
debug_print(f" 🥼 容器: {vessel}")
debug_print(f" {dissolve_emoji} 溶解类型: {dissolve_type}")
if is_liquid_dissolve:
debug_print(f" - 溶剂: {solvent} ({final_volume}mL)")
debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL)")
if is_solid_dissolve:
debug_print(f" - 试剂: {reagent}")
debug_print(f" - 质量: {final_mass}g")
debug_print(f" - 摩尔: {mol}")
debug_print(f" - 温度: {final_temp}°C")
debug_print(f" - 时间: {final_time}s")
debug_print(f" 🧪 试剂: {reagent}")
debug_print(f" ⚖️ 质量: {final_mass}g")
debug_print(f" 🧬 摩尔: {mol}")
debug_print(f" 🌡️ 温度: {final_temp}°C")
debug_print(f" ⏱️ 时间: {final_time}s")
debug_print("=" * 60)
# 添加完成日志
summary_msg = f"溶解协议完成: {vessel}"
if is_liquid_dissolve:
summary_msg += f" (使用 {final_volume}mL {solvent})"
if is_solid_dissolve:
summary_msg += f" (溶解 {final_mass}g {reagent})"
action_sequence.append(create_action_log(summary_msg, "🎉"))
return action_sequence
# === 便捷函数 ===
@@ -656,6 +794,7 @@ def generate_dissolve_protocol(
def dissolve_solid_by_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union[str, float],
temp: Union[str, float] = 25.0, time: Union[str, float] = "10 min") -> List[Dict[str, Any]]:
"""按质量溶解固体"""
debug_print(f"🧂 快速固体溶解: {reagent} ({mass}) → {vessel}")
return generate_dissolve_protocol(
G, vessel,
mass=mass,
@@ -667,6 +806,7 @@ def dissolve_solid_by_mass(G: nx.DiGraph, vessel: str, reagent: str, mass: Union
def dissolve_solid_by_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
temp: Union[str, float] = 25.0, time: Union[str, float] = "10 min") -> List[Dict[str, Any]]:
"""按摩尔数溶解固体"""
debug_print(f"🧬 按摩尔数溶解固体: {reagent} ({mol}) → {vessel}")
return generate_dissolve_protocol(
G, vessel,
mol=mol,
@@ -678,6 +818,7 @@ def dissolve_solid_by_moles(G: nx.DiGraph, vessel: str, reagent: str, mol: str,
def dissolve_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
temp: Union[str, float] = 25.0, time: Union[str, float] = "5 min") -> List[Dict[str, Any]]:
"""用溶剂溶解"""
debug_print(f"💧 溶剂溶解: {solvent} ({volume}) → {vessel}")
return generate_dissolve_protocol(
G, vessel,
solvent=solvent,
@@ -688,6 +829,7 @@ def dissolve_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Unio
def dissolve_at_room_temp(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float]) -> List[Dict[str, Any]]:
"""室温溶解"""
debug_print(f"🌡️ 室温溶解: {solvent} ({volume}) → {vessel}")
return generate_dissolve_protocol(
G, vessel,
solvent=solvent,
@@ -699,6 +841,7 @@ def dissolve_at_room_temp(G: nx.DiGraph, vessel: str, solvent: str, volume: Unio
def dissolve_with_heating(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
temp: Union[str, float] = "60 °C", time: Union[str, float] = "15 min") -> List[Dict[str, Any]]:
"""加热溶解"""
debug_print(f"🔥 加热溶解: {solvent} ({volume}) → {vessel} @ {temp}")
return generate_dissolve_protocol(
G, vessel,
solvent=solvent,
@@ -710,33 +853,37 @@ def dissolve_with_heating(G: nx.DiGraph, vessel: str, solvent: str, volume: Unio
# 测试函数
def test_dissolve_protocol():
"""测试溶解协议的各种参数解析"""
print("=== DISSOLVE PROTOCOL 修复版测试 ===")
debug_print("=== DISSOLVE PROTOCOL 增强版测试 ===")
# 测试体积解析
debug_print("💧 测试体积解析...")
volumes = ["10 mL", "?", 10.0, "1 L", "500 μL"]
for vol in volumes:
result = parse_volume_input(vol)
print(f"体积解析: {vol}{result}mL")
debug_print(f"📏 体积解析: {vol}{result}mL")
# 测试质量解析
debug_print("⚖️ 测试质量解析...")
masses = ["2.9 g", "?", 2.5, "500 mg"]
for mass in masses:
result = parse_mass_input(mass)
print(f"质量解析: {mass}{result}g")
debug_print(f"⚖️ 质量解析: {mass}{result}g")
# 测试温度解析
debug_print("🌡️ 测试温度解析...")
temps = ["60 °C", "room temperature", "?", 25.0, "reflux"]
for temp in temps:
result = parse_temperature_input(temp)
print(f"温度解析: {temp}{result}°C")
debug_print(f"🌡️ 温度解析: {temp}{result}°C")
# 测试时间解析
debug_print("⏱️ 测试时间解析...")
times = ["30 min", "1 h", "?", 60.0]
for time in times:
result = parse_time_input(time)
print(f"时间解析: {time}{result}s")
debug_print(f"⏱️ 时间解析: {time}{result}s")
print("✅ 测试完成")
debug_print("✅ 测试完成")
if __name__ == "__main__":
test_dissolve_protocol()

View File

@@ -1,16 +1,68 @@
import networkx as nx
import logging
import uuid # 🔧 移到顶部
import uuid
import sys
from typing import List, Dict, Any, Optional
from .pump_protocol import generate_pump_protocol_with_rinsing, generate_pump_protocol
# 设置日志
logger = logging.getLogger(__name__)
# 确保输出编码为UTF-8
if hasattr(sys.stdout, 'reconfigure'):
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except:
pass
def debug_print(message):
"""调试输出函数"""
print(f"[EVACUATE_REFILL] {message}", flush=True)
logger.info(f"[EVACUATE_REFILL] {message}")
"""调试输出函数 - 支持中文"""
try:
# 确保消息是字符串格式
safe_message = str(message)
print(f"[抽真空充气] {safe_message}", flush=True)
logger.info(f"[抽真空充气] {safe_message}")
except UnicodeEncodeError:
# 如果编码失败,尝试替换不支持的字符
safe_message = str(message).encode('utf-8', errors='replace').decode('utf-8')
print(f"[抽真空充气] {safe_message}", flush=True)
logger.info(f"[抽真空充气] {safe_message}")
except Exception as e:
# 最后的安全措施
fallback_message = f"日志输出错误: {repr(message)}"
print(f"[抽真空充气] {fallback_message}", flush=True)
logger.info(f"[抽真空充气] {fallback_message}")
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
"""创建一个动作日志 - 支持中文和emoji"""
try:
full_message = f"{emoji} {message}"
debug_print(full_message)
logger.info(full_message)
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1,
"log_message": full_message,
"progress_message": full_message
}
}
except Exception as e:
# 如果emoji有问题使用纯文本
safe_message = f"[日志] {message}"
debug_print(safe_message)
logger.info(safe_message)
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1,
"log_message": safe_message,
"progress_message": safe_message
}
}
def find_gas_source(G: nx.DiGraph, gas: str) -> str:
"""
@@ -19,9 +71,10 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
2. 气体类型匹配data.gas_type
3. 默认气源
"""
debug_print(f"正在查找气体 '{gas}' 的气源...")
debug_print(f"🔍 正在查找气体 '{gas}' 的气源...")
# 第一步:通过容器名称匹配
debug_print(f"📋 方法1: 容器名称匹配...")
gas_source_patterns = [
f"gas_source_{gas}",
f"gas_{gas}",
@@ -32,12 +85,15 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
f"bottle_{gas}"
]
debug_print(f"🎯 尝试的容器名称: {gas_source_patterns}")
for pattern in gas_source_patterns:
if pattern in G.nodes():
debug_print(f"通过名称匹配找到气源: {pattern}")
debug_print(f"通过名称找到气源: {pattern}")
return pattern
# 第二步:通过气体类型匹配 (data.gas_type)
debug_print(f"📋 方法2: 气体类型匹配...")
for node_id in G.nodes():
node_data = G.nodes[node_id]
node_class = node_data.get('class', '') or ''
@@ -52,7 +108,7 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
gas_type = data.get('gas_type', '')
if gas_type.lower() == gas.lower():
debug_print(f"通过气体类型匹配找到气源: {node_id} (gas_type: {gas_type})")
debug_print(f"通过气体类型找到气源: {node_id} (气体类型: {gas_type})")
return node_id
# 检查 config.gas_type
@@ -60,10 +116,11 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
config_gas_type = config.get('gas_type', '')
if config_gas_type.lower() == gas.lower():
debug_print(f"通过配置气体类型匹配找到气源: {node_id} (config.gas_type: {config_gas_type})")
debug_print(f"通过配置气体类型找到气源: {node_id} (配置气体类型: {config_gas_type})")
return node_id
# 第三步:查找所有可用的气源设备
debug_print(f"📋 方法3: 查找可用气源...")
available_gas_sources = []
for node_id in G.nodes():
node_data = G.nodes[node_id]
@@ -74,12 +131,13 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
(node_id.startswith('flask_') and any(g in node_id.lower() for g in ['air', 'nitrogen', 'argon']))):
data = node_data.get('data', {})
gas_type = data.get('gas_type', 'unknown')
available_gas_sources.append(f"{node_id} (gas_type: {gas_type})")
gas_type = data.get('gas_type', '未知')
available_gas_sources.append(f"{node_id} (气体类型: {gas_type})")
debug_print(f"可用气源列表: {available_gas_sources}")
debug_print(f"📊 可用气源: {available_gas_sources}")
# 第四步:如果找不到特定气体,使用默认的第一个气源
debug_print(f"📋 方法4: 查找默认气源...")
default_gas_sources = [
node for node in G.nodes()
if ((G.nodes[node].get('class') or '').find('virtual_gas_source') != -1
@@ -91,11 +149,12 @@ def find_gas_source(G: nx.DiGraph, gas: str) -> str:
debug_print(f"⚠️ 未找到特定气体 '{gas}',使用默认气源: {default_source}")
return default_source
raise ValueError(f"找不到气体 '{gas}' 对应的气源。可用气源: {available_gas_sources}")
debug_print(f"❌ 所有方法都失败了!")
raise ValueError(f"无法找到气体 '{gas}' 的气源。可用气源: {available_gas_sources}")
def find_vacuum_pump(G: nx.DiGraph) -> str:
"""查找真空泵设备"""
debug_print("查找真空泵设备...")
debug_print("🔍 正在查找真空泵...")
vacuum_pumps = []
for node in G.nodes():
@@ -106,16 +165,18 @@ def find_vacuum_pump(G: nx.DiGraph) -> str:
'vacuum_pump' in node.lower() or
'vacuum' in node_class.lower()):
vacuum_pumps.append(node)
debug_print(f"📋 发现真空泵: {node}")
if not vacuum_pumps:
raise ValueError("系统中未找到真空泵设备")
debug_print(f"系统中未找到真空泵")
raise ValueError("系统中未找到真空泵")
debug_print(f"找到真空泵: {vacuum_pumps[0]}")
debug_print(f"✅ 使用真空泵: {vacuum_pumps[0]}")
return vacuum_pumps[0]
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> Optional[str]:
"""查找与指定容器相连的搅拌器"""
debug_print(f"查找与容器 {vessel} 连的搅拌器...")
debug_print(f"🔍 正在查找与容器 {vessel}的搅拌器...")
stirrer_nodes = []
for node in G.nodes():
@@ -124,24 +185,27 @@ def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> Optional[str]:
if 'virtual_stirrer' in node_class or 'stirrer' in node.lower():
stirrer_nodes.append(node)
debug_print(f"📋 发现搅拌器: {node}")
debug_print(f"📊 找到的搅拌器总数: {len(stirrer_nodes)}")
# 检查哪个搅拌器与目标容器相连
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
debug_print(f"找到连接的搅拌器: {stirrer}")
debug_print(f"找到连接的搅拌器: {stirrer}")
return stirrer
# 如果没有连接的搅拌器,返回第一个可用的
if stirrer_nodes:
debug_print(f"未找到直接连接的搅拌器,使用第一个可用的: {stirrer_nodes[0]}")
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个可用的: {stirrer_nodes[0]}")
return stirrer_nodes[0]
debug_print("未找到搅拌器")
debug_print("未找到搅拌器")
return None
def find_vacuum_solenoid_valve(G: nx.DiGraph, vacuum_pump: str) -> Optional[str]:
"""查找真空泵相关的电磁阀 - 根据实际连接逻辑"""
debug_print(f"查找真空泵 {vacuum_pump} 相关的电磁阀...")
"""查找真空泵相关的电磁阀"""
debug_print(f"🔍 正在查找真空泵 {vacuum_pump} 的电磁阀...")
# 查找所有电磁阀
solenoid_valves = []
@@ -151,29 +215,30 @@ def find_vacuum_solenoid_valve(G: nx.DiGraph, vacuum_pump: str) -> Optional[str]
if ('solenoid' in node_class.lower() or 'solenoid_valve' in node.lower()):
solenoid_valves.append(node)
debug_print(f"📋 发现电磁阀: {node}")
debug_print(f"找到的电磁阀: {solenoid_valves}")
debug_print(f"📊 找到的电磁阀: {solenoid_valves}")
# 🔧 修复:根据实际组态图连接逻辑查找
# vacuum_pump_1 <- solenoid_valve_1 <- multiway_valve_2
# 检查连接关系
debug_print(f"📋 方法1: 检查连接关系...")
for solenoid in solenoid_valves:
# 检查电磁阀是否连接到真空泵
if G.has_edge(solenoid, vacuum_pump) or G.has_edge(vacuum_pump, solenoid):
debug_print(f" 找到连接真空泵的电磁阀: {solenoid}")
debug_print(f" 找到连接真空电磁阀: {solenoid}")
return solenoid
# 通过命名规则查找(备选方案)
# 通过命名规则查找
debug_print(f"📋 方法2: 检查命名规则...")
for solenoid in solenoid_valves:
if 'vacuum' in solenoid.lower() or solenoid == 'solenoid_valve_1':
debug_print(f" 通过命名规则找到真空电磁阀: {solenoid}")
debug_print(f" 通过命名找到真空电磁阀: {solenoid}")
return solenoid
debug_print("⚠️ 未找到真空电磁阀")
return None
def find_gas_solenoid_valve(G: nx.DiGraph, gas_source: str) -> Optional[str]:
"""查找气源相关的电磁阀 - 根据实际连接逻辑"""
debug_print(f"查找气源 {gas_source} 相关的电磁阀...")
"""查找气源相关的电磁阀"""
debug_print(f"🔍 正在查找气源 {gas_source} 的电磁阀...")
# 查找所有电磁阀
solenoid_valves = []
@@ -184,18 +249,20 @@ def find_gas_solenoid_valve(G: nx.DiGraph, gas_source: str) -> Optional[str]:
if ('solenoid' in node_class.lower() or 'solenoid_valve' in node.lower()):
solenoid_valves.append(node)
# 🔧 修复:根据实际组态图连接逻辑查找
# gas_source_1 -> solenoid_valve_2 -> multiway_valve_2
debug_print(f"📊 找到的电磁阀: {solenoid_valves}")
# 检查连接关系
debug_print(f"📋 方法1: 检查连接关系...")
for solenoid in solenoid_valves:
# 检查气源是否连接到电磁阀
if G.has_edge(gas_source, solenoid) or G.has_edge(solenoid, gas_source):
debug_print(f" 找到连接气源电磁阀: {solenoid}")
debug_print(f" 找到连接气源电磁阀: {solenoid}")
return solenoid
# 通过命名规则查找(备选方案)
# 通过命名规则查找
debug_print(f"📋 方法2: 检查命名规则...")
for solenoid in solenoid_valves:
if 'gas' in solenoid.lower() or solenoid == 'solenoid_valve_2':
debug_print(f" 通过命名规则找到气源电磁阀: {solenoid}")
debug_print(f" 通过命名找到气源电磁阀: {solenoid}")
return solenoid
debug_print("⚠️ 未找到气源电磁阀")
@@ -208,7 +275,7 @@ def generate_evacuateandrefill_protocol(
**kwargs
) -> List[Dict[str, Any]]:
"""
生成抽真空和充气操作的动作序列 - 最终修复版本
生成抽真空和充气操作的动作序列 - 中文版
Args:
G: 设备图
@@ -223,75 +290,113 @@ def generate_evacuateandrefill_protocol(
# 硬编码重复次数为 3
repeats = 3
# 🔧 修复:在函数开始就生成协议ID
# 生成协议ID
protocol_id = str(uuid.uuid4())
debug_print(f"生成协议ID: {protocol_id}")
debug_print(f"🆔 生成协议ID: {protocol_id}")
debug_print("=" * 60)
debug_print("开始生成抽真空充气协议")
debug_print(f"输入参数:")
debug_print(f" - vessel: {vessel}")
debug_print(f" - gas: {gas}")
debug_print(f" - repeats: {repeats} (硬编码)")
debug_print(f" - 其他参数: {kwargs}")
debug_print("🧪 开始生成抽真空充气协议")
debug_print(f"📋 原始参数:")
debug_print(f" 🥼 容器: '{vessel}'")
debug_print(f" 💨 气体: '{gas}'")
debug_print(f" 🔄 循环次数: {repeats} (硬编码)")
debug_print(f" 📦 其他参数: {kwargs}")
debug_print("=" * 60)
action_sequence = []
# === 参数验证和修正 ===
debug_print("步骤1: 参数验证和修正...")
debug_print("🔍 步骤1: 参数验证和修正...")
action_sequence.append(create_action_log(f"开始抽真空充气操作 - 容器: {vessel}", "🎬"))
action_sequence.append(create_action_log(f"目标气体: {gas}", "💨"))
action_sequence.append(create_action_log(f"循环次数: {repeats}", "🔄"))
# 验证必需参数
if not vessel:
raise ValueError("vessel 参数不能为空")
debug_print("❌ 容器参数不能为空")
raise ValueError("容器参数不能为空")
if not gas:
raise ValueError("gas 参数不能为空")
debug_print("❌ 气体参数不能为空")
raise ValueError("气体参数不能为空")
if vessel not in G.nodes():
raise ValueError(f"容器 '{vessel}' 不存在于系统中")
debug_print(f"容器 '{vessel}' 在系统中不存在")
raise ValueError(f"容器 '{vessel}' 在系统中不存在")
debug_print("✅ 基本参数验证通过")
action_sequence.append(create_action_log("参数验证通过", ""))
# 标准化气体名称
debug_print("🔧 标准化气体名称...")
gas_aliases = {
'n2': 'nitrogen',
'ar': 'argon',
'air': 'air',
'o2': 'oxygen',
'co2': 'carbon_dioxide',
'h2': 'hydrogen'
'h2': 'hydrogen',
'氮气': 'nitrogen',
'氩气': 'argon',
'空气': 'air',
'氧气': 'oxygen',
'二氧化碳': 'carbon_dioxide',
'氢气': 'hydrogen'
}
original_gas = gas
gas_lower = gas.lower().strip()
if gas_lower in gas_aliases:
gas = gas_aliases[gas_lower]
debug_print(f"标准化气体名称: {original_gas} -> {gas}")
debug_print(f"🔄 标准化气体名称: {original_gas} -> {gas}")
action_sequence.append(create_action_log(f"气体名称标准化: {original_gas} -> {gas}", "🔄"))
debug_print(f"最终参数: vessel={vessel}, gas={gas}, repeats={repeats}")
debug_print(f"📋 最终参数: 容器={vessel}, 气体={gas}, 重复={repeats}")
# === 查找设备 ===
debug_print("步骤2: 查找设备...")
debug_print("🔍 步骤2: 查找设备...")
action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
try:
vacuum_pump = find_vacuum_pump(G)
gas_source = find_gas_source(G, gas)
vacuum_solenoid = find_vacuum_solenoid_valve(G, vacuum_pump)
gas_solenoid = find_gas_solenoid_valve(G, gas_source)
stirrer_id = find_connected_stirrer(G, vessel)
action_sequence.append(create_action_log(f"找到真空泵: {vacuum_pump}", "🌪️"))
debug_print(f"设备配置:")
debug_print(f" - 真空泵: {vacuum_pump}")
debug_print(f" - 气源: {gas_source}")
debug_print(f" - 真空电磁阀: {vacuum_solenoid}")
debug_print(f" - 气源电磁阀: {gas_solenoid}")
debug_print(f" - 搅拌器: {stirrer_id}")
gas_source = find_gas_source(G, gas)
action_sequence.append(create_action_log(f"找到气源: {gas_source}", "💨"))
vacuum_solenoid = find_vacuum_solenoid_valve(G, vacuum_pump)
if vacuum_solenoid:
action_sequence.append(create_action_log(f"找到真空电磁阀: {vacuum_solenoid}", "🚪"))
else:
action_sequence.append(create_action_log("未找到真空电磁阀", "⚠️"))
gas_solenoid = find_gas_solenoid_valve(G, gas_source)
if gas_solenoid:
action_sequence.append(create_action_log(f"找到气源电磁阀: {gas_solenoid}", "🚪"))
else:
action_sequence.append(create_action_log("未找到气源电磁阀", "⚠️"))
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_id}", "🌪️"))
else:
action_sequence.append(create_action_log("未找到搅拌器", "⚠️"))
debug_print(f"📊 设备配置:")
debug_print(f" 🌪️ 真空泵: {vacuum_pump}")
debug_print(f" 💨 气源: {gas_source}")
debug_print(f" 🚪 真空电磁阀: {vacuum_solenoid}")
debug_print(f" 🚪 气源电磁阀: {gas_solenoid}")
debug_print(f" 🌪️ 搅拌器: {stirrer_id}")
except Exception as e:
debug_print(f"❌ 设备查找失败: {str(e)}")
action_sequence.append(create_action_log(f"设备查找失败: {str(e)}", ""))
raise ValueError(f"设备查找失败: {str(e)}")
# === 参数设置 ===
debug_print("步骤3: 参数设置...")
debug_print("🔍 步骤3: 参数设置...")
action_sequence.append(create_action_log("设置操作参数...", "⚙️"))
# 根据气体类型调整参数
if gas.lower() in ['nitrogen', 'argon']:
@@ -300,87 +405,108 @@ def generate_evacuateandrefill_protocol(
PUMP_FLOW_RATE = 2.0
VACUUM_TIME = 30.0
REFILL_TIME = 20.0
debug_print("惰性气体使用标准参数")
debug_print("💨 惰性气体: 使用标准参数")
action_sequence.append(create_action_log("检测到惰性气体,使用标准参数", "💨"))
elif gas.lower() in ['air', 'oxygen']:
VACUUM_VOLUME = 20.0
REFILL_VOLUME = 20.0
PUMP_FLOW_RATE = 1.5
VACUUM_TIME = 45.0
REFILL_TIME = 25.0
debug_print("活性气体使用保守参数")
debug_print("🔥 活性气体: 使用保守参数")
action_sequence.append(create_action_log("检测到活性气体,使用保守参数", "🔥"))
else:
VACUUM_VOLUME = 15.0
REFILL_VOLUME = 15.0
PUMP_FLOW_RATE = 1.0
VACUUM_TIME = 60.0
REFILL_TIME = 30.0
debug_print("未知气体使用安全参数")
debug_print("未知气体: 使用安全参数")
action_sequence.append(create_action_log("未知气体类型,使用安全参数", ""))
STIR_SPEED = 200.0
debug_print(f"操作参数:")
debug_print(f" - 抽真空体积: {VACUUM_VOLUME}mL")
debug_print(f" - 充气体积: {REFILL_VOLUME}mL")
debug_print(f" - 泵流速: {PUMP_FLOW_RATE}mL/s")
debug_print(f" - 抽真空时间: {VACUUM_TIME}s")
debug_print(f" - 充气时间: {REFILL_TIME}s")
debug_print(f" - 搅拌速度: {STIR_SPEED}RPM")
debug_print(f"⚙️ 操作参数:")
debug_print(f" 📏 真空体积: {VACUUM_VOLUME}mL")
debug_print(f" 📏 充气体积: {REFILL_VOLUME}mL")
debug_print(f" 泵流速: {PUMP_FLOW_RATE}mL/s")
debug_print(f" ⏱️ 真空时间: {VACUUM_TIME}s")
debug_print(f" ⏱️ 充气时间: {REFILL_TIME}s")
debug_print(f" 🌪️ 搅拌速度: {STIR_SPEED}RPM")
action_sequence.append(create_action_log(f"真空体积: {VACUUM_VOLUME}mL", "📏"))
action_sequence.append(create_action_log(f"充气体积: {REFILL_VOLUME}mL", "📏"))
action_sequence.append(create_action_log(f"泵流速: {PUMP_FLOW_RATE}mL/s", ""))
# === 路径验证 ===
debug_print("步骤4: 路径验证...")
debug_print("🔍 步骤4: 路径验证...")
action_sequence.append(create_action_log("验证传输路径...", "🛤️"))
try:
# 验证抽真空路径: vessel -> vacuum_pump (通过八通阀和电磁阀)
# 验证抽真空路径
if nx.has_path(G, vessel, vacuum_pump):
vacuum_path = nx.shortest_path(G, source=vessel, target=vacuum_pump)
debug_print(f"真空路径: {' '.join(vacuum_path)}")
debug_print(f"真空路径: {' -> '.join(vacuum_path)}")
action_sequence.append(create_action_log(f"真空路径: {' -> '.join(vacuum_path)}", "🛤️"))
else:
debug_print(f"⚠️ 真空路径不存在,继续执行但可能有问题")
debug_print(f"⚠️ 真空路径不存在,继续执行但可能有问题")
action_sequence.append(create_action_log("真空路径检查: 路径不存在", "⚠️"))
# 验证充气路径: gas_source -> vessel (通过电磁阀和八通阀)
# 验证充气路径
if nx.has_path(G, gas_source, vessel):
gas_path = nx.shortest_path(G, source=gas_source, target=vessel)
debug_print(f"充气路径: {' '.join(gas_path)}")
debug_print(f"✅ 气体路径: {' -> '.join(gas_path)}")
action_sequence.append(create_action_log(f"气体路径: {' -> '.join(gas_path)}", "🛤️"))
else:
debug_print(f"⚠️ 气路径不存在,继续执行但可能有问题")
debug_print(f"⚠️ 气路径不存在,继续执行但可能有问题")
action_sequence.append(create_action_log("气体路径检查: 路径不存在", "⚠️"))
except Exception as e:
debug_print(f"⚠️ 路径验证失败: {str(e)},继续执行")
action_sequence.append(create_action_log(f"路径验证失败: {str(e)}", "⚠️"))
# === 启动搅拌器 ===
debug_print("步骤5: 启动搅拌器...")
debug_print("🔍 步骤5: 启动搅拌器...")
if stirrer_id:
debug_print(f"启动搅拌器: {stirrer_id}")
debug_print(f"🌪️ 启动搅拌器: {stirrer_id}")
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {STIR_SPEED}rpm)", "🌪️"))
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": STIR_SPEED,
"purpose": "抽真空充气操作前启动搅拌"
"purpose": "抽真空充气前预搅拌"
}
})
# 等待搅拌稳定
action_sequence.append(create_action_log("等待搅拌稳定...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5.0}
})
else:
debug_print("未找到搅拌器,跳过搅拌启动")
debug_print("⚠️ 未找到搅拌器,跳过搅拌启动")
action_sequence.append(create_action_log("跳过搅拌器启动", "⏭️"))
# === 执行 3 次抽真空-充气循环 ===
debug_print("步骤6: 执行抽真空-充气循环...")
# === 执行循环 ===
debug_print("🔍 步骤6: 执行抽真空-充气循环...")
action_sequence.append(create_action_log(f"开始 {repeats} 次抽真空-充气循环", "🔄"))
for cycle in range(repeats):
debug_print(f"=== 第 {cycle+1}/{repeats} 循环 ===")
debug_print(f"=== 第 {cycle+1}/{repeats} 循环 ===")
action_sequence.append(create_action_log(f"{cycle+1}/{repeats} 轮循环开始", "🚀"))
# ============ 抽真空阶段 ============
debug_print(f"抽真空阶段开始")
debug_print(f"🌪️ 抽真空阶段开始")
action_sequence.append(create_action_log("开始抽真空阶段", "🌪️"))
# 启动真空泵
debug_print(f"启动真空泵: {vacuum_pump}")
debug_print(f"🔛 启动真空泵: {vacuum_pump}")
action_sequence.append(create_action_log(f"启动真空泵: {vacuum_pump}", "🔛"))
action_sequence.append({
"device_id": vacuum_pump,
"action_name": "set_status",
@@ -389,17 +515,19 @@ def generate_evacuateandrefill_protocol(
# 开启真空电磁阀
if vacuum_solenoid:
debug_print(f"真空电磁阀: {vacuum_solenoid}")
debug_print(f"🚪 打开真空电磁阀: {vacuum_solenoid}")
action_sequence.append(create_action_log(f"打开真空电磁阀: {vacuum_solenoid}", "🚪"))
action_sequence.append({
"device_id": vacuum_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {"command": "OPEN"}
})
# 抽真空操作 - 使用液体转移协议
debug_print(f"抽真空操作: {vessel} {vacuum_pump}")
# 抽真空操作
debug_print(f"🌪️ 抽真空操作: {vessel} -> {vacuum_pump}")
action_sequence.append(create_action_log(f"开始抽真空: {vessel} -> {vacuum_pump}", "🌪️"))
try:
vacuum_transfer_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=vessel,
@@ -419,8 +547,10 @@ def generate_evacuateandrefill_protocol(
if vacuum_transfer_actions:
action_sequence.extend(vacuum_transfer_actions)
debug_print(f"✅ 添加了 {len(vacuum_transfer_actions)} 个抽真空动作")
action_sequence.append(create_action_log(f"抽真空协议完成 ({len(vacuum_transfer_actions)} 个操作)", ""))
else:
debug_print("⚠️ 抽真空协议返回空序列,添加手动动作")
action_sequence.append(create_action_log("抽真空协议为空,使用手动等待", "⚠️"))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": VACUUM_TIME}
@@ -428,13 +558,15 @@ def generate_evacuateandrefill_protocol(
except Exception as e:
debug_print(f"❌ 抽真空失败: {str(e)}")
# 添加等待时间作为备选
action_sequence.append(create_action_log(f"抽真空失败: {str(e)}", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": VACUUM_TIME}
})
# 抽真空后等待
wait_minutes = VACUUM_TIME / 60
action_sequence.append(create_action_log(f"抽真空后等待 ({wait_minutes:.1f} 分钟)", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": VACUUM_TIME}
@@ -442,7 +574,8 @@ def generate_evacuateandrefill_protocol(
# 关闭真空电磁阀
if vacuum_solenoid:
debug_print(f"关闭真空电磁阀: {vacuum_solenoid}")
debug_print(f"🚪 关闭真空电磁阀: {vacuum_solenoid}")
action_sequence.append(create_action_log(f"关闭真空电磁阀: {vacuum_solenoid}", "🚪"))
action_sequence.append({
"device_id": vacuum_solenoid,
"action_name": "set_valve_position",
@@ -450,24 +583,28 @@ def generate_evacuateandrefill_protocol(
})
# 关闭真空泵
debug_print(f"关闭真空泵: {vacuum_pump}")
debug_print(f"🔴 停止真空泵: {vacuum_pump}")
action_sequence.append(create_action_log(f"停止真空泵: {vacuum_pump}", "🔴"))
action_sequence.append({
"device_id": vacuum_pump,
"action_name": "set_status",
"action_kwargs": {"string": "OFF"}
})
# 抽真空后等待
# 阶段间等待
action_sequence.append(create_action_log("抽真空阶段完成,短暂等待", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5.0}
})
# ============ 充气阶段 ============
debug_print(f"充气阶段开始")
debug_print(f"💨 充气阶段开始")
action_sequence.append(create_action_log("开始气体充气阶段", "💨"))
# 启动气源
debug_print(f"启动气源: {gas_source}")
debug_print(f"🔛 启动气源: {gas_source}")
action_sequence.append(create_action_log(f"启动气源: {gas_source}", "🔛"))
action_sequence.append({
"device_id": gas_source,
"action_name": "set_status",
@@ -476,17 +613,19 @@ def generate_evacuateandrefill_protocol(
# 开启气源电磁阀
if gas_solenoid:
debug_print(f"气源电磁阀: {gas_solenoid}")
debug_print(f"🚪 打开气源电磁阀: {gas_solenoid}")
action_sequence.append(create_action_log(f"打开气源电磁阀: {gas_solenoid}", "🚪"))
action_sequence.append({
"device_id": gas_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {"command": "OPEN"}
})
# 充气操作 - 使用液体转移协议
debug_print(f"充气操作: {gas_source} {vessel}")
# 充气操作
debug_print(f"💨 充气操作: {gas_source} -> {vessel}")
action_sequence.append(create_action_log(f"开始气体充气: {gas_source} -> {vessel}", "💨"))
try:
gas_transfer_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=gas_source,
@@ -506,22 +645,26 @@ def generate_evacuateandrefill_protocol(
if gas_transfer_actions:
action_sequence.extend(gas_transfer_actions)
debug_print(f"✅ 添加了 {len(gas_transfer_actions)} 个充气动作")
action_sequence.append(create_action_log(f"气体充气协议完成 ({len(gas_transfer_actions)} 个操作)", ""))
else:
debug_print("⚠️ 充气协议返回空序列,添加手动动作")
action_sequence.append(create_action_log("充气协议为空,使用手动等待", "⚠️"))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": REFILL_TIME}
})
except Exception as e:
debug_print(f"❌ 充气失败: {str(e)}")
# 添加等待时间作为备选
debug_print(f"气体充气失败: {str(e)}")
action_sequence.append(create_action_log(f"气体充气失败: {str(e)}", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": REFILL_TIME}
})
# 充气后等待
refill_wait_minutes = REFILL_TIME / 60
action_sequence.append(create_action_log(f"充气后等待 ({refill_wait_minutes:.1f} 分钟)", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": REFILL_TIME}
@@ -529,7 +672,8 @@ def generate_evacuateandrefill_protocol(
# 关闭气源电磁阀
if gas_solenoid:
debug_print(f"关闭气源电磁阀: {gas_solenoid}")
debug_print(f"🚪 关闭气源电磁阀: {gas_solenoid}")
action_sequence.append(create_action_log(f"关闭气源电磁阀: {gas_solenoid}", "🚪"))
action_sequence.append({
"device_id": gas_solenoid,
"action_name": "set_valve_position",
@@ -537,68 +681,92 @@ def generate_evacuateandrefill_protocol(
})
# 关闭气源
debug_print(f"关闭气源: {gas_source}")
debug_print(f"🔴 停止气源: {gas_source}")
action_sequence.append(create_action_log(f"停止气源: {gas_source}", "🔴"))
action_sequence.append({
"device_id": gas_source,
"action_name": "set_status",
"action_kwargs": {"string": "OFF"}
})
# 等待下一次循环
# 循环间等待
if cycle < repeats - 1:
debug_print(f"等待下一循环...")
debug_print(f"等待下一循环...")
action_sequence.append(create_action_log("等待下一个循环...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 10.0}
})
else:
action_sequence.append(create_action_log(f"{cycle+1}/{repeats} 轮循环完成", ""))
# === 停止搅拌器 ===
debug_print("步骤7: 停止搅拌器...")
debug_print("🔍 步骤7: 停止搅拌器...")
if stirrer_id:
debug_print(f"停止搅拌器: {stirrer_id}")
debug_print(f"🛑 停止搅拌器: {stirrer_id}")
action_sequence.append(create_action_log(f"停止搅拌器: {stirrer_id}", "🛑"))
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stop_stir",
"action_kwargs": {"vessel": vessel}
})
else:
action_sequence.append(create_action_log("跳过搅拌器停止", "⏭️"))
# === 最终等待 ===
action_sequence.append(create_action_log("最终稳定等待...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 10.0}
})
# === 总结 ===
total_time = (VACUUM_TIME + REFILL_TIME + 25) * repeats + 20
debug_print("=" * 60)
debug_print(f"抽真空充气协议生成完成")
debug_print(f"总动作数: {len(action_sequence)}")
debug_print(f"处理容器: {vessel}")
debug_print(f"使用气体: {gas}")
debug_print(f"重复次数: {repeats} (硬编码)")
debug_print(f"🎉 抽真空充气协议生成完成")
debug_print(f"📊 协议统计:")
debug_print(f" 📋 总动作数: {len(action_sequence)}")
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f} 分钟)")
debug_print(f" 🥼 处理容器: {vessel}")
debug_print(f" 💨 使用气体: {gas}")
debug_print(f" 🔄 重复次数: {repeats}")
debug_print("=" * 60)
# 添加完成日志
summary_msg = f"抽真空充气协议完成: {vessel} (使用 {gas}{repeats} 次循环)"
action_sequence.append(create_action_log(summary_msg, "🎉"))
return action_sequence
# === 便捷函数 ===
def generate_nitrogen_purge_protocol(G: nx.DiGraph, vessel: str, **kwargs) -> List[Dict[str, Any]]:
"""生成氮气置换协议"""
debug_print(f"💨 生成氮气置换协议: {vessel}")
return generate_evacuateandrefill_protocol(G, vessel, "nitrogen", **kwargs)
def generate_argon_purge_protocol(G: nx.DiGraph, vessel: str, **kwargs) -> List[Dict[str, Any]]:
"""生成氩气置换协议"""
debug_print(f"💨 生成氩气置换协议: {vessel}")
return generate_evacuateandrefill_protocol(G, vessel, "argon", **kwargs)
def generate_air_purge_protocol(G: nx.DiGraph, vessel: str, **kwargs) -> List[Dict[str, Any]]:
"""生成空气置换协议"""
debug_print(f"💨 生成空气置换协议: {vessel}")
return generate_evacuateandrefill_protocol(G, vessel, "air", **kwargs)
def generate_inert_atmosphere_protocol(G: nx.DiGraph, vessel: str, gas: str = "nitrogen", **kwargs) -> List[Dict[str, Any]]:
"""生成惰性气氛协议"""
debug_print(f"🛡️ 生成惰性气氛协议: {vessel} (使用 {gas})")
return generate_evacuateandrefill_protocol(G, vessel, gas, **kwargs)
# 测试函数
def test_evacuateandrefill_protocol():
"""测试抽真空充气协议"""
debug_print("=== EVACUATE AND REFILL PROTOCOL 测试 ===")
debug_print("测试完成")
debug_print("=== 抽真空充气协议增强中文版测试 ===")
debug_print("测试完成")
if __name__ == "__main__":
test_evacuateandrefill_protocol()

View File

@@ -273,7 +273,6 @@ def generate_pump_protocol(
if not pump_backbone:
debug_print("PUMP_TRANSFER: 没有泵骨架节点,可能是直接容器连接或只有电磁阀")
# 🔧 对于气体传输,这是正常的,直接返回空序列
return pump_action_sequence
if transfer_flowrate == 0:
@@ -319,10 +318,31 @@ def generate_pump_protocol(
volume_left = volume
debug_print(f"PUMP_TRANSFER: 需要 {repeats} 次转移,单次最大体积 {min_transfer_volume} mL")
# 🆕 只在开头打印总体概览
if repeats > 1:
debug_print(f"🔄 分批转移概览: 总体积 {volume:.2f}mL需要 {repeats} 次转移")
logger.info(f"🔄 分批转移概览: 总体积 {volume:.2f}mL需要 {repeats} 次转移")
# 🔧 创建一个自定义的wait动作用于在执行时打印日志
def create_progress_log_action(message: str) -> Dict[str, Any]:
"""创建一个特殊的等待动作,在执行时打印进度日志"""
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1, # 很短的等待时间
"progress_message": message # 自定义字段,用于进度日志
}
}
# 生成泵操作序列
for i in range(repeats):
current_volume = min(volume_left, min_transfer_volume)
# 🆕 在每次循环开始时添加进度日志
if repeats > 1:
start_message = f"🚀 准备开始第 {i+1}/{repeats} 次转移: {current_volume:.2f}mL ({from_vessel}{to_vessel}) 🚰"
pump_action_sequence.append(create_progress_log_action(start_message))
# 🔧 修复:安全地获取边数据
def get_safe_edge_data(node_a, node_b, key):
try:
@@ -426,6 +446,426 @@ def generate_pump_protocol(
])
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 3}})
# 🆕 在每次循环结束时添加完成日志
if repeats > 1:
remaining_volume = volume_left - current_volume
if remaining_volume > 0:
end_message = f"✅ 第 {i+1}/{repeats} 次转移完成! 剩余 {remaining_volume:.2f}mL 待转移 ⏳"
else:
end_message = f"🎉 第 {i+1}/{repeats} 次转移完成! 全部 {volume:.2f}mL 转移完毕 ✨"
pump_action_sequence.append(create_progress_log_action(end_message))
volume_left -= current_volume
return pump_action_sequence
def generate_pump_protocol_with_rinsing(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
volume: float = 0.0,
amount: str = "",
time: float = 0.0, # 🔧 修复:统一使用 time
viscous: bool = False,
rinsing_solvent: str = "",
rinsing_volume: float = 0.0,
rinsing_repeats: int = 0,
solid: bool = False,
flowrate: float = 2.5,
transfer_flowrate: float = 0.5,
rate_spec: str = "",
event: str = "",
through: str = "",
**kwargs
) -> List[Dict[str, Any]]:
"""
原有的同步版本,添加防冲突机制
"""
# 添加执行锁,防止并发调用
import threading
if not hasattr(generate_pump_protocol_with_rinsing, '_lock'):
generate_pump_protocol_with_rinsing._lock = threading.Lock()
with generate_pump_protocol_with_rinsing._lock:
debug_print("=" * 60)
debug_print(f"PUMP_TRANSFER: 🚀 开始生成协议 (同步版本)")
debug_print(f" 📍 路径: {from_vessel} -> {to_vessel}")
debug_print(f" 🕐 时间戳: {time_module.time()}")
debug_print(f" 🔒 获得执行锁")
debug_print("=" * 60)
# 短暂延迟,避免快速重复调用
time_module.sleep(0.01)
debug_print("🔍 步骤1: 开始体积处理...")
# 1. 处理体积参数
final_volume = volume
debug_print(f"📋 初始设置: final_volume = {final_volume}")
# 🔧 修复如果volume为0ROS2传入的空值从容器读取实际体积
if volume == 0.0:
debug_print("🎯 检测到 volume=0.0,开始自动体积检测...")
# 直接从源容器读取实际体积
actual_volume = get_vessel_liquid_volume(G, from_vessel)
debug_print(f"📖 从容器 '{from_vessel}' 读取到体积: {actual_volume}mL")
if actual_volume > 0:
final_volume = actual_volume
debug_print(f"✅ 成功设置体积为: {final_volume}mL")
else:
final_volume = 10.0 # 如果读取失败,使用默认值
logger.warning(f"⚠️ 无法从容器读取体积,使用默认值: {final_volume}mL")
else:
debug_print(f"📌 体积非零,直接使用: {final_volume}mL")
# 处理 amount 参数
if amount and amount.strip():
debug_print(f"🔍 检测到 amount 参数: '{amount}',开始解析...")
parsed_volume = _parse_amount_to_volume(amount)
debug_print(f"📖 从 amount 解析得到体积: {parsed_volume}mL")
if parsed_volume > 0:
final_volume = parsed_volume
debug_print(f"✅ 使用从 amount 解析的体积: {final_volume}mL")
elif parsed_volume == 0.0 and amount.lower().strip() == "all":
debug_print("🎯 检测到 amount='all',从容器读取全部体积...")
actual_volume = get_vessel_liquid_volume(G, from_vessel)
if actual_volume > 0:
final_volume = actual_volume
debug_print(f"✅ amount='all',设置体积为: {final_volume}mL")
# 最终体积验证
debug_print(f"🔍 步骤2: 最终体积验证...")
if final_volume <= 0:
logger.error(f"❌ 体积无效: {final_volume}mL")
final_volume = 10.0
logger.warning(f"⚠️ 强制设置为默认值: {final_volume}mL")
debug_print(f"✅ 最终确定体积: {final_volume}mL")
# 2. 处理流速参数
debug_print(f"🔍 步骤3: 处理流速参数...")
debug_print(f" - 原始 flowrate: {flowrate}")
debug_print(f" - 原始 transfer_flowrate: {transfer_flowrate}")
final_flowrate = flowrate if flowrate > 0 else 2.5
final_transfer_flowrate = transfer_flowrate if transfer_flowrate > 0 else 0.5
if flowrate <= 0:
logger.warning(f"⚠️ flowrate <= 0修正为: {final_flowrate}mL/s")
if transfer_flowrate <= 0:
logger.warning(f"⚠️ transfer_flowrate <= 0修正为: {final_transfer_flowrate}mL/s")
debug_print(f"✅ 修正后流速: flowrate={final_flowrate}mL/s, transfer_flowrate={final_transfer_flowrate}mL/s")
# 3. 根据时间计算流速
if time > 0 and final_volume > 0:
debug_print(f"🔍 步骤4: 根据时间计算流速...")
calculated_flowrate = final_volume / time
debug_print(f" - 计算得到流速: {calculated_flowrate}mL/s")
if flowrate <= 0 or flowrate == 2.5:
final_flowrate = min(calculated_flowrate, 10.0)
debug_print(f" - 调整 flowrate 为: {final_flowrate}mL/s")
if transfer_flowrate <= 0 or transfer_flowrate == 0.5:
final_transfer_flowrate = min(calculated_flowrate, 5.0)
debug_print(f" - 调整 transfer_flowrate 为: {final_transfer_flowrate}mL/s")
# 4. 根据速度规格调整
if rate_spec:
debug_print(f"🔍 步骤5: 根据速度规格调整...")
debug_print(f" - 速度规格: '{rate_spec}'")
if rate_spec == "dropwise":
final_flowrate = min(final_flowrate, 0.1)
final_transfer_flowrate = min(final_transfer_flowrate, 0.1)
debug_print(f" - dropwise模式流速调整为: {final_flowrate}mL/s")
elif rate_spec == "slowly":
final_flowrate = min(final_flowrate, 0.5)
final_transfer_flowrate = min(final_transfer_flowrate, 0.3)
debug_print(f" - slowly模式流速调整为: {final_flowrate}mL/s")
elif rate_spec == "quickly":
final_flowrate = max(final_flowrate, 5.0)
final_transfer_flowrate = max(final_transfer_flowrate, 2.0)
debug_print(f" - quickly模式流速调整为: {final_flowrate}mL/s")
try:
# 🆕 修复在这里调用带有循环日志的generate_pump_protocol_with_loop_logging函数
pump_action_sequence = generate_pump_protocol_with_loop_logging(
G, from_vessel, to_vessel, final_volume,
final_flowrate, final_transfer_flowrate
)
debug_print(f"🔓 释放执行锁")
return pump_action_sequence
except Exception as e:
logger.error(f"❌ 协议生成失败: {str(e)}")
return [
{
"device_id": "system",
"action_name": "log_message",
"action_kwargs": {
"message": f"❌ 协议生成失败: {str(e)}"
}
}
]
def generate_pump_protocol_with_loop_logging(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
volume: float,
flowrate: float = 2.5,
transfer_flowrate: float = 0.5,
) -> List[Dict[str, Any]]:
"""
生成泵操作的动作序列 - 带循环日志版本
🔧 修复:正确处理包含电磁阀的路径,并在合适时机打印循环日志
"""
pump_action_sequence = []
nodes = G.nodes(data=True)
# 验证输入参数
if volume <= 0:
logger.error(f"无效的体积参数: {volume}mL")
return pump_action_sequence
if flowrate <= 0:
flowrate = 2.5
logger.warning(f"flowrate <= 0使用默认值 {flowrate}mL/s")
if transfer_flowrate <= 0:
transfer_flowrate = 0.5
logger.warning(f"transfer_flowrate <= 0使用默认值 {transfer_flowrate}mL/s")
# 验证容器存在
if from_vessel not in G.nodes():
logger.error(f"源容器 '{from_vessel}' 不存在")
return pump_action_sequence
if to_vessel not in G.nodes():
logger.error(f"目标容器 '{to_vessel}' 不存在")
return pump_action_sequence
try:
shortest_path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
debug_print(f"PUMP_TRANSFER: 路径 {from_vessel} -> {to_vessel}: {shortest_path}")
except nx.NetworkXNoPath:
logger.error(f"无法找到从 '{from_vessel}''{to_vessel}' 的路径")
return pump_action_sequence
# 🔧 关键修复:正确构建泵骨架,排除容器和电磁阀
pump_backbone = []
for node in shortest_path:
# 跳过起始和结束容器
if node == from_vessel or node == to_vessel:
continue
# 跳过电磁阀(电磁阀不参与泵操作)
node_data = G.nodes.get(node, {})
node_class = node_data.get("class", "") or ""
if ("solenoid" in node_class.lower() or "solenoid_valve" in node.lower()):
debug_print(f"PUMP_TRANSFER: 跳过电磁阀 {node}")
continue
# 只包含多通阀和泵
if ("multiway" in node_class.lower() or "valve" in node_class.lower() or "pump" in node_class.lower()):
pump_backbone.append(node)
debug_print(f"PUMP_TRANSFER: 过滤后的泵骨架: {pump_backbone}")
if not pump_backbone:
debug_print("PUMP_TRANSFER: 没有泵骨架节点,可能是直接容器连接或只有电磁阀")
return pump_action_sequence
if transfer_flowrate == 0:
transfer_flowrate = flowrate
try:
pumps_from_node, valve_from_node = build_pump_valve_maps(G, pump_backbone)
except Exception as e:
debug_print(f"PUMP_TRANSFER: 构建泵-阀门映射失败: {str(e)}")
return pump_action_sequence
if not pumps_from_node:
debug_print("PUMP_TRANSFER: 没有可用的泵映射")
return pump_action_sequence
# 🔧 修复:安全地获取最小转移体积
try:
min_transfer_volumes = []
for node in pump_backbone:
if node in pumps_from_node:
pump_node = pumps_from_node[node]
if pump_node in nodes:
pump_config = nodes[pump_node].get("config", {})
max_volume = pump_config.get("max_volume")
if max_volume is not None:
min_transfer_volumes.append(max_volume)
if min_transfer_volumes:
min_transfer_volume = min(min_transfer_volumes)
else:
min_transfer_volume = 25.0 # 默认值
debug_print(f"PUMP_TRANSFER: 无法获取泵的最大体积,使用默认值: {min_transfer_volume}mL")
except Exception as e:
debug_print(f"PUMP_TRANSFER: 获取最小转移体积失败: {str(e)}")
min_transfer_volume = 25.0 # 默认值
repeats = int(np.ceil(volume / min_transfer_volume))
if repeats > 1 and (from_vessel.startswith("pump") or to_vessel.startswith("pump")):
logger.error("Cannot transfer volume larger than min_transfer_volume between two pumps.")
return pump_action_sequence
volume_left = volume
debug_print(f"PUMP_TRANSFER: 需要 {repeats} 次转移,单次最大体积 {min_transfer_volume} mL")
# 🆕 只在开头打印总体概览
if repeats > 1:
debug_print(f"🔄 分批转移概览: 总体积 {volume:.2f}mL需要 {repeats} 次转移")
logger.info(f"🔄 分批转移概览: 总体积 {volume:.2f}mL需要 {repeats} 次转移")
# 🔧 创建一个自定义的wait动作用于在执行时打印日志
def create_progress_log_action(message: str) -> Dict[str, Any]:
"""创建一个特殊的等待动作,在执行时打印进度日志"""
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1, # 很短的等待时间
"progress_message": message # 自定义字段,用于进度日志
}
}
# 生成泵操作序列
for i in range(repeats):
current_volume = min(volume_left, min_transfer_volume)
# 🆕 在每次循环开始时添加进度日志
if repeats > 1:
start_message = f"🚀 准备开始第 {i+1}/{repeats} 次转移: {current_volume:.2f}mL ({from_vessel}{to_vessel}) 🚰"
pump_action_sequence.append(create_progress_log_action(start_message))
# 🔧 修复:安全地获取边数据
def get_safe_edge_data(node_a, node_b, key):
try:
edge_data = G.get_edge_data(node_a, node_b)
if edge_data and "port" in edge_data:
port_data = edge_data["port"]
if isinstance(port_data, dict) and key in port_data:
return port_data[key]
return "default"
except Exception as e:
debug_print(f"PUMP_TRANSFER: 获取边数据失败 {node_a}->{node_b}: {str(e)}")
return "default"
# 从源容器吸液
if not from_vessel.startswith("pump") and pump_backbone:
first_pump_node = pump_backbone[0]
if first_pump_node in valve_from_node and first_pump_node in pumps_from_node:
port_command = get_safe_edge_data(first_pump_node, from_vessel, first_pump_node)
pump_action_sequence.extend([
{
"device_id": valve_from_node[first_pump_node],
"action_name": "set_valve_position",
"action_kwargs": {
"command": port_command
}
},
{
"device_id": pumps_from_node[first_pump_node],
"action_name": "set_position",
"action_kwargs": {
"position": float(current_volume),
"max_velocity": transfer_flowrate
}
}
])
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 3}})
# 泵间转移
for nodeA, nodeB in zip(pump_backbone[:-1], pump_backbone[1:]):
if nodeA in valve_from_node and nodeB in valve_from_node and nodeA in pumps_from_node and nodeB in pumps_from_node:
port_a = get_safe_edge_data(nodeA, nodeB, nodeA)
port_b = get_safe_edge_data(nodeB, nodeA, nodeB)
pump_action_sequence.append([
{
"device_id": valve_from_node[nodeA],
"action_name": "set_valve_position",
"action_kwargs": {
"command": port_a
}
},
{
"device_id": valve_from_node[nodeB],
"action_name": "set_valve_position",
"action_kwargs": {
"command": port_b
}
}
])
pump_action_sequence.append([
{
"device_id": pumps_from_node[nodeA],
"action_name": "set_position",
"action_kwargs": {
"position": 0.0,
"max_velocity": transfer_flowrate
}
},
{
"device_id": pumps_from_node[nodeB],
"action_name": "set_position",
"action_kwargs": {
"position": float(current_volume),
"max_velocity": transfer_flowrate
}
}
])
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 3}})
# 排液到目标容器
if not to_vessel.startswith("pump") and pump_backbone:
last_pump_node = pump_backbone[-1]
if last_pump_node in valve_from_node and last_pump_node in pumps_from_node:
port_command = get_safe_edge_data(last_pump_node, to_vessel, last_pump_node)
pump_action_sequence.extend([
{
"device_id": valve_from_node[last_pump_node],
"action_name": "set_valve_position",
"action_kwargs": {
"command": port_command
}
},
{
"device_id": pumps_from_node[last_pump_node],
"action_name": "set_position",
"action_kwargs": {
"position": 0.0,
"max_velocity": flowrate
}
}
])
pump_action_sequence.append({"action_name": "wait", "action_kwargs": {"time": 3}})
# 🆕 在每次循环结束时添加完成日志
if repeats > 1:
remaining_volume = volume_left - current_volume
if remaining_volume > 0:
end_message = f"✅ 第 {i+1}/{repeats} 次转移完成! 剩余 {remaining_volume:.2f}mL 待转移 ⏳"
else:
end_message = f"🎉 第 {i+1}/{repeats} 次转移完成! 全部 {volume:.2f}mL 转移完毕 ✨"
pump_action_sequence.append(create_progress_log_action(end_message))
volume_left -= current_volume
return pump_action_sequence
@@ -891,58 +1331,386 @@ def generate_pump_protocol_with_rinsing(
final_flowrate = max(final_flowrate, 5.0)
final_transfer_flowrate = max(final_transfer_flowrate, 2.0)
debug_print(f" - quickly模式流速调整为: {final_flowrate}mL/s")
# # 5. 处理冲洗参数
# debug_print(f"🔍 步骤6: 处理冲洗参数...")
# final_rinsing_solvent = rinsing_solvent
# final_rinsing_volume = rinsing_volume if rinsing_volume > 0 else 5.0
# final_rinsing_repeats = rinsing_repeats if rinsing_repeats > 0 else 2
# if rinsing_volume <= 0:
# logger.warning(f"⚠️ rinsing_volume <= 0修正为: {final_rinsing_volume}mL")
# if rinsing_repeats <= 0:
# logger.warning(f"⚠️ rinsing_repeats <= 0修正为: {final_rinsing_repeats}次")
# # 根据物理属性调整冲洗参数
# if viscous or solid:
# final_rinsing_repeats = max(final_rinsing_repeats, 3)
# final_rinsing_volume = max(final_rinsing_volume, 10.0)
# debug_print(f"🧪 粘稠/固体物质,调整冲洗参数:{final_rinsing_repeats}次,{final_rinsing_volume}mL")
# 参数总结
debug_print("📊 最终参数总结:")
debug_print(f" - 体积: {final_volume}mL")
debug_print(f" - 流速: {final_flowrate}mL/s")
debug_print(f" - 转移流速: {final_transfer_flowrate}mL/s")
# debug_print(f" - 冲洗溶剂: '{final_rinsing_solvent}'")
# debug_print(f" - 冲洗体积: {final_rinsing_volume}mL")
# debug_print(f" - 冲洗次数: {final_rinsing_repeats}次")
# ========== 执行基础转移 ==========
debug_print("🔧 步骤7: 开始执行基础转移...")
try:
debug_print(f" - 调用 generate_pump_protocol...")
debug_print(f" - 参数: G, '{from_vessel}', '{to_vessel}', {final_volume}, {final_flowrate}, {final_transfer_flowrate}")
# # 5. 处理冲洗参数
# debug_print(f"🔍 步骤6: 处理冲洗参数...")
# final_rinsing_solvent = rinsing_solvent
# final_rinsing_volume = rinsing_volume if rinsing_volume > 0 else 5.0
# final_rinsing_repeats = rinsing_repeats if rinsing_repeats > 0 else 2
pump_action_sequence = generate_pump_protocol(
G, from_vessel, to_vessel, final_volume,
final_flowrate, final_transfer_flowrate
)
# if rinsing_volume <= 0:
# logger.warning(f"⚠️ rinsing_volume <= 0修正为: {final_rinsing_volume}mL")
# if rinsing_repeats <= 0:
# logger.warning(f"⚠️ rinsing_repeats <= 0修正为: {final_rinsing_repeats}次")
debug_print(f" - generate_pump_protocol 返回结果:")
debug_print(f" - 动作序列长度: {len(pump_action_sequence)}")
debug_print(f" - 动作序列是否为空: {len(pump_action_sequence) == 0}")
# # 根据物理属性调整冲洗参数
# if viscous or solid:
# final_rinsing_repeats = max(final_rinsing_repeats, 3)
# final_rinsing_volume = max(final_rinsing_volume, 10.0)
# debug_print(f"🧪 粘稠/固体物质,调整冲洗参数:{final_rinsing_repeats}次,{final_rinsing_volume}mL")
try:
pump_action_sequence = generate_pump_protocol(
G, from_vessel, to_vessel, final_volume,
flowrate, transfer_flowrate
)
if not pump_action_sequence:
debug_print("❌ 基础转移协议生成为空,可能是路径问题")
debug_print(f" - 源容器存在: {from_vessel in G.nodes()}")
debug_print(f" - 目标容器存在: {to_vessel in G.nodes()}")
# 为每个动作添加唯一标识
# for i, action in enumerate(pump_action_sequence):
# if isinstance(action, dict):
# action['_protocol_id'] = protocol_id
# action['_action_sequence'] = i
# elif isinstance(action, list):
# for j, sub_action in enumerate(action):
# if isinstance(sub_action, dict):
# sub_action['_protocol_id'] = protocol_id
# sub_action['_action_sequence'] = f"{i}_{j}"
#
# debug_print(f"📊 协议 {protocol_id} 生成完成,共 {len(pump_action_sequence)} 个动作")
debug_print(f"🔓 释放执行锁")
return pump_action_sequence
if from_vessel in G.nodes() and to_vessel in G.nodes():
try:
path = nx.shortest_path(G, source=from_vessel, target=to_vessel)
debug_print(f" - 路径存在: {path}")
except Exception as path_error:
debug_print(f" - 无法找到路径: {str(path_error)}")
except Exception as e:
logger.error(f"❌ 协议生成失败: {str(e)}")
return [
{
"device_id": "system",
"action_name": "log_message",
"action_kwargs": {
"message": f"❌ 协议生成失败: {str(e)}"
},
'_protocol_id': protocol_id,
'_action_sequence': 0
"message": f"⚠️ 路径问题,无法转移: {final_volume}mL 从 {from_vessel}{to_vessel}"
}
}
]
debug_print(f"✅ 基础转移生成了 {len(pump_action_sequence)} 个动作")
# 打印前几个动作用于调试
if len(pump_action_sequence) > 0:
debug_print("🔍 前几个动作预览:")
for i, action in enumerate(pump_action_sequence[:3]):
debug_print(f" 动作 {i+1}: {action}")
if len(pump_action_sequence) > 3:
debug_print(f" ... 还有 {len(pump_action_sequence) - 3} 个动作")
except Exception as e:
debug_print(f"❌ 基础转移失败: {str(e)}")
import traceback
debug_print(f"详细错误: {traceback.format_exc()}")
return [
{
"device_id": "system",
"action_name": "log_message",
"action_kwargs": {
"message": f"❌ 转移失败: {final_volume}mL 从 {from_vessel}{to_vessel}, 错误: {str(e)}"
}
}
]
# ========== 执行冲洗操作 ==========
# debug_print("🔧 步骤8: 检查冲洗操作...")
# if final_rinsing_solvent and final_rinsing_solvent.strip() and final_rinsing_repeats > 0:
# debug_print(f"🧽 开始冲洗操作,溶剂: '{final_rinsing_solvent}'")
# try:
# if final_rinsing_solvent.strip() != "air":
# debug_print(" - 执行液体冲洗...")
# rinsing_actions = _generate_rinsing_sequence(
# G, from_vessel, to_vessel, final_rinsing_solvent,
# final_rinsing_volume, final_rinsing_repeats,
# final_flowrate, final_transfer_flowrate
# )
# pump_action_sequence.extend(rinsing_actions)
# debug_print(f" - 添加了 {len(rinsing_actions)} 个冲洗动作")
# else:
# debug_print(" - 执行空气冲洗...")
# air_rinsing_actions = _generate_air_rinsing_sequence(
# G, from_vessel, to_vessel, final_rinsing_volume, final_rinsing_repeats,
# final_flowrate, final_transfer_flowrate
# )
# pump_action_sequence.extend(air_rinsing_actions)
# debug_print(f" - 添加了 {len(air_rinsing_actions)} 个空气冲洗动作")
# except Exception as e:
# debug_print(f"⚠️ 冲洗操作失败: {str(e)},跳过冲洗")
# else:
# debug_print(f"⏭️ 跳过冲洗操作")
# debug_print(f" - 溶剂: '{final_rinsing_solvent}'")
# debug_print(f" - 次数: {final_rinsing_repeats}")
# debug_print(f" - 条件满足: {bool(final_rinsing_solvent and final_rinsing_solvent.strip() and final_rinsing_repeats > 0)}")
# ========== 最终结果 ==========
debug_print("=" * 60)
debug_print(f"🎉 PUMP_TRANSFER: 协议生成完成")
debug_print(f" 📊 总动作数: {len(pump_action_sequence)}")
debug_print(f" 📋 最终体积: {final_volume}mL")
debug_print(f" 🚀 执行路径: {from_vessel} -> {to_vessel}")
# 最终验证
if len(pump_action_sequence) == 0:
debug_print("🚨 协议生成结果为空!这是异常情况")
return [
{
"device_id": "system",
"action_name": "log_message",
"action_kwargs": {
"message": f"🚨 协议生成失败: 无法生成任何动作序列"
}
}
]
debug_print("=" * 60)
return pump_action_sequence
async def generate_pump_protocol_with_rinsing_async(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
volume: float = 0.0,
amount: str = "",
time: float = 0.0,
viscous: bool = False,
rinsing_solvent: str = "",
rinsing_volume: float = 0.0,
rinsing_repeats: int = 0,
solid: bool = False,
flowrate: float = 2.5,
transfer_flowrate: float = 0.5,
rate_spec: str = "",
event: str = "",
through: str = "",
**kwargs
) -> List[Dict[str, Any]]:
"""
异步版本的泵转移协议生成器,避免并发问题
"""
debug_print("=" * 60)
debug_print(f"PUMP_TRANSFER: 🚀 开始生成协议 (异步版本)")
debug_print(f" 📍 路径: {from_vessel} -> {to_vessel}")
debug_print(f" 🕐 时间戳: {time_module.time()}")
debug_print("=" * 60)
# 添加唯一标识符
protocol_id = f"pump_transfer_{int(time_module.time() * 1000000)}"
debug_print(f"📋 协议ID: {protocol_id}")
# 调用原有的同步版本
result = generate_pump_protocol_with_rinsing(
G, from_vessel, to_vessel, volume, amount, time, viscous,
rinsing_solvent, rinsing_volume, rinsing_repeats, solid,
flowrate, transfer_flowrate, rate_spec, event, through, **kwargs
)
# 为每个动作添加唯一标识
for i, action in enumerate(result):
if isinstance(action, dict):
action['_protocol_id'] = protocol_id
action['_action_sequence'] = i
action['_timestamp'] = time_module.time()
debug_print(f"📊 协议 {protocol_id} 生成完成,共 {len(result)} 个动作")
return result
# 保持原有的同步版本兼容性
def generate_pump_protocol_with_rinsing(
G: nx.DiGraph,
from_vessel: str,
to_vessel: str,
volume: float = 0.0,
amount: str = "",
time: float = 0.0,
viscous: bool = False,
rinsing_solvent: str = "",
rinsing_volume: float = 0.0,
rinsing_repeats: int = 0,
solid: bool = False,
flowrate: float = 2.5,
transfer_flowrate: float = 0.5,
rate_spec: str = "",
event: str = "",
through: str = "",
**kwargs
) -> List[Dict[str, Any]]:
"""
原有的同步版本,添加防冲突机制
"""
# 添加执行锁,防止并发调用
import threading
if not hasattr(generate_pump_protocol_with_rinsing, '_lock'):
generate_pump_protocol_with_rinsing._lock = threading.Lock()
with generate_pump_protocol_with_rinsing._lock:
debug_print("=" * 60)
debug_print(f"PUMP_TRANSFER: 🚀 开始生成协议 (同步版本)")
debug_print(f" 📍 路径: {from_vessel} -> {to_vessel}")
debug_print(f" 🕐 时间戳: {time_module.time()}")
debug_print(f" 🔒 获得执行锁")
debug_print("=" * 60)
# 短暂延迟,避免快速重复调用
time_module.sleep(0.01)
debug_print("🔍 步骤1: 开始体积处理...")
# 1. 处理体积参数
final_volume = volume
debug_print(f"📋 初始设置: final_volume = {final_volume}")
# 🔧 修复如果volume为0ROS2传入的空值从容器读取实际体积
if volume == 0.0:
debug_print("🎯 检测到 volume=0.0,开始自动体积检测...")
# 直接从源容器读取实际体积
actual_volume = get_vessel_liquid_volume(G, from_vessel)
debug_print(f"📖 从容器 '{from_vessel}' 读取到体积: {actual_volume}mL")
if actual_volume > 0:
final_volume = actual_volume
debug_print(f"✅ 成功设置体积为: {final_volume}mL")
else:
final_volume = 10.0 # 如果读取失败,使用默认值
logger.warning(f"⚠️ 无法从容器读取体积,使用默认值: {final_volume}mL")
else:
debug_print(f"📌 体积非零,直接使用: {final_volume}mL")
# 处理 amount 参数
if amount and amount.strip():
debug_print(f"🔍 检测到 amount 参数: '{amount}',开始解析...")
parsed_volume = _parse_amount_to_volume(amount)
debug_print(f"📖 从 amount 解析得到体积: {parsed_volume}mL")
if parsed_volume > 0:
final_volume = parsed_volume
debug_print(f"✅ 使用从 amount 解析的体积: {final_volume}mL")
elif parsed_volume == 0.0 and amount.lower().strip() == "all":
debug_print("🎯 检测到 amount='all',从容器读取全部体积...")
actual_volume = get_vessel_liquid_volume(G, from_vessel)
if actual_volume > 0:
final_volume = actual_volume
debug_print(f"✅ amount='all',设置体积为: {final_volume}mL")
# 最终体积验证
debug_print(f"🔍 步骤2: 最终体积验证...")
if final_volume <= 0:
logger.error(f"❌ 体积无效: {final_volume}mL")
final_volume = 10.0
logger.warning(f"⚠️ 强制设置为默认值: {final_volume}mL")
debug_print(f"✅ 最终确定体积: {final_volume}mL")
# 2. 处理流速参数
debug_print(f"🔍 步骤3: 处理流速参数...")
debug_print(f" - 原始 flowrate: {flowrate}")
debug_print(f" - 原始 transfer_flowrate: {transfer_flowrate}")
final_flowrate = flowrate if flowrate > 0 else 2.5
final_transfer_flowrate = transfer_flowrate if transfer_flowrate > 0 else 0.5
if flowrate <= 0:
logger.warning(f"⚠️ flowrate <= 0修正为: {final_flowrate}mL/s")
if transfer_flowrate <= 0:
logger.warning(f"⚠️ transfer_flowrate <= 0修正为: {final_transfer_flowrate}mL/s")
debug_print(f"✅ 修正后流速: flowrate={final_flowrate}mL/s, transfer_flowrate={final_transfer_flowrate}mL/s")
# 3. 根据时间计算流速
if time > 0 and final_volume > 0:
debug_print(f"🔍 步骤4: 根据时间计算流速...")
calculated_flowrate = final_volume / time
debug_print(f" - 计算得到流速: {calculated_flowrate}mL/s")
if flowrate <= 0 or flowrate == 2.5:
final_flowrate = min(calculated_flowrate, 10.0)
debug_print(f" - 调整 flowrate 为: {final_flowrate}mL/s")
if transfer_flowrate <= 0 or transfer_flowrate == 0.5:
final_transfer_flowrate = min(calculated_flowrate, 5.0)
debug_print(f" - 调整 transfer_flowrate 为: {final_transfer_flowrate}mL/s")
# 4. 根据速度规格调整
if rate_spec:
debug_print(f"🔍 步骤5: 根据速度规格调整...")
debug_print(f" - 速度规格: '{rate_spec}'")
if rate_spec == "dropwise":
final_flowrate = min(final_flowrate, 0.1)
final_transfer_flowrate = min(final_transfer_flowrate, 0.1)
debug_print(f" - dropwise模式流速调整为: {final_flowrate}mL/s")
elif rate_spec == "slowly":
final_flowrate = min(final_flowrate, 0.5)
final_transfer_flowrate = min(final_transfer_flowrate, 0.3)
debug_print(f" - slowly模式流速调整为: {final_flowrate}mL/s")
elif rate_spec == "quickly":
final_flowrate = max(final_flowrate, 5.0)
final_transfer_flowrate = max(final_transfer_flowrate, 2.0)
debug_print(f" - quickly模式流速调整为: {final_flowrate}mL/s")
# # 5. 处理冲洗参数
# debug_print(f"🔍 步骤6: 处理冲洗参数...")
# final_rinsing_solvent = rinsing_solvent
# final_rinsing_volume = rinsing_volume if rinsing_volume > 0 else 5.0
# final_rinsing_repeats = rinsing_repeats if rinsing_repeats > 0 else 2
# if rinsing_volume <= 0:
# logger.warning(f"⚠️ rinsing_volume <= 0修正为: {final_rinsing_volume}mL")
# if rinsing_repeats <= 0:
# logger.warning(f"⚠️ rinsing_repeats <= 0修正为: {final_rinsing_repeats}次")
# # 根据物理属性调整冲洗参数
# if viscous or solid:
# final_rinsing_repeats = max(final_rinsing_repeats, 3)
# final_rinsing_volume = max(final_rinsing_volume, 10.0)
# debug_print(f"🧪 粘稠/固体物质,调整冲洗参数:{final_rinsing_repeats}次,{final_rinsing_volume}mL")
try:
pump_action_sequence = generate_pump_protocol(
G, from_vessel, to_vessel, final_volume,
flowrate, transfer_flowrate
)
# 为每个动作添加唯一标识
# for i, action in enumerate(pump_action_sequence):
# if isinstance(action, dict):
# action['_protocol_id'] = protocol_id
# action['_action_sequence'] = i
# elif isinstance(action, list):
# for j, sub_action in enumerate(action):
# if isinstance(sub_action, dict):
# sub_action['_protocol_id'] = protocol_id
# sub_action['_action_sequence'] = f"{i}_{j}"
#
# debug_print(f"📊 协议 {protocol_id} 生成完成,共 {len(pump_action_sequence)} 个动作")
debug_print(f"🔓 释放执行锁")
return pump_action_sequence
except Exception as e:
logger.error(f"❌ 协议生成失败: {str(e)}")
return [
{
"device_id": "system",
"action_name": "log_message",
"action_kwargs": {
"message": f"❌ 协议生成失败: {str(e)}"
}
}
]
def _parse_amount_to_volume(amount: str) -> float:
"""解析 amount 字符串为体积"""

View File

@@ -1,15 +1,67 @@
import networkx as nx
import re
import logging
import sys
from typing import List, Dict, Any, Union
from .pump_protocol import generate_pump_protocol_with_rinsing
logger = logging.getLogger(__name__)
# 确保输出编码为UTF-8
if hasattr(sys.stdout, 'reconfigure'):
try:
sys.stdout.reconfigure(encoding='utf-8')
sys.stderr.reconfigure(encoding='utf-8')
except:
pass
def debug_print(message):
"""调试输出"""
print(f"[SEPARATE] {message}", flush=True)
logger.info(f"[SEPARATE] {message}")
"""调试输出函数 - 支持中文"""
try:
# 确保消息是字符串格式
safe_message = str(message)
print(f"[分离协议] {safe_message}", flush=True)
logger.info(f"[分离协议] {safe_message}")
except UnicodeEncodeError:
# 如果编码失败,尝试替换不支持的字符
safe_message = str(message).encode('utf-8', errors='replace').decode('utf-8')
print(f"[分离协议] {safe_message}", flush=True)
logger.info(f"[分离协议] {safe_message}")
except Exception as e:
# 最后的安全措施
fallback_message = f"日志输出错误: {repr(message)}"
print(f"[分离协议] {fallback_message}", flush=True)
logger.info(f"[分离协议] {fallback_message}")
def create_action_log(message: str, emoji: str = "📝") -> Dict[str, Any]:
"""创建一个动作日志 - 支持中文和emoji"""
try:
full_message = f"{emoji} {message}"
debug_print(full_message)
logger.info(full_message)
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1,
"log_message": full_message,
"progress_message": full_message
}
}
except Exception as e:
# 如果emoji有问题使用纯文本
safe_message = f"[日志] {message}"
debug_print(safe_message)
logger.info(safe_message)
return {
"action_name": "wait",
"action_kwargs": {
"time": 0.1,
"log_message": safe_message,
"progress_message": safe_message
}
}
def parse_volume_input(volume_input: Union[str, float]) -> float:
"""
@@ -22,52 +74,58 @@ def parse_volume_input(volume_input: Union[str, float]) -> float:
float: 体积(毫升)
"""
if isinstance(volume_input, (int, float)):
debug_print(f"📏 体积输入为数值: {volume_input}")
return float(volume_input)
if not volume_input or not str(volume_input).strip():
debug_print(f"⚠️ 体积输入为空,返回 0.0mL")
return 0.0
volume_str = str(volume_input).lower().strip()
debug_print(f"解析体积输入: '{volume_str}'")
debug_print(f"🔍 解析体积输入: '{volume_str}'")
# 处理未知体积
if volume_str in ['?', 'unknown', 'tbd', 'to be determined']:
if volume_str in ['?', 'unknown', 'tbd', 'to be determined', '未知', '待定']:
default_volume = 100.0 # 默认100mL
debug_print(f"检测到未知体积,使用默认值: {default_volume}mL")
debug_print(f"检测到未知体积,使用默认值: {default_volume}mL")
return default_volume
# 移除空格并提取数字和单位
volume_clean = re.sub(r'\s+', '', volume_str)
# 匹配数字和单位的正则表达式
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter)?', volume_clean)
match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter|毫升|升|微升)?', volume_clean)
if not match:
debug_print(f"⚠️ 无法解析体积: '{volume_str}'使用默认值100mL")
debug_print(f"⚠️ 无法解析体积: '{volume_str}',使用默认值 100mL")
return 100.0
value = float(match.group(1))
unit = match.group(2) or 'ml' # 默认单位为毫升
# 转换为毫升
if unit in ['l', 'liter']:
if unit in ['l', 'liter', '']:
volume = value * 1000.0 # L -> mL
elif unit in ['μl', 'ul', 'microliter']:
debug_print(f"🔄 体积转换: {value}L -> {volume}mL")
elif unit in ['μl', 'ul', 'microliter', '微升']:
volume = value / 1000.0 # μL -> mL
else: # ml, milliliter 或默认
debug_print(f"🔄 体积转换: {value}μL -> {volume}mL")
else: # ml, milliliter, 毫升 或默认
volume = value # 已经是mL
debug_print(f"✅ 体积已为毫升单位: {volume}mL")
debug_print(f"体积转换: {value}{unit}{volume}mL")
return volume
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
"""查找溶剂容器"""
"""查找溶剂容器,支持多种匹配模式"""
if not solvent or not solvent.strip():
debug_print("⏭️ 未指定溶剂,跳过溶剂容器查找")
return ""
debug_print(f"查找溶剂 '{solvent}' 的容器...")
debug_print(f"🔍 正在查找溶剂 '{solvent}' 的容器...")
# 🔧 方法1直接搜索 data.reagent_name 和 config.reagent
debug_print(f"📋 方法1: 搜索试剂字段...")
for node in G.nodes():
node_data = G.nodes[node].get('data', {})
node_type = G.nodes[node].get('type', '')
@@ -80,16 +138,17 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
# 精确匹配
if reagent_name == solvent.lower() or config_reagent == solvent.lower():
debug_print(f"✅ 通过reagent字段找到容器: {node}")
debug_print(f"✅ 通过试剂字段精确匹配找到容器: {node}")
return node
# 模糊匹配
if (solvent.lower() in reagent_name and reagent_name) or \
(solvent.lower() in config_reagent and config_reagent):
debug_print(f"✅ 通过reagent字段模糊匹配到容器: {node}")
debug_print(f"✅ 通过试剂字段模糊匹配到容器: {node}")
return node
# 🔧 方法2常见的容器命名规则
debug_print(f"📋 方法2: 使用命名规则...")
solvent_clean = solvent.lower().replace(' ', '_').replace('-', '_')
possible_names = [
f"flask_{solvent_clean}",
@@ -99,9 +158,14 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
f"{solvent_clean}_bottle",
f"solvent_{solvent_clean}",
f"reagent_{solvent_clean}",
f"reagent_bottle_{solvent_clean}"
f"reagent_bottle_{solvent_clean}",
f"reagent_bottle_1", # 通用试剂瓶
f"reagent_bottle_2",
f"reagent_bottle_3"
]
debug_print(f"🎯 尝试的容器名称: {possible_names[:5]}... (共 {len(possible_names)} 个)")
for name in possible_names:
if name in G.nodes():
node_type = G.nodes[name].get('type', '')
@@ -110,53 +174,94 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
return name
# 🔧 方法3使用第一个试剂瓶作为备选
debug_print(f"📋 方法3: 查找备用试剂瓶...")
for node_id in G.nodes():
node_data = G.nodes[node_id]
if (node_data.get('type') == 'container' and
('reagent' in node_id.lower() or 'bottle' in node_id.lower())):
debug_print(f"⚠️ 未找到专用容器,使用备容器: {node_id}")
debug_print(f"⚠️ 未找到专用容器,使用备容器: {node_id}")
return node_id
debug_print(f"⚠️ 未找到溶剂 '{solvent}' 的容器")
debug_print(f"❌ 无法找到溶剂 '{solvent}' 的容器")
return ""
def find_separator_device(G: nx.DiGraph, vessel: str) -> str:
"""查找分离器设备"""
debug_print(f"查找容器 '{vessel}' 对应的分离器设备...")
"""查找分离器设备,支持多种查找方式"""
debug_print(f"🔍 正在查找容器 '{vessel}' 的分离器设备...")
# 方法1查找连接到容器的分离器设备
debug_print(f"📋 方法1: 检查连接的分离器...")
separator_nodes = []
for node in G.nodes():
node_class = G.nodes[node].get('class', '').lower()
if 'separator' in node_class:
separator_nodes.append(node)
debug_print(f"📋 发现分离器设备: {node}")
# 检查是否连接到目标容器
if G.has_edge(node, vessel) or G.has_edge(vessel, node):
debug_print(f"✅ 找到连接的分离器: {node}")
return node
debug_print(f"📊 找到的分离器总数: {len(separator_nodes)}")
# 方法2根据命名规则查找
debug_print(f"📋 方法2: 使用命名规则...")
possible_names = [
f"{vessel}_controller",
f"{vessel}_separator",
vessel, # 容器本身可能就是分离器
"separator_1",
"virtual_separator"
"virtual_separator",
"liquid_handler_1", # 液体处理器也可能用于分离
"controller_1"
]
debug_print(f"🎯 尝试的分离器名称: {possible_names}")
for name in possible_names:
if name in G.nodes():
node_class = G.nodes[name].get('class', '').lower()
if 'separator' in node_class:
if 'separator' in node_class or 'controller' in node_class:
debug_print(f"✅ 通过命名规则找到分离器: {name}")
return name
# 方法3查找第一个分离器设备
for node in G.nodes():
node_class = G.nodes[node].get('class', '').lower()
if 'separator' in node_class:
debug_print(f"⚠️ 使用第一个分离器设备: {node}")
return node
debug_print(f"📋 方法3: 使用第一个可用分离器...")
if separator_nodes:
debug_print(f"⚠️ 使用第一个分离器设备: {separator_nodes[0]}")
return separator_nodes[0]
debug_print(f"⚠️ 未找到分离器设备")
debug_print(f" 未找到分离器设备")
return ""
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
"""查找连接到指定容器的搅拌器"""
debug_print(f"🔍 正在查找与容器 {vessel} 连接的搅拌器...")
stirrer_nodes = []
for node in G.nodes():
node_data = G.nodes[node]
node_class = node_data.get('class', '') or ''
if 'stirrer' in node_class.lower():
stirrer_nodes.append(node)
debug_print(f"📋 发现搅拌器: {node}")
debug_print(f"📊 找到的搅拌器总数: {len(stirrer_nodes)}")
# 检查哪个搅拌器与目标容器相连
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
debug_print(f"✅ 找到连接的搅拌器: {stirrer}")
return stirrer
# 如果没有连接的搅拌器,返回第一个可用的
if stirrer_nodes:
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个可用的: {stirrer_nodes[0]}")
return stirrer_nodes[0]
debug_print("❌ 未找到搅拌器")
return ""
def generate_separate_protocol(
@@ -185,7 +290,7 @@ def generate_separate_protocol(
**kwargs
) -> List[Dict[str, Any]]:
"""
生成分离操作的协议序列 - 修复
生成分离操作的协议序列 - 增强中文
支持XDL参数格式
- vessel: 分离容器(必需)
@@ -206,26 +311,31 @@ def generate_separate_protocol(
"""
debug_print("=" * 60)
debug_print("开始生成分离协议 - 修复")
debug_print(f"原始参数:")
debug_print(f" - vessel: '{vessel}'")
debug_print(f" - purpose: '{purpose}'")
debug_print(f" - product_phase: '{product_phase}'")
debug_print(f" - solvent: '{solvent}'")
debug_print(f" - volume: {volume} (类型: {type(volume)})")
debug_print(f" - repeats: {repeats}")
debug_print(f" - product_vessel: '{product_vessel}'")
debug_print(f" - waste_vessel: '{waste_vessel}'")
debug_print("🧪 开始生成分离协议 - 增强中文")
debug_print(f"📋 原始参数:")
debug_print(f" 🥼 容器: '{vessel}'")
debug_print(f" 🎯 分离目的: '{purpose}'")
debug_print(f" 📊 产物相: '{product_phase}'")
debug_print(f" 💧 溶剂: '{solvent}'")
debug_print(f" 📏 体积: {volume} (类型: {type(volume)})")
debug_print(f" 🔄 重复次数: {repeats}")
debug_print(f" 🎯 产物容器: '{product_vessel}'")
debug_print(f" 🗑️ 废液容器: '{waste_vessel}'")
debug_print(f" 📦 其他参数: {kwargs}")
debug_print("=" * 60)
action_sequence = []
# === 参数验证和标准化 ===
debug_print("步骤1: 参数验证和标准化...")
debug_print("🔍 步骤1: 参数验证和标准化...")
action_sequence.append(create_action_log(f"开始分离操作 - 容器: {vessel}", "🎬"))
action_sequence.append(create_action_log(f"分离目的: {purpose}", "🧪"))
action_sequence.append(create_action_log(f"产物相: {product_phase}", "📊"))
# 统一容器参数
final_vessel = vessel or separation_vessel
if not final_vessel:
debug_print("❌ 必须指定分离容器")
raise ValueError("必须指定分离容器 (vessel 或 separation_vessel)")
final_to_vessel = to_vessel or product_vessel
@@ -237,14 +347,18 @@ def generate_separate_protocol(
# 🔧 修复确保repeats至少为1
if repeats <= 0:
repeats = 1
debug_print(f"⚠️ repeats参数 <= 0自动设置为1")
debug_print(f"⚠️ 重复次数参数 <= 0自动设置为 1")
debug_print(f"标准化参数:")
debug_print(f" - 分离容器: '{final_vessel}'")
debug_print(f" - 产物容器: '{final_to_vessel}'")
debug_print(f" - 废液容器: '{final_waste_vessel}'")
debug_print(f" - 溶剂体积: {final_volume}mL")
debug_print(f" - 重复次数: {repeats}")
debug_print(f"🔧 标准化后的参数:")
debug_print(f" 🥼 分离容器: '{final_vessel}'")
debug_print(f" 🎯 产物容器: '{final_to_vessel}'")
debug_print(f" 🗑️ 废液容器: '{final_waste_vessel}'")
debug_print(f" 📏 溶剂体积: {final_volume}mL")
debug_print(f" 🔄 重复次数: {repeats}")
action_sequence.append(create_action_log(f"分离容器: {final_vessel}", "🧪"))
action_sequence.append(create_action_log(f"溶剂体积: {final_volume}mL", "📏"))
action_sequence.append(create_action_log(f"重复次数: {repeats}", "🔄"))
# 验证必需参数
if not purpose:
@@ -254,66 +368,147 @@ def generate_separate_protocol(
if purpose not in ["wash", "extract", "separate"]:
debug_print(f"⚠️ 未知的分离目的 '{purpose}',使用默认值 'separate'")
purpose = "separate"
action_sequence.append(create_action_log(f"未知目的,使用: {purpose}", "⚠️"))
if product_phase not in ["top", "bottom"]:
debug_print(f"⚠️ 未知的产物相 '{product_phase}',使用默认值 'top'")
product_phase = "top"
action_sequence.append(create_action_log(f"未知相别,使用: {product_phase}", "⚠️"))
debug_print("✅ 参数验证通过")
action_sequence.append(create_action_log("参数验证通过", ""))
# === 查找设备 ===
debug_print("步骤2: 查找设备...")
debug_print("🔍 步骤2: 查找设备...")
action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
# 查找分离器设备
separator_device = find_separator_device(G, final_vessel)
if not separator_device:
debug_print("⚠️ 未找到分离器设备,可能无法执行分离操作")
if separator_device:
action_sequence.append(create_action_log(f"找到分离器设备: {separator_device}", "🧪"))
else:
debug_print("⚠️ 未找到分离器设备,可能无法执行分离")
action_sequence.append(create_action_log("未找到分离器设备", "⚠️"))
# 查找搅拌器
stirrer_device = find_connected_stirrer(G, final_vessel)
if stirrer_device:
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_device}", "🌪️"))
else:
action_sequence.append(create_action_log("未找到搅拌器", "⚠️"))
# 查找溶剂容器(如果需要)
solvent_vessel = ""
if solvent and solvent.strip():
solvent_vessel = find_solvent_vessel(G, solvent)
if solvent_vessel:
action_sequence.append(create_action_log(f"找到溶剂容器: {solvent_vessel}", "💧"))
else:
action_sequence.append(create_action_log(f"未找到溶剂容器: {solvent}", "⚠️"))
debug_print(f"设备映射:")
debug_print(f" - 分离器设备: '{separator_device}'")
debug_print(f" - 溶剂容器: '{solvent_vessel}'")
debug_print(f"📊 设备配置:")
debug_print(f" 🧪 分离器设备: '{separator_device}'")
debug_print(f" 🌪️ 搅拌器设备: '{stirrer_device}'")
debug_print(f" 💧 溶剂容器: '{solvent_vessel}'")
# === 执行分离流程 ===
debug_print("步骤3: 执行分离流程...")
debug_print("🔍 步骤3: 执行分离流程...")
action_sequence.append(create_action_log("开始分离工作流程", "🎯"))
try:
for repeat_idx in range(repeats):
debug_print(f"3.{repeat_idx+1}: 第 {repeat_idx+1}/{repeats} 次分离")
cycle_num = repeat_idx + 1
debug_print(f"🔄 第{cycle_num}轮: 开始分离循环 {cycle_num}/{repeats}")
action_sequence.append(create_action_log(f"分离循环 {cycle_num}/{repeats} 开始", "🔄"))
# 步骤3.1: 添加溶剂(如果需要)
if solvent_vessel and final_volume > 0:
debug_print(f"3.{repeat_idx+1}.1: 添加溶剂 {solvent} ({final_volume}mL)")
debug_print(f"🔄 第{cycle_num}轮 步骤1: 添加溶剂 {solvent} ({final_volume}mL)")
action_sequence.append(create_action_log(f"向分离容器添加 {final_volume}mL {solvent}", "💧"))
# 使用pump protocol添加溶剂
pump_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=solvent_vessel,
to_vessel=final_vessel,
volume=final_volume,
amount="",
time=0.0,
viscous=False,
rinsing_solvent="",
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=2.5,
transfer_flowrate=0.5,
rate_spec="",
event="",
through="",
**kwargs
)
action_sequence.extend(pump_actions)
debug_print(f"✅ 溶剂添加完成,添加了 {len(pump_actions)} 个动作")
try:
# 使用pump protocol添加溶剂
pump_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=solvent_vessel,
to_vessel=final_vessel,
volume=final_volume,
amount="",
time=0.0,
viscous=False,
rinsing_solvent="",
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=2.5,
transfer_flowrate=0.5,
rate_spec="",
event="",
through="",
**kwargs
)
action_sequence.extend(pump_actions)
debug_print(f"✅ 溶剂添加完成,添加了 {len(pump_actions)} 个动作")
action_sequence.append(create_action_log(f"溶剂转移完成 ({len(pump_actions)} 个操作)", ""))
except Exception as e:
debug_print(f"❌ 溶剂添加失败: {str(e)}")
action_sequence.append(create_action_log(f"溶剂添加失败: {str(e)}", ""))
else:
debug_print(f"🔄 第{cycle_num}轮 步骤1: 无需添加溶剂")
action_sequence.append(create_action_log("无需添加溶剂", "⏭️"))
# 步骤3.2: 执行分离操作
# 步骤3.2: 启动搅拌(如果有搅拌器)
if stirrer_device and stir_time > 0:
debug_print(f"🔄 第{cycle_num}轮 步骤2: 开始搅拌 ({stir_speed}rpm持续 {stir_time}s)")
action_sequence.append(create_action_log(f"开始搅拌: {stir_speed}rpm持续 {stir_time}s", "🌪️"))
action_sequence.append({
"device_id": stirrer_device,
"action_name": "start_stir",
"action_kwargs": {
"vessel": final_vessel,
"stir_speed": stir_speed,
"purpose": f"分离混合 - {purpose}"
}
})
# 搅拌等待
stir_minutes = stir_time / 60
action_sequence.append(create_action_log(f"搅拌中,持续 {stir_minutes:.1f} 分钟", "⏱️"))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": stir_time}
})
# 停止搅拌
action_sequence.append(create_action_log("停止搅拌器", "🛑"))
action_sequence.append({
"device_id": stirrer_device,
"action_name": "stop_stir",
"action_kwargs": {"vessel": final_vessel}
})
else:
debug_print(f"🔄 第{cycle_num}轮 步骤2: 无需搅拌")
action_sequence.append(create_action_log("无需搅拌", "⏭️"))
# 步骤3.3: 静置分层
if settling_time > 0:
debug_print(f"🔄 第{cycle_num}轮 步骤3: 静置分层 ({settling_time}s)")
settling_minutes = settling_time / 60
action_sequence.append(create_action_log(f"静置分层 ({settling_minutes:.1f} 分钟)", "⚖️"))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": settling_time}
})
else:
debug_print(f"🔄 第{cycle_num}轮 步骤3: 未指定静置时间")
action_sequence.append(create_action_log("未指定静置时间", "⏭️"))
# 步骤3.4: 执行分离操作
if separator_device:
debug_print(f"3.{repeat_idx+1}.2: 执行分离操作")
debug_print(f"🔄 第{cycle_num}轮 步骤4: 执行分离操作")
action_sequence.append(create_action_log(f"执行分离: 收集{product_phase}", "🧪"))
# 调用分离器设备的separate方法
separate_action = {
@@ -330,31 +525,44 @@ def generate_separate_protocol(
"solvent_volume": final_volume,
"through": through,
"repeats": 1, # 每次调用只做一次分离
"stir_time": stir_time,
"stir_time": 0, # 已经在上面完成
"stir_speed": stir_speed,
"settling_time": settling_time
"settling_time": 0 # 已经在上面完成
}
}
action_sequence.append(separate_action)
debug_print(f"✅ 分离操作添加完成")
debug_print(f"✅ 分离操作添加")
action_sequence.append(create_action_log("分离操作完成", ""))
# 收集结果
if final_to_vessel:
action_sequence.append(create_action_log(f"产物 ({product_phase}相) 收集到: {final_to_vessel}", "📦"))
if final_waste_vessel:
action_sequence.append(create_action_log(f"废相收集到: {final_waste_vessel}", "🗑️"))
else:
debug_print(f"3.{repeat_idx+1}.2: 无分离器设备,跳过分离操作")
debug_print(f"🔄 第{cycle_num}轮 步骤4: 无分离器设备,跳过分离")
action_sequence.append(create_action_log("无分离器设备可用", ""))
# 添加等待时间模拟分离
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": stir_time + settling_time}
"action_kwargs": {"time": 10.0}
})
# 等待间隔(除了最后一次)
# 循环间等待(除了最后一次)
if repeat_idx < repeats - 1:
debug_print(f"🔄 第{cycle_num}轮: 等待下一次循环...")
action_sequence.append(create_action_log("等待下一次循环...", ""))
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
})
else:
action_sequence.append(create_action_log(f"分离循环 {cycle_num}/{repeats} 完成", "🌟"))
except Exception as e:
debug_print(f"⚠️ 分离流程执行失败: {str(e)}")
debug_print(f" 分离工作流程执行失败: {str(e)}")
action_sequence.append(create_action_log(f"分离工作流程失败: {str(e)}", ""))
# 添加错误日志
action_sequence.append({
"device_id": "system",
@@ -365,22 +573,31 @@ def generate_separate_protocol(
})
# === 最终结果 ===
total_time = (stir_time + settling_time + 15) * repeats # 估算总时间
debug_print("=" * 60)
debug_print(f" 分离协议生成完成")
debug_print(f"📊 总动作数: {len(action_sequence)}")
debug_print(f"📋 处理总结:")
debug_print(f" - 分离容器: {final_vessel}")
debug_print(f" - 分离目的: {purpose}")
debug_print(f" - 产物相: {product_phase}")
debug_print(f" - 重复次数: {repeats}")
debug_print(f"🎉 分离协议生成完成")
debug_print(f"📊 协议统计:")
debug_print(f" 📋 总动作数: {len(action_sequence)}")
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f} 分钟)")
debug_print(f" 🥼 分离容器: {final_vessel}")
debug_print(f" 🎯 分离目的: {purpose}")
debug_print(f" 📊 产物相: {product_phase}")
debug_print(f" 🔄 重复次数: {repeats}")
if solvent:
debug_print(f" - 溶剂: {solvent} ({final_volume}mL)")
debug_print(f" 💧 溶剂: {solvent} ({final_volume}mL)")
if final_to_vessel:
debug_print(f" - 产物容器: {final_to_vessel}")
debug_print(f" 🎯 产物容器: {final_to_vessel}")
if final_waste_vessel:
debug_print(f" - 废液容器: {final_waste_vessel}")
debug_print(f" 🗑️ 废液容器: {final_waste_vessel}")
debug_print("=" * 60)
# 添加完成日志
summary_msg = f"分离协议完成: {final_vessel} ({purpose}{repeats} 次循环)"
if solvent:
summary_msg += f",使用 {final_volume}mL {solvent}"
action_sequence.append(create_action_log(summary_msg, "🎉"))
return action_sequence
# === 便捷函数 ===
@@ -388,6 +605,7 @@ def generate_separate_protocol(
def separate_phases_only(G: nx.DiGraph, vessel: str, product_phase: str = "top",
product_vessel: str = "", waste_vessel: str = "") -> List[Dict[str, Any]]:
"""仅进行相分离(不添加溶剂)"""
debug_print(f"⚡ 快速相分离: {vessel} ({product_phase}相)")
return generate_separate_protocol(
G, vessel=vessel,
purpose="separate",
@@ -399,6 +617,7 @@ def separate_phases_only(G: nx.DiGraph, vessel: str, product_phase: str = "top",
def wash_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
product_phase: str = "top", repeats: int = 1) -> List[Dict[str, Any]]:
"""用溶剂洗涤"""
debug_print(f"🧽 用{solvent}洗涤: {vessel} ({repeats} 次)")
return generate_separate_protocol(
G, vessel=vessel,
purpose="wash",
@@ -411,6 +630,7 @@ def wash_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[st
def extract_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union[str, float],
product_phase: str = "bottom", repeats: int = 3) -> List[Dict[str, Any]]:
"""用溶剂萃取"""
debug_print(f"🧪 用{solvent}萃取: {vessel} ({repeats} 次)")
return generate_separate_protocol(
G, vessel=vessel,
purpose="extract",
@@ -423,6 +643,7 @@ def extract_with_solvent(G: nx.DiGraph, vessel: str, solvent: str, volume: Union
def separate_aqueous_organic(G: nx.DiGraph, vessel: str, organic_phase: str = "top",
product_vessel: str = "", waste_vessel: str = "") -> List[Dict[str, Any]]:
"""水-有机相分离"""
debug_print(f"💧 水-有机相分离: {vessel} (有机相: {organic_phase})")
return generate_separate_protocol(
G, vessel=vessel,
purpose="separate",
@@ -434,15 +655,16 @@ def separate_aqueous_organic(G: nx.DiGraph, vessel: str, organic_phase: str = "t
# 测试函数
def test_separate_protocol():
"""测试分离协议的各种参数解析"""
print("=== SEPARATE PROTOCOL 增强版测试 ===")
debug_print("=== 分离协议增强中文版测试 ===")
# 测试体积解析
volumes = ["200 mL", "?", 100.0, "1 L", "500 μL"]
debug_print("🧪 测试体积解析...")
volumes = ["200 mL", "?", 100.0, "1 L", "500 μL", "未知", "50毫升"]
for vol in volumes:
result = parse_volume_input(vol)
print(f"体积解析: {vol} {result}mL")
debug_print(f"📊 体积解析结果: {vol} -> {result}mL")
print("✅ 测试完成")
debug_print("✅ 测试完成")
if __name__ == "__main__":
test_separate_protocol()