Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions unilabos/ros/nodes/base_device_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -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日志发送消息的适配器"""
Expand Down Expand Up @@ -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:
Expand Down
98 changes: 97 additions & 1 deletion unilabos/ros/nodes/presets/host_node.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import collections
import json
import os
import threading
import time
import traceback
Expand Down Expand Up @@ -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):
"""
获取资源回调
Expand All @@ -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"])
Expand Down