mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-19 14:01:20 +00:00
Fix/resource UUID and doc fix (#109)
* Fix ResourceTreeSet load error * Raise error when using unsupported type to create ResourceTreeSet * Fix children key error * Fix children key error * Fix workstation resource not tracking * Fix workstation deck & children resource dupe * Fix workstation deck & children resource dupe * Fix multiple resource error * Fix resource tree update * Fix resource tree update * Force confirm uuid * Tip more error log * Refactor Bioyond workstation and experiment workflow (#105) Refactored the Bioyond workstation classes to improve parameter handling and workflow management. Updated experiment.py to use BioyondReactionStation with deck and material mappings, and enhanced workflow step parameter mapping and execution logic. Adjusted JSON experiment configs, improved workflow sequence handling, and added UUID assignment to PLR materials. Removed unused station_config and material cache logic, and added detailed docstrings and debug output for workflow methods. * Fix resource get. Fix resource parent not found. Mapping uuid for all resources. * mount parent uuid * Add logging configuration based on BasicConfig in main function * fix workstation node error * fix workstation node error * Update boot example * temp fix for resource get * temp fix for resource get * provide error info when cant find plr type * pack repo info * fix to plr type error * fix to plr type error * Update regular container method * support no size init * fix comprehensive_station.json * fix comprehensive_station.json * fix type conversion * fix state loading for regular container * Update deploy-docs.yml * Update deploy-docs.yml --------- Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com>
This commit is contained in:
@@ -18,7 +18,7 @@ from unilabos_msgs.srv import (
|
||||
ResourceDelete,
|
||||
ResourceUpdate,
|
||||
ResourceList,
|
||||
SerialCommand,
|
||||
SerialCommand, ResourceGet,
|
||||
) # type: ignore
|
||||
from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialCommand_Response
|
||||
from unique_identifier_msgs.msg import UUID
|
||||
@@ -41,6 +41,7 @@ from unilabos.ros.nodes.resource_tracker import (
|
||||
ResourceTreeSet,
|
||||
ResourceTreeInstance,
|
||||
)
|
||||
from unilabos.utils import logger
|
||||
from unilabos.utils.exception import DeviceClassInvalid
|
||||
from unilabos.utils.type_check import serialize_result_info
|
||||
from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot
|
||||
@@ -99,17 +100,6 @@ class HostNode(BaseROS2DeviceNode):
|
||||
"""
|
||||
if self._instance is not None:
|
||||
self._instance.lab_logger().critical("[Host Node] HostNode instance already exists.")
|
||||
# 初始化Node基类,传递空参数覆盖列表
|
||||
BaseROS2DeviceNode.__init__(
|
||||
self,
|
||||
driver_instance=self,
|
||||
device_id=device_id,
|
||||
status_types={},
|
||||
action_value_mappings=lab_registry.device_type_registry["host_node"]["class"]["action_value_mappings"],
|
||||
hardware_interface={},
|
||||
print_publish=False,
|
||||
resource_tracker=self._resource_tracker, # host node并不是通过initialize 包一层传进来的
|
||||
)
|
||||
|
||||
# 设置单例实例
|
||||
self.__class__._instance = self
|
||||
@@ -127,6 +117,91 @@ class HostNode(BaseROS2DeviceNode):
|
||||
bridges = []
|
||||
self.bridges = bridges
|
||||
|
||||
# 创建 host_node 作为一个单独的 ResourceTree
|
||||
host_node_dict = {
|
||||
"id": "host_node",
|
||||
"uuid": str(uuid.uuid4()),
|
||||
"parent_uuid": "",
|
||||
"name": "host_node",
|
||||
"type": "device",
|
||||
"class": "host_node",
|
||||
"config": {},
|
||||
"data": {},
|
||||
"children": [],
|
||||
"description": "",
|
||||
"schema": {},
|
||||
"model": {},
|
||||
"icon": "",
|
||||
}
|
||||
|
||||
# 创建 host_node 的 ResourceTree
|
||||
host_node_instance = ResourceDictInstance.get_resource_instance_from_dict(host_node_dict)
|
||||
host_node_tree = ResourceTreeInstance(host_node_instance)
|
||||
resources_config.trees.insert(0, host_node_tree)
|
||||
try:
|
||||
for bridge in self.bridges:
|
||||
if hasattr(bridge, "resource_tree_add") and resources_config:
|
||||
from unilabos.app.web.client import HTTPClient
|
||||
|
||||
client: HTTPClient = bridge
|
||||
resource_start_time = time.time()
|
||||
# 传递 ResourceTreeSet 对象,在 client 中转换为字典并获取 UUID 映射
|
||||
uuid_mapping = client.resource_tree_add(resources_config, "", True)
|
||||
device_uuid = resources_config.root_nodes[0].res_content.uuid
|
||||
resource_end_time = time.time()
|
||||
logger.info(
|
||||
f"[Host Node-Resource] 物料上传 {round(resource_end_time - resource_start_time, 5) * 1000} ms"
|
||||
)
|
||||
for edge in self.resources_edge_config:
|
||||
edge["source_uuid"] = uuid_mapping.get(edge["source_uuid"], edge["source_uuid"])
|
||||
edge["target_uuid"] = uuid_mapping.get(edge["target_uuid"], edge["target_uuid"])
|
||||
resource_add_res = client.resource_edge_add(self.resources_edge_config)
|
||||
resource_edge_end_time = time.time()
|
||||
logger.info(
|
||||
f"[Host Node-Resource] 物料关系上传 {round(resource_edge_end_time - resource_end_time, 5) * 1000} ms"
|
||||
)
|
||||
# resources_config 通过各个设备的 resource_tracker 进行uuid更新,利用uuid_mapping
|
||||
# resources_config 的 root node 是
|
||||
# # 创建反向映射:new_uuid -> old_uuid
|
||||
# reverse_uuid_mapping = {new_uuid: old_uuid for old_uuid, new_uuid in uuid_mapping.items()}
|
||||
# for tree in resources_config.trees:
|
||||
# node = tree.root_node
|
||||
# if node.res_content.type == "device":
|
||||
# if node.res_content.id == "host_node":
|
||||
# continue
|
||||
# # slave节点走c2s更新接口,拿到add自行update uuid
|
||||
# device_tracker = self.devices_instances[node.res_content.id].resource_tracker
|
||||
# old_uuid = reverse_uuid_mapping.get(node.res_content.uuid)
|
||||
# if old_uuid:
|
||||
# # 找到旧UUID,使用UUID查找
|
||||
# resource_instance = device_tracker.uuid_to_resources.get(old_uuid)
|
||||
# else:
|
||||
# # 未找到旧UUID,使用name查找
|
||||
# resource_instance = device_tracker.figure_resource(
|
||||
# {"name": node.res_content.name}
|
||||
# )
|
||||
# device_tracker.loop_update_uuid(resource_instance, uuid_mapping)
|
||||
# else:
|
||||
# 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:
|
||||
logger.error(f"[Host Node-Resource] 添加物料出错!\n{traceback.format_exc()}")
|
||||
# 初始化Node基类,传递空参数覆盖列表
|
||||
BaseROS2DeviceNode.__init__(
|
||||
self,
|
||||
driver_instance=self,
|
||||
device_id=device_id,
|
||||
device_uuid=host_node_dict["uuid"],
|
||||
status_types={},
|
||||
action_value_mappings=lab_registry.device_type_registry["host_node"]["class"]["action_value_mappings"],
|
||||
hardware_interface={},
|
||||
print_publish=False,
|
||||
resource_tracker=self._resource_tracker, # host node并不是通过initialize 包一层传进来的
|
||||
)
|
||||
|
||||
# 创建设备、动作客户端和目标存储
|
||||
self.devices_names: Dict[str, str] = {device_id: self.namespace} # 存储设备名称和命名空间的映射
|
||||
self.devices_instances: Dict[str, ROS2DeviceNode] = {} # 存储设备实例
|
||||
@@ -207,81 +282,7 @@ class HostNode(BaseROS2DeviceNode):
|
||||
].items():
|
||||
controller_config["update_rate"] = update_rate
|
||||
self.initialize_controller(controller_id, controller_config)
|
||||
# 创建 host_node 作为一个单独的 ResourceTree
|
||||
|
||||
host_node_dict = {
|
||||
"id": "host_node",
|
||||
"uuid": str(uuid.uuid4()),
|
||||
"parent_uuid": "",
|
||||
"name": "host_node",
|
||||
"type": "device",
|
||||
"class": "host_node",
|
||||
"config": {},
|
||||
"data": {},
|
||||
"children": [],
|
||||
"description": "",
|
||||
"schema": {},
|
||||
"model": {},
|
||||
"icon": "",
|
||||
}
|
||||
|
||||
# 创建 host_node 的 ResourceTree
|
||||
host_node_instance = ResourceDictInstance.get_resource_instance_from_dict(host_node_dict)
|
||||
host_node_tree = ResourceTreeInstance(host_node_instance)
|
||||
resources_config.trees.insert(0, host_node_tree)
|
||||
try:
|
||||
for bridge in self.bridges:
|
||||
if hasattr(bridge, "resource_tree_add") and resources_config:
|
||||
from unilabos.app.web.client import HTTPClient
|
||||
|
||||
client: HTTPClient = bridge
|
||||
resource_start_time = time.time()
|
||||
# 传递 ResourceTreeSet 对象,在 client 中转换为字典并获取 UUID 映射
|
||||
uuid_mapping = client.resource_tree_add(resources_config, "", True)
|
||||
resource_end_time = time.time()
|
||||
self.lab_logger().info(
|
||||
f"[Host Node-Resource] 物料上传 {round(resource_end_time - resource_start_time, 5) * 1000} ms"
|
||||
)
|
||||
for edge in self.resources_edge_config:
|
||||
edge["source_uuid"] = uuid_mapping.get(edge["source_uuid"], edge["source_uuid"])
|
||||
edge["target_uuid"] = uuid_mapping.get(edge["target_uuid"], edge["target_uuid"])
|
||||
resource_add_res = client.resource_edge_add(self.resources_edge_config)
|
||||
resource_edge_end_time = time.time()
|
||||
self.lab_logger().info(
|
||||
f"[Host Node-Resource] 物料关系上传 {round(resource_edge_end_time - resource_end_time, 5) * 1000} ms"
|
||||
)
|
||||
# resources_config 通过各个设备的 resource_tracker 进行uuid更新,利用uuid_mapping
|
||||
# resources_config 的 root node 是
|
||||
# 创建反向映射:new_uuid -> old_uuid
|
||||
reverse_uuid_mapping = {new_uuid: old_uuid for old_uuid, new_uuid in uuid_mapping.items()}
|
||||
for tree in resources_config.trees:
|
||||
node = tree.root_node
|
||||
if node.res_content.type == "device":
|
||||
for sub_node in node.children:
|
||||
# 只有二级子设备
|
||||
if sub_node.res_content.type != "device":
|
||||
# slave节点走c2s更新接口,拿到add自行update uuid
|
||||
device_tracker = self.devices_instances[node.res_content.id].resource_tracker
|
||||
# sub_node.res_content.uuid 已经是新UUID,需要用旧UUID去查找
|
||||
old_uuid = reverse_uuid_mapping.get(sub_node.res_content.uuid)
|
||||
if old_uuid:
|
||||
# 找到旧UUID,使用UUID查找
|
||||
resource_instance = device_tracker.figure_resource({"uuid": old_uuid})
|
||||
else:
|
||||
# 未找到旧UUID,使用name查找
|
||||
resource_instance = device_tracker.figure_resource(
|
||||
{"name": sub_node.res_content.name}
|
||||
)
|
||||
device_tracker.loop_update_uuid(resource_instance, uuid_mapping)
|
||||
else:
|
||||
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())
|
||||
# 创建定时器,定期发现设备
|
||||
self._discovery_timer = self.create_timer(
|
||||
discovery_interval, self._discovery_devices_callback, callback_group=ReentrantCallbackGroup()
|
||||
@@ -862,7 +863,7 @@ class HostNode(BaseROS2DeviceNode):
|
||||
),
|
||||
}
|
||||
|
||||
def _resource_tree_action_add_callback(self, data: dict, response: SerialCommand_Response): # OK
|
||||
async def _resource_tree_action_add_callback(self, data: dict, response: SerialCommand_Response): # OK
|
||||
resource_tree_set = ResourceTreeSet.load(data["data"])
|
||||
mount_uuid = data["mount_uuid"]
|
||||
first_add = data["first_add"]
|
||||
@@ -903,7 +904,7 @@ class HostNode(BaseROS2DeviceNode):
|
||||
response.response = json.dumps(uuid_mapping) if success else "FAILED"
|
||||
self.lab_logger().info(f"[Host Node-Resource] Resource tree add completed, success: {success}")
|
||||
|
||||
def _resource_tree_action_get_callback(self, data: dict, response: SerialCommand_Response): # OK
|
||||
async def _resource_tree_action_get_callback(self, data: dict, response: SerialCommand_Response): # OK
|
||||
uuid_list: List[str] = data["data"]
|
||||
with_children: bool = data["with_children"]
|
||||
from unilabos.app.web.client import http_client
|
||||
@@ -911,7 +912,7 @@ class HostNode(BaseROS2DeviceNode):
|
||||
resource_response = http_client.resource_tree_get(uuid_list, with_children)
|
||||
response.response = json.dumps(resource_response)
|
||||
|
||||
def _resource_tree_action_remove_callback(self, data: dict, response: SerialCommand_Response):
|
||||
async def _resource_tree_action_remove_callback(self, data: dict, response: SerialCommand_Response):
|
||||
"""
|
||||
子节点通知Host物料树删除
|
||||
"""
|
||||
@@ -919,7 +920,7 @@ class HostNode(BaseROS2DeviceNode):
|
||||
response.response = "OK"
|
||||
self.lab_logger().info(f"[Host Node-Resource] Resource tree remove completed")
|
||||
|
||||
def _resource_tree_action_update_callback(self, data: dict, response: SerialCommand_Response):
|
||||
async def _resource_tree_action_update_callback(self, data: dict, response: SerialCommand_Response):
|
||||
"""
|
||||
子节点通知Host物料树更新
|
||||
"""
|
||||
@@ -932,20 +933,29 @@ class HostNode(BaseROS2DeviceNode):
|
||||
|
||||
from unilabos.app.web.client import http_client
|
||||
|
||||
resource_start_time = time.time()
|
||||
uuid_mapping = http_client.resource_tree_update(resource_tree_set, "", False)
|
||||
success = bool(uuid_mapping)
|
||||
resource_end_time = time.time()
|
||||
self.lab_logger().info(
|
||||
f"[Host Node-Resource] 物料更新上传 {round(resource_end_time - resource_start_time, 5) * 1000} ms"
|
||||
)
|
||||
if uuid_mapping:
|
||||
self.lab_logger().info(f"[Host Node-Resource] UUID映射: {len(uuid_mapping)} 个节点")
|
||||
# 还需要加入到资源图中,暂不实现,考虑资源图新的获取方式
|
||||
response.response = json.dumps(uuid_mapping)
|
||||
self.lab_logger().info(f"[Host Node-Resource] Resource tree add completed, success: {success}")
|
||||
uuid_to_trees: Dict[str, List[ResourceTreeInstance]] = collections.defaultdict(list)
|
||||
for tree in resource_tree_set.trees:
|
||||
uuid_to_trees[tree.root_node.res_content.parent_uuid].append(tree)
|
||||
|
||||
def _resource_tree_update_callback(self, request: SerialCommand_Request, response: SerialCommand_Response):
|
||||
for uid, trees in uuid_to_trees.items():
|
||||
new_tree_set = ResourceTreeSet(trees)
|
||||
resource_start_time = time.time()
|
||||
self.lab_logger().info(
|
||||
f"[Host Node-Resource] 物料 {[root_node.res_content.id for root_node in new_tree_set.root_nodes]} {uid} 挂载 {trees[0].root_node.res_content.parent_uuid} 请求更新上传"
|
||||
)
|
||||
uuid_mapping = http_client.resource_tree_add(new_tree_set, uid, False)
|
||||
success = bool(uuid_mapping)
|
||||
resource_end_time = time.time()
|
||||
self.lab_logger().info(
|
||||
f"[Host Node-Resource] 物料更新上传 {round(resource_end_time - resource_start_time, 5) * 1000} ms"
|
||||
)
|
||||
if uuid_mapping:
|
||||
self.lab_logger().info(f"[Host Node-Resource] UUID映射: {len(uuid_mapping)} 个节点")
|
||||
# 还需要加入到资源图中,暂不实现,考虑资源图新的获取方式
|
||||
response.response = json.dumps(uuid_mapping)
|
||||
self.lab_logger().info(f"[Host Node-Resource] Resource tree add completed, success: {success}")
|
||||
|
||||
async def _resource_tree_update_callback(self, request: SerialCommand_Request, response: SerialCommand_Response):
|
||||
"""
|
||||
子节点通知Host物料树更新
|
||||
|
||||
@@ -958,13 +968,13 @@ class HostNode(BaseROS2DeviceNode):
|
||||
action = data["action"]
|
||||
data = data["data"]
|
||||
if action == "add":
|
||||
self._resource_tree_action_add_callback(data, response)
|
||||
await self._resource_tree_action_add_callback(data, response)
|
||||
elif action == "get":
|
||||
self._resource_tree_action_get_callback(data, response)
|
||||
await self._resource_tree_action_get_callback(data, response)
|
||||
elif action == "update":
|
||||
self._resource_tree_action_update_callback(data, response)
|
||||
await self._resource_tree_action_update_callback(data, response)
|
||||
elif action == "remove":
|
||||
self._resource_tree_action_remove_callback(data, response)
|
||||
await self._resource_tree_action_remove_callback(data, response)
|
||||
else:
|
||||
self.lab_logger().error(f"[Host Node-Resource] Invalid action: {action}")
|
||||
response.response = "ERROR"
|
||||
|
||||
@@ -6,13 +6,14 @@ from typing import List, Dict, Any, Optional, TYPE_CHECKING
|
||||
|
||||
import rclpy
|
||||
from rosidl_runtime_py import message_to_ordereddict
|
||||
from unilabos_msgs.msg import Resource
|
||||
from unilabos_msgs.srv import ResourceUpdate
|
||||
|
||||
from unilabos.messages import * # type: ignore # protocol names
|
||||
from rclpy.action import ActionServer, ActionClient
|
||||
from rclpy.action.server import ServerGoalHandle
|
||||
from rclpy.callback_groups import ReentrantCallbackGroup
|
||||
from unilabos_msgs.msg import Resource # type: ignore
|
||||
from unilabos_msgs.srv import ResourceGet, ResourceUpdate # type: ignore
|
||||
from unilabos_msgs.srv._serial_command import SerialCommand_Request, SerialCommand_Response
|
||||
|
||||
from unilabos.compile import action_protocol_generators
|
||||
from unilabos.resources.graphio import list_to_nested_dict, nested_dict_to_list
|
||||
@@ -20,11 +21,11 @@ from unilabos.ros.initialize_device import initialize_device_from_dict
|
||||
from unilabos.ros.msgs.message_converter import (
|
||||
get_action_type,
|
||||
convert_to_ros_msg,
|
||||
convert_from_ros_msg,
|
||||
convert_from_ros_msg_with_mapping,
|
||||
)
|
||||
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, DeviceNodeResourceTracker, ROS2DeviceNode
|
||||
from unilabos.utils.type_check import serialize_result_info, get_result_info_str
|
||||
from unilabos.ros.nodes.resource_tracker import ResourceTreeSet
|
||||
from unilabos.utils.type_check import get_result_info_str
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from unilabos.devices.workstation.workstation_base import WorkstationBase
|
||||
@@ -50,6 +51,7 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
*,
|
||||
driver_instance: "WorkstationBase",
|
||||
device_id: str,
|
||||
device_uuid: str,
|
||||
status_types: Dict[str, Any],
|
||||
action_value_mappings: Dict[str, Any],
|
||||
hardware_interface: Dict[str, Any],
|
||||
@@ -64,6 +66,7 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
super().__init__(
|
||||
driver_instance=driver_instance,
|
||||
device_id=device_id,
|
||||
device_uuid=device_uuid,
|
||||
status_types=status_types,
|
||||
action_value_mappings={**action_value_mappings, **self.protocol_action_mappings},
|
||||
hardware_interface=hardware_interface,
|
||||
@@ -222,16 +225,28 @@ class ROS2WorkstationNode(BaseROS2DeviceNode):
|
||||
# 向Host查询物料当前状态
|
||||
for k, v in goal.get_fields_and_field_types().items():
|
||||
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
|
||||
r = ResourceGet.Request()
|
||||
resource_id = (
|
||||
protocol_kwargs[k]["id"] if v == "unilabos_msgs/Resource" else protocol_kwargs[k][0]["id"]
|
||||
)
|
||||
r.id = resource_id
|
||||
r.with_children = True
|
||||
response = await self._resource_clients["resource_get"].call_async(r)
|
||||
protocol_kwargs[k] = list_to_nested_dict(
|
||||
[convert_from_ros_msg(rs) for rs in response.resources]
|
||||
)
|
||||
self.lab_logger().info(f"{protocol_name} 查询资源状态: Key: {k} Type: {v}")
|
||||
|
||||
try:
|
||||
# 统一处理单个或多个资源
|
||||
resource_id = (
|
||||
protocol_kwargs[k]["id"] if v == "unilabos_msgs/Resource" else protocol_kwargs[k][0]["id"]
|
||||
)
|
||||
r = SerialCommand_Request()
|
||||
r.command = json.dumps({"id": resource_id, "with_children": True})
|
||||
# 发送请求并等待响应
|
||||
response: SerialCommand_Response = await self._resource_clients[
|
||||
"resource_get"
|
||||
].call_async(
|
||||
r
|
||||
) # type: ignore
|
||||
raw_data = json.loads(response.response)
|
||||
tree_set = ResourceTreeSet.from_raw_list(raw_data)
|
||||
target = tree_set.dump()
|
||||
protocol_kwargs[k] = target[0][0] if v == "unilabos_msgs/Resource" else target
|
||||
except Exception as ex:
|
||||
self.lab_logger().error(f"查询资源失败: {k}, 错误: {ex}\n{traceback.format_exc()}")
|
||||
raise
|
||||
|
||||
self.lab_logger().info(f"🔍 最终的 vessel: {protocol_kwargs.get('vessel', 'NOT_FOUND')}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user