From 35ada068ccf79426ea0c78bd82bbded36f03a027 Mon Sep 17 00:00:00 2001 From: Xuwznln <18435084+Xuwznln@users.noreply.github.com> Date: Sun, 20 Apr 2025 18:24:45 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E6=94=AF=E6=8C=81local=5Fconfig=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=20=E6=B7=BB=E5=8A=A0=E6=B3=A8=E5=86=8C=E8=A1=A8descri?= =?UTF-8?q?ption=E5=AD=97=E6=AE=B5=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #11 * Update README and MQTTClient for installation instructions and code improvements * feat: 支持local_config启动 add: 增加对crt path的说明,为传入config.py的相对路径 move: web component * add: registry description --------- Co-authored-by: Harvey Que --- .gitignore | 2 +- MANIFEST.in | 2 ++ docs/user_guide/configuration.md | 8 +++++--- unilabos/app/main.py | 11 ++++++---- unilabos/{ => app}/web/__init__.py | 8 ++++---- unilabos/{ => app}/web/api.py | 2 +- unilabos/{ => app}/web/client.py | 0 unilabos/{ => app}/web/pages.py | 6 +++--- unilabos/{ => app}/web/server.py | 4 ++-- unilabos/{ => app}/web/static/styles.css | 0 unilabos/{ => app}/web/templates/base.html | 0 unilabos/{ => app}/web/templates/home.html | 0 unilabos/{ => app}/web/templates/status.html | 0 unilabos/{ => app}/web/utils/__init__.py | 0 unilabos/{ => app}/web/utils/action_utils.py | 0 unilabos/{ => app}/web/utils/device_utils.py | 0 unilabos/{ => app}/web/utils/host_utils.py | 2 +- unilabos/{ => app}/web/utils/ros_utils.py | 2 +- unilabos/config/config.py | 17 ++++++---------- .../registry/device_comms/modbus_ioboard.yaml | 1 + unilabos/registry/device_comms/serial.yaml | 1 + .../devices/characterization_optic.yaml | 1 + unilabos/registry/devices/liquid_handler.yaml | 1 + .../devices/organic_miscellaneous.yaml | 2 ++ unilabos/registry/devices/pump_and_valve.yaml | 3 +++ unilabos/registry/devices/robot_agv.yaml | 1 + unilabos/registry/devices/robot_arm.yaml | 1 + unilabos/registry/devices/robot_gripper.yaml | 2 ++ .../registry/devices/robot_linear_motion.yaml | 2 ++ unilabos/registry/devices/temperature.yaml | 3 +++ .../registry/devices/vacuum_and_purge.yaml | 2 ++ unilabos/registry/devices/work_station.yaml | 1 + unilabos/registry/registry.py | 7 ++++--- .../registry/resources/opentrons/deck.yaml | 1 + .../resources/opentrons/plate_adapters.yaml | 1 + .../registry/resources/opentrons/plates.yaml | 15 ++++++++++++++ .../resources/opentrons/reservoirs.yaml | 6 ++++++ .../resources/opentrons/tip_racks.yaml | 13 ++++++++++++ .../resources/opentrons/tube_racks.yaml | 20 +++++++++++++++++++ 39 files changed, 114 insertions(+), 34 deletions(-) rename unilabos/{ => app}/web/__init__.py (56%) rename unilabos/{ => app}/web/api.py (98%) rename unilabos/{ => app}/web/client.py (100%) rename unilabos/{ => app}/web/pages.py (96%) rename unilabos/{ => app}/web/server.py (96%) rename unilabos/{ => app}/web/static/styles.css (100%) rename unilabos/{ => app}/web/templates/base.html (100%) rename unilabos/{ => app}/web/templates/home.html (100%) rename unilabos/{ => app}/web/templates/status.html (100%) rename unilabos/{ => app}/web/utils/__init__.py (100%) rename unilabos/{ => app}/web/utils/action_utils.py (100%) rename unilabos/{ => app}/web/utils/device_utils.py (100%) rename unilabos/{ => app}/web/utils/host_utils.py (97%) rename unilabos/{ => app}/web/utils/ros_utils.py (97%) diff --git a/.gitignore b/.gitignore index 5ff8b0f7..333df5bf 100644 --- a/.gitignore +++ b/.gitignore @@ -229,6 +229,6 @@ CATKIN_IGNORE .DS_Store -local_config.py +/**/local_config.py *.graphml \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index df3ec4b8..036215b8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,3 @@ recursive-include unilabos/registry *.yaml +recursive-include unilabos/app/web *.html +recursive-include unilabos/app/web *.css diff --git a/docs/user_guide/configuration.md b/docs/user_guide/configuration.md index 9a8d67cd..049f59ac 100644 --- a/docs/user_guide/configuration.md +++ b/docs/user_guide/configuration.md @@ -46,9 +46,9 @@ class MQConfig: port: int = 8883 # 可以直接提供证书文件路径 - ca_file: str = "/path/to/ca.pem" - cert_file: str = "/path/to/cert.pem" - key_file: str = "/path/to/key.pem" + ca_file: str = "/path/to/ca.pem" # 相对config.py所在目录的路径 + cert_file: str = "/path/to/cert.pem" # 相对config.py所在目录的路径 + key_file: str = "/path/to/key.pem" # 相对config.py所在目录的路径 # 或者直接提供证书内容 ca_content: str = "" @@ -102,6 +102,8 @@ class ROSConfig: unilab --config path/to/your/config.py ``` +如果您不涉及多环境开发,可以在unilabos的安装路径中手动添加local_config.py的文件 + # 启动Uni-Lab python -m unilabos.app.main --config path/to/your/config.py ``` diff --git a/unilabos/app/main.py b/unilabos/app/main.py index 11d790b3..d418c5c1 100644 --- a/unilabos/app/main.py +++ b/unilabos/app/main.py @@ -69,14 +69,17 @@ def main(): args_dict = vars(args) # 加载配置文件 - 这里保持最先加载配置的逻辑 - if args_dict.get("config"): - config_path = args_dict["config"] + config_path = args_dict.get("config") + if config_path: if not os.path.exists(config_path): print_status(f"配置文件 {config_path} 不存在", "error") elif not config_path.endswith(".py"): print_status(f"配置文件 {config_path} 不是Python文件,必须以.py结尾", "error") else: load_config(config_path) + else: + print_status(f"启动 UniLab-OS时,配置文件参数未正确传入 --config '{config_path}' 尝试本地配置...", "warning") + load_config(config_path) # 设置BasicConfig参数 BasicConfig.is_host_mode = not args_dict.get("without_host", False) @@ -92,8 +95,8 @@ def main(): from unilabos.app.mq import mqtt_client from unilabos.registry.registry import build_registry from unilabos.app.backend import start_backend - from unilabos.web import http_client - from unilabos.web import start_server + from unilabos.app.web import http_client + from unilabos.app.web import start_server # 显示启动横幅 print_unilab_banner(args_dict) diff --git a/unilabos/web/__init__.py b/unilabos/app/web/__init__.py similarity index 56% rename from unilabos/web/__init__.py rename to unilabos/app/web/__init__.py index 7872ee0f..3fccdd7f 100644 --- a/unilabos/web/__init__.py +++ b/unilabos/app/web/__init__.py @@ -4,10 +4,10 @@ Web UI 模块 提供了UniLab系统的Web界面功能 """ -from unilabos.web.pages import setup_web_pages -from unilabos.web.server import setup_server, start_server -from unilabos.web.client import http_client -from unilabos.web.api import setup_api_routes +from unilabos.app.web.pages import setup_web_pages +from unilabos.app.web.server import setup_server, start_server +from unilabos.app.web.client import http_client +from unilabos.app.web.api import setup_api_routes __all__ = [ "setup_web_pages", # 设置Web页面 diff --git a/unilabos/web/api.py b/unilabos/app/web/api.py similarity index 98% rename from unilabos/web/api.py rename to unilabos/app/web/api.py index 84169a45..eb18b4dc 100644 --- a/unilabos/web/api.py +++ b/unilabos/app/web/api.py @@ -18,7 +18,7 @@ from unilabos.app.model import ( JobPreintakeFinishReq, JobFinishReq, ) -from unilabos.web.utils.host_utils import get_host_node_info +from unilabos.app.web.utils.host_utils import get_host_node_info # 创建API路由器 api = APIRouter() diff --git a/unilabos/web/client.py b/unilabos/app/web/client.py similarity index 100% rename from unilabos/web/client.py rename to unilabos/app/web/client.py diff --git a/unilabos/web/pages.py b/unilabos/app/web/pages.py similarity index 96% rename from unilabos/web/pages.py rename to unilabos/app/web/pages.py index ecbe84f2..7216a66c 100644 --- a/unilabos/web/pages.py +++ b/unilabos/app/web/pages.py @@ -20,9 +20,9 @@ from unilabos.app.mq import mqtt_client from unilabos.ros.msgs.message_converter import msg_converter_manager from unilabos.utils.log import error from unilabos.utils.type_check import TypeEncoder -from unilabos.web.utils.device_utils import get_registry_info -from unilabos.web.utils.host_utils import get_host_node_info -from unilabos.web.utils.ros_utils import get_ros_node_info, update_ros_node_info +from unilabos.app.web.utils.device_utils import get_registry_info +from unilabos.app.web.utils.host_utils import get_host_node_info +from unilabos.app.web.utils.ros_utils import get_ros_node_info, update_ros_node_info # 设置Jinja2模板环境 template_dir = Path(__file__).parent / "templates" diff --git a/unilabos/web/server.py b/unilabos/app/web/server.py similarity index 96% rename from unilabos/web/server.py rename to unilabos/app/web/server.py index 723db9b4..2a85d107 100644 --- a/unilabos/web/server.py +++ b/unilabos/app/web/server.py @@ -13,8 +13,8 @@ from starlette.responses import Response from unilabos.utils.fastapi.log_adapter import setup_fastapi_logging from unilabos.utils.log import info, error -from unilabos.web.api import setup_api_routes -from unilabos.web.pages import setup_web_pages +from unilabos.app.web.api import setup_api_routes +from unilabos.app.web.pages import setup_web_pages # 创建FastAPI应用 app = FastAPI( diff --git a/unilabos/web/static/styles.css b/unilabos/app/web/static/styles.css similarity index 100% rename from unilabos/web/static/styles.css rename to unilabos/app/web/static/styles.css diff --git a/unilabos/web/templates/base.html b/unilabos/app/web/templates/base.html similarity index 100% rename from unilabos/web/templates/base.html rename to unilabos/app/web/templates/base.html diff --git a/unilabos/web/templates/home.html b/unilabos/app/web/templates/home.html similarity index 100% rename from unilabos/web/templates/home.html rename to unilabos/app/web/templates/home.html diff --git a/unilabos/web/templates/status.html b/unilabos/app/web/templates/status.html similarity index 100% rename from unilabos/web/templates/status.html rename to unilabos/app/web/templates/status.html diff --git a/unilabos/web/utils/__init__.py b/unilabos/app/web/utils/__init__.py similarity index 100% rename from unilabos/web/utils/__init__.py rename to unilabos/app/web/utils/__init__.py diff --git a/unilabos/web/utils/action_utils.py b/unilabos/app/web/utils/action_utils.py similarity index 100% rename from unilabos/web/utils/action_utils.py rename to unilabos/app/web/utils/action_utils.py diff --git a/unilabos/web/utils/device_utils.py b/unilabos/app/web/utils/device_utils.py similarity index 100% rename from unilabos/web/utils/device_utils.py rename to unilabos/app/web/utils/device_utils.py diff --git a/unilabos/web/utils/host_utils.py b/unilabos/app/web/utils/host_utils.py similarity index 97% rename from unilabos/web/utils/host_utils.py rename to unilabos/app/web/utils/host_utils.py index 7d241ae0..0df5e816 100644 --- a/unilabos/web/utils/host_utils.py +++ b/unilabos/app/web/utils/host_utils.py @@ -9,7 +9,7 @@ from typing import Dict, Any from unilabos.config.config import BasicConfig from unilabos.ros.nodes.presets.host_node import HostNode -from unilabos.web.utils.action_utils import get_action_info +from unilabos.app.web.utils.action_utils import get_action_info def get_host_node_info() -> Dict[str, Any]: diff --git a/unilabos/web/utils/ros_utils.py b/unilabos/app/web/utils/ros_utils.py similarity index 97% rename from unilabos/web/utils/ros_utils.py rename to unilabos/app/web/utils/ros_utils.py index 713820fb..01733510 100644 --- a/unilabos/web/utils/ros_utils.py +++ b/unilabos/app/web/utils/ros_utils.py @@ -7,7 +7,7 @@ ROS 工具函数模块 import traceback from typing import Dict, Any -from unilabos.web.utils.action_utils import get_action_info +from unilabos.app.web.utils.action_utils import get_action_info # 存储 ROS 节点信息的全局变量 ros_node_info = {"online_devices": {}, "device_topics": {}, "device_actions": {}} diff --git a/unilabos/config/config.py b/unilabos/config/config.py index 28d787a2..5f117a74 100644 --- a/unilabos/config/config.py +++ b/unilabos/config/config.py @@ -28,9 +28,9 @@ class MQConfig: key_content = "" # 指定 - ca_file = "" - cert_file = "" - key_file = "" + ca_file = "" # 相对config.py所在目录的路径 + cert_file = "" # 相对config.py所在目录的路径 + key_file = "" # 相对config.py所在目录的路径 # OSS上传配置 @@ -97,7 +97,7 @@ def load_config(config_path=None): BasicConfig.config_path = os.path.abspath(os.path.dirname(config_path)) if not os.path.exists(config_path): logger.error(f"配置文件 {config_path} 不存在") - return + exit(1) try: module_name = "lab_" + os.path.basename(config_path).replace(".py", "") @@ -114,10 +114,5 @@ def load_config(config_path=None): traceback.print_exc() exit(1) else: - try: - import unilabos.config.local_config as local_config # type: ignore - - _update_config_from_module(local_config) - logger.info("已加载默认配置 unilabos.config.local_config") - except ImportError: - pass + config_path = os.path.join(os.path.dirname(__file__), "local_config.py") + load_config(config_path) diff --git a/unilabos/registry/device_comms/modbus_ioboard.yaml b/unilabos/registry/device_comms/modbus_ioboard.yaml index 31d33fef..14732410 100644 --- a/unilabos/registry/device_comms/modbus_ioboard.yaml +++ b/unilabos/registry/device_comms/modbus_ioboard.yaml @@ -1,4 +1,5 @@ io_snrd: + description: IO Board with 16 IOs class: module: unilabos.device_comms.SRND_16_IO:SRND_16_IO type: python diff --git a/unilabos/registry/device_comms/serial.yaml b/unilabos/registry/device_comms/serial.yaml index 3a262d8a..3a0d72a5 100644 --- a/unilabos/registry/device_comms/serial.yaml +++ b/unilabos/registry/device_comms/serial.yaml @@ -1,4 +1,5 @@ serial: + description: Serial communication interface, used when sharing same serial port for multiple devices class: module: unilabos.ros.nodes.presets:ROS2SerialNode type: ros2 diff --git a/unilabos/registry/devices/characterization_optic.yaml b/unilabos/registry/devices/characterization_optic.yaml index d1f9cb1e..0164ae4a 100644 --- a/unilabos/registry/devices/characterization_optic.yaml +++ b/unilabos/registry/devices/characterization_optic.yaml @@ -1,5 +1,6 @@ # 光学表征设备:红外、紫外可见、拉曼等 raman_home_made: + description: Raman spectroscopy device class: module: unilabos.devices.raman_uv.home_made_raman:RamanObj type: python diff --git a/unilabos/registry/devices/liquid_handler.yaml b/unilabos/registry/devices/liquid_handler.yaml index 03057bdc..4451ca06 100644 --- a/unilabos/registry/devices/liquid_handler.yaml +++ b/unilabos/registry/devices/liquid_handler.yaml @@ -1,4 +1,5 @@ liquid_handler: + description: Liquid handler device controlled by pylabrobot class: module: pylabrobot.liquid_handling:LiquidHandler type: python diff --git a/unilabos/registry/devices/organic_miscellaneous.yaml b/unilabos/registry/devices/organic_miscellaneous.yaml index ff8cf735..3085efac 100644 --- a/unilabos/registry/devices/organic_miscellaneous.yaml +++ b/unilabos/registry/devices/organic_miscellaneous.yaml @@ -1,4 +1,5 @@ separator.homemade: + description: Separator device with homemade grbl controller class: module: unilabos.devices.separator.homemade_grbl_conductivity:Separator_Controller type: python @@ -39,6 +40,7 @@ separator.homemade: additionalProperties: false rotavap.one: + description: Rotavap device class: module: unilabos.devices.rotavap.rotavap_one:RotavapOne type: python diff --git a/unilabos/registry/devices/pump_and_valve.yaml b/unilabos/registry/devices/pump_and_valve.yaml index d6538b8b..ba35700d 100644 --- a/unilabos/registry/devices/pump_and_valve.yaml +++ b/unilabos/registry/devices/pump_and_valve.yaml @@ -1,4 +1,5 @@ syringe_pump_with_valve.runze: + description: Runze Syringe pump with valve class: module: unilabos.devices.pump_and_valve.runze_backbone:RunzeSyringePump type: python @@ -25,11 +26,13 @@ syringe_pump_with_valve.runze: solenoid_valve.mock: + description: Mock solenoid valve class: module: unilabos.devices.pump_and_valve.solenoid_valve_mock:SolenoidValveMock type: python solenoid_valve: + description: Solenoid valve class: module: unilabos.devices.pump_and_valve.solenoid_valve:SolenoidValve type: python \ No newline at end of file diff --git a/unilabos/registry/devices/robot_agv.yaml b/unilabos/registry/devices/robot_agv.yaml index 384c79f6..a78107ed 100644 --- a/unilabos/registry/devices/robot_agv.yaml +++ b/unilabos/registry/devices/robot_agv.yaml @@ -1,5 +1,6 @@ # 仙工智能底盘(知行使用) agv.SEER: + description: SEER AGV class: module: unilabos.devices.agv.agv_navigator:AgvNavigator type: python diff --git a/unilabos/registry/devices/robot_arm.yaml b/unilabos/registry/devices/robot_arm.yaml index 585b2fa4..ba1e5f32 100644 --- a/unilabos/registry/devices/robot_arm.yaml +++ b/unilabos/registry/devices/robot_arm.yaml @@ -1,4 +1,5 @@ robotic_arm.UR: + description: UR robotic arm class: module: unilabos.devices.agv.ur_arm_task:UrArmTask type: python diff --git a/unilabos/registry/devices/robot_gripper.yaml b/unilabos/registry/devices/robot_gripper.yaml index 3b5a06d5..04ea338b 100644 --- a/unilabos/registry/devices/robot_gripper.yaml +++ b/unilabos/registry/devices/robot_gripper.yaml @@ -1,4 +1,5 @@ gripper.mock: + description: Mock gripper class: module: unilabos.devices.gripper.mock:MockGripper type: python @@ -21,6 +22,7 @@ gripper.mock: gripper.misumi_rz: + description: Misumi RZ gripper class: module: unilabos.devices.motor:Grasp.EleGripper type: python diff --git a/unilabos/registry/devices/robot_linear_motion.yaml b/unilabos/registry/devices/robot_linear_motion.yaml index eead2454..91e253d2 100644 --- a/unilabos/registry/devices/robot_linear_motion.yaml +++ b/unilabos/registry/devices/robot_linear_motion.yaml @@ -1,4 +1,5 @@ linear_motion.grbl: + description: Grbl CNC class: module: unilabos.devices.cnc.grbl_sync:GrblCNC type: python @@ -38,6 +39,7 @@ linear_motion.grbl: motor.iCL42: + description: iCL42 motor class: module: unilabos.devices.motor.iCL42:iCL42Driver type: python diff --git a/unilabos/registry/devices/temperature.yaml b/unilabos/registry/devices/temperature.yaml index ba5d75df..1c01b4e8 100644 --- a/unilabos/registry/devices/temperature.yaml +++ b/unilabos/registry/devices/temperature.yaml @@ -1,4 +1,5 @@ heaterstirrer.dalong: + description: DaLong heater stirrer class: module: unilabos.devices.heaterstirrer.dalong:HeaterStirrer_DaLong type: python @@ -34,6 +35,7 @@ heaterstirrer.dalong: success: success chiller: + description: Chiller class: module: unilabos.devices.temperature.chiller:Chiller type: python @@ -46,6 +48,7 @@ chiller: result: success: success tempsensor: + description: Temperature sensor class: module: unilabos.devices.temperature.sensor_node:TempSensorNode type: python diff --git a/unilabos/registry/devices/vacuum_and_purge.yaml b/unilabos/registry/devices/vacuum_and_purge.yaml index 4efa5a95..4981f2c4 100644 --- a/unilabos/registry/devices/vacuum_and_purge.yaml +++ b/unilabos/registry/devices/vacuum_and_purge.yaml @@ -1,9 +1,11 @@ vacuum_pump.mock: + description: Mock vacuum pump class: module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock type: python gas_source.mock: + description: Mock gas source class: module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock type: python diff --git a/unilabos/registry/devices/work_station.yaml b/unilabos/registry/devices/work_station.yaml index 8688dd66..fadfd5ec 100644 --- a/unilabos/registry/devices/work_station.yaml +++ b/unilabos/registry/devices/work_station.yaml @@ -1,4 +1,5 @@ workstation: + description: Workstation class: module: unilabos.ros.nodes.presets.protocol_node:ROS2ProtocolNode type: ros2 diff --git a/unilabos/registry/registry.py b/unilabos/registry/registry.py index 2ea9bf3b..9c7a95b9 100644 --- a/unilabos/registry/registry.py +++ b/unilabos/registry/registry.py @@ -54,9 +54,9 @@ class Registry: if data: # 为每个资源添加文件路径信息 for resource_id, resource_info in data.items(): - # 添加文件路径 - 使用规范化的完整文件路径 resource_info["file_path"] = str(file.absolute()).replace("\\", "/") - + if "description" not in resource_info: + resource_info["description"] = "" self.resource_type_registry.update(data) logger.debug( f"[UniLab Registry] Resource-{current_resource_number} File-{i+1}/{len(files)} " @@ -112,7 +112,8 @@ class Registry: for device_id, device_config in data.items(): # 添加文件路径信息 - 使用规范化的完整文件路径 device_config["file_path"] = str(file.absolute()).replace("\\", "/") - + if "description" not in device_config: + device_config["description"] = "" if "class" in device_config: # 处理状态类型 if "status_types" in device_config["class"]: diff --git a/unilabos/registry/resources/opentrons/deck.yaml b/unilabos/registry/resources/opentrons/deck.yaml index 77fdc4f2..439da452 100644 --- a/unilabos/registry/resources/opentrons/deck.yaml +++ b/unilabos/registry/resources/opentrons/deck.yaml @@ -1,4 +1,5 @@ OTDeck: + description: Opentrons deck class: module: pylabrobot.resources.opentrons.deck:OTDeck type: pylabrobot \ No newline at end of file diff --git a/unilabos/registry/resources/opentrons/plate_adapters.yaml b/unilabos/registry/resources/opentrons/plate_adapters.yaml index f2304eda..e9c0d5a1 100644 --- a/unilabos/registry/resources/opentrons/plate_adapters.yaml +++ b/unilabos/registry/resources/opentrons/plate_adapters.yaml @@ -1,4 +1,5 @@ Opentrons_96_adapter_Vb: + description: Opentrons 96 adapter Vb class: module: pylabrobot.resources.opentrons.plate_adapters:Opentrons_96_adapter_Vb type: pylabrobot \ No newline at end of file diff --git a/unilabos/registry/resources/opentrons/plates.yaml b/unilabos/registry/resources/opentrons/plates.yaml index a92a0abb..f15da8ed 100644 --- a/unilabos/registry/resources/opentrons/plates.yaml +++ b/unilabos/registry/resources/opentrons/plates.yaml @@ -1,74 +1,89 @@ corning_6_wellplate_16point8ml_flat: + description: Corning 6 wellplate 16.8ml flat class: module: pylabrobot.resources.opentrons.plates:corning_6_wellplate_16point8ml_flat type: pylabrobot corning_12_wellplate_6point9ml_flat: + description: Corning 12 wellplate 6.9ml flat class: module: pylabrobot.resources.opentrons.plates:corning_12_wellplate_6point9ml_flat type: pylabrobot corning_24_wellplate_3point4ml_flat: + description: Corning 24 wellplate 3.4ml flat class: module: pylabrobot.resources.opentrons.plates:corning_24_wellplate_3point4ml_flat type: pylabrobot corning_48_wellplate_1point6ml_flat: + description: Corning 48 wellplate 1.6ml flat class: module: pylabrobot.resources.opentrons.plates:corning_48_wellplate_1point6ml_flat type: pylabrobot corning_96_wellplate_360ul_flat: + description: Corning 96 wellplate 360ul flat class: module: pylabrobot.resources.opentrons.plates:corning_96_wellplate_360ul_flat type: pylabrobot corning_384_wellplate_112ul_flat: + description: Corning 384 wellplate 112ul flat class: module: pylabrobot.resources.opentrons.plates:corning_384_wellplate_112ul_flat type: pylabrobot nest_96_wellplate_2ml_deep: + description: Nest 96 wellplate 2ml deep class: module: pylabrobot.resources.opentrons.plates:nest_96_wellplate_2ml_deep type: pylabrobot nest_96_wellplate_200ul_flat: + description: Nest 96 wellplate 200ul flat class: module: pylabrobot.resources.opentrons.plates:nest_96_wellplate_200ul_flat type: pylabrobot nest_96_wellplate_100ul_pcr_full_skirt: + description: Nest 96 wellplate 100ul pcr full skirt class: module: pylabrobot.resources.opentrons.plates:nest_96_wellplate_100ul_pcr_full_skirt type: pylabrobot appliedbiosystemsmicroamp_384_wellplate_40ul: + description: Applied Biosystems microamp 384 wellplate 40ul class: module: pylabrobot.resources.opentrons.plates:appliedbiosystemsmicroamp_384_wellplate_40ul type: pylabrobot thermoscientificnunc_96_wellplate_1300ul: + description: Thermoscientific Nunc 96 wellplate 1300ul class: module: pylabrobot.resources.opentrons.plates:thermoscientificnunc_96_wellplate_1300ul type: pylabrobot thermoscientificnunc_96_wellplate_2000ul: + description: Thermoscientific Nunc 96 wellplate 2000ul class: module: pylabrobot.resources.opentrons.plates:thermoscientificnunc_96_wellplate_2000ul type: pylabrobot usascientific_96_wellplate_2point4ml_deep: + description: USAScientific 96 wellplate 2.4ml deep class: module: pylabrobot.resources.opentrons.plates:usascientific_96_wellplate_2point4ml_deep type: pylabrobot biorad_96_wellplate_200ul_pcr: + description: BioRad 96 wellplate 200ul pcr class: module: pylabrobot.resources.opentrons.plates:biorad_96_wellplate_200ul_pcr type: pylabrobot biorad_384_wellplate_50ul: + description: BioRad 384 wellplate 50ul class: module: pylabrobot.resources.opentrons.plates:biorad_384_wellplate_50ul type: pylabrobot diff --git a/unilabos/registry/resources/opentrons/reservoirs.yaml b/unilabos/registry/resources/opentrons/reservoirs.yaml index 5bcb092e..f966f0b0 100644 --- a/unilabos/registry/resources/opentrons/reservoirs.yaml +++ b/unilabos/registry/resources/opentrons/reservoirs.yaml @@ -1,29 +1,35 @@ agilent_1_reservoir_290ml: + description: Agilent 1 reservoir 290ml class: module: pylabrobot.resources.opentrons.reserviors:agilent_1_reservoir_290ml type: pylabrobot axygen_1_reservoir_90ml: + description: Axygen 1 reservoir 90ml class: module: pylabrobot.resources.opentrons.reserviors:axygen_1_reservoir_90ml type: pylabrobot nest_12_reservoir_15ml: + description: Nest 12 reservoir 15ml class: module: pylabrobot.resources.opentrons.reserviors:nest_12_reservoir_15ml type: pylabrobot nest_1_reservoir_195ml: + description: Nest 1 reservoir 195ml class: module: pylabrobot.resources.opentrons.reserviors:nest_1_reservoir_195ml type: pylabrobot nest_1_reservoir_290ml: + description: Nest 1 reservoir 290ml class: module: pylabrobot.resources.opentrons.reserviors:nest_1_reservoir_290ml type: pylabrobot usascientific_12_reservoir_22ml: + description: USAScientific 12 reservoir 22ml class: module: pylabrobot.resources.opentrons.reserviors:usascientific_12_reservoir_22ml type: pylabrobot diff --git a/unilabos/registry/resources/opentrons/tip_racks.yaml b/unilabos/registry/resources/opentrons/tip_racks.yaml index 44ef090a..c5292e2c 100644 --- a/unilabos/registry/resources/opentrons/tip_racks.yaml +++ b/unilabos/registry/resources/opentrons/tip_racks.yaml @@ -1,64 +1,77 @@ eppendorf_96_tiprack_1000ul_eptips: + description: Eppendorf 96 tiprack 1000ul eptips class: module: pylabrobot.resources.opentrons.tip_racks:eppendorf_96_tiprack_1000ul_eptips type: pylabrobot tipone_96_tiprack_200ul: + description: TipOne 96 tiprack 200ul class: module: pylabrobot.resources.opentrons.tip_racks:tipone_96_tiprack_200ul type: pylabrobot opentrons_96_tiprack_300ul: + description: Opentrons 96 tiprack 300ul class: module: pylabrobot.resources.opentrons.tip_racks:opentrons_96_tiprack_300ul type: pylabrobot opentrons_96_tiprack_10ul: + description: Opentrons 96 tiprack 10ul class: module: pylabrobot.resources.opentrons.tip_racks:opentrons_96_tiprack_10ul type: pylabrobot opentrons_96_filtertiprack_10ul: + description: Opentrons 96 filtertiprack 10ul class: module: pylabrobot.resources.opentrons.tip_racks:opentrons_96_filtertiprack_10ul type: pylabrobot geb_96_tiprack_10ul: + description: Geb 96 tiprack 10ul class: module: pylabrobot.resources.opentrons.tip_racks:geb_96_tiprack_10ul type: pylabrobot opentrons_96_filtertiprack_200ul: + description: Opentrons 96 filtertiprack 200ul class: module: pylabrobot.resources.opentrons.tip_racks:opentrons_96_filtertiprack_200ul type: pylabrobot eppendorf_96_tiprack_10ul_eptips: + description: Eppendorf 96 tiprack 10ul eptips class: module: pylabrobot.resources.opentrons.tip_racks:eppendorf_96_tiprack_10ul_eptips type: pylabrobot opentrons_96_tiprack_1000ul: + description: Opentrons 96 tiprack 1000ul class: module: pylabrobot.resources.opentrons.tip_racks:opentrons_96_tiprack_1000ul type: pylabrobot opentrons_96_tiprack_20ul: + description: Opentrons 96 tiprack 20ul class: module: pylabrobot.resources.opentrons.tip_racks:opentrons_96_tiprack_20ul type: pylabrobot opentrons_96_filtertiprack_1000ul: + description: Opentrons 96 filtertiprack 1000ul class: module: pylabrobot.resources.opentrons.tip_racks:opentrons_96_filtertiprack_1000ul type: pylabrobot opentrons_96_filtertiprack_20ul: + description: Opentrons 96 filtertiprack 20ul class: module: pylabrobot.resources.opentrons.tip_racks:opentrons_96_filtertiprack_20ul type: pylabrobot geb_96_tiprack_1000ul: + description: Geb 96 tiprack 1000ul class: module: pylabrobot.resources.opentrons.tip_racks:geb_96_tiprack_1000ul type: pylabrobot diff --git a/unilabos/registry/resources/opentrons/tube_racks.yaml b/unilabos/registry/resources/opentrons/tube_racks.yaml index 75081c21..6a33d818 100644 --- a/unilabos/registry/resources/opentrons/tube_racks.yaml +++ b/unilabos/registry/resources/opentrons/tube_racks.yaml @@ -1,99 +1,119 @@ opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap: + description: Opentrons 24 tuberack eppendorf 2ml safelock snapcap class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap type: pylabrobot opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap_acrylic: + description: Opentrons 24 tuberack eppendorf 2ml safelock snapcap acrylic class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap_acrylic type: pylabrobot opentrons_6_tuberack_falcon_50ml_conical: + description: Opentrons 6 tuberack falcon 50ml conical class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_6_tuberack_falcon_50ml_conical type: pylabrobot opentrons_15_tuberack_nest_15ml_conical: + description: Opentrons 15 tuberack nest 15ml conical class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_15_tuberack_nest_15ml_conical type: pylabrobot opentrons_24_tuberack_nest_2ml_screwcap: + description: Opentrons 24 tuberack nest 2ml screwcap class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_24_tuberack_nest_2ml_screwcap type: pylabrobot opentrons_24_tuberack_generic_0point75ml_snapcap_acrylic: + description: Opentrons 24 tuberack generic 0.75ml snapcap acrylic class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_24_tuberack_generic_0point75ml_snapcap_acrylic type: pylabrobot opentrons_10_tuberack_nest_4x50ml_6x15ml_conical: + description: Opentrons 10 tuberack nest 4x50ml 6x15ml conical class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_10_tuberack_nest_4x50ml_6x15ml_conical type: pylabrobot opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical_acrylic: + description: Opentrons 10 tuberack falcon 4x50ml 6x15ml conical acrylic class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical_acrylic type: pylabrobot opentrons_24_tuberack_nest_1point5ml_screwcap: + description: Opentrons 24 tuberack nest 1.5ml screwcap class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_24_tuberack_nest_1point5ml_screwcap type: pylabrobot opentrons_24_tuberack_nest_1point5ml_snapcap: + description: Opentrons 24 tuberack nest 1.5ml snapcap class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_24_tuberack_nest_1point5ml_snapcap type: pylabrobot opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical: + description: Opentrons 10 tuberack falcon 4x50ml 6x15ml conical class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical type: pylabrobot opentrons_24_tuberack_nest_2ml_snapcap: + description: Opentrons 24 tuberack nest 2ml snapcap class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_24_tuberack_nest_2ml_snapcap type: pylabrobot opentrons_24_tuberack_nest_0point5ml_screwcap: + description: Opentrons 24 tuberack nest 0.5ml screwcap class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_24_tuberack_nest_0point5ml_screwcap type: pylabrobot opentrons_24_tuberack_eppendorf_1point5ml_safelock_snapcap: + description: Opentrons 24 tuberack eppendorf 1.5ml safelock snapcap class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_24_tuberack_eppendorf_1point5ml_safelock_snapcap type: pylabrobot opentrons_6_tuberack_nest_50ml_conical: + description: Opentrons 6 tuberack nest 50ml conical class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_6_tuberack_nest_50ml_conical type: pylabrobot opentrons_15_tuberack_falcon_15ml_conical: + description: Opentrons 15 tuberack falcon 15ml conical class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_15_tuberack_falcon_15ml_conical type: pylabrobot opentrons_24_tuberack_generic_2ml_screwcap: + description: Opentrons 24 tuberack generic 2ml screwcap class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_24_tuberack_generic_2ml_screwcap type: pylabrobot opentrons_96_well_aluminum_block: + description: Opentrons 96 well aluminum block class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_96_well_aluminum_block type: pylabrobot opentrons_24_aluminumblock_generic_2ml_screwcap: + description: Opentrons 24 aluminumblock generic 2ml screwcap class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_24_aluminumblock_generic_2ml_screwcap type: pylabrobot opentrons_24_aluminumblock_nest_1point5ml_snapcap: + description: Opentrons 24 aluminumblock nest 1.5ml snapcap class: module: pylabrobot.resources.opentrons.tube_racks:opentrons_24_aluminumblock_nest_1point5ml_snapcap type: pylabrobot From 9fa6b713688c7a8fedf9b58135297614cefdccae Mon Sep 17 00:00:00 2001 From: wznln <18435084+Xuwznln@users.noreply.github.com> Date: Mon, 21 Apr 2025 15:13:17 +0800 Subject: [PATCH 2/7] fix: allow empty mqtt file config --- unilabos/config/config.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/unilabos/config/config.py b/unilabos/config/config.py index 5f117a74..5c1f7b9c 100644 --- a/unilabos/config/config.py +++ b/unilabos/config/config.py @@ -75,20 +75,29 @@ def _update_config_from_module(module): # 需要先判断是否为相对路径 if MQConfig.ca_file.startswith("."): MQConfig.ca_file = os.path.join(BasicConfig.config_path, MQConfig.ca_file) - with open(MQConfig.ca_file, "r", encoding="utf-8") as f: - MQConfig.ca_content = f.read() + if len(MQConfig.ca_file) != 0: + 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 MQConfig.cert_file.startswith("."): MQConfig.cert_file = os.path.join(BasicConfig.config_path, MQConfig.cert_file) - with open(MQConfig.cert_file, "r", encoding="utf-8") as f: - MQConfig.cert_content = f.read() + if len(MQConfig.ca_file) != 0: + 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 MQConfig.key_file.startswith("."): MQConfig.key_file = os.path.join(BasicConfig.config_path, MQConfig.key_file) - with open(MQConfig.key_file, "r", encoding="utf-8") as f: - MQConfig.key_content = f.read() + if len(MQConfig.ca_file) != 0: + 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): From a4fd428dc3e665f61242578264d9342dd91cd90d Mon Sep 17 00:00:00 2001 From: wznln <18435084+Xuwznln@users.noreply.github.com> Date: Mon, 21 Apr 2025 15:37:30 +0800 Subject: [PATCH 3/7] update README.md --- README.md | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 4658432a..b3256575 100644 --- a/README.md +++ b/README.md @@ -25,21 +25,6 @@ Uni-Lab 操作系统是一个用于实验室自动化的综合平台,旨在连 - [在线文档](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环境 @@ -52,17 +37,30 @@ mamba env create -f unilabos-[YOUR_OS].yaml mamba activate unilab # 或更新现有环境 +# 其中 `[YOUR_OS]` 可以是 `win64`, `linux-64`, `osx-64`, 或 `osx-arm64`。 conda env update --file unilabos-[YOUR_OS].yml -n 环境名 # 现阶段,需要安装 `unilabos_msgs` 包 # 可以前往 Release 页面下载系统对应的包进行安装 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) From 136bb1ded01cc4cc67e6fc8d01e33c1ccd663e72 Mon Sep 17 00:00:00 2001 From: wznln <18435084+Xuwznln@users.noreply.github.com> Date: Wed, 23 Apr 2025 11:53:34 +0800 Subject: [PATCH 4/7] fix: skip when adding resource error --- unilabos/ros/nodes/presets/host_node.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/unilabos/ros/nodes/presets/host_node.py b/unilabos/ros/nodes/presets/host_node.py index d5280a42..34132b35 100644 --- a/unilabos/ros/nodes/presets/host_node.py +++ b/unilabos/ros/nodes/presets/host_node.py @@ -1,6 +1,7 @@ import copy import threading import time +import traceback import uuid from typing import Optional, Dict, Any, List, ClassVar, Set @@ -132,10 +133,14 @@ class HostNode(BaseROS2DeviceNode): controller_config["update_rate"] = update_rate self.initialize_controller(controller_id, controller_config) - for bridge in self.bridges: - if hasattr(bridge, "resource_add"): - self.lab_logger().info("[Host Node-Resource] Adding resources to bridge.") - bridge.resource_add(add_schema(resources_config)) + try: + for bridge in self.bridges: + if hasattr(bridge, "resource_add"): + 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( From 0cd11fa46bd20ae8dcef4588b2cc0894d6762258 Mon Sep 17 00:00:00 2001 From: wznln <18435084+Xuwznln@users.noreply.github.com> Date: Wed, 23 Apr 2025 14:35:18 +0800 Subject: [PATCH 5/7] fix: sync task no log output fix: registry typo --- unilabos/registry/device_comms/serial.yaml | 2 +- .../devices/organic_miscellaneous.yaml | 2 +- unilabos/registry/devices/pump_and_valve.yaml | 4 ++ unilabos/ros/nodes/base_device_node.py | 9 ++++ unilabos/ros/nodes/presets/protocol_node.py | 43 +++++++++++-------- unilabos/ros/nodes/presets/serial_node.py | 1 + 6 files changed, 40 insertions(+), 21 deletions(-) diff --git a/unilabos/registry/device_comms/serial.yaml b/unilabos/registry/device_comms/serial.yaml index 3a0d72a5..86cf7e50 100644 --- a/unilabos/registry/device_comms/serial.yaml +++ b/unilabos/registry/device_comms/serial.yaml @@ -1,7 +1,7 @@ serial: description: Serial communication interface, used when sharing same serial port for multiple devices class: - module: unilabos.ros.nodes.presets:ROS2SerialNode + module: unilabos.ros.nodes.presets.serial_node:ROS2SerialNode type: ros2 schema: properties: {} \ No newline at end of file diff --git a/unilabos/registry/devices/organic_miscellaneous.yaml b/unilabos/registry/devices/organic_miscellaneous.yaml index 3085efac..a3f6f0e3 100644 --- a/unilabos/registry/devices/organic_miscellaneous.yaml +++ b/unilabos/registry/devices/organic_miscellaneous.yaml @@ -1,7 +1,7 @@ separator.homemade: description: Separator device with homemade grbl controller class: - module: unilabos.devices.separator.homemade_grbl_conductivity:Separator_Controller + module: unilabos.devices.separator.homemade_grbl_conductivity:SeparatorController type: python status_types: sensordata: Float64 diff --git a/unilabos/registry/devices/pump_and_valve.yaml b/unilabos/registry/devices/pump_and_valve.yaml index ba35700d..5fdd8606 100644 --- a/unilabos/registry/devices/pump_and_valve.yaml +++ b/unilabos/registry/devices/pump_and_valve.yaml @@ -3,6 +3,10 @@ syringe_pump_with_valve.runze: class: module: unilabos.devices.pump_and_valve.runze_backbone:RunzeSyringePump type: python + hardware_interface: + name: hardware_interface + read: send_command + write: send_command schema: type: object properties: diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index 1424b0c6..de2c5e15 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -430,6 +430,15 @@ class BaseROS2DeviceNode(Node, Generic[T]): self.lab_logger().info(f"同步执行动作 {ACTION}") 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"] feedback_msg_types = action_type.Feedback.get_fields_and_field_types() result_msg_types = action_type.Result.get_fields_and_field_types() diff --git a/unilabos/ros/nodes/presets/protocol_node.py b/unilabos/ros/nodes/presets/protocol_node.py index 323f6880..c4dfd083 100644 --- a/unilabos/ros/nodes/presets/protocol_node.py +++ b/unilabos/ros/nodes/presets/protocol_node.py @@ -20,7 +20,7 @@ from unilabos.ros.msgs.message_converter import ( convert_from_ros_msg, 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): @@ -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(): if device_config.get("type", "device") != "device": self.lab_logger().debug(f"[Protocol Node] Skipping type {device_config['type']} {device_id} already existed, skipping.") 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: continue 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 # 设置硬件接口代理 - if d and hasattr(d, "_hardware_interface"): + if d: if ( - hasattr(d, d._hardware_interface["name"]) - and hasattr(d, d._hardware_interface["write"]) - and (d._hardware_interface["read"] is None or hasattr(d, d._hardware_interface["read"])) + hasattr(d.driver_instance, d.ros_node_instance._hardware_interface["name"]) + and hasattr(d.driver_instance, d.ros_node_instance._hardware_interface["write"]) + 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"]) - read = d._hardware_interface.get("read", None) - write = d._hardware_interface.get("write", None) + name = getattr(d.driver_instance, d.ros_node_instance._hardware_interface["name"]) + read = d.ros_node_instance._hardware_interface.get("read", None) + write = d.ros_node_instance._hardware_interface.get("write", None) # 如果硬件接口是字符串,通过通信设备提供 - if isinstance(name, str) and communication_node_id in self.sub_devices: - self._setup_hardware_proxy(d, self.sub_devices[communication_node_id], read, write) + if isinstance(name, str) and name in self.sub_devices: + self._setup_hardware_proxy(d, self.sub_devices[name], read, write) 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", [])] - write_func = getattr(communication_device, communication_device._hardware_interface["write"]) - read_func = getattr(communication_device, communication_device._hardware_interface["read"]) + 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.ros_node_instance, communication_device.ros_node_instance._hardware_interface["write"]) + read_func = getattr(communication_device.ros_node_instance, communication_device.ros_node_instance._hardware_interface["read"]) def _read(): return read_func(*extra_info) @@ -247,9 +252,9 @@ class ROS2ProtocolNode(BaseROS2DeviceNode): return write_func(*extra_info, command) if read_method: - setattr(device, read_method, _read) + setattr(device.driver_instance, read_method, _read) if write_method: - setattr(device, write_method, _write) + setattr(device.driver_instance, write_method, _write) async def _update_resources(self, goal, protocol_kwargs): diff --git a/unilabos/ros/nodes/presets/serial_node.py b/unilabos/ros/nodes/presets/serial_node.py index 6f01a07a..ac9cd59e 100644 --- a/unilabos/ros/nodes/presets/serial_node.py +++ b/unilabos/ros/nodes/presets/serial_node.py @@ -21,6 +21,7 @@ class ROS2SerialNode(BaseROS2DeviceNode): self.hardware_interface = Serial(baudrate=baudrate, port=port) 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 # 初始化BaseROS2DeviceNode,使用自身作为driver_instance From 2f69480f921235a07e9c615fc8c13433e5213057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E4=BF=8A=E6=9D=B0?= <1800011822@pku.edu.cn> Date: Wed, 23 Apr 2025 14:53:29 +0800 Subject: [PATCH 6/7] unit convert --- unilabos/compile/clean_protocol.py | 4 ++-- unilabos/compile/evaporate_protocol.py | 4 ++-- unilabos/compile/pump_protocol.py | 16 +++++++------- unilabos/compile/separate_protocol.py | 26 +++++++++++------------ unilabos/ros/nodes/base_device_node.py | 12 ++++++++--- unilabos/ros/nodes/presets/serial_node.py | 10 ++++----- 6 files changed, 39 insertions(+), 33 deletions(-) diff --git a/unilabos/compile/clean_protocol.py b/unilabos/compile/clean_protocol.py index b2ab1414..655ce217 100644 --- a/unilabos/compile/clean_protocol.py +++ b/unilabos/compile/clean_protocol.py @@ -6,7 +6,7 @@ def generate_clean_protocol( G: nx.DiGraph, vessel: str, # Vessel to clean. 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. repeats: int = 1, # Optional. Number of cleaning cycles to perform. ) -> list[dict]: @@ -27,7 +27,7 @@ def generate_clean_protocol( from_vessel = f"flask_{solvent}" waste_vessel = f"waste_workup" - transfer_flowrate = flowrate = 2500.0 + transfer_flowrate = flowrate = 2.5 # 生成泵操作的动作序列 for i in range(repeats): diff --git a/unilabos/compile/evaporate_protocol.py b/unilabos/compile/evaporate_protocol.py index 8c729666..15af5e11 100644 --- a/unilabos/compile/evaporate_protocol.py +++ b/unilabos/compile/evaporate_protocol.py @@ -24,8 +24,8 @@ def generate_evaporate_protocol( # 生成泵操作的动作序列 pump_action_sequence = [] - reactor_volume = 500000.0 - transfer_flowrate = flowrate = 2500.0 + reactor_volume = 500.0 + transfer_flowrate = flowrate = 2.5 # 开启冷凝器 pump_action_sequence.append({ diff --git a/unilabos/compile/pump_protocol.py b/unilabos/compile/pump_protocol.py index 9b4c2884..60670286 100644 --- a/unilabos/compile/pump_protocol.py +++ b/unilabos/compile/pump_protocol.py @@ -7,7 +7,7 @@ def generate_pump_protocol( from_vessel: str, to_vessel: str, volume: float, - flowrate: float = 500.0, + flowrate: float = 0.5, transfer_flowrate: float = 0, ) -> list[dict]: """ @@ -141,11 +141,11 @@ def generate_pump_protocol_with_rinsing( time: float = 0, viscous: bool = False, rinsing_solvent: str = "air", - rinsing_volume: float = 5000.0, + rinsing_volume: float = 5.0, rinsing_repeats: int = 2, solid: bool = False, - flowrate: float = 2500.0, - transfer_flowrate: float = 500.0, + flowrate: float = 2.5, + transfer_flowrate: float = 0.5, ) -> 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. @@ -159,11 +159,11 @@ def generate_pump_protocol_with_rinsing( 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). 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). 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时的流速 - transfer_flowrate (float, optional): The flow rate for the transfer action (default is 500.0). 泵骨架中转移流速(若不指定,默认与注入流速相同) + 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 0.5). 泵骨架中转移流速(若不指定,默认与注入流速相同) Returns: 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. 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" waste_vessel = f"waste_workup" diff --git a/unilabos/compile/separate_protocol.py b/unilabos/compile/separate_protocol.py index 0ba0d1c8..cbb028cb 100644 --- a/unilabos/compile/separate_protocol.py +++ b/unilabos/compile/separate_protocol.py @@ -11,7 +11,7 @@ def generate_separate_protocol( to_vessel: str, # Vessel to send product 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_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'. repeats: int = 1, # Optional. Number of separations to perform. 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 = [] - reactor_volume = 500000.0 + reactor_volume = 500.0 waste_vessel = waste_phase_to_vessel # TODO:通过物料管理系统找到溶剂的容器 @@ -46,7 +46,7 @@ def generate_separate_protocol( separator_controller = f"{separation_vessel}_controller" separation_vessel_bottom = f"flask_{separation_vessel}" - transfer_flowrate = flowrate = 2500.0 + transfer_flowrate = flowrate = 2.5 if from_vessel != separation_vessel: pump_action_sequence.append( @@ -140,8 +140,8 @@ def generate_separate_protocol( "action_kwargs": { "from_vessel": separation_vessel_bottom, "to_vessel": to_vessel, - "volume": 250000.0, - "time": 250000.0 / flowrate, + "volume": 250.0, + "time": 250.0 / flowrate, # "transfer_flowrate": transfer_flowrate, } } @@ -164,8 +164,8 @@ def generate_separate_protocol( "action_kwargs": { "from_vessel": separation_vessel_bottom, "to_vessel": waste_vessel, - "volume": 250000.0, - "time": 250000.0 / flowrate, + "volume": 250.0, + "time": 250.0 / flowrate, # "transfer_flowrate": transfer_flowrate, } } @@ -179,8 +179,8 @@ def generate_separate_protocol( "action_kwargs": { "from_vessel": separation_vessel_bottom, "to_vessel": waste_vessel, - "volume": 250000.0, - "time": 250000.0 / flowrate, + "volume": 250.0, + "time": 250.0 / flowrate, # "transfer_flowrate": transfer_flowrate, } } @@ -203,8 +203,8 @@ def generate_separate_protocol( "action_kwargs": { "from_vessel": separation_vessel_bottom, "to_vessel": to_vessel, - "volume": 250000.0, - "time": 250000.0 / flowrate, + "volume": 250.0, + "time": 250.0 / flowrate, # "transfer_flowrate": transfer_flowrate, } } @@ -221,8 +221,8 @@ def generate_separate_protocol( "action_kwargs": { "from_vessel": to_vessel, "to_vessel": separation_vessel, - "volume": 250000.0, - "time": 250000.0 / flowrate, + "volume": 250.0, + "time": 250.0 / flowrate, # "transfer_flowrate": transfer_flowrate, } } diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index de2c5e15..5a10b0d1 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -342,9 +342,15 @@ class BaseROS2DeviceNode(Node, Generic[T]): else: return getattr(self.driver_instance, attr_name) except AttributeError as ex: - self.lab_logger().error( - f"publish error, {str(type(self.driver_instance))[8:-2]} has no attribute '{attr_name}'" - ) + if ex.args[0].startswith(f"AttributeError: '{self.driver_instance.__class__.__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, attr_name, get_device_attr, msg_type, initial_period, self._print_publish diff --git a/unilabos/ros/nodes/presets/serial_node.py b/unilabos/ros/nodes/presets/serial_node.py index ac9cd59e..545682bd 100644 --- a/unilabos/ros/nodes/presets/serial_node.py +++ b/unilabos/ros/nodes/presets/serial_node.py @@ -47,7 +47,7 @@ class ROS2SerialNode(BaseROS2DeviceNode): self.lab_logger().info(f"【ROS2SerialNode.__init__】创建串口写入服务: serialwrite") 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: if self._closing: self.lab_logger().error(f"【ROS2SerialNode.send_command】设备正在关闭,无法发送命令") @@ -59,23 +59,23 @@ class ROS2SerialNode(BaseROS2DeviceNode): response = self.hardware_interface.write(full_command_data) # time.sleep(0.05) 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 def read_data(self): - self.lab_logger().debug(f"【ROS2SerialNode.read_data】读取数据") + # self.lab_logger().debug(f"【ROS2SerialNode.read_data】读取数据") with self._query_lock: if self._closing: self.lab_logger().error(f"【ROS2SerialNode.read_data】设备正在关闭,无法读取数据") raise RuntimeError data = self.hardware_interface.read_until(b"\n") result = self._receive(data) - self.lab_logger().debug(f"【ROS2SerialNode.read_data】读取到数据: {result}") + # self.lab_logger().debug(f"【ROS2SerialNode.read_data】读取到数据: {result}") return result def _receive(self, data: bytes): 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 def handle_serial_request(self, request, response): From f476b4098324633d106d9b26645e30d16260a82e Mon Sep 17 00:00:00 2001 From: wznln <18435084+Xuwznln@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:04:08 +0800 Subject: [PATCH 7/7] fix: unit conversion feat: better attr publisher log --- unilabos/compile/clean_protocol.py | 4 ++-- unilabos/compile/evaporate_protocol.py | 4 ++-- unilabos/compile/pump_protocol.py | 16 +++++++------- unilabos/compile/separate_protocol.py | 26 +++++++++++------------ unilabos/ros/nodes/base_device_node.py | 12 ++++++++--- unilabos/ros/nodes/presets/serial_node.py | 10 ++++----- 6 files changed, 39 insertions(+), 33 deletions(-) diff --git a/unilabos/compile/clean_protocol.py b/unilabos/compile/clean_protocol.py index b2ab1414..655ce217 100644 --- a/unilabos/compile/clean_protocol.py +++ b/unilabos/compile/clean_protocol.py @@ -6,7 +6,7 @@ def generate_clean_protocol( G: nx.DiGraph, vessel: str, # Vessel to clean. 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. repeats: int = 1, # Optional. Number of cleaning cycles to perform. ) -> list[dict]: @@ -27,7 +27,7 @@ def generate_clean_protocol( from_vessel = f"flask_{solvent}" waste_vessel = f"waste_workup" - transfer_flowrate = flowrate = 2500.0 + transfer_flowrate = flowrate = 2.5 # 生成泵操作的动作序列 for i in range(repeats): diff --git a/unilabos/compile/evaporate_protocol.py b/unilabos/compile/evaporate_protocol.py index 8c729666..15af5e11 100644 --- a/unilabos/compile/evaporate_protocol.py +++ b/unilabos/compile/evaporate_protocol.py @@ -24,8 +24,8 @@ def generate_evaporate_protocol( # 生成泵操作的动作序列 pump_action_sequence = [] - reactor_volume = 500000.0 - transfer_flowrate = flowrate = 2500.0 + reactor_volume = 500.0 + transfer_flowrate = flowrate = 2.5 # 开启冷凝器 pump_action_sequence.append({ diff --git a/unilabos/compile/pump_protocol.py b/unilabos/compile/pump_protocol.py index 9b4c2884..60670286 100644 --- a/unilabos/compile/pump_protocol.py +++ b/unilabos/compile/pump_protocol.py @@ -7,7 +7,7 @@ def generate_pump_protocol( from_vessel: str, to_vessel: str, volume: float, - flowrate: float = 500.0, + flowrate: float = 0.5, transfer_flowrate: float = 0, ) -> list[dict]: """ @@ -141,11 +141,11 @@ def generate_pump_protocol_with_rinsing( time: float = 0, viscous: bool = False, rinsing_solvent: str = "air", - rinsing_volume: float = 5000.0, + rinsing_volume: float = 5.0, rinsing_repeats: int = 2, solid: bool = False, - flowrate: float = 2500.0, - transfer_flowrate: float = 500.0, + flowrate: float = 2.5, + transfer_flowrate: float = 0.5, ) -> 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. @@ -159,11 +159,11 @@ def generate_pump_protocol_with_rinsing( 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). 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). 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时的流速 - transfer_flowrate (float, optional): The flow rate for the transfer action (default is 500.0). 泵骨架中转移流速(若不指定,默认与注入流速相同) + 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 0.5). 泵骨架中转移流速(若不指定,默认与注入流速相同) Returns: 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. 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" waste_vessel = f"waste_workup" diff --git a/unilabos/compile/separate_protocol.py b/unilabos/compile/separate_protocol.py index 0ba0d1c8..cbb028cb 100644 --- a/unilabos/compile/separate_protocol.py +++ b/unilabos/compile/separate_protocol.py @@ -11,7 +11,7 @@ def generate_separate_protocol( to_vessel: str, # Vessel to send product 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_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'. repeats: int = 1, # Optional. Number of separations to perform. 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 = [] - reactor_volume = 500000.0 + reactor_volume = 500.0 waste_vessel = waste_phase_to_vessel # TODO:通过物料管理系统找到溶剂的容器 @@ -46,7 +46,7 @@ def generate_separate_protocol( separator_controller = f"{separation_vessel}_controller" separation_vessel_bottom = f"flask_{separation_vessel}" - transfer_flowrate = flowrate = 2500.0 + transfer_flowrate = flowrate = 2.5 if from_vessel != separation_vessel: pump_action_sequence.append( @@ -140,8 +140,8 @@ def generate_separate_protocol( "action_kwargs": { "from_vessel": separation_vessel_bottom, "to_vessel": to_vessel, - "volume": 250000.0, - "time": 250000.0 / flowrate, + "volume": 250.0, + "time": 250.0 / flowrate, # "transfer_flowrate": transfer_flowrate, } } @@ -164,8 +164,8 @@ def generate_separate_protocol( "action_kwargs": { "from_vessel": separation_vessel_bottom, "to_vessel": waste_vessel, - "volume": 250000.0, - "time": 250000.0 / flowrate, + "volume": 250.0, + "time": 250.0 / flowrate, # "transfer_flowrate": transfer_flowrate, } } @@ -179,8 +179,8 @@ def generate_separate_protocol( "action_kwargs": { "from_vessel": separation_vessel_bottom, "to_vessel": waste_vessel, - "volume": 250000.0, - "time": 250000.0 / flowrate, + "volume": 250.0, + "time": 250.0 / flowrate, # "transfer_flowrate": transfer_flowrate, } } @@ -203,8 +203,8 @@ def generate_separate_protocol( "action_kwargs": { "from_vessel": separation_vessel_bottom, "to_vessel": to_vessel, - "volume": 250000.0, - "time": 250000.0 / flowrate, + "volume": 250.0, + "time": 250.0 / flowrate, # "transfer_flowrate": transfer_flowrate, } } @@ -221,8 +221,8 @@ def generate_separate_protocol( "action_kwargs": { "from_vessel": to_vessel, "to_vessel": separation_vessel, - "volume": 250000.0, - "time": 250000.0 / flowrate, + "volume": 250.0, + "time": 250.0 / flowrate, # "transfer_flowrate": transfer_flowrate, } } diff --git a/unilabos/ros/nodes/base_device_node.py b/unilabos/ros/nodes/base_device_node.py index de2c5e15..0ff03a68 100644 --- a/unilabos/ros/nodes/base_device_node.py +++ b/unilabos/ros/nodes/base_device_node.py @@ -342,9 +342,15 @@ class BaseROS2DeviceNode(Node, Generic[T]): else: return getattr(self.driver_instance, attr_name) except AttributeError as ex: - self.lab_logger().error( - f"publish error, {str(type(self.driver_instance))[8:-2]} has no attribute '{attr_name}'" - ) + if ex.args[0].startswith(f"AttributeError: '{self.driver_instance.__class__.__name__}' object"): + 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, attr_name, get_device_attr, msg_type, initial_period, self._print_publish diff --git a/unilabos/ros/nodes/presets/serial_node.py b/unilabos/ros/nodes/presets/serial_node.py index ac9cd59e..545682bd 100644 --- a/unilabos/ros/nodes/presets/serial_node.py +++ b/unilabos/ros/nodes/presets/serial_node.py @@ -47,7 +47,7 @@ class ROS2SerialNode(BaseROS2DeviceNode): self.lab_logger().info(f"【ROS2SerialNode.__init__】创建串口写入服务: serialwrite") 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: if self._closing: self.lab_logger().error(f"【ROS2SerialNode.send_command】设备正在关闭,无法发送命令") @@ -59,23 +59,23 @@ class ROS2SerialNode(BaseROS2DeviceNode): response = self.hardware_interface.write(full_command_data) # time.sleep(0.05) 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 def read_data(self): - self.lab_logger().debug(f"【ROS2SerialNode.read_data】读取数据") + # self.lab_logger().debug(f"【ROS2SerialNode.read_data】读取数据") with self._query_lock: if self._closing: self.lab_logger().error(f"【ROS2SerialNode.read_data】设备正在关闭,无法读取数据") raise RuntimeError data = self.hardware_interface.read_until(b"\n") result = self._receive(data) - self.lab_logger().debug(f"【ROS2SerialNode.read_data】读取到数据: {result}") + # self.lab_logger().debug(f"【ROS2SerialNode.read_data】读取到数据: {result}") return result def _receive(self, data: bytes): 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 def handle_serial_request(self, request, response):