diff --git a/unilabos/app/main.py b/unilabos/app/main.py index 99d733dd..8de9a75f 100644 --- a/unilabos/app/main.py +++ b/unilabos/app/main.py @@ -12,6 +12,15 @@ from typing import Dict, Any, List import networkx as nx import yaml +# Windows 中文系统 stdout 默认 GBK,无法编码 banner / emoji 日志中的 Unicode 字符 +# 强制 stdout/stderr 用 UTF-8,避免 print 触发 UnicodeEncodeError 导致进程崩溃 +if sys.platform == "win32": + for _stream in (sys.stdout, sys.stderr): + try: + _stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[attr-defined] + except (AttributeError, OSError): + pass + # 首先添加项目根目录到路径 current_dir = os.path.dirname(os.path.abspath(__file__)) unilabos_dir = os.path.dirname(os.path.dirname(current_dir)) diff --git a/unilabos/devices/virtual/virtual_multiway_valve.py b/unilabos/devices/virtual/virtual_multiway_valve.py index 1512f33d..b6a95ddf 100644 --- a/unilabos/devices/virtual/virtual_multiway_valve.py +++ b/unilabos/devices/virtual/virtual_multiway_valve.py @@ -2,6 +2,8 @@ import time import logging from typing import Union, Dict, Optional +from unilabos.registry.decorators import topic_config + class VirtualMultiwayValve: """ @@ -41,13 +43,11 @@ class VirtualMultiwayValve: def target_position(self) -> int: return self._target_position - def get_current_position(self) -> int: - """获取当前阀门位置 📍""" - return self._current_position - - def get_current_port(self) -> str: - """获取当前连接的端口名称 🔌""" - return self._current_position + @property + @topic_config() + def current_port(self) -> str: + """当前连接的端口名称 🔌""" + return self.port def set_position(self, command: Union[int, str]): """ @@ -169,12 +169,14 @@ class VirtualMultiwayValve: self._status = "Idle" self._valve_state = "Closed" - close_msg = f"🔒 阀门已关闭,保持在位置 {self._current_position} ({self.get_current_port()})" + close_msg = f"🔒 阀门已关闭,保持在位置 {self._current_position} ({self.port})" self.logger.info(close_msg) return close_msg - def get_valve_position(self) -> int: - """获取阀门位置 - 兼容性方法 📍""" + @property + @topic_config() + def valve_position(self) -> int: + """阀门位置 📍""" return self._current_position def set_valve_position(self, command: Union[int, str]): @@ -229,19 +231,16 @@ class VirtualMultiwayValve: self.logger.info(f"🔄 从端口 {self._current_position} 切换到泵位置...") return self.set_to_pump_position() - def get_flow_path(self) -> str: - """获取当前流路路径描述 🌊""" - current_port = self.get_current_port() + @property + @topic_config() + def flow_path(self) -> str: + """当前流路路径描述 🌊""" if self._current_position == 0: - flow_path = f"🚰 转移泵已连接 (位置 {self._current_position})" - else: - flow_path = f"🔌 端口 {self._current_position} 已连接 ({current_port})" - - # 删除debug日志:self.logger.debug(f"🌊 当前流路: {flow_path}") - return flow_path + return f"🚰 转移泵已连接 (位置 {self._current_position})" + return f"🔌 端口 {self._current_position} 已连接 ({self.current_port})" def __str__(self): - current_port = self.get_current_port() + current_port = self.current_port status_emoji = "✅" if self._status == "Idle" else "🔄" if self._status == "Busy" else "❌" return f"🔄 VirtualMultiwayValve({status_emoji} 位置: {self._current_position}/{self.max_positions}, 端口: {current_port}, 状态: {self._status})" @@ -253,7 +252,7 @@ if __name__ == "__main__": print("🔄 === 虚拟九通阀门测试 === ✨") print(f"🏠 初始状态: {valve}") - print(f"🌊 当前流路: {valve.get_flow_path()}") + print(f"🌊 当前流路: {valve.flow_path}") # 切换到试剂瓶1(1号位) print(f"\n🔌 切换到1号位: {valve.set_position(1)}") diff --git a/unilabos/devices/virtual/virtual_stirrer.py b/unilabos/devices/virtual/virtual_stirrer.py index 8e95617f..5bd4b9e1 100644 --- a/unilabos/devices/virtual/virtual_stirrer.py +++ b/unilabos/devices/virtual/virtual_stirrer.py @@ -3,6 +3,7 @@ import logging import time as time_module from typing import Dict, Any +from unilabos.registry.decorators import topic_config from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode class VirtualStirrer: @@ -314,9 +315,11 @@ class VirtualStirrer: def min_speed(self) -> float: return self._min_speed - def get_device_info(self) -> Dict[str, Any]: - """获取设备状态信息 📊""" - info = { + @property + @topic_config() + def device_info(self) -> Dict[str, Any]: + """设备状态快照信息 📊""" + return { "device_id": self.device_id, "status": self.status, "operation_mode": self.operation_mode, @@ -325,12 +328,9 @@ class VirtualStirrer: "is_stirring": self.is_stirring, "remaining_time": self.remaining_time, "max_speed": self._max_speed, - "min_speed": self._min_speed + "min_speed": self._min_speed, } - - # self.logger.debug(f"📊 设备信息: 模式={self.operation_mode}, 速度={self.current_speed} RPM, 搅拌={self.is_stirring}") - return info - + def __str__(self): status_emoji = "✅" if self.operation_mode == "Idle" else "🌪️" if self.operation_mode == "Stirring" else "🛑" if self.operation_mode == "Settling" else "❌" return f"🌪️ VirtualStirrer({status_emoji} {self.device_id}: {self.operation_mode}, {self.current_speed} RPM)" \ No newline at end of file diff --git a/unilabos/devices/virtual/virtual_transferpump.py b/unilabos/devices/virtual/virtual_transferpump.py index 2d3c9d8b..f7b24f18 100644 --- a/unilabos/devices/virtual/virtual_transferpump.py +++ b/unilabos/devices/virtual/virtual_transferpump.py @@ -4,6 +4,7 @@ from enum import Enum from typing import Union, Optional import logging +from unilabos.registry.decorators import topic_config from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode @@ -385,8 +386,10 @@ class VirtualTransferPump: """获取当前体积""" return self._current_volume - def get_remaining_capacity(self) -> float: - """获取剩余容量""" + @property + @topic_config() + def remaining_capacity(self) -> float: + """剩余容量 (ml)""" return self.max_volume - self._current_volume def is_empty(self) -> bool: diff --git a/unilabos/registry/devices/virtual_device.yaml b/unilabos/registry/devices/virtual_device.yaml index 0fce3824..b828c6d2 100644 --- a/unilabos/registry/devices/virtual_device.yaml +++ b/unilabos/registry/devices/virtual_device.yaml @@ -3960,6 +3960,14 @@ virtual_separator: io_type: source label: bottom_phase_out side: SOUTH + - data_key: top_outlet + data_source: executor + data_type: fluid + description: 上相(轻相)液体输出口 + handler_key: topphaseout + io_type: source + label: top_phase_out + side: NORTH - data_key: mechanical_port data_source: handle data_type: mechanical diff --git a/unilabos/utils/environment_check.py b/unilabos/utils/environment_check.py index 366694be..18b5f158 100644 --- a/unilabos/utils/environment_check.py +++ b/unilabos/utils/environment_check.py @@ -188,7 +188,13 @@ class EnvironmentChecker: "crcmod": "crcmod-plus", } - self.special_packages = {"pylabrobot": "git+https://github.com/Xuwznln/pylabrobot.git"} + # 中文 locale 下走 Gitee 镜像,规避 GitHub 拉取失败 + pylabrobot_url = ( + "git+https://gitee.com/xuwznln/pylabrobot.git" + if _is_chinese_locale() + else "git+https://github.com/Xuwznln/pylabrobot.git" + ) + self.special_packages = {"pylabrobot": pylabrobot_url} self.version_requirements = { "msgcenterpy": "0.1.8",