mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 21:11:12 +00:00
1140 lines
28 KiB
Markdown
1140 lines
28 KiB
Markdown
# 添加设备:注册表配置完整指南
|
||
|
||
本文档说明如何为设备创建和配置注册表,包括基本结构、特殊类型识别、动作配置等内容。
|
||
|
||
## 概述
|
||
|
||
注册表(Registry)是 Uni-Lab 的设备配置系统,采用 YAML 格式定义设备的:
|
||
|
||
- 可用动作(Actions)
|
||
- 状态类型(Status Types)
|
||
- 初始化参数(Init Parameters)
|
||
- 连接点(Handles)
|
||
|
||
好消息是系统会自动生成大部分配置内容,你只需要提供核心信息,让系统帮你完成剩余工作。
|
||
|
||
## 快速开始:使用注册表编辑器
|
||
|
||
推荐使用 UniLabOS 自带的可视化编辑器,它能帮你自动生成大部分配置,省去手写的麻烦。
|
||
|
||
### 使用步骤
|
||
|
||
1. 启动 UniLabOS
|
||
2. 在浏览器中打开"注册表编辑器"页面
|
||
3. 上传你的 Python 设备驱动文件
|
||
4. 点击"分析文件",让系统读取类信息
|
||
5. 填写基本信息(设备描述、图标等)
|
||
6. 点击"生成注册表",复制生成的内容
|
||
7. 保存到 `unilabos/registry/devices/your_device.yaml`
|
||
|
||
**提示**:我们提供了测试驱动用于在界面上尝试注册表生成,参见:`test/registry/example_devices.py`
|
||
|
||
## 注册表的基本结构
|
||
|
||
### 核心字段说明
|
||
|
||
| 字段名 | 类型 | 需要手写 | 说明 |
|
||
| ----------------- | ------ | -------- | --------------------------------- |
|
||
| 设备标识符 | string | 是 | 设备的唯一名字,如 `mock_chiller` |
|
||
| class | object | 部分 | 设备的核心信息,必须配置 |
|
||
| description | string | 否 | 设备描述,系统默认给空字符串 |
|
||
| handles | array | 否 | 连接关系,默认为空 |
|
||
| icon | string | 否 | 图标路径,默认为空 |
|
||
| init_param_schema | object | 否 | 初始化参数,系统自动分析生成 |
|
||
| version | string | 否 | 版本号,默认 "1.0.0" |
|
||
| category | array | 否 | 设备分类,默认使用文件名 |
|
||
| config_info | array | 否 | 嵌套配置,默认为空 |
|
||
| file_path | string | 否 | 文件路径,系统自动设置 |
|
||
| registry_type | string | 否 | 注册表类型,自动设为 "device" |
|
||
|
||
### class 字段详解
|
||
|
||
class 是核心部分,包含这些内容:
|
||
|
||
| 字段名 | 类型 | 需要手写 | 说明 |
|
||
| --------------------- | ------ | -------- | ---------------------------------- |
|
||
| module | string | 是 | Python 类的路径,必须写 |
|
||
| type | string | 是 | 驱动类型,一般写 "python" |
|
||
| status_types | object | 否 | 状态类型,系统自动分析生成 |
|
||
| action_value_mappings | object | 部分 | 动作配置,系统会自动生成一些基础的 |
|
||
|
||
### 基本结构示例
|
||
|
||
```yaml
|
||
my_device:
|
||
class:
|
||
module: unilabos.devices.my_module.my_device:MyDevice
|
||
type: python
|
||
status_types:
|
||
status: str
|
||
temperature: float
|
||
action_value_mappings:
|
||
# 动作配置(详见后文)
|
||
action_name:
|
||
type: UniLabJsonCommand
|
||
goal: { ... }
|
||
result: { ... }
|
||
|
||
description: '设备描述'
|
||
version: '1.0.0'
|
||
category:
|
||
- device_category
|
||
handles: []
|
||
icon: ''
|
||
init_param_schema:
|
||
config:
|
||
properties:
|
||
port:
|
||
default: DEFAULT_PORT
|
||
type: string
|
||
required: []
|
||
type: object
|
||
data:
|
||
properties:
|
||
status:
|
||
type: string
|
||
temperature:
|
||
type: number
|
||
required:
|
||
- status
|
||
type: object
|
||
```
|
||
|
||
## 创建注册表的方式
|
||
|
||
### 方式 1: 使用注册表编辑器(推荐)
|
||
|
||
适合大多数场景,快速高效。
|
||
|
||
**步骤**:
|
||
|
||
1. 启动 Uni-Lab
|
||
2. 访问 Web 界面的"注册表编辑器"
|
||
3. 上传您的 Python 设备驱动文件
|
||
4. 点击"分析文件"
|
||
5. 填写描述和图标
|
||
6. 点击"生成注册表"
|
||
7. 复制生成的 YAML 内容
|
||
8. 保存到 `unilabos/registry/devices/your_device.yaml`
|
||
|
||
### 方式 2: 使用--complete_registry 参数(开发调试)
|
||
|
||
适合开发阶段,自动补全配置。
|
||
|
||
```bash
|
||
# 启动时自动补全注册表
|
||
unilab -g dev.json --complete_registry --registry_path ./my_registry
|
||
```
|
||
|
||
系统会:
|
||
|
||
1. 扫描 Python 类
|
||
2. 分析方法签名和类型
|
||
3. 自动生成缺失的字段
|
||
4. 保存到注册表文件
|
||
|
||
**或者在代码中**:
|
||
|
||
```python
|
||
# 启动系统时使用参数
|
||
启动系统时用 complete_registry=True 参数,让系统自动补全
|
||
```
|
||
|
||
### 方式 3: 手动编写(高级)
|
||
|
||
适合需要精细控制或特殊需求的场景。
|
||
|
||
**最小化配置示例**:
|
||
|
||
```yaml
|
||
# devices/my_device.yaml
|
||
my_device:
|
||
class:
|
||
module: unilabos.devices.my_module.my_device:MyDevice
|
||
type: python
|
||
```
|
||
|
||
然后启动时使用 `--complete_registry` 让系统自动补全其余内容。
|
||
|
||
## action_value_mappings 详解
|
||
|
||
这个部分定义设备能做哪些动作。系统会自动生成大部分动作,你通常只需要添加特殊的自定义动作。
|
||
|
||
### 系统自动生成的动作
|
||
|
||
1. **以 `auto-` 开头的动作**:从 Python 类的方法自动生成
|
||
2. **通用的驱动动作**:
|
||
- `_execute_driver_command`:同步执行驱动命令(仅本地可用)
|
||
- `_execute_driver_command_async`:异步执行驱动命令(仅本地可用)
|
||
|
||
### 动作配置字段
|
||
|
||
| 字段名 | 需要手写 | 说明 |
|
||
| ---------------- | -------- | -------------------------------- |
|
||
| type | 是 | 动作类型,必须指定 |
|
||
| goal | 是 | 输入参数映射 |
|
||
| feedback | 否 | 实时反馈,通常为空 |
|
||
| result | 是 | 结果返回映射 |
|
||
| goal_default | 部分 | 参数默认值,ROS 动作会自动生成 |
|
||
| schema | 部分 | 前端表单配置,ROS 动作会自动生成 |
|
||
| handles | 否 | 连接关系,默认为空 |
|
||
| placeholder_keys | 否 | 特殊输入字段配置 |
|
||
|
||
### 动作类型
|
||
|
||
| 类型 | 使用场景 | 系统自动生成内容 |
|
||
| ---------------------- | -------------------- | ---------------------- |
|
||
| UniLabJsonCommand | 自定义同步 JSON 命令 | 无 |
|
||
| UniLabJsonCommandAsync | 自定义异步 JSON 命令 | 无 |
|
||
| ROS 动作类型 | 标准 ROS 动作 | goal_default 和 schema |
|
||
|
||
**常用的 ROS 动作类型**:
|
||
|
||
- `SendCmd`:发送简单命令
|
||
- `NavigateThroughPoses`:导航动作
|
||
- `SingleJointPosition`:单关节位置控制
|
||
- `Stir`:搅拌动作
|
||
- `HeatChill`、`HeatChillStart`:加热冷却动作
|
||
|
||
### 动作命名建议
|
||
|
||
根据设备用途来起名字:
|
||
|
||
- **启动停止类**:`start`、`stop`、`pause`、`resume`
|
||
- **设置参数类**:`set_speed`、`set_temperature`、`set_timer`
|
||
- **移动控制类**:`move_to_position`、`move_through_points`
|
||
- **功能操作类**:`stir`、`heat_chill_start`、`heat_chill_stop`
|
||
- **开关控制类**:`valve_open_cmd`、`valve_close_cmd`、`push_to`
|
||
- **命令执行类**:`send_nav_task`、`execute_command_from_outer`
|
||
|
||
### 动作配置示例
|
||
|
||
```yaml
|
||
heat_chill_start:
|
||
type: HeatChillStart
|
||
goal:
|
||
purpose: purpose
|
||
temp: temp
|
||
goal_default:
|
||
purpose: ''
|
||
temp: 0.0
|
||
handles:
|
||
output:
|
||
- handler_key: labware
|
||
label: Labware
|
||
data_type: resource
|
||
data_source: handle
|
||
data_key: liquid
|
||
placeholder_keys:
|
||
purpose: unilabos_resources
|
||
result:
|
||
status: status
|
||
success: success
|
||
schema:
|
||
description: '启动加热冷却功能'
|
||
properties:
|
||
goal:
|
||
properties:
|
||
purpose:
|
||
type: string
|
||
description: '用途说明'
|
||
temp:
|
||
type: number
|
||
description: '目标温度'
|
||
required:
|
||
- purpose
|
||
- temp
|
||
title: HeatChillStart_Goal
|
||
type: object
|
||
required:
|
||
- goal
|
||
title: HeatChillStart
|
||
type: object
|
||
feedback: {}
|
||
```
|
||
|
||
## 特殊类型的自动识别
|
||
|
||
### ResourceSlot 和 DeviceSlot 识别
|
||
|
||
当您在驱动代码中使用这些特殊类型时,系统会自动识别并生成相应的前端选择器。
|
||
|
||
**Python 驱动代码示例**:
|
||
|
||
```python
|
||
from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot
|
||
from typing import List
|
||
|
||
class MyDevice:
|
||
def test_resource(
|
||
self,
|
||
resource: ResourceSlot, # 单个资源
|
||
resources: List[ResourceSlot], # 多个资源
|
||
device: DeviceSlot, # 单个设备
|
||
devices: List[DeviceSlot] # 多个设备
|
||
):
|
||
pass
|
||
```
|
||
|
||
**自动生成的注册表**(使用--complete_registry):
|
||
|
||
```yaml
|
||
my_device:
|
||
class:
|
||
action_value_mappings:
|
||
test_resource:
|
||
type: UniLabJsonCommand
|
||
goal:
|
||
resource: resource
|
||
resources: resources
|
||
device: device
|
||
devices: devices
|
||
placeholder_keys:
|
||
resource: unilabos_resources # 自动添加!
|
||
resources: unilabos_resources # 自动添加!
|
||
device: unilabos_devices # 自动添加!
|
||
devices: unilabos_devices # 自动添加!
|
||
result:
|
||
success: success
|
||
```
|
||
|
||
### 识别规则
|
||
|
||
| Python 类型 | placeholder_keys 值 | 前端效果 |
|
||
| -------------------- | -------------------- | -------------- |
|
||
| `ResourceSlot` | `unilabos_resources` | 单选资源下拉框 |
|
||
| `List[ResourceSlot]` | `unilabos_resources` | 多选资源下拉框 |
|
||
| `DeviceSlot` | `unilabos_devices` | 单选设备下拉框 |
|
||
| `List[DeviceSlot]` | `unilabos_devices` | 多选设备下拉框 |
|
||
|
||
### 前端 UI 效果
|
||
|
||
#### 单选资源
|
||
|
||
```yaml
|
||
placeholder_keys:
|
||
source: unilabos_resources
|
||
```
|
||
|
||
**前端渲染**:
|
||
|
||
```
|
||
Source: [下拉选择框 ▼]
|
||
├── plate_1 (96孔板)
|
||
├── tiprack_1 (枪头架)
|
||
├── reservoir_1 (试剂槽)
|
||
└── ...
|
||
```
|
||
|
||
#### 多选资源
|
||
|
||
```yaml
|
||
placeholder_keys:
|
||
targets: unilabos_resources
|
||
```
|
||
|
||
**前端渲染**:
|
||
|
||
```
|
||
Targets: [多选下拉框 ▼]
|
||
☑ plate_1 (96孔板)
|
||
☐ plate_2 (384孔板)
|
||
☑ plate_3 (96孔板)
|
||
└── ...
|
||
```
|
||
|
||
#### 单选设备
|
||
|
||
```yaml
|
||
placeholder_keys:
|
||
pump: unilabos_devices
|
||
```
|
||
|
||
**前端渲染**:
|
||
|
||
```
|
||
Pump: [下拉选择框 ▼]
|
||
├── pump_1 (注射泵A)
|
||
├── pump_2 (注射泵B)
|
||
└── ...
|
||
```
|
||
|
||
#### 多选设备
|
||
|
||
```yaml
|
||
placeholder_keys:
|
||
sync_devices: unilabos_devices
|
||
```
|
||
|
||
**前端渲染**:
|
||
|
||
```
|
||
Sync Devices: [多选下拉框 ▼]
|
||
☑ heater_1 (加热器A)
|
||
☑ stirrer_1 (搅拌器)
|
||
☐ pump_1 (注射泵)
|
||
```
|
||
|
||
### 手动配置 placeholder_keys
|
||
|
||
如果需要手动添加或覆盖自动生成的 placeholder_keys:
|
||
|
||
#### 场景 1: 非标准参数名
|
||
|
||
```yaml
|
||
action_value_mappings:
|
||
custom_action:
|
||
goal:
|
||
my_custom_resource_param: resource_param
|
||
my_device_param: device_param
|
||
placeholder_keys:
|
||
my_custom_resource_param: unilabos_resources
|
||
my_device_param: unilabos_devices
|
||
```
|
||
|
||
#### 场景 2: 混合类型
|
||
|
||
```python
|
||
def mixed_params(
|
||
self,
|
||
resource: ResourceSlot,
|
||
normal_param: str,
|
||
device: DeviceSlot
|
||
):
|
||
pass
|
||
```
|
||
|
||
```yaml
|
||
placeholder_keys:
|
||
resource: unilabos_resources # 资源选择
|
||
device: unilabos_devices # 设备选择
|
||
# normal_param不需要placeholder_keys
|
||
```
|
||
|
||
#### 场景 3: 自定义选择器
|
||
|
||
```yaml
|
||
placeholder_keys:
|
||
special_param: custom_selector # 使用自定义选择器
|
||
```
|
||
|
||
## 系统自动生成的字段
|
||
|
||
### status_types
|
||
|
||
系统会扫描你的 Python 类,从状态方法(property 或 get\_方法)自动生成这部分:
|
||
|
||
```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
|
||
```
|
||
|
||
**注意事项**:
|
||
|
||
- 系统会查找所有 `get_` 开头的方法和 `@property` 装饰的属性
|
||
- 类型会自动转成相应的类型(如 `str`、`float`、`bool`)
|
||
- 如果类型是 `Any`、`None` 或未知的,默认使用 `String`
|
||
|
||
### init_param_schema
|
||
|
||
完全由系统自动生成,无需手动编写:
|
||
|
||
```yaml
|
||
init_param_schema:
|
||
config: # 从 __init__ 方法分析得出
|
||
properties:
|
||
port:
|
||
type: string
|
||
default: '/dev/ttyUSB0'
|
||
baudrate:
|
||
type: integer
|
||
default: 9600
|
||
required: []
|
||
type: object
|
||
|
||
data: # 根据 status_types 生成的前端类型定义
|
||
properties:
|
||
current_temperature:
|
||
type: number
|
||
is_heating:
|
||
type: boolean
|
||
status:
|
||
type: string
|
||
required:
|
||
- status
|
||
type: object
|
||
```
|
||
|
||
**生成规则**:
|
||
|
||
- `config` 部分:分析 `__init__` 方法的参数、类型和默认值
|
||
- `data` 部分:根据 `status_types` 生成前端显示用的类型定义
|
||
|
||
### 其他自动填充的字段
|
||
|
||
```yaml
|
||
version: '1.0.0' # 默认版本
|
||
category: ['文件名'] # 使用 yaml 文件名作为类别
|
||
description: '' # 默认为空
|
||
icon: '' # 默认为空
|
||
handles: [] # 默认空数组
|
||
config_info: [] # 默认空数组
|
||
file_path: '/path/to/file' # 系统自动填写
|
||
registry_type: 'device' # 自动设为设备类型
|
||
```
|
||
|
||
### handles 字段
|
||
|
||
定义设备连接关系:
|
||
|
||
```yaml
|
||
handles: # 大多数情况为空,除非设备需要特定连接
|
||
- handler_key: device_output
|
||
label: Device Output
|
||
data_type: resource
|
||
data_source: value
|
||
data_key: default_value
|
||
```
|
||
|
||
### 可选配置字段
|
||
|
||
```yaml
|
||
description: '设备的详细描述'
|
||
|
||
icon: 'device_icon.webp' # 设备图标文件名(会上传到OSS)
|
||
|
||
version: '0.0.1' # 版本号
|
||
|
||
category: # 设备分类,前端用于分组显示
|
||
- 'heating'
|
||
- 'cooling'
|
||
- 'temperature_control'
|
||
|
||
config_info: # 嵌套配置,用于包含子设备
|
||
- children:
|
||
- opentrons_24_tuberack_nest_1point5ml_snapcap_A1
|
||
- other_nested_component
|
||
```
|
||
|
||
## 完整示例
|
||
|
||
### Python 驱动代码
|
||
|
||
```python
|
||
# unilabos/devices/my_lab/liquid_handler.py
|
||
|
||
from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot
|
||
from typing import List, Dict, Any, Optional
|
||
|
||
class AdvancedLiquidHandler:
|
||
"""高级液体处理工作站"""
|
||
|
||
def __init__(self, config: Dict[str, Any]):
|
||
self.simulation = config.get('simulation', False)
|
||
self._status = "idle"
|
||
self._temperature = 25.0
|
||
|
||
@property
|
||
def status(self) -> str:
|
||
"""设备状态"""
|
||
return self._status
|
||
|
||
@property
|
||
def temperature(self) -> float:
|
||
"""当前温度"""
|
||
return self._temperature
|
||
|
||
def transfer(
|
||
self,
|
||
source: ResourceSlot,
|
||
target: ResourceSlot,
|
||
volume: float,
|
||
tip: Optional[ResourceSlot] = None
|
||
) -> Dict[str, Any]:
|
||
"""转移液体"""
|
||
return {"success": True}
|
||
|
||
def multi_transfer(
|
||
self,
|
||
source: ResourceSlot,
|
||
targets: List[ResourceSlot],
|
||
volumes: List[float]
|
||
) -> Dict[str, Any]:
|
||
"""多目标转移"""
|
||
return {"success": True}
|
||
|
||
def coordinate_with_heater(
|
||
self,
|
||
plate: ResourceSlot,
|
||
heater: DeviceSlot,
|
||
temperature: float
|
||
) -> Dict[str, Any]:
|
||
"""与加热器协同"""
|
||
return {"success": True}
|
||
```
|
||
|
||
### 生成的完整注册表
|
||
|
||
```yaml
|
||
# unilabos/registry/devices/advanced_liquid_handler.yaml
|
||
|
||
advanced_liquid_handler:
|
||
class:
|
||
module: unilabos.devices.my_lab.liquid_handler:AdvancedLiquidHandler
|
||
type: python
|
||
|
||
# 自动提取的状态类型
|
||
status_types:
|
||
status: str
|
||
temperature: float
|
||
|
||
# 自动生成的初始化参数
|
||
init_param_schema:
|
||
config:
|
||
properties:
|
||
simulation:
|
||
type: boolean
|
||
default: false
|
||
type: object
|
||
data:
|
||
properties:
|
||
status:
|
||
type: string
|
||
temperature:
|
||
type: number
|
||
required:
|
||
- status
|
||
type: object
|
||
|
||
# 动作映射
|
||
action_value_mappings:
|
||
transfer:
|
||
type: UniLabJsonCommand
|
||
goal:
|
||
source: source
|
||
target: target
|
||
volume: volume
|
||
tip: tip
|
||
goal_default:
|
||
source: {}
|
||
target: {}
|
||
volume: 0.0
|
||
tip: null
|
||
placeholder_keys:
|
||
source: unilabos_resources # 自动添加
|
||
target: unilabos_resources # 自动添加
|
||
tip: unilabos_resources # 自动添加
|
||
result:
|
||
success: success
|
||
schema:
|
||
description: '转移液体'
|
||
properties:
|
||
goal:
|
||
properties:
|
||
source:
|
||
type: object
|
||
description: '源容器'
|
||
target:
|
||
type: object
|
||
description: '目标容器'
|
||
volume:
|
||
type: number
|
||
description: '体积(μL)'
|
||
tip:
|
||
type: object
|
||
description: '枪头(可选)'
|
||
required:
|
||
- source
|
||
- target
|
||
- volume
|
||
type: object
|
||
required:
|
||
- goal
|
||
type: object
|
||
|
||
multi_transfer:
|
||
type: UniLabJsonCommand
|
||
goal:
|
||
source: source
|
||
targets: targets
|
||
volumes: volumes
|
||
placeholder_keys:
|
||
source: unilabos_resources # 单选
|
||
targets: unilabos_resources # 多选
|
||
result:
|
||
success: success
|
||
|
||
coordinate_with_heater:
|
||
type: UniLabJsonCommand
|
||
goal:
|
||
plate: plate
|
||
heater: heater
|
||
temperature: temperature
|
||
placeholder_keys:
|
||
plate: unilabos_resources # 资源选择
|
||
heater: unilabos_devices # 设备选择
|
||
result:
|
||
success: success
|
||
|
||
description: '高级液体处理工作站,支持多目标转移和设备协同'
|
||
version: '1.0.0'
|
||
category:
|
||
- liquid_handling
|
||
handles: []
|
||
icon: ''
|
||
```
|
||
|
||
### 另一个完整示例:温度控制器
|
||
|
||
```yaml
|
||
my_temperature_controller:
|
||
class:
|
||
action_value_mappings:
|
||
heat_start:
|
||
type: HeatChillStart
|
||
goal:
|
||
target_temp: temp
|
||
vessel: vessel
|
||
goal_default:
|
||
target_temp: 25.0
|
||
vessel: ''
|
||
handles:
|
||
output:
|
||
- handler_key: heated_sample
|
||
label: Heated Sample
|
||
data_type: resource
|
||
data_source: handle
|
||
data_key: sample
|
||
placeholder_keys:
|
||
vessel: unilabos_resources
|
||
result:
|
||
status: status
|
||
success: success
|
||
schema:
|
||
description: '启动加热功能'
|
||
properties:
|
||
goal:
|
||
properties:
|
||
target_temp:
|
||
type: number
|
||
description: '目标温度'
|
||
vessel:
|
||
type: string
|
||
description: '容器标识'
|
||
required:
|
||
- target_temp
|
||
- vessel
|
||
title: HeatStart_Goal
|
||
type: object
|
||
required:
|
||
- goal
|
||
title: HeatStart
|
||
type: object
|
||
feedback: {}
|
||
|
||
stop:
|
||
type: UniLabJsonCommand
|
||
goal: {}
|
||
goal_default: {}
|
||
handles: {}
|
||
result:
|
||
status: status
|
||
schema:
|
||
description: '停止设备'
|
||
properties:
|
||
goal:
|
||
type: object
|
||
title: Stop_Goal
|
||
title: Stop
|
||
type: object
|
||
feedback: {}
|
||
|
||
module: unilabos.devices.temperature.my_controller:MyTemperatureController
|
||
status_types:
|
||
current_temperature: float
|
||
target_temperature: float
|
||
is_heating: bool
|
||
is_cooling: bool
|
||
status: str
|
||
vessel: str
|
||
type: python
|
||
|
||
description: '我的温度控制器设备'
|
||
handles: []
|
||
icon: 'temperature_controller.webp'
|
||
init_param_schema:
|
||
config:
|
||
properties:
|
||
port:
|
||
default: '/dev/ttyUSB0'
|
||
type: string
|
||
baudrate:
|
||
default: 9600
|
||
type: number
|
||
required: []
|
||
type: object
|
||
data:
|
||
properties:
|
||
current_temperature:
|
||
type: number
|
||
target_temperature:
|
||
type: number
|
||
is_heating:
|
||
type: boolean
|
||
is_cooling:
|
||
type: boolean
|
||
status:
|
||
type: string
|
||
vessel:
|
||
type: string
|
||
required:
|
||
- current_temperature
|
||
- target_temperature
|
||
- status
|
||
type: object
|
||
|
||
version: '1.0.0'
|
||
category:
|
||
- 'temperature_control'
|
||
- 'heating'
|
||
config_info: []
|
||
```
|
||
|
||
## 部署和使用
|
||
|
||
### Python 驱动类要求
|
||
|
||
你的设备类需要符合以下要求:
|
||
|
||
```python
|
||
from unilabos.common.device_base import DeviceBase
|
||
|
||
class MyDevice(DeviceBase):
|
||
def __init__(self, config):
|
||
"""初始化,参数会自动分析到 init_param_schema.config"""
|
||
super().__init__(config)
|
||
self.port = config.get('port', '/dev/ttyUSB0')
|
||
|
||
# 状态方法(会自动生成到 status_types)
|
||
@property
|
||
def status(self):
|
||
"""返回设备状态"""
|
||
return "idle"
|
||
|
||
@property
|
||
def temperature(self):
|
||
"""返回当前温度"""
|
||
return 25.0
|
||
|
||
# 动作方法(会自动生成 auto- 开头的动作)
|
||
async def start_heating(self, temperature: float):
|
||
"""开始加热到指定温度"""
|
||
pass
|
||
|
||
def stop(self):
|
||
"""停止操作"""
|
||
pass
|
||
```
|
||
|
||
### 方法一:使用编辑器(推荐)
|
||
|
||
1. 先编写 Python 驱动类
|
||
2. 使用注册表编辑器自动生成 yaml 配置
|
||
3. 保存生成的文件到 `devices/` 目录
|
||
4. 重启 UniLabOS 即可使用
|
||
|
||
### 方法二:手动编写(简化版)
|
||
|
||
1. 创建最小配置:
|
||
|
||
```yaml
|
||
# devices/my_device.yaml
|
||
my_device:
|
||
class:
|
||
module: unilabos.devices.my_module.my_device:MyDevice
|
||
type: python
|
||
```
|
||
|
||
2. 启动系统时使用 `--complete_registry` 参数,让系统自动补全
|
||
|
||
3. 检查生成的配置是否符合预期
|
||
|
||
### 系统集成
|
||
|
||
1. 将 yaml 文件放到 `unilabos/registry/devices/` 目录
|
||
2. 系统启动时会自动扫描并加载设备
|
||
3. 系统会自动补全所有缺失的字段
|
||
4. 设备即可在前端界面中使用
|
||
|
||
### 高级配置
|
||
|
||
如果需要特殊设置,可以手动添加:
|
||
|
||
```yaml
|
||
my_device:
|
||
class:
|
||
module: unilabos.devices.my_module.my_device:MyDevice
|
||
type: python
|
||
action_value_mappings:
|
||
# 自定义动作
|
||
special_command:
|
||
type: UniLabJsonCommand
|
||
goal: {}
|
||
result: {}
|
||
|
||
# 可选的自定义配置
|
||
description: '我的特殊设备'
|
||
icon: 'my_device.webp'
|
||
category: ['temperature', 'heating']
|
||
```
|
||
|
||
## 调试和验证
|
||
|
||
### 1. 检查生成的注册表
|
||
|
||
```bash
|
||
# 使用complete_registry生成
|
||
unilab -g dev.json --complete_registry
|
||
|
||
# 查看生成的文件
|
||
cat unilabos/registry/devices/my_device.yaml
|
||
```
|
||
|
||
### 2. 验证 placeholder_keys
|
||
|
||
确认:
|
||
|
||
- ResourceSlot 参数有 `unilabos_resources`
|
||
- DeviceSlot 参数有 `unilabos_devices`
|
||
- List 类型被正确识别
|
||
|
||
### 3. 测试前端效果
|
||
|
||
1. 启动 Uni-Lab
|
||
2. 访问 Web 界面
|
||
3. 选择设备
|
||
4. 调用动作
|
||
5. 检查是否显示正确的选择器
|
||
|
||
### 4. 检查文件路径和导入
|
||
|
||
```bash
|
||
# 确认模块路径正确
|
||
python -c "from unilabos.devices.my_module.my_device import MyDevice"
|
||
```
|
||
|
||
## 常见问题
|
||
|
||
### Q1: placeholder_keys 没有自动生成
|
||
|
||
**检查**:
|
||
|
||
1. 是否使用了`--complete_registry`参数?
|
||
2. 类型注解是否正确?
|
||
|
||
```python
|
||
# ✓ 正确
|
||
def method(self, resource: ResourceSlot):
|
||
|
||
# ✗ 错误(缺少类型注解)
|
||
def method(self, resource):
|
||
```
|
||
|
||
3. 是否正确导入?
|
||
```python
|
||
from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot
|
||
```
|
||
|
||
### Q2: 前端显示普通输入框而不是选择器
|
||
|
||
**原因**: placeholder_keys 未正确配置
|
||
|
||
**解决**:
|
||
|
||
```yaml
|
||
# 检查YAML中是否有
|
||
placeholder_keys:
|
||
resource: unilabos_resources
|
||
```
|
||
|
||
### Q3: 多选不工作
|
||
|
||
**检查类型注解**:
|
||
|
||
```python
|
||
# ✓ 正确 - 会生成多选
|
||
def method(self, resources: List[ResourceSlot]):
|
||
|
||
# ✗ 错误 - 会生成单选
|
||
def method(self, resources: ResourceSlot):
|
||
```
|
||
|
||
### Q4: 运行时收到错误的类型
|
||
|
||
**说明**: 运行时会自动转换
|
||
|
||
前端传递:
|
||
|
||
```json
|
||
{
|
||
"resource": "plate_1" // 字符串ID
|
||
}
|
||
```
|
||
|
||
运行时收到:
|
||
|
||
```python
|
||
resource.id # "plate_1"
|
||
resource.name # "96孔板"
|
||
resource.type # "resource"
|
||
# 完整的Resource对象
|
||
```
|
||
|
||
### Q5: 设备加载不了
|
||
|
||
**检查**:
|
||
|
||
1. 确认 `class.module` 路径是否正确
|
||
2. 确认 Python 驱动类能否正常导入
|
||
3. 使用 yaml 验证器检查文件格式
|
||
4. 查看 UniLabOS 启动日志中的错误信息
|
||
|
||
### Q6: 自动生成失败
|
||
|
||
**检查**:
|
||
|
||
1. 确认类继承了正确的基类
|
||
2. 确保状态方法的返回类型注解清晰
|
||
3. 检查类能否被动态导入
|
||
4. 确认启用了 `complete_registry=True`
|
||
|
||
### Q7: 前端显示问题
|
||
|
||
**解决步骤**:
|
||
|
||
1. 删除旧的 yaml 文件,用编辑器重新生成
|
||
2. 清除浏览器缓存,重新加载页面
|
||
3. 确认必需字段(如 `schema`)都存在
|
||
4. 检查 `goal_default` 和 `schema` 的数据类型是否一致
|
||
|
||
### Q8: 动作执行出错
|
||
|
||
**检查**:
|
||
|
||
1. 确认动作方法名符合规范(如 `execute_<action_name>`)
|
||
2. 检查 `goal` 字段的参数映射是否正确
|
||
3. 确认方法返回值格式符合 `result` 映射
|
||
4. 在驱动类中添加异常处理
|
||
|
||
## 最佳实践
|
||
|
||
### 开发流程
|
||
|
||
1. **优先使用编辑器**:除非有特殊需求,否则优先使用注册表编辑器
|
||
2. **最小化配置**:手动配置时只定义必要字段,让系统自动生成其他内容
|
||
3. **增量开发**:先创建基本配置,后续根据需要添加特殊动作
|
||
4. **及时测试**:每次修改后及时在开发环境测试
|
||
|
||
### 代码规范
|
||
|
||
1. **始终使用类型注解**
|
||
|
||
```python
|
||
# ✓ 好
|
||
def method(self, resource: ResourceSlot, device: DeviceSlot):
|
||
pass
|
||
|
||
# ✗ 差
|
||
def method(self, resource, device):
|
||
pass
|
||
```
|
||
|
||
2. **提供有意义的参数名**
|
||
|
||
```python
|
||
# ✓ 好 - 清晰的参数名
|
||
def transfer(self, source: ResourceSlot, target: ResourceSlot):
|
||
pass
|
||
|
||
# ✗ 差 - 模糊的参数名
|
||
def transfer(self, r1: ResourceSlot, r2: ResourceSlot):
|
||
pass
|
||
```
|
||
|
||
3. **使用 Optional 表示可选参数**
|
||
|
||
```python
|
||
from typing import Optional
|
||
|
||
def method(
|
||
self,
|
||
required_resource: ResourceSlot,
|
||
optional_resource: Optional[ResourceSlot] = None
|
||
):
|
||
pass
|
||
```
|
||
|
||
4. **添加详细的文档字符串**
|
||
|
||
```python
|
||
def method(
|
||
self,
|
||
source: ResourceSlot, # 源容器
|
||
targets: List[ResourceSlot] # 目标容器列表
|
||
) -> Dict[str, Any]:
|
||
"""方法说明
|
||
|
||
Args:
|
||
source: 源容器,必须包含足够的液体
|
||
targets: 目标容器列表,每个容器应该为空
|
||
|
||
Returns:
|
||
包含操作结果的字典
|
||
"""
|
||
pass
|
||
```
|
||
|
||
5. **方法命名规范**
|
||
|
||
- 状态方法使用 `@property` 装饰器或 `get_` 前缀
|
||
- 动作方法使用动词开头
|
||
- 保持命名清晰、一致
|
||
|
||
6. **完善的错误处理**
|
||
- 实现完善的错误处理
|
||
- 添加日志记录
|
||
- 提供有意义的错误信息
|
||
|
||
### 配置管理
|
||
|
||
1. **版本控制**:所有 yaml 文件纳入版本控制
|
||
2. **命名一致性**:设备 ID、文件名、类名保持一致的命名风格
|
||
3. **定期更新**:定期运行完整注册以更新自动生成的字段
|
||
4. **备份配置**:在修改前备份重要的手动配置
|
||
5. **文档同步**:保持配置文件和文档的同步更新
|
||
|
||
### 测试验证
|
||
|
||
1. **本地测试**:在本地环境充分测试后再部署
|
||
2. **渐进部署**:先部署到测试环境,验证无误后再上生产环境
|
||
3. **监控日志**:密切监控设备加载和运行日志
|
||
4. **回滚准备**:准备快速回滚机制,以应对紧急情况
|
||
5. **自动化测试**:编写单元测试和集成测试
|
||
|
||
### 性能优化
|
||
|
||
1. **按需加载**:只加载实际使用的设备类型
|
||
2. **缓存利用**:充分利用系统的注册表缓存机制
|
||
3. **资源管理**:合理管理设备连接和资源占用
|
||
4. **监控指标**:设置关键性能指标的监控和告警
|
||
|
||
## 参考资料
|
||
|
||
- {doc}`add_device` - 设备驱动编写指南
|
||
- {doc}`04_add_device_testing` - 设备测试指南
|
||
- Python [typing 模块](https://docs.python.org/3/library/typing.html)
|
||
- [YAML 语法](https://yaml.org/)
|
||
- [JSON Schema](https://json-schema.org/)
|