mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2026-02-03 21:05:09 +00:00
Workbench example, adjust log level, and ci check (#220) * TestLatency Return Value Example & gitignore update * Adjust log level & Add workbench virtual example & Add not action decorator & Add check_mode & * Add CI Check Fix/workstation yb revision (#217) * Revert log change & update registry * Revert opcua client & move electrolyte node Workstation yb merge dev ready 260113 (#216) * feat(bioyond): 添加计算实验设计功能,支持化合物配比和滴定比例参数 * feat(bioyond): 添加测量小瓶功能,支持基本参数配置 * feat(bioyond): 添加测量小瓶配置,支持新设备参数 * feat(bioyond): 更新仓库布局和尺寸,支持竖向排列的测量小瓶和试剂存放堆栈 * feat(bioyond): 优化任务创建流程,确保无论成功与否都清理任务队列以避免重复累积 * feat(bioyond): 添加设置反应器温度功能,支持温度范围和异常处理 * feat(bioyond): 调整反应器位置配置,统一坐标格式 * feat(bioyond): 添加调度器启动功能,支持任务队列执行并处理异常 * feat(bioyond): 优化调度器启动功能,添加异常处理并更新相关配置 * feat(opcua): 增强节点ID解析兼容性和数据类型处理 改进节点ID解析逻辑以支持多种格式,包括字符串和数字标识符 添加数据类型转换处理,确保写入值时类型匹配 优化错误提示信息,便于调试节点连接问题 * feat(registry): 新增后处理站的设备配置文件 添加后处理站的YAML配置文件,包含动作映射、状态类型和设备描述 * 添加调度器启动功能,合并物料参数配置,优化物料参数处理逻辑 * 添加从 Bioyond 系统自动同步工作流序列的功能,并更新相关配置 * fix:兼容 BioyondReactionStation 中 workflow_sequence 被重写为 property * fix:同步工作流序列 * feat: remove commented workflow synchronization from `reaction_station.py`. * 添加时间约束功能及相关配置 * fix:自动更新物料缓存功能,添加物料时更新缓存并在删除时移除缓存项 * fix:在添加物料时处理字符串和字典返回值,确保正确更新缓存 * fix:更新奔曜错误处理报送为物料变更报送,调整日志记录和响应消息 * feat:添加实验报告简化功能,去除冗余信息并保留关键信息 * feat: 添加任务状态事件发布功能,监控并报告任务运行、超时、完成和错误状态 * fix: 修复添加物料时数据格式错误 * Refactor bioyond_dispensing_station and reaction_station_bioyond YAML configurations - Removed redundant action value mappings from bioyond_dispensing_station. - Updated goal properties in bioyond_dispensing_station to use enums for target_stack and other parameters. - Changed data types for end_point and start_point in reaction_station_bioyond to use string enums (Start, End). - Simplified descriptions and updated measurement units from μL to mL where applicable. - Removed unused commands from reaction_station_bioyond to streamline the configuration. * fix:Change the material unit from μL to mL * fix:refresh_material_cache * feat: 动态获取工作流步骤ID,优化工作流配置 * feat: 添加清空服务端所有非核心工作流功能 * fix:修复Bottle类的序列化和反序列化方法 * feat:增强材料缓存更新逻辑,支持处理返回数据中的详细信息 * Add debug log * feat(workstation): update bioyond config migration and coin cell material search logic - Migrate bioyond_cell config to JSON structure and remove global variable dependencies - Implement material search confirmation dialog auto-handling - Add documentation: 20260113_物料搜寻确认弹窗自动处理功能.md and 20260113_配置迁移修改总结.md * Refactor module paths for Bioyond devices in YAML configuration files - Updated the module path for BioyondDispensingStation in bioyond_dispensing_station.yaml to reflect the new directory structure. - Updated the module path for BioyondReactionStation and BioyondReactor in reaction_station_bioyond.yaml to align with the revised organization of the codebase. * fix: WareHouse 的不可哈希类型错误,优化父节点去重逻辑 * refactor: Move config from module to instance initialization * fix: 修正 reaction_station 目录名拼写错误 * feat: Integrate material search logic and cleanup deprecated files - Update coin_cell_assembly.py with material search dialog handling - Update YB_warehouses.py with latest warehouse configurations - Remove outdated documentation and test data files * Refactor: Use instance attributes for action names and workflow step IDs * refactor: Split tipbox storage into left and right warehouses * refactor: Merge tipbox storage left and right into single warehouse --------- Co-authored-by: ZiWei <131428629+ZiWei09@users.noreply.github.com> Co-authored-by: Andy6M <xieqiming1132@qq.com> fix: WareHouse 的不可哈希类型错误,优化父节点去重逻辑 fix parent_uuid fetch when bind_parent_id == node_name 物料更新也是用父节点进行报送 Add None conversion for tube rack etc. Add set_liquid example. Add create_resource and test_resource example. Add restart. Temp allow action message. Add no_update_feedback option. Create session_id by edge. bump version to 0.10.15 temp cancel update req
231 lines
6.9 KiB
Python
231 lines
6.9 KiB
Python
from functools import wraps
|
||
from typing import Any, Callable, Optional, TypeVar
|
||
|
||
F = TypeVar("F", bound=Callable[..., Any])
|
||
|
||
|
||
def singleton(cls):
|
||
"""
|
||
单例装饰器
|
||
确保被装饰的类只有一个实例
|
||
"""
|
||
instances = {}
|
||
|
||
def get_instance(*args, **kwargs):
|
||
if cls not in instances:
|
||
instances[cls] = cls(*args, **kwargs)
|
||
return instances[cls]
|
||
|
||
return get_instance
|
||
|
||
|
||
def topic_config(
|
||
period: Optional[float] = None,
|
||
print_publish: Optional[bool] = None,
|
||
qos: Optional[int] = None,
|
||
) -> Callable[[F], F]:
|
||
"""
|
||
Topic发布配置装饰器
|
||
|
||
用于装饰 get_{attr_name} 方法或 @property,控制对应属性的ROS topic发布行为。
|
||
|
||
Args:
|
||
period: 发布周期(秒)。None 表示使用默认值 5.0
|
||
print_publish: 是否打印发布日志。None 表示使用节点默认配置
|
||
qos: QoS深度配置。None 表示使用默认值 10
|
||
|
||
Example:
|
||
class MyDriver:
|
||
# 方式1: 装饰 get_{attr_name} 方法
|
||
@topic_config(period=1.0, print_publish=False, qos=5)
|
||
def get_temperature(self):
|
||
return self._temperature
|
||
|
||
# 方式2: 与 @property 连用(topic_config 放在下面)
|
||
@property
|
||
@topic_config(period=0.1)
|
||
def position(self):
|
||
return self._position
|
||
|
||
Note:
|
||
与 @property 连用时,@topic_config 必须放在 @property 下面,
|
||
这样装饰器执行顺序为:先 topic_config 添加配置,再 property 包装。
|
||
"""
|
||
|
||
def decorator(func: F) -> F:
|
||
@wraps(func)
|
||
def wrapper(*args, **kwargs):
|
||
return func(*args, **kwargs)
|
||
|
||
# 在函数上附加配置属性 (type: ignore 用于动态属性)
|
||
wrapper._topic_period = period # type: ignore[attr-defined]
|
||
wrapper._topic_print_publish = print_publish # type: ignore[attr-defined]
|
||
wrapper._topic_qos = qos # type: ignore[attr-defined]
|
||
wrapper._has_topic_config = True # type: ignore[attr-defined]
|
||
|
||
return wrapper # type: ignore[return-value]
|
||
|
||
return decorator
|
||
|
||
|
||
def get_topic_config(func) -> dict:
|
||
"""
|
||
获取函数上的topic配置
|
||
|
||
Args:
|
||
func: 被装饰的函数
|
||
|
||
Returns:
|
||
包含 period, print_publish, qos 的配置字典
|
||
"""
|
||
if hasattr(func, "_has_topic_config") and getattr(func, "_has_topic_config", False):
|
||
return {
|
||
"period": getattr(func, "_topic_period", None),
|
||
"print_publish": getattr(func, "_topic_print_publish", None),
|
||
"qos": getattr(func, "_topic_qos", None),
|
||
}
|
||
return {}
|
||
|
||
|
||
def subscribe(
|
||
topic: str,
|
||
msg_type: Optional[type] = None,
|
||
qos: int = 10,
|
||
) -> Callable[[F], F]:
|
||
"""
|
||
Topic订阅装饰器
|
||
|
||
用于装饰 driver 类中的方法,使其成为 ROS topic 的订阅回调。
|
||
当 ROS2DeviceNode 初始化时,会自动扫描并创建对应的订阅者。
|
||
|
||
Args:
|
||
topic: Topic 名称模板,支持以下占位符:
|
||
- {device_id}: 设备ID (如 "pump_1")
|
||
- {namespace}: 完整命名空间 (如 "/devices/pump_1")
|
||
msg_type: ROS 消息类型。如果为 None,需要在回调函数的类型注解中指定
|
||
qos: QoS 深度配置,默认为 10
|
||
|
||
Example:
|
||
from std_msgs.msg import String, Float64
|
||
|
||
class MyDriver:
|
||
@subscribe(topic="/devices/{device_id}/set_speed", msg_type=Float64)
|
||
def on_speed_update(self, msg: Float64):
|
||
self._speed = msg.data
|
||
print(f"Speed updated to: {self._speed}")
|
||
|
||
@subscribe(topic="{namespace}/command")
|
||
def on_command(self, msg: String):
|
||
# msg_type 可从类型注解推断
|
||
self.execute_command(msg.data)
|
||
|
||
Note:
|
||
- 回调方法的第一个参数是 self,第二个参数是收到的 ROS 消息
|
||
- topic 中的占位符会在创建订阅时被实际值替换
|
||
"""
|
||
|
||
def decorator(func: F) -> F:
|
||
@wraps(func)
|
||
def wrapper(*args, **kwargs):
|
||
return func(*args, **kwargs)
|
||
|
||
# 在函数上附加订阅配置
|
||
wrapper._subscribe_topic = topic # type: ignore[attr-defined]
|
||
wrapper._subscribe_msg_type = msg_type # type: ignore[attr-defined]
|
||
wrapper._subscribe_qos = qos # type: ignore[attr-defined]
|
||
wrapper._has_subscribe = True # type: ignore[attr-defined]
|
||
|
||
return wrapper # type: ignore[return-value]
|
||
|
||
return decorator
|
||
|
||
|
||
def get_subscribe_config(func) -> dict:
|
||
"""
|
||
获取函数上的订阅配置
|
||
|
||
Args:
|
||
func: 被装饰的函数
|
||
|
||
Returns:
|
||
包含 topic, msg_type, qos 的配置字典
|
||
"""
|
||
if hasattr(func, "_has_subscribe") and getattr(func, "_has_subscribe", False):
|
||
return {
|
||
"topic": getattr(func, "_subscribe_topic", None),
|
||
"msg_type": getattr(func, "_subscribe_msg_type", None),
|
||
"qos": getattr(func, "_subscribe_qos", 10),
|
||
}
|
||
return {}
|
||
|
||
|
||
def get_all_subscriptions(instance) -> list:
|
||
"""
|
||
扫描实例的所有方法,获取带有 @subscribe 装饰器的方法及其配置
|
||
|
||
Args:
|
||
instance: 要扫描的实例
|
||
|
||
Returns:
|
||
包含 (method_name, method, config) 元组的列表
|
||
"""
|
||
subscriptions = []
|
||
for attr_name in dir(instance):
|
||
if attr_name.startswith("_"):
|
||
continue
|
||
try:
|
||
attr = getattr(instance, attr_name)
|
||
if callable(attr):
|
||
config = get_subscribe_config(attr)
|
||
if config:
|
||
subscriptions.append((attr_name, attr, config))
|
||
except Exception:
|
||
pass
|
||
return subscriptions
|
||
|
||
|
||
def not_action(func: F) -> F:
|
||
"""
|
||
标记方法为非动作的装饰器
|
||
|
||
用于装饰 driver 类中的方法,使其在 complete_registry 时不被识别为动作。
|
||
适用于辅助方法、内部工具方法等不应暴露为设备动作的公共方法。
|
||
|
||
Example:
|
||
class MyDriver:
|
||
@not_action
|
||
def helper_method(self):
|
||
# 这个方法不会被注册为动作
|
||
pass
|
||
|
||
def actual_action(self, param: str):
|
||
# 这个方法会被注册为动作
|
||
self.helper_method()
|
||
|
||
Note:
|
||
- 可以与其他装饰器组合使用,@not_action 应放在最外层
|
||
- 仅影响 complete_registry 的动作识别,不影响方法的正常调用
|
||
"""
|
||
|
||
@wraps(func)
|
||
def wrapper(*args, **kwargs):
|
||
return func(*args, **kwargs)
|
||
|
||
# 在函数上附加标记
|
||
wrapper._is_not_action = True # type: ignore[attr-defined]
|
||
|
||
return wrapper # type: ignore[return-value]
|
||
|
||
|
||
def is_not_action(func) -> bool:
|
||
"""
|
||
检查函数是否被标记为非动作
|
||
|
||
Args:
|
||
func: 被检查的函数
|
||
|
||
Returns:
|
||
如果函数被 @not_action 装饰则返回 True,否则返回 False
|
||
"""
|
||
return getattr(func, "_is_not_action", False)
|