Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .conda/base/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

package:
name: unilabos
version: 0.10.19
version: 0.10.18

source:
path: ../../unilabos
Expand Down Expand Up @@ -54,7 +54,7 @@ requirements:
- pymodbus
- matplotlib
- pylibftdi
- uni-lab::unilabos-env ==0.10.19
- uni-lab::unilabos-env ==0.10.18

about:
repository: https://github.com/deepmodeling/Uni-Lab-OS
Expand Down
2 changes: 1 addition & 1 deletion .conda/environment/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

package:
name: unilabos-env
version: 0.10.19
version: 0.10.18

build:
noarch: generic
Expand Down
4 changes: 2 additions & 2 deletions .conda/full/recipe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

package:
name: unilabos-full
version: 0.10.19
version: 0.10.18

build:
noarch: generic

requirements:
run:
# Base unilabos package (includes unilabos-env)
- uni-lab::unilabos ==0.10.19
- uni-lab::unilabos ==0.10.18
# Documentation tools
- sphinx
- sphinx_rtd_theme
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
uv pip uninstall enum34 || echo enum34 not installed, skipping
uv pip install .

- name: Run check mode (AST registry validation)
- name: Run check mode (complete_registry)
run: |
call conda activate check-env
echo Running check mode...
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ output/
unilabos_data/
pyrightconfig.json
.cursorignore
device_package*/
## Python

# Byte-compiled / optimized / DLL files
Expand Down
109 changes: 13 additions & 96 deletions docs/developer_guide/add_device.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ 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
Expand All @@ -26,23 +23,19 @@ 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

# 使用 @topic_config 装饰的属性,接入 Uni-Lab 时会定时对外广播
# 会被自动识别的设备属性,接入 Uni-Lab 时会定时对外广播
@property
@topic_config(period=2.0) # 可自定义发布周期
def status(self) -> str:
return self._status

Expand Down Expand Up @@ -156,7 +149,7 @@ my_device: # 设备唯一标识符

系统会自动分析您的 Python 驱动类并生成:

- `status_types`:从 `@topic_config` 装饰的 `@property` 或方法自动识别状态属性
- `status_types`:从 `@property` 装饰的方法自动识别状态属性
- `action_value_mappings`:从类方法自动生成动作映射
- `init_param_schema`:从 `__init__` 方法分析初始化参数
- `schema`:前端显示用的属性类型定义
Expand Down Expand Up @@ -186,9 +179,7 @@ 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:
"""设备类文档字符串

Expand All @@ -207,9 +198,8 @@ 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]:
Expand All @@ -227,61 +217,34 @@ class MyDevice:

## 状态属性 vs 动作方法

### 状态属性(@property + @topic_config
### 状态属性(@property)

状态属性需要同时使用 `@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_` 前缀去除 > 方法名

**特点**:

- 必须使用 `@topic_config` 装饰器
- 支持 `@property` 和普通方法
- 添加到注册表的 `status_types`
- 使用`@property`装饰器
- 只读,不能有参数
- 自动添加到注册表的`status_types`
- 定期发布到 ROS2 topic

> **⚠️ 重要:** 仅有 `@property` 装饰器而没有 `@topic_config` 的属性**不会**被广播。这是一个 Breaking Change。

### 动作方法

动作方法是设备可以执行的操作:
Expand Down Expand Up @@ -534,7 +497,6 @@ class LiquidHandler:
self._status = "idle"

@property
@topic_config()
def status(self) -> str:
return self._status

Expand Down Expand Up @@ -924,52 +886,7 @@ class MyDevice:

## 最佳实践

### 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. 类型注解
### 1. 类型注解

```python
from typing import Dict, Any, Optional, List
Expand All @@ -984,7 +901,7 @@ def method(
pass
```

### 4. 文档字符串
### 2. 文档字符串

```python
def method(self, param: float) -> Dict[str, Any]:
Expand All @@ -1006,7 +923,7 @@ def method(self, param: float) -> Dict[str, Any]:
pass
```

### 5. 配置验证
### 3. 配置验证

```python
def __init__(self, config: Dict[str, Any]):
Expand All @@ -1020,7 +937,7 @@ def __init__(self, config: Dict[str, Any]):
self.baudrate = config['baudrate']
```

### 6. 资源清理
### 4. 资源清理

```python
def __del__(self):
Expand All @@ -1029,7 +946,7 @@ def __del__(self):
self.connection.close()
```

### 7. 设计前端友好的返回值
### 5. 设计前端友好的返回值

**记住:返回值会直接显示在 Web 界面**

Expand Down
Loading