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>
449 lines
18 KiB
Python
449 lines
18 KiB
Python
from typing import List, Dict, Any
|
||
import networkx as nx
|
||
from .utils.vessel_parser import get_vessel, find_solvent_vessel
|
||
from .pump_protocol import generate_pump_protocol
|
||
|
||
|
||
def find_solvent_vessel_by_any_match(G: nx.DiGraph, solvent: str) -> str:
|
||
"""
|
||
增强版溶剂容器查找,支持各种匹配方式的别名函数
|
||
"""
|
||
return find_solvent_vessel(G, solvent)
|
||
|
||
|
||
def find_waste_vessel(G: nx.DiGraph) -> str:
|
||
"""
|
||
查找废液容器
|
||
"""
|
||
possible_waste_names = [
|
||
"waste_workup",
|
||
"flask_waste",
|
||
"bottle_waste",
|
||
"waste",
|
||
"waste_vessel",
|
||
"waste_container"
|
||
]
|
||
|
||
for waste_name in possible_waste_names:
|
||
if waste_name in G.nodes():
|
||
return waste_name
|
||
|
||
raise ValueError(f"未找到废液容器。尝试了以下名称: {possible_waste_names}")
|
||
|
||
|
||
def find_connected_heatchill(G: nx.DiGraph, vessel: str) -> str:
|
||
"""
|
||
查找与指定容器相连的加热冷却设备
|
||
"""
|
||
# 查找所有加热冷却设备节点
|
||
heatchill_nodes = [node for node in G.nodes()
|
||
if (G.nodes[node].get('class') or '') == 'virtual_heatchill']
|
||
|
||
# 检查哪个加热设备与目标容器相连(机械连接)
|
||
for heatchill in heatchill_nodes:
|
||
if G.has_edge(heatchill, vessel) or G.has_edge(vessel, heatchill):
|
||
return heatchill
|
||
|
||
# 如果没有直接连接,返回第一个可用的加热设备
|
||
if heatchill_nodes:
|
||
return heatchill_nodes[0]
|
||
|
||
return None # 没有加热设备也可以工作,只是不能加热
|
||
|
||
|
||
def generate_clean_vessel_protocol(
|
||
G: nx.DiGraph,
|
||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||
solvent: str,
|
||
volume: float,
|
||
temp: float,
|
||
repeats: int = 1
|
||
) -> List[Dict[str, Any]]:
|
||
"""
|
||
生成容器清洗操作的协议序列,复用 pump_protocol 的成熟算法
|
||
|
||
清洗流程:
|
||
1. 查找溶剂容器和废液容器
|
||
2. 如果需要加热,启动加热设备
|
||
3. 重复以下操作 repeats 次:
|
||
a. 使用 pump_protocol 将溶剂从溶剂容器转移到目标容器
|
||
b. (可选) 等待清洗作用时间
|
||
c. 使用 pump_protocol 将清洗液从目标容器转移到废液容器
|
||
4. 如果加热了,停止加热
|
||
|
||
Args:
|
||
G: 有向图,节点为设备和容器,边为流体管道
|
||
vessel: 要清洗的容器字典(包含id字段)
|
||
solvent: 用于清洗的溶剂名称
|
||
volume: 每次清洗使用的溶剂体积
|
||
temp: 清洗时的温度
|
||
repeats: 清洗操作的重复次数,默认为 1
|
||
|
||
Returns:
|
||
List[Dict[str, Any]]: 容器清洗操作的动作序列
|
||
|
||
Raises:
|
||
ValueError: 当找不到必要的容器或设备时抛出异常
|
||
|
||
Examples:
|
||
clean_protocol = generate_clean_vessel_protocol(G, {"id": "main_reactor"}, "water", 100.0, 60.0, 2)
|
||
"""
|
||
# 🔧 核心修改:从字典中提取容器ID
|
||
vessel_id, vessel_data = get_vessel(vessel)
|
||
|
||
action_sequence = []
|
||
|
||
print(f"CLEAN_VESSEL: 开始生成容器清洗协议")
|
||
print(f" - 目标容器: {vessel} (ID: {vessel_id})")
|
||
print(f" - 清洗溶剂: {solvent}")
|
||
print(f" - 清洗体积: {volume} mL")
|
||
print(f" - 清洗温度: {temp}°C")
|
||
print(f" - 重复次数: {repeats}")
|
||
|
||
# 验证目标容器存在
|
||
if vessel_id not in G.nodes():
|
||
raise ValueError(f"目标容器 '{vessel_id}' 不存在于系统中")
|
||
|
||
# 查找溶剂容器
|
||
try:
|
||
solvent_vessel = find_solvent_vessel(G, solvent)
|
||
print(f"CLEAN_VESSEL: 找到溶剂容器: {solvent_vessel}")
|
||
except ValueError as e:
|
||
raise ValueError(f"无法找到溶剂容器: {str(e)}")
|
||
|
||
# 查找废液容器
|
||
try:
|
||
waste_vessel = find_waste_vessel(G)
|
||
print(f"CLEAN_VESSEL: 找到废液容器: {waste_vessel}")
|
||
except ValueError as e:
|
||
raise ValueError(f"无法找到废液容器: {str(e)}")
|
||
|
||
# 查找加热设备(可选)
|
||
heatchill_id = find_connected_heatchill(G, vessel_id) # 🔧 使用 vessel_id
|
||
if heatchill_id:
|
||
print(f"CLEAN_VESSEL: 找到加热设备: {heatchill_id}")
|
||
else:
|
||
print(f"CLEAN_VESSEL: 未找到加热设备,将在室温下清洗")
|
||
|
||
# 🔧 新增:记录清洗前的容器状态
|
||
print(f"CLEAN_VESSEL: 记录清洗前容器状态...")
|
||
original_liquid_volume = 0.0
|
||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||
current_volume = vessel["data"]["liquid_volume"]
|
||
if isinstance(current_volume, list) and len(current_volume) > 0:
|
||
original_liquid_volume = current_volume[0]
|
||
elif isinstance(current_volume, (int, float)):
|
||
original_liquid_volume = current_volume
|
||
print(f"CLEAN_VESSEL: 清洗前液体体积: {original_liquid_volume:.2f}mL")
|
||
|
||
# 第一步:如果需要加热且有加热设备,启动加热
|
||
if temp > 25.0 and heatchill_id:
|
||
print(f"CLEAN_VESSEL: 启动加热至 {temp}°C")
|
||
heatchill_start_action = {
|
||
"device_id": heatchill_id,
|
||
"action_name": "heat_chill_start",
|
||
"action_kwargs": {
|
||
"vessel": {"id": vessel_id}, # 🔧 使用 vessel_id
|
||
"temp": temp,
|
||
"purpose": f"cleaning with {solvent}"
|
||
}
|
||
}
|
||
action_sequence.append(heatchill_start_action)
|
||
|
||
# 等待温度稳定
|
||
wait_action = {
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": 30} # 等待30秒让温度稳定
|
||
}
|
||
action_sequence.append(wait_action)
|
||
|
||
# 第二步:重复清洗操作
|
||
for repeat in range(repeats):
|
||
print(f"CLEAN_VESSEL: 执行第 {repeat + 1} 次清洗")
|
||
|
||
# 2a. 使用 pump_protocol 将溶剂转移到目标容器
|
||
print(f"CLEAN_VESSEL: 将 {volume} mL {solvent} 转移到 {vessel_id}")
|
||
try:
|
||
# 调用成熟的 pump_protocol 算法
|
||
add_solvent_actions = generate_pump_protocol(
|
||
G=G,
|
||
from_vessel=solvent_vessel,
|
||
to_vessel=vessel_id, # 🔧 使用 vessel_id
|
||
volume=volume,
|
||
flowrate=2.5, # 适中的流速,避免飞溅
|
||
transfer_flowrate=2.5
|
||
)
|
||
action_sequence.extend(add_solvent_actions)
|
||
|
||
# 🔧 新增:更新容器体积(添加清洗溶剂)
|
||
print(f"CLEAN_VESSEL: 更新容器体积 - 添加清洗溶剂 {volume:.2f}mL")
|
||
if "data" not in vessel:
|
||
vessel["data"] = {}
|
||
|
||
if "liquid_volume" in vessel["data"]:
|
||
current_volume = vessel["data"]["liquid_volume"]
|
||
if isinstance(current_volume, list):
|
||
if len(current_volume) > 0:
|
||
vessel["data"]["liquid_volume"][0] += volume
|
||
print(f"CLEAN_VESSEL: 添加溶剂后体积: {vessel['data']['liquid_volume'][0]:.2f}mL (+{volume:.2f}mL)")
|
||
else:
|
||
vessel["data"]["liquid_volume"] = [volume]
|
||
print(f"CLEAN_VESSEL: 初始化清洗体积: {volume:.2f}mL")
|
||
elif isinstance(current_volume, (int, float)):
|
||
vessel["data"]["liquid_volume"] += volume
|
||
print(f"CLEAN_VESSEL: 添加溶剂后体积: {vessel['data']['liquid_volume']:.2f}mL (+{volume:.2f}mL)")
|
||
else:
|
||
vessel["data"]["liquid_volume"] = volume
|
||
print(f"CLEAN_VESSEL: 重置体积为: {volume:.2f}mL")
|
||
else:
|
||
vessel["data"]["liquid_volume"] = volume
|
||
print(f"CLEAN_VESSEL: 创建新体积记录: {volume:.2f}mL")
|
||
|
||
# 🔧 同时更新图中的容器数据
|
||
if vessel_id in G.nodes():
|
||
if 'data' not in G.nodes[vessel_id]:
|
||
G.nodes[vessel_id]['data'] = {}
|
||
|
||
vessel_node_data = G.nodes[vessel_id]['data']
|
||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||
|
||
if isinstance(current_node_volume, list):
|
||
if len(current_node_volume) > 0:
|
||
G.nodes[vessel_id]['data']['liquid_volume'][0] += volume
|
||
else:
|
||
G.nodes[vessel_id]['data']['liquid_volume'] = [volume]
|
||
else:
|
||
G.nodes[vessel_id]['data']['liquid_volume'] = current_node_volume + volume
|
||
|
||
print(f"CLEAN_VESSEL: 图节点体积数据已更新")
|
||
|
||
except Exception as e:
|
||
raise ValueError(f"无法将溶剂转移到容器: {str(e)}")
|
||
|
||
# 2b. 等待清洗作用时间(让溶剂充分清洗容器)
|
||
cleaning_wait_time = 60 if temp > 50.0 else 30 # 高温下等待更久
|
||
print(f"CLEAN_VESSEL: 等待清洗作用 {cleaning_wait_time} 秒")
|
||
wait_action = {
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": cleaning_wait_time}
|
||
}
|
||
action_sequence.append(wait_action)
|
||
|
||
# 2c. 使用 pump_protocol 将清洗液转移到废液容器
|
||
print(f"CLEAN_VESSEL: 将清洗液从 {vessel_id} 转移到废液容器")
|
||
try:
|
||
# 调用成熟的 pump_protocol 算法
|
||
remove_waste_actions = generate_pump_protocol(
|
||
G=G,
|
||
from_vessel=vessel_id, # 🔧 使用 vessel_id
|
||
to_vessel=waste_vessel,
|
||
volume=volume,
|
||
flowrate=2.5, # 适中的流速
|
||
transfer_flowrate=2.5
|
||
)
|
||
action_sequence.extend(remove_waste_actions)
|
||
|
||
# 🔧 新增:更新容器体积(移除清洗液)
|
||
print(f"CLEAN_VESSEL: 更新容器体积 - 移除清洗液 {volume:.2f}mL")
|
||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||
current_volume = vessel["data"]["liquid_volume"]
|
||
if isinstance(current_volume, list):
|
||
if len(current_volume) > 0:
|
||
vessel["data"]["liquid_volume"][0] = max(0.0, vessel["data"]["liquid_volume"][0] - volume)
|
||
print(f"CLEAN_VESSEL: 移除清洗液后体积: {vessel['data']['liquid_volume'][0]:.2f}mL (-{volume:.2f}mL)")
|
||
else:
|
||
vessel["data"]["liquid_volume"] = [0.0]
|
||
print(f"CLEAN_VESSEL: 重置体积为0mL")
|
||
elif isinstance(current_volume, (int, float)):
|
||
vessel["data"]["liquid_volume"] = max(0.0, current_volume - volume)
|
||
print(f"CLEAN_VESSEL: 移除清洗液后体积: {vessel['data']['liquid_volume']:.2f}mL (-{volume:.2f}mL)")
|
||
else:
|
||
vessel["data"]["liquid_volume"] = 0.0
|
||
print(f"CLEAN_VESSEL: 重置体积为0mL")
|
||
|
||
# 🔧 同时更新图中的容器数据
|
||
if vessel_id in G.nodes():
|
||
vessel_node_data = G.nodes[vessel_id].get('data', {})
|
||
current_node_volume = vessel_node_data.get('liquid_volume', 0.0)
|
||
|
||
if isinstance(current_node_volume, list):
|
||
if len(current_node_volume) > 0:
|
||
G.nodes[vessel_id]['data']['liquid_volume'][0] = max(0.0, current_node_volume[0] - volume)
|
||
else:
|
||
G.nodes[vessel_id]['data']['liquid_volume'] = [0.0]
|
||
else:
|
||
G.nodes[vessel_id]['data']['liquid_volume'] = max(0.0, current_node_volume - volume)
|
||
|
||
print(f"CLEAN_VESSEL: 图节点体积数据已更新")
|
||
|
||
except Exception as e:
|
||
raise ValueError(f"无法将清洗液转移到废液容器: {str(e)}")
|
||
|
||
# 2d. 清洗循环间的短暂等待
|
||
if repeat < repeats - 1: # 不是最后一次清洗
|
||
print(f"CLEAN_VESSEL: 清洗循环间等待")
|
||
wait_action = {
|
||
"action_name": "wait",
|
||
"action_kwargs": {"time": 10}
|
||
}
|
||
action_sequence.append(wait_action)
|
||
|
||
# 第三步:如果加热了,停止加热
|
||
if temp > 25.0 and heatchill_id:
|
||
print(f"CLEAN_VESSEL: 停止加热")
|
||
heatchill_stop_action = {
|
||
"device_id": heatchill_id,
|
||
"action_name": "heat_chill_stop",
|
||
"action_kwargs": {
|
||
"vessel": {"id": vessel_id}, # 🔧 使用 vessel_id
|
||
}
|
||
}
|
||
action_sequence.append(heatchill_stop_action)
|
||
|
||
# 🔧 新增:清洗完成后的状态报告
|
||
final_liquid_volume = 0.0
|
||
if "data" in vessel and "liquid_volume" in vessel["data"]:
|
||
current_volume = vessel["data"]["liquid_volume"]
|
||
if isinstance(current_volume, list) and len(current_volume) > 0:
|
||
final_liquid_volume = current_volume[0]
|
||
elif isinstance(current_volume, (int, float)):
|
||
final_liquid_volume = current_volume
|
||
|
||
print(f"CLEAN_VESSEL: 清洗完成")
|
||
print(f" - 清洗前体积: {original_liquid_volume:.2f}mL")
|
||
print(f" - 清洗后体积: {final_liquid_volume:.2f}mL")
|
||
print(f" - 生成了 {len(action_sequence)} 个动作")
|
||
|
||
return action_sequence
|
||
|
||
|
||
# 便捷函数:常用清洗方案
|
||
def generate_quick_clean_protocol(
|
||
G: nx.DiGraph,
|
||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||
solvent: str = "water",
|
||
volume: float = 100.0
|
||
) -> List[Dict[str, Any]]:
|
||
"""快速清洗:室温,单次清洗"""
|
||
return generate_clean_vessel_protocol(G, vessel, solvent, volume, 25.0, 1)
|
||
|
||
|
||
def generate_thorough_clean_protocol(
|
||
G: nx.DiGraph,
|
||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||
solvent: str = "water",
|
||
volume: float = 150.0,
|
||
temp: float = 60.0
|
||
) -> List[Dict[str, Any]]:
|
||
"""深度清洗:加热,多次清洗"""
|
||
return generate_clean_vessel_protocol(G, vessel, solvent, volume, temp, 3)
|
||
|
||
|
||
def generate_organic_clean_protocol(
|
||
G: nx.DiGraph,
|
||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
||
volume: float = 100.0
|
||
) -> List[Dict[str, Any]]:
|
||
"""有机清洗:先用有机溶剂,再用水清洗"""
|
||
action_sequence = []
|
||
|
||
# 第一步:有机溶剂清洗
|
||
try:
|
||
organic_actions = generate_clean_vessel_protocol(
|
||
G, vessel, "acetone", volume, 25.0, 2
|
||
)
|
||
action_sequence.extend(organic_actions)
|
||
except ValueError:
|
||
# 如果没有丙酮,尝试乙醇
|
||
try:
|
||
organic_actions = generate_clean_vessel_protocol(
|
||
G, vessel, "ethanol", volume, 25.0, 2
|
||
)
|
||
action_sequence.extend(organic_actions)
|
||
except ValueError:
|
||
print("警告:未找到有机溶剂,跳过有机清洗步骤")
|
||
|
||
# 第二步:水清洗
|
||
water_actions = generate_clean_vessel_protocol(
|
||
G, vessel, "water", volume, 25.0, 2
|
||
)
|
||
action_sequence.extend(water_actions)
|
||
|
||
return action_sequence
|
||
|
||
|
||
def get_vessel_liquid_volume(G: nx.DiGraph, vessel: str) -> float:
|
||
"""获取容器中的液体体积(修复版)"""
|
||
if vessel not in G.nodes():
|
||
return 0.0
|
||
|
||
vessel_data = G.nodes[vessel].get('data', {})
|
||
liquids = vessel_data.get('liquid', [])
|
||
|
||
total_volume = 0.0
|
||
for liquid in liquids:
|
||
if isinstance(liquid, dict):
|
||
# 支持两种格式:新格式 (name, volume) 和旧格式 (liquid_type, liquid_volume)
|
||
volume = liquid.get('volume') or liquid.get('liquid_volume', 0.0)
|
||
total_volume += volume
|
||
|
||
return total_volume
|
||
|
||
|
||
def get_vessel_liquid_types(G: nx.DiGraph, vessel: str) -> List[str]:
|
||
"""获取容器中所有液体的类型"""
|
||
if vessel not in G.nodes():
|
||
return []
|
||
|
||
vessel_data = G.nodes[vessel].get('data', {})
|
||
liquids = vessel_data.get('liquid', [])
|
||
|
||
liquid_types = []
|
||
for liquid in liquids:
|
||
if isinstance(liquid, dict):
|
||
# 支持两种格式的液体类型字段
|
||
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
|
||
if liquid_type:
|
||
liquid_types.append(liquid_type)
|
||
|
||
return liquid_types
|
||
|
||
|
||
def find_vessel_by_content(G: nx.DiGraph, content: str) -> List[str]:
|
||
"""
|
||
根据内容物查找所有匹配的容器
|
||
返回匹配容器的ID列表
|
||
"""
|
||
matching_vessels = []
|
||
|
||
for node_id in G.nodes():
|
||
if G.nodes[node_id].get('type') == 'container':
|
||
# 检查容器名称匹配
|
||
node_name = G.nodes[node_id].get('name', '').lower()
|
||
if content.lower() in node_id.lower() or content.lower() in node_name:
|
||
matching_vessels.append(node_id)
|
||
continue
|
||
|
||
# 检查液体类型匹配
|
||
vessel_data = G.nodes[node_id].get('data', {})
|
||
liquids = vessel_data.get('liquid', [])
|
||
config_data = G.nodes[node_id].get('config', {})
|
||
|
||
# 检查 reagent_name 和 config.reagent
|
||
reagent_name = vessel_data.get('reagent_name', '').lower()
|
||
config_reagent = config_data.get('reagent', '').lower()
|
||
|
||
if (content.lower() == reagent_name or
|
||
content.lower() == config_reagent):
|
||
matching_vessels.append(node_id)
|
||
continue
|
||
|
||
# 检查液体列表
|
||
for liquid in liquids:
|
||
if isinstance(liquid, dict):
|
||
liquid_type = liquid.get('liquid_type') or liquid.get('name', '')
|
||
if liquid_type.lower() == content.lower():
|
||
matching_vessels.append(node_id)
|
||
break
|
||
|
||
return matching_vessels |