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
24 KiB
设备图文件说明
设备图文件定义了实验室中所有设备、资源及其连接关系。本文档说明如何创建和使用设备图文件。
概述
设备图文件采用 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 坐标
示例 2:有机合成工作站(带 Links)
这是一个格林纳德反应的流动化学工作站配置,展示了完整的设备连接和通信关系。
文件位置: 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() 方法自动处理旧格式的节点数据,确保向后兼容性。
自动转换规则:
-
自动生成缺失字段:
# 如果缺少 id,使用 name 作为 id if "id" not in content: content["id"] = content["name"] # 如果缺少 uuid,自动生成 if "uuid" not in content: content["uuid"] = str(uuid.uuid4()) -
Position 格式转换:
# 旧格式:简单的 x/y 坐标 "position": {"x": 100, "y": 200} # 自动转换为新格式 "position": { "position": {"x": 100, "y": 200} } -
默认值填充:
# 自动填充空字段 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"] = {} -
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] 联网获取设备加载文件成功
云端图文件管理:
- 登录 https://leap-lab.bohrium.com
- 进入"设备配置"
- 创建或编辑配置
- 保存到云端
本地启动时会自动同步最新配置。
调试图文件
验证 JSON 格式
# 使用Python验证
python -c "import json; json.load(open('workshop1.json'))"
# 使用在线工具
# https://jsonlint.com/
检查节点引用
确保:
- 所有
links中的source和target都存在于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 讨论区