添加设备:注册表配置完整指南

本文档说明如何为设备创建和配置注册表,包括基本结构、特殊类型识别、动作配置等内容。

概述

注册表(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

部分

动作配置,系统会自动生成一些基础的

基本结构示例

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 参数(开发调试)

适合开发阶段,自动补全配置。

# 启动时自动补全注册表
unilab -g dev.json --complete_registry --registry_path ./my_registry

系统会:

  1. 扫描 Python 类

  2. 分析方法签名和类型

  3. 自动生成缺失的字段

  4. 保存到注册表文件

或者在代码中

# 启动系统时使用参数
启动系统时用 complete_registry=True 参数让系统自动补全

方式 3: 手动编写(高级)

适合需要精细控制或特殊需求的场景。

最小化配置示例

# 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:搅拌动作

  • HeatChillHeatChillStart:加热冷却动作

动作命名建议

根据设备用途来起名字:

  • 启动停止类startstoppauseresume

  • 设置参数类set_speedset_temperatureset_timer

  • 移动控制类move_to_positionmove_through_points

  • 功能操作类stirheat_chill_startheat_chill_stop

  • 开关控制类valve_open_cmdvalve_close_cmdpush_to

  • 命令执行类send_nav_taskexecute_command_from_outer

动作配置示例

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 驱动代码示例

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):

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 效果

单选资源

placeholder_keys:
  source: unilabos_resources

前端渲染:

Source: [下拉选择框 ▼]
        ├── plate_1 (96孔板)
        ├── tiprack_1 (枪头架)
        ├── reservoir_1 (试剂槽)
        └── ...

多选资源

placeholder_keys:
  targets: unilabos_resources

前端渲染:

Targets: [多选下拉框 ▼]
         ☑ plate_1 (96孔板)
         ☐ plate_2 (384孔板)
         ☑ plate_3 (96孔板)
         └── ...

单选设备

placeholder_keys:
  pump: unilabos_devices

前端渲染:

Pump: [下拉选择框 ▼]
      ├── pump_1 (注射泵A)
      ├── pump_2 (注射泵B)
      └── ...

多选设备

placeholder_keys:
  sync_devices: unilabos_devices

前端渲染:

Sync Devices: [多选下拉框 ▼]
              ☑ heater_1 (加热器A)
              ☑ stirrer_1 (搅拌器)
              ☐ pump_1 (注射泵)

手动配置 placeholder_keys

如果需要手动添加或覆盖自动生成的 placeholder_keys:

场景 1: 非标准参数名

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: 混合类型

def mixed_params(
    self,
    resource: ResourceSlot,
    normal_param: str,
    device: DeviceSlot
):
    pass
placeholder_keys:
  resource: unilabos_resources # 资源选择
  device: unilabos_devices # 设备选择
  # normal_param不需要placeholder_keys

场景 3: 自定义选择器

placeholder_keys:
  special_param: custom_selector # 使用自定义选择器

系统自动生成的字段

status_types

系统会扫描你的 Python 类,从状态方法(property 或 get_方法)自动生成这部分:

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 装饰的属性

  • 类型会自动转成相应的类型(如 strfloatbool

  • 如果类型是 AnyNone 或未知的,默认使用 String

init_param_schema

完全由系统自动生成,无需手动编写:

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 生成前端显示用的类型定义

其他自动填充的字段

version: '1.0.0' # 默认版本
category: ['文件名'] # 使用 yaml 文件名作为类别
description: '' # 默认为空
icon: '' # 默认为空
handles: [] # 默认空数组
config_info: [] # 默认空数组
file_path: '/path/to/file' # 系统自动填写
registry_type: 'device' # 自动设为设备类型

handles 字段

定义设备连接关系:

handles: # 大多数情况为空,除非设备需要特定连接
  - handler_key: device_output
    label: Device Output
    data_type: resource
    data_source: value
    data_key: default_value

可选配置字段

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 驱动代码

# 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}

生成的完整注册表

# 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: ''

另一个完整示例:温度控制器

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 驱动类要求

你的设备类需要符合以下要求:

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. 创建最小配置:

# devices/my_device.yaml
my_device:
  class:
    module: unilabos.devices.my_module.my_device:MyDevice
    type: python
  1. 启动系统时使用 --complete_registry 参数,让系统自动补全

  2. 检查生成的配置是否符合预期

系统集成

  1. 将 yaml 文件放到 unilabos/registry/devices/ 目录

  2. 系统启动时会自动扫描并加载设备

  3. 系统会自动补全所有缺失的字段

  4. 设备即可在前端界面中使用

高级配置

如果需要特殊设置,可以手动添加:

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. 检查生成的注册表

# 使用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. 检查文件路径和导入

# 确认模块路径正确
python -c "from unilabos.devices.my_module.my_device import MyDevice"

常见问题

Q1: placeholder_keys 没有自动生成

检查:

  1. 是否使用了--complete_registry参数?

  2. 类型注解是否正确?

    # ✓ 正确
    def method(self, resource: ResourceSlot):
    
    # ✗ 错误(缺少类型注解)
    def method(self, resource):
    
  3. 是否正确导入?

    from unilabos.registry.placeholder_type import ResourceSlot, DeviceSlot
    

Q2: 前端显示普通输入框而不是选择器

原因: placeholder_keys 未正确配置

解决:

# 检查YAML中是否有
placeholder_keys:
  resource: unilabos_resources

Q3: 多选不工作

检查类型注解:

# ✓ 正确 - 会生成多选
def method(self, resources: List[ResourceSlot]):

# ✗ 错误 - 会生成单选
def method(self, resources: ResourceSlot):

Q4: 运行时收到错误的类型

说明: 运行时会自动转换

前端传递:

{
  "resource": "plate_1" // 字符串ID
}

运行时收到:

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_defaultschema 的数据类型是否一致

Q8: 动作执行出错

检查:

  1. 确认动作方法名符合规范(如 execute_<action_name>

  2. 检查 goal 字段的参数映射是否正确

  3. 确认方法返回值格式符合 result 映射

  4. 在驱动类中添加异常处理

最佳实践

开发流程

  1. 优先使用编辑器:除非有特殊需求,否则优先使用注册表编辑器

  2. 最小化配置:手动配置时只定义必要字段,让系统自动生成其他内容

  3. 增量开发:先创建基本配置,后续根据需要添加特殊动作

  4. 及时测试:每次修改后及时在开发环境测试

代码规范

  1. 始终使用类型注解

# ✓ 好
def method(self, resource: ResourceSlot, device: DeviceSlot):
    pass

# ✗ 差
def method(self, resource, device):
    pass
  1. 提供有意义的参数名

# ✓ 好 - 清晰的参数名
def transfer(self, source: ResourceSlot, target: ResourceSlot):
    pass

# ✗ 差 - 模糊的参数名
def transfer(self, r1: ResourceSlot, r2: ResourceSlot):
    pass
  1. 使用 Optional 表示可选参数

from typing import Optional

def method(
    self,
    required_resource: ResourceSlot,
    optional_resource: Optional[ResourceSlot] = None
):
    pass
  1. 添加详细的文档字符串

def method(
    self,
    source: ResourceSlot,        # 源容器
    targets: List[ResourceSlot]  # 目标容器列表
) -> Dict[str, Any]:
    """方法说明

    Args:
        source: 源容器,必须包含足够的液体
        targets: 目标容器列表,每个容器应该为空

    Returns:
        包含操作结果的字典
    """
    pass
  1. 方法命名规范

    • 状态方法使用 @property 装饰器或 get_ 前缀

    • 动作方法使用动词开头

    • 保持命名清晰、一致

  2. 完善的错误处理

    • 实现完善的错误处理

    • 添加日志记录

    • 提供有意义的错误信息

配置管理

  1. 版本控制:所有 yaml 文件纳入版本控制

  2. 命名一致性:设备 ID、文件名、类名保持一致的命名风格

  3. 定期更新:定期运行完整注册以更新自动生成的字段

  4. 备份配置:在修改前备份重要的手动配置

  5. 文档同步:保持配置文件和文档的同步更新

测试验证

  1. 本地测试:在本地环境充分测试后再部署

  2. 渐进部署:先部署到测试环境,验证无误后再上生产环境

  3. 监控日志:密切监控设备加载和运行日志

  4. 回滚准备:准备快速回滚机制,以应对紧急情况

  5. 自动化测试:编写单元测试和集成测试

性能优化

  1. 按需加载:只加载实际使用的设备类型

  2. 缓存利用:充分利用系统的注册表缓存机制

  3. 资源管理:合理管理设备连接和资源占用

  4. 监控指标:设置关键性能指标的监控和告警

参考资料