Merge branch 'refs/heads/main' into dev

This commit is contained in:
wznln
2025-04-29 10:04:56 +08:00
13 changed files with 118 additions and 81 deletions

View File

@@ -25,21 +25,6 @@ Uni-Lab 操作系统是一个用于实验室自动化的综合平台,旨在连
- [在线文档](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/) - [在线文档](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/)
## 安装指南
请先克隆此仓库:
```bash
git clone https://github.com/dptech-corp/Uni-Lab-OS.git
cd Uni-Lab-OS
```
然后安装 Uni-Lab-OS:
```bash
pip install .
```
## 快速开始 ## 快速开始
1. 配置Conda环境 1. 配置Conda环境
@@ -52,17 +37,30 @@ mamba env create -f unilabos-[YOUR_OS].yaml
mamba activate unilab mamba activate unilab
# 或更新现有环境 # 或更新现有环境
# 其中 `[YOUR_OS]` 可以是 `win64`, `linux-64`, `osx-64`, 或 `osx-arm64`。
conda env update --file unilabos-[YOUR_OS].yml -n 环境名 conda env update --file unilabos-[YOUR_OS].yml -n 环境名
# 现阶段,需要安装 `unilabos_msgs` 包 # 现阶段,需要安装 `unilabos_msgs` 包
# 可以前往 Release 页面下载系统对应的包进行安装 # 可以前往 Release 页面下载系统对应的包进行安装
conda install ros-humble-unilabos-msgs-0.8.0-xxxxx.tar.bz2 conda install ros-humble-unilabos-msgs-0.8.0-xxxxx.tar.bz2
# 安装PyLabRobot等前置
git clone https://github.com/PyLabRobot/pylabrobot
pip install .[opentrons]
``` ```
其中 `[YOUR_OS]` 可以是 `win64`, `linux-64`, `osx-64`, 或 `osx-arm64` 2. 安装 Uni-Lab-OS:
```bash
# 克隆仓库
git clone https://github.com/dptech-corp/Uni-Lab-OS.git
cd Uni-Lab-OS
2. 启动 Uni-Lab 系统: # 安装 Uni-Lab-OS
pip install .
```
3. 启动 Uni-Lab 系统:
请见[文档-启动样例](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/boot_examples/index.html) 请见[文档-启动样例](https://readthedocs.dp.tech/Uni-Lab/v0.8.0/boot_examples/index.html)

View File

@@ -6,7 +6,7 @@ def generate_clean_protocol(
G: nx.DiGraph, G: nx.DiGraph,
vessel: str, # Vessel to clean. vessel: str, # Vessel to clean.
solvent: str, # Solvent to clean vessel with. solvent: str, # Solvent to clean vessel with.
volume: float = 25000.0, # Optional. Volume of solvent to clean vessel with. volume: float = 25.0, # Optional. Volume of solvent to clean vessel with.
temp: float = 25, # Optional. Temperature to heat vessel to while cleaning. temp: float = 25, # Optional. Temperature to heat vessel to while cleaning.
repeats: int = 1, # Optional. Number of cleaning cycles to perform. repeats: int = 1, # Optional. Number of cleaning cycles to perform.
) -> list[dict]: ) -> list[dict]:
@@ -27,7 +27,7 @@ def generate_clean_protocol(
from_vessel = f"flask_{solvent}" from_vessel = f"flask_{solvent}"
waste_vessel = f"waste_workup" waste_vessel = f"waste_workup"
transfer_flowrate = flowrate = 2500.0 transfer_flowrate = flowrate = 2.5
# 生成泵操作的动作序列 # 生成泵操作的动作序列
for i in range(repeats): for i in range(repeats):

View File

@@ -24,8 +24,8 @@ def generate_evaporate_protocol(
# 生成泵操作的动作序列 # 生成泵操作的动作序列
pump_action_sequence = [] pump_action_sequence = []
reactor_volume = 500000.0 reactor_volume = 500.0
transfer_flowrate = flowrate = 2500.0 transfer_flowrate = flowrate = 2.5
# 开启冷凝器 # 开启冷凝器
pump_action_sequence.append({ pump_action_sequence.append({

View File

@@ -7,7 +7,7 @@ def generate_pump_protocol(
from_vessel: str, from_vessel: str,
to_vessel: str, to_vessel: str,
volume: float, volume: float,
flowrate: float = 500.0, flowrate: float = 0.5,
transfer_flowrate: float = 0, transfer_flowrate: float = 0,
) -> list[dict]: ) -> list[dict]:
""" """
@@ -141,11 +141,11 @@ def generate_pump_protocol_with_rinsing(
time: float = 0, time: float = 0,
viscous: bool = False, viscous: bool = False,
rinsing_solvent: str = "air", rinsing_solvent: str = "air",
rinsing_volume: float = 5000.0, rinsing_volume: float = 5.0,
rinsing_repeats: int = 2, rinsing_repeats: int = 2,
solid: bool = False, solid: bool = False,
flowrate: float = 2500.0, flowrate: float = 2.5,
transfer_flowrate: float = 500.0, transfer_flowrate: float = 0.5,
) -> list[dict]: ) -> list[dict]:
""" """
Generates a pump protocol for transferring a specified volume between vessels, including rinsing steps with a chosen solvent. This function constructs a sequence of pump actions based on the provided parameters and the shortest path in a directed graph. Generates a pump protocol for transferring a specified volume between vessels, including rinsing steps with a chosen solvent. This function constructs a sequence of pump actions based on the provided parameters and the shortest path in a directed graph.
@@ -159,11 +159,11 @@ def generate_pump_protocol_with_rinsing(
time (float, optional): Time over which to perform the transfer (default is 0). time (float, optional): Time over which to perform the transfer (default is 0).
viscous (bool, optional): Indicates if the fluid is viscous (default is False). viscous (bool, optional): Indicates if the fluid is viscous (default is False).
rinsing_solvent (str, optional): The solvent to use for rinsing (default is "air"). rinsing_solvent (str, optional): The solvent to use for rinsing (default is "air").
rinsing_volume (float, optional): The volume of rinsing solvent to use (default is 5000.0). rinsing_volume (float, optional): The volume of rinsing solvent to use (default is 5.0).
rinsing_repeats (int, optional): The number of times to repeat rinsing (default is 2). rinsing_repeats (int, optional): The number of times to repeat rinsing (default is 2).
solid (bool, optional): Indicates if the transfer involves a solid (default is False). solid (bool, optional): Indicates if the transfer involves a solid (default is False).
flowrate (float, optional): The flow rate for the transfer (default is 2500.0). 最终注入容器B时的流速 flowrate (float, optional): The flow rate for the transfer (default is 2.5). 最终注入容器B时的流速
transfer_flowrate (float, optional): The flow rate for the transfer action (default is 500.0). 泵骨架中转移流速(若不指定,默认与注入流速相同) transfer_flowrate (float, optional): The flow rate for the transfer action (default is 0.5). 泵骨架中转移流速(若不指定,默认与注入流速相同)
Returns: Returns:
list[dict]: A sequence of pump actions to be executed for the transfer and rinsing process. 泵操作的动作序列. list[dict]: A sequence of pump actions to be executed for the transfer and rinsing process. 泵操作的动作序列.
@@ -172,7 +172,7 @@ def generate_pump_protocol_with_rinsing(
AssertionError: If the number of rinsing solvents does not match the number of rinsing repeats. AssertionError: If the number of rinsing solvents does not match the number of rinsing repeats.
Examples: Examples:
pump_protocol = generate_pump_protocol_with_rinsing(G, "vessel_A", "vessel_B", 100.0, rinsing_solvent="water") pump_protocol = generate_pump_protocol_with_rinsing(G, "vessel_A", "vessel_B", 0.1, rinsing_solvent="water")
""" """
air_vessel = "flask_air" air_vessel = "flask_air"
waste_vessel = f"waste_workup" waste_vessel = f"waste_workup"

View File

@@ -11,7 +11,7 @@ def generate_separate_protocol(
to_vessel: str, # Vessel to send product phase to. to_vessel: str, # Vessel to send product phase to.
waste_phase_to_vessel: str, # Optional. Vessel to send waste phase to. waste_phase_to_vessel: str, # Optional. Vessel to send waste phase to.
solvent: str, # Optional. Solvent to add to separation vessel after contents of from_vessel has been transferred to create two phases. solvent: str, # Optional. Solvent to add to separation vessel after contents of from_vessel has been transferred to create two phases.
solvent_volume: float = 50000, # Optional. Volume of solvent to add. solvent_volume: float = 50, # Optional. Volume of solvent to add (mL).
through: str = "", # Optional. Solid chemical to send product phase through on way to to_vessel, e.g. 'celite'. through: str = "", # Optional. Solid chemical to send product phase through on way to to_vessel, e.g. 'celite'.
repeats: int = 1, # Optional. Number of separations to perform. repeats: int = 1, # Optional. Number of separations to perform.
stir_time: float = 30, # Optional. Time stir for after adding solvent, before separation of phases. stir_time: float = 30, # Optional. Time stir for after adding solvent, before separation of phases.
@@ -32,7 +32,7 @@ def generate_separate_protocol(
# 生成泵操作的动作序列 # 生成泵操作的动作序列
pump_action_sequence = [] pump_action_sequence = []
reactor_volume = 500000.0 reactor_volume = 500.0
waste_vessel = waste_phase_to_vessel waste_vessel = waste_phase_to_vessel
# TODO通过物料管理系统找到溶剂的容器 # TODO通过物料管理系统找到溶剂的容器
@@ -46,7 +46,7 @@ def generate_separate_protocol(
separator_controller = f"{separation_vessel}_controller" separator_controller = f"{separation_vessel}_controller"
separation_vessel_bottom = f"flask_{separation_vessel}" separation_vessel_bottom = f"flask_{separation_vessel}"
transfer_flowrate = flowrate = 2500.0 transfer_flowrate = flowrate = 2.5
if from_vessel != separation_vessel: if from_vessel != separation_vessel:
pump_action_sequence.append( pump_action_sequence.append(
@@ -140,8 +140,8 @@ def generate_separate_protocol(
"action_kwargs": { "action_kwargs": {
"from_vessel": separation_vessel_bottom, "from_vessel": separation_vessel_bottom,
"to_vessel": to_vessel, "to_vessel": to_vessel,
"volume": 250000.0, "volume": 250.0,
"time": 250000.0 / flowrate, "time": 250.0 / flowrate,
# "transfer_flowrate": transfer_flowrate, # "transfer_flowrate": transfer_flowrate,
} }
} }
@@ -164,8 +164,8 @@ def generate_separate_protocol(
"action_kwargs": { "action_kwargs": {
"from_vessel": separation_vessel_bottom, "from_vessel": separation_vessel_bottom,
"to_vessel": waste_vessel, "to_vessel": waste_vessel,
"volume": 250000.0, "volume": 250.0,
"time": 250000.0 / flowrate, "time": 250.0 / flowrate,
# "transfer_flowrate": transfer_flowrate, # "transfer_flowrate": transfer_flowrate,
} }
} }
@@ -179,8 +179,8 @@ def generate_separate_protocol(
"action_kwargs": { "action_kwargs": {
"from_vessel": separation_vessel_bottom, "from_vessel": separation_vessel_bottom,
"to_vessel": waste_vessel, "to_vessel": waste_vessel,
"volume": 250000.0, "volume": 250.0,
"time": 250000.0 / flowrate, "time": 250.0 / flowrate,
# "transfer_flowrate": transfer_flowrate, # "transfer_flowrate": transfer_flowrate,
} }
} }
@@ -203,8 +203,8 @@ def generate_separate_protocol(
"action_kwargs": { "action_kwargs": {
"from_vessel": separation_vessel_bottom, "from_vessel": separation_vessel_bottom,
"to_vessel": to_vessel, "to_vessel": to_vessel,
"volume": 250000.0, "volume": 250.0,
"time": 250000.0 / flowrate, "time": 250.0 / flowrate,
# "transfer_flowrate": transfer_flowrate, # "transfer_flowrate": transfer_flowrate,
} }
} }
@@ -221,8 +221,8 @@ def generate_separate_protocol(
"action_kwargs": { "action_kwargs": {
"from_vessel": to_vessel, "from_vessel": to_vessel,
"to_vessel": separation_vessel, "to_vessel": separation_vessel,
"volume": 250000.0, "volume": 250.0,
"time": 250000.0 / flowrate, "time": 250.0 / flowrate,
# "transfer_flowrate": transfer_flowrate, # "transfer_flowrate": transfer_flowrate,
} }
} }

View File

@@ -75,20 +75,29 @@ def _update_config_from_module(module):
# 需要先判断是否为相对路径 # 需要先判断是否为相对路径
if MQConfig.ca_file.startswith("."): if MQConfig.ca_file.startswith("."):
MQConfig.ca_file = os.path.join(BasicConfig.config_path, MQConfig.ca_file) MQConfig.ca_file = os.path.join(BasicConfig.config_path, MQConfig.ca_file)
with open(MQConfig.ca_file, "r", encoding="utf-8") as f: if len(MQConfig.ca_file) != 0:
MQConfig.ca_content = f.read() with open(MQConfig.ca_file, "r", encoding="utf-8") as f:
MQConfig.ca_content = f.read()
else:
logger.warning("Skipping CA file loading, ca_file is empty")
if len(MQConfig.cert_content) == 0: if len(MQConfig.cert_content) == 0:
# 需要先判断是否为相对路径 # 需要先判断是否为相对路径
if MQConfig.cert_file.startswith("."): if MQConfig.cert_file.startswith("."):
MQConfig.cert_file = os.path.join(BasicConfig.config_path, MQConfig.cert_file) MQConfig.cert_file = os.path.join(BasicConfig.config_path, MQConfig.cert_file)
with open(MQConfig.cert_file, "r", encoding="utf-8") as f: if len(MQConfig.ca_file) != 0:
MQConfig.cert_content = f.read() with open(MQConfig.cert_file, "r", encoding="utf-8") as f:
MQConfig.cert_content = f.read()
else:
logger.warning("Skipping cert file loading, cert_file is empty")
if len(MQConfig.key_content) == 0: if len(MQConfig.key_content) == 0:
# 需要先判断是否为相对路径 # 需要先判断是否为相对路径
if MQConfig.key_file.startswith("."): if MQConfig.key_file.startswith("."):
MQConfig.key_file = os.path.join(BasicConfig.config_path, MQConfig.key_file) MQConfig.key_file = os.path.join(BasicConfig.config_path, MQConfig.key_file)
with open(MQConfig.key_file, "r", encoding="utf-8") as f: if len(MQConfig.ca_file) != 0:
MQConfig.key_content = f.read() with open(MQConfig.key_file, "r", encoding="utf-8") as f:
MQConfig.key_content = f.read()
else:
logger.warning("Skipping key file loading, key_file is empty")
def load_config(config_path=None): def load_config(config_path=None):

View File

@@ -1,7 +1,7 @@
serial: serial:
description: Serial communication interface, used when sharing same serial port for multiple devices description: Serial communication interface, used when sharing same serial port for multiple devices
class: class:
module: unilabos.ros.nodes.presets:ROS2SerialNode module: unilabos.ros.nodes.presets.serial_node:ROS2SerialNode
type: ros2 type: ros2
schema: schema:
properties: {} properties: {}

View File

@@ -1,7 +1,7 @@
separator.homemade: separator.homemade:
description: Separator device with homemade grbl controller description: Separator device with homemade grbl controller
class: class:
module: unilabos.devices.separator.homemade_grbl_conductivity:Separator_Controller module: unilabos.devices.separator.homemade_grbl_conductivity:SeparatorController
type: python type: python
status_types: status_types:
sensordata: Float64 sensordata: Float64

View File

@@ -3,6 +3,10 @@ syringe_pump_with_valve.runze:
class: class:
module: unilabos.devices.pump_and_valve.runze_backbone:RunzeSyringePump module: unilabos.devices.pump_and_valve.runze_backbone:RunzeSyringePump
type: python type: python
hardware_interface:
name: hardware_interface
read: send_command
write: send_command
schema: schema:
type: object type: object
properties: properties:

View File

@@ -342,9 +342,15 @@ class BaseROS2DeviceNode(Node, Generic[T]):
else: else:
return getattr(self.driver_instance, attr_name) return getattr(self.driver_instance, attr_name)
except AttributeError as ex: except AttributeError as ex:
self.lab_logger().error( if ex.args[0].startswith(f"AttributeError: '{self.driver_instance.__class__.__name__}' object"):
f"publish error, {str(type(self.driver_instance))[8:-2]} has no attribute '{attr_name}'" self.lab_logger().error(
) f"publish error, {str(type(self.driver_instance))[8:-2]} has no attribute '{attr_name}'"
)
else:
self.lab_logger().error(
f"publish error, when {str(type(self.driver_instance))[8:-2]} getting attribute '{attr_name}'"
)
self.lab_logger().error(traceback.format_exc())
self._property_publishers[attr_name] = PropertyPublisher( self._property_publishers[attr_name] = PropertyPublisher(
self, attr_name, get_device_attr, msg_type, initial_period, self._print_publish self, attr_name, get_device_attr, msg_type, initial_period, self._print_publish
@@ -430,6 +436,15 @@ class BaseROS2DeviceNode(Node, Generic[T]):
self.lab_logger().info(f"同步执行动作 {ACTION}") self.lab_logger().info(f"同步执行动作 {ACTION}")
future = self._executor.submit(ACTION, **action_kwargs) future = self._executor.submit(ACTION, **action_kwargs)
def _handle_future_exception(fut):
try:
fut.result()
except Exception as e:
error(f"同步任务 {ACTION.__name__} 报错了")
error(traceback.format_exc())
future.add_done_callback(_handle_future_exception)
action_type = action_value_mapping["type"] action_type = action_value_mapping["type"]
feedback_msg_types = action_type.Feedback.get_fields_and_field_types() feedback_msg_types = action_type.Feedback.get_fields_and_field_types()
result_msg_types = action_type.Result.get_fields_and_field_types() result_msg_types = action_type.Result.get_fields_and_field_types()

View File

@@ -1,6 +1,7 @@
import copy import copy
import threading import threading
import time import time
import traceback
import uuid import uuid
from typing import Optional, Dict, Any, List, ClassVar, Set from typing import Optional, Dict, Any, List, ClassVar, Set
@@ -132,10 +133,14 @@ class HostNode(BaseROS2DeviceNode):
controller_config["update_rate"] = update_rate controller_config["update_rate"] = update_rate
self.initialize_controller(controller_id, controller_config) self.initialize_controller(controller_id, controller_config)
for bridge in self.bridges: try:
if hasattr(bridge, "resource_add"): for bridge in self.bridges:
self.lab_logger().info("[Host Node-Resource] Adding resources to bridge.") if hasattr(bridge, "resource_add"):
bridge.resource_add(add_schema(resources_config)) self.lab_logger().info("[Host Node-Resource] Adding resources to bridge.")
bridge.resource_add(add_schema(resources_config))
except Exception as ex:
self.lab_logger().error("[Host Node-Resource] 添加物料出错!")
self.lab_logger().error(traceback.format_exc())
# 创建定时器,定期发现设备 # 创建定时器,定期发现设备
self._discovery_timer = self.create_timer( self._discovery_timer = self.create_timer(

View File

@@ -20,7 +20,7 @@ from unilabos.ros.msgs.message_converter import (
convert_from_ros_msg, convert_from_ros_msg,
convert_from_ros_msg_with_mapping, convert_from_ros_msg_with_mapping,
) )
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, DeviceNodeResourceTracker from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, DeviceNodeResourceTracker, ROS2DeviceNode
class ROS2ProtocolNode(BaseROS2DeviceNode): class ROS2ProtocolNode(BaseROS2DeviceNode):
@@ -55,34 +55,39 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
) )
# 初始化子设备 # 初始化子设备
communication_node_id = None self.communication_node_id_to_instance = {}
for device_id, device_config in self.children.items(): for device_id, device_config in self.children.items():
if device_config.get("type", "device") != "device": if device_config.get("type", "device") != "device":
self.lab_logger().debug(f"[Protocol Node] Skipping type {device_config['type']} {device_id} already existed, skipping.") self.lab_logger().debug(f"[Protocol Node] Skipping type {device_config['type']} {device_id} already existed, skipping.")
continue continue
d = self.initialize_device(device_id, device_config) try:
d = self.initialize_device(device_id, device_config)
except Exception as ex:
self.lab_logger().error(f"[Protocol Node] Failed to initialize device {device_id}: {ex}")
d = None
if d is None: if d is None:
continue continue
if "serial_" in device_id or "io_" in device_id: if "serial_" in device_id or "io_" in device_id:
communication_node_id = device_id self.communication_node_id_to_instance[device_id] = d
continue continue
# 设置硬件接口代理 # 设置硬件接口代理
if d and hasattr(d, "_hardware_interface"): if d:
if ( if (
hasattr(d, d._hardware_interface["name"]) hasattr(d.driver_instance, d.ros_node_instance._hardware_interface["name"])
and hasattr(d, d._hardware_interface["write"]) and hasattr(d.driver_instance, d.ros_node_instance._hardware_interface["write"])
and (d._hardware_interface["read"] is None or hasattr(d, d._hardware_interface["read"])) and (d.ros_node_instance._hardware_interface["read"] is None or hasattr(d.driver_instance, d.ros_node_instance._hardware_interface["read"]))
): ):
name = getattr(d, d._hardware_interface["name"]) name = getattr(d.driver_instance, d.ros_node_instance._hardware_interface["name"])
read = d._hardware_interface.get("read", None) read = d.ros_node_instance._hardware_interface.get("read", None)
write = d._hardware_interface.get("write", None) write = d.ros_node_instance._hardware_interface.get("write", None)
# 如果硬件接口是字符串,通过通信设备提供 # 如果硬件接口是字符串,通过通信设备提供
if isinstance(name, str) and communication_node_id in self.sub_devices: if isinstance(name, str) and name in self.sub_devices:
self._setup_hardware_proxy(d, self.sub_devices[communication_node_id], read, write) self._setup_hardware_proxy(d, self.sub_devices[name], read, write)
def _setup_protocol_names(self, protocol_type): def _setup_protocol_names(self, protocol_type):
# 处理协议类型 # 处理协议类型
@@ -234,11 +239,11 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
"""还没有改过的部分""" """还没有改过的部分"""
def _setup_hardware_proxy(self, device, communication_device, read_method, write_method): def _setup_hardware_proxy(self, device: ROS2DeviceNode, communication_device: ROS2DeviceNode, read_method, write_method):
"""为设备设置硬件接口代理""" """为设备设置硬件接口代理"""
extra_info = [getattr(device, info) for info in communication_device._hardware_interface.get("extra_info", [])] extra_info = [getattr(device.driver_instance, info) for info in communication_device.ros_node_instance._hardware_interface.get("extra_info", [])]
write_func = getattr(communication_device, communication_device._hardware_interface["write"]) write_func = getattr(communication_device.ros_node_instance, communication_device.ros_node_instance._hardware_interface["write"])
read_func = getattr(communication_device, communication_device._hardware_interface["read"]) read_func = getattr(communication_device.ros_node_instance, communication_device.ros_node_instance._hardware_interface["read"])
def _read(): def _read():
return read_func(*extra_info) return read_func(*extra_info)
@@ -247,9 +252,9 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
return write_func(*extra_info, command) return write_func(*extra_info, command)
if read_method: if read_method:
setattr(device, read_method, _read) setattr(device.driver_instance, read_method, _read)
if write_method: if write_method:
setattr(device, write_method, _write) setattr(device.driver_instance, write_method, _write)
async def _update_resources(self, goal, protocol_kwargs): async def _update_resources(self, goal, protocol_kwargs):

View File

@@ -21,6 +21,7 @@ class ROS2SerialNode(BaseROS2DeviceNode):
self.hardware_interface = Serial(baudrate=baudrate, port=port) self.hardware_interface = Serial(baudrate=baudrate, port=port)
except (OSError, SerialException) as e: except (OSError, SerialException) as e:
# 因为还没调用父类初始化,无法使用日志,直接抛出异常 # 因为还没调用父类初始化,无法使用日志,直接抛出异常
# print(f"Failed to connect to serial port {port} at {baudrate} baudrate.")
raise RuntimeError(f"Failed to connect to serial port {port} at {baudrate} baudrate.") from e raise RuntimeError(f"Failed to connect to serial port {port} at {baudrate} baudrate.") from e
# 初始化BaseROS2DeviceNode使用自身作为driver_instance # 初始化BaseROS2DeviceNode使用自身作为driver_instance
@@ -46,7 +47,7 @@ class ROS2SerialNode(BaseROS2DeviceNode):
self.lab_logger().info(f"【ROS2SerialNode.__init__】创建串口写入服务: serialwrite") self.lab_logger().info(f"【ROS2SerialNode.__init__】创建串口写入服务: serialwrite")
def send_command(self, command: str): def send_command(self, command: str):
self.lab_logger().info(f"【ROS2SerialNode.send_command】发送命令: {command}") # self.lab_logger().debug(f"【ROS2SerialNode.send_command】发送命令: {command}")
with self._query_lock: with self._query_lock:
if self._closing: if self._closing:
self.lab_logger().error(f"【ROS2SerialNode.send_command】设备正在关闭无法发送命令") self.lab_logger().error(f"【ROS2SerialNode.send_command】设备正在关闭无法发送命令")
@@ -58,23 +59,23 @@ class ROS2SerialNode(BaseROS2DeviceNode):
response = self.hardware_interface.write(full_command_data) response = self.hardware_interface.write(full_command_data)
# time.sleep(0.05) # time.sleep(0.05)
output = self._receive(self.hardware_interface.read_until(b"\n")) output = self._receive(self.hardware_interface.read_until(b"\n"))
self.lab_logger().info(f"【ROS2SerialNode.send_command】接收响应: {output}") # self.lab_logger().debug(f"【ROS2SerialNode.send_command】接收响应: {output}")
return output return output
def read_data(self): def read_data(self):
self.lab_logger().debug(f"【ROS2SerialNode.read_data】读取数据") # self.lab_logger().debug(f"【ROS2SerialNode.read_data】读取数据")
with self._query_lock: with self._query_lock:
if self._closing: if self._closing:
self.lab_logger().error(f"【ROS2SerialNode.read_data】设备正在关闭无法读取数据") self.lab_logger().error(f"【ROS2SerialNode.read_data】设备正在关闭无法读取数据")
raise RuntimeError raise RuntimeError
data = self.hardware_interface.read_until(b"\n") data = self.hardware_interface.read_until(b"\n")
result = self._receive(data) result = self._receive(data)
self.lab_logger().debug(f"【ROS2SerialNode.read_data】读取到数据: {result}") # self.lab_logger().debug(f"【ROS2SerialNode.read_data】读取到数据: {result}")
return result return result
def _receive(self, data: bytes): def _receive(self, data: bytes):
ascii_string = "".join(chr(byte) for byte in data) ascii_string = "".join(chr(byte) for byte in data)
self.lab_logger().debug(f"【ROS2SerialNode._receive】接收数据: {ascii_string}") # self.lab_logger().debug(f"【ROS2SerialNode._receive】接收数据: {ascii_string}")
return ascii_string return ascii_string
def handle_serial_request(self, request, response): def handle_serial_request(self, request, response):