diff --git a/unilabos/app/ws_client.py b/unilabos/app/ws_client.py index 1a859671..9a2535ce 100644 --- a/unilabos/app/ws_client.py +++ b/unilabos/app/ws_client.py @@ -1093,7 +1093,7 @@ class WebSocketClient(BaseCommunicationClient): }, } self.message_processor.send_message(message) - logger.debug(f"[WebSocketClient] Device status published: {device_id}.{property_name}") + logger.trace(f"[WebSocketClient] Device status published: {device_id}.{property_name}") def publish_job_status( self, feedback_data: dict, item: QueueItem, status: str, return_info: Optional[dict] = None diff --git a/unilabos/registry/registry.py b/unilabos/registry/registry.py index cff7614e..48e2df77 100644 --- a/unilabos/registry/registry.py +++ b/unilabos/registry/registry.py @@ -294,7 +294,7 @@ class Registry: logger.warning(f"[UniLab Registry] 设备 {device_id} 的 {field_name} 类型为空,跳过替换") return type_name convert_manager = { # 将python基本对象转为ros2基本对象 - "builtins:str": "String", + "str": "String", "bool": "Bool", "int": "Int64", "float": "Float64", diff --git a/unilabos/resources/graphio.py b/unilabos/resources/graphio.py index 3a8d2e46..697c08ab 100644 --- a/unilabos/resources/graphio.py +++ b/unilabos/resources/graphio.py @@ -430,7 +430,7 @@ def resource_ulab_to_plr(resource: dict, plr_model=False) -> "ResourcePLR": return resource_plr -def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None): +def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None, with_children=True): def replace_plr_type_to_ulab(source: str): replace_info = { "plate": "plate", @@ -445,12 +445,12 @@ def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None): else: print("转换pylabrobot的时候,出现未知类型", source) return "container" - def resource_plr_to_ulab_inner(d: dict, all_states: dict) -> dict: + def resource_plr_to_ulab_inner(d: dict, all_states: dict, child=True) -> dict: r = { "id": d["name"], "name": d["name"], "sample_id": None, - "children": [resource_plr_to_ulab_inner(child, all_states) for child in d["children"]], + "children": [resource_plr_to_ulab_inner(child, all_states) for child in d["children"]] if child else [], "parent": d["parent_name"] if d["parent_name"] else parent_name if parent_name else None, "type": replace_plr_type_to_ulab(d.get("category")), # FIXME plr自带的type是python class name "class": d.get("class", ""), @@ -465,7 +465,7 @@ def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None): return r d = resource_plr.serialize() all_states = resource_plr.serialize_all_state() - r = resource_plr_to_ulab_inner(d, all_states) + r = resource_plr_to_ulab_inner(d, all_states, with_children) return r diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index cf116faa..8a2df1f2 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -12,6 +12,7 @@ import asyncio import rclpy import yaml +from msgcenterpy import ROS2MessageInstance from rclpy.node import Node from rclpy.action import ActionServer, ActionClient from rclpy.action.server import ServerGoalHandle @@ -177,7 +178,7 @@ class PropertyPublisher: try: self.publisher_ = node.create_publisher(msg_type, f"{name}", 10) except AttributeError as ex: - logger.error(f"创建发布者失败,可能由于注册表有误,类型: {msg_type},错误: {ex}\n{traceback.format_exc()}") + self.node.lab_logger().error(f"创建发布者 {name} 失败,可能由于注册表有误,类型: {msg_type},错误: {ex}\n{traceback.format_exc()}") self.timer = node.create_timer(self.timer_period, self.publish_property) self.__loop = get_event_loop() str_msg_type = str(msg_type)[8:-2] @@ -399,9 +400,13 @@ class BaseROS2DeviceNode(Node, Generic[T]): logger.info(f"更新物料{container_query_dict['name']}的数据{resource['data']} dict") else: logger.info(f"更新物料{container_query_dict['name']}出现不支持的数据类型{type(resource)} {resource}") - response = await rclient.call_async(request) + response: ResourceAdd.Response = await rclient.call_async(request) # 应该先add_resource了 - res.response = "OK" + final_response = { + "created_resources": [ROS2MessageInstance(i).get_python_dict() for i in request.resources], + "liquid_input_resources": [] + } + res.response = json.dumps(final_response) # 如果driver自己就有assign的方法,那就使用driver自己的assign方法 if hasattr(self.driver_instance, "create_resource"): create_resource_func = getattr(self.driver_instance, "create_resource") @@ -418,7 +423,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): ) res.response = get_result_info_str("", True, ret) except Exception as e: - traceback.print_exc() + self.lab_logger().error(f"运行设备的create_resource出错:{create_resource_func}\n{traceback.format_exc()}") res.response = get_result_info_str(traceback.format_exc(), False, {}) return res # 接下来该根据bind_parent_id进行assign了,目前只有plr可以进行assign,不然没有办法输入到物料系统中 @@ -438,6 +443,7 @@ class BaseROS2DeviceNode(Node, Generic[T]): # resources.list() resources_tree = dict_to_tree(copy.deepcopy({r["id"]: r for r in resources})) plr_instance = resource_ulab_to_plr(resources_tree[0], contain_model) + if isinstance(plr_instance, Plate): empty_liquid_info_in = [(None, 0)] * plr_instance.num_items for liquid_type, liquid_volume, liquid_input_slot in zip( @@ -445,6 +451,11 @@ class BaseROS2DeviceNode(Node, Generic[T]): ): empty_liquid_info_in[liquid_input_slot] = (liquid_type, liquid_volume) plr_instance.set_well_liquids(empty_liquid_info_in) + input_wells_ulr = [ + convert_to_ros_msg(Resource, resource_plr_to_ulab(plr_instance.get_well(LIQUID_INPUT_SLOT), with_children=False)) for r in LIQUID_INPUT_SLOT + ] + final_response["liquid_input_resources"] = [ROS2MessageInstance(i).get_python_dict() for i in input_wells_ulr] + res.response = json.dumps(final_response) if isinstance(resource, OTDeck) and "slot" in other_calling_param: other_calling_param["slot"] = int(other_calling_param["slot"]) resource.assign_child_at_slot(plr_instance, **other_calling_param) diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index 18811920..70bde938 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -13,6 +13,7 @@ from geometry_msgs.msg import Point from rclpy.action import ActionClient, get_action_server_names_and_types_by_node from rclpy.callback_groups import ReentrantCallbackGroup from rclpy.service import Service +from rosidl_runtime_py import set_message_fields from unilabos_msgs.msg import Resource # type: ignore from unilabos_msgs.srv import ( ResourceAdd, @@ -366,7 +367,7 @@ class HostNode(BaseROS2DeviceNode): bind_parent_ids: list[str], bind_locations: list[Point], other_calling_params: list[str], - ): + ) -> List[str]: responses = [] for resource, device_id, bind_parent_id, bind_location, other_calling_param in zip( resources, device_ids, bind_parent_ids, bind_locations, other_calling_params @@ -403,8 +404,8 @@ class HostNode(BaseROS2DeviceNode): }, ensure_ascii=False, ) - response = await sclient.call_async(request) - responses.append(response) + response: SerialCommand.Response = await sclient.call_async(request) + responses.append(response.response) return responses async def create_resource( @@ -463,11 +464,23 @@ class HostNode(BaseROS2DeviceNode): ) ] - response = await self.create_resource_detailed( + response: List[str] = await self.create_resource_detailed( resources, device_ids, bind_parent_id, bind_location, other_calling_param ) - return response + try: + new_li = [] + for i in response: + res = json.loads(i) + new_li.append(res) + return { + "resources": new_li, + "liquid_input_resources": new_li + } + except Exception as ex: + pass + _n = "\n" + raise ValueError(f"创建资源时失败!\n{_n.join(response)}") def initialize_device(self, device_id: str, device_config: Dict[str, Any]) -> None: """ diff --git a/unilabos/ros/nodes/presets/protocol_node.py b/unilabos/ros/nodes/presets/protocol_node.py index 857f8133..24e89703 100644 --- a/unilabos/ros/nodes/presets/protocol_node.py +++ b/unilabos/ros/nodes/presets/protocol_node.py @@ -218,7 +218,7 @@ class ROS2ProtocolNode(BaseROS2DeviceNode): [convert_from_ros_msg(rs) for rs in response.resources] ) - self.lab_logger().info(f"🔍 最终的 vessel: {protocol_kwargs.get('vessel', 'NOT_FOUND')}") + # self.lab_logger().info(f"🔍 最终的 vessel: {protocol_kwargs.get('vessel', 'NOT_FOUND')}") from unilabos.resources.graphio import physical_setup_graph @@ -325,6 +325,8 @@ class ROS2ProtocolNode(BaseROS2DeviceNode): async def execute_single_action(self, device_id, action_name, action_kwargs): """执行单个动作""" # 构建动作ID + if action_name == "log_message": + self.lab_logger().info(f"[Protocol Log] {action_kwargs}") if device_id in ["", None, "self"]: action_id = f"/devices/{self.device_id}/{action_name}" else: diff --git a/unilabos/utils/environment_check.py b/unilabos/utils/environment_check.py index cd50b822..c916a929 100644 --- a/unilabos/utils/environment_check.py +++ b/unilabos/utils/environment_check.py @@ -19,7 +19,7 @@ class EnvironmentChecker: # 包导入名 : pip安装名 # "pymodbus.framer.FramerType": "pymodbus==3.9.2", "websockets": "websockets", - "paho.mqtt": "paho-mqtt", + "msgcenterpy": "msgcenterpy", "opentrons_shared_data": "opentrons_shared_data", }