diff --git a/unilabos/compile/adjustph_protocol.py b/unilabos/compile/adjustph_protocol.py index 0f7dce2..374268a 100644 --- a/unilabos/compile/adjustph_protocol.py +++ b/unilabos/compile/adjustph_protocol.py @@ -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) diff --git a/unilabos/compile/stir_protocol.py b/unilabos/compile/stir_protocol.py index 70405ff..e13c1f8 100644 --- a/unilabos/compile/stir_protocol.py +++ b/unilabos/compile/stir_protocol.py @@ -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): diff --git a/unilabos/compile/wash_solid_protocol.py b/unilabos/compile/wash_solid_protocol.py index e19d768..b167c85 100644 --- a/unilabos/compile/wash_solid_protocol.py +++ b/unilabos/compile/wash_solid_protocol.py @@ -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): diff --git a/unilabos/registry/devices/virtual_device.yaml b/unilabos/registry/devices/virtual_device.yaml index 4823a2f..8879632 100644 --- a/unilabos/registry/devices/virtual_device.yaml +++ b/unilabos/registry/devices/virtual_device.yaml @@ -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 \ No newline at end of file diff --git a/unilabos/resources/graphio.py b/unilabos/resources/graphio.py index 956e7fb..0e5e27f 100644 --- a/unilabos/resources/graphio.py +++ b/unilabos/resources/graphio.py @@ -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 } # 如果存在多个根节点,返回所有根节点 diff --git a/unilabos/ros/nodes/presets/protocol_node.py b/unilabos/ros/nodes/presets/protocol_node.py index a17d4e9..72e3d65 100644 --- a/unilabos/ros/nodes/presets/protocol_node.py +++ b/unilabos/ros/nodes/presets/protocol_node.py @@ -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"]: + 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"]: - # 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"]: + 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