# yaml 注册表编写指南

## 快速开始：使用注册表编辑器

推荐使用 UniLabOS 自带的可视化编辑器，它能帮你自动生成大部分配置，省去手写的麻烦。

### 怎么用编辑器

1. 启动 UniLabOS
2. 在浏览器中打开"注册表编辑器"页面
3. 选择你的 Python 设备驱动文件
4. 点击"分析文件"，让系统读取你的类信息
5. 填写一些基本信息（设备描述、图标啥的）
6. 点击"生成注册表"，复制生成的内容
7. 把内容保存到 `devices/` 目录下

我们为你准备了一个测试驱动，用于在界面上尝试注册表生成，参见目录：test\registry\example_devices.py

---

## 手动编写指南

如果你想自己写 yaml 文件，或者想深入了解结构，查阅下方说明。

## 注册表的基本结构

yaml 注册表就是设备的配置文件，里面定义了设备怎么用、有什么功能。好消息是系统会自动帮你填大部分内容，你只需要写两个必需的东西：设备名和 class 信息。

### 各字段用途

| 字段名            | 类型   | 需要手写 | 说明                                |
| ----------------- | ------ | -------- | ----------------------------------- |
| 设备标识符        | 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 | 部分     | 动作配置，系统会自动生成一些基础的 |

## 怎么创建新的注册表

### 创建文件

在 devices 文件夹里新建一个 yaml 文件，比如 `new_device.yaml`。

### 完整结构是什么样的

```yaml
new_device: # 设备名，要唯一
  class: # 核心配置
    action_value_mappings: # 动作配置（后面会详细说）
      action_name:
        # 具体的动作设置
    module: unilabos.devices.your_module.new_device:NewDeviceClass # 你的 Python 类
    status_types: # 状态类型（系统会自动生成）
      status: str
      temperature: float
      # 其他状态
    type: python # 驱动类型，一般就是 python

  description: New Device Description # 设备描述
  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

  version: 0.0.1 # 版本号
  category:
    - device_category # 设备类别
  config_info: [] # 嵌套配置，通常为空
```

## 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`：加热冷却动作

### 复杂一点的例子

```yaml
heat_chill_start:
  type: HeatChillStart
  goal:
    purpose: purpose
    temp: temp
  goal_default: # ROS动作会自动生成，你也可以手动覆盖
    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 系统会自动生成，不用写
```

### 动作名字怎么起

根据设备用途来起名字：

- 启动停止类：`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`

### 常用的动作类型

- `UniLabJsonCommand`：自定义 JSON 命令（不走 ROS）
- `UniLabJsonCommandAsync`：异步 JSON 命令（不走 ROS）
- `SendCmd`：发送简单命令
- `NavigateThroughPoses`：导航相关
- `SingleJointPosition`：单关节控制
- `Stir`：搅拌
- `HeatChill`、`HeatChillStart`：加热冷却
- 其他的 ROS 动作类型：看具体的 ROS 服务

### 示例：完整的动作配置

```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: {}
```

## 系统自动生成的字段

### status_types

系统会扫描你的 Python 类，从状态方法自动生成这部分：

```yaml
status_types:
  current_temperature: float # 从 get_current_temperature() 方法来的
  is_heating: bool # 从 get_is_heating() 方法来的
  status: str # 从 get_status() 方法来的
```

注意几点：

- 系统会找所有 `get_` 开头的方法
- 类型会自动转成 ROS 类型（比如 `str` 变成 `String`）
- 如果类型是 `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 字段

这个是定义设备连接关系的，类似动作里的 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
```

## 完整的例子

这里是一个比较完整的设备配置示例：

```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: []
```

## 怎么部署和使用

### 方法一：用编辑器（推荐）

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=True` 参数，让系统自动补全

3. 检查一下生成的配置是不是你想要的

### 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）
    def get_status(self):
        """返回设备状态"""
        return "idle"

    def get_temperature(self):
        """返回当前温度"""
        return 25.0

    # 动作方法（会自动生成 auto- 开头的动作）
    async def start_heating(self, temperature: float):
        """开始加热到指定温度"""
        pass

    def stop(self):
        """停止操作"""
        pass
```

### 系统集成

1. 把 yaml 文件放到 `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. 检查模块路径：确认 `class.module` 路径写对了
2. 确认类能导入：看看你的 Python 驱动类能不能正常导入
3. 检查语法：用 yaml 验证器看看文件格式对不对
4. 查看日志：看 UniLabOS 启动时有没有报错信息

### 自动生成失败了

1. 类分析出问题：确认你的类继承了正确的基类
2. 方法类型不明确：确保状态方法的返回类型写清楚了
3. 导入有问题：检查类能不能被动态导入
4. 没开完整注册：确认启用了 `complete_registry=True`

### 前端显示有问题

1. 重新生成：删掉旧的 yaml 文件，用编辑器重新生成
2. 清除缓存：清除浏览器缓存，重新加载页面
3. 检查字段：确认必需的字段（比如 `schema`）都有
4. 验证数据：检查 `goal_default` 和 `schema` 的数据类型是不是一致

### 动作执行出错

1. 方法名不对：确认动作方法名符合规范（比如 `execute_<action_name>`）
2. 参数映射错误：检查 `goal` 字段的参数映射是否正确
3. 返回格式不对：确认方法返回值格式符合 `result` 映射
4. 没异常处理：在驱动类里加上异常处理

## 最佳实践

### 开发流程

1. **优先使用编辑器**：除非有特殊需求，否则优先使用注册表编辑器
2. **最小化配置**：手动配置时只定义必要字段，让系统自动生成其他内容
3. **增量开发**：先创建基本配置，后续根据需要添加特殊动作

### 代码规范

1. **方法命名**：状态方法使用 `get_` 前缀，动作方法使用动词开头
2. **类型注解**：为方法参数和返回值添加类型注解
3. **文档字符串**：为类和方法添加详细的文档字符串
4. **异常处理**：实现完善的错误处理和日志记录

### 配置管理

1. **版本控制**：所有 yaml 文件纳入版本控制
2. **命名一致性**：设备 ID、文件名、类名保持一致的命名风格
3. **定期更新**：定期运行完整注册以更新自动生成的字段
4. **备份配置**：在修改前备份重要的手动配置

### 测试验证

1. **本地测试**：在本地环境充分测试后再部署
2. **渐进部署**：先部署到测试环境，验证无误后再上生产环境
3. **监控日志**：密切监控设备加载和运行日志
4. **回滚准备**：准备快速回滚机制，以应对紧急情况

### 性能优化

1. **按需加载**：只加载实际使用的设备类型
2. **缓存利用**：充分利用系统的注册表缓存机制
3. **资源管理**：合理管理设备连接和资源占用
4. **监控指标**：设置关键性能指标的监控和告警
