优化了全protocol的运行时间,除了pumptransfer相关的还没

This commit is contained in:
KCFeng425
2025-07-15 10:31:19 +08:00
parent 23eb1139a9
commit ac294194e6
17 changed files with 1800 additions and 2165 deletions

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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:

View File

@@ -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__":

View File

@@ -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__":

View File

@@ -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()

View File

@@ -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()

File diff suppressed because it is too large Load Diff

View File

@@ -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")
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)

View File

@@ -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

View File

@@ -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)
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

View File

@@ -1,16 +1,20 @@
import time
import logging
from typing import Union, Dict, Optional
class VirtualMultiwayValve:
"""
虚拟九通阀门 - 0号位连接transfer pump1-8号位连接其他设备
虚拟九通阀门 - 0号位连接transfer pump1-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()}")
# 切换到试剂瓶11号位
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}")
# 切换到试剂瓶22号位
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()}")
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}")

View File

@@ -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:

View File

@@ -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__":

View File

@@ -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)
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)"