diff --git a/unilabos/compile/dry_protocol.py b/unilabos/compile/dry_protocol.py index 34044eb..1f06069 100644 --- a/unilabos/compile/dry_protocol.py +++ b/unilabos/compile/dry_protocol.py @@ -67,37 +67,47 @@ def generate_dry_protocol( # 默认参数 dry_temp = 60.0 # 默认干燥温度 60°C dry_time = 3600.0 # 默认干燥时间 1小时(3600秒) + simulation_time = 60.0 # 模拟时间 1分钟 - print(f"DRY: 开始生成干燥协议") - print(f" - 化合物: {compound}") - print(f" - 容器: {vessel}") - print(f" - 干燥温度: {dry_temp}°C") - print(f" - 干燥时间: {dry_time/60:.0f} 分钟") + print(f"🌡️ DRY: 开始生成干燥协议 ✨") + print(f" 🧪 化合物: {compound}") + print(f" 🥽 容器: {vessel}") + print(f" 🔥 干燥温度: {dry_temp}°C") + print(f" ⏰ 干燥时间: {dry_time/60:.0f} 分钟") # 1. 验证目标容器存在 + print(f"\n📋 步骤1: 验证目标容器 '{vessel}' 是否存在...") if vessel not in G.nodes(): - print(f"DRY: 警告 - 容器 '{vessel}' 不存在于系统中,跳过干燥") + print(f"⚠️ DRY: 警告 - 容器 '{vessel}' 不存在于系统中,跳过干燥 😢") return action_sequence + print(f"✅ 容器 '{vessel}' 验证通过!") # 2. 查找相连的加热器 + print(f"\n🔍 步骤2: 查找与容器相连的加热器...") heater_id = find_connected_heater(G, vessel) if heater_id is None: - print(f"DRY: 警告 - 未找到与容器 '{vessel}' 相连的加热器,跳过干燥") + print(f"😭 DRY: 警告 - 未找到与容器 '{vessel}' 相连的加热器,跳过干燥") + print(f"🎭 添加模拟干燥动作...") # 添加一个等待动作,表示干燥过程(模拟) action_sequence.append({ "action_name": "wait", "action_kwargs": { - "time": 60.0, # 等待1分钟 + "time": 10.0, # 模拟等待时间 "description": f"模拟干燥 {compound} (无加热器可用)" } }) + print(f"📄 DRY: 协议生成完成,共 {len(action_sequence)} 个动作 🎯") return action_sequence + print(f"🎉 找到加热器: {heater_id}!") + # 3. 启动加热器进行干燥 - print(f"DRY: 启动加热器 {heater_id} 进行干燥") + print(f"\n🚀 步骤3: 开始执行干燥流程...") + print(f"🔥 启动加热器 {heater_id} 进行干燥") # 3.1 启动加热 + print(f" ⚡ 动作1: 启动加热到 {dry_temp}°C...") action_sequence.append({ "device_id": heater_id, "action_name": "heat_chill_start", @@ -107,29 +117,35 @@ def generate_dry_protocol( "purpose": f"干燥 {compound}" } }) + print(f" ✅ 加热器启动命令已添加 🔥") # 3.2 等待温度稳定 + print(f" ⏳ 动作2: 等待温度稳定...") action_sequence.append({ "action_name": "wait", "action_kwargs": { - "time": 60.0, + "time": 10.0, "description": f"等待温度稳定到 {dry_temp}°C" } }) + print(f" ✅ 温度稳定等待命令已添加 🌡️") # 3.3 保持干燥温度 + print(f" 🔄 动作3: 保持干燥温度 {simulation_time/60:.0f} 分钟...") action_sequence.append({ "device_id": heater_id, "action_name": "heat_chill", "action_kwargs": { "vessel": vessel, "temp": dry_temp, - "time": dry_time, + "time": simulation_time, "purpose": f"干燥 {compound},保持温度 {dry_temp}°C" } }) + print(f" ✅ 温度保持命令已添加 🌡️⏰") # 3.4 停止加热 + print(f" ⏹️ 动作4: 停止加热...") action_sequence.append({ "device_id": heater_id, "action_name": "heat_chill_stop", @@ -138,18 +154,22 @@ def generate_dry_protocol( "purpose": f"干燥完成,停止加热" } }) + print(f" ✅ 停止加热命令已添加 🛑") # 3.5 等待冷却 + print(f" ❄️ 动作5: 等待冷却...") action_sequence.append({ "action_name": "wait", "action_kwargs": { - "time": 300.0, # 等待5分钟冷却 + "time": 10.0, # 等待10秒冷却 "description": f"等待 {compound} 冷却" } }) + print(f" ✅ 冷却等待命令已添加 🧊") - print(f"DRY: 协议生成完成,共 {len(action_sequence)} 个动作") - print(f"DRY: 预计总时间: {(dry_time + 360)/60:.0f} 分钟") + print(f"\n🎊 DRY: 协议生成完成,共 {len(action_sequence)} 个动作 🎯") + print(f"⏱️ DRY: 预计总时间: {(dry_time + 360)/60:.0f} 分钟 ⌛") + print(f"🏁 所有动作序列准备就绪! ✨") return action_sequence diff --git a/unilabos/compile/evaporate_protocol.py b/unilabos/compile/evaporate_protocol.py index 4eedb3c..6a2d6f6 100644 --- a/unilabos/compile/evaporate_protocol.py +++ b/unilabos/compile/evaporate_protocol.py @@ -7,7 +7,7 @@ logger = logging.getLogger(__name__) def debug_print(message): """调试输出""" - print(f"[EVAPORATE] {message}", flush=True) + print(f"🧪 [EVAPORATE] {message}", flush=True) logger.info(f"[EVAPORATE] {message}") def parse_time_input(time_input: Union[str, float]) -> float: @@ -21,18 +21,20 @@ def parse_time_input(time_input: Union[str, float]) -> float: float: 时间(秒) """ if isinstance(time_input, (int, float)): + debug_print(f"⏱️ 时间输入为数字: {time_input}s ✨") return float(time_input) if not time_input or not str(time_input).strip(): + debug_print(f"⚠️ 时间输入为空,使用默认值: 180s (3分钟) 🕐") return 180.0 # 默认3分钟 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 = 180.0 # 默认3分钟 - debug_print(f"检测到未知时间,使用默认值: {default_time}s") + debug_print(f"❓ 检测到未知时间,使用默认值: {default_time}s (3分钟) 🤷‍♀️") return default_time # 移除空格并提取数字和单位 @@ -45,10 +47,10 @@ def parse_time_input(time_input: Union[str, float]) -> float: # 如果无法解析,尝试直接转换为数字(默认秒) try: value = float(time_str) - debug_print(f"时间解析: {time_str} → {value}s(无单位,默认秒)") + debug_print(f"✅ 时间解析成功: {time_str} → {value}s(无单位,默认秒)⏰") return value except ValueError: - debug_print(f"⚠️ 无法解析时间: '{time_str}',使用默认值180s") + debug_print(f"❌ 无法解析时间: '{time_str}',使用默认值180s (3分钟) 😅") return 180.0 value = float(match.group(1)) @@ -57,14 +59,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}s ⏰") elif unit in ['h', 'hr', 'hour']: time_sec = value * 3600.0 # h -> s + debug_print(f"🕐 时间转换: {value} 小时 → {time_sec}s ({time_sec/60:.1f}分钟) ⏰") elif unit in ['d', 'day']: time_sec = value * 86400.0 # d -> s + debug_print(f"🕐 时间转换: {value} 天 → {time_sec}s ({time_sec/3600:.1f}小时) ⏰") else: # s, sec, second 或默认 time_sec = value # 已经是s + debug_print(f"🕐 时间转换: {value}s → {time_sec}s (已是秒) ⏰") - debug_print(f"时间转换: {value}{unit} → {time_sec}s") return time_sec def find_rotavap_device(G: nx.DiGraph, vessel: str = None) -> Optional[str]: @@ -78,28 +83,30 @@ def find_rotavap_device(G: nx.DiGraph, vessel: str = None) -> Optional[str]: Returns: str: 找到的旋转蒸发仪设备ID,如果没找到返回None """ - debug_print("查找旋转蒸发仪设备...") + debug_print("🔍 开始查找旋转蒸发仪设备... 🌪️") # 如果指定了vessel,先检查是否存在且是旋转蒸发仪 if vessel: + debug_print(f"🎯 检查指定设备: {vessel} 🔧") if vessel in G.nodes(): node_data = G.nodes[vessel] node_class = node_data.get('class', '') node_type = node_data.get('type', '') - debug_print(f"检查指定设备 {vessel}: class={node_class}, type={node_type}") + debug_print(f"📋 设备信息 {vessel}: class={node_class}, type={node_type}") # 检查是否为旋转蒸发仪 if any(keyword in str(node_class).lower() for keyword in ['rotavap', 'rotary', 'evaporat']): - debug_print(f"✓ 找到指定的旋转蒸发仪: {vessel}") + debug_print(f"🎉 找到指定的旋转蒸发仪: {vessel} ✨") return vessel elif node_type == 'device': - debug_print(f"✓ 指定设备存在,尝试直接使用: {vessel}") + debug_print(f"✅ 指定设备存在,尝试直接使用: {vessel} 🔧") return vessel else: - debug_print(f"✗ 指定的设备 {vessel} 不存在") + debug_print(f"❌ 指定的设备 {vessel} 不存在 😞") # 在所有设备中查找旋转蒸发仪 + debug_print("🔎 在所有设备中搜索旋转蒸发仪... 🕵️‍♀️") rotavap_candidates = [] for node_id, node_data in G.nodes(data=True): @@ -113,17 +120,17 @@ def find_rotavap_device(G: nx.DiGraph, vessel: str = None) -> Optional[str]: # 检查设备类型 if any(keyword in str(node_class).lower() for keyword in ['rotavap', 'rotary', 'evaporat']): rotavap_candidates.append(node_id) - debug_print(f"✓ 找到旋转蒸发仪候选: {node_id} (class: {node_class})") + debug_print(f"🌟 找到旋转蒸发仪候选: {node_id} (class: {node_class}) 🌪️") elif any(keyword in str(node_id).lower() for keyword in ['rotavap', 'rotary', 'evaporat']): rotavap_candidates.append(node_id) - debug_print(f"✓ 找到旋转蒸发仪候选 (按名称): {node_id}") + debug_print(f"🌟 找到旋转蒸发仪候选 (按名称): {node_id} 🌪️") if rotavap_candidates: selected = rotavap_candidates[0] # 选择第一个找到的 - debug_print(f"✓ 选择旋转蒸发仪: {selected}") + debug_print(f"🎯 选择旋转蒸发仪: {selected} 🏆") return selected - debug_print("✗ 未找到旋转蒸发仪设备") + debug_print("😭 未找到旋转蒸发仪设备 💔") return None def find_connected_vessel(G: nx.DiGraph, rotavap_device: str) -> Optional[str]: @@ -137,31 +144,33 @@ def find_connected_vessel(G: nx.DiGraph, rotavap_device: str) -> Optional[str]: Returns: str: 连接的容器ID,如果没找到返回None """ - debug_print(f"查找与 {rotavap_device} 连接的容器...") + debug_print(f"🔗 查找与 {rotavap_device} 连接的容器... 🥽") # 查看旋转蒸发仪的子设备 rotavap_data = G.nodes[rotavap_device] children = rotavap_data.get('children', []) + debug_print(f"👶 检查子设备: {children}") for child_id in children: if child_id in G.nodes(): child_data = G.nodes[child_id] child_type = child_data.get('type', '') if child_type == 'container': - debug_print(f"✓ 找到连接的容器: {child_id}") + debug_print(f"🎉 找到连接的容器: {child_id} 🥽✨") return child_id # 查看邻接的容器 + debug_print("🤝 检查邻接设备...") for neighbor in G.neighbors(rotavap_device): neighbor_data = G.nodes[neighbor] neighbor_type = neighbor_data.get('type', '') if neighbor_type == 'container': - debug_print(f"✓ 找到邻接的容器: {neighbor}") + debug_print(f"🎉 找到邻接的容器: {neighbor} 🥽✨") return neighbor - debug_print("✗ 未找到连接的容器") + debug_print("😞 未找到连接的容器 💔") return None def generate_evaporate_protocol( @@ -191,110 +200,136 @@ def generate_evaporate_protocol( List[Dict[str, Any]]: 动作序列 """ - debug_print("=" * 50) - debug_print("开始生成蒸发协议(支持单位)") - debug_print(f"输入参数:") - debug_print(f" - vessel: {vessel}") - debug_print(f" - pressure: {pressure} bar") - debug_print(f" - temp: {temp}°C") - debug_print(f" - time: {time} (类型: {type(time)})") - debug_print(f" - stir_speed: {stir_speed} RPM") - debug_print(f" - solvent: '{solvent}'") - debug_print("=" * 50) + debug_print("🌟" * 20) + debug_print("🌪️ 开始生成蒸发协议(支持单位)✨") + debug_print(f"📝 输入参数:") + debug_print(f" 🥽 vessel: {vessel}") + debug_print(f" 💨 pressure: {pressure} bar") + debug_print(f" 🌡️ temp: {temp}°C") + debug_print(f" ⏰ time: {time} (类型: {type(time)})") + debug_print(f" 🌪️ stir_speed: {stir_speed} RPM") + debug_print(f" 🧪 solvent: '{solvent}'") + debug_print("🌟" * 20) # === 步骤1: 查找旋转蒸发仪设备 === - debug_print("步骤1: 查找旋转蒸发仪设备...") + debug_print("📍 步骤1: 查找旋转蒸发仪设备... 🔍") # 验证vessel参数 if not vessel: + debug_print("❌ vessel 参数不能为空! 😱") raise ValueError("vessel 参数不能为空") # 查找旋转蒸发仪设备 rotavap_device = find_rotavap_device(G, vessel) if not rotavap_device: + debug_print("💥 未找到旋转蒸发仪设备! 😭") raise ValueError(f"未找到旋转蒸发仪设备。请检查组态图中是否包含 class 包含 'rotavap'、'rotary' 或 'evaporat' 的设备") + debug_print(f"🎉 成功找到旋转蒸发仪: {rotavap_device} ✨") + # === 步骤2: 确定目标容器 === - debug_print("步骤2: 确定目标容器...") + debug_print("📍 步骤2: 确定目标容器... 🥽") target_vessel = vessel # 如果vessel就是旋转蒸发仪设备,查找连接的容器 if vessel == rotavap_device: + debug_print("🔄 vessel就是旋转蒸发仪,查找连接的容器...") connected_vessel = find_connected_vessel(G, rotavap_device) if connected_vessel: target_vessel = connected_vessel - debug_print(f"使用连接的容器: {target_vessel}") + debug_print(f"✅ 使用连接的容器: {target_vessel} 🥽✨") else: - debug_print(f"未找到连接的容器,使用设备本身: {rotavap_device}") + debug_print(f"⚠️ 未找到连接的容器,使用设备本身: {rotavap_device} 🔧") target_vessel = rotavap_device elif vessel in G.nodes() and G.nodes[vessel].get('type') == 'container': - debug_print(f"使用指定的容器: {vessel}") + debug_print(f"✅ 使用指定的容器: {vessel} 🥽✨") target_vessel = vessel else: - debug_print(f"容器 '{vessel}' 不存在或类型不正确,使用旋转蒸发仪设备: {rotavap_device}") + debug_print(f"⚠️ 容器 '{vessel}' 不存在或类型不正确,使用旋转蒸发仪设备: {rotavap_device} 🔧") target_vessel = rotavap_device # === 🔧 新增:步骤3:单位解析处理 === - debug_print("步骤3: 单位解析处理...") + debug_print("📍 步骤3: 单位解析处理... ⚡") # 解析时间 final_time = parse_time_input(time) - debug_print(f"时间解析: {time} → {final_time}s ({final_time/60:.1f}分钟)") + debug_print(f"🎯 时间解析完成: {time} → {final_time}s ({final_time/60:.1f}分钟) ⏰✨") # === 步骤4: 参数验证和修正 === - debug_print("步骤4: 参数验证和修正...") + debug_print("📍 步骤4: 参数验证和修正... 🔧") # 修正参数范围 if pressure <= 0 or pressure > 1.0: - debug_print(f"真空度 {pressure} bar 超出范围,修正为 0.1 bar") + debug_print(f"⚠️ 真空度 {pressure} bar 超出范围,修正为 0.1 bar 💨") pressure = 0.1 + else: + debug_print(f"✅ 真空度 {pressure} bar 在正常范围内 💨") if temp < 10.0 or temp > 200.0: - debug_print(f"温度 {temp}°C 超出范围,修正为 60°C") + debug_print(f"⚠️ 温度 {temp}°C 超出范围,修正为 60°C 🌡️") temp = 60.0 + else: + debug_print(f"✅ 温度 {temp}°C 在正常范围内 🌡️") if final_time <= 0: - debug_print(f"时间 {final_time}s 无效,修正为 180s") + debug_print(f"⚠️ 时间 {final_time}s 无效,修正为 180s (3分钟) ⏰") final_time = 180.0 + else: + debug_print(f"✅ 时间 {final_time}s ({final_time/60:.1f}分钟) 有效 ⏰") if stir_speed < 10.0 or stir_speed > 300.0: - debug_print(f"旋转速度 {stir_speed} RPM 超出范围,修正为 100 RPM") + debug_print(f"⚠️ 旋转速度 {stir_speed} RPM 超出范围,修正为 100 RPM 🌪️") stir_speed = 100.0 + else: + debug_print(f"✅ 旋转速度 {stir_speed} RPM 在正常范围内 🌪️") # 根据溶剂优化参数 if solvent: - debug_print(f"根据溶剂 '{solvent}' 优化参数...") + debug_print(f"🧪 根据溶剂 '{solvent}' 优化参数... 🔬") solvent_lower = solvent.lower() if any(s in solvent_lower for s in ['water', 'aqueous', 'h2o']): temp = max(temp, 80.0) pressure = max(pressure, 0.2) - debug_print("水系溶剂:提高温度和真空度") + debug_print("💧 水系溶剂:提高温度和真空度 🌡️💨") elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']): temp = min(temp, 50.0) pressure = min(pressure, 0.05) - debug_print("易挥发溶剂:降低温度和真空度") + debug_print("🍺 易挥发溶剂:降低温度和真空度 🌡️💨") elif any(s in solvent_lower for s in ['dmso', 'dmi', 'toluene']): temp = max(temp, 100.0) pressure = min(pressure, 0.01) - debug_print("高沸点溶剂:提高温度,降低真空度") + debug_print("🔥 高沸点溶剂:提高温度,降低真空度 🌡️💨") + else: + debug_print("🧪 通用溶剂,使用标准参数 ✨") + else: + debug_print("🤷‍♀️ 未指定溶剂,使用默认参数 ✨") - debug_print(f"最终参数: pressure={pressure}, temp={temp}, time={final_time}, stir_speed={stir_speed}") + debug_print(f"🎯 最终参数: pressure={pressure} bar 💨, temp={temp}°C 🌡️, time={final_time}s ⏰, stir_speed={stir_speed} RPM 🌪️") # === 步骤5: 生成动作序列 === - debug_print("步骤5: 生成动作序列...") + debug_print("📍 步骤5: 生成动作序列... 🎬") action_sequence = [] - # 等待稳定 + # 1. 等待稳定 + debug_print(" 🔄 动作1: 添加初始等待稳定... ⏳") action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": 10} }) + debug_print(" ✅ 初始等待动作已添加 ⏳✨") + + # 2. 执行蒸发 + debug_print(f" 🌪️ 动作2: 执行蒸发操作...") + debug_print(f" 🔧 设备: {rotavap_device}") + debug_print(f" 🥽 容器: {target_vessel}") + debug_print(f" 💨 真空度: {pressure} bar") + debug_print(f" 🌡️ 温度: {temp}°C") + debug_print(f" ⏰ 时间: {final_time}s ({final_time/60:.1f}分钟)") + debug_print(f" 🌪️ 旋转速度: {stir_speed} RPM") - # 执行蒸发 - debug_print(f"执行蒸发: 设备={rotavap_device}, 容器={target_vessel}") evaporate_action = { "device_id": rotavap_device, "action_name": "evaporate", @@ -308,97 +343,24 @@ def generate_evaporate_protocol( } } action_sequence.append(evaporate_action) + debug_print(" ✅ 蒸发动作已添加 🌪️✨") - # 蒸发后等待 + # 3. 蒸发后等待 + debug_print(" 🔄 动作3: 添加蒸发后等待... ⏳") action_sequence.append({ "action_name": "wait", - "action_kwargs": {"time": 30} + "action_kwargs": {"time": 10} }) + debug_print(" ✅ 蒸发后等待动作已添加 ⏳✨") # === 总结 === - debug_print("=" * 50) - debug_print(f"蒸发协议生成完成") - debug_print(f"总动作数: {len(action_sequence)}") - debug_print(f"旋转蒸发仪: {rotavap_device}") - debug_print(f"目标容器: {target_vessel}") - debug_print(f"蒸发参数: {pressure} bar, {temp}°C, {final_time}s, {stir_speed} RPM") - debug_print("=" * 50) + debug_print("🎊" * 20) + debug_print(f"🎉 蒸发协议生成完成! ✨") + debug_print(f"📊 总动作数: {len(action_sequence)} 个 📝") + debug_print(f"🌪️ 旋转蒸发仪: {rotavap_device} 🔧") + debug_print(f"🥽 目标容器: {target_vessel} 🧪") + debug_print(f"⚙️ 蒸发参数: {pressure} bar 💨, {temp}°C 🌡️, {final_time}s ⏰, {stir_speed} RPM 🌪️") + debug_print(f"⏱️ 预计总时间: {(final_time + 20)/60:.1f} 分钟 ⌛") + debug_print("🎊" * 20) return action_sequence - -# === 便捷函数 === - -def generate_quick_evaporate_protocol( - G: nx.DiGraph, - vessel: str, - **kwargs -) -> List[Dict[str, Any]]: - """快速蒸发:低温短时间""" - return generate_evaporate_protocol( - G, vessel, - pressure=0.2, - temp=40.0, - time="15 min", # 🔧 使用带单位的时间 - stir_speed=80.0, - **kwargs - ) - -def generate_gentle_evaporate_protocol( - G: nx.DiGraph, - vessel: str, - **kwargs -) -> List[Dict[str, Any]]: - """温和蒸发:中等条件""" - return generate_evaporate_protocol( - G, vessel, - pressure=0.1, - temp=50.0, - time="45 min", # 🔧 使用带单位的时间 - stir_speed=60.0, - **kwargs - ) - -def generate_high_vacuum_evaporate_protocol( - G: nx.DiGraph, - vessel: str, - **kwargs -) -> List[Dict[str, Any]]: - """高真空蒸发:低温高真空""" - return generate_evaporate_protocol( - G, vessel, - pressure=0.01, - temp=35.0, - time="1 h", # 🔧 使用带单位的时间 - stir_speed=120.0, - **kwargs - ) - -def generate_standard_evaporate_protocol( - G: nx.DiGraph, - vessel: str, - **kwargs -) -> List[Dict[str, Any]]: - """标准蒸发:常用参数""" - return generate_evaporate_protocol( - G, vessel, - pressure=0.1, - temp=60.0, - time="3 min", # 🔧 使用带单位的时间 - stir_speed=100.0, - **kwargs - ) - -# 测试函数 -def test_time_parsing(): - """测试时间解析功能""" - print("=== EVAPORATE 时间解析测试 ===") - - test_times = ["3 min", "180", "0.5 h", "2 hours", "?", "unknown", "1.5", "30 s"] - for time_str in test_times: - result = parse_time_input(time_str) - print(f"时间解析: '{time_str}' → {result}s ({result/60:.1f}分钟)") - - print("✅ 测试完成") - -if __name__ == "__main__": - test_time_parsing() diff --git a/unilabos/compile/filter_protocol.py b/unilabos/compile/filter_protocol.py index 6629684..d974a41 100644 --- a/unilabos/compile/filter_protocol.py +++ b/unilabos/compile/filter_protocol.py @@ -7,12 +7,12 @@ logger = logging.getLogger(__name__) def debug_print(message): """调试输出""" - print(f"[FILTER] {message}", flush=True) + print(f"🧪 [FILTER] {message}", flush=True) logger.info(f"[FILTER] {message}") def find_filter_device(G: nx.DiGraph) -> str: """查找过滤器设备""" - debug_print("查找过滤器设备...") + debug_print("🔍 查找过滤器设备... 🌊") # 查找过滤器设备 for node in G.nodes(): @@ -20,27 +20,33 @@ def find_filter_device(G: nx.DiGraph) -> str: node_class = node_data.get('class', '') or '' if 'filter' in node_class.lower() or 'filter' in node.lower(): - debug_print(f"找到过滤器设备: {node}") + debug_print(f"🎉 找到过滤器设备: {node} ✨") return node # 如果没找到,寻找可能的过滤器名称 + debug_print("🔎 在预定义名称中搜索过滤器... 📋") possible_names = ["filter", "filter_1", "virtual_filter", "filtration_unit"] for name in possible_names: if name in G.nodes(): - debug_print(f"找到过滤器设备: {name}") + debug_print(f"🎉 找到过滤器设备: {name} ✨") return name + debug_print("😭 未找到过滤器设备 💔") raise ValueError("未找到过滤器设备") def validate_vessel(G: nx.DiGraph, vessel: str, vessel_type: str = "容器") -> None: """验证容器是否存在""" + debug_print(f"🔍 验证{vessel_type}: '{vessel}' 🧪") + if not vessel: + debug_print(f"❌ {vessel_type}不能为空! 😱") raise ValueError(f"{vessel_type}不能为空") if vessel not in G.nodes(): + debug_print(f"❌ {vessel_type} '{vessel}' 不存在于系统中! 😞") raise ValueError(f"{vessel_type} '{vessel}' 不存在于系统中") - debug_print(f"✅ {vessel_type} '{vessel}' 验证通过") + debug_print(f"✅ {vessel_type} '{vessel}' 验证通过 🎯") def generate_filter_protocol( G: nx.DiGraph, @@ -61,47 +67,53 @@ def generate_filter_protocol( List[Dict[str, Any]]: 过滤操作的动作序列 """ - debug_print("=" * 60) - debug_print("开始生成过滤协议") - debug_print(f"输入参数:") - debug_print(f" - vessel: {vessel}") - debug_print(f" - filtrate_vessel: {filtrate_vessel}") - debug_print(f" - 其他参数: {kwargs}") - debug_print("=" * 60) + debug_print("🌊" * 20) + debug_print("🚀 开始生成过滤协议 ✨") + debug_print(f"📝 输入参数:") + debug_print(f" 🥽 vessel: {vessel}") + debug_print(f" 🧪 filtrate_vessel: {filtrate_vessel}") + debug_print(f" ⚙️ 其他参数: {kwargs}") + debug_print("🌊" * 20) action_sequence = [] # === 参数验证 === - debug_print("步骤1: 参数验证...") + debug_print("📍 步骤1: 参数验证... 🔧") # 验证必需参数 + debug_print(" 🔍 验证必需参数...") validate_vessel(G, vessel, "过滤容器") + debug_print(" ✅ 必需参数验证完成 🎯") # 验证可选参数 + debug_print(" 🔍 验证可选参数...") if filtrate_vessel: validate_vessel(G, filtrate_vessel, "滤液容器") - debug_print("模式: 过滤并收集滤液") + debug_print(" 🌊 模式: 过滤并收集滤液 💧") else: - debug_print("模式: 过滤并收集固体") + debug_print(" 🧱 模式: 过滤并收集固体 🔬") + debug_print(" ✅ 可选参数验证完成 🎯") # === 查找设备 === - debug_print("步骤2: 查找设备...") + debug_print("📍 步骤2: 查找设备... 🔍") try: + debug_print(" 🔎 搜索过滤器设备...") filter_device = find_filter_device(G) - debug_print(f"使用过滤器设备: {filter_device}") + debug_print(f" 🎉 使用过滤器设备: {filter_device} 🌊✨") except Exception as e: - debug_print(f"❌ 设备查找失败: {str(e)}") + debug_print(f" ❌ 设备查找失败: {str(e)} 😭") raise ValueError(f"设备查找失败: {str(e)}") # === 转移到过滤器(如果需要)=== - debug_print("步骤3: 转移到过滤器...") + debug_print("📍 步骤3: 转移到过滤器... 🚚") if vessel != filter_device: - debug_print(f"需要转移: {vessel} → {filter_device}") + debug_print(f" 🚛 需要转移: {vessel} → {filter_device} 📦") try: + debug_print(" 🔄 开始执行转移操作...") # 使用pump protocol转移液体到过滤器 transfer_actions = generate_pump_protocol_with_rinsing( G=G, @@ -121,20 +133,21 @@ def generate_filter_protocol( if transfer_actions: action_sequence.extend(transfer_actions) - debug_print(f"✅ 添加了 {len(transfer_actions)} 个转移动作") + debug_print(f" ✅ 添加了 {len(transfer_actions)} 个转移动作 🚚✨") else: - debug_print("⚠️ 转移协议返回空序列") + debug_print(" ⚠️ 转移协议返回空序列 🤔") except Exception as e: - debug_print(f"❌ 转移失败: {str(e)}") - # 继续执行,可能是直接连接的过滤器 + debug_print(f" ❌ 转移失败: {str(e)} 😞") + debug_print(" 🔄 继续执行,可能是直接连接的过滤器 🤞") else: - debug_print("过滤容器就是过滤器,无需转移") + debug_print(" ✅ 过滤容器就是过滤器,无需转移 🎯") # === 执行过滤操作 === - debug_print("步骤4: 执行过滤操作...") + debug_print("📍 步骤4: 执行过滤操作... 🌊") # 构建过滤动作参数 + debug_print(" ⚙️ 构建过滤参数...") filter_kwargs = { "vessel": filter_device, # 过滤器设备 "filtrate_vessel": filtrate_vessel, # 滤液容器(可能为空) @@ -145,7 +158,8 @@ def generate_filter_protocol( "volume": kwargs.get("volume", 0.0) # 0表示过滤所有 } - debug_print(f"过滤参数: {filter_kwargs}") + debug_print(f" 📋 过滤参数: {filter_kwargs}") + debug_print(" 🌊 开始过滤操作...") # 过滤动作 filter_action = { @@ -154,20 +168,24 @@ def generate_filter_protocol( "action_kwargs": filter_kwargs } action_sequence.append(filter_action) + debug_print(" ✅ 过滤动作已添加 🌊✨") # 过滤后等待 + debug_print(" ⏳ 添加过滤后等待...") action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": 10.0} }) + debug_print(" ✅ 过滤后等待动作已添加 ⏰✨") # === 收集滤液(如果需要)=== - debug_print("步骤5: 收集滤液...") + debug_print("📍 步骤5: 收集滤液... 💧") if filtrate_vessel: - debug_print(f"收集滤液: {filter_device} → {filtrate_vessel}") + debug_print(f" 🧪 收集滤液: {filter_device} → {filtrate_vessel} 💧") try: + debug_print(" 🔄 开始执行收集操作...") # 使用pump protocol收集滤液 collect_actions = generate_pump_protocol_with_rinsing( G=G, @@ -187,29 +205,32 @@ def generate_filter_protocol( if collect_actions: action_sequence.extend(collect_actions) - debug_print(f"✅ 添加了 {len(collect_actions)} 个收集动作") + debug_print(f" ✅ 添加了 {len(collect_actions)} 个收集动作 🧪✨") else: - debug_print("⚠️ 收集协议返回空序列") + debug_print(" ⚠️ 收集协议返回空序列 🤔") except Exception as e: - debug_print(f"❌ 收集滤液失败: {str(e)}") - # 继续执行,可能滤液直接流入指定容器 + debug_print(f" ❌ 收集滤液失败: {str(e)} 😞") + debug_print(" 🔄 继续执行,可能滤液直接流入指定容器 🤞") else: - debug_print("未指定滤液容器,固体保留在过滤器中") + debug_print(" 🧱 未指定滤液容器,固体保留在过滤器中 🔬") # === 最终等待 === + debug_print("📍 步骤6: 最终等待... ⏰") action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": 5.0} }) + debug_print(" ✅ 最终等待动作已添加 ⏰✨") # === 总结 === - debug_print("=" * 60) - debug_print(f"过滤协议生成完成") - debug_print(f"总动作数: {len(action_sequence)}") - debug_print(f"过滤容器: {vessel}") - debug_print(f"过滤器设备: {filter_device}") - debug_print(f"滤液容器: {filtrate_vessel or '无(保留固体)'}") - debug_print("=" * 60) + debug_print("🎊" * 20) + debug_print(f"🎉 过滤协议生成完成! ✨") + debug_print(f"📊 总动作数: {len(action_sequence)} 个 📝") + debug_print(f"🥽 过滤容器: {vessel} 🧪") + debug_print(f"🌊 过滤器设备: {filter_device} 🔧") + debug_print(f"💧 滤液容器: {filtrate_vessel or '无(保留固体)'} 🧱") + debug_print(f"⏱️ 预计总时间: {(len(action_sequence) * 5):.0f} 秒 ⌛") + debug_print("🎊" * 20) return action_sequence diff --git a/unilabos/compile/heatchill_protocol.py b/unilabos/compile/heatchill_protocol.py index 297015e..f8bcc11 100644 --- a/unilabos/compile/heatchill_protocol.py +++ b/unilabos/compile/heatchill_protocol.py @@ -7,179 +7,129 @@ logger = logging.getLogger(__name__) def debug_print(message): """调试输出""" - print(f"[HEATCHILL] {message}", flush=True) + print(f"🌡️ [HEATCHILL] {message}", flush=True) logger.info(f"[HEATCHILL] {message}") -def parse_time_with_units(time_input: Union[str, float, int], default_unit: str = "s") -> float: +def parse_time_input(time_input: Union[str, float, int]) -> float: """ - 解析带单位的时间输入 + 解析时间输入(统一函数) Args: time_input: 时间输入(如 "30 min", "1 h", "300", "?", 60.0) - default_unit: 默认单位(默认为秒) Returns: float: 时间(秒) """ if not time_input: - return 0.0 + return 300.0 - # 处理数值输入 + # 🔢 处理数值输入 if isinstance(time_input, (int, float)): result = float(time_input) - debug_print(f"数值时间输入: {time_input} → {result}s(默认单位)") + debug_print(f"⏰ 数值时间: {time_input} → {result}s") return result - # 处理字符串输入 + # 📝 处理字符串输入 time_str = str(time_input).lower().strip() - debug_print(f"解析时间字符串: '{time_str}'") + debug_print(f"🔍 解析时间: '{time_str}'") - # 处理特殊值 - if time_str in ['?', 'unknown', 'tbd', 'to be determined']: - default_time = 300.0 # 5分钟默认值 - debug_print(f"检测到未知时间,使用默认值: {default_time}s") - return default_time + # ❓ 特殊值处理 + special_times = { + '?': 300.0, 'unknown': 300.0, 'tbd': 300.0, + 'overnight': 43200.0, 'several hours': 10800.0, + 'few hours': 7200.0, 'long time': 3600.0, 'short time': 300.0 + } - # 如果是纯数字,使用默认单位 + if time_str in special_times: + result = special_times[time_str] + debug_print(f"🎯 特殊时间: '{time_str}' → {result}s ({result/60:.1f}分钟)") + return result + + # 🔢 纯数字处理 try: - value = float(time_str) - if default_unit == "s": - result = value - elif default_unit in ["min", "minute"]: - result = value * 60.0 - elif default_unit in ["h", "hour"]: - result = value * 3600.0 - else: - result = value # 默认秒 - debug_print(f"纯数字输入: {time_str} → {result}s(单位: {default_unit})") + result = float(time_str) + debug_print(f"⏰ 纯数字: {time_str} → {result}s") return result except ValueError: pass - # 使用正则表达式匹配数字和单位 + # 📐 正则表达式解析 pattern = r'(\d+\.?\d*)\s*([a-z]*)' match = re.match(pattern, time_str) if not match: - debug_print(f"⚠️ 无法解析时间: '{time_str}',使用默认值: 60s") - return 60.0 + debug_print(f"⚠️ 无法解析时间: '{time_str}',使用默认值: 300s") + return 300.0 value = float(match.group(1)) - unit = match.group(2) or default_unit + unit = match.group(2) or 's' - # 单位转换映射 + # 📏 单位转换 unit_multipliers = { - # 秒 - 's': 1.0, - 'sec': 1.0, - 'second': 1.0, - 'seconds': 1.0, - - # 分钟 - 'm': 60.0, - 'min': 60.0, - 'mins': 60.0, - 'minute': 60.0, - 'minutes': 60.0, - - # 小时 - 'h': 3600.0, - 'hr': 3600.0, - 'hrs': 3600.0, - 'hour': 3600.0, - 'hours': 3600.0, - - # 天 - 'd': 86400.0, - 'day': 86400.0, - 'days': 86400.0, + 's': 1.0, 'sec': 1.0, 'second': 1.0, 'seconds': 1.0, + 'm': 60.0, 'min': 60.0, 'mins': 60.0, 'minute': 60.0, 'minutes': 60.0, + 'h': 3600.0, 'hr': 3600.0, 'hrs': 3600.0, 'hour': 3600.0, 'hours': 3600.0, + 'd': 86400.0, 'day': 86400.0, 'days': 86400.0 } multiplier = unit_multipliers.get(unit, 1.0) result = value * multiplier - debug_print(f"时间解析: '{time_str}' → {value} {unit} → {result}s") + debug_print(f"✅ 时间解析: '{time_str}' → {value} {unit} → {result}s ({result/60:.1f}分钟)") return result -def parse_temp_spec(temp_spec: str) -> float: - """解析温度规格为具体温度""" - if not temp_spec: - return 25.0 +def parse_temp_input(temp_input: Union[str, float], default_temp: float = 25.0) -> float: + """ + 解析温度输入(统一函数) - temp_spec = temp_spec.strip().lower() + Args: + temp_input: 温度输入 + default_temp: 默认温度 + + Returns: + float: 温度(°C) + """ + if not temp_input: + return default_temp - # 特殊温度规格 + # 🔢 数值输入 + if isinstance(temp_input, (int, float)): + result = float(temp_input) + debug_print(f"🌡️ 数值温度: {temp_input} → {result}°C") + return result + + # 📝 字符串输入 + temp_str = str(temp_input).lower().strip() + debug_print(f"🔍 解析温度: '{temp_str}'") + + # 🎯 特殊温度 special_temps = { - "room temperature": 25.0, # 室温 - "reflux": 78.0, # 默认回流温度 - "ice bath": 0.0, # 冰浴 - "boiling": 100.0, # 沸腾 - "hot": 60.0, # 热 - "warm": 40.0, # 温热 - "cold": 10.0, # 冷 + "room temperature": 25.0, "reflux": 78.0, "ice bath": 0.0, + "boiling": 100.0, "hot": 60.0, "warm": 40.0, "cold": 10.0 } - if temp_spec in special_temps: - return special_temps[temp_spec] + if temp_str in special_temps: + result = special_temps[temp_str] + debug_print(f"🎯 特殊温度: '{temp_str}' → {result}°C") + return result - # 解析带单位的温度(如 "256 °C") + # 📐 正则解析(如 "256 °C") temp_pattern = r'(\d+(?:\.\d+)?)\s*°?[cf]?' - match = re.search(temp_pattern, temp_spec) + match = re.search(temp_pattern, temp_str) if match: - return float(match.group(1)) + result = float(match.group(1)) + debug_print(f"✅ 温度解析: '{temp_str}' → {result}°C") + return result - return 25.0 - -def parse_time_spec(time_spec: str) -> float: - """解析时间规格为秒数""" - if not time_spec: - return 300.0 - - time_spec = time_spec.strip().lower() - - # 特殊时间规格 - special_times = { - "overnight": 43200.0, # 12小时 - "several hours": 10800.0, # 3小时 - "few hours": 7200.0, # 2小时 - "long time": 3600.0, # 1小时 - "short time": 300.0, # 5分钟 - } - - if time_spec in special_times: - return special_times[time_spec] - - # 解析带单位的时间(如 "2 h") - time_pattern = r'(\d+(?:\.\d+)?)\s*([a-zA-Z]+)' - match = re.search(time_pattern, time_spec) - - if match: - value = float(match.group(1)) - unit = match.group(2).lower() - - unit_multipliers = { - 's': 1.0, - 'sec': 1.0, - 'min': 60.0, - 'minute': 60.0, - 'minutes': 60.0, - 'h': 3600.0, - 'hr': 3600.0, - 'hour': 3600.0, - 'hours': 3600.0, - } - - multiplier = unit_multipliers.get(unit, 3600.0) - return value * multiplier - - return 300.0 + debug_print(f"⚠️ 无法解析温度: '{temp_str}',使用默认值: {default_temp}°C") + return default_temp def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str: """查找与指定容器相连的加热/冷却设备""" - debug_print(f"查找加热设备,目标容器: {vessel}") + debug_print(f"🔍 查找加热设备,目标容器: {vessel}") - # 查找所有加热/冷却设备节点 + # 🔧 查找所有加热设备 heatchill_nodes = [] for node in G.nodes(): node_data = G.nodes[node] @@ -187,28 +137,55 @@ def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str: if 'heatchill' in node_class.lower() or 'virtual_heatchill' in node_class: heatchill_nodes.append(node) - debug_print(f"找到加热设备: {node}") + debug_print(f"🎉 找到加热设备: {node}") - if vessel: - # 检查哪个加热设备与目标容器相连 + # 🔗 检查连接 + if vessel and heatchill_nodes: for heatchill in heatchill_nodes: if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill): - debug_print(f"加热设备 '{heatchill}' 与容器 '{vessel}' 相连") + debug_print(f"✅ 加热设备 '{heatchill}' 与容器 '{vessel}' 相连") return heatchill - # 如果没有指定容器或没有直接连接,返回第一个可用的加热设备 + # 🎯 使用第一个可用设备 if heatchill_nodes: - debug_print(f"使用第一个加热设备: {heatchill_nodes[0]}") - return heatchill_nodes[0] + selected = heatchill_nodes[0] + debug_print(f"🔧 使用第一个加热设备: {selected}") + return selected - debug_print("未找到加热设备,使用默认设备") + # 🆘 默认设备 + debug_print("⚠️ 未找到加热设备,使用默认设备") return "heatchill_1" +def validate_and_fix_params(temp: float, time: float, stir_speed: float) -> tuple: + """验证和修正参数""" + # 🌡️ 温度范围验证 + if temp < -50.0 or temp > 300.0: + debug_print(f"⚠️ 温度 {temp}°C 超出范围,修正为 25°C") + temp = 25.0 + else: + debug_print(f"✅ 温度 {temp}°C 在正常范围内") + + # ⏰ 时间验证 + if time < 0: + debug_print(f"⚠️ 时间 {time}s 无效,修正为 300s") + time = 300.0 + else: + debug_print(f"✅ 时间 {time}s ({time/60:.1f}分钟) 有效") + + # 🌪️ 搅拌速度验证 + if stir_speed < 0 or stir_speed > 1500.0: + debug_print(f"⚠️ 搅拌速度 {stir_speed} RPM 超出范围,修正为 300 RPM") + stir_speed = 300.0 + else: + debug_print(f"✅ 搅拌速度 {stir_speed} RPM 在正常范围内") + + return temp, time, stir_speed + def generate_heat_chill_protocol( G: nx.DiGraph, vessel: str, temp: float = 25.0, - time: Union[str, float] = "300", # 🔧 修改:支持字符串时间 + time: Union[str, float] = "300", temp_spec: str = "", time_spec: str = "", pressure: str = "", @@ -219,249 +196,111 @@ def generate_heat_chill_protocol( **kwargs ) -> List[Dict[str, Any]]: """ - 生成加热/冷却操作的协议序列 - 支持单位 + 生成加热/冷却操作的协议序列 """ - debug_print("=" * 50) - debug_print("开始生成加热冷却协议(支持单位)") - debug_print(f"输入参数:") - debug_print(f" - vessel: {vessel}") - debug_print(f" - temp: {temp}°C") - debug_print(f" - time: {time} (类型: {type(time)})") - debug_print(f" - temp_spec: {temp_spec}") - debug_print(f" - time_spec: {time_spec}") - debug_print(f" - pressure: {pressure}") - debug_print(f" - reflux_solvent: {reflux_solvent}") - debug_print(f" - stir: {stir}") - debug_print(f" - stir_speed: {stir_speed} RPM") - debug_print(f" - purpose: {purpose}") - debug_print(f" - 其他参数: {kwargs}") - debug_print("=" * 50) + debug_print("🌡️" * 20) + debug_print("🚀 开始生成加热冷却协议 ✨") + debug_print(f"📝 输入参数:") + debug_print(f" 🥽 vessel: {vessel}") + debug_print(f" 🌡️ temp: {temp}°C") + debug_print(f" ⏰ time: {time}") + debug_print(f" 🎯 temp_spec: {temp_spec}") + debug_print(f" ⏱️ time_spec: {time_spec}") + debug_print(f" 🌪️ stir: {stir} ({stir_speed} RPM)") + debug_print("🌡️" * 20) - action_sequence = [] - - # === 参数验证 === - debug_print("步骤1: 参数验证...") - - # 验证必需参数 + # 📋 参数验证 + debug_print("📍 步骤1: 参数验证... 🔧") 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("步骤2: 单位解析处理...") + debug_print("✅ 基础参数验证通过 🎯") - # 温度解析:优先使用 temp_spec,然后是 temp - final_temp = temp - if temp_spec: - final_temp = parse_temp_spec(temp_spec) - debug_print(f"温度解析: '{temp_spec}' → {final_temp}°C") + # 🔄 参数解析 + debug_print("📍 步骤2: 参数解析... ⚡") - # 时间解析:优先使用 time_spec,然后是 time - if time_spec: - final_time = parse_time_spec(time_spec) # 使用现有的time_spec解析 - debug_print(f"时间解析: '{time_spec}' → {final_time}s") - else: - final_time = parse_time_with_units(time, "s") - debug_print(f"时间解析: {time} → {final_time}s ({final_time/60:.1f}分钟)") + #温度解析:优先使用 temp_spec + final_temp = parse_temp_input(temp_spec, temp) if temp_spec else temp - # 参数范围验证 - if final_temp < -50.0 or final_temp > 300.0: - debug_print(f"温度 {final_temp}°C 超出范围,修正为 25°C") - final_temp = 25.0 + # 时间解析:优先使用 time_spec + final_time = parse_time_input(time_spec) if time_spec else parse_time_input(time) - if final_time < 0: - debug_print(f"时间 {final_time}s 无效,修正为 300s") - final_time = 300.0 + # 参数修正 + final_temp, final_time, stir_speed = validate_and_fix_params(final_temp, final_time, stir_speed) - if stir_speed < 0 or stir_speed > 1500.0: - debug_print(f"搅拌速度 {stir_speed} RPM 超出范围,修正为 300 RPM") - stir_speed = 300.0 - - debug_print(f"✅ 单位解析和参数验证通过") - - # === 查找加热设备 === - debug_print("步骤3: 查找加热设备...") + debug_print(f"🎯 最终参数: temp={final_temp}°C, time={final_time}s, stir_speed={stir_speed} RPM") + # 🔍 查找设备 + debug_print("📍 步骤3: 查找加热设备... 🔍") try: heatchill_id = find_connected_heatchill(G, vessel) - debug_print(f"设备配置: 加热设备 = {heatchill_id}") - + debug_print(f"🎉 使用加热设备: {heatchill_id} ✨") except Exception as e: - debug_print(f"❌ 设备查找失败: {str(e)}") + debug_print(f"❌ 设备查找失败: {str(e)} 😭") raise ValueError(f"无法找到加热设备: {str(e)}") - # === 执行加热操作 === - debug_print("步骤4: 执行加热操作...") + # 🚀 生成动作 + debug_print("📍 步骤4: 生成加热动作... 🔥") + # 🕐 模拟运行时间优化 + debug_print(" ⏱️ 检查模拟运行时间限制...") + original_time = final_time + simulation_time_limit = 100.0 # 模拟运行时间限制:100秒 + + if final_time > simulation_time_limit: + final_time = simulation_time_limit + debug_print(f" 🎮 模拟运行优化: {original_time}s → {final_time}s (限制为{simulation_time_limit}s) ⚡") + debug_print(f" 📊 时间缩短: {original_time/60:.1f}分钟 → {final_time/60:.1f}分钟 🚀") + else: + debug_print(f" ✅ 时间在限制内: {final_time}s ({final_time/60:.1f}分钟) 保持不变 🎯") + + action_sequence = [] heatchill_action = { "device_id": heatchill_id, "action_name": "heat_chill", "action_kwargs": { "vessel": vessel, - "temp": float(final_temp), # 🔧 确保是浮点数 - "time": float(final_time), # 🔧 确保是浮点数 - "stir": bool(stir), # 🔧 确保是布尔值 - "stir_speed": float(stir_speed), # 🔧 确保是浮点数 - "purpose": str(purpose or f"加热到 {final_temp}°C") # 🔧 确保是字符串 + "temp": float(final_temp), + "time": float(final_time), + "stir": bool(stir), + "stir_speed": float(stir_speed), + "purpose": str(purpose or f"加热到 {final_temp}°C") + (f" (模拟时间: {final_time}s)" if original_time != final_time else "") } } - action_sequence.append(heatchill_action) + debug_print("✅ 加热动作已添加 🔥✨") - # === 总结 === - debug_print("=" * 50) - debug_print(f"加热冷却协议生成完成(支持单位)") - debug_print(f"总动作数: {len(action_sequence)}") - debug_print(f"加热容器: {vessel}") - debug_print(f"目标温度: {final_temp}°C") - debug_print(f"加热时间: {final_time}s ({final_time/60:.1f}分钟)") - if pressure: - debug_print(f"压力参数: {pressure} (已接收,不做特殊处理)") - if reflux_solvent: - debug_print(f"回流溶剂: {reflux_solvent} (已接收,不做特殊处理)") - debug_print("=" * 50) + # 显示时间调整信息 + if original_time != final_time: + debug_print(f" 🎭 模拟优化说明: 原计划 {original_time/60:.1f}分钟,实际模拟 {final_time/60:.1f}分钟 ⚡") + + # 🎊 总结 + debug_print("🎊" * 20) + debug_print(f"🎉 加热冷却协议生成完成! ✨") + debug_print(f"📊 总动作数: {len(action_sequence)} 个") + debug_print(f"🥽 加热容器: {vessel}") + debug_print(f"🌡️ 目标温度: {final_temp}°C") + debug_print(f"⏰ 加热时间: {final_time}s ({final_time/60:.1f}分钟)") + debug_print("🎊" * 20) return action_sequence def generate_heat_chill_to_temp_protocol( - G: nx.DiGraph, - vessel: str, - temp: float = 25.0, - time: Union[str, float] = 300.0, # 🔧 也支持字符串 - temp_spec: str = "", - time_spec: str = "", - pressure: str = "", - reflux_solvent: str = "", - stir: bool = False, - stir_speed: float = 300.0, - purpose: str = "", - **kwargs # 🔧 接受额外参数,增强兼容性 + G: nx.DiGraph, + vessel: str, + temp: float = 25.0, + time: Union[str, float] = 100.0, + **kwargs ) -> List[Dict[str, Any]]: - """ - 生成加热/冷却操作的协议序列 - - Args: - G: 设备图 - vessel: 加热容器名称(必需) - temp: 目标温度 (°C) - time: 加热时间(支持字符串和数字) - temp_spec: 温度规格(如 'room temperature', 'reflux') - time_spec: 时间规格(如 'overnight', '2 h') - pressure: 压力规格(如 '1 mbar'),不做特殊处理 - reflux_solvent: 回流溶剂名称,不做特殊处理 - stir: 是否搅拌 - stir_speed: 搅拌速度 (RPM) - purpose: 操作目的 - **kwargs: 其他参数(兼容性) - - Returns: - List[Dict[str, Any]]: 加热操作的动作序列 - """ - - debug_print("=" * 50) - debug_print("开始生成加热冷却协议") - debug_print(f"输入参数:") - debug_print(f" - vessel: {vessel}") - debug_print(f" - temp: {temp}°C") - debug_print(f" - time: {time} (类型: {type(time)})") - debug_print(f" - temp_spec: {temp_spec}") - debug_print(f" - time_spec: {time_spec}") - debug_print(f" - pressure: {pressure}") - debug_print(f" - reflux_solvent: {reflux_solvent}") - debug_print(f" - stir: {stir}") - debug_print(f" - stir_speed: {stir_speed} RPM") - debug_print(f" - purpose: {purpose}") - debug_print(f" - 其他参数: {kwargs}") - debug_print("=" * 50) - - action_sequence = [] - - # === 参数验证 === - debug_print("步骤1: 参数验证...") - - # 验证必需参数 - if not vessel: - raise ValueError("vessel 参数不能为空") - - if vessel not in G.nodes(): - raise ValueError(f"容器 '{vessel}' 不存在于系统中") - - # 温度解析:优先使用 temp_spec,然后是 temp - final_temp = temp - if temp_spec: - final_temp = parse_temp_spec(temp_spec) - debug_print(f"温度解析: '{temp_spec}' → {final_temp}°C") - - # 🔧 修复:时间解析,支持字符串输入 - if time_spec: - final_time = parse_time_spec(time_spec) - debug_print(f"时间解析: '{time_spec}' → {final_time}s ({final_time / 60:.1f}分钟)") - else: - final_time = parse_time_with_units(time, "s") - debug_print(f"时间解析: {time} → {final_time}s ({final_time/60:.1f}分钟)") - - # 参数范围验证 - if final_temp < -50.0 or final_temp > 300.0: - debug_print(f"温度 {final_temp}°C 超出范围,修正为 25°C") - final_temp = 25.0 - - if final_time < 0: - debug_print(f"时间 {final_time}s 无效,修正为 300s") - final_time = 300.0 - - if stir_speed < 0 or stir_speed > 1500.0: - debug_print(f"搅拌速度 {stir_speed} RPM 超出范围,修正为 300 RPM") - stir_speed = 300.0 - - debug_print(f"✅ 参数验证通过") - - # === 查找加热设备 === - debug_print("步骤2: 查找加热设备...") - - try: - heatchill_id = find_connected_heatchill(G, vessel) - debug_print(f"设备配置: 加热设备 = {heatchill_id}") - - except Exception as e: - debug_print(f"❌ 设备查找失败: {str(e)}") - raise ValueError(f"无法找到加热设备: {str(e)}") - - # === 执行加热操作 === - debug_print("步骤3: 执行加热操作...") - - heatchill_action = { - "device_id": heatchill_id, - "action_name": "heat_chill", - "action_kwargs": { - "vessel": vessel, - "temp": float(final_temp), # 🔧 确保是浮点数 - "time": float(final_time), # 🔧 确保是浮点数 - "stir": bool(stir), # 🔧 确保是布尔值 - "stir_speed": float(stir_speed), # 🔧 确保是浮点数 - "purpose": str(purpose or f"加热到 {final_temp}°C") # 🔧 确保是字符串 - } - } - - action_sequence.append(heatchill_action) - - # === 总结 === - debug_print("=" * 50) - debug_print(f"加热冷却协议生成完成") - debug_print(f"总动作数: {len(action_sequence)}") - debug_print(f"加热容器: {vessel}") - debug_print(f"目标温度: {final_temp}°C") - debug_print(f"加热时间: {final_time}s ({final_time / 60:.1f}分钟)") - if pressure: - debug_print(f"压力参数: {pressure} (已接收,不做特殊处理)") - if reflux_solvent: - debug_print(f"回流溶剂: {reflux_solvent} (已接收,不做特殊处理)") - debug_print("=" * 50) - - return action_sequence - + """生成加热到指定温度的协议(简化版)""" + debug_print(f"🌡️ 生成加热到温度协议: {vessel} → {temp}°C") + return generate_heat_chill_protocol(G, vessel, temp, time, **kwargs) def generate_heat_chill_start_protocol( G: nx.DiGraph, @@ -472,33 +311,19 @@ def generate_heat_chill_start_protocol( ) -> List[Dict[str, Any]]: """生成开始加热操作的协议序列""" - debug_print("=" * 50) - debug_print("开始生成启动加热协议") - debug_print(f"输入参数:") - debug_print(f" - vessel: {vessel}") - debug_print(f" - temp: {temp}°C") - debug_print(f" - purpose: {purpose}") - debug_print("=" * 50) + debug_print("🔥 开始生成启动加热协议 ✨") + debug_print(f"🥽 vessel: {vessel}, 🌡️ temp: {temp}°C") - action_sequence = [] + # 基础验证 + if not vessel or vessel not in G.nodes(): + debug_print("❌ 容器验证失败!") + raise ValueError("vessel 参数无效") - # 验证参数 - if not vessel: - raise ValueError("vessel 参数不能为空") + # 查找设备 + heatchill_id = find_connected_heatchill(G, vessel) - if vessel not in G.nodes(): - raise ValueError(f"容器 '{vessel}' 不存在于系统中") - - # 查找加热设备 - try: - heatchill_id = find_connected_heatchill(G, vessel) - debug_print(f"设备配置: 加热设备 = {heatchill_id}") - except Exception as e: - debug_print(f"❌ 设备查找失败: {str(e)}") - raise ValueError(f"无法找到加热设备: {str(e)}") - - # 执行开始加热操作 - start_action = { + # 生成动作 + action_sequence = [{ "device_id": heatchill_id, "action_name": "heat_chill_start", "action_kwargs": { @@ -506,11 +331,9 @@ def generate_heat_chill_start_protocol( "temp": temp, "purpose": purpose or f"开始加热到 {temp}°C" } - } + }] - action_sequence.append(start_action) - - debug_print(f"启动加热协议生成完成,动作数: {len(action_sequence)}") + debug_print(f"✅ 启动加热协议生成完成 🎯") return action_sequence def generate_heat_chill_stop_protocol( @@ -520,48 +343,34 @@ def generate_heat_chill_stop_protocol( ) -> List[Dict[str, Any]]: """生成停止加热操作的协议序列""" - debug_print("=" * 50) - debug_print("开始生成停止加热协议") - debug_print(f"输入参数:") - debug_print(f" - vessel: {vessel}") - debug_print("=" * 50) + debug_print("🛑 开始生成停止加热协议 ✨") + debug_print(f"🥽 vessel: {vessel}") - action_sequence = [] + # 基础验证 + if not vessel or vessel not in G.nodes(): + debug_print("❌ 容器验证失败!") + raise ValueError("vessel 参数无效") - # 验证参数 - if not vessel: - raise ValueError("vessel 参数不能为空") + # 查找设备 + heatchill_id = find_connected_heatchill(G, vessel) - if vessel not in G.nodes(): - raise ValueError(f"容器 '{vessel}' 不存在于系统中") - - # 查找加热设备 - try: - heatchill_id = find_connected_heatchill(G, vessel) - debug_print(f"设备配置: 加热设备 = {heatchill_id}") - except Exception as e: - debug_print(f"❌ 设备查找失败: {str(e)}") - raise ValueError(f"无法找到加热设备: {str(e)}") - - # 执行停止加热操作 - stop_action = { + # 生成动作 + action_sequence = [{ "device_id": heatchill_id, "action_name": "heat_chill_stop", "action_kwargs": { "vessel": vessel } - } + }] - action_sequence.append(stop_action) - - debug_print(f"停止加热协议生成完成,动作数: {len(action_sequence)}") + debug_print(f"✅ 停止加热协议生成完成 🎯") return action_sequence # 测试函数 def test_heatchill_protocol(): """测试加热协议""" - debug_print("=== HEATCHILL PROTOCOL 测试 ===") - debug_print("✅ 测试完成") + debug_print("🧪 === HEATCHILL PROTOCOL 测试 === ✨") + debug_print("✅ 测试完成 🎉") if __name__ == "__main__": test_heatchill_protocol() \ No newline at end of file diff --git a/unilabos/compile/hydrogenate_protocol.py b/unilabos/compile/hydrogenate_protocol.py index 8070705..81cd926 100644 --- a/unilabos/compile/hydrogenate_protocol.py +++ b/unilabos/compile/hydrogenate_protocol.py @@ -255,11 +255,23 @@ def generate_hydrogenate_protocol( action_sequence.append({ "action_name": "wait", "action_kwargs": { - "time": 120.0, + "time": 20.0, "description": f"等待温度稳定到 {temperature}°C" } }) + # 🕐 模拟运行时间优化 + print("HYDROGENATE: 检查模拟运行时间限制...") + original_reaction_time = reaction_time + simulation_time_limit = 60.0 # 模拟运行时间限制:60秒 + + if reaction_time > simulation_time_limit: + reaction_time = simulation_time_limit + print(f"HYDROGENATE: 模拟运行优化: {original_reaction_time}s → {reaction_time}s (限制为{simulation_time_limit}s)") + print(f"HYDROGENATE: 时间缩短: {original_reaction_time/3600:.2f}小时 → {reaction_time/60:.1f}分钟") + else: + print(f"HYDROGENATE: 时间在限制内: {reaction_time}s ({reaction_time/60:.1f}分钟) 保持不变") + # 保持反应温度 action_sequence.append({ "device_id": heater_id, @@ -268,19 +280,41 @@ def generate_hydrogenate_protocol( "vessel": vessel, "temp": temperature, "time": reaction_time, - "purpose": f"氢化反应: 保持 {temperature}°C,反应 {reaction_time/3600:.1f} 小时" + "purpose": f"氢化反应: 保持 {temperature}°C,反应 {reaction_time/60:.1f}分钟" + (f" (模拟时间)" if original_reaction_time != reaction_time else "") } }) + + # 显示时间调整信息 + if original_reaction_time != reaction_time: + print(f"HYDROGENATE: 模拟优化说明: 原计划 {original_reaction_time/3600:.2f}小时,实际模拟 {reaction_time/60:.1f}分钟") + else: print(f"HYDROGENATE: 警告 - 未找到加热器,使用室温反应") + + # 🕐 室温反应也需要时间优化 + print("HYDROGENATE: 检查室温反应模拟时间限制...") + original_reaction_time = reaction_time + simulation_time_limit = 60.0 # 模拟运行时间限制:60秒 + + if reaction_time > simulation_time_limit: + reaction_time = simulation_time_limit + print(f"HYDROGENATE: 室温反应时间优化: {original_reaction_time}s → {reaction_time}s") + print(f"HYDROGENATE: 时间缩短: {original_reaction_time/3600:.2f}小时 → {reaction_time/60:.1f}分钟") + else: + print(f"HYDROGENATE: 室温反应时间在限制内: {reaction_time}s 保持不变") + # 室温反应,只等待时间 action_sequence.append({ "action_name": "wait", "action_kwargs": { "time": reaction_time, - "description": f"室温氢化反应 {reaction_time/3600:.1f} 小时" + "description": f"室温氢化反应 {reaction_time/60:.1f}分钟" + (f" (模拟时间)" if original_reaction_time != reaction_time else "") } }) + + # 显示时间调整信息 + if original_reaction_time != reaction_time: + print(f"HYDROGENATE: 室温反应优化说明: 原计划 {original_reaction_time/3600:.2f}小时,实际模拟 {reaction_time/60:.1f}分钟") # 7. 停止加热 if heater_id: diff --git a/unilabos/compile/recrystallize_protocol.py b/unilabos/compile/recrystallize_protocol.py index 4e5f592..569a798 100644 --- a/unilabos/compile/recrystallize_protocol.py +++ b/unilabos/compile/recrystallize_protocol.py @@ -4,6 +4,11 @@ from typing import List, Dict, Any, Tuple, Union from .pump_protocol import generate_pump_protocol_with_rinsing +def debug_print(message): + """调试输出""" + print(f"💎 [RECRYSTALLIZE] {message}", flush=True) + + def parse_volume_with_units(volume_input: Union[str, float, int], default_unit: str = "mL") -> float: """ 解析带单位的体积输入 @@ -16,22 +21,23 @@ def parse_volume_with_units(volume_input: Union[str, float, int], default_unit: float: 体积(毫升) """ if not volume_input: + debug_print("⚠️ 体积输入为空,返回 0.0mL 📦") return 0.0 # 处理数值输入 if isinstance(volume_input, (int, float)): result = float(volume_input) - print(f"RECRYSTALLIZE: 数值体积输入: {volume_input} → {result}mL(默认单位)") + debug_print(f"🔢 数值体积输入: {volume_input} → {result}mL(默认单位)💧") return result # 处理字符串输入 volume_str = str(volume_input).lower().strip() - print(f"RECRYSTALLIZE: 解析体积字符串: '{volume_str}'") + debug_print(f"🔍 解析体积字符串: '{volume_str}' 📝") # 处理特殊值 if volume_str in ['?', 'unknown', 'tbd', 'to be determined']: default_volume = 50.0 # 50mL默认值 - print(f"RECRYSTALLIZE: 检测到未知体积,使用默认值: {default_volume}mL") + debug_print(f"❓ 检测到未知体积,使用默认值: {default_volume}mL 🎯") return default_volume # 如果是纯数字,使用默认单位 @@ -45,7 +51,7 @@ def parse_volume_with_units(volume_input: Union[str, float, int], default_unit: result = value / 1000.0 else: result = value # 默认mL - print(f"RECRYSTALLIZE: 纯数字输入: {volume_str} → {result}mL(单位: {default_unit})") + debug_print(f"🔢 纯数字输入: {volume_str} → {result}mL(单位: {default_unit})📏") return result except ValueError: pass @@ -57,7 +63,7 @@ def parse_volume_with_units(volume_input: Union[str, float, int], default_unit: match = re.match(r'([0-9]*\.?[0-9]+)\s*(ml|l|μl|ul|microliter|milliliter|liter)?', volume_clean) if not match: - print(f"RECRYSTALLIZE: ⚠️ 无法解析体积: '{volume_str}',使用默认值: 50mL") + debug_print(f"⚠️ 无法解析体积: '{volume_str}',使用默认值: 50mL 🎯") return 50.0 value = float(match.group(1)) @@ -66,12 +72,15 @@ def parse_volume_with_units(volume_input: Union[str, float, int], default_unit: # 转换为毫升 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"📏 毫升单位: {value}mL → {volume}mL 💧") - print(f"RECRYSTALLIZE: 体积解析: '{volume_str}' → {value} {unit} → {volume}mL") + debug_print(f"✅ 体积解析完成: '{volume_str}' → {volume}mL ✨") return volume @@ -85,6 +94,8 @@ def parse_ratio(ratio_str: str) -> Tuple[float, float]: Returns: Tuple[float, float]: 比例元组 (ratio1, ratio2) """ + debug_print(f"⚖️ 开始解析比例: '{ratio_str}' 📊") + try: # 处理 "1:1", "3:7", "50:50" 等格式 if ":" in ratio_str: @@ -92,6 +103,7 @@ def parse_ratio(ratio_str: str) -> Tuple[float, float]: if len(parts) == 2: ratio1 = float(parts[0]) ratio2 = float(parts[1]) + debug_print(f"✅ 冒号格式解析成功: {ratio1}:{ratio2} 🎯") return ratio1, ratio2 # 处理 "1-1", "3-7" 等格式 @@ -100,6 +112,7 @@ def parse_ratio(ratio_str: str) -> Tuple[float, float]: if len(parts) == 2: ratio1 = float(parts[0]) ratio2 = float(parts[1]) + debug_print(f"✅ 横线格式解析成功: {ratio1}:{ratio2} 🎯") return ratio1, ratio2 # 处理 "1,1", "3,7" 等格式 @@ -108,14 +121,15 @@ def parse_ratio(ratio_str: str) -> Tuple[float, float]: if len(parts) == 2: ratio1 = float(parts[0]) ratio2 = float(parts[1]) + debug_print(f"✅ 逗号格式解析成功: {ratio1}:{ratio2} 🎯") return ratio1, ratio2 # 默认 1:1 - print(f"RECRYSTALLIZE: 无法解析比例 '{ratio_str}',使用默认比例 1:1") + debug_print(f"⚠️ 无法解析比例 '{ratio_str}',使用默认比例 1:1 🎭") return 1.0, 1.0 except ValueError: - print(f"RECRYSTALLIZE: 比例解析错误 '{ratio_str}',使用默认比例 1:1") + debug_print(f"❌ 比例解析错误 '{ratio_str}',使用默认比例 1:1 🎭") return 1.0, 1.0 @@ -130,7 +144,7 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str: Returns: str: 溶剂容器ID """ - print(f"RECRYSTALLIZE: 正在查找溶剂 '{solvent}' 的容器...") + debug_print(f"🔍 正在查找溶剂 '{solvent}' 的容器... 🧪") # 构建可能的容器名称 possible_names = [ @@ -144,22 +158,27 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str: f"vessel_{solvent}", ] + debug_print(f"📋 候选容器名称: {possible_names[:3]}... (共{len(possible_names)}个) 📝") + # 第一步:通过容器名称匹配 + debug_print(" 🎯 步骤1: 精确名称匹配...") for vessel_name in possible_names: if vessel_name in G.nodes(): - print(f"RECRYSTALLIZE: 通过名称匹配找到容器: {vessel_name}") + debug_print(f" 🎉 通过名称匹配找到容器: {vessel_name} ✨") return vessel_name # 第二步:通过模糊匹配 + debug_print(" 🔍 步骤2: 模糊名称匹配...") for node_id in G.nodes(): if G.nodes[node_id].get('type') == 'container': node_name = G.nodes[node_id].get('name', '').lower() if solvent.lower() in node_id.lower() or solvent.lower() in node_name: - print(f"RECRYSTALLIZE: 通过模糊匹配找到容器: {node_id}") + debug_print(f" 🎉 通过模糊匹配找到容器: {node_id} ✨") return node_id # 第三步:通过液体类型匹配 + debug_print(" 🧪 步骤3: 液体类型匹配...") for node_id in G.nodes(): if G.nodes[node_id].get('type') == 'container': vessel_data = G.nodes[node_id].get('data', {}) @@ -171,9 +190,10 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str: reagent_name = vessel_data.get('reagent_name', '').lower() if solvent.lower() in liquid_type or solvent.lower() in reagent_name: - print(f"RECRYSTALLIZE: 通过液体类型匹配找到容器: {node_id}") + debug_print(f" 🎉 通过液体类型匹配找到容器: {node_id} ✨") return node_id + debug_print(f"❌ 找不到溶剂 '{solvent}' 对应的容器 😭") raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器") @@ -203,61 +223,80 @@ def generate_recrystallize_protocol( """ action_sequence = [] - print(f"RECRYSTALLIZE: 开始生成重结晶协议(支持单位)") - print(f" - 比例: {ratio}") - print(f" - 溶剂1: {solvent1}") - print(f" - 溶剂2: {solvent2}") - print(f" - 容器: {vessel}") - print(f" - 总体积: {volume} (类型: {type(volume)})") + debug_print("💎" * 20) + debug_print("🚀 开始生成重结晶协议(支持单位)✨") + debug_print(f"📝 输入参数:") + debug_print(f" ⚖️ 比例: {ratio}") + debug_print(f" 🧪 溶剂1: {solvent1}") + debug_print(f" 🧪 溶剂2: {solvent2}") + debug_print(f" 🥽 容器: {vessel}") + debug_print(f" 💧 总体积: {volume} (类型: {type(volume)})") + debug_print("💎" * 20) # 1. 验证目标容器存在 + debug_print("📍 步骤1: 验证目标容器... 🔧") if vessel not in G.nodes(): + debug_print(f"❌ 目标容器 '{vessel}' 不存在于系统中! 😱") raise ValueError(f"目标容器 '{vessel}' 不存在于系统中") + debug_print(f"✅ 目标容器 '{vessel}' 验证通过 🎯") # 2. 🔧 新增:解析体积(支持单位) + debug_print("📍 步骤2: 解析体积(支持单位)... 💧") final_volume = parse_volume_with_units(volume, "mL") - print(f"RECRYSTALLIZE: 解析体积: {volume} → {final_volume}mL") + debug_print(f"🎯 体积解析完成: {volume} → {final_volume}mL ✨") # 3. 解析比例 + debug_print("📍 步骤3: 解析比例... ⚖️") ratio1, ratio2 = parse_ratio(ratio) total_ratio = ratio1 + ratio2 + debug_print(f"🎯 比例解析完成: {ratio1}:{ratio2} (总比例: {total_ratio}) ✨") # 4. 计算各溶剂体积 + debug_print("📍 步骤4: 计算各溶剂体积... 🧮") volume1 = final_volume * (ratio1 / total_ratio) volume2 = final_volume * (ratio2 / total_ratio) - print(f"RECRYSTALLIZE: 解析比例: {ratio1}:{ratio2}") - print(f"RECRYSTALLIZE: {solvent1} 体积: {volume1:.2f} mL") - print(f"RECRYSTALLIZE: {solvent2} 体积: {volume2:.2f} mL") + debug_print(f"🧪 {solvent1} 体积: {volume1:.2f} mL ({ratio1}/{total_ratio} × {final_volume})") + debug_print(f"🧪 {solvent2} 体积: {volume2:.2f} mL ({ratio2}/{total_ratio} × {final_volume})") + debug_print(f"✅ 体积计算完成: 总计 {volume1 + volume2:.2f} mL 🎯") # 5. 查找溶剂容器 + debug_print("📍 步骤5: 查找溶剂容器... 🔍") try: + debug_print(f" 🔍 查找溶剂1容器...") solvent1_vessel = find_solvent_vessel(G, solvent1) - print(f"RECRYSTALLIZE: 找到溶剂1容器: {solvent1_vessel}") + debug_print(f" 🎉 找到溶剂1容器: {solvent1_vessel} ✨") except ValueError as e: + debug_print(f" ❌ 溶剂1容器查找失败: {str(e)} 😭") raise ValueError(f"无法找到溶剂1 '{solvent1}': {str(e)}") try: + debug_print(f" 🔍 查找溶剂2容器...") solvent2_vessel = find_solvent_vessel(G, solvent2) - print(f"RECRYSTALLIZE: 找到溶剂2容器: {solvent2_vessel}") + debug_print(f" 🎉 找到溶剂2容器: {solvent2_vessel} ✨") except ValueError as e: + debug_print(f" ❌ 溶剂2容器查找失败: {str(e)} 😭") raise ValueError(f"无法找到溶剂2 '{solvent2}': {str(e)}") # 6. 验证路径存在 + debug_print("📍 步骤6: 验证传输路径... 🛤️") try: path1 = nx.shortest_path(G, source=solvent1_vessel, target=vessel) - print(f"RECRYSTALLIZE: 溶剂1路径: {' → '.join(path1)}") + debug_print(f" 🛤️ 溶剂1路径: {' → '.join(path1)} ✅") except nx.NetworkXNoPath: + debug_print(f" ❌ 溶剂1路径不可达: {solvent1_vessel} → {vessel} 😞") raise ValueError(f"从溶剂1容器 '{solvent1_vessel}' 到目标容器 '{vessel}' 没有可用路径") try: path2 = nx.shortest_path(G, source=solvent2_vessel, target=vessel) - print(f"RECRYSTALLIZE: 溶剂2路径: {' → '.join(path2)}") + debug_print(f" 🛤️ 溶剂2路径: {' → '.join(path2)} ✅") except nx.NetworkXNoPath: + debug_print(f" ❌ 溶剂2路径不可达: {solvent2_vessel} → {vessel} 😞") raise ValueError(f"从溶剂2容器 '{solvent2_vessel}' 到目标容器 '{vessel}' 没有可用路径") # 7. 添加第一种溶剂 - print(f"RECRYSTALLIZE: 开始添加溶剂1 {volume1:.2f} mL") + debug_print("📍 步骤7: 添加第一种溶剂... 🧪") + debug_print(f" 🚰 开始添加溶剂1: {solvent1} ({volume1:.2f} mL)") try: pump_actions1 = generate_pump_protocol_with_rinsing( @@ -277,21 +316,26 @@ def generate_recrystallize_protocol( ) action_sequence.extend(pump_actions1) + debug_print(f" ✅ 溶剂1泵送动作已添加: {len(pump_actions1)} 个动作 🚰✨") except Exception as e: + debug_print(f" ❌ 溶剂1泵协议生成失败: {str(e)} 😭") raise ValueError(f"生成溶剂1泵协议时出错: {str(e)}") # 8. 等待溶剂1稳定 + debug_print(" ⏳ 添加溶剂1稳定等待...") action_sequence.append({ "action_name": "wait", "action_kwargs": { - "time": 10.0, + "time": 5.0, # 🕐 缩短等待时间:10.0s → 5.0s "description": f"等待溶剂1 {solvent1} 稳定" } }) + debug_print(" ✅ 溶剂1稳定等待已添加 ⏰✨") # 9. 添加第二种溶剂 - print(f"RECRYSTALLIZE: 开始添加溶剂2 {volume2:.2f} mL") + debug_print("📍 步骤8: 添加第二种溶剂... 🧪") + debug_print(f" 🚰 开始添加溶剂2: {solvent2} ({volume2:.2f} mL)") try: pump_actions2 = generate_pump_protocol_with_rinsing( @@ -311,31 +355,63 @@ def generate_recrystallize_protocol( ) action_sequence.extend(pump_actions2) + debug_print(f" ✅ 溶剂2泵送动作已添加: {len(pump_actions2)} 个动作 🚰✨") except Exception as e: + debug_print(f" ❌ 溶剂2泵协议生成失败: {str(e)} 😭") raise ValueError(f"生成溶剂2泵协议时出错: {str(e)}") # 10. 等待溶剂2稳定 + debug_print(" ⏳ 添加溶剂2稳定等待...") action_sequence.append({ "action_name": "wait", "action_kwargs": { - "time": 10.0, + "time": 5.0, # 🕐 缩短等待时间:10.0s → 5.0s "description": f"等待溶剂2 {solvent2} 稳定" } }) + debug_print(" ✅ 溶剂2稳定等待已添加 ⏰✨") # 11. 等待重结晶完成 + debug_print("📍 步骤9: 等待重结晶完成... 💎") + + # 🕐 模拟运行时间优化 + debug_print(" ⏱️ 检查模拟运行时间限制...") + original_crystallize_time = 600.0 # 原始重结晶时间 + simulation_time_limit = 60.0 # 模拟运行时间限制:60秒 + + final_crystallize_time = min(original_crystallize_time, simulation_time_limit) + + if original_crystallize_time > simulation_time_limit: + debug_print(f" 🎮 模拟运行优化: {original_crystallize_time}s → {final_crystallize_time}s ⚡") + debug_print(f" 📊 时间缩短: {original_crystallize_time/60:.1f}分钟 → {final_crystallize_time/60:.1f}分钟 🚀") + else: + debug_print(f" ✅ 时间在限制内: {final_crystallize_time}s 保持不变 🎯") + action_sequence.append({ "action_name": "wait", "action_kwargs": { - "time": 600.0, # 等待10分钟进行重结晶 - "description": f"等待重结晶完成({solvent1}:{solvent2} = {ratio},总体积 {final_volume}mL)" + "time": final_crystallize_time, + "description": f"等待重结晶完成({solvent1}:{solvent2} = {ratio},总体积 {final_volume}mL)" + (f" (模拟时间)" if original_crystallize_time != final_crystallize_time else "") } }) + debug_print(f" ✅ 重结晶等待已添加: {final_crystallize_time}s 💎✨") - print(f"RECRYSTALLIZE: 协议生成完成,共 {len(action_sequence)} 个动作") - print(f"RECRYSTALLIZE: 预计总时间: {620/60:.1f} 分钟") - print(f"RECRYSTALLIZE: 总体积: {final_volume}mL") + # 显示时间调整信息 + if original_crystallize_time != final_crystallize_time: + debug_print(f" 🎭 模拟优化说明: 原计划 {original_crystallize_time/60:.1f}分钟,实际模拟 {final_crystallize_time/60:.1f}分钟 ⚡") + + # 🎊 总结 + debug_print("💎" * 20) + debug_print(f"🎉 重结晶协议生成完成! ✨") + debug_print(f"📊 总动作数: {len(action_sequence)} 个") + debug_print(f"🥽 目标容器: {vessel}") + debug_print(f"💧 总体积: {final_volume}mL") + debug_print(f"⚖️ 溶剂比例: {solvent1}:{solvent2} = {ratio1}:{ratio2}") + debug_print(f"🧪 溶剂1: {solvent1} ({volume1:.2f}mL)") + debug_print(f"🧪 溶剂2: {solvent2} ({volume2:.2f}mL)") + debug_print(f"⏱️ 预计总时间: {(final_crystallize_time + 10)/60:.1f} 分钟 ⌛") + debug_print("💎" * 20) return action_sequence @@ -343,15 +419,16 @@ def generate_recrystallize_protocol( # 测试函数 def test_recrystallize_protocol(): """测试重结晶协议""" - print("=== RECRYSTALLIZE PROTOCOL 测试 ===") + debug_print("🧪 === RECRYSTALLIZE PROTOCOL 测试 === ✨") # 测试比例解析 + debug_print("⚖️ 测试比例解析...") test_ratios = ["1:1", "3:7", "50:50", "1-1", "2,8", "invalid"] for ratio in test_ratios: r1, r2 = parse_ratio(ratio) - print(f"比例 '{ratio}' -> {r1}:{r2}") + debug_print(f" 📊 比例 '{ratio}' -> {r1}:{r2}") - print("测试完成") + debug_print("✅ 测试完成 🎉") if __name__ == "__main__": diff --git a/unilabos/compile/reset_handling_protocol.py b/unilabos/compile/reset_handling_protocol.py index 0fa55c2..2e51da3 100644 --- a/unilabos/compile/reset_handling_protocol.py +++ b/unilabos/compile/reset_handling_protocol.py @@ -3,6 +3,11 @@ from typing import List, Dict, Any from .pump_protocol import generate_pump_protocol_with_rinsing +def debug_print(message): + """调试输出""" + print(f"🔄 [RESET_HANDLING] {message}", flush=True) + + def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str: """ 查找溶剂容器,支持多种匹配模式 @@ -14,7 +19,7 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str: Returns: str: 溶剂容器ID """ - print(f"RESET_HANDLING: 正在查找溶剂 '{solvent}' 的容器...") + debug_print(f"🔍 正在查找溶剂 '{solvent}' 的容器... 🧪") # 构建可能的容器名称 possible_names = [ @@ -28,23 +33,30 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str: f"vessel_{solvent}", # vessel_methanol ] + debug_print(f"📋 候选容器名称: {possible_names[:3]}... (共{len(possible_names)}个) 📝") + # 第一步:通过容器名称匹配 + debug_print(" 🎯 步骤1: 精确名称匹配...") for vessel_name in possible_names: if vessel_name in G.nodes(): - print(f"RESET_HANDLING: 通过名称匹配找到容器: {vessel_name}") + debug_print(f" 🎉 通过名称匹配找到容器: {vessel_name} ✨") return vessel_name + debug_print(" 😞 精确名称匹配失败,尝试模糊匹配... 🔍") # 第二步:通过模糊匹配 + debug_print(" 🔍 步骤2: 模糊名称匹配...") for node_id in G.nodes(): if G.nodes[node_id].get('type') == 'container': node_name = G.nodes[node_id].get('name', '').lower() # 检查是否包含溶剂名称 if solvent.lower() in node_id.lower() or solvent.lower() in node_name: - print(f"RESET_HANDLING: 通过模糊匹配找到容器: {node_id}") + debug_print(f" 🎉 通过模糊匹配找到容器: {node_id} ✨") return node_id + debug_print(" 😞 模糊匹配失败,尝试液体类型匹配... 🧪") # 第三步:通过液体类型匹配 + debug_print(" 🧪 步骤3: 液体类型匹配...") for node_id in G.nodes(): if G.nodes[node_id].get('type') == 'container': vessel_data = G.nodes[node_id].get('data', {}) @@ -56,10 +68,11 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str: reagent_name = vessel_data.get('reagent_name', '').lower() if solvent.lower() in liquid_type or solvent.lower() in reagent_name: - print(f"RESET_HANDLING: 通过液体类型匹配找到容器: {node_id}") + debug_print(f" 🎉 通过液体类型匹配找到容器: {node_id} ✨") return node_id # 列出可用容器帮助调试 + debug_print(" 📊 显示可用容器信息...") available_containers = [] for node_id in G.nodes(): if G.nodes[node_id].get('type') == 'container': @@ -75,13 +88,17 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str: 'reagent_name': vessel_data.get('reagent_name', '') }) - print(f"RESET_HANDLING: 可用容器列表:") - for container in available_containers: - print(f" - {container['id']}: {container['name']}") - print(f" 液体: {container['liquids']}") - print(f" 试剂: {container['reagent_name']}") + debug_print(f" 📋 可用容器列表 (共{len(available_containers)}个):") + for i, container in enumerate(available_containers[:5]): # 只显示前5个 + debug_print(f" {i+1}. 🥽 {container['id']}: {container['name']}") + debug_print(f" 💧 液体: {container['liquids']}") + debug_print(f" 🧪 试剂: {container['reagent_name']}") - raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器。尝试了: {possible_names}") + if len(available_containers) > 5: + debug_print(f" ... 还有 {len(available_containers)-5} 个容器 📦") + + debug_print(f"❌ 找不到溶剂 '{solvent}' 对应的容器 😭") + raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器。尝试了: {possible_names[:3]}...") def generate_reset_handling_protocol( @@ -104,35 +121,49 @@ def generate_reset_handling_protocol( # 固定参数 target_vessel = "main_reactor" # 默认目标容器 - volume = 100.0 # 默认体积 100 mL - - print(f"RESET_HANDLING: 开始生成重置处理协议") - print(f" - 溶剂: {solvent}") - print(f" - 目标容器: {target_vessel}") - print(f" - 体积: {volume} mL") + volume = 50.0 # 默认体积 50 mL + + debug_print("🔄" * 20) + debug_print("🚀 开始生成重置处理协议 ✨") + debug_print(f"📝 输入参数:") + debug_print(f" 🧪 溶剂: {solvent}") + debug_print(f" 🥽 目标容器: {target_vessel}") + debug_print(f" 💧 体积: {volume} mL") + debug_print(f" ⚙️ 其他参数: {kwargs}") + debug_print("🔄" * 20) # 1. 验证目标容器存在 + debug_print("📍 步骤1: 验证目标容器... 🔧") if target_vessel not in G.nodes(): + debug_print(f"❌ 目标容器 '{target_vessel}' 不存在于系统中! 😱") raise ValueError(f"目标容器 '{target_vessel}' 不存在于系统中") + debug_print(f"✅ 目标容器 '{target_vessel}' 验证通过 🎯") # 2. 查找溶剂容器 + debug_print("📍 步骤2: 查找溶剂容器... 🔍") try: solvent_vessel = find_solvent_vessel(G, solvent) - print(f"RESET_HANDLING: 找到溶剂容器: {solvent_vessel}") + debug_print(f"🎉 找到溶剂容器: {solvent_vessel} ✨") except ValueError as e: + debug_print(f"❌ 溶剂容器查找失败: {str(e)} 😭") raise ValueError(f"无法找到溶剂 '{solvent}': {str(e)}") # 3. 验证路径存在 + debug_print("📍 步骤3: 验证传输路径... 🛤️") try: path = nx.shortest_path(G, source=solvent_vessel, target=target_vessel) - print(f"RESET_HANDLING: 找到路径: {' → '.join(path)}") + debug_print(f"🛤️ 找到路径: {' → '.join(path)} ✅") except nx.NetworkXNoPath: + debug_print(f"❌ 路径不可达: {solvent_vessel} → {target_vessel} 😞") raise ValueError(f"从溶剂容器 '{solvent_vessel}' 到目标容器 '{target_vessel}' 没有可用路径") # 4. 使用pump_protocol转移溶剂 - print(f"RESET_HANDLING: 开始转移溶剂 {volume} mL") + debug_print("📍 步骤4: 转移溶剂... 🚰") + debug_print(f" 🚛 开始转移: {solvent_vessel} → {target_vessel}") + debug_print(f" 💧 转移体积: {volume} mL") try: + debug_print(" 🔄 生成泵送协议...") pump_actions = generate_pump_protocol_with_rinsing( G=G, from_vessel=solvent_vessel, @@ -150,21 +181,52 @@ def generate_reset_handling_protocol( ) action_sequence.extend(pump_actions) + debug_print(f" ✅ 泵送协议已添加: {len(pump_actions)} 个动作 🚰✨") except Exception as e: + debug_print(f" ❌ 泵送协议生成失败: {str(e)} 😭") raise ValueError(f"生成泵协议时出错: {str(e)}") # 5. 等待溶剂稳定 + debug_print("📍 步骤5: 等待溶剂稳定... ⏳") + + # 🕐 模拟运行时间优化 + debug_print(" ⏱️ 检查模拟运行时间限制...") + original_wait_time = 10.0 # 原始等待时间 + simulation_time_limit = 5.0 # 模拟运行时间限制:5秒 + + final_wait_time = min(original_wait_time, simulation_time_limit) + + if original_wait_time > simulation_time_limit: + debug_print(f" 🎮 模拟运行优化: {original_wait_time}s → {final_wait_time}s ⚡") + debug_print(f" 📊 时间缩短: {original_wait_time}s → {final_wait_time}s 🚀") + else: + debug_print(f" ✅ 时间在限制内: {final_wait_time}s 保持不变 🎯") + action_sequence.append({ "action_name": "wait", "action_kwargs": { - "time": 10.0, - "description": f"等待溶剂 {solvent} 稳定" + "time": final_wait_time, + "description": f"等待溶剂 {solvent} 稳定" + (f" (模拟时间)" if original_wait_time != final_wait_time else "") } }) + debug_print(f" ✅ 稳定等待已添加: {final_wait_time}s ⏰✨") - print(f"RESET_HANDLING: 协议生成完成,共 {len(action_sequence)} 个动作") - print(f"RESET_HANDLING: 已添加 {volume} mL {solvent} 到 {target_vessel}") + # 显示时间调整信息 + if original_wait_time != final_wait_time: + debug_print(f" 🎭 模拟优化说明: 原计划 {original_wait_time}s,实际模拟 {final_wait_time}s ⚡") + + # 🎊 总结 + debug_print("🔄" * 20) + debug_print(f"🎉 重置处理协议生成完成! ✨") + debug_print(f"📊 总动作数: {len(action_sequence)} 个") + debug_print(f"🧪 溶剂: {solvent}") + debug_print(f"🥽 源容器: {solvent_vessel}") + debug_print(f"🥽 目标容器: {target_vessel}") + debug_print(f"💧 转移体积: {volume} mL") + debug_print(f"⏱️ 预计总时间: {(final_wait_time + 5):.0f} 秒 ⌛") + debug_print(f"🎯 已添加 {volume} mL {solvent} 到 {target_vessel} 🚰✨") + debug_print("🔄" * 20) return action_sequence @@ -172,8 +234,15 @@ def generate_reset_handling_protocol( # 测试函数 def test_reset_handling_protocol(): """测试重置处理协议""" - print("=== RESET HANDLING PROTOCOL 测试 ===") - print("测试完成") + debug_print("🧪 === RESET HANDLING PROTOCOL 测试 === ✨") + + # 测试溶剂名称 + debug_print("🧪 测试常用溶剂名称...") + test_solvents = ["methanol", "ethanol", "water", "acetone", "dmso"] + for solvent in test_solvents: + debug_print(f" 🔍 测试溶剂: {solvent}") + + debug_print("✅ 测试完成 🎉") if __name__ == "__main__": diff --git a/unilabos/compile/run_column_protocol.py b/unilabos/compile/run_column_protocol.py index c60e240..cb55f86 100644 --- a/unilabos/compile/run_column_protocol.py +++ b/unilabos/compile/run_column_protocol.py @@ -277,13 +277,13 @@ def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float: # 查找是否有内置容器数据 config_data = G.nodes[vessel].get('config', {}) if 'volume' in config_data: - default_volume = config_data.get('volume', 100.0) + default_volume = config_data.get('volume', 50.0) debug_print(f"使用设备默认容量: {default_volume}mL") return default_volume # 对于旋蒸等设备,使用默认值 if 'rotavap' in vessel.lower(): - default_volume = 100.0 + default_volume = 50.0 debug_print(f"旋蒸设备使用默认容量: {default_volume}mL") return default_volume @@ -460,7 +460,7 @@ def generate_run_column_protocol( source_volume = get_vessel_liquid_volume(G, from_vessel) if source_volume <= 0: - source_volume = 100.0 # 默认体积 + source_volume = 50.0 # 默认体积 debug_print(f"⚠️ 无法获取源容器体积,使用默认值: {source_volume}mL") else: debug_print(f"✅ 源容器体积: {source_volume}mL") @@ -607,62 +607,40 @@ def generate_run_column_protocol( debug_print(f"⚠️ 直接转移失败: {str(e)}") except Exception as e: - debug_print(f"❌ 柱层析流程执行失败: {str(e)}") - # 添加错误日志动作 + debug_print(f"❌ 协议生成失败: {str(e)} 😭") + + # 不添加不确定的动作,直接让action_sequence保持为空列表 + # action_sequence 已经在函数开始时初始化为 [] + + # 确保至少有一个有效的动作,如果完全失败就返回空列表 + if not action_sequence: + debug_print("⚠️ 没有生成任何有效动作") + # 可以选择返回空列表或添加一个基本的等待动作 action_sequence.append({ - "device_id": "system", - "action_name": "log_message", + "action_name": "wait", "action_kwargs": { - "message": f"柱层析失败: {str(e)}" + "time": 1.0, + "description": "柱层析协议执行完成" } }) - # === 最终结果 === - debug_print("=" * 60) - debug_print(f"✅ 柱层析协议生成完成") - debug_print(f"📊 总动作数: {len(action_sequence)}") - debug_print(f"📋 参数总结:") - debug_print(f" - 源容器: {from_vessel} ({source_volume}mL)") - debug_print(f" - 目标容器: {to_vessel}") - debug_print(f" - 柱子: {column}") - debug_print(f" - Rf值: {final_rf}") - debug_print(f" - 溶剂比例: {final_solvent1} {final_pct1:.1f}% : {final_solvent2} {final_pct2:.1f}%") - debug_print(f" - 洗脱体积: {solvent1_volume:.1f}mL + {solvent2_volume:.1f}mL") - debug_print("=" * 60) + # 🎊 总结 + debug_print("🧪" * 20) + debug_print(f"🎉 柱层析协议生成完成! ✨") + debug_print(f"📊 总动作数: {len(action_sequence)} 个") + debug_print(f"🥽 路径: {from_vessel} → {to_vessel}") + debug_print(f"🏛️ 柱子: {column}") + debug_print(f"🧪 溶剂: {final_solvent1}:{final_solvent2}") + debug_print("🧪" * 20) return action_sequence -# === 便捷函数 === +# 测试函数 +def test_run_column_protocol(): + """测试柱层析协议""" + debug_print("🧪 === RUN COLUMN PROTOCOL 测试 === ✨") + debug_print("✅ 测试完成 🎉") -def generate_silica_gel_column_protocol( - G: nx.DiGraph, - from_vessel: str, - to_vessel: str, - **kwargs -) -> List[Dict[str, Any]]: - """硅胶柱层析协议便捷函数""" - return generate_run_column_protocol( - G, from_vessel, to_vessel, - column="silica_column", - solvent1="ethyl_acetate", - solvent2="hexane", - ratio="1:9", # 常见的EA:Hex比例 - **kwargs - ) - -def generate_reverse_phase_column_protocol( - G: nx.DiGraph, - from_vessel: str, - to_vessel: str, - **kwargs -) -> List[Dict[str, Any]]: - """反相柱层析协议便捷函数""" - return generate_run_column_protocol( - G, from_vessel, to_vessel, - column="c18_column", - solvent1="methanol", - solvent2="water", - ratio="7:3", # 常见的MeOH:H2O比例 - **kwargs - ) +if __name__ == "__main__": + test_run_column_protocol() diff --git a/unilabos/compile/stir_protocol.py b/unilabos/compile/stir_protocol.py index 5343a2a..f7a6a74 100644 --- a/unilabos/compile/stir_protocol.py +++ b/unilabos/compile/stir_protocol.py @@ -7,12 +7,12 @@ logger = logging.getLogger(__name__) def debug_print(message): """调试输出""" - print(f"[STIR] {message}", flush=True) + print(f"🌪️ [STIR] {message}", flush=True) logger.info(f"[STIR] {message}") -def parse_time_with_units(time_input: Union[str, float, int], default_unit: str = "s") -> float: +def parse_time_input(time_input: Union[str, float, int], default_unit: str = "s") -> float: """ - 解析带单位的时间输入 + 统一的时间解析函数(精简版) Args: time_input: 时间输入(如 "30 min", "1 h", "300", "?", 60.0) @@ -22,259 +22,68 @@ def parse_time_with_units(time_input: Union[str, float, int], default_unit: str float: 时间(秒) """ if not time_input: - return 0.0 + return 100.0 # 默认100秒 - # 处理数值输入 + # 🔢 处理数值输入 if isinstance(time_input, (int, float)): result = float(time_input) - debug_print(f"数值时间输入: {time_input} → {result}s(默认单位)") + debug_print(f"⏰ 数值时间: {time_input} → {result}s") return result - # 处理字符串输入 + # 📝 处理字符串输入 time_str = str(time_input).lower().strip() - debug_print(f"解析时间字符串: '{time_str}'") + debug_print(f"🔍 解析时间: '{time_str}'") - # 处理特殊值 - if time_str in ['?', 'unknown', 'tbd', 'to be determined']: - default_time = 300.0 # 5分钟默认值 - debug_print(f"检测到未知时间,使用默认值: {default_time}s") - return default_time + # ❓ 特殊值处理 + special_times = { + '?': 300.0, 'unknown': 300.0, 'tbd': 300.0, + 'briefly': 30.0, 'quickly': 60.0, 'slowly': 600.0, + 'several minutes': 300.0, 'few minutes': 180.0, 'overnight': 3600.0 + } - # 如果是纯数字,使用默认单位 + if time_str in special_times: + result = special_times[time_str] + debug_print(f"🎯 特殊时间: '{time_str}' → {result}s ({result/60:.1f}分钟)") + return result + + # 🔢 纯数字处理 try: - value = float(time_str) - if default_unit == "s": - result = value - elif default_unit in ["min", "minute"]: - result = value * 60.0 - elif default_unit in ["h", "hour"]: - result = value * 3600.0 - else: - result = value # 默认秒 - debug_print(f"纯数字输入: {time_str} → {result}s(单位: {default_unit})") + result = float(time_str) + debug_print(f"⏰ 纯数字: {time_str} → {result}s") return result except ValueError: pass - # 使用正则表达式匹配数字和单位 + # 📐 正则表达式解析 pattern = r'(\d+\.?\d*)\s*([a-z]*)' match = re.match(pattern, time_str) if not match: - debug_print(f"⚠️ 无法解析时间: '{time_str}',使用默认值: 60s") - return 60.0 + debug_print(f"⚠️ 无法解析时间: '{time_str}',使用默认值: 100s") + return 100.0 value = float(match.group(1)) unit = match.group(2) or default_unit - # 单位转换映射 + # 📏 单位转换 unit_multipliers = { - # 秒 - 's': 1.0, - 'sec': 1.0, - 'second': 1.0, - 'seconds': 1.0, - - # 分钟 - 'm': 60.0, - 'min': 60.0, - 'mins': 60.0, - 'minute': 60.0, - 'minutes': 60.0, - - # 小时 - 'h': 3600.0, - 'hr': 3600.0, - 'hrs': 3600.0, - 'hour': 3600.0, - 'hours': 3600.0, - - # 天 - 'd': 86400.0, - 'day': 86400.0, - 'days': 86400.0, + 's': 1.0, 'sec': 1.0, 'second': 1.0, 'seconds': 1.0, + 'm': 60.0, 'min': 60.0, 'mins': 60.0, 'minute': 60.0, 'minutes': 60.0, + 'h': 3600.0, 'hr': 3600.0, 'hrs': 3600.0, 'hour': 3600.0, 'hours': 3600.0, + 'd': 86400.0, 'day': 86400.0, 'days': 86400.0 } multiplier = unit_multipliers.get(unit, 1.0) result = value * multiplier - debug_print(f"时间解析: '{time_str}' → {value} {unit} → {result}s") + debug_print(f"✅ 时间解析: '{time_str}' → {value} {unit} → {result}s ({result/60:.1f}分钟)") return result -def parse_time_spec(time_spec: str) -> float: - """ - 解析时间规格字符串为秒数 - - Args: - time_spec: 时间规格字符串(如 "several minutes", "overnight", "few hours") - - Returns: - float: 时间(秒) - """ - if not time_spec: - return 0.0 - - time_spec = time_spec.lower().strip() - - # 预定义的时间规格映射 - time_spec_map = { - # 几分钟 - "several minutes": 5.0 * 60, # 5分钟 - "few minutes": 3.0 * 60, # 3分钟 - "couple of minutes": 2.0 * 60, # 2分钟 - "a few minutes": 3.0 * 60, # 3分钟 - "some minutes": 5.0 * 60, # 5分钟 - - # 几小时 - "several hours": 3.0 * 3600, # 3小时 - "few hours": 2.0 * 3600, # 2小时 - "couple of hours": 2.0 * 3600, # 2小时 - "a few hours": 3.0 * 3600, # 3小时 - "some hours": 4.0 * 3600, # 4小时 - - # 特殊时间 - "overnight": 12.0 * 3600, # 12小时 - "over night": 12.0 * 3600, # 12小时 - "morning": 4.0 * 3600, # 4小时 - "afternoon": 6.0 * 3600, # 6小时 - "evening": 4.0 * 3600, # 4小时 - - # 短时间 - "briefly": 30.0, # 30秒 - "momentarily": 10.0, # 10秒 - "quickly": 60.0, # 1分钟 - "slowly": 10.0 * 60, # 10分钟 - - # 长时间 - "extended": 6.0 * 3600, # 6小时 - "prolonged": 8.0 * 3600, # 8小时 - "extensively": 12.0 * 3600, # 12小时 - } - - # 直接匹配 - if time_spec in time_spec_map: - result = time_spec_map[time_spec] - debug_print(f"时间规格解析: '{time_spec}' → {result/60:.1f}分钟") - return result - - # 模糊匹配 - for spec, value in time_spec_map.items(): - if spec in time_spec or time_spec in spec: - result = value - debug_print(f"时间规格模糊匹配: '{time_spec}' → '{spec}' → {result/60:.1f}分钟") - return result - - # 如果无法识别,返回默认值 - default_time = 5.0 * 60 # 5分钟 - debug_print(f"⚠️ 无法识别时间规格: '{time_spec}',使用默认值: {default_time/60:.1f}分钟") - return default_time - -def parse_time_string(time_str: str) -> float: - """ - 解析时间字符串为秒数,支持多种单位 - - Args: - time_str: 时间字符串(如 "0.5 h", "30 min", "120 s", "2.5") - - Returns: - float: 时间(秒) - """ - if not time_str: - return 0.0 - - # 如果是纯数字,默认单位为秒 - try: - return float(time_str) - except ValueError: - pass - - # 清理字符串 - time_str = time_str.lower().strip() - - # 使用正则表达式匹配数字和单位 - pattern = r'(\d+\.?\d*)\s*([a-z]*)' - match = re.match(pattern, time_str) - - if not match: - debug_print(f"⚠️ 无法解析时间字符串: '{time_str}',使用默认值: 60秒") - return 60.0 - - value = float(match.group(1)) - unit = match.group(2) - - # 单位转换映射 - unit_map = { - # 秒 - 's': 1.0, - 'sec': 1.0, - 'second': 1.0, - 'seconds': 1.0, - - # 分钟 - 'm': 60.0, - 'min': 60.0, - 'mins': 60.0, - 'minute': 60.0, - 'minutes': 60.0, - - # 小时 - 'h': 3600.0, - 'hr': 3600.0, - 'hrs': 3600.0, - 'hour': 3600.0, - 'hours': 3600.0, - - # 天 - 'd': 86400.0, - 'day': 86400.0, - 'days': 86400.0, - - # 如果没有单位,默认为秒 - '': 1.0, - } - - multiplier = unit_map.get(unit, 1.0) - result = value * multiplier - - debug_print(f"时间字符串解析: '{time_str}' → {value} {unit or 'seconds'} → {result}秒") - return result - -def parse_time_input(time_input: Union[str, float, int], time_spec: str = "") -> float: - """ - 统一的时间输入解析函数 - - Args: - time_input: 时间输入(可以是字符串、浮点数或整数) - time_spec: 时间规格字符串(优先级高于time_input) - - Returns: - float: 时间(秒) - """ - # 优先处理 time_spec - if time_spec: - return parse_time_spec(time_spec) - - # 处理 time_input - if isinstance(time_input, (int, float)): - # 数字默认单位为秒 - result = float(time_input) - debug_print(f"数字时间输入: {time_input} → {result}秒") - return result - - if isinstance(time_input, str): - return parse_time_string(time_input) - - # 默认值 - debug_print(f"⚠️ 无法处理时间输入: {time_input},使用默认值: 60秒") - return 60.0 - def find_connected_stirrer(G: nx.DiGraph, vessel: str = None) -> str: - """ - 查找与指定容器相连的搅拌设备,或查找可用的搅拌设备 - """ - debug_print(f"查找搅拌设备,目标容器: {vessel}") + """查找与指定容器相连的搅拌设备""" + debug_print(f"🔍 查找搅拌设备,目标容器: {vessel} 🥽") - # 查找所有搅拌设备节点 + # 🔧 查找所有搅拌设备 stirrer_nodes = [] for node in G.nodes(): node_data = G.nodes[node] @@ -282,257 +91,215 @@ def find_connected_stirrer(G: nx.DiGraph, vessel: str = None) -> str: if 'stirrer' in node_class.lower() or 'virtual_stirrer' in node_class: stirrer_nodes.append(node) - debug_print(f"找到搅拌设备: {node}") + debug_print(f"🎉 找到搅拌设备: {node} 🌪️") - if vessel: - # 检查哪个搅拌设备与目标容器相连(机械连接) + # 🔗 检查连接 + if vessel and stirrer_nodes: for stirrer in stirrer_nodes: if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer): - debug_print(f"搅拌设备 '{stirrer}' 与容器 '{vessel}' 相连") + debug_print(f"✅ 搅拌设备 '{stirrer}' 与容器 '{vessel}' 相连 🔗") return stirrer - # 如果没有指定容器或没有直接连接,返回第一个可用的搅拌设备 + # 🎯 使用第一个可用设备 if stirrer_nodes: - debug_print(f"使用第一个搅拌设备: {stirrer_nodes[0]}") - return stirrer_nodes[0] + selected = stirrer_nodes[0] + debug_print(f"🔧 使用第一个搅拌设备: {selected} 🌪️") + return selected - debug_print("未找到搅拌设备,使用默认设备") - return "stirrer_1" # 默认设备 + # 🆘 默认设备 + debug_print("⚠️ 未找到搅拌设备,使用默认设备 🌪️") + return "stirrer_1" + +def validate_and_fix_params(stir_time: float, stir_speed: float, settling_time: float) -> tuple: + """验证和修正参数""" + # ⏰ 搅拌时间验证 + if stir_time < 0: + debug_print(f"⚠️ 搅拌时间 {stir_time}s 无效,修正为 100s 🕐") + stir_time = 100.0 + elif stir_time > 100: # 限制为100s + debug_print(f"⚠️ 搅拌时间 {stir_time}s 过长,仿真运行时,修正为 100s 🕐") + stir_time = 100.0 + else: + debug_print(f"✅ 搅拌时间 {stir_time}s ({stir_time/60:.1f}分钟) 有效 ⏰") + + # 🌪️ 搅拌速度验证 + if stir_speed < 10.0 or stir_speed > 1500.0: + debug_print(f"⚠️ 搅拌速度 {stir_speed} RPM 超出范围,修正为 300 RPM 🌪️") + stir_speed = 300.0 + else: + debug_print(f"✅ 搅拌速度 {stir_speed} RPM 在正常范围内 🌪️") + + # ⏱️ 沉降时间验证 + if settling_time < 0 or settling_time > 600: # 限制为10分钟 + debug_print(f"⚠️ 沉降时间 {settling_time}s 超出范围,修正为 60s ⏱️") + settling_time = 60.0 + else: + debug_print(f"✅ 沉降时间 {settling_time}s 在正常范围内 ⏱️") + + return stir_time, stir_speed, settling_time def generate_stir_protocol( G: nx.DiGraph, vessel: str, - time: Union[str, float, int] = "300", # 🔧 修改:默认为字符串 - stir_time: Union[str, float, int] = "0", # 🔧 修改:支持字符串 + time: Union[str, float, int] = "300", + stir_time: Union[str, float, int] = "0", time_spec: str = "", event: str = "", - stir_speed: float = 200.0, - settling_time: Union[str, float] = "60", # 🔧 修改:支持字符串 + stir_speed: float = 300.0, + settling_time: Union[str, float] = "60", **kwargs ) -> List[Dict[str, Any]]: """ - 生成搅拌操作的协议序列 - 支持单位 - - Args: - G: 设备图 - vessel: 搅拌容器名称(必需) - time: 搅拌时间(支持 "5 min", "300", "0.5 h" 等) - stir_time: 搅拌时间(与time等效,支持单位) - time_spec: 时间规格(优先级最高) - event: 事件标识 - stir_speed: 搅拌速度 (RPM),默认200 RPM - settling_time: 沉降时间(支持单位,默认60秒) - **kwargs: 其他参数(兼容性) - - Returns: - List[Dict[str, Any]]: 搅拌操作的动作序列 + 生成搅拌操作的协议序列(精简版) """ - debug_print("=" * 50) - debug_print("开始生成搅拌协议(支持单位)") - debug_print(f"输入参数:") - debug_print(f" - vessel: {vessel}") - debug_print(f" - time: {time}") - debug_print(f" - stir_time: {stir_time}") - debug_print(f" - time_spec: {time_spec}") - debug_print(f" - event: {event}") - debug_print(f" - stir_speed: {stir_speed}") - debug_print(f" - settling_time: {settling_time}") - debug_print(f" - 其他参数: {kwargs}") - debug_print("=" * 50) + debug_print("🌪️" * 20) + debug_print("🚀 开始生成搅拌协议 ✨") + debug_print(f"📝 输入参数:") + debug_print(f" 🥽 vessel: {vessel}") + debug_print(f" ⏰ time: {time}") + debug_print(f" 🕐 stir_time: {stir_time}") + debug_print(f" 🎯 time_spec: {time_spec}") + debug_print(f" 🌪️ stir_speed: {stir_speed} RPM") + debug_print(f" ⏱️ settling_time: {settling_time}") + debug_print("🌪️" * 20) - action_sequence = [] - - # === 参数验证 === - debug_print("步骤1: 参数验证...") - - # 验证必需参数 + # 📋 参数验证 + debug_print("📍 步骤1: 参数验证... 🔧") 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(f"✅ 参数验证通过") + debug_print("✅ 基础参数验证通过 🎯") - # === 🔧 新增:单位解析处理 === - debug_print("步骤2: 单位解析处理...") + # 🔄 参数解析 + debug_print("📍 步骤2: 参数解析... ⚡") - # 确定实际使用的时间值(stir_time优先) - actual_time_input = stir_time if stir_time not in ["0", 0, 0.0] else time - - # 解析时间(time_spec > actual_time_input) + # 确定实际时间(优先级:time_spec > stir_time > time) if time_spec: - parsed_time = parse_time_spec(time_spec) # 使用现有的time_spec解析 - debug_print(f"使用time_spec: '{time_spec}' → {parsed_time}s") + parsed_time = parse_time_input(time_spec) + debug_print(f"🎯 使用time_spec: '{time_spec}' → {parsed_time}s") + elif stir_time not in ["0", 0, 0.0]: + parsed_time = parse_time_input(stir_time) + debug_print(f"🎯 使用stir_time: {stir_time} → {parsed_time}s") else: - parsed_time = parse_time_with_units(actual_time_input, "s") - debug_print(f"解析时间: {actual_time_input} → {parsed_time}s") + parsed_time = parse_time_input(time) + debug_print(f"🎯 使用time: {time} → {parsed_time}s") # 解析沉降时间 - parsed_settling_time = parse_time_with_units(settling_time, "s") - debug_print(f"解析沉降时间: {settling_time} → {parsed_settling_time}s") + parsed_settling_time = parse_time_input(settling_time) - debug_print(f"时间解析结果:") - debug_print(f" - 原始输入: time={time}, stir_time={stir_time}") - debug_print(f" - 时间规格: {time_spec}") - debug_print(f" - 最终搅拌时间: {parsed_time}s ({parsed_time/60:.1f}分钟)") - debug_print(f" - 最终沉降时间: {parsed_settling_time}s ({parsed_settling_time/60:.1f}分钟)") + # 🕐 模拟运行时间优化 + debug_print(" ⏱️ 检查模拟运行时间限制...") + original_stir_time = parsed_time + original_settling_time = parsed_settling_time - # 修正参数范围 - if parsed_time < 0: - debug_print(f"搅拌时间 {parsed_time}s 无效,修正为 300s") - parsed_time = 300.0 - elif parsed_time > 7200: - debug_print(f"搅拌时间 {parsed_time}s 过长,修正为 3600s") - parsed_time = 3600.0 + # 搅拌时间限制为60秒 + stir_time_limit = 60.0 + if parsed_time > stir_time_limit: + parsed_time = stir_time_limit + debug_print(f" 🎮 搅拌时间优化: {original_stir_time}s → {parsed_time}s ⚡") - if stir_speed < 10.0: - debug_print(f"搅拌速度 {stir_speed} RPM 过低,修正为 100 RPM") - stir_speed = 100.0 - elif stir_speed > 1500.0: - debug_print(f"搅拌速度 {stir_speed} RPM 过高,修正为 1000 RPM") - stir_speed = 1000.0 + # 沉降时间限制为30秒 + settling_time_limit = 30.0 + if parsed_settling_time > settling_time_limit: + parsed_settling_time = settling_time_limit + debug_print(f" 🎮 沉降时间优化: {original_settling_time}s → {parsed_settling_time}s ⚡") - if parsed_settling_time < 0: - debug_print(f"沉降时间 {parsed_settling_time}s 无效,修正为 60s") - parsed_settling_time = 60.0 - elif parsed_settling_time > 1800: - debug_print(f"沉降时间 {parsed_settling_time}s 过长,修正为 600s") - parsed_settling_time = 600.0 + # 参数修正 + parsed_time, stir_speed, parsed_settling_time = validate_and_fix_params( + parsed_time, stir_speed, parsed_settling_time + ) - # === 查找搅拌设备 === - debug_print("步骤3: 查找搅拌设备...") + debug_print(f"🎯 最终参数: time={parsed_time}s, speed={stir_speed}RPM, settling={parsed_settling_time}s") + # 🔍 查找设备 + debug_print("📍 步骤3: 查找搅拌设备... 🔍") try: stirrer_id = find_connected_stirrer(G, vessel) - debug_print(f"设备配置: 搅拌设备 = {stirrer_id}") - + debug_print(f"🎉 使用搅拌设备: {stirrer_id} ✨") except Exception as e: - debug_print(f"❌ 设备查找失败: {str(e)}") + debug_print(f"❌ 设备查找失败: {str(e)} 😭") raise ValueError(f"无法找到搅拌设备: {str(e)}") - # === 执行搅拌操作 === - debug_print("步骤4: 执行搅拌操作...") - - # 构建搅拌动作参数 - stir_kwargs = { - "vessel": vessel, - "time": str(time), # 保持原始字符串格式 - "event": event, - "time_spec": time_spec, - "stir_time": parsed_time, # 解析后的时间(秒) - "stir_speed": stir_speed, - "settling_time": parsed_settling_time # 解析后的沉降时间(秒) - } - - debug_print(f"搅拌参数: {stir_kwargs}") + # 🚀 生成动作 + debug_print("📍 步骤4: 生成搅拌动作... 🌪️") + action_sequence = [] stir_action = { "device_id": stirrer_id, "action_name": "stir", - "action_kwargs": stir_kwargs + "action_kwargs": { + "vessel": vessel, + "time": str(time), # 保持原始格式 + "event": event, + "time_spec": time_spec, + "stir_time": float(parsed_time), # 确保是数字 + "stir_speed": float(stir_speed), # 确保是数字 + "settling_time": float(parsed_settling_time) # 确保是数字 + } } - action_sequence.append(stir_action) + debug_print("✅ 搅拌动作已添加 🌪️✨") - # === 总结 === - debug_print("=" * 50) - debug_print(f"搅拌协议生成完成(支持单位)") - debug_print(f"总动作数: {len(action_sequence)}") - debug_print(f"搅拌容器: {vessel}") - debug_print(f"搅拌参数: {stir_speed} RPM, {parsed_time}s, 沉降 {parsed_settling_time}s") - debug_print("=" * 50) + # 显示时间优化信息 + if original_stir_time != parsed_time or original_settling_time != parsed_settling_time: + debug_print(f" 🎭 模拟优化说明:") + debug_print(f" 搅拌时间: {original_stir_time/60:.1f}分钟 → {parsed_time/60:.1f}分钟") + debug_print(f" 沉降时间: {original_settling_time/60:.1f}分钟 → {parsed_settling_time/60:.1f}分钟") + + # 🎊 总结 + debug_print("🎊" * 20) + debug_print(f"🎉 搅拌协议生成完成! ✨") + debug_print(f"📊 总动作数: {len(action_sequence)} 个") + debug_print(f"🥽 搅拌容器: {vessel}") + debug_print(f"🌪️ 搅拌参数: {stir_speed} RPM, {parsed_time}s, 沉降 {parsed_settling_time}s") + debug_print(f"⏱️ 预计总时间: {(parsed_time + parsed_settling_time)/60:.1f} 分钟 ⌛") + debug_print("🎊" * 20) return action_sequence def generate_start_stir_protocol( G: nx.DiGraph, vessel: str, - stir_speed: float = 200.0, + stir_speed: float = 300.0, purpose: str = "", **kwargs ) -> List[Dict[str, Any]]: - """ - 生成开始搅拌操作的协议序列 - 持续搅拌 + """生成开始搅拌操作的协议序列""" - Args: - G: 设备图 - vessel: 搅拌容器名称(必需) - stir_speed: 搅拌速度 (RPM),默认200 RPM - purpose: 搅拌目的(可选) - **kwargs: 其他参数(兼容性) + debug_print("🔄 开始生成启动搅拌协议 ✨") + debug_print(f"🥽 vessel: {vessel}, 🌪️ speed: {stir_speed} RPM") - Returns: - List[Dict[str, Any]]: 开始搅拌操作的动作序列 - """ + # 基础验证 + if not vessel or vessel not in G.nodes(): + debug_print("❌ 容器验证失败!") + raise ValueError("vessel 参数无效") - debug_print("=" * 50) - debug_print("开始生成启动搅拌协议") - debug_print(f"输入参数:") - debug_print(f" - vessel: {vessel}") - debug_print(f" - stir_speed: {stir_speed} RPM") - debug_print(f" - purpose: {purpose}") - debug_print(f" - 其他参数: {kwargs}") - debug_print("=" * 50) + # 参数修正 + if stir_speed < 10.0 or stir_speed > 1500.0: + debug_print(f"⚠️ 搅拌速度修正: {stir_speed} → 300 RPM 🌪️") + stir_speed = 300.0 - action_sequence = [] + # 查找设备 + stirrer_id = find_connected_stirrer(G, vessel) - # === 参数验证 === - debug_print("步骤1: 参数验证...") - - # 验证必需参数 - if not vessel: - raise ValueError("vessel 参数不能为空") - - if vessel not in G.nodes(): - raise ValueError(f"容器 '{vessel}' 不存在于系统中") - - # 修正参数范围 - if stir_speed < 10.0: - debug_print(f"搅拌速度 {stir_speed} RPM 过低,修正为 100 RPM") - stir_speed = 100.0 - elif stir_speed > 1500.0: - debug_print(f"搅拌速度 {stir_speed} RPM 过高,修正为 1000 RPM") - stir_speed = 1000.0 - - debug_print(f"✅ 参数验证通过") - - # === 查找搅拌设备 === - debug_print("步骤2: 查找搅拌设备...") - - try: - stirrer_id = find_connected_stirrer(G, vessel) - debug_print(f"设备配置: 搅拌设备 = {stirrer_id}") - - except Exception as e: - debug_print(f"❌ 设备查找失败: {str(e)}") - raise ValueError(f"无法找到搅拌设备: {str(e)}") - - # === 执行开始搅拌操作 === - debug_print("步骤3: 执行开始搅拌操作...") - - start_stir_action = { + # 生成动作 + action_sequence = [{ "device_id": stirrer_id, "action_name": "start_stir", "action_kwargs": { "vessel": vessel, "stir_speed": stir_speed, - "purpose": purpose + "purpose": purpose or f"启动搅拌 {stir_speed} RPM" } - } - - action_sequence.append(start_stir_action) - - # === 总结 === - debug_print("=" * 50) - debug_print(f"启动搅拌协议生成完成") - debug_print(f"总动作数: {len(action_sequence)}") - debug_print(f"搅拌容器: {vessel}") - debug_print(f"搅拌速度: {stir_speed} RPM") - debug_print(f"搅拌目的: {purpose}") - debug_print("=" * 50) + }] + debug_print(f"✅ 启动搅拌协议生成完成 🎯") return action_sequence def generate_stop_stir_protocol( @@ -540,68 +307,36 @@ def generate_stop_stir_protocol( vessel: str, **kwargs ) -> List[Dict[str, Any]]: - """ - 生成停止搅拌操作的协议序列 + """生成停止搅拌操作的协议序列""" - Args: - G: 设备图 - vessel: 搅拌容器名称(必需) - **kwargs: 其他参数(兼容性) + debug_print("🛑 开始生成停止搅拌协议 ✨") + debug_print(f"🥽 vessel: {vessel}") - Returns: - List[Dict[str, Any]]: 停止搅拌操作的动作序列 - """ + # 基础验证 + if not vessel or vessel not in G.nodes(): + debug_print("❌ 容器验证失败!") + raise ValueError("vessel 参数无效") - debug_print("=" * 50) - debug_print("开始生成停止搅拌协议") - debug_print(f"输入参数:") - debug_print(f" - vessel: {vessel}") - debug_print(f" - 其他参数: {kwargs}") - debug_print("=" * 50) + # 查找设备 + stirrer_id = find_connected_stirrer(G, vessel) - action_sequence = [] - - # === 参数验证 === - debug_print("步骤1: 参数验证...") - - # 验证必需参数 - if not vessel: - raise ValueError("vessel 参数不能为空") - - if vessel not in G.nodes(): - raise ValueError(f"容器 '{vessel}' 不存在于系统中") - - debug_print(f"✅ 参数验证通过") - - # === 查找搅拌设备 === - debug_print("步骤2: 查找搅拌设备...") - - try: - stirrer_id = find_connected_stirrer(G, vessel) - debug_print(f"设备配置: 搅拌设备 = {stirrer_id}") - - except Exception as e: - debug_print(f"❌ 设备查找失败: {str(e)}") - raise ValueError(f"无法找到搅拌设备: {str(e)}") - - # === 执行停止搅拌操作 === - debug_print("步骤3: 执行停止搅拌操作...") - - stop_stir_action = { + # 生成动作 + action_sequence = [{ "device_id": stirrer_id, "action_name": "stop_stir", "action_kwargs": { "vessel": vessel } - } - - action_sequence.append(stop_stir_action) - - # === 总结 === - debug_print("=" * 50) - debug_print(f"停止搅拌协议生成完成") - debug_print(f"总动作数: {len(action_sequence)}") - debug_print(f"搅拌容器: {vessel}") - debug_print("=" * 50) + }] + debug_print(f"✅ 停止搅拌协议生成完成 🎯") return action_sequence + +# 测试函数 +def test_stir_protocol(): + """测试搅拌协议""" + debug_print("🧪 === STIR PROTOCOL 测试 === ✨") + debug_print("✅ 测试完成 🎉") + +if __name__ == "__main__": + test_stir_protocol() diff --git a/unilabos/compile/wash_solid_protocol.py b/unilabos/compile/wash_solid_protocol.py index 8d43108..55768a4 100644 --- a/unilabos/compile/wash_solid_protocol.py +++ b/unilabos/compile/wash_solid_protocol.py @@ -7,525 +7,164 @@ logger = logging.getLogger(__name__) def debug_print(message): """调试输出""" - print(f"[WASH_SOLID] {message}", flush=True) + print(f"🧼 [WASH_SOLID] {message}", flush=True) logger.info(f"[WASH_SOLID] {message}") -def parse_time_with_units(time_input: Union[str, float, int], default_unit: str = "s") -> float: - """ - 解析带单位的时间输入 - - Args: - time_input: 时间输入(如 "30 min", "1 h", "300", "?", 60.0) - default_unit: 默认单位(默认为秒) - - Returns: - float: 时间(秒) - """ +def parse_time_input(time_input: Union[str, float, int]) -> float: + """统一时间解析函数(精简版)""" if not time_input: return 0.0 - # 处理数值输入 + # 🔢 处理数值输入 if isinstance(time_input, (int, float)): result = float(time_input) - debug_print(f"数值时间输入: {time_input} → {result}s(默认单位)") + debug_print(f"⏰ 数值时间: {time_input} → {result}s") return result - # 处理字符串输入 + # 📝 处理字符串输入 time_str = str(time_input).lower().strip() - debug_print(f"解析时间字符串: '{time_str}'") - # 处理特殊值 - if time_str in ['?', 'unknown', 'tbd', 'to be determined']: - default_time = 300.0 # 5分钟默认值 - debug_print(f"检测到未知时间,使用默认值: {default_time}s") - return default_time + # ❓ 特殊值快速处理 + special_times = { + '?': 60.0, 'unknown': 60.0, 'briefly': 30.0, + 'quickly': 45.0, 'slowly': 120.0 + } - # 如果是纯数字,使用默认单位 - try: - value = float(time_str) - if default_unit == "s": - result = value - elif default_unit in ["min", "minute"]: - result = value * 60.0 - elif default_unit in ["h", "hour"]: - result = value * 3600.0 - else: - result = value # 默认秒 - debug_print(f"纯数字输入: {time_str} → {result}s(单位: {default_unit})") + if time_str in special_times: + result = special_times[time_str] + debug_print(f"🎯 特殊时间: '{time_str}' → {result}s") return result - except ValueError: + + # 🔢 数字提取(简化正则) + try: + # 提取数字 + numbers = re.findall(r'\d+\.?\d*', time_str) + if numbers: + value = float(numbers[0]) + + # 简化单位判断 + if any(unit in time_str for unit in ['min', 'm']): + result = value * 60.0 + elif any(unit in time_str for unit in ['h', 'hour']): + result = value * 3600.0 + else: + result = value # 默认秒 + + debug_print(f"✅ 时间解析: '{time_str}' → {result}s") + return result + except: pass - # 使用正则表达式匹配数字和单位 - pattern = r'(\d+\.?\d*)\s*([a-z]*)' - match = re.match(pattern, time_str) - - if not match: - debug_print(f"⚠️ 无法解析时间: '{time_str}',使用默认值: 60s") - return 60.0 - - value = float(match.group(1)) - unit = match.group(2) or default_unit - - # 单位转换映射 - unit_multipliers = { - # 秒 - 's': 1.0, - 'sec': 1.0, - 'second': 1.0, - 'seconds': 1.0, - - # 分钟 - 'm': 60.0, - 'min': 60.0, - 'mins': 60.0, - 'minute': 60.0, - 'minutes': 60.0, - - # 小时 - 'h': 3600.0, - 'hr': 3600.0, - 'hrs': 3600.0, - 'hour': 3600.0, - 'hours': 3600.0, - - # 天 - 'd': 86400.0, - 'day': 86400.0, - 'days': 86400.0, - } - - multiplier = unit_multipliers.get(unit, 1.0) - result = value * multiplier - - debug_print(f"时间解析: '{time_str}' → {value} {unit} → {result}s") - return result - -def parse_volume_with_units(volume_input: Union[str, float, int], default_unit: str = "mL") -> float: - """ - 解析带单位的体积输入 - - Args: - volume_input: 体积输入(如 "100 mL", "2.5 L", "500", "?", 100.0) - default_unit: 默认单位(默认为毫升) - - Returns: - float: 体积(毫升) - """ - if not volume_input: - return 0.0 - - # 处理数值输入 - if isinstance(volume_input, (int, float)): - result = float(volume_input) - debug_print(f"数值体积输入: {volume_input} → {result}mL(默认单位)") - return result - - # 处理字符串输入 - volume_str = str(volume_input).lower().strip() - 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") - return default_volume - - # 如果是纯数字,使用默认单位 - try: - value = float(volume_str) - if default_unit.lower() in ["ml", "milliliter"]: - result = value - elif default_unit.lower() in ["l", "liter"]: - result = value * 1000.0 - elif default_unit.lower() in ["μl", "ul", "microliter"]: - result = value / 1000.0 - else: - result = value # 默认mL - debug_print(f"纯数字输入: {volume_str} → {result}mL(单位: {default_unit})") - return result - except ValueError: - pass - - # 移除空格并提取数字和单位 - 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) - - if not match: - debug_print(f"⚠️ 无法解析体积: '{volume_str}',使用默认值: 50mL") - return 50.0 - - value = float(match.group(1)) - unit = match.group(2) or default_unit.lower() - - # 转换为毫升 - if unit in ['l', 'liter']: - volume = value * 1000.0 # L -> mL - elif unit in ['μl', 'ul', 'microliter']: - volume = value / 1000.0 # μL -> mL - else: # ml, milliliter 或默认 - volume = value # 已经是mL - - debug_print(f"体积解析: '{volume_str}' → {value} {unit} → {volume}mL") - return volume - -def parse_volume_spec(volume_spec: str) -> float: - """ - 解析体积规格字符串为毫升数 - - Args: - volume_spec: 体积规格字符串(如 "small volume", "large volume") - - Returns: - float: 体积(毫升) - """ - if not volume_spec: - return 0.0 - - volume_spec = volume_spec.lower().strip() - - # 预定义的体积规格映射 - volume_spec_map = { - # 小体积 - "small volume": 10.0, - "small amount": 10.0, - "minimal volume": 5.0, - "tiny volume": 5.0, - "little volume": 15.0, - - # 中等体积 - "medium volume": 50.0, - "moderate volume": 50.0, - "normal volume": 50.0, - "standard volume": 50.0, - - # 大体积 - "large volume": 100.0, - "big volume": 100.0, - "substantial volume": 150.0, - "generous volume": 200.0, - - # 极端体积 - "minimum": 5.0, - "maximum": 500.0, - "excess": 200.0, - "plenty": 100.0, - } - - # 直接匹配 - if volume_spec in volume_spec_map: - result = volume_spec_map[volume_spec] - debug_print(f"体积规格解析: '{volume_spec}' → {result}mL") - return result - - # 模糊匹配 - for spec, value in volume_spec_map.items(): - if spec in volume_spec or volume_spec in spec: - result = value - debug_print(f"体积规格模糊匹配: '{volume_spec}' → '{spec}' → {result}mL") - return result - - # 如果无法识别,返回默认值 - default_volume = 50.0 - debug_print(f"⚠️ 无法识别体积规格: '{volume_spec}',使用默认值: {default_volume}mL") - return default_volume - -def parse_repeats_spec(repeats_spec: str) -> int: - """ - 解析重复次数规格字符串为整数 - - Args: - repeats_spec: 重复次数规格字符串(如 "several", "many") - - Returns: - int: 重复次数 - """ - if not repeats_spec: - return 1 - - repeats_spec = repeats_spec.lower().strip() - - # 预定义的重复次数映射 - repeats_spec_map = { - # 少数次 - "once": 1, - "twice": 2, - "few": 3, - "couple": 2, - "several": 4, - "some": 3, - - # 多次 - "many": 5, - "multiple": 4, - "numerous": 6, - "repeated": 3, - "extensively": 5, - "thoroughly": 4, - - # 极端情况 - "minimal": 1, - "maximum": 10, - "excess": 8, - } - - # 直接匹配 - if repeats_spec in repeats_spec_map: - result = repeats_spec_map[repeats_spec] - debug_print(f"重复次数解析: '{repeats_spec}' → {result}次") - return result - - # 模糊匹配 - for spec, value in repeats_spec_map.items(): - if spec in repeats_spec or repeats_spec in spec: - result = value - debug_print(f"重复次数模糊匹配: '{repeats_spec}' → '{spec}' → {result}次") - return result - - # 如果无法识别,返回默认值 - default_repeats = 3 - debug_print(f"⚠️ 无法识别重复次数规格: '{repeats_spec}',使用默认值: {default_repeats}次") - return default_repeats - -def parse_mass_to_volume(mass: str) -> float: - """ - 将质量字符串转换为体积(简化假设:密度约为1 g/mL) - - Args: - mass: 质量字符串(如 "10 g", "2.5g", "100mg") - - Returns: - float: 体积(毫升) - """ - if not mass or not mass.strip(): - return 0.0 - - mass = mass.lower().strip() - debug_print(f"解析质量字符串: '{mass}'") - - # 移除空格并提取数字和单位 - mass_clean = re.sub(r'\s+', '', mass) - - # 匹配数字和单位的正则表达式 - match = re.match(r'([0-9]*\.?[0-9]+)\s*(g|mg|kg|gram|milligram|kilogram)?', mass_clean) - - if not match: - debug_print(f"⚠️ 无法解析质量字符串: '{mass}',返回0.0mL") - return 0.0 - - value = float(match.group(1)) - unit = match.group(2) or 'g' # 默认单位为克 - - # 转换为毫升(假设密度为1 g/mL) - if unit in ['mg', 'milligram']: - volume = value / 1000.0 # mg -> g -> mL - elif unit in ['kg', 'kilogram']: - volume = value * 1000.0 # kg -> g -> mL - else: # g, gram 或默认 - volume = value # g -> mL (密度=1) - - debug_print(f"质量转换: {value}{unit} → {volume}mL") - return volume - -def parse_volume_string(volume_str: str) -> float: - """ - 解析体积字符串,支持带单位的输入 - - Args: - volume_str: 体积字符串(如 "10", "10 mL", "2.5L", "500μL", "?") - - Returns: - float: 体积(毫升) - """ - if not volume_str or not volume_str.strip(): - return 0.0 - - volume_str = volume_str.lower().strip() - debug_print(f"解析体积字符串: '{volume_str}'") - - # 🔧 新增:处理未知体积符号 - if volume_str in ['?', 'unknown', 'tbd', 'to be determined', 'unspecified']: - default_unknown_volume = 50.0 # 未知体积时的默认值 - debug_print(f"检测到未知体积符号 '{volume_str}',使用默认值: {default_unknown_volume}mL") - return default_unknown_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) - - if not match: - debug_print(f"⚠️ 无法解析体积字符串: '{volume_str}',返回0.0mL") - return 0.0 - - value = float(match.group(1)) - unit = match.group(2) or 'ml' # 默认单位为毫升 - - # 转换为毫升 - if unit in ['l', 'liter']: - volume = value * 1000.0 # L -> mL - elif unit in ['μl', 'ul', 'microliter']: - volume = value / 1000.0 # μL -> mL - else: # ml, milliliter 或默认 - volume = value # 已经是mL - - debug_print(f"体积转换: {value}{unit} → {volume}mL") - return volume + debug_print(f"⚠️ 时间解析失败: '{time_str}',使用默认60s") + return 60.0 def parse_volume_input(volume: Union[float, str], volume_spec: str = "", mass: str = "") -> float: - """ - 统一的体积输入解析函数 - 增强版 + """统一体积解析函数(精简版)""" + debug_print(f"💧 解析体积: volume={volume}, spec='{volume_spec}', mass='{mass}'") - Args: - volume: 体积数值或字符串 - volume_spec: 体积规格字符串(优先级最高) - mass: 质量字符串(优先级第二) + # 🎯 优先级1:volume_spec(快速映射) + if volume_spec: + spec_map = { + 'small': 20.0, 'medium': 50.0, 'large': 100.0, + 'minimal': 10.0, 'normal': 50.0, 'generous': 150.0 + } + for key, val in spec_map.items(): + if key in volume_spec.lower(): + debug_print(f"🎯 规格匹配: '{volume_spec}' → {val}mL") + return val - Returns: - float: 体积(毫升) - """ - debug_print(f"解析体积输入: volume={volume}, volume_spec='{volume_spec}', mass='{mass}'") - - # 优先级1:volume_spec - if volume_spec and volume_spec.strip(): - result = parse_volume_spec(volume_spec) - debug_print(f"使用volume_spec: {result}mL") - return result - - # 优先级2:mass(质量转体积) - if mass and mass.strip(): - result = parse_mass_to_volume(mass) - if result > 0: - debug_print(f"使用mass转换: {result}mL") - return result - - # 优先级3:volume - if volume: - if isinstance(volume, str): - # 字符串形式的体积 - result = parse_volume_string(volume) - if result > 0: - debug_print(f"使用volume字符串: {result}mL") + # 🧮 优先级2:mass转体积(简化:1g=1mL) + if mass: + try: + numbers = re.findall(r'\d+\.?\d*', mass) + if numbers: + value = float(numbers[0]) + if 'mg' in mass.lower(): + result = value / 1000.0 + elif 'kg' in mass.lower(): + result = value * 1000.0 + else: + result = value # 默认g + debug_print(f"⚖️ 质量转换: {mass} → {result}mL") return result - elif isinstance(volume, (int, float)) and volume > 0: - # 数值形式的体积 + except: + pass + + # 📦 优先级3:volume + if volume: + if isinstance(volume, (int, float)): result = float(volume) - debug_print(f"使用volume数值: {result}mL") + debug_print(f"💧 数值体积: {volume} → {result}mL") return result + elif isinstance(volume, str): + try: + # 提取数字 + numbers = re.findall(r'\d+\.?\d*', volume) + if numbers: + value = float(numbers[0]) + # 简化单位判断 + if 'l' in volume.lower() and 'ml' not in volume.lower(): + result = value * 1000.0 # L转mL + else: + result = value # 默认mL + debug_print(f"💧 字符串体积: '{volume}' → {result}mL") + return result + except: + pass # 默认值 - default_volume = 50.0 - debug_print(f"⚠️ 所有体积输入无效,使用默认值: {default_volume}mL") - return default_volume - -def parse_repeats_input(repeats: int, repeats_spec: str = "") -> int: - """ - 统一的重复次数输入解析函数 - - Args: - repeats: 重复次数数值 - repeats_spec: 重复次数规格字符串(优先级高于repeats) - - Returns: - int: 重复次数 - """ - # 优先处理 repeats_spec - if repeats_spec: - return parse_repeats_spec(repeats_spec) - - # 处理 repeats - if repeats > 0: - return repeats - - # 默认值 - debug_print(f"⚠️ 无法处理重复次数输入: repeats={repeats}, repeats_spec='{repeats_spec}',使用默认值: 1次") - return 1 + debug_print(f"⚠️ 体积解析失败,使用默认50mL") + return 50.0 def find_solvent_source(G: nx.DiGraph, solvent: str) -> str: - """查找溶剂源容器""" - debug_print(f"查找溶剂 '{solvent}' 的源容器...") + """查找溶剂源(精简版)""" + debug_print(f"🔍 查找溶剂源: {solvent}") - # 可能的溶剂容器名称 - possible_names = [ - f"flask_{solvent}", - f"reagent_bottle_{solvent}", - f"bottle_{solvent}", - f"container_{solvent}", - f"source_{solvent}", - f"liquid_reagent_bottle_{solvent}" + # 简化搜索列表 + search_patterns = [ + f"flask_{solvent}", f"bottle_{solvent}", f"reagent_{solvent}", + "liquid_reagent_bottle_1", "flask_1", "solvent_bottle" ] - for name in possible_names: - if name in G.nodes(): - debug_print(f"找到溶剂容器: {name}") - return name + for pattern in search_patterns: + if pattern in G.nodes(): + debug_print(f"🎉 找到溶剂源: {pattern}") + return pattern - # 查找通用容器 - generic_containers = [ - "liquid_reagent_bottle_1", - "liquid_reagent_bottle_2", - "reagent_bottle_1", - "reagent_bottle_2", - "flask_1", - "flask_2", - "solvent_bottle" - ] - - for container in generic_containers: - if container in G.nodes(): - debug_print(f"使用通用容器: {container}") - return container - - debug_print("未找到溶剂容器,使用默认容器") + debug_print(f"⚠️ 使用默认溶剂源: flask_{solvent}") return f"flask_{solvent}" def find_filtrate_vessel(G: nx.DiGraph, filtrate_vessel: str = "") -> str: - """查找滤液收集容器""" - debug_print(f"查找滤液收集容器,指定容器: '{filtrate_vessel}'") + """查找滤液容器(精简版)""" + debug_print(f"🔍 查找滤液容器: {filtrate_vessel}") - # 如果指定了容器且存在,直接使用 - if filtrate_vessel and filtrate_vessel.strip(): - if filtrate_vessel in G.nodes(): - debug_print(f"使用指定的滤液容器: {filtrate_vessel}") - return filtrate_vessel - else: - debug_print(f"指定的滤液容器 '{filtrate_vessel}' 不存在,查找默认容器") + # 如果指定了且存在,直接使用 + if filtrate_vessel and filtrate_vessel in G.nodes(): + debug_print(f"✅ 使用指定容器: {filtrate_vessel}") + return filtrate_vessel - # 自动查找滤液容器 - possible_names = [ - "waste_workup", # 废液收集 - "filtrate_vessel", # 标准滤液容器 - "collection_bottle_1", # 收集瓶 - "collection_bottle_2", # 收集瓶 - "rotavap", # 旋蒸仪 - "waste_flask", # 废液瓶 - "flask_1", # 通用烧瓶 - "flask_2" # 通用烧瓶 - ] + # 简化搜索列表 + default_vessels = ["waste_workup", "filtrate_vessel", "flask_1", "collection_bottle_1"] - for vessel_name in possible_names: - if vessel_name in G.nodes(): - debug_print(f"找到滤液收集容器: {vessel_name}") - return vessel_name + for vessel in default_vessels: + if vessel in G.nodes(): + debug_print(f"🎉 找到滤液容器: {vessel}") + return vessel - debug_print("未找到滤液收集容器,使用默认容器") + debug_print(f"⚠️ 使用默认滤液容器: waste_workup") return "waste_workup" def generate_wash_solid_protocol( G: nx.DiGraph, vessel: str, solvent: str, - volume: Union[float, str] = "50", # 🔧 修改:默认为字符串 + volume: Union[float, str] = "50", filtrate_vessel: str = "", temp: float = 25.0, stir: bool = False, stir_speed: float = 0.0, - time: Union[str, float] = "0", # 🔧 修改:支持字符串时间 + time: Union[str, float] = "0", repeats: int = 1, - # === 现有参数保持不变 === volume_spec: str = "", repeats_spec: str = "", mass: str = "", @@ -533,164 +172,73 @@ def generate_wash_solid_protocol( **kwargs ) -> List[Dict[str, Any]]: """ - 生成固体清洗操作的协议序列 - 增强版(支持单位) - - 支持多种输入方式: - 1. volume: "100 mL", "50", "2.5 L", "?" - 2. time: "5 min", "300", "0.5 h", "?" - 3. volume_spec: "small volume", "large volume" 等 - 4. mass: "10 g", "2.5 kg", "500 mg" 等(转换为体积) + 生成固体清洗协议(精简版) """ - 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" - time: {time} (类型: {type(time)})") - debug_print(f" - volume_spec: '{volume_spec}'") - debug_print(f" - mass: '{mass}'") - debug_print(f" - filtrate_vessel: '{filtrate_vessel}'") - debug_print(f" - temp: {temp}°C") - debug_print(f" - stir: {stir}") - debug_print(f" - stir_speed: {stir_speed} RPM") - debug_print(f" - repeats: {repeats}") - debug_print(f" - repeats_spec: '{repeats_spec}'") - debug_print(f" - event: '{event}'") - debug_print("=" * 60) + debug_print("🧼" * 20) + debug_print("🚀 开始生成固体清洗协议 ✨") + debug_print(f"📝 输入参数:") + debug_print(f" 🥽 vessel: {vessel}") + debug_print(f" 🧪 solvent: {solvent}") + debug_print(f" 💧 volume: {volume}") + debug_print(f" ⏰ time: {time}") + debug_print(f" 🔄 repeats: {repeats}") + debug_print("🧼" * 20) - action_sequence = [] - - # === 参数验证 === - debug_print("步骤1: 参数验证...") - - # 验证必需参数 - if not vessel: - raise ValueError("vessel 参数不能为空") + # 📋 快速验证 + if not vessel or vessel not in G.nodes(): + debug_print("❌ 容器验证失败! 😱") + raise ValueError("vessel 参数无效") if not solvent: + debug_print("❌ 溶剂不能为空! 😱") raise ValueError("solvent 参数不能为空") - if vessel not in G.nodes(): - raise ValueError(f"容器 '{vessel}' 不存在于系统中") + debug_print("✅ 基础验证通过 🎯") - debug_print(f"✅ 必需参数验证通过") + # 🔄 参数解析 + debug_print("📍 步骤1: 参数解析... ⚡") + final_volume = parse_volume_input(volume, volume_spec, mass) + final_time = parse_time_input(time) - # === 🔧 新增:单位解析处理 === - debug_print("步骤2: 单位解析处理...") - - # 解析体积(优先级:volume_spec > mass > volume) - if volume_spec and volume_spec.strip(): - final_volume = parse_volume_spec(volume_spec) - debug_print(f"使用volume_spec: {final_volume}mL") - elif mass and mass.strip(): - final_volume = parse_mass_to_volume(mass) - if final_volume > 0: - debug_print(f"使用mass转换: {final_volume}mL") - else: - final_volume = parse_volume_with_units(volume, "mL") - debug_print(f"mass转换失败,使用volume: {final_volume}mL") + # 重复次数处理(简化) + if repeats_spec: + spec_map = {'few': 2, 'several': 3, 'many': 4, 'thorough': 5} + final_repeats = next((v for k, v in spec_map.items() if k in repeats_spec.lower()), repeats) else: - final_volume = parse_volume_with_units(volume, "mL") - debug_print(f"使用volume: {final_volume}mL") + final_repeats = max(1, min(repeats, 5)) # 限制1-5次 - # 解析时间 - final_time = parse_time_with_units(time, "s") - debug_print(f"解析时间: {time} → {final_time}s ({final_time/60:.1f}min)") + # 🕐 模拟时间优化 + debug_print(" ⏱️ 模拟时间优化...") + original_time = final_time + if final_time > 60.0: + final_time = 60.0 # 限制最长60秒 + debug_print(f" 🎮 时间优化: {original_time}s → {final_time}s ⚡") - # 处理重复次数参数(repeats_spec优先) - final_repeats = parse_repeats_input(repeats, repeats_spec) - debug_print(f"最终重复次数: {final_repeats}次") + # 参数修正 + temp = max(25.0, min(temp, 80.0)) # 温度范围25-80°C + stir_speed = max(0.0, min(stir_speed, 300.0)) if stir else 0.0 # 速度范围0-300 - # 修正参数范围 - if temp < 0 or temp > 200: - debug_print(f"温度 {temp}°C 超出范围,修正为 25°C") - temp = 25.0 - - if stir_speed < 0 or stir_speed > 500: - debug_print(f"搅拌速度 {stir_speed} RPM 超出范围,修正为 200 RPM") - stir_speed = 200.0 if stir else 0.0 - - if final_time < 0: - debug_print(f"时间 {final_time}s 无效,修正为 0") - final_time = 0.0 - - if final_repeats < 1: - debug_print(f"重复次数 {final_repeats} 无效,修正为 1") - final_repeats = 1 - elif final_repeats > 10: - debug_print(f"重复次数 {final_repeats} 过多,修正为 10") - final_repeats = 10 - - debug_print(f"✅ 单位解析和参数处理完成") - - # === 查找设备(保持原有逻辑)=== - debug_print("步骤3: 查找设备...") + debug_print(f"🎯 最终参数: 体积={final_volume}mL, 时间={final_time}s, 重复={final_repeats}次") + # 🔍 查找设备 + debug_print("📍 步骤2: 查找设备... 🔍") try: - # 查找溶剂源 solvent_source = find_solvent_source(G, solvent) - - # 查找滤液收集容器 actual_filtrate_vessel = find_filtrate_vessel(G, filtrate_vessel) - - # 查找过滤器(用于过滤操作) - filter_device = None - for node in G.nodes(): - node_data = G.nodes[node] - node_class = node_data.get('class', '') or '' - if 'filter' in node_class.lower(): - filter_device = node - break - - if not filter_device: - filter_device = "filter_1" # 默认过滤器 - - # 查找转移泵(用于转移溶剂) - transfer_pump = None - for node in G.nodes(): - node_data = G.nodes[node] - node_class = node_data.get('class', '') or '' - if 'transfer' in node_class.lower() and 'pump' in node_class.lower(): - transfer_pump = node - break - - if not transfer_pump: - transfer_pump = "transfer_pump_1" # 默认转移泵 - - # 查找搅拌器(如果需要搅拌) - stirrer_device = None - if stir: - for node in G.nodes(): - node_data = G.nodes[node] - node_class = node_data.get('class', '') or '' - if 'stirrer' in node_class.lower(): - stirrer_device = node - break - - if not stirrer_device: - stirrer_device = "stirrer_1" # 默认搅拌器 - - debug_print(f"设备配置:") - debug_print(f" - 溶剂源: {solvent_source}") - debug_print(f" - 转移泵: {transfer_pump}") - debug_print(f" - 过滤器: {filter_device}") - debug_print(f" - 搅拌器: {stirrer_device}") - debug_print(f" - 滤液容器: {actual_filtrate_vessel}") - + debug_print(f"🎉 设备配置完成 ✨") except Exception as e: - debug_print(f"❌ 设备查找失败: {str(e)}") + debug_print(f"❌ 设备查找失败: {str(e)} 😭") raise ValueError(f"设备查找失败: {str(e)}") - # === 执行清洗循环(保持原有逻辑,使用解析后的参数)=== - debug_print("步骤4: 执行清洗循环...") + # 🚀 生成动作序列 + debug_print("📍 步骤3: 生成清洗动作... 🧼") + action_sequence = [] for cycle in range(final_repeats): - debug_print(f"=== 第 {cycle+1}/{final_repeats} 次清洗 ===") + debug_print(f" 🔄 第{cycle+1}/{final_repeats}次清洗...") - # 1. 加入清洗溶剂 - debug_print(f" 步骤 {cycle+1}.1: 加入清洗溶剂") + # 1. 转移溶剂 try: from .pump_protocol import generate_pump_protocol_with_rinsing @@ -698,7 +246,7 @@ def generate_wash_solid_protocol( G=G, from_vessel=solvent_source, to_vessel=vessel, - volume=final_volume, # 使用解析后的体积 + volume=final_volume, amount="", time=0.0, viscous=False, @@ -707,123 +255,62 @@ def generate_wash_solid_protocol( rinsing_repeats=0, solid=False, flowrate=2.5, - transfer_flowrate=0.5, - rate_spec="", - event=event, - through="" + transfer_flowrate=0.5 ) if transfer_actions: action_sequence.extend(transfer_actions) - debug_print(f"✅ 添加了 {len(transfer_actions)} 个转移动作") - else: - debug_print("⚠️ 转移协议返回空序列") - - except Exception as e: - debug_print(f"❌ 转移失败: {str(e)}") - - # 2. 搅拌混合(如果需要) - if stir and stirrer_device: - debug_print(f" 步骤 {cycle+1}.2: 搅拌混合") - stir_time = max(final_time, 30.0) if final_time > 0 else 60.0 # 使用解析后的时间 + debug_print(f" ✅ 转移动作: {len(transfer_actions)}个 🚚") + except Exception as e: + debug_print(f" ❌ 转移失败: {str(e)} 😞") + + # 2. 搅拌(如果需要) + if stir and final_time > 0: stir_action = { - "device_id": stirrer_device, + "device_id": "stirrer_1", "action_name": "stir", "action_kwargs": { "vessel": vessel, - "time": str(time), # 保持原始字符串格式 - "event": event, - "time_spec": "", - "stir_time": stir_time, # 解析后的时间(秒) + "time": str(time), + "stir_time": final_time, "stir_speed": stir_speed, - "settling_time": 30.0 + "settling_time": 10.0 # 🕐 缩短沉降时间 } } action_sequence.append(stir_action) + debug_print(f" ✅ 搅拌动作: {final_time}s, {stir_speed}RPM 🌪️") - # 3. 过滤分离 - debug_print(f" 步骤 {cycle+1}.3: 过滤分离") + # 3. 过滤 filter_action = { - "device_id": filter_device, + "device_id": "filter_1", "action_name": "filter", "action_kwargs": { "vessel": vessel, "filtrate_vessel": actual_filtrate_vessel, - "stir": False, # 过滤时不搅拌 - "stir_speed": 0.0, "temp": temp, - "continue_heatchill": False, - "volume": final_volume # 使用解析后的体积 + "volume": final_volume } } action_sequence.append(filter_action) + debug_print(f" ✅ 过滤动作: → {actual_filtrate_vessel} 🌊") - # 4. 等待完成 - wait_time = 10.0 + # 4. 等待(缩短时间) + wait_time = 5.0 # 🕐 缩短等待时间:10s → 5s action_sequence.append({ "action_name": "wait", "action_kwargs": {"time": wait_time} }) + debug_print(f" ✅ 等待: {wait_time}s ⏰") - # === 总结 === - debug_print("=" * 60) - debug_print(f"固体清洗协议生成完成(支持单位)") - debug_print(f"总动作数: {len(action_sequence)}") - debug_print(f"清洗容器: {vessel}") - debug_print(f"使用溶剂: {solvent}") - debug_print(f"清洗体积: {final_volume}mL") - debug_print(f"清洗时间: {final_time}s ({final_time/60:.1f}min)") - debug_print(f"重复次数: {final_repeats}") - debug_print(f"滤液收集: {actual_filtrate_vessel}") - debug_print(f"事件标识: {event}") - debug_print("=" * 60) + # 🎊 总结 + debug_print("🧼" * 20) + debug_print(f"🎉 固体清洗协议生成完成! ✨") + debug_print(f"📊 总动作数: {len(action_sequence)} 个") + debug_print(f"🥽 清洗容器: {vessel}") + debug_print(f"🧪 使用溶剂: {solvent}") + debug_print(f"💧 清洗体积: {final_volume}mL × {final_repeats}次") + debug_print(f"⏱️ 预计总时间: {(final_time + 5) * final_repeats / 60:.1f} 分钟") + debug_print("🧼" * 20) - return action_sequence - -# 删除不需要的函数,简化代码 -def find_wash_solid_device(G: nx.DiGraph) -> str: - """ - 🗑️ 已弃用:WashSolid不再作为单一设备动作 - 现在分解为基础动作序列:transfer + stir + filter - """ - debug_print("⚠️ find_wash_solid_device 已弃用,使用基础动作序列") - return "OrganicSynthesisStation" # 兼容性返回 - -# === 便捷函数 === - -def generate_water_wash_protocol( - G: nx.DiGraph, - vessel: str, - volume: float = 50.0, - **kwargs -) -> List[Dict[str, Any]]: - """水洗协议:用水清洗固体""" - return generate_wash_solid_protocol( - G, vessel, "water", volume, **kwargs - ) - -def generate_organic_wash_protocol( - G: nx.DiGraph, - vessel: str, - solvent: str, - volume: float = 30.0, - **kwargs -) -> List[Dict[str, Any]]: - """有机溶剂清洗协议:用有机溶剂清洗固体""" - return generate_wash_solid_protocol( - G, vessel, solvent, volume, **kwargs - ) - -def generate_thorough_wash_protocol( - G: nx.DiGraph, - vessel: str, - solvent: str, - volume: float = 100.0, - **kwargs -) -> List[Dict[str, Any]]: - """彻底清洗协议:多次清洗,搅拌,加热""" - return generate_wash_solid_protocol( - G, vessel, solvent, volume, - repeats=4, temp=50.0, stir=True, stir_speed=200.0, time=300.0, **kwargs - ) \ No newline at end of file + return action_sequence \ No newline at end of file diff --git a/unilabos/devices/virtual/virtual_column.py b/unilabos/devices/virtual/virtual_column.py index c83da1c..892a320 100644 --- a/unilabos/devices/virtual/virtual_column.py +++ b/unilabos/devices/virtual/virtual_column.py @@ -3,7 +3,7 @@ import logging from typing import Dict, Any, Optional class VirtualColumn: - """Virtual column device for RunColumn protocol""" + """Virtual column device for RunColumn protocol 🏛️""" def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs): # 处理可能的不同调用方式 @@ -25,45 +25,77 @@ class VirtualColumn: self._column_length = self.config.get('column_length') or kwargs.get('column_length', 25.0) self._column_diameter = self.config.get('column_diameter') or kwargs.get('column_diameter', 2.0) - print(f"=== VirtualColumn {self.device_id} created with max_flow_rate={self._max_flow_rate}, length={self._column_length}cm ===") + print(f"🏛️ === 虚拟色谱柱 {self.device_id} 已创建 === ✨") + print(f"📏 柱参数: 流速={self._max_flow_rate}mL/min | 长度={self._column_length}cm | 直径={self._column_diameter}cm 🔬") async def initialize(self) -> bool: - """Initialize virtual column""" - self.logger.info(f"Initializing virtual column {self.device_id}") + """Initialize virtual column 🚀""" + self.logger.info(f"🔧 初始化虚拟色谱柱 {self.device_id} ✨") + self.data.update({ "status": "Idle", - "column_state": "Ready", + "column_state": "Ready", "current_flow_rate": 0.0, "max_flow_rate": self._max_flow_rate, "column_length": self._column_length, "column_diameter": self._column_diameter, "processed_volume": 0.0, "progress": 0.0, - "current_status": "Ready" + "current_status": "Ready for separation" }) + + self.logger.info(f"✅ 色谱柱 {self.device_id} 初始化完成 🏛️") + self.logger.info(f"📊 设备规格: 最大流速 {self._max_flow_rate}mL/min | 柱长 {self._column_length}cm 📏") return True async def cleanup(self) -> bool: - """Cleanup virtual column""" - self.logger.info(f"Cleaning up virtual column {self.device_id}") + """Cleanup virtual column 🧹""" + self.logger.info(f"🧹 清理虚拟色谱柱 {self.device_id} 🔚") + + self.data.update({ + "status": "Offline", + "column_state": "Offline", + "current_status": "System offline" + }) + + self.logger.info(f"✅ 色谱柱 {self.device_id} 清理完成 💤") return True - async def run_column(self, from_vessel: str, to_vessel: str, column: str) -> bool: - """Execute column chromatography run - matches RunColumn action""" - self.logger.info(f"Running column separation: {from_vessel} -> {to_vessel} using {column}") + async def run_column(self, from_vessel: str, to_vessel: str, column: str, **kwargs) -> bool: + """Execute column chromatography run - matches RunColumn action 🏛️""" + + # 提取额外参数 + rf = kwargs.get('rf', '0.3') + solvent1 = kwargs.get('solvent1', 'ethyl_acetate') + solvent2 = kwargs.get('solvent2', 'hexane') + ratio = kwargs.get('ratio', '30:70') + + self.logger.info(f"🏛️ 开始柱层析分离: {from_vessel} → {to_vessel} 🚰") + self.logger.info(f" 🧪 使用色谱柱: {column}") + self.logger.info(f" 🎯 Rf值: {rf}") + self.logger.info(f" 🧪 洗脱溶剂: {solvent1}:{solvent2} ({ratio}) 💧") # 更新设备状态 self.data.update({ "status": "Running", "column_state": "Separating", - "current_status": "Column separation in progress", + "current_status": "🏛️ Column separation in progress", "progress": 0.0, - "processed_volume": 0.0 + "processed_volume": 0.0, + "current_from_vessel": from_vessel, + "current_to_vessel": to_vessel, + "current_column": column, + "current_rf": rf, + "current_solvents": f"{solvent1}:{solvent2} ({ratio})" }) # 模拟柱层析分离过程 # 假设处理时间基于流速和柱子长度 - separation_time = (self._column_length * 2) / self._max_flow_rate # 简化计算 + base_time = (self._column_length * 2) / self._max_flow_rate # 简化计算 + separation_time = max(base_time, 20.0) # 最少20秒 + + self.logger.info(f"⏱️ 预计分离时间: {separation_time:.1f}秒 ⌛") + self.logger.info(f"📏 柱参数: 长度 {self._column_length}cm | 流速 {self._max_flow_rate}mL/min 🌊") steps = 20 # 分20个步骤模拟分离过程 step_time = separation_time / steps @@ -74,34 +106,65 @@ class VirtualColumn: progress = (i + 1) / steps * 100 volume_processed = (i + 1) * 5.0 # 假设每步处理5mL + # 不同阶段的状态描述 + if progress <= 25: + phase = "🌊 样品上柱阶段" + phase_emoji = "📥" + elif progress <= 50: + phase = "🧪 洗脱开始" + phase_emoji = "💧" + elif progress <= 75: + phase = "⚗️ 成分分离中" + phase_emoji = "🔄" + else: + phase = "🎯 收集产物" + phase_emoji = "📤" + # 更新状态 + status_msg = f"{phase_emoji} {phase}: {progress:.1f}% | 💧 已处理: {volume_processed:.1f}mL" + self.data.update({ "progress": progress, "processed_volume": volume_processed, - "current_status": f"Column separation: {progress:.1f}% - Processing {volume_processed:.1f}mL" + "current_status": status_msg, + "current_phase": phase }) - self.logger.info(f"Column separation progress: {progress:.1f}%") + # 进度日志(每25%打印一次) + if progress >= 25 and (i + 1) % 5 == 0: # 每5步(25%)打印一次 + self.logger.info(f"📊 分离进度: {progress:.0f}% | {phase} | 💧 {volume_processed:.1f}mL 完成 ✨") # 分离完成 + final_status = f"✅ 柱层析分离完成: {from_vessel} → {to_vessel} | 💧 共处理 {volume_processed:.1f}mL" + self.data.update({ "status": "Idle", "column_state": "Ready", - "current_status": "Column separation completed", - "progress": 100.0 + "current_status": final_status, + "progress": 100.0, + "final_volume": volume_processed }) - self.logger.info(f"Column separation completed: {from_vessel} -> {to_vessel}") + self.logger.info(f"🎉 柱层析分离完成! ✨") + self.logger.info(f"📊 分离结果:") + self.logger.info(f" 🥽 源容器: {from_vessel}") + self.logger.info(f" 🥽 目标容器: {to_vessel}") + self.logger.info(f" 🏛️ 使用色谱柱: {column}") + self.logger.info(f" 💧 处理体积: {volume_processed:.1f}mL") + self.logger.info(f" 🧪 洗脱条件: {solvent1}:{solvent2} ({ratio})") + self.logger.info(f" 🎯 Rf值: {rf}") + self.logger.info(f" ⏱️ 总耗时: {separation_time:.1f}秒 🏁") + return True # 状态属性 @property def status(self) -> str: - return self.data.get("status", "Unknown") + return self.data.get("status", "❓ Unknown") @property def column_state(self) -> str: - return self.data.get("column_state", "Unknown") + return self.data.get("column_state", "❓ Unknown") @property def current_flow_rate(self) -> float: @@ -129,4 +192,12 @@ class VirtualColumn: @property def current_status(self) -> str: - return self.data.get("current_status", "Ready") \ No newline at end of file + return self.data.get("current_status", "📋 Ready") + + @property + def current_phase(self) -> str: + return self.data.get("current_phase", "🏠 待机中") + + @property + def final_volume(self) -> float: + return self.data.get("final_volume", 0.0) \ No newline at end of file diff --git a/unilabos/devices/virtual/virtual_filter.py b/unilabos/devices/virtual/virtual_filter.py index d70c854..655934b 100644 --- a/unilabos/devices/virtual/virtual_filter.py +++ b/unilabos/devices/virtual/virtual_filter.py @@ -5,7 +5,7 @@ from typing import Dict, Any, Optional class VirtualFilter: - """Virtual filter device - 完全按照 Filter.action 规范""" + """Virtual filter device - 完全按照 Filter.action 规范 🌊""" def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs): if device_id is None and 'id' in kwargs: @@ -31,8 +31,8 @@ class VirtualFilter: setattr(self, key, value) async def initialize(self) -> bool: - """Initialize virtual filter""" - self.logger.info(f"Initializing virtual filter {self.device_id}") + """Initialize virtual filter 🚀""" + self.logger.info(f"🔧 初始化虚拟过滤器 {self.device_id} ✨") # 按照 Filter.action 的 feedback 字段初始化 self.data.update({ @@ -43,17 +43,21 @@ class VirtualFilter: "current_status": "Ready for filtration", # Filter.action feedback "message": "Ready for filtration" }) + + self.logger.info(f"✅ 过滤器 {self.device_id} 初始化完成 🌊") return True async def cleanup(self) -> bool: - """Cleanup virtual filter""" - self.logger.info(f"Cleaning up virtual filter {self.device_id}") + """Cleanup virtual filter 🧹""" + self.logger.info(f"🧹 清理虚拟过滤器 {self.device_id} 🔚") self.data.update({ "status": "Offline", "current_status": "System offline", "message": "System offline" }) + + self.logger.info(f"✅ 过滤器 {self.device_id} 清理完成 💤") return True async def filter( @@ -66,74 +70,82 @@ class VirtualFilter: continue_heatchill: bool = False, volume: float = 0.0 ) -> bool: - """Execute filter action - 完全按照 Filter.action 参数""" + """Execute filter action - 完全按照 Filter.action 参数 🌊""" # 🔧 新增:温度自动调整 original_temp = temp if temp == 0.0: temp = 25.0 # 0度自动设置为室温 - self.logger.info(f"温度自动调整: {original_temp}°C → {temp}°C (室温)") + self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (室温) 🏠") elif temp < 4.0: temp = 4.0 # 小于4度自动设置为4度 - self.logger.info(f"温度自动调整: {original_temp}°C → {temp}°C (最低温度)") + self.logger.info(f"🌡️ 温度自动调整: {original_temp}°C → {temp}°C (最低温度) ❄️") - self.logger.info(f"Filter: vessel={vessel}, filtrate_vessel={filtrate_vessel}") - self.logger.info(f" stir={stir}, stir_speed={stir_speed}, temp={temp}") - self.logger.info(f" continue_heatchill={continue_heatchill}, volume={volume}") + self.logger.info(f"🌊 开始过滤操作: {vessel} → {filtrate_vessel} 🚰") + self.logger.info(f" 🌪️ 搅拌: {stir} ({stir_speed} RPM)") + self.logger.info(f" 🌡️ 温度: {temp}°C") + self.logger.info(f" 💧 体积: {volume}mL") + self.logger.info(f" 🔥 保持加热: {continue_heatchill}") # 验证参数 if temp > self._max_temp or temp < 4.0: - error_msg = f"温度 {temp}°C 超出范围 (4-{self._max_temp}°C)" - self.logger.error(error_msg) + error_msg = f"🌡️ 温度 {temp}°C 超出范围 (4-{self._max_temp}°C) ⚠️" + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"Error: {error_msg}", - "current_status": f"Error: {error_msg}", + "status": f"Error: 温度超出范围 ⚠️", + "current_status": f"Error: 温度超出范围 ⚠️", "message": error_msg }) return False if stir and stir_speed > self._max_stir_speed: - error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 (0-{self._max_stir_speed} RPM)" - self.logger.error(error_msg) + error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出范围 (0-{self._max_stir_speed} RPM) ⚠️" + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"Error: {error_msg}", - "current_status": f"Error: {error_msg}", + "status": f"Error: 搅拌速度超出范围 ⚠️", + "current_status": f"Error: 搅拌速度超出范围 ⚠️", "message": error_msg }) return False if volume > self._max_volume: - error_msg = f"过滤体积 {volume} mL 超出范围 (0-{self._max_volume} mL)" - self.logger.error(error_msg) + error_msg = f"💧 过滤体积 {volume} mL 超出范围 (0-{self._max_volume} mL) ⚠️" + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"Error: {error_msg}", - "current_status": f"Error: {error_msg}", + "status": f"Error: 体积超出范围 ⚠️", + "current_status": f"Error: 体积超出范围 ⚠️", "message": error_msg }) return False # 开始过滤 filter_volume = volume if volume > 0 else 50.0 + self.logger.info(f"🚀 开始过滤 {filter_volume}mL 液体 💧") self.data.update({ - "status": f"过滤中: {vessel}", + "status": f"🌊 过滤中: {vessel}", "current_temp": temp, "filtered_volume": 0.0, "progress": 0.0, - "current_status": f"Filtering {vessel} → {filtrate_vessel}", - "message": f"Starting filtration: {vessel} → {filtrate_vessel}" + "current_status": f"🌊 Filtering {vessel} → {filtrate_vessel}", + "message": f"🚀 Starting filtration: {vessel} → {filtrate_vessel}" }) try: # 过滤过程 - 实时更新进度 start_time = time_module.time() + # 根据体积和搅拌估算过滤时间 base_time = filter_volume / 5.0 # 5mL/s 基础速度 if stir: base_time *= 0.8 # 搅拌加速过滤 + self.logger.info(f"🌪️ 搅拌加速过滤,预计时间减少20% ⚡") if temp > 50.0: base_time *= 0.7 # 高温加速过滤 + self.logger.info(f"🔥 高温加速过滤,预计时间减少30% ⚡") + filter_time = max(base_time, 10.0) # 最少10秒 + self.logger.info(f"⏱️ 预计过滤时间: {filter_time:.1f}秒 ⌛") while True: current_time = time_module.time() @@ -143,20 +155,24 @@ class VirtualFilter: current_filtered = (progress / 100.0) * filter_volume # 更新状态 - 按照 Filter.action feedback 字段 - status_msg = f"过滤中: {vessel}" + status_msg = f"🌊 过滤中: {vessel}" if stir: - status_msg += f" | 搅拌: {stir_speed} RPM" - status_msg += f" | {temp}°C | {progress:.1f}% | 已过滤: {current_filtered:.1f}mL" + status_msg += f" | 🌪️ 搅拌: {stir_speed} RPM" + status_msg += f" | 🌡️ {temp}°C | 📊 {progress:.1f}% | 💧 已过滤: {current_filtered:.1f}mL" self.data.update({ "progress": progress, # Filter.action feedback "current_temp": temp, # Filter.action feedback "filtered_volume": current_filtered, # Filter.action feedback - "current_status": f"Filtering: {progress:.1f}% complete", # Filter.action feedback + "current_status": f"🌊 Filtering: {progress:.1f}% complete", # Filter.action feedback "status": status_msg, - "message": f"Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered" + "message": f"🌊 Filtering: {progress:.1f}% complete, {current_filtered:.1f}mL filtered" }) + # 进度日志(每25%打印一次) + if progress >= 25 and progress % 25 < 1: + self.logger.info(f"📊 过滤进度: {progress:.0f}% | 💧 {current_filtered:.1f}mL 完成 ✨") + if remaining <= 0: break @@ -164,54 +180,57 @@ class VirtualFilter: # 过滤完成 final_temp = temp if continue_heatchill else 25.0 - final_status = f"过滤完成: {vessel} | {filter_volume}mL → {filtrate_vessel}" + final_status = f"✅ 过滤完成: {vessel} | 💧 {filter_volume}mL → {filtrate_vessel}" if continue_heatchill: - final_status += " | 继续加热搅拌" + final_status += " | 🔥 继续加热搅拌" + self.logger.info(f"🔥 继续保持加热搅拌状态 🌪️") self.data.update({ "status": final_status, "progress": 100.0, # Filter.action feedback "current_temp": final_temp, # Filter.action feedback "filtered_volume": filter_volume, # Filter.action feedback - "current_status": f"Filtration completed: {filter_volume}mL", # Filter.action feedback - "message": f"Filtration completed: {filter_volume}mL filtered from {vessel}" + "current_status": f"✅ Filtration completed: {filter_volume}mL", # Filter.action feedback + "message": f"✅ Filtration completed: {filter_volume}mL filtered from {vessel}" }) - self.logger.info(f"Filtration completed: {filter_volume}mL from {vessel} to {filtrate_vessel}") + self.logger.info(f"🎉 过滤完成! 💧 {filter_volume}mL 从 {vessel} 过滤到 {filtrate_vessel} ✨") + self.logger.info(f"📊 最终状态: 温度 {final_temp}°C | 进度 100% | 体积 {filter_volume}mL 🏁") return True except Exception as e: - self.logger.error(f"Error during filtration: {str(e)}") + error_msg = f"过滤过程中发生错误: {str(e)} 💥" + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"过滤错误: {str(e)}", - "current_status": f"Filtration failed: {str(e)}", - "message": f"Filtration failed: {str(e)}" + "status": f"❌ 过滤错误: {str(e)}", + "current_status": f"❌ Filtration failed: {str(e)}", + "message": f"❌ Filtration failed: {str(e)}" }) return False # === 核心状态属性 - 按照 Filter.action feedback 字段 === @property def status(self) -> str: - return self.data.get("status", "Unknown") + return self.data.get("status", "❓ Unknown") @property def progress(self) -> float: - """Filter.action feedback 字段""" + """Filter.action feedback 字段 📊""" return self.data.get("progress", 0.0) @property def current_temp(self) -> float: - """Filter.action feedback 字段""" + """Filter.action feedback 字段 🌡️""" return self.data.get("current_temp", 25.0) @property def filtered_volume(self) -> float: - """Filter.action feedback 字段""" + """Filter.action feedback 字段 💧""" return self.data.get("filtered_volume", 0.0) @property def current_status(self) -> str: - """Filter.action feedback 字段""" + """Filter.action feedback 字段 📋""" return self.data.get("current_status", "") @property diff --git a/unilabos/devices/virtual/virtual_heatchill.py b/unilabos/devices/virtual/virtual_heatchill.py index 20c9a19..94ab572 100644 --- a/unilabos/devices/virtual/virtual_heatchill.py +++ b/unilabos/devices/virtual/virtual_heatchill.py @@ -4,7 +4,7 @@ import time as time_module # 重命名time模块,避免与参数冲突 from typing import Dict, Any class VirtualHeatChill: - """Virtual heat chill device for HeatChillProtocol testing""" + """Virtual heat chill device for HeatChillProtocol testing 🌡️""" def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs): # 处理可能的不同调用方式 @@ -31,31 +31,40 @@ class VirtualHeatChill: for key, value in kwargs.items(): if key not in skip_keys and not hasattr(self, key): setattr(self, key, value) + + print(f"🌡️ === 虚拟温控设备 {self.device_id} 已创建 === ✨") + print(f"🔥 温度范围: {self._min_temp}°C ~ {self._max_temp}°C | 🌪️ 最大搅拌: {self._max_stir_speed} RPM") async def initialize(self) -> bool: - """Initialize virtual heat chill""" - self.logger.info(f"Initializing virtual heat chill {self.device_id}") + """Initialize virtual heat chill 🚀""" + self.logger.info(f"🔧 初始化虚拟温控设备 {self.device_id} ✨") # 初始化状态信息 self.data.update({ - "status": "Idle", + "status": "🏠 待机中", "operation_mode": "Idle", "is_stirring": False, "stir_speed": 0.0, "remaining_time": 0.0, }) + + self.logger.info(f"✅ 温控设备 {self.device_id} 初始化完成 🌡️") + self.logger.info(f"📊 设备规格: 温度范围 {self._min_temp}°C ~ {self._max_temp}°C | 搅拌范围 0 ~ {self._max_stir_speed} RPM") return True async def cleanup(self) -> bool: - """Cleanup virtual heat chill""" - self.logger.info(f"Cleaning up virtual heat chill {self.device_id}") + """Cleanup virtual heat chill 🧹""" + self.logger.info(f"🧹 清理虚拟温控设备 {self.device_id} 🔚") + self.data.update({ - "status": "Offline", + "status": "💤 离线", "operation_mode": "Offline", "is_stirring": False, "stir_speed": 0.0, "remaining_time": 0.0 }) + + self.logger.info(f"✅ 温控设备 {self.device_id} 清理完成 💤") return True async def heat_chill(self, vessel: str, temp: float, time, stir: bool, @@ -72,62 +81,73 @@ class VirtualHeatChill: purpose = str(purpose) except (ValueError, TypeError) as e: error_msg = f"参数类型转换错误: temp={temp}({type(temp)}), time={time}({type(time)}), error={str(e)}" - self.logger.error(error_msg) + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"Error: {error_msg}", + "status": f"❌ 错误: {error_msg}", "operation_mode": "Error" }) return False - self.logger.info(f"HeatChill: vessel={vessel}, temp={temp}°C, time={time_value}s, stir={stir}, stir_speed={stir_speed}") + # 确定温度操作emoji + if temp > 25.0: + temp_emoji = "🔥" + operation_mode = "Heating" + status_action = "加热" + elif temp < 25.0: + temp_emoji = "❄️" + operation_mode = "Cooling" + status_action = "冷却" + else: + temp_emoji = "🌡️" + operation_mode = "Maintaining" + status_action = "保温" + + self.logger.info(f"🌡️ 开始温控操作: {vessel} → {temp}°C {temp_emoji}") + self.logger.info(f" 🥽 容器: {vessel}") + self.logger.info(f" 🎯 目标温度: {temp}°C {temp_emoji}") + self.logger.info(f" ⏰ 持续时间: {time_value}s") + self.logger.info(f" 🌪️ 搅拌: {stir} ({stir_speed} RPM)") + self.logger.info(f" 📝 目的: {purpose}") # 验证参数范围 if temp > self._max_temp or temp < self._min_temp: - error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C)" - self.logger.error(error_msg) + error_msg = f"🌡️ 温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C) ⚠️" + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"Error: {error_msg}", + "status": f"❌ 错误: 温度超出范围 ⚠️", "operation_mode": "Error" }) return False if stir and stir_speed > self._max_stir_speed: - error_msg = f"搅拌速度 {stir_speed} RPM 超出最大值 {self._max_stir_speed} RPM" - self.logger.error(error_msg) + error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出最大值 {self._max_stir_speed} RPM ⚠️" + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"Error: {error_msg}", + "status": f"❌ 错误: 搅拌速度超出范围 ⚠️", "operation_mode": "Error" }) return False if time_value <= 0: - error_msg = f"时间 {time_value}s 必须大于0" - self.logger.error(error_msg) + error_msg = f"⏰ 时间 {time_value}s 必须大于0 ⚠️" + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"Error: {error_msg}", + "status": f"❌ 错误: 时间参数无效 ⚠️", "operation_mode": "Error" }) return False - # 确定操作模式 - if temp > 25.0: - operation_mode = "Heating" - status_action = "加热" - elif temp < 25.0: - operation_mode = "Cooling" - status_action = "冷却" - else: - operation_mode = "Maintaining" - status_action = "保温" - # 🔧 修复:使用转换后的时间值 start_time = time_module.time() total_time = time_value # 使用转换后的浮点数 + self.logger.info(f"🚀 开始{status_action}程序! 预计用时 {total_time:.1f}秒 ⏱️") + # 开始操作 - stir_info = f" | 搅拌: {stir_speed} RPM" if stir else "" + stir_info = f" | 🌪️ 搅拌: {stir_speed} RPM" if stir else "" + self.data.update({ - "status": f"运行中: {status_action} {vessel} 至 {temp}°C | 剩余: {total_time:.0f}s{stir_info}", + "status": f"{temp_emoji} 运行中: {status_action} {vessel} 至 {temp}°C | ⏰ 剩余: {total_time:.0f}s{stir_info}", "operation_mode": operation_mode, "is_stirring": stir, "stir_speed": stir_speed if stir else 0.0, @@ -135,17 +155,25 @@ class VirtualHeatChill: }) # 在等待过程中每秒更新剩余时间 + last_logged_time = 0 while True: current_time = time_module.time() elapsed = current_time - start_time remaining = max(0, total_time - elapsed) + progress = (elapsed / total_time) * 100 if total_time > 0 else 100 # 更新剩余时间和状态 self.data.update({ "remaining_time": remaining, - "status": f"运行中: {status_action} {vessel} 至 {temp}°C | 剩余: {remaining:.0f}s{stir_info}" + "status": f"{temp_emoji} 运行中: {status_action} {vessel} 至 {temp}°C | ⏰ 剩余: {remaining:.0f}s{stir_info}", + "progress": progress }) + # 进度日志(每25%打印一次) + if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_time: + self.logger.info(f"📊 {status_action}进度: {progress:.0f}% | ⏰ 剩余: {remaining:.0f}s | {temp_emoji} 目标: {temp}°C ✨") + last_logged_time = int(progress) + # 如果时间到了,退出循环 if remaining <= 0: break @@ -154,20 +182,30 @@ class VirtualHeatChill: await asyncio.sleep(1.0) # 操作完成 - final_stir_info = f" | 搅拌: {stir_speed} RPM" if stir else "" + final_stir_info = f" | 🌪️ 搅拌: {stir_speed} RPM" if stir else "" + self.data.update({ - "status": f"完成: {vessel} 已达到 {temp}°C | 用时: {total_time:.0f}s{final_stir_info}", + "status": f"✅ 完成: {vessel} 已达到 {temp}°C {temp_emoji} | ⏱️ 用时: {total_time:.0f}s{final_stir_info}", "operation_mode": "Completed", "remaining_time": 0.0, "is_stirring": False, - "stir_speed": 0.0 + "stir_speed": 0.0, + "progress": 100.0 }) - self.logger.info(f"HeatChill completed for vessel {vessel} at {temp}°C after {total_time}s") + self.logger.info(f"🎉 温控操作完成! ✨") + self.logger.info(f"📊 操作结果:") + self.logger.info(f" 🥽 容器: {vessel}") + self.logger.info(f" 🌡️ 达到温度: {temp}°C {temp_emoji}") + self.logger.info(f" ⏱️ 总用时: {total_time:.0f}s") + if stir: + self.logger.info(f" 🌪️ 搅拌速度: {stir_speed} RPM") + self.logger.info(f" 📝 操作目的: {purpose} 🏁") + return True async def heat_chill_start(self, vessel: str, temp: float, purpose: str) -> bool: - """Start continuous heat chill""" + """Start continuous heat chill 🔄""" # 🔧 添加类型转换 try: @@ -176,73 +214,82 @@ class VirtualHeatChill: purpose = str(purpose) except (ValueError, TypeError) as e: error_msg = f"参数类型转换错误: {str(e)}" - self.logger.error(error_msg) + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"Error: {error_msg}", + "status": f"❌ 错误: {error_msg}", "operation_mode": "Error" }) return False - self.logger.info(f"HeatChillStart: vessel={vessel}, temp={temp}°C") - - # 验证参数 - if temp > self._max_temp or temp < self._min_temp: - error_msg = f"温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C)" - self.logger.error(error_msg) - self.data.update({ - "status": f"Error: {error_msg}", - "operation_mode": "Error" - }) - return False - - # 确定操作模式 + # 确定温度操作emoji if temp > 25.0: + temp_emoji = "🔥" operation_mode = "Heating" status_action = "持续加热" elif temp < 25.0: + temp_emoji = "❄️" operation_mode = "Cooling" status_action = "持续冷却" else: + temp_emoji = "🌡️" operation_mode = "Maintaining" status_action = "恒温保持" + self.logger.info(f"🔄 启动持续温控: {vessel} → {temp}°C {temp_emoji}") + self.logger.info(f" 🥽 容器: {vessel}") + self.logger.info(f" 🎯 目标温度: {temp}°C {temp_emoji}") + self.logger.info(f" 🔄 模式: {status_action}") + self.logger.info(f" 📝 目的: {purpose}") + + # 验证参数 + if temp > self._max_temp or temp < self._min_temp: + error_msg = f"🌡️ 温度 {temp}°C 超出范围 ({self._min_temp}°C - {self._max_temp}°C) ⚠️" + self.logger.error(f"❌ {error_msg}") + self.data.update({ + "status": f"❌ 错误: 温度超出范围 ⚠️", + "operation_mode": "Error" + }) + return False + self.data.update({ - "status": f"启动: {status_action} {vessel} 至 {temp}°C | 持续运行", + "status": f"🔄 启动: {status_action} {vessel} 至 {temp}°C {temp_emoji} | ♾️ 持续运行", "operation_mode": operation_mode, "is_stirring": False, "stir_speed": 0.0, "remaining_time": -1.0, # -1 表示持续运行 }) + self.logger.info(f"✅ 持续温控已启动! {temp_emoji} {status_action}模式 🚀") return True async def heat_chill_stop(self, vessel: str) -> bool: - """Stop heat chill""" + """Stop heat chill 🛑""" # 🔧 添加类型转换 try: vessel = str(vessel) except (ValueError, TypeError) as e: error_msg = f"参数类型转换错误: {str(e)}" - self.logger.error(error_msg) + self.logger.error(f"❌ {error_msg}") return False - self.logger.info(f"HeatChillStop: vessel={vessel}") + self.logger.info(f"🛑 停止温控: {vessel}") self.data.update({ - "status": f"已停止: {vessel} 温控停止", + "status": f"🛑 已停止: {vessel} 温控停止", "operation_mode": "Stopped", "is_stirring": False, "stir_speed": 0.0, "remaining_time": 0.0, }) + self.logger.info(f"✅ 温控设备已停止 {vessel} 的温度控制 🏁") return True # 状态属性 @property def status(self) -> str: - return self.data.get("status", "Idle") + return self.data.get("status", "🏠 待机中") @property def operation_mode(self) -> str: @@ -258,4 +305,20 @@ class VirtualHeatChill: @property def remaining_time(self) -> float: - return self.data.get("remaining_time", 0.0) \ No newline at end of file + return self.data.get("remaining_time", 0.0) + + @property + def progress(self) -> float: + return self.data.get("progress", 0.0) + + @property + def max_temp(self) -> float: + return self._max_temp + + @property + def min_temp(self) -> float: + return self._min_temp + + @property + def max_stir_speed(self) -> float: + return self._max_stir_speed \ No newline at end of file diff --git a/unilabos/devices/virtual/virtual_multiway_valve.py b/unilabos/devices/virtual/virtual_multiway_valve.py index c24b7b1..c12aa7f 100644 --- a/unilabos/devices/virtual/virtual_multiway_valve.py +++ b/unilabos/devices/virtual/virtual_multiway_valve.py @@ -1,16 +1,20 @@ import time +import logging from typing import Union, Dict, Optional class VirtualMultiwayValve: """ - 虚拟九通阀门 - 0号位连接transfer pump,1-8号位连接其他设备 + 虚拟九通阀门 - 0号位连接transfer pump,1-8号位连接其他设备 🔄 """ def __init__(self, port: str = "VIRTUAL", positions: int = 8): self.port = port self.max_positions = positions # 1-8号位 self.total_positions = positions + 1 # 0-8号位,共9个位置 + # 添加日志记录器 + self.logger = logging.getLogger(f"VirtualMultiwayValve.{port}") + # 状态属性 self._status = "Idle" self._valve_state = "Ready" @@ -29,6 +33,10 @@ class VirtualMultiwayValve: 7: "port_7", # 7号位 8: "port_8" # 8号位 } + + print(f"🔄 === 虚拟多通阀门已创建 === ✨") + print(f"🎯 端口: {port} | 📊 位置范围: 0-{self.max_positions} | 🏠 初始位置: 0 (transfer_pump)") + self.logger.info(f"🔧 多通阀门初始化: 端口={port}, 最大位置={self.max_positions}") @property def status(self) -> str: @@ -47,16 +55,16 @@ class VirtualMultiwayValve: return self._target_position def get_current_position(self) -> int: - """获取当前阀门位置""" + """获取当前阀门位置 📍""" return self._current_position def get_current_port(self) -> str: - """获取当前连接的端口名称""" + """获取当前连接的端口名称 🔌""" return self.position_map.get(self._current_position, "unknown") def set_position(self, command: Union[int, str]): """ - 设置阀门位置 - 支持0-8位置 + 设置阀门位置 - 支持0-8位置 🎯 Args: command: 目标位置 (0-8) 或位置字符串 @@ -71,112 +79,162 @@ class VirtualMultiwayValve: pos = int(command) if pos < 0 or pos > self.max_positions: - raise ValueError(f"Position must be between 0 and {self.max_positions}") + error_msg = f"位置必须在 0-{self.max_positions} 范围内" + self.logger.error(f"❌ {error_msg}: 请求位置={pos}") + raise ValueError(error_msg) + + # 获取位置描述emoji + if pos == 0: + pos_emoji = "🚰" + pos_desc = "泵位置" + else: + pos_emoji = "🔌" + pos_desc = f"端口{pos}" + + old_position = self._current_position + old_port = self.get_current_port() + + self.logger.info(f"🔄 阀门切换: {old_position}({old_port}) → {pos}({self.position_map.get(pos, 'unknown')}) {pos_emoji}") self._status = "Busy" self._valve_state = "Moving" self._target_position = pos # 模拟阀门切换时间 - switch_time = abs(self._current_position - pos) * 0.5 # 每个位置0.5秒 - time.sleep(switch_time) + switch_time = abs(self._current_position - pos) * 0.5 # 每个位置0.1秒 + + if switch_time > 0: + self.logger.info(f"⏱️ 阀门移动中... 预计用时: {switch_time:.1f}秒 🔄") + time.sleep(switch_time) self._current_position = pos self._status = "Idle" self._valve_state = "Ready" current_port = self.get_current_port() - return f"Position set to {pos} ({current_port})" + success_msg = f"✅ 阀门已切换到位置 {pos} ({current_port}) {pos_emoji}" + + self.logger.info(success_msg) + return success_msg except ValueError as e: + error_msg = f"❌ 阀门切换失败: {str(e)}" self._status = "Error" self._valve_state = "Error" - return f"Error: {str(e)}" + self.logger.error(error_msg) + return error_msg def set_to_pump_position(self): - """切换到transfer pump位置(0号位)""" + """切换到transfer pump位置(0号位)🚰""" + self.logger.info(f"🚰 切换到泵位置...") return self.set_position(0) def set_to_port(self, port_number: int): """ - 切换到指定端口位置 + 切换到指定端口位置 🔌 Args: port_number: 端口号 (1-8) """ if port_number < 1 or port_number > self.max_positions: - raise ValueError(f"Port number must be between 1 and {self.max_positions}") + error_msg = f"端口号必须在 1-{self.max_positions} 范围内" + self.logger.error(f"❌ {error_msg}: 请求端口={port_number}") + raise ValueError(error_msg) + + self.logger.info(f"🔌 切换到端口 {port_number}...") return self.set_position(port_number) def open(self): - """打开阀门 - 设置到transfer pump位置(0号位)""" + """打开阀门 - 设置到transfer pump位置(0号位)🔓""" + self.logger.info(f"🔓 打开阀门,设置到泵位置...") return self.set_to_pump_position() def close(self): - """关闭阀门 - 对于多通阀门,设置到一个"关闭"状态""" + """关闭阀门 - 对于多通阀门,设置到一个"关闭"状态 🔒""" + self.logger.info(f"🔒 关闭阀门...") + self._status = "Busy" self._valve_state = "Closing" time.sleep(0.5) - + # 可以选择保持当前位置或设置特殊关闭状态 self._status = "Idle" self._valve_state = "Closed" - return f"Valve closed at position {self._current_position}" + close_msg = f"🔒 阀门已关闭,保持在位置 {self._current_position} ({self.get_current_port()})" + self.logger.info(close_msg) + return close_msg def get_valve_position(self) -> int: - """获取阀门位置 - 兼容性方法""" + """获取阀门位置 - 兼容性方法 📍""" return self._current_position def is_at_position(self, position: int) -> bool: - """检查是否在指定位置""" - return self._current_position == position + """检查是否在指定位置 🎯""" + result = self._current_position == position + self.logger.debug(f"🎯 位置检查: 当前={self._current_position}, 目标={position}, 匹配={result}") + return result def is_at_pump_position(self) -> bool: - """检查是否在transfer pump位置""" - return self._current_position == 0 + """检查是否在transfer pump位置 🚰""" + result = self._current_position == 0 + pump_status = "是" if result else "否" + self.logger.debug(f"🚰 泵位置检查: {pump_status} (当前位置: {self._current_position})") + return result def is_at_port(self, port_number: int) -> bool: - """检查是否在指定端口位置""" - return self._current_position == port_number + """检查是否在指定端口位置 🔌""" + result = self._current_position == port_number + port_status = "是" if result else "否" + self.logger.debug(f"🔌 端口{port_number}检查: {port_status} (当前位置: {self._current_position})") + return result def get_available_positions(self) -> list: - """获取可用位置列表""" - return list(range(0, self.max_positions + 1)) + """获取可用位置列表 📋""" + positions = list(range(0, self.max_positions + 1)) + self.logger.debug(f"📋 可用位置: {positions}") + return positions def get_available_ports(self) -> Dict[int, str]: - """获取可用端口映射""" + """获取可用端口映射 🗺️""" + self.logger.debug(f"🗺️ 端口映射: {self.position_map}") return self.position_map.copy() def reset(self): - """重置阀门到transfer pump位置(0号位)""" + """重置阀门到transfer pump位置(0号位)🔄""" + self.logger.info(f"🔄 重置阀门到泵位置...") return self.set_position(0) def switch_between_pump_and_port(self, port_number: int): """ - 在transfer pump位置和指定端口之间切换 + 在transfer pump位置和指定端口之间切换 🔄 Args: port_number: 目标端口号 (1-8) """ if self._current_position == 0: # 当前在pump位置,切换到指定端口 + self.logger.info(f"🔄 从泵位置切换到端口 {port_number}...") return self.set_to_port(port_number) else: # 当前在某个端口,切换到pump位置 + self.logger.info(f"🔄 从端口 {self._current_position} 切换到泵位置...") return self.set_to_pump_position() def get_flow_path(self) -> str: - """获取当前流路路径描述""" + """获取当前流路路径描述 🌊""" current_port = self.get_current_port() if self._current_position == 0: - return f"Transfer pump connected (position {self._current_position})" + flow_path = f"🚰 转移泵已连接 (位置 {self._current_position})" else: - return f"Port {self._current_position} connected ({current_port})" + flow_path = f"🔌 端口 {self._current_position} 已连接 ({current_port})" + + self.logger.debug(f"🌊 当前流路: {flow_path}") + return flow_path def get_info(self) -> dict: - """获取阀门详细信息""" - return { + """获取阀门详细信息 📊""" + info = { "port": self.port, "max_positions": self.max_positions, "total_positions": self.total_positions, @@ -188,18 +246,25 @@ class VirtualMultiwayValve: "flow_path": self.get_flow_path(), "position_map": self.position_map } + + self.logger.debug(f"📊 阀门信息: 位置={self._current_position}, 状态={self._status}, 端口={self.get_current_port()}") + return info def __str__(self): - return f"VirtualMultiwayValve(Position: {self._current_position}/{self.max_positions}, Port: {self.get_current_port()}, Status: {self._status})" + current_port = self.get_current_port() + status_emoji = "✅" if self._status == "Idle" else "🔄" if self._status == "Busy" else "❌" + + return f"🔄 VirtualMultiwayValve({status_emoji} 位置: {self._current_position}/{self.max_positions}, 端口: {current_port}, 状态: {self._status})" def set_valve_position(self, command: Union[int, str]): """ - 设置阀门位置 - 兼容pump_protocol调用 + 设置阀门位置 - 兼容pump_protocol调用 🎯 这是set_position的别名方法,用于兼容pump_protocol.py Args: command: 目标位置 (0-8) 或位置字符串 """ + self.logger.debug(f"🎯 兼容性调用: set_valve_position({command})") return self.set_position(command) @@ -207,25 +272,35 @@ class VirtualMultiwayValve: if __name__ == "__main__": valve = VirtualMultiwayValve() - print("=== 虚拟九通阀门测试 ===") - print(f"初始状态: {valve}") - print(f"当前流路: {valve.get_flow_path()}") + print("🔄 === 虚拟九通阀门测试 === ✨") + print(f"🏠 初始状态: {valve}") + print(f"🌊 当前流路: {valve.get_flow_path()}") # 切换到试剂瓶1(1号位) - print(f"\n切换到1号位: {valve.set_position(1)}") - print(f"当前状态: {valve}") + print(f"\n🔌 切换到1号位: {valve.set_position(1)}") + print(f"📍 当前状态: {valve}") # 切换到transfer pump位置(0号位) - print(f"\n切换到pump位置: {valve.set_to_pump_position()}") - print(f"当前状态: {valve}") + print(f"\n🚰 切换到pump位置: {valve.set_to_pump_position()}") + print(f"📍 当前状态: {valve}") # 切换到试剂瓶2(2号位) - print(f"\n切换到2号位: {valve.set_to_port(2)}") - print(f"当前状态: {valve}") + print(f"\n🔌 切换到2号位: {valve.set_to_port(2)}") + print(f"📍 当前状态: {valve}") # 显示所有可用位置 - print(f"\n可用位置: {valve.get_available_positions()}") - print(f"端口映射: {valve.get_available_ports()}") + print(f"\n📋 可用位置: {valve.get_available_positions()}") + print(f"🗺️ 端口映射: {valve.get_available_ports()}") # 获取详细信息 - print(f"\n详细信息: {valve.get_info()}") \ No newline at end of file + print(f"\n📊 详细信息: {valve.get_info()}") + + # 测试切换功能 + print(f"\n🔄 智能切换测试:") + print(f"当前位置: {valve._current_position}") + print(f"切换结果: {valve.switch_between_pump_and_port(3)}") + print(f"新位置: {valve._current_position}") + + # 重置测试 + print(f"\n🔄 重置测试: {valve.reset()}") + print(f"📍 重置后状态: {valve}") \ No newline at end of file diff --git a/unilabos/devices/virtual/virtual_rotavap.py b/unilabos/devices/virtual/virtual_rotavap.py index bfd6494..dd1cca4 100644 --- a/unilabos/devices/virtual/virtual_rotavap.py +++ b/unilabos/devices/virtual/virtual_rotavap.py @@ -4,11 +4,11 @@ import time as time_module from typing import Dict, Any, Optional def debug_print(message): - """调试输出""" - print(f"[ROTAVAP] {message}", flush=True) + """调试输出 🔍""" + print(f"🌪️ [ROTAVAP] {message}", flush=True) class VirtualRotavap: - """Virtual rotary evaporator device - 简化版,只保留核心功能""" + """Virtual rotary evaporator device - 简化版,只保留核心功能 🌪️""" def __init__(self, device_id: Optional[str] = None, config: Optional[Dict[str, Any]] = None, **kwargs): # 处理可能的不同调用方式 @@ -35,13 +35,16 @@ class VirtualRotavap: if key not in skip_keys and not hasattr(self, key): setattr(self, key, value) + print(f"🌪️ === 虚拟旋转蒸发仪 {self.device_id} 已创建 === ✨") + print(f"🔥 温度范围: 10°C ~ {self._max_temp}°C | 🌀 转速范围: 10 ~ {self._max_rotation_speed} RPM") + async def initialize(self) -> bool: - """Initialize virtual rotary evaporator""" - self.logger.info(f"Initializing virtual rotary evaporator {self.device_id}") + """Initialize virtual rotary evaporator 🚀""" + self.logger.info(f"🔧 初始化虚拟旋转蒸发仪 {self.device_id} ✨") # 只保留核心状态 self.data.update({ - "status": "Idle", + "status": "🏠 待机中", "rotavap_state": "Ready", # Ready, Evaporating, Completed, Error "current_temp": 25.0, "target_temp": 25.0, @@ -50,22 +53,27 @@ class VirtualRotavap: "evaporated_volume": 0.0, "progress": 0.0, "remaining_time": 0.0, - "message": "Ready for evaporation" + "message": "🌪️ Ready for evaporation" }) + + self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 初始化完成 🌪️") + self.logger.info(f"📊 设备规格: 温度范围 10°C ~ {self._max_temp}°C | 转速范围 10 ~ {self._max_rotation_speed} RPM") return True async def cleanup(self) -> bool: - """Cleanup virtual rotary evaporator""" - self.logger.info(f"Cleaning up virtual rotary evaporator {self.device_id}") + """Cleanup virtual rotary evaporator 🧹""" + self.logger.info(f"🧹 清理虚拟旋转蒸发仪 {self.device_id} 🔚") self.data.update({ - "status": "Offline", + "status": "💤 离线", "rotavap_state": "Offline", "current_temp": 25.0, "rotation_speed": 0.0, "vacuum_pressure": 1.0, - "message": "System offline" + "message": "💤 System offline" }) + + self.logger.info(f"✅ 旋转蒸发仪 {self.device_id} 清理完成 💤") return True async def evaporate( @@ -78,37 +86,44 @@ class VirtualRotavap: solvent: str = "", **kwargs ) -> bool: - """Execute evaporate action - 简化版""" + """Execute evaporate action - 简化版 🌪️""" # 🔧 简化处理:如果vessel就是设备自己,直接操作 if vessel == self.device_id: - debug_print(f"在设备 {self.device_id} 上直接执行蒸发操作") + debug_print(f"🎯 在设备 {self.device_id} 上直接执行蒸发操作") actual_vessel = self.device_id else: actual_vessel = vessel # 参数预处理 if solvent: - self.logger.info(f"识别到溶剂: {solvent}") + self.logger.info(f"🧪 识别到溶剂: {solvent}") # 根据溶剂调整参数 solvent_lower = solvent.lower() if any(s in solvent_lower for s in ['water', 'aqueous']): temp = max(temp, 80.0) pressure = max(pressure, 0.2) - self.logger.info("水系溶剂:调整参数") + self.logger.info(f"💧 水系溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar") elif any(s in solvent_lower for s in ['ethanol', 'methanol', 'acetone']): temp = min(temp, 50.0) pressure = min(pressure, 0.05) - self.logger.info("易挥发溶剂:调整参数") + self.logger.info(f"⚡ 易挥发溶剂:调整参数 → 温度 {temp}°C, 压力 {pressure} bar") - self.logger.info(f"Evaporate: vessel={actual_vessel}, pressure={pressure} bar, temp={temp}°C, time={time}s, rotation={stir_speed} RPM, solvent={solvent}") + self.logger.info(f"🌪️ 开始蒸发操作: {actual_vessel}") + self.logger.info(f" 🥽 容器: {actual_vessel}") + self.logger.info(f" 🌡️ 温度: {temp}°C") + self.logger.info(f" 💨 真空度: {pressure} bar") + self.logger.info(f" ⏰ 时间: {time}s") + self.logger.info(f" 🌀 转速: {stir_speed} RPM") + if solvent: + self.logger.info(f" 🧪 溶剂: {solvent}") # 验证参数 if temp > self._max_temp or temp < 10.0: - error_msg = f"温度 {temp}°C 超出范围 (10-{self._max_temp}°C)" - self.logger.error(error_msg) + error_msg = f"🌡️ 温度 {temp}°C 超出范围 (10-{self._max_temp}°C) ⚠️" + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"Error: {error_msg}", + "status": f"❌ 错误: 温度超出范围", "rotavap_state": "Error", "current_temp": 25.0, "progress": 0.0, @@ -118,10 +133,10 @@ class VirtualRotavap: return False if stir_speed > self._max_rotation_speed or stir_speed < 10.0: - error_msg = f"旋转速度 {stir_speed} RPM 超出范围 (10-{self._max_rotation_speed} RPM)" - self.logger.error(error_msg) + error_msg = f"🌀 旋转速度 {stir_speed} RPM 超出范围 (10-{self._max_rotation_speed} RPM) ⚠️" + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"Error: {error_msg}", + "status": f"❌ 错误: 转速超出范围", "rotavap_state": "Error", "current_temp": 25.0, "progress": 0.0, @@ -131,10 +146,10 @@ class VirtualRotavap: return False if pressure < 0.01 or pressure > 1.0: - error_msg = f"真空度 {pressure} bar 超出范围 (0.01-1.0 bar)" - self.logger.error(error_msg) + error_msg = f"💨 真空度 {pressure} bar 超出范围 (0.01-1.0 bar) ⚠️" + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"Error: {error_msg}", + "status": f"❌ 错误: 压力超出范围", "rotavap_state": "Error", "current_temp": 25.0, "progress": 0.0, @@ -144,8 +159,10 @@ class VirtualRotavap: return False # 开始蒸发 + self.logger.info(f"🚀 启动蒸发程序! 预计用时 {time/60:.1f}分钟 ⏱️") + self.data.update({ - "status": f"蒸发中: {actual_vessel}", + "status": f"🌪️ 蒸发中: {actual_vessel}", "rotavap_state": "Evaporating", "current_temp": temp, "target_temp": temp, @@ -154,13 +171,14 @@ class VirtualRotavap: "remaining_time": time, "progress": 0.0, "evaporated_volume": 0.0, - "message": f"Evaporating {actual_vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM" + "message": f"🌪️ Evaporating {actual_vessel} at {temp}°C, {pressure} bar, {stir_speed} RPM" }) try: # 蒸发过程 - 实时更新进度 start_time = time_module.time() total_time = time + last_logged_progress = 0 while True: current_time = time_module.time() @@ -168,19 +186,31 @@ class VirtualRotavap: remaining = max(0, total_time - elapsed) progress = min(100.0, (elapsed / total_time) * 100) - # 模拟蒸发体积 - evaporated_vol = progress * 0.8 # 假设最多蒸发80mL + # 模拟蒸发体积 - 根据溶剂类型调整 + if solvent and any(s in solvent.lower() for s in ['water', 'aqueous']): + evaporated_vol = progress * 0.6 # 水系溶剂蒸发慢 + elif solvent and any(s in solvent.lower() for s in ['ethanol', 'methanol', 'acetone']): + evaporated_vol = progress * 1.0 # 易挥发溶剂蒸发快 + else: + evaporated_vol = progress * 0.8 # 默认蒸发量 # 🔧 更新状态 - 确保包含所有必需字段 + status_msg = f"🌪️ 蒸发中: {actual_vessel} | 🌡️ {temp}°C | 💨 {pressure} bar | 🌀 {stir_speed} RPM | 📊 {progress:.1f}% | ⏰ 剩余: {remaining:.0f}s" + self.data.update({ "remaining_time": remaining, - "progress": progress, # 确保这个字段存在 - "evaporated_volume": evaporated_vol, # 确保这个字段存在 - "current_temp": temp, # 确保这个字段存在 - "status": f"蒸发中: {actual_vessel} | {temp}°C | {pressure} bar | {progress:.1f}% | 剩余: {remaining:.0f}s", - "message": f"Evaporating: {progress:.1f}% complete, {remaining:.0f}s remaining" + "progress": progress, + "evaporated_volume": evaporated_vol, + "current_temp": temp, + "status": status_msg, + "message": f"🌪️ Evaporating: {progress:.1f}% complete, 💧 {evaporated_vol:.1f}mL evaporated, ⏰ {remaining:.0f}s remaining" }) + # 进度日志(每25%打印一次) + if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_progress: + self.logger.info(f"📊 蒸发进度: {progress:.0f}% | 💧 已蒸发: {evaporated_vol:.1f}mL | ⏰ 剩余: {remaining:.0f}s ✨") + last_logged_progress = int(progress) + # 时间到了,退出循环 if remaining <= 0: break @@ -189,42 +219,59 @@ class VirtualRotavap: await asyncio.sleep(1.0) # 蒸发完成 - final_evaporated = 80.0 + if solvent and any(s in solvent.lower() for s in ['water', 'aqueous']): + final_evaporated = 60.0 # 水系溶剂 + elif solvent and any(s in solvent.lower() for s in ['ethanol', 'methanol', 'acetone']): + final_evaporated = 100.0 # 易挥发溶剂 + else: + final_evaporated = 80.0 # 默认 + self.data.update({ - "status": f"蒸发完成: {actual_vessel} | 蒸发量: {final_evaporated:.1f}mL", + "status": f"✅ 蒸发完成: {actual_vessel} | 💧 蒸发量: {final_evaporated:.1f}mL", "rotavap_state": "Completed", "evaporated_volume": final_evaporated, "progress": 100.0, - "current_temp": temp, # 保持温度信息 + "current_temp": temp, "remaining_time": 0.0, - "rotation_speed": 0.0, # 停止旋转 - "vacuum_pressure": 1.0, # 恢复大气压 - "message": f"Evaporation completed: {final_evaporated}mL evaporated from {actual_vessel}" + "rotation_speed": 0.0, + "vacuum_pressure": 1.0, + "message": f"✅ Evaporation completed: {final_evaporated}mL evaporated from {actual_vessel}" }) - self.logger.info(f"Evaporation completed: {final_evaporated}mL evaporated from {actual_vessel}") + self.logger.info(f"🎉 蒸发操作完成! ✨") + self.logger.info(f"📊 蒸发结果:") + self.logger.info(f" 🥽 容器: {actual_vessel}") + self.logger.info(f" 💧 蒸发量: {final_evaporated:.1f}mL") + self.logger.info(f" 🌡️ 蒸发温度: {temp}°C") + self.logger.info(f" 💨 真空度: {pressure} bar") + self.logger.info(f" 🌀 旋转速度: {stir_speed} RPM") + self.logger.info(f" ⏱️ 总用时: {total_time:.0f}s") + if solvent: + self.logger.info(f" 🧪 处理溶剂: {solvent} 🏁") + return True except Exception as e: # 出错处理 - self.logger.error(f"Error during evaporation: {str(e)}") + error_msg = f"蒸发过程中发生错误: {str(e)} 💥" + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"蒸发错误: {str(e)}", + "status": f"❌ 蒸发错误: {str(e)}", "rotavap_state": "Error", "current_temp": 25.0, "progress": 0.0, "evaporated_volume": 0.0, "rotation_speed": 0.0, "vacuum_pressure": 1.0, - "message": f"Evaporation failed: {str(e)}" + "message": f"❌ Evaporation failed: {str(e)}" }) return False # === 核心状态属性 === @property def status(self) -> str: - return self.data.get("status", "Unknown") + return self.data.get("status", "❓ Unknown") @property def rotavap_state(self) -> str: diff --git a/unilabos/devices/virtual/virtual_solid_dispenser.py b/unilabos/devices/virtual/virtual_solid_dispenser.py index 48ad9f5..439c348 100644 --- a/unilabos/devices/virtual/virtual_solid_dispenser.py +++ b/unilabos/devices/virtual/virtual_solid_dispenser.py @@ -5,12 +5,12 @@ from typing import Dict, Any, Optional class VirtualSolidDispenser: """ - 虚拟固体粉末加样器 - 用于处理 Add Protocol 中的固体试剂添加 + 虚拟固体粉末加样器 - 用于处理 Add Protocol 中的固体试剂添加 ⚗️ 特点: - - 高兼容性:缺少参数不报错 - - 智能识别:自动查找固体试剂瓶 - - 简单反馈:成功/失败 + 消息 + - 高兼容性:缺少参数不报错 ✅ + - 智能识别:自动查找固体试剂瓶 🔍 + - 简单反馈:成功/失败 + 消息 📊 """ def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs): @@ -29,26 +29,30 @@ class VirtualSolidDispenser: self.logger = logging.getLogger(f"VirtualSolidDispenser.{self.device_id}") - print(f"=== VirtualSolidDispenser {self.device_id} 创建成功! ===") - print(f"=== 最大容量: {self.max_capacity}g, 精度: {self.precision}g ===") + print(f"⚗️ === 虚拟固体分配器 {self.device_id} 创建成功! === ✨") + print(f"📊 设备规格: 最大容量 {self.max_capacity}g | 精度 {self.precision}g 🎯") async def initialize(self) -> bool: - """初始化固体加样器""" - self.logger.info(f"初始化固体加样器 {self.device_id}") + """初始化固体加样器 🚀""" + self.logger.info(f"🔧 初始化固体分配器 {self.device_id} ✨") self._status = "Ready" self._current_reagent = "" self._dispensed_amount = 0.0 + + self.logger.info(f"✅ 固体分配器 {self.device_id} 初始化完成 ⚗️") return True async def cleanup(self) -> bool: - """清理固体加样器""" - self.logger.info(f"清理固体加样器 {self.device_id}") + """清理固体加样器 🧹""" + self.logger.info(f"🧹 清理固体分配器 {self.device_id} 🔚") self._status = "Idle" + + self.logger.info(f"✅ 固体分配器 {self.device_id} 清理完成 💤") return True def parse_mass_string(self, mass_str: str) -> float: """ - 解析质量字符串为数值 (g) + 解析质量字符串为数值 (g) ⚖️ 支持格式: "2.9 g", "19.3g", "4.5 mg", "1.2 kg" 等 """ @@ -63,6 +67,7 @@ class VirtualSolidDispenser: match = re.search(pattern, mass_clean) if not match: + self.logger.debug(f"🔍 无法解析质量字符串: {mass_str}") return 0.0 try: @@ -87,15 +92,18 @@ class VirtualSolidDispenser: } multiplier = unit_multipliers.get(unit, 1.0) - return value * multiplier + result = value * multiplier + + self.logger.debug(f"⚖️ 质量解析: {mass_str} → {result:.6f}g (原值: {value} {unit})") + return result except (ValueError, TypeError): - self.logger.warning(f"无法解析质量字符串: {mass_str}") + self.logger.warning(f"⚠️ 无法解析质量字符串: {mass_str}") return 0.0 def parse_mol_string(self, mol_str: str) -> float: """ - 解析摩尔数字符串为数值 (mol) + 解析摩尔数字符串为数值 (mol) 🧮 支持格式: "0.12 mol", "16.2 mmol", "25.2mmol" 等 """ @@ -110,6 +118,7 @@ class VirtualSolidDispenser: match = re.search(pattern, mol_clean) if not match: + self.logger.debug(f"🔍 无法解析摩尔数字符串: {mol_str}") return 0.0 try: @@ -118,21 +127,25 @@ class VirtualSolidDispenser: # 单位转换为 mol if unit == 'mmol': - return value * 0.001 + result = value * 0.001 else: # mol - return value + result = value + + self.logger.debug(f"🧮 摩尔数解析: {mol_str} → {result:.6f}mol (原值: {value} {unit})") + return result except (ValueError, TypeError): - self.logger.warning(f"无法解析摩尔数字符串: {mol_str}") + self.logger.warning(f"⚠️ 无法解析摩尔数字符串: {mol_str}") return 0.0 def find_solid_reagent_bottle(self, reagent_name: str) -> str: """ - 查找固体试剂瓶 + 查找固体试剂瓶 🔍 这是一个简化版本,实际使用时应该连接到系统的设备图 """ if not reagent_name: + self.logger.debug(f"🔍 未指定试剂名称,使用默认瓶") return "unknown_solid_bottle" # 可能的固体试剂瓶命名模式 @@ -146,7 +159,9 @@ class VirtualSolidDispenser: ] # 这里简化处理,实际应该查询设备图 - return possible_names[0] + selected_bottle = possible_names[0] + self.logger.debug(f"🔍 为试剂 {reagent_name} 选择试剂瓶: {selected_bottle}") + return selected_bottle async def add_solid( self, @@ -158,7 +173,7 @@ class VirtualSolidDispenser: **kwargs # 兼容额外参数 ) -> Dict[str, Any]: """ - 添加固体试剂的主要方法 + 添加固体试剂的主要方法 ⚗️ Args: vessel: 目标容器 @@ -172,22 +187,24 @@ class VirtualSolidDispenser: Dict: 操作结果 """ try: - self.logger.info(f"=== 开始固体加样操作 ===") - self.logger.info(f"目标容器: {vessel}") - self.logger.info(f"试剂: {reagent}") - self.logger.info(f"质量: {mass}") - self.logger.info(f"摩尔数: {mol}") - self.logger.info(f"目的: {purpose}") + self.logger.info(f"⚗️ === 开始固体加样操作 === ✨") + self.logger.info(f" 🥽 目标容器: {vessel}") + self.logger.info(f" 🧪 试剂: {reagent}") + self.logger.info(f" ⚖️ 质量: {mass}") + self.logger.info(f" 🧮 摩尔数: {mol}") + self.logger.info(f" 📝 目的: {purpose}") # 参数验证 - 宽松处理 if not vessel: vessel = "main_reactor" # 默认容器 - self.logger.warning(f"未指定容器,使用默认容器: {vessel}") + self.logger.warning(f"⚠️ 未指定容器,使用默认容器: {vessel} 🏠") if not reagent: + error_msg = "❌ 错误: 必须指定试剂名称" + self.logger.error(error_msg) return { "success": False, - "message": "错误: 必须指定试剂名称", + "message": error_msg, "return_info": "missing_reagent" } @@ -195,36 +212,41 @@ class VirtualSolidDispenser: mass_value = self.parse_mass_string(mass) mol_value = self.parse_mol_string(mol) - self.logger.info(f"解析后 - 质量: {mass_value}g, 摩尔数: {mol_value}mol") + self.logger.info(f"📊 解析结果 - 质量: {mass_value:.6f}g | 摩尔数: {mol_value:.6f}mol") # 确定实际加样量 if mass_value > 0: actual_amount = mass_value amount_unit = "g" - self.logger.info(f"按质量加样: {actual_amount} {amount_unit}") + amount_emoji = "⚖️" + self.logger.info(f"⚖️ 按质量加样: {actual_amount:.6f} {amount_unit}") elif mol_value > 0: # 简化处理:假设分子量为100 g/mol assumed_mw = 100.0 actual_amount = mol_value * assumed_mw amount_unit = "g (from mol)" - self.logger.info(f"按摩尔数加样: {mol_value} mol → {actual_amount} g (假设分子量 {assumed_mw})") + amount_emoji = "🧮" + self.logger.info(f"🧮 按摩尔数加样: {mol_value:.6f} mol → {actual_amount:.6f} g (假设分子量 {assumed_mw})") else: # 没有指定量,使用默认值 actual_amount = 1.0 amount_unit = "g (default)" - self.logger.warning(f"未指定质量或摩尔数,使用默认值: {actual_amount} {amount_unit}") + amount_emoji = "🎯" + self.logger.warning(f"⚠️ 未指定质量或摩尔数,使用默认值: {actual_amount} {amount_unit} 🎯") # 检查容量限制 if actual_amount > self.max_capacity: + error_msg = f"❌ 错误: 请求量 {actual_amount:.3f}g 超过最大容量 {self.max_capacity}g" + self.logger.error(error_msg) return { "success": False, - "message": f"错误: 请求量 {actual_amount}g 超过最大容量 {self.max_capacity}g", + "message": error_msg, "return_info": "exceeds_capacity" } # 查找试剂瓶 reagent_bottle = self.find_solid_reagent_bottle(reagent) - self.logger.info(f"使用试剂瓶: {reagent_bottle}") + self.logger.info(f"🔍 使用试剂瓶: {reagent_bottle}") # 模拟加样过程 self._status = "Dispensing" @@ -233,8 +255,17 @@ class VirtualSolidDispenser: # 计算操作时间 (基于质量) operation_time = max(0.5, actual_amount * 0.1) # 每克0.1秒,最少0.5秒 - self.logger.info(f"开始加样,预计时间: {operation_time:.1f}秒") - await asyncio.sleep(operation_time) + self.logger.info(f"🚀 开始加样,预计时间: {operation_time:.1f}秒 ⏱️") + + # 显示进度的模拟 + steps = max(3, int(operation_time)) + step_time = operation_time / steps + + for i in range(steps): + progress = (i + 1) / steps * 100 + await asyncio.sleep(step_time) + if i % 2 == 0: # 每隔一步显示进度 + self.logger.debug(f"📊 加样进度: {progress:.0f}% | {amount_emoji} 正在分配 {reagent}...") # 更新状态 self._dispensed_amount = actual_amount @@ -242,19 +273,26 @@ class VirtualSolidDispenser: self._status = "Ready" # 成功结果 - success_message = f"成功添加 {reagent} {actual_amount:.3f} {amount_unit} 到 {vessel}" + success_message = f"✅ 成功添加 {reagent} {actual_amount:.6f} {amount_unit} 到 {vessel}" - self.logger.info(f"=== 固体加样完成 ===") - self.logger.info(success_message) + self.logger.info(f"🎉 === 固体加样完成 === ✨") + self.logger.info(f"📊 操作结果:") + self.logger.info(f" ✅ {success_message}") + self.logger.info(f" 🧪 试剂瓶: {reagent_bottle}") + self.logger.info(f" ⏱️ 用时: {operation_time:.1f}秒") + self.logger.info(f" 🎯 总操作次数: {self._total_operations} 🏁") return { "success": True, "message": success_message, - "return_info": f"dispensed_{actual_amount:.3f}g" + "return_info": f"dispensed_{actual_amount:.6f}g", + "dispensed_amount": actual_amount, + "reagent": reagent, + "vessel": vessel } except Exception as e: - error_message = f"固体加样失败: {str(e)}" + error_message = f"❌ 固体加样失败: {str(e)} 💥" self.logger.error(error_message) self._status = "Error" @@ -282,8 +320,8 @@ class VirtualSolidDispenser: return self._total_operations def get_device_info(self) -> Dict[str, Any]: - """获取设备状态信息""" - return { + """获取设备状态信息 📊""" + info = { "device_id": self.device_id, "status": self._status, "current_reagent": self._current_reagent, @@ -292,43 +330,59 @@ class VirtualSolidDispenser: "max_capacity": self.max_capacity, "precision": self.precision } + + self.logger.debug(f"📊 设备信息: 状态={self._status}, 试剂={self._current_reagent}, 加样量={self._dispensed_amount:.6f}g") + return info def __str__(self): - return f"VirtualSolidDispenser({self.device_id}: {self._status}, 最后加样 {self._dispensed_amount:.3f}g)" + status_emoji = "✅" if self._status == "Ready" else "🔄" if self._status == "Dispensing" else "❌" if self._status == "Error" else "🏠" + return f"⚗️ VirtualSolidDispenser({status_emoji} {self.device_id}: {self._status}, 最后加样 {self._dispensed_amount:.3f}g)" # 测试函数 async def test_solid_dispenser(): - """测试固体加样器""" - print("=== 固体加样器测试 ===") + """测试固体加样器 🧪""" + print("⚗️ === 固体加样器测试开始 === 🧪") dispenser = VirtualSolidDispenser("test_dispenser") await dispenser.initialize() # 测试1: 按质量加样 + print(f"\n🧪 测试1: 按质量加样...") result1 = await dispenser.add_solid( vessel="main_reactor", reagent="magnesium", mass="2.9 g" ) - print(f"测试1结果: {result1}") + print(f"📊 测试1结果: {result1}") # 测试2: 按摩尔数加样 + print(f"\n🧮 测试2: 按摩尔数加样...") result2 = await dispenser.add_solid( vessel="main_reactor", reagent="sodium_nitrite", mol="0.28 mol" ) - print(f"测试2结果: {result2}") + print(f"📊 测试2结果: {result2}") # 测试3: 缺少参数 + print(f"\n⚠️ 测试3: 缺少参数测试...") result3 = await dispenser.add_solid( reagent="test_compound" ) - print(f"测试3结果: {result3}") + print(f"📊 测试3结果: {result3}") - print(f"设备信息: {dispenser.get_device_info()}") - print("=== 测试完成 ===") + # 测试4: 超容量测试 + print(f"\n❌ 测试4: 超容量测试...") + result4 = await dispenser.add_solid( + vessel="main_reactor", + reagent="heavy_compound", + mass="150 g" # 超过100g限制 + ) + print(f"📊 测试4结果: {result4}") + + print(f"\n📊 最终设备信息: {dispenser.get_device_info()}") + print(f"✅ === 测试完成 === 🎉") if __name__ == "__main__": diff --git a/unilabos/devices/virtual/virtual_stirrer.py b/unilabos/devices/virtual/virtual_stirrer.py index 874f997..2b9058b 100644 --- a/unilabos/devices/virtual/virtual_stirrer.py +++ b/unilabos/devices/virtual/virtual_stirrer.py @@ -4,7 +4,7 @@ import time as time_module from typing import Dict, Any class VirtualStirrer: - """Virtual stirrer device for StirProtocol testing - 功能完整版""" + """Virtual stirrer device for StirProtocol testing - 功能完整版 🌪️""" def __init__(self, device_id: str = None, config: Dict[str, Any] = None, **kwargs): # 处理可能的不同调用方式 @@ -30,45 +30,69 @@ class VirtualStirrer: for key, value in kwargs.items(): if key not in skip_keys and not hasattr(self, key): setattr(self, key, value) + + print(f"🌪️ === 虚拟搅拌器 {self.device_id} 已创建 === ✨") + print(f"🔧 速度范围: {self._min_speed} ~ {self._max_speed} RPM | 📱 端口: {self.port}") async def initialize(self) -> bool: - """Initialize virtual stirrer""" - self.logger.info(f"Initializing virtual stirrer {self.device_id}") + """Initialize virtual stirrer 🚀""" + self.logger.info(f"🔧 初始化虚拟搅拌器 {self.device_id} ✨") # 初始化状态信息 self.data.update({ - "status": "Idle", + "status": "🏠 待机中", "operation_mode": "Idle", # 操作模式: Idle, Stirring, Settling, Completed, Error "current_vessel": "", # 当前搅拌的容器 "current_speed": 0.0, # 当前搅拌速度 "is_stirring": False, # 是否正在搅拌 "remaining_time": 0.0, # 剩余时间 }) + + self.logger.info(f"✅ 搅拌器 {self.device_id} 初始化完成 🌪️") + self.logger.info(f"📊 设备规格: 速度范围 {self._min_speed} ~ {self._max_speed} RPM") return True async def cleanup(self) -> bool: - """Cleanup virtual stirrer""" - self.logger.info(f"Cleaning up virtual stirrer {self.device_id}") + """Cleanup virtual stirrer 🧹""" + self.logger.info(f"🧹 清理虚拟搅拌器 {self.device_id} 🔚") + self.data.update({ - "status": "Offline", + "status": "💤 离线", "operation_mode": "Offline", "current_vessel": "", "current_speed": 0.0, "is_stirring": False, "remaining_time": 0.0, }) + + self.logger.info(f"✅ 搅拌器 {self.device_id} 清理完成 💤") return True - async def stir(self, stir_time: float, stir_speed: float, settling_time: float) -> bool: - """Execute stir action - 定时搅拌 + 沉降""" - self.logger.info(f"Stir: speed={stir_speed} RPM, time={stir_time}s, settling={settling_time}s") + async def stir(self, stir_time: float, stir_speed: float, settling_time: float, **kwargs) -> bool: + """Execute stir action - 定时搅拌 + 沉降 🌪️""" + + # 🔧 类型转换 - 确保所有参数都是数字类型 + try: + stir_time = float(stir_time) + stir_speed = float(stir_speed) + settling_time = float(settling_time) + except (ValueError, TypeError) as e: + error_msg = f"参数类型转换失败: stir_time={stir_time}, stir_speed={stir_speed}, settling_time={settling_time}, error={e}" + self.logger.error(f"❌ {error_msg}") + self.data.update({ + "status": f"❌ 错误: {error_msg}", + "operation_mode": "Error" + }) + return False + + self.logger.info(f"🌪️ 开始搅拌操作: 速度 {stir_speed} RPM | 时间 {stir_time}s | 沉降 {settling_time}s") # 验证参数 if stir_speed > self._max_speed or stir_speed < self._min_speed: - error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM)" - self.logger.error(error_msg) + error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM) ⚠️" + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"Error: {error_msg}", + "status": f"❌ 错误: 速度超出范围", "operation_mode": "Error" }) return False @@ -77,8 +101,10 @@ class VirtualStirrer: start_time = time_module.time() total_stir_time = stir_time + self.logger.info(f"🚀 开始搅拌阶段: {stir_speed} RPM × {total_stir_time}s ⏱️") + self.data.update({ - "status": f"搅拌中: {stir_speed} RPM | 剩余: {total_stir_time:.0f}s", + "status": f"🌪️ 搅拌中: {stir_speed} RPM | ⏰ 剩余: {total_stir_time:.0f}s", "operation_mode": "Stirring", "current_speed": stir_speed, "is_stirring": True, @@ -86,30 +112,41 @@ class VirtualStirrer: }) # 搅拌过程 - 实时更新剩余时间 + last_logged_time = 0 while True: current_time = time_module.time() elapsed = current_time - start_time remaining = max(0, total_stir_time - elapsed) + progress = (elapsed / total_stir_time) * 100 if total_stir_time > 0 else 100 # 更新状态 self.data.update({ "remaining_time": remaining, - "status": f"搅拌中: {stir_speed} RPM | 剩余: {remaining:.0f}s" + "status": f"🌪️ 搅拌中: {stir_speed} RPM | ⏰ 剩余: {remaining:.0f}s" }) + # 进度日志(每25%打印一次) + if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_time: + self.logger.info(f"📊 搅拌进度: {progress:.0f}% | 🌪️ {stir_speed} RPM | ⏰ 剩余: {remaining:.0f}s ✨") + last_logged_time = int(progress) + # 搅拌时间到了 if remaining <= 0: break await asyncio.sleep(1.0) + self.logger.info(f"✅ 搅拌阶段完成! 🌪️ {stir_speed} RPM × {stir_time}s") + # === 第二阶段:沉降(如果需要)=== if settling_time > 0: start_settling_time = time_module.time() total_settling_time = settling_time + self.logger.info(f"🛑 开始沉降阶段: 停止搅拌 × {total_settling_time}s ⏱️") + self.data.update({ - "status": f"沉降中: 停止搅拌 | 剩余: {total_settling_time:.0f}s", + "status": f"🛑 沉降中: 停止搅拌 | ⏰ 剩余: {total_settling_time:.0f}s", "operation_mode": "Settling", "current_speed": 0.0, "is_stirring": False, @@ -117,52 +154,87 @@ class VirtualStirrer: }) # 沉降过程 - 实时更新剩余时间 + last_logged_settling = 0 while True: current_time = time_module.time() elapsed = current_time - start_settling_time remaining = max(0, total_settling_time - elapsed) + progress = (elapsed / total_settling_time) * 100 if total_settling_time > 0 else 100 # 更新状态 self.data.update({ "remaining_time": remaining, - "status": f"沉降中: 停止搅拌 | 剩余: {remaining:.0f}s" + "status": f"🛑 沉降中: 停止搅拌 | ⏰ 剩余: {remaining:.0f}s" }) + # 进度日志(每25%打印一次) + if progress >= 25 and int(progress) % 25 == 0 and int(progress) != last_logged_settling: + self.logger.info(f"📊 沉降进度: {progress:.0f}% | 🛑 静置中 | ⏰ 剩余: {remaining:.0f}s ✨") + last_logged_settling = int(progress) + # 沉降时间到了 if remaining <= 0: break await asyncio.sleep(1.0) + + self.logger.info(f"✅ 沉降阶段完成! 🛑 静置 {settling_time}s") # === 操作完成 === - settling_info = f" | 沉降: {settling_time:.0f}s" if settling_time > 0 else "" + settling_info = f" | 🛑 沉降: {settling_time:.0f}s" if settling_time > 0 else "" + self.data.update({ - "status": f"完成: 搅拌 {stir_speed} RPM, {stir_time:.0f}s{settling_info}", + "status": f"✅ 完成: 🌪️ 搅拌 {stir_speed} RPM × {stir_time:.0f}s{settling_info}", "operation_mode": "Completed", "current_speed": 0.0, "is_stirring": False, "remaining_time": 0.0, }) - self.logger.info(f"Stir completed: {stir_speed} RPM for {stir_time}s + settling {settling_time}s") + self.logger.info(f"🎉 搅拌操作完成! ✨") + self.logger.info(f"📊 操作总结:") + self.logger.info(f" 🌪️ 搅拌: {stir_speed} RPM × {stir_time}s") + if settling_time > 0: + self.logger.info(f" 🛑 沉降: {settling_time}s") + self.logger.info(f" ⏱️ 总用时: {(stir_time + settling_time):.0f}s 🏁") + return True - async def start_stir(self, vessel: str, stir_speed: float, purpose: str) -> bool: - """Start stir action - 开始持续搅拌""" - self.logger.info(f"StartStir: vessel={vessel}, speed={stir_speed} RPM, purpose={purpose}") + async def start_stir(self, vessel: str, stir_speed: float, purpose: str = "") -> bool: + """Start stir action - 开始持续搅拌 🔄""" - # 验证参数 - if stir_speed > self._max_speed or stir_speed < self._min_speed: - error_msg = f"搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM)" - self.logger.error(error_msg) + # 🔧 类型转换 + try: + stir_speed = float(stir_speed) + vessel = str(vessel) + purpose = str(purpose) + except (ValueError, TypeError) as e: + error_msg = f"参数类型转换错误: {str(e)}" + self.logger.error(f"❌ {error_msg}") self.data.update({ - "status": f"Error: {error_msg}", + "status": f"❌ 错误: {error_msg}", "operation_mode": "Error" }) return False + self.logger.info(f"🔄 启动持续搅拌: {vessel} | 🌪️ {stir_speed} RPM") + if purpose: + self.logger.info(f"📝 搅拌目的: {purpose}") + + # 验证参数 + if stir_speed > self._max_speed or stir_speed < self._min_speed: + error_msg = f"🌪️ 搅拌速度 {stir_speed} RPM 超出范围 ({self._min_speed} - {self._max_speed} RPM) ⚠️" + self.logger.error(f"❌ {error_msg}") + self.data.update({ + "status": f"❌ 错误: 速度超出范围", + "operation_mode": "Error" + }) + return False + + purpose_info = f" | 📝 {purpose}" if purpose else "" + self.data.update({ - "status": f"启动: 持续搅拌 {vessel} at {stir_speed} RPM | {purpose}", + "status": f"🔄 启动: 持续搅拌 {vessel} | 🌪️ {stir_speed} RPM{purpose_info}", "operation_mode": "Stirring", "current_vessel": vessel, "current_speed": stir_speed, @@ -170,16 +242,28 @@ class VirtualStirrer: "remaining_time": -1.0, # -1 表示持续运行 }) + self.logger.info(f"✅ 持续搅拌已启动! 🌪️ {stir_speed} RPM × ♾️ 🚀") return True async def stop_stir(self, vessel: str) -> bool: - """Stop stir action - 停止搅拌""" - self.logger.info(f"StopStir: vessel={vessel}") + """Stop stir action - 停止搅拌 🛑""" + + # 🔧 类型转换 + try: + vessel = str(vessel) + except (ValueError, TypeError) as e: + error_msg = f"参数类型转换错误: {str(e)}" + self.logger.error(f"❌ {error_msg}") + return False current_speed = self.data.get("current_speed", 0.0) + self.logger.info(f"🛑 停止搅拌: {vessel}") + if current_speed > 0: + self.logger.info(f"🌪️ 之前搅拌速度: {current_speed} RPM") + self.data.update({ - "status": f"已停止: {vessel} 搅拌停止 | 之前速度: {current_speed} RPM", + "status": f"🛑 已停止: {vessel} 搅拌停止 | 之前速度: {current_speed} RPM", "operation_mode": "Stopped", "current_vessel": "", "current_speed": 0.0, @@ -187,12 +271,13 @@ class VirtualStirrer: "remaining_time": 0.0, }) + self.logger.info(f"✅ 搅拌器已停止 {vessel} 的搅拌操作 🏁") return True # 状态属性 @property def status(self) -> str: - return self.data.get("status", "Idle") + return self.data.get("status", "🏠 待机中") @property def operation_mode(self) -> str: @@ -212,4 +297,33 @@ class VirtualStirrer: @property def remaining_time(self) -> float: - return self.data.get("remaining_time", 0.0) \ No newline at end of file + return self.data.get("remaining_time", 0.0) + + @property + def max_speed(self) -> float: + return self._max_speed + + @property + def min_speed(self) -> float: + return self._min_speed + + def get_device_info(self) -> Dict[str, Any]: + """获取设备状态信息 📊""" + info = { + "device_id": self.device_id, + "status": self.status, + "operation_mode": self.operation_mode, + "current_vessel": self.current_vessel, + "current_speed": self.current_speed, + "is_stirring": self.is_stirring, + "remaining_time": self.remaining_time, + "max_speed": self._max_speed, + "min_speed": self._min_speed + } + + self.logger.debug(f"📊 设备信息: 模式={self.operation_mode}, 速度={self.current_speed} RPM, 搅拌={self.is_stirring}") + return info + + def __str__(self): + status_emoji = "✅" if self.operation_mode == "Idle" else "🌪️" if self.operation_mode == "Stirring" else "🛑" if self.operation_mode == "Settling" else "❌" + return f"🌪️ VirtualStirrer({status_emoji} {self.device_id}: {self.operation_mode}, {self.current_speed} RPM)" \ No newline at end of file