mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-05 14:05:12 +00:00
fix: Protocol node resource run (#65)
* stir和adjustph的中的bug修不好 * fix sub-resource query in protocol node compiling * add resource placeholder to vessels * add the rest yaml * Update work_station.yaml --------- Co-authored-by: KCFeng425 <2100011801@stu.pku.edu.cn>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import networkx as nx
|
import networkx as nx
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Dict, Any
|
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__)
|
||||||
@@ -216,7 +216,7 @@ def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume
|
|||||||
|
|
||||||
def generate_adjust_ph_protocol(
|
def generate_adjust_ph_protocol(
|
||||||
G: nx.DiGraph,
|
G: nx.DiGraph,
|
||||||
vessel: dict, # 🔧 修改:从字符串改为字典类型
|
vessel:Union[dict,str], # 🔧 修改:从字符串改为字典类型
|
||||||
ph_value: float,
|
ph_value: float,
|
||||||
reagent: str,
|
reagent: str,
|
||||||
**kwargs
|
**kwargs
|
||||||
@@ -235,8 +235,17 @@ def generate_adjust_ph_protocol(
|
|||||||
List[Dict[str, Any]]: 动作序列
|
List[Dict[str, Any]]: 动作序列
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 🔧 核心修改:从字典中提取容器ID
|
# 统一处理vessel参数
|
||||||
vessel_id = vessel["id"]
|
if isinstance(vessel, dict):
|
||||||
|
vessel_id = list(vessel.values())[0].get("id", "")
|
||||||
|
vessel_data = vessel.get("data", {})
|
||||||
|
else:
|
||||||
|
vessel_id = str(vessel)
|
||||||
|
vessel_data = G.nodes[vessel_id].get("data", {}) if vessel_id in G.nodes() else {}
|
||||||
|
|
||||||
|
if not vessel_id:
|
||||||
|
debug_print(f"❌ vessel 参数无效,必须包含id字段或直接提供容器ID. vessel: {vessel}")
|
||||||
|
raise ValueError("vessel 参数无效,必须包含id字段或直接提供容器ID")
|
||||||
|
|
||||||
debug_print("=" * 60)
|
debug_print("=" * 60)
|
||||||
debug_print("🧪 开始生成pH调节协议")
|
debug_print("🧪 开始生成pH调节协议")
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
|||||||
"""
|
"""
|
||||||
if isinstance(time_input, (int, float)):
|
if isinstance(time_input, (int, float)):
|
||||||
debug_print(f"⏱️ 时间输入为数字: {time_input}s ✨")
|
debug_print(f"⏱️ 时间输入为数字: {time_input}s ✨")
|
||||||
return float(time_input)
|
return float(time_input) # 🔧 确保返回float
|
||||||
|
|
||||||
if not time_input or not str(time_input).strip():
|
if not time_input or not str(time_input).strip():
|
||||||
debug_print(f"⚠️ 时间输入为空,使用默认值: 180s (3分钟) 🕐")
|
debug_print(f"⚠️ 时间输入为空,使用默认值: 180s (3分钟) 🕐")
|
||||||
@@ -48,7 +48,7 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
|||||||
try:
|
try:
|
||||||
value = float(time_str)
|
value = float(time_str)
|
||||||
debug_print(f"✅ 时间解析成功: {time_str} → {value}s(无单位,默认秒)⏰")
|
debug_print(f"✅ 时间解析成功: {time_str} → {value}s(无单位,默认秒)⏰")
|
||||||
return value
|
return float(value) # 🔧 确保返回float
|
||||||
except ValueError:
|
except ValueError:
|
||||||
debug_print(f"❌ 无法解析时间: '{time_str}',使用默认值180s (3分钟) 😅")
|
debug_print(f"❌ 无法解析时间: '{time_str}',使用默认值180s (3分钟) 😅")
|
||||||
return 180.0
|
return 180.0
|
||||||
@@ -70,7 +70,7 @@ def parse_time_input(time_input: Union[str, float]) -> float:
|
|||||||
time_sec = value # 已经是s
|
time_sec = value # 已经是s
|
||||||
debug_print(f"🕐 时间转换: {value}s → {time_sec}s (已是秒) ⏰")
|
debug_print(f"🕐 时间转换: {value}s → {time_sec}s (已是秒) ⏰")
|
||||||
|
|
||||||
return time_sec
|
return float(time_sec) # 🔧 确保返回float
|
||||||
|
|
||||||
def find_rotavap_device(G: nx.DiGraph, vessel: str = None) -> Optional[str]:
|
def find_rotavap_device(G: nx.DiGraph, vessel: str = None) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
@@ -389,12 +389,12 @@ def generate_evaporate_protocol(
|
|||||||
"device_id": rotavap_device,
|
"device_id": rotavap_device,
|
||||||
"action_name": "evaporate",
|
"action_name": "evaporate",
|
||||||
"action_kwargs": {
|
"action_kwargs": {
|
||||||
"vessel": target_vessel, # 使用 target_vessel
|
"vessel": target_vessel,
|
||||||
"pressure": pressure,
|
"pressure": float(pressure),
|
||||||
"temp": temp,
|
"temp": float(temp),
|
||||||
"time": final_time,
|
"time": float(final_time), # 🔧 强制转换为float类型
|
||||||
"stir_speed": stir_speed,
|
"stir_speed": float(stir_speed),
|
||||||
"solvent": solvent
|
"solvent": str(solvent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
action_sequence.append(evaporate_action)
|
action_sequence.append(evaporate_action)
|
||||||
|
|||||||
@@ -170,33 +170,94 @@ def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
|
|||||||
debug_print(f" 🎉 通过名称匹配找到容器: {vessel_name} ✨")
|
debug_print(f" 🎉 通过名称匹配找到容器: {vessel_name} ✨")
|
||||||
return vessel_name
|
return vessel_name
|
||||||
|
|
||||||
# 第二步:通过模糊匹配
|
# 第二步:通过模糊匹配(节点ID和名称)
|
||||||
debug_print(" 🔍 步骤2: 模糊名称匹配...")
|
debug_print(" 🔍 步骤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()
|
||||||
|
|
||||||
if solvent.lower() in node_id.lower() or solvent.lower() in node_name:
|
if solvent.lower() in node_id.lower() or solvent.lower() in node_name:
|
||||||
debug_print(f" 🎉 通过模糊匹配找到容器: {node_id} ✨")
|
debug_print(f" 🎉 通过模糊匹配找到容器: {node_id} (名称: {node_name}) ✨")
|
||||||
return node_id
|
return node_id
|
||||||
|
|
||||||
# 第三步:通过液体类型匹配
|
# 第三步:通过配置中的试剂信息匹配
|
||||||
debug_print(" 🧪 步骤3: 液体类型匹配...")
|
debug_print(" 🧪 步骤3: 配置试剂信息匹配...")
|
||||||
|
for node_id in G.nodes():
|
||||||
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
|
# 检查 config 中的 reagent 字段
|
||||||
|
node_config = G.nodes[node_id].get('config', {})
|
||||||
|
config_reagent = node_config.get('reagent', '').lower()
|
||||||
|
|
||||||
|
if config_reagent and solvent.lower() == config_reagent:
|
||||||
|
debug_print(f" 🎉 通过config.reagent匹配找到容器: {node_id} (试剂: {config_reagent}) ✨")
|
||||||
|
return node_id
|
||||||
|
|
||||||
|
# 第四步:通过数据中的试剂信息匹配
|
||||||
|
debug_print(" 🧪 步骤4: 数据试剂信息匹配...")
|
||||||
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', {})
|
||||||
liquids = vessel_data.get('liquid', [])
|
|
||||||
|
|
||||||
|
# 检查 data 中的 reagent_name 字段
|
||||||
|
reagent_name = vessel_data.get('reagent_name', '').lower()
|
||||||
|
if reagent_name and solvent.lower() == reagent_name:
|
||||||
|
debug_print(f" 🎉 通过data.reagent_name匹配找到容器: {node_id} (试剂: {reagent_name}) ✨")
|
||||||
|
return node_id
|
||||||
|
|
||||||
|
# 检查 data 中的液体信息
|
||||||
|
liquids = vessel_data.get('liquid', [])
|
||||||
for liquid in liquids:
|
for liquid in liquids:
|
||||||
if isinstance(liquid, dict):
|
if isinstance(liquid, dict):
|
||||||
liquid_type = (liquid.get('liquid_type') or liquid.get('name', '')).lower()
|
liquid_type = (liquid.get('liquid_type') or liquid.get('name', '')).lower()
|
||||||
reagent_name = vessel_data.get('reagent_name', '').lower()
|
|
||||||
|
|
||||||
if solvent.lower() in liquid_type or solvent.lower() in reagent_name:
|
if solvent.lower() in liquid_type:
|
||||||
debug_print(f" 🎉 通过液体类型匹配找到容器: {node_id} ✨")
|
debug_print(f" 🎉 通过液体类型匹配找到容器: {node_id} (液体类型: {liquid_type}) ✨")
|
||||||
return node_id
|
return node_id
|
||||||
|
|
||||||
|
# 第五步:部分匹配(如果前面都没找到)
|
||||||
|
debug_print(" 🔍 步骤5: 部分匹配...")
|
||||||
|
for node_id in G.nodes():
|
||||||
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
|
node_config = G.nodes[node_id].get('config', {})
|
||||||
|
node_data = G.nodes[node_id].get('data', {})
|
||||||
|
node_name = G.nodes[node_id].get('name', '').lower()
|
||||||
|
|
||||||
|
config_reagent = node_config.get('reagent', '').lower()
|
||||||
|
data_reagent = node_data.get('reagent_name', '').lower()
|
||||||
|
|
||||||
|
# 检查是否包含溶剂名称
|
||||||
|
if (solvent.lower() in config_reagent or
|
||||||
|
solvent.lower() in data_reagent or
|
||||||
|
solvent.lower() in node_name or
|
||||||
|
solvent.lower() in node_id.lower()):
|
||||||
|
debug_print(f" 🎉 通过部分匹配找到容器: {node_id} ✨")
|
||||||
|
debug_print(f" - 节点名称: {node_name}")
|
||||||
|
debug_print(f" - 配置试剂: {config_reagent}")
|
||||||
|
debug_print(f" - 数据试剂: {data_reagent}")
|
||||||
|
return node_id
|
||||||
|
|
||||||
|
# 调试信息:列出所有容器
|
||||||
|
debug_print(" 🔎 调试信息:列出所有容器...")
|
||||||
|
container_list = []
|
||||||
|
for node_id in G.nodes():
|
||||||
|
if G.nodes[node_id].get('type') == 'container':
|
||||||
|
node_config = G.nodes[node_id].get('config', {})
|
||||||
|
node_data = G.nodes[node_id].get('data', {})
|
||||||
|
node_name = G.nodes[node_id].get('name', '')
|
||||||
|
|
||||||
|
container_info = {
|
||||||
|
'id': node_id,
|
||||||
|
'name': node_name,
|
||||||
|
'config_reagent': node_config.get('reagent', ''),
|
||||||
|
'data_reagent': node_data.get('reagent_name', '')
|
||||||
|
}
|
||||||
|
container_list.append(container_info)
|
||||||
|
debug_print(f" - 容器: {node_id}, 名称: {node_name}, config试剂: {node_config.get('reagent', '')}, data试剂: {node_data.get('reagent_name', '')}")
|
||||||
|
|
||||||
debug_print(f"❌ 找不到溶剂 '{solvent}' 对应的容器 😭")
|
debug_print(f"❌ 找不到溶剂 '{solvent}' 对应的容器 😭")
|
||||||
|
debug_print(f"🔍 查找的溶剂: '{solvent}' (小写: '{solvent.lower()}')")
|
||||||
|
debug_print(f"📊 总共发现 {len(container_list)} 个容器")
|
||||||
|
|
||||||
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器")
|
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ def extract_vessel_id(vessel: Union[str, dict]) -> str:
|
|||||||
str: vessel_id
|
str: vessel_id
|
||||||
"""
|
"""
|
||||||
if isinstance(vessel, dict):
|
if isinstance(vessel, dict):
|
||||||
vessel_id = vessel.get("id", "")
|
vessel_id = list(vessel.values())[0].get("id", "")
|
||||||
debug_print(f"🔧 从vessel字典提取ID: {vessel_id}")
|
debug_print(f"🔧 从vessel字典提取ID: {vessel_id}")
|
||||||
return vessel_id
|
return vessel_id
|
||||||
elif isinstance(vessel, str):
|
elif isinstance(vessel, str):
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ def extract_vessel_id(vessel: Union[str, dict]) -> str:
|
|||||||
str: vessel_id
|
str: vessel_id
|
||||||
"""
|
"""
|
||||||
if isinstance(vessel, dict):
|
if isinstance(vessel, dict):
|
||||||
vessel_id = vessel.get("id", "")
|
vessel_id = list(vessel.values())[0].get("id", "")
|
||||||
debug_print(f"🔧 从vessel字典提取ID: {vessel_id}")
|
debug_print(f"🔧 从vessel字典提取ID: {vessel_id}")
|
||||||
return vessel_id
|
return vessel_id
|
||||||
elif isinstance(vessel, str):
|
elif isinstance(vessel, str):
|
||||||
|
|||||||
@@ -70,11 +70,32 @@ class VirtualMultiwayValve:
|
|||||||
command: 目标位置 (0-8) 或位置字符串
|
command: 目标位置 (0-8) 或位置字符串
|
||||||
0: transfer pump位置
|
0: transfer pump位置
|
||||||
1-8: 其他设备位置
|
1-8: 其他设备位置
|
||||||
|
'default': 默认位置(0号位)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 如果是字符串形式的位置,先转换为数字
|
# 🔧 处理特殊字符串命令
|
||||||
if isinstance(command, str):
|
if isinstance(command, str):
|
||||||
pos = int(command)
|
command_lower = command.lower().strip()
|
||||||
|
|
||||||
|
# 处理特殊命令
|
||||||
|
if command_lower in ['default', 'pump', 'transfer_pump', 'home']:
|
||||||
|
pos = 0 # 默认位置为0号位(transfer pump)
|
||||||
|
self.logger.info(f"🔧 特殊命令 '{command}' 映射到位置 {pos}")
|
||||||
|
elif command_lower in ['open']:
|
||||||
|
pos = 0 # open命令也映射到0号位
|
||||||
|
self.logger.info(f"🔧 OPEN命令映射到位置 {pos}")
|
||||||
|
elif command_lower in ['close', 'closed']:
|
||||||
|
# 关闭命令保持当前位置
|
||||||
|
pos = self._current_position
|
||||||
|
self.logger.info(f"🔧 CLOSE命令保持当前位置 {pos}")
|
||||||
|
else:
|
||||||
|
# 尝试转换为数字
|
||||||
|
try:
|
||||||
|
pos = int(command)
|
||||||
|
except ValueError:
|
||||||
|
error_msg = f"无法识别的命令: '{command}'"
|
||||||
|
self.logger.error(f"❌ {error_msg}")
|
||||||
|
raise ValueError(error_msg)
|
||||||
else:
|
else:
|
||||||
pos = int(command)
|
pos = int(command)
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,20 @@ class VirtualRotavap:
|
|||||||
) -> bool:
|
) -> bool:
|
||||||
"""Execute evaporate action - 简化版 🌪️"""
|
"""Execute evaporate action - 简化版 🌪️"""
|
||||||
|
|
||||||
|
# 🔧 新增:确保time参数是数值类型
|
||||||
|
if isinstance(time, str):
|
||||||
|
try:
|
||||||
|
time = float(time)
|
||||||
|
except ValueError:
|
||||||
|
self.logger.error(f"❌ 无法转换时间参数 '{time}' 为数值,使用默认值180.0秒")
|
||||||
|
time = 180.0
|
||||||
|
elif not isinstance(time, (int, float)):
|
||||||
|
self.logger.error(f"❌ 时间参数类型无效: {type(time)},使用默认值180.0秒")
|
||||||
|
time = 180.0
|
||||||
|
|
||||||
|
# 确保time是float类型
|
||||||
|
time = float(time)
|
||||||
|
|
||||||
# 🔧 简化处理:如果vessel就是设备自己,直接操作
|
# 🔧 简化处理:如果vessel就是设备自己,直接操作
|
||||||
if vessel == self.device_id:
|
if vessel == self.device_id:
|
||||||
debug_print(f"🎯 在设备 {self.device_id} 上直接执行蒸发操作")
|
debug_print(f"🎯 在设备 {self.device_id} 上直接执行蒸发操作")
|
||||||
@@ -158,7 +172,7 @@ class VirtualRotavap:
|
|||||||
})
|
})
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 开始蒸发
|
# 开始蒸发 - 🔧 现在time已经确保是float类型
|
||||||
self.logger.info(f"🚀 启动蒸发程序! 预计用时 {time/60:.1f}分钟 ⏱️")
|
self.logger.info(f"🚀 启动蒸发程序! 预计用时 {time/60:.1f}分钟 ⏱️")
|
||||||
|
|
||||||
self.data.update({
|
self.data.update({
|
||||||
|
|||||||
@@ -4580,7 +4580,6 @@ virtual_solid_dispenser:
|
|||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
result: {}
|
||||||
required:
|
required:
|
||||||
@@ -4588,30 +4587,6 @@ virtual_solid_dispenser:
|
|||||||
title: cleanup参数
|
title: cleanup参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommandAsync
|
type: UniLabJsonCommandAsync
|
||||||
auto-find_solid_reagent_bottle:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
reagent_name: null
|
|
||||||
handles: []
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
reagent_name:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- reagent_name
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: find_solid_reagent_bottle参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-initialize:
|
auto-initialize:
|
||||||
feedback: {}
|
feedback: {}
|
||||||
goal: {}
|
goal: {}
|
||||||
@@ -4624,7 +4599,6 @@ virtual_solid_dispenser:
|
|||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
properties: {}
|
properties: {}
|
||||||
required: []
|
|
||||||
type: object
|
type: object
|
||||||
result: {}
|
result: {}
|
||||||
required:
|
required:
|
||||||
@@ -4632,58 +4606,9 @@ virtual_solid_dispenser:
|
|||||||
title: initialize参数
|
title: initialize参数
|
||||||
type: object
|
type: object
|
||||||
type: UniLabJsonCommandAsync
|
type: UniLabJsonCommandAsync
|
||||||
auto-parse_mass_string:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
mass_str: null
|
|
||||||
handles: []
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
mass_str:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- mass_str
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: parse_mass_string参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
auto-parse_mol_string:
|
|
||||||
feedback: {}
|
|
||||||
goal: {}
|
|
||||||
goal_default:
|
|
||||||
mol_str: null
|
|
||||||
handles: []
|
|
||||||
result: {}
|
|
||||||
schema:
|
|
||||||
description: ''
|
|
||||||
properties:
|
|
||||||
feedback: {}
|
|
||||||
goal:
|
|
||||||
properties:
|
|
||||||
mol_str:
|
|
||||||
type: string
|
|
||||||
required:
|
|
||||||
- mol_str
|
|
||||||
type: object
|
|
||||||
result: {}
|
|
||||||
required:
|
|
||||||
- goal
|
|
||||||
title: parse_mol_string参数
|
|
||||||
type: object
|
|
||||||
type: UniLabJsonCommand
|
|
||||||
module: unilabos.devices.virtual.virtual_solid_dispenser:VirtualSolidDispenser
|
module: unilabos.devices.virtual.virtual_solid_dispenser:VirtualSolidDispenser
|
||||||
status_types:
|
status_types:
|
||||||
current_reagent: str
|
current_reagent: str
|
||||||
device_info: dict
|
|
||||||
dispensed_amount: float
|
dispensed_amount: float
|
||||||
status: str
|
status: str
|
||||||
total_operations: int
|
total_operations: int
|
||||||
@@ -4716,14 +4641,18 @@ virtual_solid_dispenser:
|
|||||||
type: object
|
type: object
|
||||||
device_id:
|
device_id:
|
||||||
type: string
|
type: string
|
||||||
|
max_capacity:
|
||||||
|
default: 100.0
|
||||||
|
type: number
|
||||||
|
precision:
|
||||||
|
default: 0.001
|
||||||
|
type: number
|
||||||
required: []
|
required: []
|
||||||
type: object
|
type: object
|
||||||
data:
|
data:
|
||||||
properties:
|
properties:
|
||||||
current_reagent:
|
current_reagent:
|
||||||
type: string
|
type: string
|
||||||
device_info:
|
|
||||||
type: object
|
|
||||||
dispensed_amount:
|
dispensed_amount:
|
||||||
type: number
|
type: number
|
||||||
status:
|
status:
|
||||||
@@ -4735,7 +4664,6 @@ virtual_solid_dispenser:
|
|||||||
- current_reagent
|
- current_reagent
|
||||||
- dispensed_amount
|
- dispensed_amount
|
||||||
- total_operations
|
- total_operations
|
||||||
- device_info
|
|
||||||
type: object
|
type: object
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
virtual_stirrer:
|
virtual_stirrer:
|
||||||
|
|||||||
@@ -245,8 +245,13 @@ workstation:
|
|||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
amount: amount
|
amount: amount
|
||||||
|
equiv: equiv
|
||||||
|
event: event
|
||||||
mass: mass
|
mass: mass
|
||||||
|
mol: mol
|
||||||
purpose: purpose
|
purpose: purpose
|
||||||
|
rate_spec: rate_spec
|
||||||
|
ratio: ratio
|
||||||
reagent: reagent
|
reagent: reagent
|
||||||
stir: stir
|
stir: stir
|
||||||
stir_speed: stir_speed
|
stir_speed: stir_speed
|
||||||
@@ -470,6 +475,11 @@ workstation:
|
|||||||
ph_value: ph_value
|
ph_value: ph_value
|
||||||
reagent: reagent
|
reagent: reagent
|
||||||
vessel: vessel
|
vessel: vessel
|
||||||
|
volume: volume
|
||||||
|
stir: stir
|
||||||
|
stir_speed: stir_speed
|
||||||
|
stir_time: stir_time
|
||||||
|
settling_time: settling_time
|
||||||
goal_default:
|
goal_default:
|
||||||
ph_value: 0.0
|
ph_value: 0.0
|
||||||
reagent: ''
|
reagent: ''
|
||||||
@@ -493,6 +503,11 @@ workstation:
|
|||||||
z: 0.0
|
z: 0.0
|
||||||
sample_id: ''
|
sample_id: ''
|
||||||
type: ''
|
type: ''
|
||||||
|
volume: 0.0
|
||||||
|
stir: false
|
||||||
|
stir_speed: 300.0
|
||||||
|
stir_time: 60.0
|
||||||
|
settling_time: 30.0
|
||||||
handles:
|
handles:
|
||||||
input:
|
input:
|
||||||
- data_key: vessel
|
- data_key: vessel
|
||||||
@@ -511,6 +526,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -605,6 +622,21 @@ workstation:
|
|||||||
- data
|
- data
|
||||||
title: Resource
|
title: Resource
|
||||||
type: object
|
type: object
|
||||||
|
volume:
|
||||||
|
type: number
|
||||||
|
description: 'Volume of the solution to adjust pH'
|
||||||
|
stir:
|
||||||
|
type: boolean
|
||||||
|
description: "是否启用搅拌"
|
||||||
|
stir_speed:
|
||||||
|
type: number
|
||||||
|
description: "搅拌速度(RPM)"
|
||||||
|
stir_time:
|
||||||
|
type: number
|
||||||
|
description: "搅拌时间(秒)"
|
||||||
|
settling_time:
|
||||||
|
type: number
|
||||||
|
description: "pH平衡时间(秒)"
|
||||||
required:
|
required:
|
||||||
- vessel
|
- vessel
|
||||||
- ph_value
|
- ph_value
|
||||||
@@ -674,6 +706,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -853,6 +887,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -1060,6 +1096,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -1191,6 +1229,10 @@ workstation:
|
|||||||
feedback: {}
|
feedback: {}
|
||||||
goal:
|
goal:
|
||||||
amount: amount
|
amount: amount
|
||||||
|
event: event
|
||||||
|
mass: mass
|
||||||
|
mol: mol
|
||||||
|
reagent: reagent
|
||||||
solvent: solvent
|
solvent: solvent
|
||||||
stir_speed: stir_speed
|
stir_speed: stir_speed
|
||||||
temp: temp
|
temp: temp
|
||||||
@@ -1246,6 +1288,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -1429,6 +1473,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -1585,6 +1631,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -1778,6 +1826,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: vessel_out
|
handler_key: vessel_out
|
||||||
label: Evaporation Vessel
|
label: Evaporation Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_nodes
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -2014,6 +2064,9 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: filtrate_out
|
handler_key: filtrate_out
|
||||||
label: Filtrate Vessel
|
label: Filtrate Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
filtrate_vessel: unilabos_resources
|
||||||
|
vessel: unilabos_nodes
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -2195,7 +2248,7 @@ workstation:
|
|||||||
type: number
|
type: number
|
||||||
required:
|
required:
|
||||||
- vessel
|
- vessel
|
||||||
- filtrate_vessel
|
- #filtrate_vessel
|
||||||
- stir
|
- stir
|
||||||
- stir_speed
|
- stir_speed
|
||||||
- temp
|
- temp
|
||||||
@@ -2325,6 +2378,9 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: ToVesselOut
|
handler_key: ToVesselOut
|
||||||
label: To Vessel
|
label: To Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
from_vessel: unilabos_resources
|
||||||
|
to_vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -2656,6 +2712,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -2835,6 +2893,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -2986,6 +3046,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -3135,6 +3197,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -3354,6 +3418,9 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: ToVesselOut
|
handler_key: ToVesselOut
|
||||||
label: To Vessel
|
label: To Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
from_vessel: unilabos_nodes
|
||||||
|
to_vessel: unilabos_nodes
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -3667,6 +3734,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -4019,6 +4088,10 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: ToVesselOut
|
handler_key: ToVesselOut
|
||||||
label: To Vessel
|
label: To Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
column: unilabos_devices
|
||||||
|
from_vessel: unilabos_resources
|
||||||
|
to_vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -4422,6 +4495,11 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: ToVesselOut
|
handler_key: ToVesselOut
|
||||||
label: To Vessel
|
label: To Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
from_vessel: unilabos_resources
|
||||||
|
to_vessel: unilabos_resources
|
||||||
|
waste_phase_to_vessel: unilabos_resources
|
||||||
|
waste_vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -5053,6 +5131,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -5225,6 +5305,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -5391,6 +5473,8 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: VesselOut
|
handler_key: VesselOut
|
||||||
label: Vessel
|
label: Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -5556,6 +5640,9 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: ToVesselOut
|
handler_key: ToVesselOut
|
||||||
label: To Vessel
|
label: To Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
from_vessel: unilabos_nodes
|
||||||
|
to_vessel: unilabos_nodes
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
@@ -5722,6 +5809,9 @@ workstation:
|
|||||||
data_type: resource
|
data_type: resource
|
||||||
handler_key: filtrate_vessel_out
|
handler_key: filtrate_vessel_out
|
||||||
label: Filtrate Vessel
|
label: Filtrate Vessel
|
||||||
|
placeholder_keys:
|
||||||
|
filtrate_vessel: unilabos_resources
|
||||||
|
vessel: unilabos_resources
|
||||||
result: {}
|
result: {}
|
||||||
schema:
|
schema:
|
||||||
description: ''
|
description: ''
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ def dict_to_nested_dict(nodes: dict, devices_only: bool = False) -> dict:
|
|||||||
root_nodes = {
|
root_nodes = {
|
||||||
node["id"]: node
|
node["id"]: node
|
||||||
for node in nodes_list
|
for node in nodes_list
|
||||||
if node.get("parent", node.get("parent_name")) in [None, "", "None", np.nan]
|
if node.get("parent", node.get("parent_name")) in [None, "", "None", np.nan] or len(nodes_list) == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# 如果存在多个根节点,返回所有根节点
|
# 如果存在多个根节点,返回所有根节点
|
||||||
|
|||||||
@@ -189,45 +189,26 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
|
|||||||
|
|
||||||
# # 🔧 完全禁用Host查询,直接使用转换后的数据
|
# # 🔧 完全禁用Host查询,直接使用转换后的数据
|
||||||
# print(f"🔧 跳过Host查询,直接使用转换后的数据")
|
# print(f"🔧 跳过Host查询,直接使用转换后的数据")
|
||||||
|
# 向Host查询物料当前状态
|
||||||
|
for k, v in goal.get_fields_and_field_types().items():
|
||||||
|
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
|
||||||
|
r = ResourceGet.Request()
|
||||||
|
resource_id = (
|
||||||
|
protocol_kwargs[k]["id"] if v == "unilabos_msgs/Resource" else protocol_kwargs[k][0]["id"]
|
||||||
|
)
|
||||||
|
r.id = resource_id
|
||||||
|
r.with_children = True
|
||||||
|
response = await self._resource_clients["resource_get"].call_async(r)
|
||||||
|
protocol_kwargs[k] = list_to_nested_dict(
|
||||||
|
[convert_from_ros_msg(rs) for rs in response.resources]
|
||||||
|
)
|
||||||
|
|
||||||
# 🔧 额外验证:确保vessel数据完整
|
self.lab_logger().info(f"🔍 最终传递给协议的 protocol_kwargs: {protocol_kwargs}")
|
||||||
if 'vessel' in protocol_kwargs:
|
self.lab_logger().info(f"🔍 最终的 vessel: {protocol_kwargs.get('vessel', 'NOT_FOUND')}")
|
||||||
vessel_data = protocol_kwargs['vessel']
|
|
||||||
#print(f"🔍 验证vessel数据: {vessel_data}")
|
|
||||||
|
|
||||||
# 如果vessel是空字典,尝试重新构建
|
|
||||||
if not vessel_data or (isinstance(vessel_data, dict) and not vessel_data):
|
|
||||||
# print(f"⚠️ vessel数据为空,尝试从原始goal重新提取...")
|
|
||||||
|
|
||||||
# 直接从原始goal提取vessel
|
|
||||||
if hasattr(goal, 'vessel') and goal.vessel:
|
|
||||||
# print(f"🔍 原始goal.vessel: {goal.vessel}")
|
|
||||||
# 手动转换vessel
|
|
||||||
vessel_data = {
|
|
||||||
'id': goal.vessel.id,
|
|
||||||
'name': goal.vessel.name,
|
|
||||||
'type': goal.vessel.type,
|
|
||||||
'category': goal.vessel.category,
|
|
||||||
'config': goal.vessel.config,
|
|
||||||
'data': goal.vessel.data
|
|
||||||
}
|
|
||||||
protocol_kwargs['vessel'] = vessel_data
|
|
||||||
# print(f"✅ 手动重建vessel数据: {vessel_data}")
|
|
||||||
else:
|
|
||||||
# print(f"❌ 无法从原始goal提取vessel数据")
|
|
||||||
# 创建一个基本的vessel
|
|
||||||
vessel_data = {'id': 'default_vessel'}
|
|
||||||
protocol_kwargs['vessel'] = vessel_data
|
|
||||||
# print(f"🔧 创建默认vessel: {vessel_data}")
|
|
||||||
|
|
||||||
#print(f"🔍 最终传递给协议的 protocol_kwargs: {protocol_kwargs}")
|
|
||||||
#print(f"🔍 最终的 vessel: {protocol_kwargs.get('vessel', 'NOT_FOUND')}")
|
|
||||||
|
|
||||||
from unilabos.resources.graphio import physical_setup_graph
|
from unilabos.resources.graphio import physical_setup_graph
|
||||||
|
|
||||||
self.lab_logger().info(f"Working on physical setup: {physical_setup_graph}")
|
self.lab_logger().info(f"Working on physical setup: {physical_setup_graph}")
|
||||||
self.lab_logger().info(f"Protocol kwargs: {goal}")
|
|
||||||
self.lab_logger().info(f"Protocol kwargs: {action_value_mapping}")
|
|
||||||
protocol_steps = protocol_steps_generator(G=physical_setup_graph, **protocol_kwargs)
|
protocol_steps = protocol_steps_generator(G=physical_setup_graph, **protocol_kwargs)
|
||||||
|
|
||||||
self.lab_logger().info(f"Goal received: {protocol_kwargs}, running steps: \n{protocol_steps}")
|
self.lab_logger().info(f"Goal received: {protocol_kwargs}, running steps: \n{protocol_steps}")
|
||||||
@@ -263,14 +244,14 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# # 向Host更新物料当前状态
|
# 向Host更新物料当前状态
|
||||||
# for k, v in goal.get_fields_and_field_types().items():
|
for k, v in goal.get_fields_and_field_types().items():
|
||||||
# if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
|
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
|
||||||
# r = ResourceUpdate.Request()
|
r = ResourceUpdate.Request()
|
||||||
# r.resources = [
|
r.resources = [
|
||||||
# convert_to_ros_msg(Resource, rs) for rs in nested_dict_to_list(protocol_kwargs[k])
|
convert_to_ros_msg(Resource, rs) for rs in nested_dict_to_list(protocol_kwargs[k])
|
||||||
# ]
|
]
|
||||||
# response = await self._resource_clients["resource_update"].call_async(r)
|
response = await self._resource_clients["resource_update"].call_async(r)
|
||||||
|
|
||||||
# 设置成功状态和返回值
|
# 设置成功状态和返回值
|
||||||
execution_success = True
|
execution_success = True
|
||||||
|
|||||||
Reference in New Issue
Block a user