From 704e13f030bce1d7618f3faf4b079a37853a4050 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Sat, 11 Oct 2025 02:53:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Etest=5Fresource=E5=8A=A8?= =?UTF-8?q?=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- unilabos/registry/registry.py | 86 +++++++++++++++++++++---- unilabos/ros/nodes/presets/host_node.py | 42 +++++++----- 2 files changed, 99 insertions(+), 29 deletions(-) diff --git a/unilabos/registry/registry.py b/unilabos/registry/registry.py index b4e1b16f..3a57a4ba 100644 --- a/unilabos/registry/registry.py +++ b/unilabos/registry/registry.py @@ -7,16 +7,17 @@ import importlib from pathlib import Path from typing import Any, Dict, List, Union, Tuple -import msgcenterpy import yaml -from rosidl_parser.definition import UnboundedSequence -from unilabos_msgs.action import LiquidHandlerTransfer from unilabos_msgs.msg import Resource from unilabos.config.config import BasicConfig from unilabos.resources.graphio import resource_plr_to_ulab, tree_to_list -from unilabos.ros.msgs.message_converter import msg_converter_manager, ros_action_to_json_schema, String, \ - ros_message_to_json_schema +from unilabos.ros.msgs.message_converter import ( + msg_converter_manager, + ros_action_to_json_schema, + String, + ros_message_to_json_schema, +) from unilabos.utils import logger from unilabos.utils.decorator import singleton from unilabos.utils.import_manager import get_enhanced_class_info, get_class @@ -24,6 +25,7 @@ from unilabos.utils.type_check import NoAliasDumper DEFAULT_PATHS = [Path(__file__).absolute().parent] + class ROSMsgNotFound(Exception): pass @@ -139,13 +141,57 @@ class Registry: "type": self.EmptyIn, "goal": {}, "feedback": {}, - "result": {"latency_ms": "latency_ms", "time_diff_ms": "time_diff_ms"}, + "result": {}, "schema": ros_action_to_json_schema( self.EmptyIn, "用于测试延迟的动作,返回延迟时间和时间差。" ), "goal_default": {}, "handles": {}, }, + "test_resource": { + "type": "UniLabJsonCommand", + "goal": {}, + "feedback": {}, + "result": {}, + "schema": { + "description": "", + "properties": { + "feedback": {}, + "goal": { + "properties": { + "resource": { + "properties": ros_message_to_json_schema(Resource, "resource"), + "type": "object" + }, + "resources": { + "items": { + "properties": ros_message_to_json_schema(Resource, "resources"), + "title": "mount_resource", + "type": "object" + }, + "type": "array" + }, + "device": { + "type": "object" + }, + "devices": { + "items": { + "type": "object" + }, + "type": "array" + }, + }, + "type": "object" + }, + "result": {} + }, + "required": ["goal"], + "title": "transfer_resource_to_another参数", + "type": "object", + }, + "goal_default": {}, + "handles": {}, + }, }, }, "version": "1.0.0", @@ -436,10 +482,12 @@ class Registry: elif param_type == ("list", "unilabos.registry.placeholder_type:ResourceSlot"): schema["properties"][param_name] = { "items": ros_message_to_json_schema(Resource, param_name), - "type": "array" + "type": "array", } else: - schema["properties"][param_name] = self._generate_schema_from_info(param_name, param_type, param_default) + schema["properties"][param_name] = self._generate_schema_from_info( + param_name, param_type, param_default + ) if param_required: schema["required"].append(param_name) @@ -512,7 +560,9 @@ class Registry: status_type = "String" # 替换成ROS的String,便于显示 device_config["class"]["status_types"][status_name] = status_type try: - target_type = self._replace_type_with_class(status_type, device_id, f"状态 {status_name}") + target_type = self._replace_type_with_class( + status_type, device_id, f"状态 {status_name}" + ) except ROSMsgNotFound: continue if target_type in [ @@ -550,10 +600,22 @@ class Registry: "goal_default": {i["name"]: i["default"] for i in v["args"]}, "handles": [], "placeholder_keys": { - i["name"]: "unilabos_resources" if i["type"] == "unilabos.registry.placeholder_type:ResourceSlot" or i["type"] == ("list", "unilabos.registry.placeholder_type:ResourceSlot") else "unilabos_devices" + i["name"]: ( + "unilabos_resources" + if i["type"] == "unilabos.registry.placeholder_type:ResourceSlot" + or i["type"] + == ("list", "unilabos.registry.placeholder_type:ResourceSlot") + else "unilabos_devices" + ) for i in v["args"] - if i.get("type", "") in ["unilabos.registry.placeholder_type:ResourceSlot", "unilabos.registry.placeholder_type:DeviceSlot", ("list", "unilabos.registry.placeholder_type:ResourceSlot"), ("list", "unilabos.registry.placeholder_type:DeviceSlot")] - } + if i.get("type", "") + in [ + "unilabos.registry.placeholder_type:ResourceSlot", + "unilabos.registry.placeholder_type:DeviceSlot", + ("list", "unilabos.registry.placeholder_type:ResourceSlot"), + ("list", "unilabos.registry.placeholder_type:DeviceSlot"), + ] + }, } # 不生成已配置action的动作 for k, v in enhanced_info["action_methods"].items() diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index b07dc045..0bf86bce 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -15,7 +15,6 @@ from rclpy.service import Service from unilabos_msgs.msg import Resource # type: ignore from unilabos_msgs.srv import ( ResourceAdd, - ResourceGet, ResourceDelete, ResourceUpdate, ResourceList, @@ -44,6 +43,7 @@ from unilabos.ros.nodes.resource_tracker import ( ) from unilabos.utils.exception import DeviceClassInvalid from unilabos.utils.type_check import serialize_result_info +from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot if TYPE_CHECKING: from unilabos.app.ws_client import QueueItem, WSResourceChatData @@ -152,6 +152,12 @@ class HostNode(BaseROS2DeviceNode): "/devices/host_node/test_latency", callback_group=self.callback_group, ), + "/devices/host_node/test_resource": ActionClient( + self, + lab_registry.EmptyIn, + "/devices/host_node/test_resource", + callback_group=self.callback_group, + ), } # 用来存储多个ActionClient实例 self._action_value_mappings: Dict[str, Dict] = ( {} @@ -234,7 +240,8 @@ class HostNode(BaseROS2DeviceNode): ) # resources_config 通过各个设备的 resource_tracker 进行uuid更新,利用uuid_mapping # resources_config 的 root node 是 - for node in resources_config.root_nodes: + for tree in resources_config.trees: + node = tree.root_node if node.res_content.type == "device": for sub_node in node.children: # 只有二级子设备 @@ -245,8 +252,11 @@ class HostNode(BaseROS2DeviceNode): {"name": sub_node.res_content.name}) device_tracker.loop_update_uuid(resource_instance, uuid_mapping) else: - resource_instance = self.resource_tracker.figure_resource({"name": node.res_content.name}) - self._resource_tracker.loop_update_uuid(resource_instance, uuid_mapping) + try: + for plr_resource in ResourceTreeSet([tree]).to_plr_resources(): + self.resource_tracker.add_resource(plr_resource) + except Exception as ex: + self.lab_logger().warning("[Host Node-Resource] 根节点物料序列化失败!") except Exception as ex: self.lab_logger().error("[Host Node-Resource] 添加物料出错!") self.lab_logger().error(traceback.format_exc()) @@ -799,7 +809,7 @@ class HostNode(BaseROS2DeviceNode): ResourceAdd, "/resources/add", self._resource_add_callback, callback_group=ReentrantCallbackGroup() ), "resource_get": self.create_service( - ResourceGet, "/resources/get", self._resource_get_callback, callback_group=ReentrantCallbackGroup() + SerialCommand, "/resources/get", self._resource_get_callback, callback_group=ReentrantCallbackGroup() ), "resource_delete": self.create_service( ResourceDelete, @@ -1011,7 +1021,7 @@ class HostNode(BaseROS2DeviceNode): resources = [convert_to_ros_msg(Resource, resource) for resource in r] return resources - def _resource_get_callback(self, request: ResourceGet.Request, response: ResourceGet.Response): + def _resource_get_callback(self, request: SerialCommand.Request, response: SerialCommand.Response): """ 获取资源回调 @@ -1025,20 +1035,12 @@ class HostNode(BaseROS2DeviceNode): 响应对象,包含查询到的资源 """ try: - http_req = self.bridges[-1].resource_get(request.id, request.with_children) - response.resources = self._resource_get_process(http_req) + data = json.loads(request.command) + http_req = self.bridges[-1].resource_get(data["id"], data["with_children"]) + response.response = json.dumps(http_req["data"]) return response except Exception as e: self.lab_logger().error(f"[Host Node-Resource] Error retrieving from bridge: {str(e)}") - # 从 ResourceTreeSet 中查找资源 - resources_list = ( - [node.res_content.model_dump(by_alias=True) for node in self.resources_config.all_nodes] - if self.resources_config - else [] - ) - r = [resource for resource in resources_list if resource.get("id") == request.id] - self.lab_logger().debug(f"[Host Node-Resource] Retrieved from local: {len(r)} resources") - response.resources = [convert_to_ros_msg(Resource, resource) for resource in r] return response def _resource_delete_callback(self, request, response): @@ -1240,6 +1242,12 @@ class HostNode(BaseROS2DeviceNode): "status": "success", } + def test_resource(self, resource: ResourceSlot, resources: List[ResourceSlot], device: DeviceSlot, devices: List[DeviceSlot]): + return { + "resources": ResourceTreeSet.from_plr_resources([resource, *resources]).dump(), + "devices": [device, *devices], + } + def handle_pong_response(self, pong_data: dict): """ 处理pong响应