mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-14 13:14:39 +00:00
* Cleanup registry to be easy-understanding (#76) * delete deprecated mock devices * rename categories * combine chromatographic devices * rename rviz simulation nodes * organic virtual devices * parse vessel_id * run registry completion before merge --------- Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com> * fix: workstation handlers and vessel_id parsing * fix: working dir error when input config path feat: report publish topic when error * modify default discovery_interval to 15s * feat: add trace log level * feat: 添加ChinWe设备控制类,支持串口通信和电机控制功能 (#79) * fix: drop_tips not using auto resource select * fix: discard_tips error * fix: discard_tips * fix: prcxi_res * add: prcxi res fix: startup slow * feat: workstation example * fix pumps and liquid_handler handle * feat: 优化protocol node节点运行日志 * fix all protocol_compilers and remove deprecated devices * feat: 新增use_remote_resource参数 * fix and remove redundant info * bugfixes on organic protocols * fix filter protocol * fix protocol node * 临时兼容错误的driver写法 * fix: prcxi import error * use call_async in all service to avoid deadlock * fix: figure_resource * Update recipe.yaml * add workstation template and battery example * feat: add sk & ak * update workstation base * Create workstation_architecture.md * refactor: workstation_base 重构为仅含业务逻辑,通信和子设备管理交给 ProtocolNode * refactor: ProtocolNode→WorkstationNode * Add:msgs.action (#83) * update: Workstation dev 将版本号从 0.10.3 更新为 0.10.4 (#84) * Add:msgs.action * update: 将版本号从 0.10.3 更新为 0.10.4 * simplify resource system * uncompleted refactor * example for use WorkstationBase * feat: websocket * feat: websocket test * feat: workstation example * feat: action status * fix: station自己的方法注册错误 * fix: 还原protocol node处理方法 * fix: build * fix: missing job_id key * ws test version 1 * ws test version 2 * ws protocol * 增加物料关系上传日志 * 增加物料关系上传日志 * 修正物料关系上传 * 修复工站的tracker实例追踪失效问题 * 增加handle检测,增加material edge关系上传 * 修复event loop错误 * 修复edge上报错误 * 修复async错误 * 更新schema的title字段 * 主机节点信息等支持自动刷新 * 注册表编辑器 * 修复status密集发送时,消息出错 * 增加addr参数 * fix: addr param * fix: addr param * 取消labid 和 强制config输入 * Add action definitions for LiquidHandlerSetGroup and LiquidHandlerTransferGroup - Created LiquidHandlerSetGroup.action with fields for group name, wells, and volumes. - Created LiquidHandlerTransferGroup.action with fields for source and target group names and unit volume. - Both actions include response fields for return information and success status. * Add LiquidHandlerSetGroup and LiquidHandlerTransferGroup actions to CMakeLists * Add set_group and transfer_group methods to PRCXI9300Handler and update liquid_handler.yaml * result_info改为字典类型 * 新增uat的地址替换 * runze multiple pump support (cherry picked from commit49354fcf39) * remove runze multiple software obtainer (cherry picked from commit8bcc92a394) * support multiple backbone (cherry picked from commit4771ff2347) * Update runze pump format * Correct runze multiple backbone * Update runze_multiple_backbone * Correct runze pump multiple receive method. * Correct runze pump multiple receive method. * 对于PRCXI9320的transfer_group,一对多和多对多 * 移除MQTT,更新launch文档,提供注册表示例文件,更新到0.10.5 * fix import error * fix dupe upload registry * refactor ws client * add server timeout * Fix: run-column with correct vessel id (#86) * fix run_column * Update run_column_protocol.py (cherry picked from commite5aa4d940a) * resource_update use resource_add * 新增版位推荐功能 * 重新规定了版位推荐的入参 * update registry with nested obj * fix protocol node log_message, added create_resource return value * fix protocol node log_message, added create_resource return value * try fix add protocol * fix resource_add * 修复移液站错误的aspirate注册表 * Feature/xprbalance-zhida (#80) * feat(devices): add Zhida GC/MS pretreatment automation workstation * feat(devices): add mettler_toledo xpr balance * balance * 重新补全zhida注册表 * PRCXI9320 json * PRCXI9320 json * PRCXI9320 json * fix resource download * remove class for resource * bump version to 0.10.6 * 更新所有注册表 * 修复protocolnode的兼容性 * 修复protocolnode的兼容性 * Update install md * Add Defaultlayout * 更新物料接口 * fix dict to tree/nested-dict converter * coin_cell_station draft * refactor: rename "station_resource" to "deck" * add standardized BIOYOND resources: bottle_carrier, bottle * refactor and add BIOYOND resources tests * add BIOYOND deck assignment and pass all tests * fix: update resource with correct structure; remove deprecated liquid_handler set_group action * feat: 将新威电池测试系统驱动与配置文件并入 workstation_dev_YB2 (#92) * feat: 新威电池测试系统驱动与注册文件 * feat: bring neware driver & battery.json into workstation_dev_YB2 * add bioyond studio draft * bioyond station with communication init and resource sync * fix bioyond station and registry * fix: update resource with correct structure; remove deprecated liquid_handler set_group action * frontend_docs * create/update resources with POST/PUT for big amount/ small amount data * create/update resources with POST/PUT for big amount/ small amount data * refactor: add itemized_carrier instead of carrier consists of ResourceHolder * create warehouse by factory func * update bioyond launch json * add child_size for itemized_carrier * fix bioyond resource io * Workstation templates: Resources and its CRUD, and workstation tasks (#95) * coin_cell_station draft * refactor: rename "station_resource" to "deck" * add standardized BIOYOND resources: bottle_carrier, bottle * refactor and add BIOYOND resources tests * add BIOYOND deck assignment and pass all tests * fix: update resource with correct structure; remove deprecated liquid_handler set_group action * feat: 将新威电池测试系统驱动与配置文件并入 workstation_dev_YB2 (#92) * feat: 新威电池测试系统驱动与注册文件 * feat: bring neware driver & battery.json into workstation_dev_YB2 * add bioyond studio draft * bioyond station with communication init and resource sync * fix bioyond station and registry * create/update resources with POST/PUT for big amount/ small amount data * refactor: add itemized_carrier instead of carrier consists of ResourceHolder * create warehouse by factory func * update bioyond launch json * add child_size for itemized_carrier * fix bioyond resource io --------- Co-authored-by: h840473807 <47357934+h840473807@users.noreply.github.com> Co-authored-by: Xie Qiming <97236197+Andy6M@users.noreply.github.com> * 更新物料接口 * Workstation dev yb2 (#100) * Refactor and extend reaction station action messages * Refactor dispensing station tasks to enhance parameter clarity and add batch processing capabilities - Updated `create_90_10_vial_feeding_task` to include detailed parameters for 90%/10% vial feeding, improving clarity and usability. - Introduced `create_batch_90_10_vial_feeding_task` for batch processing of 90%/10% vial feeding tasks with JSON formatted input. - Added `create_batch_diamine_solution_task` for batch preparation of diamine solution, also utilizing JSON formatted input. - Refined `create_diamine_solution_task` to include additional parameters for better task configuration. - Enhanced schema descriptions and default values for improved user guidance. * 修复to_plr_resources * add update remove * 支持选择器注册表自动生成 支持转运物料 * 修复资源添加 * 修复transfer_resource_to_another生成 * 更新transfer_resource_to_another参数,支持spot入参 * 新增test_resource动作 * fix host_node error * fix host_node test_resource error * fix host_node test_resource error * 过滤本地动作 * 移动内部action以兼容host node * 修复同步任务报错不显示的bug * feat: 允许返回非本节点物料,后面可以通过decoration进行区分,就不进行warning了 * update todo * modify bioyond/plr converter, bioyond resource registry, and tests * pass the tests * update todo * add conda-pack-build.yml * add auto install script for conda-pack-build.yml (cherry picked from commit172599adcf) * update conda-pack-build.yml * update conda-pack-build.yml * update conda-pack-build.yml * update conda-pack-build.yml * update conda-pack-build.yml * Add version in __init__.py Update conda-pack-build.yml Add create_zip_archive.py * Update conda-pack-build.yml * Update conda-pack-build.yml (with mamba) * Update conda-pack-build.yml * Fix FileNotFoundError * Try fix 'charmap' codec can't encode characters in position 16-23: character maps to <undefined> * Fix unilabos msgs search error * Fix environment_check.py * Update recipe.yaml * Update registry. Update uuid loop figure method. Update install docs. * Fix nested conda pack * Fix one-key installation path error * Bump version to 0.10.7 * Workshop bj (#99) * Add LaiYu Liquid device integration and tests Introduce LaiYu Liquid device implementation, including backend, controllers, drivers, configuration, and resource files. Add hardware connection, tip pickup, and simplified test scripts, as well as experiment and registry configuration for LaiYu Liquid. Documentation and .gitignore for the device are also included. * feat(LaiYu_Liquid): 重构设备模块结构并添加硬件文档 refactor: 重新组织LaiYu_Liquid模块目录结构 docs: 添加SOPA移液器和步进电机控制指令文档 fix: 修正设备配置中的最大体积默认值 test: 新增工作台配置测试用例 chore: 删除过时的测试脚本和配置文件 * add * 重构: 将 LaiYu_Liquid.py 重命名为 laiyu_liquid_main.py 并更新所有导入引用 - 使用 git mv 将 LaiYu_Liquid.py 重命名为 laiyu_liquid_main.py - 更新所有相关文件中的导入引用 - 保持代码功能不变,仅改善命名一致性 - 测试确认所有导入正常工作 * 修复: 在 core/__init__.py 中添加 LaiYuLiquidBackend 导出 - 添加 LaiYuLiquidBackend 到导入列表 - 添加 LaiYuLiquidBackend 到 __all__ 导出列表 - 确保所有主要类都可以正确导入 * 修复大小写文件夹名字 * 电池装配工站二次开发教程(带目录)上传至dev (#94) * 电池装配工站二次开发教程 * Update intro.md * 物料教程 * 更新物料教程,json格式注释 * Update prcxi driver & fix transfer_liquid mix_times (#90) * Update prcxi driver & fix transfer_liquid mix_times * fix: correct mix_times type * Update liquid_handler registry * test: prcxi.py * Update registry from pr * fix ony-key script not exist * clean files --------- Co-authored-by: Junhan Chang <changjh@dp.tech> Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com> Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com> Co-authored-by: Xie Qiming <97236197+Andy6M@users.noreply.github.com> Co-authored-by: h840473807 <47357934+h840473807@users.noreply.github.com> Co-authored-by: LccLink <1951855008@qq.com> Co-authored-by: lixinyu1011 <61094742+lixinyu1011@users.noreply.github.com> Co-authored-by: shiyubo0410 <shiyubo@dp.tech>
752 lines
30 KiB
Python
752 lines
30 KiB
Python
from functools import partial
|
||
|
||
import networkx as nx
|
||
import logging
|
||
import uuid
|
||
import sys
|
||
from typing import List, Dict, Any, Optional
|
||
from .utils.vessel_parser import get_vessel
|
||
from .utils.logger_util import action_log
|
||
from .pump_protocol import generate_pump_protocol_with_rinsing, generate_pump_protocol
|
||
|
||
# 设置日志
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 确保输出编码为UTF-8
|
||
if hasattr(sys.stdout, 'reconfigure'):
|
||
try:
|
||
sys.stdout.reconfigure(encoding='utf-8')
|
||
sys.stderr.reconfigure(encoding='utf-8')
|
||
except:
|
||
pass
|
||
|
||
def debug_print(message):
|
||
"""调试输出函数 - 支持中文"""
|
||
try:
|
||
# 确保消息是字符串格式
|
||
safe_message = str(message)
|
||
logger.info(f"[抽真空充气] {safe_message}")
|
||
except UnicodeEncodeError:
|
||
# 如果编码失败,尝试替换不支持的字符
|
||
safe_message = str(message).encode('utf-8', errors='replace').decode('utf-8')
|
||
logger.info(f"[抽真空充气] {safe_message}")
|
||
except Exception as e:
|
||
# 最后的安全措施
|
||
fallback_message = f"日志输出错误: {repr(message)}"
|
||
logger.info(f"[抽真空充气] {fallback_message}")
|
||
|
||
create_action_log = partial(action_log, prefix="[抽真空充气]")
|
||
|
||
def find_gas_source(G: nx.DiGraph, gas: str) -> str:
|
||
"""
|
||
根据气体名称查找对应的气源,支持多种匹配模式:
|
||
1. 容器名称匹配
|
||
2. 气体类型匹配(data.gas_type)
|
||
3. 默认气源
|
||
"""
|
||
debug_print(f"🔍 正在查找气体 '{gas}' 的气源...")
|
||
|
||
# 第一步:通过容器名称匹配
|
||
debug_print(f"📋 方法1: 容器名称匹配...")
|
||
gas_source_patterns = [
|
||
f"gas_source_{gas}",
|
||
f"gas_{gas}",
|
||
f"flask_{gas}",
|
||
f"{gas}_source",
|
||
f"source_{gas}",
|
||
f"reagent_bottle_{gas}",
|
||
f"bottle_{gas}"
|
||
]
|
||
|
||
debug_print(f"🎯 尝试的容器名称: {gas_source_patterns}")
|
||
|
||
for pattern in gas_source_patterns:
|
||
if pattern in G.nodes():
|
||
debug_print(f"✅ 通过名称找到气源: {pattern}")
|
||
return pattern
|
||
|
||
# 第二步:通过气体类型匹配 (data.gas_type)
|
||
debug_print(f"📋 方法2: 气体类型匹配...")
|
||
for node_id in G.nodes():
|
||
node_data = G.nodes[node_id]
|
||
node_class = node_data.get('class', '') or ''
|
||
|
||
# 检查是否是气源设备
|
||
if ('gas_source' in node_class or
|
||
'gas' in node_id.lower() or
|
||
node_id.startswith('flask_')):
|
||
|
||
# 检查 data.gas_type
|
||
data = node_data.get('data', {})
|
||
gas_type = data.get('gas_type', '')
|
||
|
||
if gas_type.lower() == gas.lower():
|
||
debug_print(f"✅ 通过气体类型找到气源: {node_id} (气体类型: {gas_type})")
|
||
return node_id
|
||
|
||
# 检查 config.gas_type
|
||
config = node_data.get('config', {})
|
||
config_gas_type = config.get('gas_type', '')
|
||
|
||
if config_gas_type.lower() == gas.lower():
|
||
debug_print(f"✅ 通过配置气体类型找到气源: {node_id} (配置气体类型: {config_gas_type})")
|
||
return node_id
|
||
|
||
# 第三步:查找所有可用的气源设备
|
||
debug_print(f"📋 方法3: 查找可用气源...")
|
||
available_gas_sources = []
|
||
for node_id in G.nodes():
|
||
node_data = G.nodes[node_id]
|
||
node_class = node_data.get('class', '') or ''
|
||
|
||
if ('gas_source' in node_class or
|
||
'gas' in node_id.lower() or
|
||
(node_id.startswith('flask_') and any(g in node_id.lower() for g in ['air', 'nitrogen', 'argon']))):
|
||
|
||
data = node_data.get('data', {})
|
||
gas_type = data.get('gas_type', '未知')
|
||
available_gas_sources.append(f"{node_id} (气体类型: {gas_type})")
|
||
|
||
debug_print(f"📊 可用气源: {available_gas_sources}")
|
||
|
||
# 第四步:如果找不到特定气体,使用默认的第一个气源
|
||
debug_print(f"📋 方法4: 查找默认气源...")
|
||
default_gas_sources = [
|
||
node for node in G.nodes()
|
||
if ((G.nodes[node].get('class') or '').find('virtual_gas_source') != -1
|
||
or 'gas_source' in node)
|
||
]
|
||
|
||
if default_gas_sources:
|
||
default_source = default_gas_sources[0]
|
||
debug_print(f"⚠️ 未找到特定气体 '{gas}',使用默认气源: {default_source}")
|
||
return default_source
|
||
|
||
debug_print(f"❌ 所有方法都失败了!")
|
||
raise ValueError(f"无法找到气体 '{gas}' 的气源。可用气源: {available_gas_sources}")
|
||
|
||
def find_vacuum_pump(G: nx.DiGraph) -> str:
|
||
"""查找真空泵设备"""
|
||
debug_print("🔍 正在查找真空泵...")
|
||
|
||
vacuum_pumps = []
|
||
for node in G.nodes():
|
||
node_data = G.nodes[node]
|
||
node_class = node_data.get('class', '') or ''
|
||
|
||
if ('virtual_vacuum_pump' in node_class or
|
||
'vacuum_pump' in node.lower() or
|
||
'vacuum' in node_class.lower()):
|
||
vacuum_pumps.append(node)
|
||
debug_print(f"📋 发现真空泵: {node}")
|
||
|
||
if not vacuum_pumps:
|
||
debug_print(f"❌ 系统中未找到真空泵")
|
||
raise ValueError("系统中未找到真空泵")
|
||
|
||
debug_print(f"✅ 使用真空泵: {vacuum_pumps[0]}")
|
||
return vacuum_pumps[0]
|
||
|
||
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> Optional[str]:
|
||
"""查找与指定容器相连的搅拌器"""
|
||
debug_print(f"🔍 正在查找与容器 {vessel} 连接的搅拌器...")
|
||
|
||
stirrer_nodes = []
|
||
for node in G.nodes():
|
||
node_data = G.nodes[node]
|
||
node_class = node_data.get('class', '') or ''
|
||
|
||
if 'virtual_stirrer' in node_class or 'stirrer' in node.lower():
|
||
stirrer_nodes.append(node)
|
||
debug_print(f"📋 发现搅拌器: {node}")
|
||
|
||
debug_print(f"📊 找到的搅拌器总数: {len(stirrer_nodes)}")
|
||
|
||
# 检查哪个搅拌器与目标容器相连
|
||
for stirrer in stirrer_nodes:
|
||
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
|
||
debug_print(f"✅ 找到连接的搅拌器: {stirrer}")
|
||
return stirrer
|
||
|
||
# 如果没有连接的搅拌器,返回第一个可用的
|
||
if stirrer_nodes:
|
||
debug_print(f"⚠️ 未找到直接连接的搅拌器,使用第一个可用的: {stirrer_nodes[0]}")
|
||
return stirrer_nodes[0]
|
||
|
||
debug_print("❌ 未找到搅拌器")
|
||
return None
|
||
|
||
def find_vacuum_solenoid_valve(G: nx.DiGraph, vacuum_pump: str) -> Optional[str]:
|
||
"""查找真空泵相关的电磁阀"""
|
||
debug_print(f"🔍 正在查找真空泵 {vacuum_pump} 的电磁阀...")
|
||
|
||
# 查找所有电磁阀
|
||
solenoid_valves = []
|
||
for node in G.nodes():
|
||
node_data = G.nodes[node]
|
||
node_class = node_data.get('class', '') or ''
|
||
|
||
if ('solenoid' in node_class.lower() or 'solenoid_valve' in node.lower()):
|
||
solenoid_valves.append(node)
|
||
debug_print(f"📋 发现电磁阀: {node}")
|
||
|
||
debug_print(f"📊 找到的电磁阀: {solenoid_valves}")
|
||
|
||
# 检查连接关系
|
||
debug_print(f"📋 方法1: 检查连接关系...")
|
||
for solenoid in solenoid_valves:
|
||
if G.has_edge(solenoid, vacuum_pump) or G.has_edge(vacuum_pump, solenoid):
|
||
debug_print(f"✅ 找到连接的真空电磁阀: {solenoid}")
|
||
return solenoid
|
||
|
||
# 通过命名规则查找
|
||
debug_print(f"📋 方法2: 检查命名规则...")
|
||
for solenoid in solenoid_valves:
|
||
if 'vacuum' in solenoid.lower() or solenoid == 'solenoid_valve_1':
|
||
debug_print(f"✅ 通过命名找到真空电磁阀: {solenoid}")
|
||
return solenoid
|
||
|
||
debug_print("⚠️ 未找到真空电磁阀")
|
||
return None
|
||
|
||
def find_gas_solenoid_valve(G: nx.DiGraph, gas_source: str) -> Optional[str]:
|
||
"""查找气源相关的电磁阀"""
|
||
debug_print(f"🔍 正在查找气源 {gas_source} 的电磁阀...")
|
||
|
||
# 查找所有电磁阀
|
||
solenoid_valves = []
|
||
for node in G.nodes():
|
||
node_data = G.nodes[node]
|
||
node_class = node_data.get('class', '') or ''
|
||
|
||
if ('solenoid' in node_class.lower() or 'solenoid_valve' in node.lower()):
|
||
solenoid_valves.append(node)
|
||
|
||
debug_print(f"📊 找到的电磁阀: {solenoid_valves}")
|
||
|
||
# 检查连接关系
|
||
debug_print(f"📋 方法1: 检查连接关系...")
|
||
for solenoid in solenoid_valves:
|
||
if G.has_edge(gas_source, solenoid) or G.has_edge(solenoid, gas_source):
|
||
debug_print(f"✅ 找到连接的气源电磁阀: {solenoid}")
|
||
return solenoid
|
||
|
||
# 通过命名规则查找
|
||
debug_print(f"📋 方法2: 检查命名规则...")
|
||
for solenoid in solenoid_valves:
|
||
if 'gas' in solenoid.lower() or solenoid == 'solenoid_valve_2':
|
||
debug_print(f"✅ 通过命名找到气源电磁阀: {solenoid}")
|
||
return solenoid
|
||
|
||
debug_print("⚠️ 未找到气源电磁阀")
|
||
return None
|
||
|
||
def generate_evacuateandrefill_protocol(
|
||
G: nx.DiGraph,
|
||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||
gas: str,
|
||
**kwargs
|
||
) -> List[Dict[str, Any]]:
|
||
"""
|
||
生成抽真空和充气操作的动作序列 - 中文版
|
||
|
||
Args:
|
||
G: 设备图
|
||
vessel: 目标容器字典(必需)
|
||
gas: 气体名称(必需)
|
||
**kwargs: 其他参数(兼容性)
|
||
|
||
Returns:
|
||
List[Dict[str, Any]]: 动作序列
|
||
"""
|
||
|
||
# 🔧 核心修改:从字典中提取容器ID
|
||
vessel_id, vessel_data = get_vessel(vessel)
|
||
|
||
# 硬编码重复次数为 3
|
||
repeats = 3
|
||
|
||
# 生成协议ID
|
||
protocol_id = str(uuid.uuid4())
|
||
debug_print(f"🆔 生成协议ID: {protocol_id}")
|
||
|
||
debug_print("=" * 60)
|
||
debug_print("🧪 开始生成抽真空充气协议")
|
||
debug_print(f"📋 原始参数:")
|
||
debug_print(f" 🥼 vessel: {vessel} (ID: {vessel_id})")
|
||
debug_print(f" 💨 气体: '{gas}'")
|
||
debug_print(f" 🔄 循环次数: {repeats} (硬编码)")
|
||
debug_print(f" 📦 其他参数: {kwargs}")
|
||
debug_print("=" * 60)
|
||
|
||
action_sequence = []
|
||
|
||
# === 参数验证和修正 ===
|
||
debug_print("🔍 步骤1: 参数验证和修正...")
|
||
action_sequence.append(create_action_log(f"开始抽真空充气操作 - 容器: {vessel_id}", "🎬"))
|
||
action_sequence.append(create_action_log(f"目标气体: {gas}", "💨"))
|
||
action_sequence.append(create_action_log(f"循环次数: {repeats}", "🔄"))
|
||
|
||
# 验证必需参数
|
||
if not vessel_id:
|
||
debug_print("❌ 容器参数不能为空")
|
||
raise ValueError("容器参数不能为空")
|
||
|
||
if not gas:
|
||
debug_print("❌ 气体参数不能为空")
|
||
raise ValueError("气体参数不能为空")
|
||
|
||
if vessel_id not in G.nodes(): # 🔧 使用 vessel_id
|
||
debug_print(f"❌ 容器 '{vessel_id}' 在系统中不存在")
|
||
raise ValueError(f"容器 '{vessel_id}' 在系统中不存在")
|
||
|
||
debug_print("✅ 基本参数验证通过")
|
||
action_sequence.append(create_action_log("参数验证通过", "✅"))
|
||
|
||
# 标准化气体名称
|
||
debug_print("🔧 标准化气体名称...")
|
||
gas_aliases = {
|
||
'n2': 'nitrogen',
|
||
'ar': 'argon',
|
||
'air': 'air',
|
||
'o2': 'oxygen',
|
||
'co2': 'carbon_dioxide',
|
||
'h2': 'hydrogen',
|
||
'氮气': 'nitrogen',
|
||
'氩气': 'argon',
|
||
'空气': 'air',
|
||
'氧气': 'oxygen',
|
||
'二氧化碳': 'carbon_dioxide',
|
||
'氢气': 'hydrogen'
|
||
}
|
||
|
||
original_gas = gas
|
||
gas_lower = gas.lower().strip()
|
||
if gas_lower in gas_aliases:
|
||
gas = gas_aliases[gas_lower]
|
||
debug_print(f"🔄 标准化气体名称: {original_gas} -> {gas}")
|
||
action_sequence.append(create_action_log(f"气体名称标准化: {original_gas} -> {gas}", "🔄"))
|
||
|
||
debug_print(f"📋 最终参数: 容器={vessel_id}, 气体={gas}, 重复={repeats}")
|
||
|
||
# === 查找设备 ===
|
||
debug_print("🔍 步骤2: 查找设备...")
|
||
action_sequence.append(create_action_log("正在查找相关设备...", "🔍"))
|
||
|
||
try:
|
||
vacuum_pump = find_vacuum_pump(G)
|
||
action_sequence.append(create_action_log(f"找到真空泵: {vacuum_pump}", "🌪️"))
|
||
|
||
gas_source = find_gas_source(G, gas)
|
||
action_sequence.append(create_action_log(f"找到气源: {gas_source}", "💨"))
|
||
|
||
vacuum_solenoid = find_vacuum_solenoid_valve(G, vacuum_pump)
|
||
if vacuum_solenoid:
|
||
action_sequence.append(create_action_log(f"找到真空电磁阀: {vacuum_solenoid}", "🚪"))
|
||
else:
|
||
action_sequence.append(create_action_log("未找到真空电磁阀", "⚠️"))
|
||
|
||
gas_solenoid = find_gas_solenoid_valve(G, gas_source)
|
||
if gas_solenoid:
|
||
action_sequence.append(create_action_log(f"找到气源电磁阀: {gas_solenoid}", "🚪"))
|
||
else:
|
||
action_sequence.append(create_action_log("未找到气源电磁阀", "⚠️"))
|
||
|
||
stirrer_id = find_connected_stirrer(G, vessel_id) # 🔧 使用 vessel_id
|
||
if stirrer_id:
|
||
action_sequence.append(create_action_log(f"找到搅拌器: {stirrer_id}", "🌪️"))
|
||
else:
|
||
action_sequence.append(create_action_log("未找到搅拌器", "⚠️"))
|
||
|
||
debug_print(f"📊 设备配置:")
|
||
debug_print(f" 🌪️ 真空泵: {vacuum_pump}")
|
||
debug_print(f" 💨 气源: {gas_source}")
|
||
debug_print(f" 🚪 真空电磁阀: {vacuum_solenoid}")
|
||
debug_print(f" 🚪 气源电磁阀: {gas_solenoid}")
|
||
debug_print(f" 🌪️ 搅拌器: {stirrer_id}")
|
||
|
||
except Exception as e:
|
||
debug_print(f"❌ 设备查找失败: {str(e)}")
|
||
action_sequence.append(create_action_log(f"设备查找失败: {str(e)}", "❌"))
|
||
raise ValueError(f"设备查找失败: {str(e)}")
|
||
|
||
# === 参数设置 ===
|
||
debug_print("🔍 步骤3: 参数设置...")
|
||
action_sequence.append(create_action_log("设置操作参数...", "⚙️"))
|
||
|
||
# 根据气体类型调整参数
|
||
if gas.lower() in ['nitrogen', 'argon']:
|
||
VACUUM_VOLUME = 25.0
|
||
REFILL_VOLUME = 25.0
|
||
PUMP_FLOW_RATE = 2.0
|
||
VACUUM_TIME = 30.0
|
||
REFILL_TIME = 20.0
|
||
debug_print("💨 惰性气体: 使用标准参数")
|
||
action_sequence.append(create_action_log("检测到惰性气体,使用标准参数", "💨"))
|
||
elif gas.lower() in ['air', 'oxygen']:
|
||
VACUUM_VOLUME = 20.0
|
||
REFILL_VOLUME = 20.0
|
||
PUMP_FLOW_RATE = 1.5
|
||
VACUUM_TIME = 45.0
|
||
REFILL_TIME = 25.0
|
||
debug_print("🔥 活性气体: 使用保守参数")
|
||
action_sequence.append(create_action_log("检测到活性气体,使用保守参数", "🔥"))
|
||
else:
|
||
VACUUM_VOLUME = 15.0
|
||
REFILL_VOLUME = 15.0
|
||
PUMP_FLOW_RATE = 1.0
|
||
VACUUM_TIME = 60.0
|
||
REFILL_TIME = 30.0
|
||
debug_print("❓ 未知气体: 使用安全参数")
|
||
action_sequence.append(create_action_log("未知气体类型,使用安全参数", "❓"))
|
||
|
||
STIR_SPEED = 200.0
|
||
|
||
debug_print(f"⚙️ 操作参数:")
|
||
debug_print(f" 📏 真空体积: {VACUUM_VOLUME}mL")
|
||
debug_print(f" 📏 充气体积: {REFILL_VOLUME}mL")
|
||
debug_print(f" ⚡ 泵流速: {PUMP_FLOW_RATE}mL/s")
|
||
debug_print(f" ⏱️ 真空时间: {VACUUM_TIME}s")
|
||
debug_print(f" ⏱️ 充气时间: {REFILL_TIME}s")
|
||
debug_print(f" 🌪️ 搅拌速度: {STIR_SPEED}RPM")
|
||
|
||
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: 路径验证...")
|
||
action_sequence.append(create_action_log("验证传输路径...", "🛤️"))
|
||
|
||
try:
|
||
# 验证抽真空路径
|
||
if nx.has_path(G, vessel_id, vacuum_pump): # 🔧 使用 vessel_id
|
||
vacuum_path = nx.shortest_path(G, source=vessel_id, target=vacuum_pump)
|
||
debug_print(f"✅ 真空路径: {' -> '.join(vacuum_path)}")
|
||
action_sequence.append(create_action_log(f"真空路径: {' -> '.join(vacuum_path)}", "🛤️"))
|
||
else:
|
||
debug_print(f"⚠️ 真空路径不存在,继续执行但可能有问题")
|
||
action_sequence.append(create_action_log("真空路径检查: 路径不存在", "⚠️"))
|
||
|
||
# 验证充气路径
|
||
if nx.has_path(G, gas_source, vessel_id): # 🔧 使用 vessel_id
|
||
gas_path = nx.shortest_path(G, source=gas_source, target=vessel_id)
|
||
debug_print(f"✅ 气体路径: {' -> '.join(gas_path)}")
|
||
action_sequence.append(create_action_log(f"气体路径: {' -> '.join(gas_path)}", "🛤️"))
|
||
else:
|
||
debug_print(f"⚠️ 气体路径不存在,继续执行但可能有问题")
|
||
action_sequence.append(create_action_log("气体路径检查: 路径不存在", "⚠️"))
|
||
|
||
except Exception as e:
|
||
debug_print(f"⚠️ 路径验证失败: {str(e)},继续执行")
|
||
action_sequence.append(create_action_log(f"路径验证失败: {str(e)}", "⚠️"))
|
||
|
||
# === 启动搅拌器 ===
|
||
debug_print("🔍 步骤5: 启动搅拌器...")
|
||
|
||
if stirrer_id:
|
||
debug_print(f"🌪️ 启动搅拌器: {stirrer_id}")
|
||
action_sequence.append(create_action_log(f"启动搅拌器 {stirrer_id} (速度: {STIR_SPEED}rpm)", "🌪️"))
|
||
|
||
action_sequence.append({
|
||
"device_id": stirrer_id,
|
||
"action_name": "start_stir",
|
||
"action_kwargs": {
|
||
"vessel": {"id": vessel_id}, # 🔧 使用 vessel_id
|
||
"stir_speed": STIR_SPEED,
|
||
"purpose": "抽真空充气前预搅拌"
|
||
}
|
||
})
|
||
|
||
# 等待搅拌稳定
|
||
action_sequence.append(create_action_log("等待搅拌稳定...", "⏳"))
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": 5.0}
|
||
})
|
||
else:
|
||
debug_print("⚠️ 未找到搅拌器,跳过搅拌器启动")
|
||
action_sequence.append(create_action_log("跳过搅拌器启动", "⏭️"))
|
||
|
||
# === 执行循环 ===
|
||
debug_print("🔍 步骤6: 执行抽真空-充气循环...")
|
||
action_sequence.append(create_action_log(f"开始 {repeats} 次抽真空-充气循环", "🔄"))
|
||
|
||
for cycle in range(repeats):
|
||
debug_print(f"=== 第 {cycle+1}/{repeats} 轮循环 ===")
|
||
action_sequence.append(create_action_log(f"第 {cycle+1}/{repeats} 轮循环开始", "🚀"))
|
||
|
||
# ============ 抽真空阶段 ============
|
||
debug_print(f"🌪️ 抽真空阶段开始")
|
||
action_sequence.append(create_action_log("开始抽真空阶段", "🌪️"))
|
||
|
||
# 启动真空泵
|
||
debug_print(f"🔛 启动真空泵: {vacuum_pump}")
|
||
action_sequence.append(create_action_log(f"启动真空泵: {vacuum_pump}", "🔛"))
|
||
action_sequence.append({
|
||
"device_id": vacuum_pump,
|
||
"action_name": "set_status",
|
||
"action_kwargs": {"string": "ON"}
|
||
})
|
||
|
||
# 开启真空电磁阀
|
||
if vacuum_solenoid:
|
||
debug_print(f"🚪 打开真空电磁阀: {vacuum_solenoid}")
|
||
action_sequence.append(create_action_log(f"打开真空电磁阀: {vacuum_solenoid}", "🚪"))
|
||
action_sequence.append({
|
||
"device_id": vacuum_solenoid,
|
||
"action_name": "set_valve_position",
|
||
"action_kwargs": {"command": "OPEN"}
|
||
})
|
||
|
||
# 抽真空操作
|
||
debug_print(f"🌪️ 抽真空操作: {vessel_id} -> {vacuum_pump}")
|
||
action_sequence.append(create_action_log(f"开始抽真空: {vessel_id} -> {vacuum_pump}", "🌪️"))
|
||
|
||
try:
|
||
vacuum_transfer_actions = generate_pump_protocol_with_rinsing(
|
||
G=G,
|
||
from_vessel=vessel_id, # 🔧 使用 vessel_id
|
||
to_vessel=vacuum_pump,
|
||
volume=VACUUM_VOLUME,
|
||
amount="",
|
||
time=0.0,
|
||
viscous=False,
|
||
rinsing_solvent="",
|
||
rinsing_volume=0.0,
|
||
rinsing_repeats=0,
|
||
solid=False,
|
||
flowrate=PUMP_FLOW_RATE,
|
||
transfer_flowrate=PUMP_FLOW_RATE
|
||
)
|
||
|
||
if vacuum_transfer_actions:
|
||
action_sequence.extend(vacuum_transfer_actions)
|
||
debug_print(f"✅ 添加了 {len(vacuum_transfer_actions)} 个抽真空动作")
|
||
action_sequence.append(create_action_log(f"抽真空协议完成 ({len(vacuum_transfer_actions)} 个操作)", "✅"))
|
||
else:
|
||
debug_print("⚠️ 抽真空协议返回空序列,添加手动动作")
|
||
action_sequence.append(create_action_log("抽真空协议为空,使用手动等待", "⚠️"))
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": VACUUM_TIME}
|
||
})
|
||
|
||
except Exception as e:
|
||
debug_print(f"❌ 抽真空失败: {str(e)}")
|
||
action_sequence.append(create_action_log(f"抽真空失败: {str(e)}", "❌"))
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": VACUUM_TIME}
|
||
})
|
||
|
||
# 抽真空后等待
|
||
wait_minutes = VACUUM_TIME / 60
|
||
action_sequence.append(create_action_log(f"抽真空后等待 ({wait_minutes:.1f} 分钟)", "⏳"))
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": VACUUM_TIME}
|
||
})
|
||
|
||
# 关闭真空电磁阀
|
||
if vacuum_solenoid:
|
||
debug_print(f"🚪 关闭真空电磁阀: {vacuum_solenoid}")
|
||
action_sequence.append(create_action_log(f"关闭真空电磁阀: {vacuum_solenoid}", "🚪"))
|
||
action_sequence.append({
|
||
"device_id": vacuum_solenoid,
|
||
"action_name": "set_valve_position",
|
||
"action_kwargs": {"command": "CLOSED"}
|
||
})
|
||
|
||
# 关闭真空泵
|
||
debug_print(f"🔴 停止真空泵: {vacuum_pump}")
|
||
action_sequence.append(create_action_log(f"停止真空泵: {vacuum_pump}", "🔴"))
|
||
action_sequence.append({
|
||
"device_id": vacuum_pump,
|
||
"action_name": "set_status",
|
||
"action_kwargs": {"string": "OFF"}
|
||
})
|
||
|
||
# 阶段间等待
|
||
action_sequence.append(create_action_log("抽真空阶段完成,短暂等待", "⏳"))
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": 5.0}
|
||
})
|
||
|
||
# ============ 充气阶段 ============
|
||
debug_print(f"💨 充气阶段开始")
|
||
action_sequence.append(create_action_log("开始气体充气阶段", "💨"))
|
||
|
||
# 启动气源
|
||
debug_print(f"🔛 启动气源: {gas_source}")
|
||
action_sequence.append(create_action_log(f"启动气源: {gas_source}", "🔛"))
|
||
action_sequence.append({
|
||
"device_id": gas_source,
|
||
"action_name": "set_status",
|
||
"action_kwargs": {"string": "ON"}
|
||
})
|
||
|
||
# 开启气源电磁阀
|
||
if gas_solenoid:
|
||
debug_print(f"🚪 打开气源电磁阀: {gas_solenoid}")
|
||
action_sequence.append(create_action_log(f"打开气源电磁阀: {gas_solenoid}", "🚪"))
|
||
action_sequence.append({
|
||
"device_id": gas_solenoid,
|
||
"action_name": "set_valve_position",
|
||
"action_kwargs": {"command": "OPEN"}
|
||
})
|
||
|
||
# 充气操作
|
||
debug_print(f"💨 充气操作: {gas_source} -> {vessel_id}")
|
||
action_sequence.append(create_action_log(f"开始气体充气: {gas_source} -> {vessel_id}", "💨"))
|
||
|
||
try:
|
||
gas_transfer_actions = generate_pump_protocol_with_rinsing(
|
||
G=G,
|
||
from_vessel=gas_source,
|
||
to_vessel=vessel_id, # 🔧 使用 vessel_id
|
||
volume=REFILL_VOLUME,
|
||
amount="",
|
||
time=0.0,
|
||
viscous=False,
|
||
rinsing_solvent="",
|
||
rinsing_volume=0.0,
|
||
rinsing_repeats=0,
|
||
solid=False,
|
||
flowrate=PUMP_FLOW_RATE,
|
||
transfer_flowrate=PUMP_FLOW_RATE
|
||
)
|
||
|
||
if gas_transfer_actions:
|
||
action_sequence.extend(gas_transfer_actions)
|
||
debug_print(f"✅ 添加了 {len(gas_transfer_actions)} 个充气动作")
|
||
action_sequence.append(create_action_log(f"气体充气协议完成 ({len(gas_transfer_actions)} 个操作)", "✅"))
|
||
else:
|
||
debug_print("⚠️ 充气协议返回空序列,添加手动动作")
|
||
action_sequence.append(create_action_log("充气协议为空,使用手动等待", "⚠️"))
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": REFILL_TIME}
|
||
})
|
||
|
||
except Exception as e:
|
||
debug_print(f"❌ 气体充气失败: {str(e)}")
|
||
action_sequence.append(create_action_log(f"气体充气失败: {str(e)}", "❌"))
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": REFILL_TIME}
|
||
})
|
||
|
||
# 充气后等待
|
||
refill_wait_minutes = REFILL_TIME / 60
|
||
action_sequence.append(create_action_log(f"充气后等待 ({refill_wait_minutes:.1f} 分钟)", "⏳"))
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": REFILL_TIME}
|
||
})
|
||
|
||
# 关闭气源电磁阀
|
||
if gas_solenoid:
|
||
debug_print(f"🚪 关闭气源电磁阀: {gas_solenoid}")
|
||
action_sequence.append(create_action_log(f"关闭气源电磁阀: {gas_solenoid}", "🚪"))
|
||
action_sequence.append({
|
||
"device_id": gas_solenoid,
|
||
"action_name": "set_valve_position",
|
||
"action_kwargs": {"command": "CLOSED"}
|
||
})
|
||
|
||
# 关闭气源
|
||
debug_print(f"🔴 停止气源: {gas_source}")
|
||
action_sequence.append(create_action_log(f"停止气源: {gas_source}", "🔴"))
|
||
action_sequence.append({
|
||
"device_id": gas_source,
|
||
"action_name": "set_status",
|
||
"action_kwargs": {"string": "OFF"}
|
||
})
|
||
|
||
# 循环间等待
|
||
if cycle < repeats - 1:
|
||
debug_print(f"⏳ 等待下一个循环...")
|
||
action_sequence.append(create_action_log("等待下一个循环...", "⏳"))
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": 10.0}
|
||
})
|
||
else:
|
||
action_sequence.append(create_action_log(f"第 {cycle+1}/{repeats} 轮循环完成", "✅"))
|
||
|
||
# === 停止搅拌器 ===
|
||
debug_print("🔍 步骤7: 停止搅拌器...")
|
||
|
||
if stirrer_id:
|
||
debug_print(f"🛑 停止搅拌器: {stirrer_id}")
|
||
action_sequence.append(create_action_log(f"停止搅拌器: {stirrer_id}", "🛑"))
|
||
action_sequence.append({
|
||
"device_id": stirrer_id,
|
||
"action_name": "stop_stir",
|
||
"action_kwargs": {"vessel": {"id": vessel_id},} # 🔧 使用 vessel_id
|
||
})
|
||
else:
|
||
action_sequence.append(create_action_log("跳过搅拌器停止", "⏭️"))
|
||
|
||
# === 最终等待 ===
|
||
action_sequence.append(create_action_log("最终稳定等待...", "⏳"))
|
||
action_sequence.append({
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": 10.0}
|
||
})
|
||
|
||
# === 总结 ===
|
||
total_time = (VACUUM_TIME + REFILL_TIME + 25) * repeats + 20
|
||
|
||
debug_print("=" * 60)
|
||
debug_print(f"🎉 抽真空充气协议生成完成")
|
||
debug_print(f"📊 协议统计:")
|
||
debug_print(f" 📋 总动作数: {len(action_sequence)}")
|
||
debug_print(f" ⏱️ 预计总时间: {total_time:.0f}s ({total_time/60:.1f} 分钟)")
|
||
debug_print(f" 🥼 处理容器: {vessel_id}")
|
||
debug_print(f" 💨 使用气体: {gas}")
|
||
debug_print(f" 🔄 重复次数: {repeats}")
|
||
debug_print("=" * 60)
|
||
|
||
# 添加完成日志
|
||
summary_msg = f"抽真空充气协议完成: {vessel_id} (使用 {gas},{repeats} 次循环)"
|
||
action_sequence.append(create_action_log(summary_msg, "🎉"))
|
||
|
||
return action_sequence
|
||
|
||
# === 便捷函数 ===
|
||
|
||
def generate_nitrogen_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型
|
||
"""生成氮气置换协议"""
|
||
vessel_id = vessel["id"]
|
||
debug_print(f"💨 生成氮气置换协议: {vessel_id}")
|
||
return generate_evacuateandrefill_protocol(G, vessel, "nitrogen", **kwargs)
|
||
|
||
def generate_argon_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型
|
||
"""生成氩气置换协议"""
|
||
vessel_id = vessel["id"]
|
||
debug_print(f"💨 生成氩气置换协议: {vessel_id}")
|
||
return generate_evacuateandrefill_protocol(G, vessel, "argon", **kwargs)
|
||
|
||
def generate_air_purge_protocol(G: nx.DiGraph, vessel: dict, **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型
|
||
"""生成空气置换协议"""
|
||
vessel_id = vessel["id"]
|
||
debug_print(f"💨 生成空气置换协议: {vessel_id}")
|
||
return generate_evacuateandrefill_protocol(G, vessel, "air", **kwargs)
|
||
|
||
def generate_inert_atmosphere_protocol(G: nx.DiGraph, vessel: dict, gas: str = "nitrogen", **kwargs) -> List[Dict[str, Any]]: # 🔧 修改参数类型
|
||
"""生成惰性气氛协议"""
|
||
vessel_id = vessel["id"]
|
||
debug_print(f"🛡️ 生成惰性气氛协议: {vessel_id} (使用 {gas})")
|
||
return generate_evacuateandrefill_protocol(G, vessel, gas, **kwargs)
|
||
|
||
# 测试函数
|
||
def test_evacuateandrefill_protocol():
|
||
"""测试抽真空充气协议"""
|
||
debug_print("=== 抽真空充气协议增强中文版测试 ===")
|
||
debug_print("✅ 测试完成")
|
||
|
||
if __name__ == "__main__":
|
||
test_evacuateandrefill_protocol() |