Files
Uni-Lab-OS/docs/user_guide/graph_files.md
Xuwznln 75f09034ff update docs, test examples
fix liquid_handler init bug
2025-11-18 18:42:27 +08:00

861 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 设备图文件说明
设备图文件定义了实验室中所有设备、资源及其连接关系。本文档说明如何创建和使用设备图文件。
## 概述
设备图文件采用 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 设备图文件包含两个主要部分:
```json
{
"nodes": [
/* 设备和资源节点 */
],
"links": [
/* 连接关系(可选)*/
]
}
```
### Nodes节点
每个节点代表一个设备或资源。节点的定义遵循 `ResourceDict` 标准模型:
```json
{
"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位置信息
**简单格式(旧格式,兼容)**:
```json
"position": {
"x": 100,
"y": 200,
"z": 0
}
```
**完整格式(推荐)**:
```json
"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连接
定义节点之间的连接关系(可选,主要用于物理连接或通信关系的可视化):
```json
{
"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`
```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`
```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. **自动生成缺失字段**:
```python
# 如果缺少 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 格式转换**:
```python
# 旧格式:简单的 x/y 坐标
"position": {"x": 100, "y": 200}
# 自动转换为新格式
"position": {
"position": {"x": 100, "y": 200}
}
```
3. **默认值填充**:
```python
# 自动填充空字段
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 字段同步**:
```python
# 如果没有 pose使用 position
if "pose" not in content:
content["pose"] = content.get("position", {})
```
### 使用示例
```python
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) # {}
```
### 格式迁移建议
虽然系统会自动处理旧格式,但建议在新文件中使用完整的标准格式:
| 字段 | 旧格式(兼容) | 新格式(推荐) |
| ------ | ---------------------------------- | ------------------------------------------------ |
| 标识符 | 仅 `id` 或仅 `name` | `id` + `uuid` |
| 位置 | `"position": {"x": 100, "y": 200}` | 完整的 `pose` 结构 |
| 父节点 | `"parent": "parent_id"` | `"parent": "parent_id"` + `"parent_uuid": "..."` |
| 配置 | 可省略 | 显式设置为 `{}` |
| 数据 | 可省略 | 显式设置为 `{}` |
## 节点类型详解
### Device 节点
设备节点代表实际的硬件设备:
```json
{
"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 节点
资源节点代表物料容器、载具等:
```json
{
"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 定义在注册表中:
```yaml
# 设备注册表示例
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
```bash
unilab
```
### 2. 访问 Web 界面
打开浏览器访问 `http://localhost:8002`
### 3. 图形化编辑
- 拖拽添加设备和资源
- 连线建立连接关系
- 编辑节点属性
- 保存为 JSON 文件
### 4. 导出图文件
点击"导出"按钮,下载 JSON 文件到本地。
## 从云端获取图文件
如果不指定`-g`参数Uni-Lab 会自动从云端获取:
```bash
# 使用云端配置
unilab
# 日志会显示:
# [INFO] 未指定设备加载文件路径尝试从HTTP获取...
# [INFO] 联网获取设备加载文件成功
```
**云端图文件管理**:
1. 登录 https://uni-lab.bohrium.com
2. 进入"设备配置"
3. 创建或编辑配置
4. 保存到云端
本地启动时会自动同步最新配置。
## 调试图文件
### 验证 JSON 格式
```bash
# 使用Python验证
python -c "import json; json.load(open('workshop1.json'))"
# 使用在线工具
# https://jsonlint.com/
```
### 检查节点引用
确保:
- 所有`links`中的`source`和`target`都存在于`nodes`中
- `parent`字段指向的节点存在
- `class`字段对应的设备/资源在注册表中存在
### 启动时验证
```bash
# Uni-Lab启动时会验证图文件
unilab -g workshop1.json
# 查看日志中的错误或警告
# [ERROR] 节点 xxx 的source端点 yyy 不存在
# [WARNING] 节点 zzz missing 'name', defaulting to ...
```
## 最佳实践
### 1. 命名规范
```json
{
"id": "pump_reagent_1", // 小写+下划线,描述性
"name": "试剂进料泵A", // 中文显示名称
"class": "syringepump" // 使用注册表中的精确名称
}
```
### 2. 层级组织
```
host_node (主节点)
└── liquid_handler_1 (设备)
└── deck_1 (资源)
├── tiprack_1 (资源)
├── plate_1 (资源)
└── reservoir_1 (资源)
```
### 3. 配置分离
将设备特定配置放在`config`中:
```json
{
"id": "pump_1",
"class": "syringepump",
"config": {
"port": "COM3", // 设备特定
"max_flow_rate": 10, // 设备特定
"volume": 50 // 设备特定
}
}
```
### 4. 版本控制
```bash
# 使用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`字段:
```json
{
"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` 参数指定路径:
```bash
# 使用简单工作台(推荐新手)
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` 的完整字段定义:
```python
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 结构**:
```python
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 讨论区](https://github.com/dptech-corp/Uni-Lab-OS/discussions)