补充了剩下的几个protocol

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

View File

@@ -166,7 +166,7 @@ class MQTTClient:
status = {"data": device_status.get(device_id, {}), "device_id": device_id} status = {"data": device_status.get(device_id, {}), "device_id": device_id}
address = f"labs/{MQConfig.lab_id}/devices/" address = f"labs/{MQConfig.lab_id}/devices/"
self.client.publish(address, json.dumps(status), qos=2) self.client.publish(address, json.dumps(status), qos=2)
logger.debug(f"Device status published: address: {address}, {status}") # logger.debug(f"Device status published: address: {address}, {status}")
def publish_job_status(self, feedback_data: dict, job_id: str, status: str, return_info: Optional[str] = None): def publish_job_status(self, feedback_data: dict, job_id: str, status: str, return_info: Optional[str] = None):
if self.mqtt_disable: if self.mqtt_disable:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -101,7 +101,7 @@ class VirtualMultiwayValve:
self._target_position = pos self._target_position = pos
# 模拟阀门切换时间 # 模拟阀门切换时间
switch_time = abs(self._current_position - pos) * 0.5 # 每个位置0.1 switch_time = abs(self._current_position - pos) * 0.5 # 每个位置0.5
if switch_time > 0: if switch_time > 0:
self.logger.info(f"⏱️ 阀门移动中... 预计用时: {switch_time:.1f}秒 🔄") self.logger.info(f"⏱️ 阀门移动中... 预计用时: {switch_time:.1f}秒 🔄")
@@ -172,32 +172,32 @@ class VirtualMultiwayValve:
def is_at_position(self, position: int) -> bool: def is_at_position(self, position: int) -> bool:
"""检查是否在指定位置 🎯""" """检查是否在指定位置 🎯"""
result = self._current_position == position result = self._current_position == position
self.logger.debug(f"🎯 位置检查: 当前={self._current_position}, 目标={position}, 匹配={result}") # 删除debug日志self.logger.debug(f"🎯 位置检查: 当前={self._current_position}, 目标={position}, 匹配={result}")
return result return result
def is_at_pump_position(self) -> bool: def is_at_pump_position(self) -> bool:
"""检查是否在transfer pump位置 🚰""" """检查是否在transfer pump位置 🚰"""
result = self._current_position == 0 result = self._current_position == 0
pump_status = "" if result else "" # 删除debug日志pump_status = "是" if result else "否"
self.logger.debug(f"🚰 泵位置检查: {pump_status} (当前位置: {self._current_position})") # 删除debug日志self.logger.debug(f"🚰 泵位置检查: {pump_status} (当前位置: {self._current_position})")
return result return result
def is_at_port(self, port_number: int) -> bool: def is_at_port(self, port_number: int) -> bool:
"""检查是否在指定端口位置 🔌""" """检查是否在指定端口位置 🔌"""
result = self._current_position == port_number result = self._current_position == port_number
port_status = "" if result else "" # 删除debug日志port_status = "是" if result else "否"
self.logger.debug(f"🔌 端口{port_number}检查: {port_status} (当前位置: {self._current_position})") # 删除debug日志self.logger.debug(f"🔌 端口{port_number}检查: {port_status} (当前位置: {self._current_position})")
return result return result
def get_available_positions(self) -> list: def get_available_positions(self) -> list:
"""获取可用位置列表 📋""" """获取可用位置列表 📋"""
positions = list(range(0, self.max_positions + 1)) positions = list(range(0, self.max_positions + 1))
self.logger.debug(f"📋 可用位置: {positions}") # 删除debug日志self.logger.debug(f"📋 可用位置: {positions}")
return positions return positions
def get_available_ports(self) -> Dict[int, str]: def get_available_ports(self) -> Dict[int, str]:
"""获取可用端口映射 🗺️""" """获取可用端口映射 🗺️"""
self.logger.debug(f"🗺️ 端口映射: {self.position_map}") # 删除debug日志self.logger.debug(f"🗺️ 端口映射: {self.position_map}")
return self.position_map.copy() return self.position_map.copy()
def reset(self): def reset(self):
@@ -229,7 +229,7 @@ class VirtualMultiwayValve:
else: else:
flow_path = f"🔌 端口 {self._current_position} 已连接 ({current_port})" flow_path = f"🔌 端口 {self._current_position} 已连接 ({current_port})"
self.logger.debug(f"🌊 当前流路: {flow_path}") # 删除debug日志self.logger.debug(f"🌊 当前流路: {flow_path}")
return flow_path return flow_path
def get_info(self) -> dict: def get_info(self) -> dict:
@@ -247,7 +247,7 @@ class VirtualMultiwayValve:
"position_map": self.position_map "position_map": self.position_map
} }
self.logger.debug(f"📊 阀门信息: 位置={self._current_position}, 状态={self._status}, 端口={self.get_current_port()}") # 删除debug日志self.logger.debug(f"📊 阀门信息: 位置={self._current_position}, 状态={self._status}, 端口={self.get_current_port()}")
return info return info
def __str__(self): def __str__(self):
@@ -264,7 +264,7 @@ class VirtualMultiwayValve:
Args: Args:
command: 目标位置 (0-8) 或位置字符串 command: 目标位置 (0-8) 或位置字符串
""" """
self.logger.debug(f"🎯 兼容性调用: set_valve_position({command})") # 删除debug日志self.logger.debug(f"🎯 兼容性调用: set_valve_position({command})")
return self.set_position(command) return self.set_position(command)

View File

@@ -12,7 +12,7 @@ class VirtualPumpMode(Enum):
class VirtualTransferPump: class VirtualTransferPump:
"""虚拟转移泵类 - 模拟泵的基本功能,无需实际硬件""" """虚拟转移泵类 - 模拟泵的基本功能,无需实际硬件 🚰"""
def __init__(self, device_id: str = None, config: dict = None, **kwargs): def __init__(self, device_id: str = None, config: dict = None, **kwargs):
""" """
@@ -42,20 +42,31 @@ class VirtualTransferPump:
self._max_velocity = 5.0 # float self._max_velocity = 5.0 # float
self._current_volume = 0.0 # float self._current_volume = 0.0 # float
# 🚀 新增:快速模式设置 - 大幅缩短执行时间
self._fast_mode = True # 是否启用快速模式
self._fast_move_time = 1.0 # 快速移动时间(秒)
self._fast_dispense_time = 1.0 # 快速喷射时间(秒)
self.logger = logging.getLogger(f"VirtualTransferPump.{self.device_id}") self.logger = logging.getLogger(f"VirtualTransferPump.{self.device_id}")
print(f"🚰 === 虚拟转移泵 {self.device_id} 已创建 === ✨")
print(f"💨 快速模式: {'启用' if self._fast_mode else '禁用'} | 移动时间: {self._fast_move_time}s | 喷射时间: {self._fast_dispense_time}s")
print(f"📊 最大容量: {self.max_volume}mL | 端口: {self.port}")
async def initialize(self) -> bool: async def initialize(self) -> bool:
"""初始化虚拟泵""" """初始化虚拟泵 🚀"""
self.logger.info(f"Initializing virtual pump {self.device_id}") self.logger.info(f"🔧 初始化虚拟转移泵 {self.device_id}")
self._status = "Idle" self._status = "Idle"
self._position = 0.0 self._position = 0.0
self._current_volume = 0.0 self._current_volume = 0.0
self.logger.info(f"✅ 转移泵 {self.device_id} 初始化完成 🚰")
return True return True
async def cleanup(self) -> bool: async def cleanup(self) -> bool:
"""清理虚拟泵""" """清理虚拟泵 🧹"""
self.logger.info(f"Cleaning up virtual pump {self.device_id}") self.logger.info(f"🧹 清理虚拟转移泵 {self.device_id} 🔚")
self._status = "Idle" self._status = "Idle"
self.logger.info(f"✅ 转移泵 {self.device_id} 清理完成 💤")
return True return True
# 基本属性 # 基本属性
@@ -65,12 +76,12 @@ class VirtualTransferPump:
@property @property
def position(self) -> float: def position(self) -> float:
"""当前柱塞位置 (ml)""" """当前柱塞位置 (ml) 📍"""
return self._position return self._position
@property @property
def current_volume(self) -> float: def current_volume(self) -> float:
"""当前注射器中的体积 (ml)""" """当前注射器中的体积 (ml) 💧"""
return self._current_volume return self._current_volume
@property @property
@@ -82,22 +93,50 @@ class VirtualTransferPump:
return self._transfer_rate return self._transfer_rate
def set_max_velocity(self, velocity: float): def set_max_velocity(self, velocity: float):
"""设置最大速度 (ml/s)""" """设置最大速度 (ml/s) 🌊"""
self._max_velocity = max(0.1, min(50.0, velocity)) # 限制在合理范围内 self._max_velocity = max(0.1, min(50.0, velocity)) # 限制在合理范围内
self.logger.info(f"Set max velocity to {self._max_velocity} ml/s") self.logger.info(f"🌊 设置最大速度为 {self._max_velocity} mL/s")
def get_status(self) -> str: def get_status(self) -> str:
"""获取泵状态""" """获取泵状态 📋"""
return self._status return self._status
async def _simulate_operation(self, duration: float): async def _simulate_operation(self, duration: float):
"""模拟操作延时""" """模拟操作延时 ⏱️"""
self._status = "Busy" self._status = "Busy"
await asyncio.sleep(duration) await asyncio.sleep(duration)
self._status = "Idle" self._status = "Idle"
def _calculate_duration(self, volume: float, velocity: float = None) -> float: def _calculate_duration(self, volume: float, velocity: float = None) -> float:
"""计算操作持续时间""" """
计算操作持续时间 ⏰
🚀 快速模式:保留计算逻辑用于日志显示,但实际使用固定的快速时间
"""
if velocity is None:
velocity = self._max_velocity
# 📊 计算理论时间(用于日志显示)
theoretical_duration = abs(volume) / velocity
# 🚀 如果启用快速模式,使用固定的快速时间
if self._fast_mode:
# 根据操作类型选择快速时间
if abs(volume) > 0.1: # 大于0.1mL的操作
actual_duration = self._fast_move_time
else: # 很小的操作
actual_duration = 0.5
self.logger.debug(f"⚡ 快速模式: 理论时间 {theoretical_duration:.2f}s → 实际时间 {actual_duration:.2f}s")
return actual_duration
else:
# 正常模式使用理论时间
return theoretical_duration
def _calculate_display_duration(self, volume: float, velocity: float = None) -> float:
"""
计算显示用的持续时间(用于日志) 📊
这个函数返回理论计算时间,用于日志显示
"""
if velocity is None: if velocity is None:
velocity = self._max_velocity velocity = self._max_velocity
return abs(volume) / velocity return abs(volume) / velocity
@@ -105,7 +144,7 @@ class VirtualTransferPump:
# 新的set_position方法 - 专门用于SetPumpPosition动作 # 新的set_position方法 - 专门用于SetPumpPosition动作
async def set_position(self, position: float, max_velocity: float = None): async def set_position(self, position: float, max_velocity: float = None):
""" """
移动到绝对位置 - 专门用于SetPumpPosition动作 移动到绝对位置 - 专门用于SetPumpPosition动作 🎯
Args: Args:
position (float): 目标位置 (ml) position (float): 目标位置 (ml)
@@ -122,56 +161,107 @@ class VirtualTransferPump:
# 限制位置在有效范围内 # 限制位置在有效范围内
target_position = max(0.0, min(float(self.max_volume), target_position)) target_position = max(0.0, min(float(self.max_volume), target_position))
# 计算移动距离和时间 # 计算移动距离
volume_to_move = abs(target_position - self._position) volume_to_move = abs(target_position - self._position)
duration = self._calculate_duration(volume_to_move, velocity)
self.logger.info(f"SET_POSITION: Moving to {target_position} ml (current: {self._position} ml), velocity: {velocity} ml/s") # 📊 计算显示用的时间(用于日志)
display_duration = self._calculate_display_duration(volume_to_move, velocity)
# 模拟移动过程 # ⚡ 计算实际执行时间(快速模式)
start_position = self._position actual_duration = self._calculate_duration(volume_to_move, velocity)
steps = 10 if duration > 0.1 else 1 # 如果移动距离很小只用1步
step_duration = duration / steps if steps > 1 else duration
for i in range(steps + 1): # 🎯 确定操作类型和emoji
# 计算当前位置和进度 if target_position > self._position:
progress = (i / steps) * 100 if steps > 0 else 100 operation_type = "吸液"
current_pos = start_position + (target_position - start_position) * (i / steps) if steps > 0 else target_position operation_emoji = "📥"
elif target_position < self._position:
operation_type = "排液"
operation_emoji = "📤"
else:
operation_type = "保持"
operation_emoji = "📍"
self.logger.info(f"🎯 SET_POSITION: {operation_type} {operation_emoji}")
self.logger.info(f" 📍 位置: {self._position:.2f}mL → {target_position:.2f}mL (移动 {volume_to_move:.2f}mL)")
self.logger.info(f" 🌊 速度: {velocity:.2f} mL/s")
self.logger.info(f" ⏰ 预计时间: {display_duration:.2f}s")
if self._fast_mode:
self.logger.info(f" ⚡ 快速模式: 实际用时 {actual_duration:.2f}s")
# 🚀 模拟移动过程
if volume_to_move > 0.01: # 只有当移动距离足够大时才显示进度
start_position = self._position
steps = 5 if actual_duration > 0.5 else 2 # 根据实际时间调整步数
step_duration = actual_duration / steps
# 更新状态 self.logger.info(f"🚀 开始{operation_type}... {operation_emoji}")
self._status = "Moving" if i < steps else "Idle"
self._position = current_pos
self._current_volume = current_pos
# 等待一小步时间 for i in range(steps + 1):
if i < steps and step_duration > 0: # 计算当前位置和进度
await asyncio.sleep(step_duration) progress = (i / steps) * 100 if steps > 0 else 100
current_pos = start_position + (target_position - start_position) * (i / steps) if steps > 0 else target_position
# 更新状态
if i < steps:
self._status = f"{operation_type}"
status_emoji = "🔄"
else:
self._status = "Idle"
status_emoji = ""
self._position = current_pos
self._current_volume = current_pos
# 显示进度每25%或最后一步)
if i == 0:
self.logger.debug(f" 🔄 {operation_type}开始: {progress:.0f}%")
elif progress >= 50 and i == steps // 2:
self.logger.debug(f" 🔄 {operation_type}进度: {progress:.0f}%")
elif i == steps:
self.logger.info(f"{operation_type}完成: {progress:.0f}% | 当前位置: {current_pos:.2f}mL")
# 等待一小步时间
if i < steps and step_duration > 0:
await asyncio.sleep(step_duration)
else:
# 移动距离很小,直接完成
self._position = target_position
self._current_volume = target_position
self.logger.info(f" 📍 微调完成: {target_position:.2f}mL")
# 确保最终位置准确 # 确保最终位置准确
self._position = target_position self._position = target_position
self._current_volume = target_position self._current_volume = target_position
self._status = "Idle" self._status = "Idle"
self.logger.info(f"SET_POSITION: Reached position {self._position} ml, current volume: {self._current_volume} ml") # 📊 最终状态日志
if volume_to_move > 0.01:
self.logger.info(f"🎉 SET_POSITION 完成! 📍 最终位置: {self._position:.2f}mL | 💧 当前体积: {self._current_volume:.2f}mL")
# 返回符合action定义的结果 # 返回符合action定义的结果
return { return {
"success": True, "success": True,
"message": f"Successfully moved to position {self._position} ml" "message": f"✅ 成功移动到位置 {self._position:.2f}mL ({operation_type})",
"final_position": self._position,
"final_volume": self._current_volume,
"operation_type": operation_type
} }
except Exception as e: except Exception as e:
error_msg = f"Failed to set position: {str(e)}" error_msg = f"❌ 设置位置失败: {str(e)}"
self.logger.error(error_msg) self.logger.error(error_msg)
return { return {
"success": False, "success": False,
"message": error_msg "message": error_msg,
"final_position": self._position,
"final_volume": self._current_volume
} }
# 其他泵操作方法 # 其他泵操作方法
async def pull_plunger(self, volume: float, velocity: float = None): async def pull_plunger(self, volume: float, velocity: float = None):
""" """
拉取柱塞(吸液) 拉取柱塞(吸液) 📥
Args: Args:
volume (float): 要拉取的体积 (ml) volume (float): 要拉取的体积 (ml)
@@ -181,23 +271,29 @@ class VirtualTransferPump:
actual_volume = new_position - self._position actual_volume = new_position - self._position
if actual_volume <= 0: if actual_volume <= 0:
self.logger.warning("Cannot pull - already at maximum volume") self.logger.warning("⚠️ 无法吸液 - 已达到最大容量")
return return
duration = self._calculate_duration(actual_volume, velocity) display_duration = self._calculate_display_duration(actual_volume, velocity)
actual_duration = self._calculate_duration(actual_volume, velocity)
self.logger.info(f"Pulling {actual_volume} ml (from {self._position} to {new_position})") self.logger.info(f"📥 开始吸液: {actual_volume:.2f}mL")
self.logger.info(f" 📍 位置: {self._position:.2f}mL → {new_position:.2f}mL")
self.logger.info(f" ⏰ 预计时间: {display_duration:.2f}s")
await self._simulate_operation(duration) if self._fast_mode:
self.logger.info(f" ⚡ 快速模式: 实际用时 {actual_duration:.2f}s")
await self._simulate_operation(actual_duration)
self._position = new_position self._position = new_position
self._current_volume = new_position self._current_volume = new_position
self.logger.info(f"Pulled {actual_volume} ml, current volume: {self._current_volume} ml") self.logger.info(f"✅ 吸液完成: {actual_volume:.2f}mL | 💧 当前体积: {self._current_volume:.2f}mL")
async def push_plunger(self, volume: float, velocity: float = None): async def push_plunger(self, volume: float, velocity: float = None):
""" """
推出柱塞(排液) 推出柱塞(排液) 📤
Args: Args:
volume (float): 要推出的体积 (ml) volume (float): 要推出的体积 (ml)
@@ -207,35 +303,44 @@ class VirtualTransferPump:
actual_volume = self._position - new_position actual_volume = self._position - new_position
if actual_volume <= 0: if actual_volume <= 0:
self.logger.warning("Cannot push - already at minimum volume") self.logger.warning("⚠️ 无法排液 - 已达到最小容量")
return return
duration = self._calculate_duration(actual_volume, velocity) display_duration = self._calculate_display_duration(actual_volume, velocity)
actual_duration = self._calculate_duration(actual_volume, velocity)
self.logger.info(f"Pushing {actual_volume} ml (from {self._position} to {new_position})") self.logger.info(f"📤 开始排液: {actual_volume:.2f}mL")
self.logger.info(f" 📍 位置: {self._position:.2f}mL → {new_position:.2f}mL")
self.logger.info(f" ⏰ 预计时间: {display_duration:.2f}s")
await self._simulate_operation(duration) if self._fast_mode:
self.logger.info(f" ⚡ 快速模式: 实际用时 {actual_duration:.2f}s")
await self._simulate_operation(actual_duration)
self._position = new_position self._position = new_position
self._current_volume = new_position self._current_volume = new_position
self.logger.info(f"Pushed {actual_volume} ml, current volume: {self._current_volume} ml") self.logger.info(f"✅ 排液完成: {actual_volume:.2f}mL | 💧 当前体积: {self._current_volume:.2f}mL")
# 便捷操作方法 # 便捷操作方法
async def aspirate(self, volume: float, velocity: float = None): async def aspirate(self, volume: float, velocity: float = None):
"""吸液操作""" """吸液操作 📥"""
await self.pull_plunger(volume, velocity) await self.pull_plunger(volume, velocity)
async def dispense(self, volume: float, velocity: float = None): async def dispense(self, volume: float, velocity: float = None):
"""排液操作""" """排液操作 📤"""
await self.push_plunger(volume, velocity) await self.push_plunger(volume, velocity)
async def transfer(self, volume: float, aspirate_velocity: float = None, dispense_velocity: float = None): async def transfer(self, volume: float, aspirate_velocity: float = None, dispense_velocity: float = None):
"""转移操作(先吸后排)""" """转移操作(先吸后排) 🔄"""
self.logger.info(f"🔄 开始转移操作: {volume:.2f}mL")
# 吸液 # 吸液
await self.aspirate(volume, aspirate_velocity) await self.aspirate(volume, aspirate_velocity)
# 短暂停顿 # 短暂停顿
self.logger.debug("⏸️ 短暂停顿...")
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
# 排液 # 排液

View File

@@ -211,7 +211,7 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
# 逐步执行工作流 # 逐步执行工作流
step_results = [] step_results = []
for i, action in enumerate(protocol_steps): for i, action in enumerate(protocol_steps):
self.get_logger().info(f"Running step {i + 1}: {action}") # self.get_logger().info(f"Running step {i + 1}: {action}")
if isinstance(action, dict): if isinstance(action, dict):
# 如果是单个动作,直接执行 # 如果是单个动作,直接执行
if action["action_name"] == "wait": if action["action_name"] == "wait":