4 Commits

Author SHA1 Message Date
Xuwznln
13a6795657 Update organic syn station. 2025-12-15 02:34:36 +08:00
Xianwei Qi
53219d8b04 Update docs
update "laiyu" missing init file.

fix "laiyu" missing init file.

fix "🐛 fix"

🐛 fix: config file is overwrited by default args even if not be set.

mix

修改了mix,仿真流程报错问题
2025-12-14 13:13:21 +08:00
Xuwznln
b1cdef9185 update version to 0.10.12 2025-12-04 18:47:16 +08:00
Xuwznln
9854ed8c9c fix ros2 future
print all logs to file
fix resource dict dump error
2025-12-04 18:46:37 +08:00
24 changed files with 144 additions and 147 deletions

View File

@@ -1,6 +1,6 @@
package: package:
name: unilabos name: unilabos
version: 0.10.11 version: 0.10.12
source: source:
path: ../unilabos path: ../unilabos

View File

@@ -39,7 +39,9 @@ Uni-Lab-OS recommends using `mamba` for environment management. Choose the appro
```bash ```bash
# Create new environment # Create new environment
mamba create -n unilab uni-lab::unilabos -c robostack-staging -c conda-forge mamba create -n unilab python=3.11.11
mamba activate unilab
mamba install -n unilab uni-lab::unilabos -c robostack-staging -c conda-forge
``` ```
## Install Dev Uni-Lab-OS ## Install Dev Uni-Lab-OS

View File

@@ -41,7 +41,9 @@ Uni-Lab-OS 建议使用 `mamba` 管理环境。根据您的操作系统选择适
```bash ```bash
# 创建新环境 # 创建新环境
mamba create -n unilab uni-lab::unilabos -c robostack-staging -c conda-forge mamba create -n unilab python=3.11.11
mamba activate unilab
mamba install -n unilab uni-lab::unilabos -c robostack-staging -c conda-forge
``` ```
2. 安装开发版 Uni-Lab-OS: 2. 安装开发版 Uni-Lab-OS:

View File

@@ -317,45 +317,6 @@ unilab --help
如果所有命令都正常输出,说明开发环境配置成功! 如果所有命令都正常输出,说明开发环境配置成功!
### 开发工具推荐
#### IDE
- **PyCharm Professional**: 强大的 Python IDE支持远程调试
- **VS Code**: 轻量级,配合 Python 扩展使用
- **Vim/Emacs**: 适合终端开发
#### 推荐的 VS Code 扩展
- Python
- Pylance
- ROS
- URDF
- YAML
#### 调试工具
```bash
# 安装调试工具
pip install ipdb pytest pytest-cov -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
# 代码质量检查
pip install black flake8 mypy -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
```
### 设置 pre-commit 钩子(可选)
```bash
# 安装 pre-commit
pip install pre-commit -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
# 设置钩子
pre-commit install
# 手动运行检查
pre-commit run --all-files
```
--- ---
## 验证安装 ## 验证安装

View File

@@ -1,6 +1,6 @@
package: package:
name: ros-humble-unilabos-msgs name: ros-humble-unilabos-msgs
version: 0.10.11 version: 0.10.12
source: source:
path: ../../unilabos_msgs path: ../../unilabos_msgs
target_directory: src target_directory: src

View File

@@ -1,6 +1,6 @@
package: package:
name: unilabos name: unilabos
version: "0.10.11" version: "0.10.12"
source: source:
path: ../.. path: ../..

View File

@@ -4,7 +4,7 @@ package_name = 'unilabos'
setup( setup(
name=package_name, name=package_name,
version='0.10.11', version='0.10.12',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
install_requires=['setuptools'], install_requires=['setuptools'],

View File

@@ -1 +1 @@
__version__ = "0.10.11" __version__ = "0.10.12"

View File

@@ -141,7 +141,7 @@ class CommunicationClientFactory:
""" """
if cls._client_cache is None: if cls._client_cache is None:
cls._client_cache = cls.create_client(protocol) cls._client_cache = cls.create_client(protocol)
logger.info(f"[CommunicationFactory] Created {type(cls._client_cache).__name__} client") logger.trace(f"[CommunicationFactory] Created {type(cls._client_cache).__name__} client")
return cls._client_cache return cls._client_cache

View File

@@ -159,9 +159,10 @@ def parse_args():
def main(): def main():
"""主函数""" """主函数"""
# 解析命令行参数 # 解析命令行参数
args = parse_args() parser = parse_args()
convert_argv_dashes_to_underscores(args) convert_argv_dashes_to_underscores(parser)
args_dict = vars(args.parse_args()) args = parser.parse_args()
args_dict = vars(args)
# 环境检查 - 检查并自动安装必需的包 (可选) # 环境检查 - 检查并自动安装必需的包 (可选)
if not args_dict.get("skip_env_check", False): if not args_dict.get("skip_env_check", False):
@@ -220,17 +221,18 @@ def main():
logger.info(f"Log level set to '{BasicConfig.log_level}' from config file.") logger.info(f"Log level set to '{BasicConfig.log_level}' from config file.")
configure_logger(loglevel=BasicConfig.log_level, working_dir=working_dir) configure_logger(loglevel=BasicConfig.log_level, working_dir=working_dir)
if args_dict["addr"] == "test": if args.addr != parser.get_default("addr"):
if args.addr == "test":
print_status("使用测试环境地址", "info") print_status("使用测试环境地址", "info")
HTTPConfig.remote_addr = "https://uni-lab.test.bohrium.com/api/v1" HTTPConfig.remote_addr = "https://uni-lab.test.bohrium.com/api/v1"
elif args_dict["addr"] == "uat": elif args.addr == "uat":
print_status("使用uat环境地址", "info") print_status("使用uat环境地址", "info")
HTTPConfig.remote_addr = "https://uni-lab.uat.bohrium.com/api/v1" HTTPConfig.remote_addr = "https://uni-lab.uat.bohrium.com/api/v1"
elif args_dict["addr"] == "local": elif args.addr == "local":
print_status("使用本地环境地址", "info") print_status("使用本地环境地址", "info")
HTTPConfig.remote_addr = "http://127.0.0.1:48197/api/v1" HTTPConfig.remote_addr = "http://127.0.0.1:48197/api/v1"
else: else:
HTTPConfig.remote_addr = args_dict.get("addr", "") HTTPConfig.remote_addr = args.addr
# 设置BasicConfig参数 # 设置BasicConfig参数
if args_dict.get("ak", ""): if args_dict.get("ak", ""):
@@ -327,6 +329,10 @@ def main():
for ind, i in enumerate(resource_edge_info[::-1]): for ind, i in enumerate(resource_edge_info[::-1]):
source_node: ResourceDict = nodes[i["source"]] source_node: ResourceDict = nodes[i["source"]]
target_node: ResourceDict = nodes[i["target"]] target_node: ResourceDict = nodes[i["target"]]
if "sourceHandle" not in source_node:
continue
if "targetHandle" not in target_node:
continue
source_handle = i["sourceHandle"] source_handle = i["sourceHandle"]
target_handle = i["targetHandle"] target_handle = i["targetHandle"]
source_handler_keys = [ source_handler_keys = [

View File

@@ -389,7 +389,7 @@ class MessageProcessor:
self.is_running = True self.is_running = True
self.thread = threading.Thread(target=self._run, daemon=True, name="MessageProcessor") self.thread = threading.Thread(target=self._run, daemon=True, name="MessageProcessor")
self.thread.start() self.thread.start()
logger.info("[MessageProcessor] Started") logger.trace("[MessageProcessor] Started")
def stop(self) -> None: def stop(self) -> None:
"""停止消息处理线程""" """停止消息处理线程"""
@@ -939,7 +939,7 @@ class QueueProcessor:
# 事件通知机制 # 事件通知机制
self.queue_update_event = threading.Event() self.queue_update_event = threading.Event()
logger.info("[QueueProcessor] Initialized") logger.trace("[QueueProcessor] Initialized")
def set_websocket_client(self, websocket_client: "WebSocketClient"): def set_websocket_client(self, websocket_client: "WebSocketClient"):
"""设置WebSocket客户端引用""" """设置WebSocket客户端引用"""
@@ -954,7 +954,7 @@ class QueueProcessor:
self.is_running = True self.is_running = True
self.thread = threading.Thread(target=self._run, daemon=True, name="QueueProcessor") self.thread = threading.Thread(target=self._run, daemon=True, name="QueueProcessor")
self.thread.start() self.thread.start()
logger.info("[QueueProcessor] Started") logger.trace("[QueueProcessor] Started")
def stop(self) -> None: def stop(self) -> None:
"""停止队列处理线程""" """停止队列处理线程"""

View File

@@ -41,7 +41,7 @@ class WSConfig:
# HTTP配置 # HTTP配置
class HTTPConfig: class HTTPConfig:
remote_addr = "http://127.0.0.1:48197/api/v1" remote_addr = "https://uni-lab.bohrium.com/api/v1"
# ROS配置 # ROS配置

View File

@@ -989,6 +989,18 @@ class LiquidHandlerAbstract(LiquidHandlerMiddleware):
else: else:
dis_vols = [float(v) for v in dis_vols] dis_vols = [float(v) for v in dis_vols]
# 统一混合次数为标量,防止数组/列表与 int 比较时报错
if mix_times is not None and not isinstance(mix_times, (int, float)):
try:
mix_times = mix_times[0] if len(mix_times) > 0 else None
except Exception:
try:
mix_times = next(iter(mix_times))
except Exception:
pass
if mix_times is not None:
mix_times = int(mix_times)
# 识别传输模式 # 识别传输模式
num_sources = len(sources) num_sources = len(sources)
num_targets = len(targets) num_targets = len(targets)

View File

@@ -5,6 +5,7 @@ import json
import os import os
import socket import socket
import time import time
import uuid
from typing import Any, List, Dict, Optional, Tuple, TypedDict, Union, Sequence, Iterator, Literal from typing import Any, List, Dict, Optional, Tuple, TypedDict, Union, Sequence, Iterator, Literal
from pylabrobot.liquid_handling import ( from pylabrobot.liquid_handling import (
@@ -856,7 +857,30 @@ class PRCXI9300Api:
def _raw_request(self, payload: str) -> str: def _raw_request(self, payload: str) -> str:
if self.debug: if self.debug:
return " " # 调试/仿真模式下直接返回可解析的模拟 JSON避免后续 json.loads 报错
try:
req = json.loads(payload)
method = req.get("MethodName")
except Exception:
method = None
data: Any = True
if method in {"AddSolution"}:
data = str(uuid.uuid4())
elif method in {"AddWorkTabletMatrix", "AddWorkTabletMatrix2"}:
data = {"Success": True, "Message": "debug mock"}
elif method in {"GetErrorCode"}:
data = ""
elif method in {"RemoveErrorCodet", "Reset", "Start", "LoadSolution", "Pause", "Resume", "Stop"}:
data = True
elif method in {"GetStepStateList", "GetStepStatus", "GetStepState"}:
data = []
elif method in {"GetLocation"}:
data = {"X": 0, "Y": 0, "Z": 0}
elif method in {"GetResetStatus"}:
data = False
return json.dumps({"Success": True, "Msg": "debug mock", "Data": data})
with contextlib.closing(socket.socket()) as sock: with contextlib.closing(socket.socket()) as sock:
sock.settimeout(self.timeout) sock.settimeout(self.timeout)
sock.connect((self.host, self.port)) sock.connect((self.host, self.port))

View File

@@ -174,35 +174,6 @@ bioyond_dispensing_station:
title: query_resource_by_name参数 title: query_resource_by_name参数
type: object type: object
type: UniLabJsonCommand type: UniLabJsonCommand
auto-transfer_materials_to_reaction_station:
feedback: {}
goal: {}
goal_default:
target_device_id: null
transfer_groups: null
handles: {}
placeholder_keys: {}
result: {}
schema:
description: ''
properties:
feedback: {}
goal:
properties:
target_device_id:
type: string
transfer_groups:
type: array
required:
- target_device_id
- transfer_groups
type: object
result: {}
required:
- goal
title: transfer_materials_to_reaction_station参数
type: object
type: UniLabJsonCommand
auto-workflow_sample_locations: auto-workflow_sample_locations:
feedback: {} feedback: {}
goal: {} goal: {}

View File

@@ -97,7 +97,7 @@ def canonicalize_nodes_data(
for node in nodes: for node in nodes:
try: try:
print_status(f"DeviceId: {node['id']}, Class: {node['class']}", "info") # print_status(f"DeviceId: {node['id']}, Class: {node['class']}", "info")
# 使用标准化方法 # 使用标准化方法
resource_instance = ResourceDictInstance.get_resource_instance_from_dict(node) resource_instance = ResourceDictInstance.get_resource_instance_from_dict(node)
known_nodes[node["id"]] = resource_instance known_nodes[node["id"]] = resource_instance
@@ -284,10 +284,18 @@ def modify_to_backend_format(data: list[dict[str, Any]]) -> list[dict[str, Any]]
edge["sourceHandle"] = port[source] edge["sourceHandle"] = port[source]
elif "source_port" in edge: elif "source_port" in edge:
edge["sourceHandle"] = edge.pop("source_port") edge["sourceHandle"] = edge.pop("source_port")
else:
typ = edge.get("type")
if typ == "communication":
continue
if target in port: if target in port:
edge["targetHandle"] = port[target] edge["targetHandle"] = port[target]
elif "target_port" in edge: elif "target_port" in edge:
edge["targetHandle"] = edge.pop("target_port") edge["targetHandle"] = edge.pop("target_port")
else:
typ = edge.get("type")
if typ == "communication":
continue
edge["id"] = f"reactflow__edge-{source}-{edge['sourceHandle']}-{target}-{edge['targetHandle']}" edge["id"] = f"reactflow__edge-{source}-{edge['sourceHandle']}-{target}-{edge['targetHandle']}"
for key in ["source_port", "target_port"]: for key in ["source_port", "target_port"]:
if key in edge: if key in edge:

View File

@@ -582,7 +582,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
except Exception as e: except Exception as e:
self.lab_logger().error(f"更新资源uuid失败: {e}") self.lab_logger().error(f"更新资源uuid失败: {e}")
self.lab_logger().error(traceback.format_exc()) self.lab_logger().error(traceback.format_exc())
self.lab_logger().debug(f"资源更新结果: {response}") self.lab_logger().trace(f"资源更新结果: {response}")
async def get_resource(self, resources_uuid: List[str], with_children: bool = True) -> ResourceTreeSet: async def get_resource(self, resources_uuid: List[str], with_children: bool = True) -> ResourceTreeSet:
""" """
@@ -1164,7 +1164,6 @@ class BaseROS2DeviceNode(Node, Generic[T]):
execution_error = traceback.format_exc() execution_error = traceback.format_exc()
break break
##### self.lab_logger().info(f"准备执行: {action_kwargs}, 函数: {ACTION.__name__}")
time_start = time.time() time_start = time.time()
time_overall = 100 time_overall = 100
future = None future = None
@@ -1172,35 +1171,36 @@ class BaseROS2DeviceNode(Node, Generic[T]):
# 将阻塞操作放入线程池执行 # 将阻塞操作放入线程池执行
if asyncio.iscoroutinefunction(ACTION): if asyncio.iscoroutinefunction(ACTION):
try: try:
##### self.lab_logger().info(f"异步执行动作 {ACTION}") self.lab_logger().trace(f"异步执行动作 {ACTION}")
future = ROS2DeviceNode.run_async_func(ACTION, trace_error=False, **action_kwargs) def _handle_future_exception(fut: Future):
def _handle_future_exception(fut):
nonlocal execution_error, execution_success, action_return_value nonlocal execution_error, execution_success, action_return_value
try: try:
action_return_value = fut.result() action_return_value = fut.result()
if isinstance(action_return_value, BaseException):
raise action_return_value
execution_success = True execution_success = True
except Exception as e: except Exception as _:
execution_error = traceback.format_exc() execution_error = traceback.format_exc()
error( error(
f"异步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}" f"异步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}"
) )
future = ROS2DeviceNode.run_async_func(ACTION, trace_error=False, **action_kwargs)
future.add_done_callback(_handle_future_exception) future.add_done_callback(_handle_future_exception)
except Exception as e: except Exception as e:
execution_error = traceback.format_exc() execution_error = traceback.format_exc()
execution_success = False execution_success = False
self.lab_logger().error(f"创建异步任务失败: {traceback.format_exc()}") self.lab_logger().error(f"创建异步任务失败: {traceback.format_exc()}")
else: else:
##### self.lab_logger().info(f"同步执行动作 {ACTION}") self.lab_logger().trace(f"同步执行动作 {ACTION}")
future = self._executor.submit(ACTION, **action_kwargs) future = self._executor.submit(ACTION, **action_kwargs)
def _handle_future_exception(fut): def _handle_future_exception(fut: Future):
nonlocal execution_error, execution_success, action_return_value nonlocal execution_error, execution_success, action_return_value
try: try:
action_return_value = fut.result() action_return_value = fut.result()
execution_success = True execution_success = True
except Exception as e: except Exception as _:
execution_error = traceback.format_exc() execution_error = traceback.format_exc()
error( error(
f"同步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}" f"同步任务 {ACTION.__name__} 报错了\n{traceback.format_exc()}\n原始输入:{action_kwargs}"
@@ -1305,7 +1305,7 @@ class BaseROS2DeviceNode(Node, Generic[T]):
get_result_info_str(execution_error, execution_success, action_return_value), get_result_info_str(execution_error, execution_success, action_return_value),
) )
##### self.lab_logger().info(f"动作 {action_name} 完成并返回结果") self.lab_logger().trace(f"动作 {action_name} 完成并返回结果")
return result_msg return result_msg
return execute_callback return execute_callback
@@ -1540,17 +1540,29 @@ class ROS2DeviceNode:
这个类封装了设备类实例和ROS2节点的功能提供ROS2接口。 这个类封装了设备类实例和ROS2节点的功能提供ROS2接口。
它不继承设备类,而是通过代理模式访问设备类的属性和方法。 它不继承设备类,而是通过代理模式访问设备类的属性和方法。
""" """
@staticmethod
async def safe_task_wrapper(trace_callback, func, **kwargs):
try:
if callable(trace_callback):
trace_callback(await func(**kwargs))
return await func(**kwargs)
except Exception as e:
if callable(trace_callback):
trace_callback(e)
return e
@classmethod @classmethod
def run_async_func(cls, func, trace_error=True, **kwargs) -> Task: def run_async_func(cls, func, trace_error=True, inner_trace_callback=None, **kwargs) -> Task:
def _handle_future_exception(fut): def _handle_future_exception(fut: Future):
try: try:
fut.result() ret = fut.result()
if isinstance(ret, BaseException):
raise ret
except Exception as e: except Exception as e:
error(f"异步任务 {func.__name__} 报错了") error(f"异步任务 {func.__name__} 获取结果失败")
error(traceback.format_exc()) error(traceback.format_exc())
future = rclpy.get_global_executor().create_task(func(**kwargs)) future = rclpy.get_global_executor().create_task(ROS2DeviceNode.safe_task_wrapper(inner_trace_callback, func, **kwargs))
if trace_error: if trace_error:
future.add_done_callback(_handle_future_exception) future.add_done_callback(_handle_future_exception)
return future return future

View File

@@ -718,7 +718,7 @@ class HostNode(BaseROS2DeviceNode):
feedback_callback=lambda feedback_msg: self.feedback_callback(item, action_id, feedback_msg), feedback_callback=lambda feedback_msg: self.feedback_callback(item, action_id, feedback_msg),
goal_uuid=goal_uuid_obj, goal_uuid=goal_uuid_obj,
) )
future.add_done_callback(lambda future: self.goal_response_callback(item, action_id, future)) future.add_done_callback(lambda f: self.goal_response_callback(item, action_id, f))
def goal_response_callback(self, item: "QueueItem", action_id: str, future) -> None: def goal_response_callback(self, item: "QueueItem", action_id: str, future) -> None:
"""目标响应回调""" """目标响应回调"""
@@ -729,9 +729,11 @@ class HostNode(BaseROS2DeviceNode):
self.lab_logger().info(f"[Host Node] Goal {action_id} ({item.job_id}) accepted") self.lab_logger().info(f"[Host Node] Goal {action_id} ({item.job_id}) accepted")
self._goals[item.job_id] = goal_handle self._goals[item.job_id] = goal_handle
goal_handle.get_result_async().add_done_callback( goal_future = goal_handle.get_result_async()
lambda future: self.get_result_callback(item, action_id, future) goal_future.add_done_callback(
lambda f: self.get_result_callback(item, action_id, f)
) )
goal_future.result()
def feedback_callback(self, item: "QueueItem", action_id: str, feedback_msg) -> None: def feedback_callback(self, item: "QueueItem", action_id: str, feedback_msg) -> None:
"""反馈回调""" """反馈回调"""

View File

@@ -155,7 +155,7 @@ class ResourceDictInstance(object):
res_dict = self.res_content.model_dump(by_alias=True) res_dict = self.res_content.model_dump(by_alias=True)
res_dict["children"] = {child.res_content.id: child.get_plr_nested_dict() for child in self.children} res_dict["children"] = {child.res_content.id: child.get_plr_nested_dict() for child in self.children}
res_dict["parent"] = self.res_content.parent_instance_name res_dict["parent"] = self.res_content.parent_instance_name
res_dict["position"] = self.res_content.position.position.model_dump() res_dict["position"] = self.res_content.pose.position.model_dump()
del res_dict["pose"] del res_dict["pose"]
return res_dict return res_dict

View File

@@ -69,7 +69,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "device", "type": "device",
"class": "syringepump.runze", "class": "syringe_pump_with_valve.runze.SY03B-T08",
"position": { "position": {
"x": 620.6111111111111, "x": 620.6111111111111,
"y": 171, "y": 171,
@@ -93,7 +93,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 430.4087301587302, "x": 430.4087301587302,
"y": 428, "y": 428,
@@ -117,7 +117,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 295.36944444444447, "x": 295.36944444444447,
"y": 428, "y": 428,
@@ -141,7 +141,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 165.36944444444444, "x": 165.36944444444444,
"y": 428, "y": 428,
@@ -165,7 +165,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 165.36944444444444, "x": 165.36944444444444,
"y": 428, "y": 428,
@@ -189,7 +189,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 35, "x": 35,
"y": 428, "y": 428,
@@ -213,7 +213,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 698.1111111111111, "x": 698.1111111111111,
"y": 428, "y": 428,
@@ -255,7 +255,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "device", "type": "device",
"class": "syringepump.runze", "class": "syringe_pump_with_valve.runze.SY03B-T08",
"position": { "position": {
"x": 1195.611507936508, "x": 1195.611507936508,
"y": 686, "y": 686,
@@ -279,7 +279,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 1587.703373015873, "x": 1587.703373015873,
"y": 1172.5, "y": 1172.5,
@@ -299,7 +299,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "device", "type": "device",
"class": "separator_controller", "class": "separator.homemade",
"position": { "position": {
"x": 1624.4027777777778, "x": 1624.4027777777778,
"y": 665.5, "y": 665.5,
@@ -320,7 +320,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 1614.404365079365, "x": 1614.404365079365,
"y": 948, "y": 948,
@@ -340,7 +340,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 1915.7035714285714, "x": 1915.7035714285714,
"y": 665.5, "y": 665.5,
@@ -360,7 +360,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 1785.7035714285714, "x": 1785.7035714285714,
"y": 665.5, "y": 665.5,
@@ -384,7 +384,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 2054.0650793650793, "x": 2054.0650793650793,
"y": 665.5, "y": 665.5,
@@ -408,7 +408,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "device", "type": "device",
"class": "syringepump.runze", "class": "syringe_pump_with_valve.runze.SY03B-T08",
"position": { "position": {
"x": 1630.6527777777778, "x": 1630.6527777777778,
"y": 448.5, "y": 448.5,
@@ -432,7 +432,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "device", "type": "device",
"class": "rotavap", "class": "rotavap.one",
"position": { "position": {
"x": 1339.7031746031746, "x": 1339.7031746031746,
"y": 968.5, "y": 968.5,
@@ -453,7 +453,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 1339.7031746031746, "x": 1339.7031746031746,
"y": 1152, "y": 1152,
@@ -473,7 +473,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 909.722619047619, "x": 909.722619047619,
"y": 948, "y": 948,
@@ -493,7 +493,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 867.972619047619, "x": 867.972619047619,
"y": 1152, "y": 1152,
@@ -513,7 +513,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 742.722619047619, "x": 742.722619047619,
"y": 948, "y": 948,
@@ -533,7 +533,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 1206.722619047619, "x": 1206.722619047619,
"y": 948, "y": 948,
@@ -553,7 +553,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "container", "type": "container",
"class": null, "class": "container",
"position": { "position": {
"x": 1148.222619047619, "x": 1148.222619047619,
"y": 1152, "y": 1152,
@@ -573,7 +573,7 @@
"children": [], "children": [],
"parent": "YugongStation", "parent": "YugongStation",
"type": "device", "type": "device",
"class": "syringepump.runze", "class": "syringe_pump_with_valve.runze.SY03B-T08",
"position": { "position": {
"x": 1469.7031746031746, "x": 1469.7031746031746,
"y": 968.5, "y": 968.5,

View File

@@ -162,8 +162,9 @@ def configure_logger(loglevel=None, working_dir=None):
""" """
# 获取根日志记录器 # 获取根日志记录器
root_logger = logging.getLogger() root_logger = logging.getLogger()
root_logger.setLevel(TRACE_LEVEL)
# 设置日志级别 # 设置日志级别
numeric_level = logging.DEBUG
if loglevel is not None: if loglevel is not None:
if isinstance(loglevel, str): if isinstance(loglevel, str):
# 将字符串转换为logging级别 # 将字符串转换为logging级别
@@ -173,12 +174,8 @@ def configure_logger(loglevel=None, working_dir=None):
numeric_level = getattr(logging, loglevel.upper(), None) numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int): if not isinstance(numeric_level, int):
print(f"警告: 无效的日志级别 '{loglevel}',使用默认级别 DEBUG") print(f"警告: 无效的日志级别 '{loglevel}',使用默认级别 DEBUG")
numeric_level = logging.DEBUG
else: else:
numeric_level = loglevel numeric_level = loglevel
root_logger.setLevel(numeric_level)
else:
root_logger.setLevel(logging.DEBUG) # 默认级别
# 移除已存在的处理器 # 移除已存在的处理器
for handler in root_logger.handlers[:]: for handler in root_logger.handlers[:]:
@@ -186,7 +183,7 @@ def configure_logger(loglevel=None, working_dir=None):
# 创建控制台处理器 # 创建控制台处理器
console_handler = logging.StreamHandler() console_handler = logging.StreamHandler()
console_handler.setLevel(root_logger.level) # 使用与根记录器相同的级别 console_handler.setLevel(numeric_level) # 使用与根记录器相同的级别
# 使用自定义的颜色格式化器 # 使用自定义的颜色格式化器
color_formatter = ColoredFormatter() color_formatter = ColoredFormatter()
@@ -206,7 +203,7 @@ def configure_logger(loglevel=None, working_dir=None):
# 创建文件处理器 # 创建文件处理器
file_handler = logging.FileHandler(log_filepath, encoding="utf-8") file_handler = logging.FileHandler(log_filepath, encoding="utf-8")
file_handler.setLevel(root_logger.level) file_handler.setLevel(TRACE_LEVEL)
# 使用不带颜色的格式化器 # 使用不带颜色的格式化器
file_formatter = ColoredFormatter(use_colors=False) file_formatter = ColoredFormatter(use_colors=False)

View File

@@ -2,7 +2,7 @@
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?> <?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3"> <package format="3">
<name>unilabos_msgs</name> <name>unilabos_msgs</name>
<version>0.10.11</version> <version>0.10.12</version>
<description>ROS2 Messages package for unilabos devices</description> <description>ROS2 Messages package for unilabos devices</description>
<maintainer email="changjh@pku.edu.cn">Junhan Chang</maintainer> <maintainer email="changjh@pku.edu.cn">Junhan Chang</maintainer>
<maintainer email="18435084+Xuwznln@users.noreply.github.com">Xuwznln</maintainer> <maintainer email="18435084+Xuwznln@users.noreply.github.com">Xuwznln</maintainer>