fix sub-resource query in protocol node compiling

This commit is contained in:
Junhan Chang
2025-07-18 19:03:31 +08:00
parent 992b8a03d0
commit 7edffa270e
6 changed files with 34 additions and 298 deletions

View File

@@ -237,14 +237,14 @@ def generate_adjust_ph_protocol(
# 统一处理vessel参数
if isinstance(vessel, dict):
vessel_id = vessel.get("id", "")
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("❌ vessel 参数无效必须包含id字段或直接提供容器ID")
debug_print(f"❌ vessel 参数无效必须包含id字段或直接提供容器ID. vessel: {vessel}")
raise ValueError("vessel 参数无效必须包含id字段或直接提供容器ID")
debug_print("=" * 60)

View File

@@ -149,7 +149,7 @@ def extract_vessel_id(vessel: Union[str, dict]) -> str:
str: vessel_id
"""
if isinstance(vessel, dict):
vessel_id = vessel.get("id", "")
vessel_id = list(vessel.values())[0].get("id", "")
debug_print(f"🔧 从vessel字典提取ID: {vessel_id}")
return vessel_id
elif isinstance(vessel, str):

View File

@@ -165,7 +165,7 @@ def extract_vessel_id(vessel: Union[str, dict]) -> str:
str: vessel_id
"""
if isinstance(vessel, dict):
vessel_id = vessel.get("id", "")
vessel_id = list(vessel.values())[0].get("id", "")
debug_print(f"🔧 从vessel字典提取ID: {vessel_id}")
return vessel_id
elif isinstance(vessel, str):

View File

@@ -4548,7 +4548,6 @@ virtual_solid_dispenser:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
@@ -4556,30 +4555,6 @@ virtual_solid_dispenser:
title: cleanup参数
type: object
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:
feedback: {}
goal: {}
@@ -4592,7 +4567,6 @@ virtual_solid_dispenser:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
@@ -4600,58 +4574,9 @@ virtual_solid_dispenser:
title: initialize参数
type: object
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
status_types:
current_reagent: str
device_info: dict
dispensed_amount: float
status: str
total_operations: int
@@ -4683,14 +4608,18 @@ virtual_solid_dispenser:
type: object
device_id:
type: string
max_capacity:
default: 100.0
type: number
precision:
default: 0.001
type: number
required: []
type: object
data:
properties:
current_reagent:
type: string
device_info:
type: object
dispensed_amount:
type: number
status:
@@ -4702,7 +4631,6 @@ virtual_solid_dispenser:
- current_reagent
- dispensed_amount
- total_operations
- device_info
type: object
version: 0.0.1
virtual_stirrer:
@@ -5993,176 +5921,3 @@ virtual_vacuum_pump:
- status
type: object
version: 0.0.1
virtual_solid_dispenser:
class:
action_value_mappings:
auto-cleanup:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: cleanup的参数schema
properties:
feedback: {}
goal:
properties: {}
type: object
result: {}
required:
- goal
title: cleanup参数
type: object
type: UniLabJsonCommandAsync
auto-initialize:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: initialize的参数schema
properties:
feedback: {}
goal:
properties: {}
type: object
result: {}
required:
- goal
title: initialize参数
type: object
type: UniLabJsonCommandAsync
add_solid:
feedback:
current_status: status
progress: progress
goal:
vessel: vessel
reagent: reagent
mass: mass
mol: mol
purpose: purpose
event: event
rate_spec: rate_spec
equiv: equiv
ratio: ratio
goal_default:
vessel: ''
reagent: ''
mass: ''
mol: ''
purpose: ''
event: ''
rate_spec: ''
equiv: ''
ratio: ''
handles: []
result:
success: success
message: message
return_info: return_info
schema:
description: ''
properties:
feedback:
properties:
current_status:
type: string
progress:
type: number
type: object
goal:
properties:
vessel:
type: string
reagent:
type: string
mass:
type: string
mol:
type: string
purpose:
type: string
event:
type: string
rate_spec:
type: string
equiv:
type: string
ratio:
type: string
type: object
result:
properties:
success:
type: boolean
message:
type: string
return_info:
type: string
type: object
required:
- goal
title: AddSolid
type: object
type: Add # 🔧 使用 Add action type
module: unilabos.devices.virtual.virtual_solid_dispenser:VirtualSolidDispenser
status_types:
status: str
current_reagent: str
dispensed_amount: float
total_operations: int
type: python
description: Virtual Solid Dispenser for Add Protocol Testing - supports mass and molar additions
handles:
- data_key: solid_out
data_source: executor
data_type: resource
description: 固体试剂输出口
handler_key: SolidOut
io_type: source
label: SolidOut
side: SOUTH
- data_key: solid_in
data_source: handle
data_type: resource
description: 固体试剂输入口(连接试剂瓶)
handler_key: SolidIn
io_type: target
label: SolidIn
side: NORTH
icon: ''
init_param_schema:
config:
properties:
max_capacity:
default: 100.0
type: number
precision:
default: 0.001
type: number
config:
type: object
device_id:
type: string
required: []
type: object
data:
properties:
status:
type: string
current_reagent:
type: string
dispensed_amount:
type: number
total_operations:
type: integer
required:
- status
- current_reagent
- dispensed_amount
- total_operations
type: object
version: 0.0.1

View File

@@ -248,7 +248,7 @@ def dict_to_nested_dict(nodes: dict, devices_only: bool = False) -> dict:
root_nodes = {
node["id"]: node
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
}
# 如果存在多个根节点,返回所有根节点

View File

@@ -189,45 +189,26 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
# # 🔧 完全禁用Host查询直接使用转换后的数据
# print(f"🔧 跳过Host查询直接使用转换后的数据")
# 🔧 额外验证确保vessel数据完整
if 'vessel' in protocol_kwargs:
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}")
# 向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]
)
#print(f"🔍 最终传递给协议的 protocol_kwargs: {protocol_kwargs}")
#print(f"🔍 最终的 vessel: {protocol_kwargs.get('vessel', 'NOT_FOUND')}")
self.lab_logger().info(f"🔍 最终传递给协议的 protocol_kwargs: {protocol_kwargs}")
self.lab_logger().info(f"🔍 最终的 vessel: {protocol_kwargs.get('vessel', 'NOT_FOUND')}")
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"Protocol kwargs: {goal}")
self.lab_logger().info(f"Protocol kwargs: {action_value_mapping}")
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}")
@@ -263,14 +244,14 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
}
)
# # 向Host更新物料当前状态
# for k, v in goal.get_fields_and_field_types().items():
# if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
# r = ResourceUpdate.Request()
# r.resources = [
# 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)
# 向Host更新物料当前状态
for k, v in goal.get_fields_and_field_types().items():
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
r = ResourceUpdate.Request()
r.resources = [
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)
# 设置成功状态和返回值
execution_success = True