Files
Uni-Lab-OS/docs/user_guide/graph_files.md
Xuwznln ccbf5378dd update workbench example
update aksk desc

print res query logs

Fix skills exec error with action type

Update Skills

Update Skills addr

Change uni-lab. to leap-lab.
Support unit in pylabrobot

Support async func.

change to leap-lab backend. Support feedback interval. Reduce cocurrent lags.

fix create_resource_with_slot

update unilabos_formulation & batch-submit-exp

scale multi exec thread up to 48

update handle creation api

fit cocurrent gap

add running status debounce

allow non @topic_config support

update skill

add placeholder keys

always free

提交实验技能

disable samples

correct sample demo ret value

新增试剂reagent

update registry

新增manual_confirm

add workstation creation skill

add virtual_sample_demo 样品追踪测试设备

add external devices param
fix registry upload missing type

fast registry load

minor fix on skill & registry

stripe ros2 schema desc
add create-device-skill

new registry system backwards to yaml

remove not exist resource

new registry sys
exp. support with add device

correct raise create resource error

ret info fix revert

ret info fix

fix prcxi check

add create_resource schema

re signal host ready event

add websocket connection timeout and improve reconnection logic

add open_timeout parameter to websocket connection
add TimeoutError and InvalidStatus exception handling
implement exponential backoff for reconnection attempts
simplify reconnection logic flow
2026-04-16 01:33:36 +08:00

24 KiB
Raw Blame History

设备图文件说明

设备图文件定义了实验室中所有设备、资源及其连接关系。本文档说明如何创建和使用设备图文件。

概述

设备图文件采用 JSON 格式,节点定义基于 ResourceDict 标准模型(定义在 unilabos.ros.nodes.resource_tracker)。系统会自动处理旧格式并转换为标准格式,确保向后兼容性。

核心概念:

  • Nodes节点: 代表设备或资源,通过 parent 字段建立层级关系
  • Links连接: 可选的连接关系定义,用于展示设备间的物理或通信连接
  • UUID: 全局唯一标识符,用于跨系统的资源追踪
  • 自动转换: 旧格式会通过 ResourceDictInstance.get_resource_instance_from_dict() 自动转换

文件格式

Uni-Lab 支持两种格式的设备图文件:

JSON 格式(推荐)

优点:

  • 易于编辑和阅读
  • 支持注释(使用预处理)
  • 与 Web 界面完全兼容
  • 便于版本控制

示例: workshop1.json

GraphML 格式

优点:

  • 可用图形化工具编辑(如 yEd
  • 适合复杂拓扑可视化

示例: setup.graphml

JSON 文件结构

一个完整的 JSON 设备图文件包含两个主要部分:

{
  "nodes": [
    /* 设备和资源节点 */
  ],
  "links": [
    /* 连接关系(可选)*/
  ]
}

Nodes节点

每个节点代表一个设备或资源。节点的定义遵循 ResourceDict 标准模型:

{
  "id": "liquid_handler_1",
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "name": "液体处理工作站",
  "type": "device",
  "class": "liquid_handler",
  "config": {
    "port": "/dev/ttyUSB0",
    "baudrate": 9600
  },
  "data": {},
  "position": {
    "x": 100,
    "y": 200
  },
  "parent": null
}

字段说明(基于 ResourceDict 标准定义):

字段 必需 说明 示例 默认值
id 唯一标识符 "pump_1" -
uuid 全局唯一标识符 (UUID) "550e8400-e29b-41d4-a716-446655440000" 自动生成
name 显示名称 "主反应泵" -
type 节点类型 "device", "resource", "container", "deck" -
class 设备/资源类别 "liquid_handler", "syringepump.runze" ""
config Python 类的初始化参数 {"port": "COM3"} {}
data 资源的运行状态数据 {"status": "Idle", "position": 0.0} {}
position 在图中的位置 {"x": 100, "y": 200} 或完整的 pose 结构 -
pose 完整的 3D 位置信息 参见下文 -
parent 父节点 ID "deck_1" null
parent_uuid 父节点 UUID "550e8400-..." null
children 子节点 ID 列表(旧格式) ["child1", "child2"] -
description 资源描述 "用于精确控制试剂A的加料速率" ""
schema 资源 schema 定义 {} {}
model 资源 3D 模型信息 {} {}
icon 资源图标 "pump.webp" ""
extra 额外的自定义数据 {"custom_field": "value"} {}

Position 和 Pose位置信息

简单格式(旧格式,兼容):

"position": {
  "x": 100,
  "y": 200,
  "z": 0
}

完整格式(推荐):

"pose": {
  "size": {
    "width": 127.76,
    "height": 85.48,
    "depth": 10.0
  },
  "scale": {
    "x": 1.0,
    "y": 1.0,
    "z": 1.0
  },
  "layout": "x-y",
  "position": {
    "x": 100,
    "y": 200,
    "z": 0
  },
  "position3d": {
    "x": 100,
    "y": 200,
    "z": 0
  },
  "rotation": {
    "x": 0,
    "y": 0,
    "z": 0
  },
  "cross_section_type": "rectangle"
}

Links连接

定义节点之间的连接关系(可选,主要用于物理连接或通信关系的可视化):

{
  "source": "pump_1",
  "target": "reactor_1",
  "sourceHandle": "output",
  "targetHandle": "input",
  "type": "physical"
}

字段说明:

字段 必需 说明 示例
source 源节点 ID "pump_1"
target 目标节点 ID "reactor_1"
sourceHandle 源节点的连接点 "output"
targetHandle 目标节点的连接点 "input"
type 连接类型 "physical", "communication"
port 端口映射信息 {"source": "port1", "target": "port2"}

注意: Links 主要用于图形化展示和文档说明,父子关系通过 parent 字段定义,不依赖 links。

完整示例

示例 1液体处理工作站PRCXI9300

这是一个真实的液体处理工作站配置,包含设备、工作台和多个板资源。

文件位置: test/experiments/prcxi_9300.json

{
  "nodes": [
    {
      "id": "PRCXI9300",
      "name": "PRCXI9300",
      "parent": null,
      "type": "device",
      "class": "liquid_handler.prcxi",
      "position": {
        "x": 0,
        "y": 0,
        "z": 0
      },
      "config": {
        "deck": {
          "_resource_child_name": "PRCXI_Deck_9300",
          "_resource_type": "unilabos.devices.liquid_handling.prcxi.prcxi:PRCXI9300Deck"
        },
        "host": "10.181.214.132",
        "port": 9999,
        "timeout": 10.0,
        "axis": "Left",
        "channel_num": 8,
        "setup": false,
        "debug": true,
        "simulator": true,
        "matrix_id": "71593"
      },
      "data": {},
      "children": ["PRCXI_Deck_9300"]
    },
    {
      "id": "PRCXI_Deck_9300",
      "name": "PRCXI_Deck_9300",
      "parent": "PRCXI9300",
      "type": "deck",
      "class": "",
      "position": {
        "x": 0,
        "y": 0,
        "z": 0
      },
      "config": {
        "type": "PRCXI9300Deck",
        "size_x": 100,
        "size_y": 100,
        "size_z": 100,
        "rotation": {
          "x": 0,
          "y": 0,
          "z": 0,
          "type": "Rotation"
        },
        "category": "deck"
      },
      "data": {},
      "children": [
        "RackT1",
        "PlateT2",
        "trash",
        "PlateT4",
        "PlateT5",
        "PlateT6"
      ]
    },
    {
      "id": "RackT1",
      "name": "RackT1",
      "parent": "PRCXI_Deck_9300",
      "type": "tip_rack",
      "class": "",
      "position": {
        "x": 0,
        "y": 0,
        "z": 0
      },
      "config": {
        "type": "TipRack",
        "size_x": 127.76,
        "size_y": 85.48,
        "size_z": 100
      },
      "data": {},
      "children": []
    }
  ]
}

关键点:

  • 使用 parent 字段建立层级关系PRCXI9300 → Deck → Rack/Plate
  • 使用 children 字段(旧格式)列出子节点
  • config 中包含设备特定的连接参数
  • data 存储运行时状态
  • position 使用简单的 x/y/z 坐标

这是一个格林纳德反应的流动化学工作站配置,展示了完整的设备连接和通信关系。

文件位置: test/experiments/Grignard_flow_batchreact_single_pumpvalve.json

{
  "nodes": [
    {
      "id": "YugongStation",
      "name": "愚公常量合成工作站",
      "parent": null,
      "type": "device",
      "class": "workstation",
      "position": {
        "x": 620.6111111111111,
        "y": 171,
        "z": 0
      },
      "config": {
        "protocol_type": [
          "PumpTransferProtocol",
          "CleanProtocol",
          "SeparateProtocol",
          "EvaporateProtocol"
        ]
      },
      "data": {},
      "children": [
        "serial_pump",
        "pump_reagents",
        "flask_CH2Cl2",
        "reactor",
        "pump_workup",
        "separator_controller",
        "flask_separator",
        "rotavap",
        "column"
      ]
    },
    {
      "id": "serial_pump",
      "name": "serial_pump",
      "parent": "YugongStation",
      "type": "device",
      "class": "serial",
      "position": {
        "x": 620.6111111111111,
        "y": 171,
        "z": 0
      },
      "config": {
        "port": "COM7",
        "baudrate": 9600
      },
      "data": {},
      "children": []
    },
    {
      "id": "pump_reagents",
      "name": "pump_reagents",
      "parent": "YugongStation",
      "type": "device",
      "class": "syringepump.runze",
      "position": {
        "x": 620.6111111111111,
        "y": 171,
        "z": 0
      },
      "config": {
        "port": "/devices/PumpBackbone/Serial/serialwrite",
        "address": "1",
        "max_volume": 25.0
      },
      "data": {
        "max_velocity": 1.0,
        "position": 0.0,
        "status": "Idle",
        "valve_position": "0"
      },
      "children": []
    },
    {
      "id": "reactor",
      "name": "reactor",
      "parent": "YugongStation",
      "type": "container",
      "class": null,
      "position": {
        "x": 430.4087301587302,
        "y": 428,
        "z": 0
      },
      "config": {},
      "data": {},
      "children": []
    }
  ],
  "links": [
    {
      "source": "pump_reagents",
      "target": "serial_pump",
      "type": "communication",
      "port": {
        "pump_reagents": "port",
        "serial_pump": "port"
      }
    },
    {
      "source": "pump_workup",
      "target": "serial_pump",
      "type": "communication",
      "port": {
        "pump_workup": "port",
        "serial_pump": "port"
      }
    }
  ]
}

关键点:

  • 多级设备层次:工作站包含多个子设备和容器
  • links 定义通信关系(泵通过串口连接)
  • data 字段存储设备状态(如泵的位置、速度等)
  • class 可以使用点号分层(如 "syringepump.runze"
  • 容器的 class 可以为 null

格式兼容性和转换

旧格式自动转换

Uni-Lab 使用 ResourceDictInstance.get_resource_instance_from_dict() 方法自动处理旧格式的节点数据,确保向后兼容性。

自动转换规则:

  1. 自动生成缺失字段:

    # 如果缺少 id使用 name 作为 id
    if "id" not in content:
        content["id"] = content["name"]
    
    # 如果缺少 uuid自动生成
    if "uuid" not in content:
        content["uuid"] = str(uuid.uuid4())
    
  2. Position 格式转换:

    # 旧格式:简单的 x/y 坐标
    "position": {"x": 100, "y": 200}
    
    # 自动转换为新格式
    "position": {
        "position": {"x": 100, "y": 200}
    }
    
  3. 默认值填充:

    # 自动填充空字段
    if not content.get("class"):
        content["class"] = ""
    if not content.get("config"):
        content["config"] = {}
    if not content.get("data"):
        content["data"] = {}
    if not content.get("extra"):
        content["extra"] = {}
    
  4. Pose 字段同步:

    # 如果没有 pose使用 position
    if "pose" not in content:
        content["pose"] = content.get("position", {})
    

使用示例

from unilabos.resources.resource_tracker import ResourceDictInstance

# 旧格式节点
old_format_node = {
    "name": "pump_1",
    "type": "device",
    "class": "syringepump",
    "position": {"x": 100, "y": 200}
}

# 自动转换为标准格式
instance = ResourceDictInstance.get_resource_instance_from_dict(old_format_node)

# 访问标准化后的数据
print(instance.res_content.id)  # "pump_1"
print(instance.res_content.uuid)  # 自动生成的 UUID
print(instance.res_content.config)  # {}
print(instance.res_content.data)  # {}

格式迁移建议

虽然系统会自动处理旧格式,但建议在新文件中使用完整的标准格式:

字段 旧格式(兼容) 新格式(推荐)
标识符 id 或仅 name id + uuid
位置 "position": {"x": 100, "y": 200} 完整的 pose 结构
父节点 "parent": "parent_id" "parent": "parent_id" + "parent_uuid": "..."
配置 可省略 显式设置为 {}
数据 可省略 显式设置为 {}

节点类型详解

Device 节点

设备节点代表实际的硬件设备:

{
  "id": "device_id",
  "name": "设备名称",
  "type": "device",
  "class": "设备类别",
  "parent": null,
  "config": {
    "port": "COM3"
  },
  "data": {},
  "children": []
}

常见设备类别:

  • liquid_handler: 液体处理工作站
  • liquid_handler.prcxi: PRCXI 液体处理工作站
  • syringepump: 注射泵
  • syringepump.runze: 润泽注射泵
  • heaterstirrer: 加热搅拌器
  • balance: 天平
  • reactor_vessel: 反应釜
  • serial: 串口通信设备
  • workstation: 自动化工作站

Resource 节点

资源节点代表物料容器、载具等:

{
  "id": "resource_id",
  "name": "资源名称",
  "type": "resource",
  "class": "资源类别",
  "parent": "父节点ID",
  "config": {
    "size_x": 127.76,
    "size_y": 85.48,
    "size_z": 100
  },
  "data": {},
  "children": []
}

常见资源类型:

  • deck: 工作台/甲板
  • plate: 板96 孔板等)
  • tip_rack: 枪头架
  • tube: 试管
  • container: 容器
  • well: 孔位
  • bottle_carrier: 瓶架

Handle连接点

每个设备和资源可以有多个连接点handles用于定义可以连接的接口。

查看可用 handles

设备和资源的可用 handles 定义在注册表中:

# 设备注册表示例
liquid_handler:
  handles:
    - handler_key: pipette
      io_type: source
    - handler_key: deck
      io_type: target

常见 handles

设备类型 Source Handles Target Handles
output input
反应釜 output, vessel input
液体处理器 pipette deck
wells access

使用 Web 界面创建图文件

Uni-Lab 提供 Web 界面来可视化创建和编辑设备图:

1. 启动 Uni-Lab

unilab

2. 访问 Web 界面

打开浏览器访问 http://localhost:8002

3. 图形化编辑

  • 拖拽添加设备和资源
  • 连线建立连接关系
  • 编辑节点属性
  • 保存为 JSON 文件

4. 导出图文件

点击"导出"按钮,下载 JSON 文件到本地。

从云端获取图文件

如果不指定-g参数Uni-Lab 会自动从云端获取:

# 使用云端配置
unilab

# 日志会显示:
# [INFO] 未指定设备加载文件路径尝试从HTTP获取...
# [INFO] 联网获取设备加载文件成功

云端图文件管理:

  1. 登录 https://leap-lab.bohrium.com
  2. 进入"设备配置"
  3. 创建或编辑配置
  4. 保存到云端

本地启动时会自动同步最新配置。

调试图文件

验证 JSON 格式

# 使用Python验证
python -c "import json; json.load(open('workshop1.json'))"

# 使用在线工具
# https://jsonlint.com/

检查节点引用

确保:

  • 所有links中的sourcetarget都存在于nodes
  • parent字段指向的节点存在
  • class字段对应的设备/资源在注册表中存在

启动时验证

# Uni-Lab启动时会验证图文件
unilab -g workshop1.json

# 查看日志中的错误或警告
# [ERROR] 节点 xxx 的source端点 yyy 不存在
# [WARNING] 节点 zzz missing 'name', defaulting to ...

最佳实践

1. 命名规范

{
  "id": "pump_reagent_1", // 小写+下划线,描述性
  "name": "试剂进料泵A", // 中文显示名称
  "class": "syringepump" // 使用注册表中的精确名称
}

2. 层级组织

host_node (主节点)
└── liquid_handler_1 (设备)
    └── deck_1 (资源)
        ├── tiprack_1 (资源)
        ├── plate_1 (资源)
        └── reservoir_1 (资源)

3. 配置分离

将设备特定配置放在config中:

{
  "id": "pump_1",
  "class": "syringepump",
  "config": {
    "port": "COM3", // 设备特定
    "max_flow_rate": 10, // 设备特定
    "volume": 50 // 设备特定
  }
}

4. 版本控制

# 使用Git管理图文件
git add workshop1.json
git commit -m "Add new liquid handler configuration"

# 使用有意义的文件名
workshop_v1.json
workshop_production.json
workshop_test.json

5. 注释(通过描述字段)

虽然 JSON 不支持注释,但可以使用description字段:

{
  "id": "pump_1",
  "name": "进料泵",
  "description": "用于精确控制试剂A的加料速率最大流速10mL/min",
  "class": "syringepump"
}

示例文件位置

Uni-Lab 在安装时已预置了 40+ 个真实的设备图文件示例,位于 unilabos/test/experiments/ 目录。这些都是真实项目中使用的配置文件,可以直接使用或作为参考。

📁 主要示例文件

test/experiments/
├── workshop.json                                 # 综合工作台(推荐新手)
├── empty_devices.json                            # 空设备配置(最小化)
├── prcxi_9300.json                               # PRCXI液体处理工作站本文示例1
├── prcxi_9320.json                               # PRCXI 9320工作站
├── biomek.json                                   # Biomek液体处理工作站
├── Grignard_flow_batchreact_single_pumpvalve.json # 格林纳德反应工作站本文示例2
├── dispensing_station_bioyond.json               # Bioyond配液站
├── reaction_station_bioyond.json                 # Bioyond反应站
├── HPLC.json                                     # HPLC分析系统
├── plr_test.json                                 # PyLabRobot测试配置
├── lidocaine-graph.json                          # 利多卡因合成工作站
├── opcua_example.json                            # OPC UA设备集成示例
│
├── mock_devices/                                 # 虚拟设备(用于离线测试)
│   ├── mock_all.json                             # 完整虚拟设备集
│   ├── mock_pump.json                            # 虚拟泵
│   ├── mock_stirrer.json                         # 虚拟搅拌器
│   ├── mock_heater.json                          # 虚拟加热器
│   └── ...                                       # 更多虚拟设备
│
├── Protocol_Test_Station/                        # 协议测试工作站
│   ├── pumptransfer_test_station.json            # 泵转移协议测试
│   ├── heatchill_protocol_test_station.json      # 加热冷却协议测试
│   ├── filter_protocol_test_station.json         # 过滤协议测试
│   └── ...                                       # 更多协议测试
│
└── comprehensive_protocol/                       # 综合协议示例
    ├── comprehensive_station.json                # 综合工作站
    └── comprehensive_slim.json                   # 精简版综合工作站

🚀 快速使用

无需下载或创建,直接使用 -g 参数指定路径:

# 使用简单工作台(推荐新手)
unilab --ak your_ak --sk your_sk -g test/experiments/workshop.json

# 使用虚拟设备(无需真实硬件)
unilab --ak your_ak --sk your_sk -g test/experiments/mock_devices/mock_all.json

# 使用 PRCXI 液体处理工作站
unilab --ak your_ak --sk your_sk -g test/experiments/prcxi_9300.json

# 使用格林纳德反应工作站
unilab --ak your_ak --sk your_sk -g test/experiments/Grignard_flow_batchreact_single_pumpvalve.json

📚 文件分类

类别 说明 文件数量
主工作站 完整的实验工作站配置 15+
虚拟设备 用于开发测试的 mock 设备 10+
协议测试 各种实验协议的测试配置 12+
综合示例 包含多种协议的综合工作站 3+

这些文件展示了不同场景下的设备图配置,涵盖液体处理、有机合成、分析检测等多个领域,是学习和创建自己配置的绝佳参考。

快速参考ResourceDict 完整字段列表

基于 unilabos.ros.nodes.resource_tracker.ResourceDict 的完整字段定义:

class ResourceDict(BaseModel):
    # === 基础标识 ===
    id: str                          # 资源ID必需
    uuid: str                        # 全局唯一标识符(自动生成)
    name: str                        # 显示名称(必需)

    # === 类型和分类 ===
    type: Union[Literal["device"], str]  # 节点类型(必需)
    klass: str                       # 资源类别alias="class",必需)

    # === 层级关系 ===
    parent: Optional[ResourceDict]   # 父资源对象(不序列化)
    parent_uuid: Optional[str]       # 父资源UUID

    # === 位置和姿态 ===
    position: ResourceDictPosition   # 位置信息
    pose: ResourceDictPosition       # 姿态信息(推荐使用)

    # === 配置和数据 ===
    config: Dict[str, Any]           # 设备配置参数
    data: Dict[str, Any]             # 运行时状态数据
    extra: Dict[str, Any]            # 额外自定义数据

    # === 元数据 ===
    description: str                 # 资源描述
    resource_schema: Dict[str, Any]  # schema定义alias="schema"
    model: Dict[str, Any]            # 3D模型信息
    icon: str                        # 图标路径

Position/Pose 结构:

class ResourceDictPosition(BaseModel):
    size: ResourceDictPositionSize           # width, height, depth
    scale: ResourceDictPositionScale         # x, y, z
    layout: Literal["2d", "x-y", "z-y", "x-z"]
    position: ResourceDictPositionObject     # x, y, z
    position3d: ResourceDictPositionObject   # x, y, z
    rotation: ResourceDictPositionObject     # x, y, z
    cross_section_type: Literal["rectangle", "circle", "rounded_rectangle"]

下一步

  • {doc}../boot_examples/index - 查看完整启动示例
  • {doc}../developer_guide/add_device - 了解如何添加新设备
  • {doc}06_troubleshooting - 图文件相关问题排查
  • 源码参考: unilabos/ros/nodes/resource_tracker.py - ResourceDict 标准定义

获取帮助

  • 在 Web 界面中使用模板创建
  • 参考示例文件:test/experiments/ 目录
  • 查看 ResourceDict 源码了解完整定义
  • GitHub 讨论区