设备图文件说明
设备图文件定义了实验室中所有设备、资源及其连接关系。本文档说明如何创建和使用设备图文件。
概述
设备图文件采用 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 标准定义):
字段 |
必需 |
说明 |
示例 |
默认值 |
|---|---|---|---|---|
|
✓ |
唯一标识符 |
|
- |
|
全局唯一标识符 (UUID) |
|
自动生成 |
|
|
✓ |
显示名称 |
|
- |
|
✓ |
节点类型 |
|
- |
|
✓ |
设备/资源类别 |
|
|
|
Python 类的初始化参数 |
|
|
|
|
资源的运行状态数据 |
|
|
|
|
在图中的位置 |
|
- |
|
|
完整的 3D 位置信息 |
参见下文 |
- |
|
|
父节点 ID |
|
|
|
|
父节点 UUID |
|
|
|
|
子节点 ID 列表(旧格式) |
|
- |
|
|
资源描述 |
|
|
|
|
资源 schema 定义 |
|
|
|
|
资源 3D 模型信息 |
|
|
|
|
资源图标 |
|
|
|
|
额外的自定义数据 |
|
|
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"
}
字段说明:
字段 |
必需 |
说明 |
示例 |
|---|---|---|---|
|
✓ |
源节点 ID |
|
|
✓ |
目标节点 ID |
|
|
源节点的连接点 |
|
|
|
目标节点的连接点 |
|
|
|
连接类型 |
|
|
|
端口映射信息 |
|
注意: 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.ros.nodes.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) # {}
格式迁移建议
虽然系统会自动处理旧格式,但建议在新文件中使用完整的标准格式:
字段 |
旧格式(兼容) |
新格式(推荐) |
|---|---|---|
标识符 |
仅 |
|
位置 |
|
完整的 |
父节点 |
|
|
配置 |
可省略 |
显式设置为 |
数据 |
可省略 |
显式设置为 |
节点类型详解
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://uni-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"]
下一步
获取帮助
在 Web 界面中使用模板创建
参考示例文件:
test/experiments/目录查看 ResourceDict 源码了解完整定义