From 1340bae8386d73e906845db92ff13c94eff10b03 Mon Sep 17 00:00:00 2001 From: zhangshixiang <554662886@qq.com> Date: Tue, 13 Jan 2026 18:33:32 +0800 Subject: [PATCH] Revert "Merge branch 'dev' into prcix9320" This reverts commit ae75f07c8e8ea46b0ac7f6e2eae121847d7442e1. --- unilabos/registry/devices/liquid_handler.yaml | 8 +- unilabos/registry/registry.py | 32 +---- unilabos/resources/graphio.py | 6 +- unilabos/resources/resource_tracker.py | 50 ++------ unilabos/ros/msgs/message_converter.py | 6 +- unilabos/ros/nodes/base_device_node.py | 120 +++++++----------- unilabos/ros/nodes/presets/host_node.py | 23 ++-- 7 files changed, 75 insertions(+), 170 deletions(-) diff --git a/unilabos/registry/devices/liquid_handler.yaml b/unilabos/registry/devices/liquid_handler.yaml index 85891ff..c611d21 100644 --- a/unilabos/registry/devices/liquid_handler.yaml +++ b/unilabos/registry/devices/liquid_handler.yaml @@ -9284,13 +9284,7 @@ liquid_handler.prcxi: z: 0.0 sample_id: '' type: '' - handles: - input: - - data_key: wells - data_source: handle - data_type: resource - handler_key: input_wells - label: InputWells + handles: {} placeholder_keys: wells: unilabos_resources result: {} diff --git a/unilabos/registry/registry.py b/unilabos/registry/registry.py index 64aba3c..20e0245 100644 --- a/unilabos/registry/registry.py +++ b/unilabos/registry/registry.py @@ -124,25 +124,11 @@ class Registry: "output": [ { "handler_key": "labware", - "data_type": "resource", "label": "Labware", - "data_source": "executor", - "data_key": "created_resource_tree.@flatten", - }, - { - "handler_key": "liquid_slots", "data_type": "resource", - "label": "LiquidSlots", - "data_source": "executor", - "data_key": "liquid_input_resource_tree.@flatten", - }, - { - "handler_key": "materials", - "data_type": "resource", - "label": "AllMaterials", - "data_source": "executor", - "data_key": "[created_resource_tree,liquid_input_resource_tree].@flatten.@flatten", - }, + "data_source": "handle", + "data_key": "liquid", + } ] }, "placeholder_keys": { @@ -200,17 +186,7 @@ class Registry: "resources": "unilabos_resources", }, "goal_default": {}, - "handles": { - "input": [ - { - "handler_key": "input_resources", - "data_type": "resource", - "label": "InputResources", - "data_source": "handle", - "data_key": "resources", # 不为空 - }, - ] - }, + "handles": {}, }, }, }, diff --git a/unilabos/resources/graphio.py b/unilabos/resources/graphio.py index 252c8f4..faf0482 100644 --- a/unilabos/resources/graphio.py +++ b/unilabos/resources/graphio.py @@ -1151,7 +1151,11 @@ def initialize_resource(resource_config: dict, resource_type: Any = None) -> Uni if resource_class_config["type"] == "pylabrobot": resource_plr = RESOURCE(name=resource_config["name"]) if resource_type != ResourcePLR: - tree_sets = ResourceTreeSet.from_plr_resources([resource_plr], known_newly_created=True) + tree_sets = ResourceTreeSet.from_plr_resources([resource_plr]) + # r = resource_plr_to_ulab(resource_plr=resource_plr, parent_name=resource_config.get("parent", None)) + # # r = resource_plr_to_ulab(resource_plr=resource_plr) + # if resource_config.get("position") is not None: + # r["position"] = resource_config["position"] r = tree_sets.dump() else: r = resource_plr diff --git a/unilabos/resources/resource_tracker.py b/unilabos/resources/resource_tracker.py index d93cb4e..610ba3d 100644 --- a/unilabos/resources/resource_tracker.py +++ b/unilabos/resources/resource_tracker.py @@ -147,17 +147,17 @@ class ResourceDictInstance(object): if not content.get("extra"): # MagicCode content["extra"] = {} if "position" in content: - pose = content.get("pose", {}) - if "position" not in pose: + pose = content.get("pose",{}) + if "position" not in pose : if "position" in content["position"]: pose["position"] = content["position"]["position"] else: pose["position"] = {"x": 0, "y": 0, "z": 0} if "size" not in pose: pose["size"] = { - "width": content["config"].get("size_x", 0), - "height": content["config"].get("size_y", 0), - "depth": content["config"].get("size_z", 0), + "width": content["config"].get("size_x", 0), + "height": content["config"].get("size_y", 0), + "depth": content["config"].get("size_z", 0) } content["pose"] = pose return ResourceDictInstance(ResourceDict.model_validate(content)) @@ -322,7 +322,7 @@ class ResourceTreeSet(object): ) @classmethod - def from_plr_resources(cls, resources: List["PLRResource"], known_newly_created=False) -> "ResourceTreeSet": + def from_plr_resources(cls, resources: List["PLRResource"]) -> "ResourceTreeSet": """ 从plr资源创建ResourceTreeSet """ @@ -349,8 +349,7 @@ class ResourceTreeSet(object): if not uid: uid = str(uuid.uuid4()) res.unilabos_uuid = uid - if not known_newly_created: - logger.warning(f"{res}没有uuid,请设置后再传入,默认填充{uid}!\n{traceback.format_exc()}") + logger.warning(f"{res}没有uuid,请设置后再传入,默认填充{uid}!\n{traceback.format_exc()}") # 获取unilabos_extra,默认为空字典 extra = getattr(res, "unilabos_extra", {}) @@ -449,13 +448,7 @@ class ResourceTreeSet(object): from pylabrobot.utils.object_parsing import find_subclass # 类型映射 - TYPE_MAP = { - "plate": "Plate", - "well": "Well", - "deck": "Deck", - "container": "RegularContainer", - "tip_spot": "TipSpot", - } + TYPE_MAP = {"plate": "Plate", "well": "Well", "deck": "Deck", "container": "RegularContainer", "tip_spot": "TipSpot"} def collect_node_data(node: ResourceDictInstance, name_to_uuid: dict, all_states: dict, name_to_extra: dict): """一次遍历收集 name_to_uuid, all_states 和 name_to_extra""" @@ -925,33 +918,6 @@ class DeviceNodeResourceTracker(object): return self._traverse_and_process(resource, process) - def loop_find_with_uuid(self, resource, target_uuid: str): - """ - 递归遍历资源树,根据 uuid 查找并返回对应的资源 - - Args: - resource: 资源对象(可以是list、dict或实例) - target_uuid: 要查找的uuid - - Returns: - 找到的资源对象,未找到则返回None - """ - found_resource = None - - def process(res): - nonlocal found_resource - if found_resource is not None: - return 0 # 已找到,跳过后续处理 - current_uuid = self._get_resource_attr(res, "uuid", "unilabos_uuid") - if current_uuid and current_uuid == target_uuid: - found_resource = res - logger.trace(f"找到资源UUID: {target_uuid}") - return 1 - return 0 - - self._traverse_and_process(resource, process) - return found_resource - def loop_set_extra(self, resource, name_to_extra_map: Dict[str, dict]) -> int: """ 递归遍历资源树,根据 name 设置所有节点的 extra diff --git a/unilabos/ros/msgs/message_converter.py b/unilabos/ros/msgs/message_converter.py index 632d5e1..e8570d3 100644 --- a/unilabos/ros/msgs/message_converter.py +++ b/unilabos/ros/msgs/message_converter.py @@ -159,14 +159,10 @@ _msg_converter: Dict[Type, Any] = { else Pose() ), config=json.dumps(x.get("config", {})), - data=json.dumps(obtain_data_with_uuid(x)), + data=json.dumps(x.get("data", {})), ), } -def obtain_data_with_uuid(x: dict): - data = x.get("data", {}) - data["unilabos_uuid"] = x.get("uuid", None) - return data def json_or_yaml_loads(data: str) -> Any: try: diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index 8dd4a28..89c4d39 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -430,14 +430,11 @@ class BaseROS2DeviceNode(Node, Generic[T]): }) tree_response: SerialCommand.Response = await client.call_async(request) uuid_maps = json.loads(tree_response.response) - plr_instances = rts.to_plr_resources() - for plr_instance in plr_instances: - self.resource_tracker.loop_update_uuid(plr_instance, uuid_maps) - rts: ResourceTreeSet = ResourceTreeSet.from_plr_resources(plr_instances) + self.resource_tracker.loop_update_uuid(input_resources, uuid_maps) self.lab_logger().info(f"Resource tree added. UUID mapping: {len(uuid_maps)} nodes") final_response = { - "created_resource_tree": rts.dump(), - "liquid_input_resource_tree": [], + "created_resources": rts.dump(), + "liquid_input_resources": [], } res.response = json.dumps(final_response) # 如果driver自己就有assign的方法,那就使用driver自己的assign方法 @@ -463,7 +460,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): return res try: if len(rts.root_nodes) == 1 and parent_resource is not None: - plr_instance = plr_instances[0] + plr_instance = rts.to_plr_resources()[0] if isinstance(plr_instance, Plate): empty_liquid_info_in: List[Tuple[Optional[str], float]] = [(None, 0)] * plr_instance.num_items if len(ADD_LIQUID_TYPE) == 1 and len(LIQUID_VOLUME) == 1 and len(LIQUID_INPUT_SLOT) > 1: @@ -488,7 +485,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): input_wells = [] for r in LIQUID_INPUT_SLOT: input_wells.append(plr_instance.children[r]) - final_response["liquid_input_resource_tree"] = ResourceTreeSet.from_plr_resources(input_wells).dump() + final_response["liquid_input_resources"] = ResourceTreeSet.from_plr_resources(input_wells).dump() res.response = json.dumps(final_response) if issubclass(parent_resource.__class__, Deck) and hasattr(parent_resource, "assign_child_at_slot") and "slot" in other_calling_param: other_calling_param["slot"] = int(other_calling_param["slot"]) @@ -1266,8 +1263,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): ACTION, action_paramtypes = self.get_real_function(self.driver_instance, action_name) action_kwargs = convert_from_ros_msg_with_mapping(goal, action_value_mapping["goal"]) - self.lab_logger().debug(f"任务 {ACTION.__name__} 接收到原始目标: {str(action_kwargs)[:1000]}") - self.lab_logger().trace(f"任务 {ACTION.__name__} 接收到原始目标: {action_kwargs}") + self.lab_logger().debug(f"任务 {ACTION.__name__} 接收到原始目标: {action_kwargs}") error_skip = False # 向Host查询物料当前状态,如果是host本身的增加物料的请求,则直接跳过 if action_name not in ["create_resource_detailed", "create_resource"]: @@ -1283,14 +1279,9 @@ class BaseROS2DeviceNode(Node, Generic[T]): # 批量查询资源 queried_resources = [] for resource_data in resource_inputs: - unilabos_uuid = resource_data.get("data", {}).get("unilabos_uuid") - if unilabos_uuid is None: - plr_resource = await self.get_resource_with_dir( - resource_id=resource_data["id"], with_children=True - ) - else: - resource_tree = await self.get_resource([unilabos_uuid]) - plr_resource = resource_tree.to_plr_resources()[0] + plr_resource = await self.get_resource_with_dir( + resource_id=resource_data["id"], with_children=True + ) if "sample_id" in resource_data: plr_resource.unilabos_extra["sample_uuid"] = resource_data["sample_id"] queried_resources.append(plr_resource) @@ -1339,8 +1330,9 @@ class BaseROS2DeviceNode(Node, Generic[T]): execution_success = True except Exception as _: execution_error = traceback.format_exc() - error(f"异步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{str(action_kwargs)[:1000]}") - trace(f"异步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}") + error( + f"异步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}" + ) future = ROS2DeviceNode.run_async_func(ACTION, trace_error=False, **action_kwargs) future.add_done_callback(_handle_future_exception) @@ -1360,9 +1352,8 @@ class BaseROS2DeviceNode(Node, Generic[T]): except Exception as _: execution_error = traceback.format_exc() error( - f"同步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{str(action_kwargs)[:1000]}") - trace( - f"同步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}") + f"同步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}" + ) future.add_done_callback(_handle_future_exception) @@ -1430,7 +1421,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): for r in rs: res = self.resource_tracker.parent_resource(r) # 获取 resource 对象 else: - res = self.resource_tracker.parent_resource(rs) + res = self.resource_tracker.parent_resource(r) if id(res) not in seen: seen.add(id(res)) unique_resources.append(res) @@ -1506,7 +1497,8 @@ class BaseROS2DeviceNode(Node, Generic[T]): resource_data = function_args[arg_name] if isinstance(resource_data, dict) and "id" in resource_data: try: - function_args[arg_name] = self._convert_resources_sync(resource_data["uuid"])[0] + converted_resource = self._convert_resource_sync(resource_data) + function_args[arg_name] = converted_resource except Exception as e: self.lab_logger().error( f"转换ResourceSlot参数 {arg_name} 失败: {e}\n{traceback.format_exc()}" @@ -1520,8 +1512,12 @@ class BaseROS2DeviceNode(Node, Generic[T]): resource_list = function_args[arg_name] if isinstance(resource_list, list): try: - uuids = [r["uuid"] for r in resource_list if isinstance(r, dict) and "id" in r] - function_args[arg_name] = self._convert_resources_sync(*uuids) if uuids else [] + converted_resources = [] + for resource_data in resource_list: + if isinstance(resource_data, dict) and "id" in resource_data: + converted_resource = self._convert_resource_sync(resource_data) + converted_resources.append(converted_resource) + function_args[arg_name] = converted_resources except Exception as e: self.lab_logger().error( f"转换ResourceSlot列表参数 {arg_name} 失败: {e}\n{traceback.format_exc()}" @@ -1534,27 +1530,20 @@ class BaseROS2DeviceNode(Node, Generic[T]): f"执行动作时JSON缺少function_name或function_args: {ex}\n原JSON: {string}\n{traceback.format_exc()}" ) - def _convert_resources_sync(self, *uuids: str) -> List["ResourcePLR"]: - """同步转换资源 UUID 为实例 - - Args: - *uuids: 一个或多个资源 UUID - - Returns: - 单个 UUID 时返回单个资源实例,多个 UUID 时返回资源实例列表 - """ - if not uuids: - raise ValueError("至少需要提供一个 UUID") - - uuids_list = list(uuids) - future = self._resource_clients["c2s_update_resource_tree"].call_async(SerialCommand.Request( - command=json.dumps( - { - "data": {"data": uuids_list, "with_children": True}, - "action": "get", - } - ) - )) + def _convert_resource_sync(self, resource_data: Dict[str, Any]): + """同步转换资源数据为实例""" + # 创建资源查询请求 + r = SerialCommand.Request() + r.command = json.dumps( + { + "id": resource_data.get("id", None), + "uuid": resource_data.get("uuid", None), + "with_children": True, + } + ) + + # 同步调用资源查询服务 + future = self._resource_clients["resource_get"].call_async(r) # 等待结果(使用while循环,每次sleep 0.05秒,最多等待30秒) timeout = 30.0 @@ -1564,40 +1553,27 @@ class BaseROS2DeviceNode(Node, Generic[T]): elapsed += 0.05 if not future.done(): - raise Exception(f"资源查询超时: {uuids_list}") + raise Exception(f"资源查询超时: {resource_data}") response = future.result() if response is None: - raise Exception(f"资源查询返回空结果: {uuids_list}") + raise Exception(f"资源查询返回空结果: {resource_data}") raw_data = json.loads(response.response) # 转换为 PLR 资源 tree_set = ResourceTreeSet.from_raw_dict_list(raw_data) - if not len(tree_set.trees): - raise Exception(f"资源查询返回空树: {raw_data}") - plr_resources = tree_set.to_plr_resources() + plr_resource = tree_set.to_plr_resources()[0] # 通过资源跟踪器获取本地实例 - figured_resources: List[ResourcePLR] = [] - for plr_resource, tree in zip(plr_resources, tree_set.trees): - res = self.resource_tracker.figure_resource(plr_resource, try_mode=True) - if len(res) == 0: - self.lab_logger().warning(f"资源转换未能索引到实例: {tree.root_node.res_content},返回新建实例") - figured_resources.append(plr_resource) - elif len(res) == 1: - figured_resources.append(res[0]) - else: - raise ValueError(f"资源转换得到多个实例: {res}") - - mapped_plr_resources = [] - for uuid in uuids_list: - for plr_resource in figured_resources: - r = self.resource_tracker.loop_find_with_uuid(plr_resource, uuid) - mapped_plr_resources.append(r) - break - - return mapped_plr_resources + res = self.resource_tracker.figure_resource(plr_resource, try_mode=True) + if len(res) == 0: + self.lab_logger().warning(f"资源转换未能索引到实例: {resource_data},返回新建实例") + return plr_resource + elif len(res) == 1: + return res[0] + else: + raise ValueError(f"资源转换得到多个实例: {res}") async def _execute_driver_command_async(self, string: str): try: diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index 0f6b697..69c12f8 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -23,7 +23,6 @@ from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialComma from unique_identifier_msgs.msg import UUID from unilabos.registry.registry import lab_registry -from unilabos.resources.container import RegularContainer from unilabos.resources.graphio import initialize_resource from unilabos.resources.registry import add_schema from unilabos.ros.initialize_device import initialize_device_from_dict @@ -587,10 +586,11 @@ class HostNode(BaseROS2DeviceNode): ) try: - assert len(response) == 1, "Create Resource应当只返回一个结果" + new_li = [] for i in response: res = json.loads(i) - return res + new_li.append(res) + return {"resources": new_li, "liquid_input_resources": new_li} except Exception as ex: pass _n = "\n" @@ -795,8 +795,7 @@ class HostNode(BaseROS2DeviceNode): assign_sample_id(action_kwargs) goal_msg = convert_to_ros_msg(action_client._action_type.Goal(), action_kwargs) - self.lab_logger().info(f"[Host Node] Sending goal for {action_id}: {str(goal_msg)[:1000]}") - self.lab_logger().trace(f"[Host Node] Sending goal for {action_id}: {goal_msg}") + self.lab_logger().info(f"[Host Node] Sending goal for {action_id}: {goal_msg}") action_client.wait_for_server() goal_uuid_obj = UUID(uuid=list(u.bytes)) @@ -1134,11 +1133,11 @@ class HostNode(BaseROS2DeviceNode): 接收序列化的 ResourceTreeSet 数据并进行处理 """ + self.lab_logger().info(f"[Host Node-Resource] Resource tree add request received") try: # 解析请求数据 data = json.loads(request.command) action = data["action"] - self.lab_logger().info(f"[Host Node-Resource] Resource tree {action} request received") data = data["data"] if action == "add": await self._resource_tree_action_add_callback(data, response) @@ -1244,7 +1243,7 @@ class HostNode(BaseROS2DeviceNode): data = json.loads(request.command) if "uuid" in data and data["uuid"] is not None: http_req = http_client.resource_tree_get([data["uuid"]], data["with_children"]) - elif "id" in data: + elif "id" in data and data["id"].startswith("/"): http_req = http_client.resource_get(data["id"], data["with_children"]) else: raise ValueError("没有使用正确的物料 id 或 uuid") @@ -1454,16 +1453,10 @@ class HostNode(BaseROS2DeviceNode): } def test_resource( - self, resource: ResourceSlot = None, resources: List[ResourceSlot] = None, device: DeviceSlot = None, devices: List[DeviceSlot] = None + self, resource: ResourceSlot, resources: List[ResourceSlot], device: DeviceSlot, devices: List[DeviceSlot] ) -> TestResourceReturn: - if resources is None: - resources = [] - if devices is None: - devices = [] - if resource is None: - resource = RegularContainer("test_resource传入None") return { - "resources": ResourceTreeSet.from_plr_resources([resource, *resources], known_newly_created=True).dump(), + "resources": ResourceTreeSet.from_plr_resources([resource, *resources]).dump(), "devices": [device, *devices], }