mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-03-24 16:09:15 +00:00
new registry sys
exp. support with add device
This commit is contained in:
@@ -15,6 +15,9 @@ Python 类设备驱动在完成注册表后可以直接在 Uni-Lab 中使用,
|
||||
**示例:**
|
||||
|
||||
```python
|
||||
from unilabos.registry.decorators import device, topic_config
|
||||
|
||||
@device(id="mock_gripper", category=["gripper"], description="Mock Gripper")
|
||||
class MockGripper:
|
||||
def __init__(self):
|
||||
self._position: float = 0.0
|
||||
@@ -23,19 +26,23 @@ class MockGripper:
|
||||
self._status = "Idle"
|
||||
|
||||
@property
|
||||
@topic_config() # 添加 @topic_config 才会定时广播
|
||||
def position(self) -> float:
|
||||
return self._position
|
||||
|
||||
@property
|
||||
@topic_config()
|
||||
def velocity(self) -> float:
|
||||
return self._velocity
|
||||
|
||||
@property
|
||||
@topic_config()
|
||||
def torque(self) -> float:
|
||||
return self._torque
|
||||
|
||||
# 会被自动识别的设备属性,接入 Uni-Lab 时会定时对外广播
|
||||
# 使用 @topic_config 装饰的属性,接入 Uni-Lab 时会定时对外广播
|
||||
@property
|
||||
@topic_config(period=2.0) # 可自定义发布周期
|
||||
def status(self) -> str:
|
||||
return self._status
|
||||
|
||||
@@ -149,7 +156,7 @@ my_device: # 设备唯一标识符
|
||||
|
||||
系统会自动分析您的 Python 驱动类并生成:
|
||||
|
||||
- `status_types`:从 `@property` 装饰的方法自动识别状态属性
|
||||
- `status_types`:从 `@topic_config` 装饰的 `@property` 或方法自动识别状态属性
|
||||
- `action_value_mappings`:从类方法自动生成动作映射
|
||||
- `init_param_schema`:从 `__init__` 方法分析初始化参数
|
||||
- `schema`:前端显示用的属性类型定义
|
||||
@@ -179,7 +186,9 @@ Uni-Lab 设备驱动是一个 Python 类,需要遵循以下结构:
|
||||
|
||||
```python
|
||||
from typing import Dict, Any
|
||||
from unilabos.registry.decorators import device, topic_config
|
||||
|
||||
@device(id="my_device", category=["general"], description="My Device")
|
||||
class MyDevice:
|
||||
"""设备类文档字符串
|
||||
|
||||
@@ -198,8 +207,9 @@ class MyDevice:
|
||||
# 初始化硬件连接
|
||||
|
||||
@property
|
||||
@topic_config() # 必须添加 @topic_config 才会广播
|
||||
def status(self) -> str:
|
||||
"""设备状态(会自动广播)"""
|
||||
"""设备状态(通过 @topic_config 广播)"""
|
||||
return self._status
|
||||
|
||||
def my_action(self, param: float) -> Dict[str, Any]:
|
||||
@@ -217,34 +227,61 @@ class MyDevice:
|
||||
|
||||
## 状态属性 vs 动作方法
|
||||
|
||||
### 状态属性(@property)
|
||||
### 状态属性(@property + @topic_config)
|
||||
|
||||
状态属性会被自动识别并定期广播:
|
||||
状态属性需要同时使用 `@property` 和 `@topic_config` 装饰器才会被识别并定期广播:
|
||||
|
||||
```python
|
||||
from unilabos.registry.decorators import topic_config
|
||||
|
||||
@property
|
||||
@topic_config() # 必须添加,否则不会广播
|
||||
def temperature(self) -> float:
|
||||
"""当前温度"""
|
||||
return self._read_temperature()
|
||||
|
||||
@property
|
||||
@topic_config(period=2.0) # 可自定义发布周期(秒)
|
||||
def status(self) -> str:
|
||||
"""设备状态: idle, running, error"""
|
||||
return self._status
|
||||
|
||||
@property
|
||||
@topic_config(name="ready") # 可自定义发布名称
|
||||
def is_ready(self) -> bool:
|
||||
"""设备是否就绪"""
|
||||
return self._status == "idle"
|
||||
```
|
||||
|
||||
也可以使用普通方法(非 @property)配合 `@topic_config`:
|
||||
|
||||
```python
|
||||
@topic_config(period=10.0)
|
||||
def get_sensor_data(self) -> Dict[str, float]:
|
||||
"""获取传感器数据(get_ 前缀会自动去除,发布名为 sensor_data)"""
|
||||
return {"temp": self._temp, "humidity": self._humidity}
|
||||
```
|
||||
|
||||
**`@topic_config` 参数**:
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `period` | float | 5.0 | 发布周期(秒) |
|
||||
| `print_publish` | bool | 节点默认 | 是否打印发布日志 |
|
||||
| `qos` | int | 10 | QoS 深度 |
|
||||
| `name` | str | None | 自定义发布名称 |
|
||||
|
||||
**发布名称优先级**:`@topic_config(name=...)` > `get_` 前缀去除 > 方法名
|
||||
|
||||
**特点**:
|
||||
|
||||
- 使用`@property`装饰器
|
||||
- 只读,不能有参数
|
||||
- 自动添加到注册表的`status_types`
|
||||
- 必须使用 `@topic_config` 装饰器
|
||||
- 支持 `@property` 和普通方法
|
||||
- 添加到注册表的 `status_types`
|
||||
- 定期发布到 ROS2 topic
|
||||
|
||||
> **⚠️ 重要:** 仅有 `@property` 装饰器而没有 `@topic_config` 的属性**不会**被广播。这是一个 Breaking Change。
|
||||
|
||||
### 动作方法
|
||||
|
||||
动作方法是设备可以执行的操作:
|
||||
@@ -497,6 +534,7 @@ class LiquidHandler:
|
||||
self._status = "idle"
|
||||
|
||||
@property
|
||||
@topic_config()
|
||||
def status(self) -> str:
|
||||
return self._status
|
||||
|
||||
@@ -886,7 +924,52 @@ class MyDevice:
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 类型注解
|
||||
### 1. 使用 `@device` 装饰器标识设备类
|
||||
|
||||
```python
|
||||
from unilabos.registry.decorators import device
|
||||
|
||||
@device(id="my_device", category=["heating"], description="My Heating Device", icon="heater.webp")
|
||||
class MyDevice:
|
||||
...
|
||||
```
|
||||
|
||||
- `id`:设备唯一标识符,用于注册表匹配
|
||||
- `category`:分类列表,前端用于分组显示
|
||||
- `description`:设备描述
|
||||
- `icon`:图标文件名(可选)
|
||||
|
||||
### 2. 使用 `@topic_config` 声明需要广播的状态
|
||||
|
||||
```python
|
||||
from unilabos.registry.decorators import topic_config
|
||||
|
||||
# ✓ @property + @topic_config → 会广播
|
||||
@property
|
||||
@topic_config(period=2.0)
|
||||
def temperature(self) -> float:
|
||||
return self._temp
|
||||
|
||||
# ✓ 普通方法 + @topic_config → 会广播(get_ 前缀自动去除)
|
||||
@topic_config(period=10.0)
|
||||
def get_sensor_data(self) -> Dict[str, float]:
|
||||
return {"temp": self._temp}
|
||||
|
||||
# ✓ 使用 name 参数自定义发布名称
|
||||
@property
|
||||
@topic_config(name="ready")
|
||||
def is_ready(self) -> bool:
|
||||
return self._status == "idle"
|
||||
|
||||
# ✗ 仅有 @property,没有 @topic_config → 不会广播
|
||||
@property
|
||||
def internal_state(self) -> str:
|
||||
return self._state
|
||||
```
|
||||
|
||||
> **注意:** 与 `@property` 连用时,`@topic_config` 必须放在 `@property` 下面。
|
||||
|
||||
### 3. 类型注解
|
||||
|
||||
```python
|
||||
from typing import Dict, Any, Optional, List
|
||||
@@ -901,7 +984,7 @@ def method(
|
||||
pass
|
||||
```
|
||||
|
||||
### 2. 文档字符串
|
||||
### 4. 文档字符串
|
||||
|
||||
```python
|
||||
def method(self, param: float) -> Dict[str, Any]:
|
||||
@@ -923,7 +1006,7 @@ def method(self, param: float) -> Dict[str, Any]:
|
||||
pass
|
||||
```
|
||||
|
||||
### 3. 配置验证
|
||||
### 5. 配置验证
|
||||
|
||||
```python
|
||||
def __init__(self, config: Dict[str, Any]):
|
||||
@@ -937,7 +1020,7 @@ def __init__(self, config: Dict[str, Any]):
|
||||
self.baudrate = config['baudrate']
|
||||
```
|
||||
|
||||
### 4. 资源清理
|
||||
### 6. 资源清理
|
||||
|
||||
```python
|
||||
def __del__(self):
|
||||
@@ -946,7 +1029,7 @@ def __del__(self):
|
||||
self.connection.close()
|
||||
```
|
||||
|
||||
### 5. 设计前端友好的返回值
|
||||
### 7. 设计前端友好的返回值
|
||||
|
||||
**记住:返回值会直接显示在 Web 界面**
|
||||
|
||||
|
||||
@@ -422,18 +422,20 @@ placeholder_keys:
|
||||
|
||||
### status_types
|
||||
|
||||
系统会扫描你的 Python 类,从状态方法(property 或 get\_方法)自动生成这部分:
|
||||
系统会扫描你的 Python 类,从带有 `@topic_config` 装饰器的 `@property` 或方法自动生成这部分:
|
||||
|
||||
```yaml
|
||||
status_types:
|
||||
current_temperature: float # 从 get_current_temperature() 或 @property current_temperature
|
||||
is_heating: bool # 从 get_is_heating() 或 @property is_heating
|
||||
status: str # 从 get_status() 或 @property status
|
||||
current_temperature: float # 从 @topic_config 装饰的 @property 或方法
|
||||
is_heating: bool
|
||||
status: str
|
||||
```
|
||||
|
||||
**注意事项**:
|
||||
|
||||
- 系统会查找所有 `get_` 开头的方法和 `@property` 装饰的属性
|
||||
- 仅有带 `@topic_config` 装饰器的 `@property` 或方法才会被识别为状态属性
|
||||
- 没有 `@topic_config` 的 `@property` 不会生成 status_types,也不会广播
|
||||
- `get_` 前缀的方法名会自动去除前缀(如 `get_temperature` → `temperature`)
|
||||
- 类型会自动转成相应的类型(如 `str`、`float`、`bool`)
|
||||
- 如果类型是 `Any`、`None` 或未知的,默认使用 `String`
|
||||
|
||||
@@ -537,11 +539,13 @@ class AdvancedLiquidHandler:
|
||||
self._temperature = 25.0
|
||||
|
||||
@property
|
||||
@topic_config()
|
||||
def status(self) -> str:
|
||||
"""设备状态"""
|
||||
return self._status
|
||||
|
||||
@property
|
||||
@topic_config()
|
||||
def temperature(self) -> float:
|
||||
"""当前温度"""
|
||||
return self._temperature
|
||||
@@ -809,21 +813,23 @@ my_temperature_controller:
|
||||
你的设备类需要符合以下要求:
|
||||
|
||||
```python
|
||||
from unilabos.common.device_base import DeviceBase
|
||||
from unilabos.registry.decorators import device, topic_config
|
||||
|
||||
class MyDevice(DeviceBase):
|
||||
@device(id="my_device", category=["temperature"], description="My Device")
|
||||
class MyDevice:
|
||||
def __init__(self, config):
|
||||
"""初始化,参数会自动分析到 init_param_schema.config"""
|
||||
super().__init__(config)
|
||||
self.port = config.get('port', '/dev/ttyUSB0')
|
||||
|
||||
# 状态方法(会自动生成到 status_types)
|
||||
# 状态方法(必须添加 @topic_config 才会生成到 status_types 并广播)
|
||||
@property
|
||||
@topic_config()
|
||||
def status(self):
|
||||
"""返回设备状态"""
|
||||
return "idle"
|
||||
|
||||
@property
|
||||
@topic_config()
|
||||
def temperature(self):
|
||||
"""返回当前温度"""
|
||||
return 25.0
|
||||
@@ -1039,7 +1045,34 @@ resource.type # "resource"
|
||||
|
||||
### 代码规范
|
||||
|
||||
1. **始终使用类型注解**
|
||||
1. **使用 `@device` 装饰器标识设备类**
|
||||
|
||||
```python
|
||||
from unilabos.registry.decorators import device
|
||||
|
||||
@device(id="my_device", category=["heating"], description="My Device")
|
||||
class MyDevice:
|
||||
...
|
||||
```
|
||||
|
||||
2. **使用 `@topic_config` 声明广播属性**
|
||||
|
||||
```python
|
||||
from unilabos.registry.decorators import topic_config
|
||||
|
||||
# ✓ 需要广播的状态属性
|
||||
@property
|
||||
@topic_config(period=2.0)
|
||||
def temperature(self) -> float:
|
||||
return self._temp
|
||||
|
||||
# ✗ 仅有 @property 不会广播
|
||||
@property
|
||||
def internal_counter(self) -> int:
|
||||
return self._counter
|
||||
```
|
||||
|
||||
3. **始终使用类型注解**
|
||||
|
||||
```python
|
||||
# ✓ 好
|
||||
@@ -1051,7 +1084,7 @@ def method(self, resource, device):
|
||||
pass
|
||||
```
|
||||
|
||||
2. **提供有意义的参数名**
|
||||
4. **提供有意义的参数名**
|
||||
|
||||
```python
|
||||
# ✓ 好 - 清晰的参数名
|
||||
@@ -1063,7 +1096,7 @@ def transfer(self, r1: ResourceSlot, r2: ResourceSlot):
|
||||
pass
|
||||
```
|
||||
|
||||
3. **使用 Optional 表示可选参数**
|
||||
5. **使用 Optional 表示可选参数**
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
@@ -1076,7 +1109,7 @@ def method(
|
||||
pass
|
||||
```
|
||||
|
||||
4. **添加详细的文档字符串**
|
||||
6. **添加详细的文档字符串**
|
||||
|
||||
```python
|
||||
def method(
|
||||
@@ -1096,13 +1129,13 @@ def method(
|
||||
pass
|
||||
```
|
||||
|
||||
5. **方法命名规范**
|
||||
7. **方法命名规范**
|
||||
|
||||
- 状态方法使用 `@property` 装饰器或 `get_` 前缀
|
||||
- 状态方法使用 `@property` + `@topic_config` 装饰器,或普通方法 + `@topic_config`
|
||||
- 动作方法使用动词开头
|
||||
- 保持命名清晰、一致
|
||||
|
||||
6. **完善的错误处理**
|
||||
8. **完善的错误处理**
|
||||
- 实现完善的错误处理
|
||||
- 添加日志记录
|
||||
- 提供有意义的错误信息
|
||||
|
||||
@@ -221,10 +221,10 @@ Laboratory A Laboratory B
|
||||
|
||||
```bash
|
||||
# 实验室A
|
||||
unilab --ak your_ak --sk your_sk --upload_registry --use_remote_resource
|
||||
unilab --ak your_ak --sk your_sk --upload_registry
|
||||
|
||||
# 实验室B
|
||||
unilab --ak your_ak --sk your_sk --upload_registry --use_remote_resource
|
||||
unilab --ak your_ak --sk your_sk --upload_registry
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user