新增test_resource动作

This commit is contained in:
Xuwznln
2025-10-11 02:53:18 +08:00
parent 0c42d60cf2
commit 704e13f030
2 changed files with 99 additions and 29 deletions

View File

@@ -7,16 +7,17 @@ import importlib
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Union, Tuple from typing import Any, Dict, List, Union, Tuple
import msgcenterpy
import yaml import yaml
from rosidl_parser.definition import UnboundedSequence
from unilabos_msgs.action import LiquidHandlerTransfer
from unilabos_msgs.msg import Resource from unilabos_msgs.msg import Resource
from unilabos.config.config import BasicConfig from unilabos.config.config import BasicConfig
from unilabos.resources.graphio import resource_plr_to_ulab, tree_to_list 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, \ from unilabos.ros.msgs.message_converter import (
ros_message_to_json_schema msg_converter_manager,
ros_action_to_json_schema,
String,
ros_message_to_json_schema,
)
from unilabos.utils import logger from unilabos.utils import logger
from unilabos.utils.decorator import singleton from unilabos.utils.decorator import singleton
from unilabos.utils.import_manager import get_enhanced_class_info, get_class 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] DEFAULT_PATHS = [Path(__file__).absolute().parent]
class ROSMsgNotFound(Exception): class ROSMsgNotFound(Exception):
pass pass
@@ -139,13 +141,57 @@ class Registry:
"type": self.EmptyIn, "type": self.EmptyIn,
"goal": {}, "goal": {},
"feedback": {}, "feedback": {},
"result": {"latency_ms": "latency_ms", "time_diff_ms": "time_diff_ms"}, "result": {},
"schema": ros_action_to_json_schema( "schema": ros_action_to_json_schema(
self.EmptyIn, "用于测试延迟的动作,返回延迟时间和时间差。" self.EmptyIn, "用于测试延迟的动作,返回延迟时间和时间差。"
), ),
"goal_default": {}, "goal_default": {},
"handles": {}, "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", "version": "1.0.0",
@@ -436,10 +482,12 @@ class Registry:
elif param_type == ("list", "unilabos.registry.placeholder_type:ResourceSlot"): elif param_type == ("list", "unilabos.registry.placeholder_type:ResourceSlot"):
schema["properties"][param_name] = { schema["properties"][param_name] = {
"items": ros_message_to_json_schema(Resource, param_name), "items": ros_message_to_json_schema(Resource, param_name),
"type": "array" "type": "array",
} }
else: 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: if param_required:
schema["required"].append(param_name) schema["required"].append(param_name)
@@ -512,7 +560,9 @@ class Registry:
status_type = "String" # 替换成ROS的String便于显示 status_type = "String" # 替换成ROS的String便于显示
device_config["class"]["status_types"][status_name] = status_type device_config["class"]["status_types"][status_name] = status_type
try: 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: except ROSMsgNotFound:
continue continue
if target_type in [ if target_type in [
@@ -550,10 +600,22 @@ class Registry:
"goal_default": {i["name"]: i["default"] for i in v["args"]}, "goal_default": {i["name"]: i["default"] for i in v["args"]},
"handles": [], "handles": [],
"placeholder_keys": { "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"] 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的动作 # 不生成已配置action的动作
for k, v in enhanced_info["action_methods"].items() for k, v in enhanced_info["action_methods"].items()

View File

@@ -15,7 +15,6 @@ from rclpy.service import Service
from unilabos_msgs.msg import Resource # type: ignore from unilabos_msgs.msg import Resource # type: ignore
from unilabos_msgs.srv import ( from unilabos_msgs.srv import (
ResourceAdd, ResourceAdd,
ResourceGet,
ResourceDelete, ResourceDelete,
ResourceUpdate, ResourceUpdate,
ResourceList, ResourceList,
@@ -44,6 +43,7 @@ from unilabos.ros.nodes.resource_tracker import (
) )
from unilabos.utils.exception import DeviceClassInvalid from unilabos.utils.exception import DeviceClassInvalid
from unilabos.utils.type_check import serialize_result_info from unilabos.utils.type_check import serialize_result_info
from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot
if TYPE_CHECKING: if TYPE_CHECKING:
from unilabos.app.ws_client import QueueItem, WSResourceChatData from unilabos.app.ws_client import QueueItem, WSResourceChatData
@@ -152,6 +152,12 @@ class HostNode(BaseROS2DeviceNode):
"/devices/host_node/test_latency", "/devices/host_node/test_latency",
callback_group=self.callback_group, 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实例 } # 用来存储多个ActionClient实例
self._action_value_mappings: Dict[str, Dict] = ( self._action_value_mappings: Dict[str, Dict] = (
{} {}
@@ -234,7 +240,8 @@ class HostNode(BaseROS2DeviceNode):
) )
# resources_config 通过各个设备的 resource_tracker 进行uuid更新利用uuid_mapping # resources_config 通过各个设备的 resource_tracker 进行uuid更新利用uuid_mapping
# resources_config 的 root node 是 # 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": if node.res_content.type == "device":
for sub_node in node.children: for sub_node in node.children:
# 只有二级子设备 # 只有二级子设备
@@ -245,8 +252,11 @@ class HostNode(BaseROS2DeviceNode):
{"name": sub_node.res_content.name}) {"name": sub_node.res_content.name})
device_tracker.loop_update_uuid(resource_instance, uuid_mapping) device_tracker.loop_update_uuid(resource_instance, uuid_mapping)
else: else:
resource_instance = self.resource_tracker.figure_resource({"name": node.res_content.name}) try:
self._resource_tracker.loop_update_uuid(resource_instance, uuid_mapping) 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: except Exception as ex:
self.lab_logger().error("[Host Node-Resource] 添加物料出错!") self.lab_logger().error("[Host Node-Resource] 添加物料出错!")
self.lab_logger().error(traceback.format_exc()) self.lab_logger().error(traceback.format_exc())
@@ -799,7 +809,7 @@ class HostNode(BaseROS2DeviceNode):
ResourceAdd, "/resources/add", self._resource_add_callback, callback_group=ReentrantCallbackGroup() ResourceAdd, "/resources/add", self._resource_add_callback, callback_group=ReentrantCallbackGroup()
), ),
"resource_get": self.create_service( "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( "resource_delete": self.create_service(
ResourceDelete, ResourceDelete,
@@ -1011,7 +1021,7 @@ class HostNode(BaseROS2DeviceNode):
resources = [convert_to_ros_msg(Resource, resource) for resource in r] resources = [convert_to_ros_msg(Resource, resource) for resource in r]
return resources 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: try:
http_req = self.bridges[-1].resource_get(request.id, request.with_children) data = json.loads(request.command)
response.resources = self._resource_get_process(http_req) http_req = self.bridges[-1].resource_get(data["id"], data["with_children"])
response.response = json.dumps(http_req["data"])
return response return response
except Exception as e: except Exception as e:
self.lab_logger().error(f"[Host Node-Resource] Error retrieving from bridge: {str(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 return response
def _resource_delete_callback(self, request, response): def _resource_delete_callback(self, request, response):
@@ -1240,6 +1242,12 @@ class HostNode(BaseROS2DeviceNode):
"status": "success", "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): def handle_pong_response(self, pong_data: dict):
""" """
处理pong响应 处理pong响应