# 设备图文件说明 设备图文件定义了实验室中所有设备、资源及其连接关系。本文档说明如何创建和使用设备图文件。 ## 概述 设备图文件采用 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)