diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index ffc106c71..fc2145fe1 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -82,6 +82,29 @@ registered_devices: Dict[str, "DeviceInfoType"] = {} +def _dump_resource_state(resource: Any) -> Dict[str, Any]: + """Read resource state from either the current tracker API or legacy .state.""" + if hasattr(resource, "serialize_state"): + state = resource.serialize_state() + if isinstance(state, dict): + return dict(state) + state = getattr(resource, "state", None) + if isinstance(state, dict): + return dict(state) + return {} + + +def _load_resource_state(resource: Any, state: Dict[str, Any]) -> None: + """Write resource state back using whichever API the resource exposes.""" + if hasattr(resource, "load_state"): + resource.load_state(state) + return + if hasattr(resource, "state"): + resource.state = state + return + raise AttributeError(f"Resource {resource} does not support state loading") + + # 实现同时记录自定义日志和ROS2日志的适配器 class ROSLoggerAdapter: """同时向自定义日志和ROS2日志发送消息的适配器""" @@ -430,8 +453,11 @@ async def append_resource(req: SerialCommand_Request, res: SerialCommand_Respons assert len(found_resources) == 1, f"找到多个同名物料: {container_instance.name}, 请检查物料系统" found_resource = found_resources[0] if isinstance(found_resource, RegularContainer): - logger.info(f"更新物料{container_instance.name}的数据{found_resource.state}") - found_resource.state.update(container_instance.state) + current_state = _dump_resource_state(found_resource) + incoming_state = _dump_resource_state(container_instance) + logger.info(f"更新物料{container_instance.name}的数据{current_state}") + current_state.update(incoming_state) + _load_resource_state(found_resource, current_state) elif isinstance(found_resource, dict): raise ValueError("已不支持 字典 版本的RegularContainer") else: diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index eb139f1fd..f87bd697c 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -1,5 +1,6 @@ import collections import json +import os import threading import time import traceback @@ -1377,6 +1378,91 @@ def _resource_get_process(self, data: Dict[str, Any]): resources = [convert_to_ros_msg(Resource, resource) for resource in r] return resources + def _resource_get_local(self, data: Dict[str, Any]): + """Resolve a resource locally before querying the bridge.""" + queries: List[Dict[str, Any]] = [] + resource_uuid = data.get("uuid") + resource_id = data.get("id") + + if resource_uuid: + queries.append({"uuid": resource_uuid}) + + if resource_id: + queries.append({"id": resource_id}) + resource_name = resource_id.rstrip("/").split("/")[-1] + if resource_name: + queries.append({"name": resource_name}) + + for query in queries: + found_resources = self._resource_tracker.figure_resource(query, try_mode=True) + if found_resources: + return found_resources[0] + return None + + @staticmethod + def _resource_dump_local(resource: Any, with_children: bool) -> List[Dict[str, Any]]: + if isinstance(resource, dict): + raw_data = [dict(resource)] + else: + raw_data = ResourceTreeSet.from_plr_resources([resource]).dump()[0] + if with_children: + return raw_data + + resource_uuid = raw_data[0].get("uuid") + resource_id = raw_data[0].get("id") + return [node for node in raw_data if node.get("uuid") == resource_uuid or node.get("id") == resource_id] + + @staticmethod + def _resource_get_from_startup_config(data: Dict[str, Any]) -> Optional[List[Dict[str, Any]]]: + startup_config_path = os.path.join(BasicConfig.working_dir, "startup_config.json") + if not os.path.exists(startup_config_path): + return None + + with open(startup_config_path, "r", encoding="utf-8") as f: + startup_data = json.load(f) + + nodes = startup_data.get("data", {}).get("nodes", []) + if not nodes: + return None + + query_uuid = data.get("uuid") + query_id = data.get("id") + query_name = query_id.rstrip("/").split("/")[-1] if query_id else None + + target_node = None + for node in nodes: + if query_uuid and node.get("uuid") == query_uuid: + target_node = node + break + if query_id and node.get("id") == query_id: + target_node = node + break + if query_name and node.get("name") == query_name: + target_node = node + break + + if target_node is None: + return None + + if not data.get("with_children", True): + return [target_node] + + target_uuid = target_node.get("uuid") + descendants: List[Dict[str, Any]] = [] + pending = {target_uuid} + while pending: + current_parent_uuid = pending.pop() + for node in nodes: + if node not in descendants and ( + node.get("uuid") == current_parent_uuid or node.get("parent_uuid") == current_parent_uuid + ): + descendants.append(node) + node_uuid = node.get("uuid") + if node_uuid and node_uuid != current_parent_uuid: + pending.add(node_uuid) + + return descendants or [target_node] + def _resource_get_callback(self, request: SerialCommand.Request, response: SerialCommand.Response): """ 获取资源回调 @@ -1391,7 +1477,17 @@ def _resource_get_callback(self, request: SerialCommand.Request, response: Seria from unilabos.app.web import http_client data = json.loads(request.command) - if "uuid" in data and data["uuid"] is not None: + local_resource = self._resource_get_local(data) + if local_resource is not None: + response.response = json.dumps(self._resource_dump_local(local_resource, data.get("with_children", True))) + return response + + startup_resource = self._resource_get_from_startup_config(data) + if startup_resource is not None: + response.response = json.dumps(startup_resource) + return response + + if data.get("uuid"): http_req = http_client.resource_tree_get([data["uuid"]], data["with_children"]) elif "id" in data: http_req = http_client.resource_get(data["id"], data["with_children"])