mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 13:01:12 +00:00
Update docs
This commit is contained in:
207
docs/developer_guide/examples/battery_plc_workstation.md
Normal file
207
docs/developer_guide/examples/battery_plc_workstation.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# 实例:电池装配工站接入(PLC控制)
|
||||
|
||||
> **文档类型**:实际应用案例
|
||||
> **适用场景**:使用 PLC 控制的电池装配工站接入
|
||||
> **前置知识**:{doc}`../add_device` | {doc}`../add_registry`
|
||||
|
||||
本指南以电池装配工站为实际案例,引导你完成 PLC 控制设备的完整接入流程,包括新建工站文件、编写驱动与寄存器读写、生成注册表、上传及注意事项。
|
||||
|
||||
## 案例概述
|
||||
|
||||
**设备类型**:电池装配工站
|
||||
**通信方式**:Modbus TCP (PLC)
|
||||
**工站基类**:`WorkstationBase`
|
||||
**主要功能**:电池组装、寄存器读写、数据采集
|
||||
|
||||
## 1. 新建工站文件
|
||||
|
||||
### 1.1 创建工站文件
|
||||
|
||||
在 `unilabos/devices/workstation/coin_cell_assembly` 目录下新建工站文件,如 `coin_cell_assembly.py`。工站类需继承 `WorkstationBase`,并在构造函数中初始化通信客户端与寄存器映射。
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
# 工站基类
|
||||
from unilabos.devices.workstation.workstation_base import WorkstationBase
|
||||
# Modbus 通讯与寄存器 CSV 支持
|
||||
from unilabos.device_comms.modbus_plc.client import TCPClient, BaseClient
|
||||
|
||||
class CoinCellAssemblyWorkstation(WorkstationBase):
|
||||
def __init__(
|
||||
self,
|
||||
station_resource,
|
||||
address: str = "192.168.1.20",
|
||||
port: str = "502",
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(station_resource=station_resource, *args, **kwargs)
|
||||
self.station_resource = station_resource # 物料台面(Deck)
|
||||
self.success: bool = False
|
||||
self.allow_data_read: bool = False
|
||||
self.csv_export_thread = None
|
||||
self.csv_export_running = False
|
||||
self.csv_export_file: Optional[str] = None
|
||||
|
||||
# 连接 PLC,并注册寄存器节点
|
||||
tcp = TCPClient(addr=address, port=port)
|
||||
tcp.client.connect()
|
||||
self.nodes = BaseClient.load_csv(".../PLC_register.csv")
|
||||
self.client = tcp.register_node_list(self.nodes)
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 2. 编写驱动与寄存器读写
|
||||
|
||||
### 2.1 寄存器示例
|
||||
|
||||
- `COIL_SYS_START_CMD`(BOOL,地址 8010):启动命令(脉冲式)
|
||||
- `COIL_SYS_START_STATUS`(BOOL,地址 8210):启动状态
|
||||
- `REG_DATA_OPEN_CIRCUIT_VOLTAGE`(FLOAT32,地址 10002):开路电压
|
||||
- `REG_DATA_ASSEMBLY_PRESSURE`(INT16,地址 10014):压制扣电压力
|
||||
|
||||
### 2.2 最小驱动示例
|
||||
|
||||
```python
|
||||
from unilabos.device_comms.modbus_plc.modbus import WorderOrder
|
||||
|
||||
def start_and_read_metrics(self):
|
||||
# 1) 下发启动(置 True 再复位 False)
|
||||
self.client.use_node('COIL_SYS_START_CMD').write(True)
|
||||
self.client.use_node('COIL_SYS_START_CMD').write(False)
|
||||
|
||||
# 2) 等待进入启动状态
|
||||
while True:
|
||||
status, _ = self.client.use_node('COIL_SYS_START_STATUS').read(1)
|
||||
if bool(status[0]):
|
||||
break
|
||||
|
||||
# 3) 读取关键数据(FLOAT32 需读 2 个寄存器并指定字节序)
|
||||
voltage, _ = self.client.use_node('REG_DATA_OPEN_CIRCUIT_VOLTAGE').read(
|
||||
2, word_order=WorderOrder.LITTLE
|
||||
)
|
||||
pressure, _ = self.client.use_node('REG_DATA_ASSEMBLY_PRESSURE').read(1)
|
||||
|
||||
return {
|
||||
'open_circuit_voltage': voltage,
|
||||
'assembly_pressure': pressure,
|
||||
}
|
||||
```
|
||||
|
||||
> 提示:若需参数下发,可在 PLC 端设置标志寄存器并完成握手复位,避免粘连与竞争。
|
||||
|
||||
## 3. 本地生成注册表并校验
|
||||
|
||||
完成工站类与驱动后,需要生成(或更新)工站注册表供系统识别。
|
||||
|
||||
|
||||
### 3.1 新增工站设备(或资源)首次生成注册表
|
||||
首先通过以下命令启动unilab。进入unilab系统状态检查页面
|
||||
|
||||
```bash
|
||||
python unilabos\app\main.py -g celljson.json --ak <user的AK> --sk <user的SK>
|
||||
```
|
||||
|
||||
点击注册表编辑,进入注册表编辑页面
|
||||
|
||||

|
||||
|
||||
按照图示步骤填写自动生成注册表信息:
|
||||
|
||||

|
||||
|
||||
步骤说明:
|
||||
1. 选择新增的工站`coin_cell_assembly.py`文件
|
||||
2. 点击分析按钮,分析`coin_cell_assembly.py`文件
|
||||
3. 选择`coin_cell_assembly.py`文件中继承`WorkstationBase`类
|
||||
4. 填写新增的工站.py文件与`unilabos`目录的距离。例如,新增的工站文件`coin_cell_assembly.py`路径为`unilabos\devices\workstation\coin_cell_assembly\coin_cell_assembly.py`,则此处填写`unilabos.devices.workstation.coin_cell_assembly`。
|
||||
5. 此处填写新定义工站的类的名字(名称可以自拟)
|
||||
6. 填写新的工站注册表备注信息
|
||||
7. 生成注册表
|
||||
|
||||
以上操作步骤完成,则会生成的新的注册表YAML文件,如下图:
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 3.2 添加新生成注册表
|
||||
在`unilabos\registry\devices`目录下新建一个yaml文件,此处新建文件命名为`coincellassemblyworkstation_device.yaml`,将上面生成的新的注册表信息粘贴到`coincellassemblyworkstation_device.yaml`文件中。
|
||||
|
||||
在终端输入以下命令进行注册表补全操作。
|
||||
```bash
|
||||
python unilabos\app\register.py --complete_registry
|
||||
```
|
||||
|
||||
|
||||
### 3.3 启动并上传注册表
|
||||
|
||||
新增设备之后,启动unilab需要增加`--upload_registry`参数,来上传注册表信息。
|
||||
|
||||
```bash
|
||||
python unilabos\app\main.py -g celljson.json --ak <user的AK> --sk <user的SK> --upload_registry
|
||||
```
|
||||
|
||||
## 4. 注意事项
|
||||
|
||||
### 4.1 验证模块路径
|
||||
|
||||
在新生成的 YAML 中,确认 `module` 指向新工站类。本例中需检查 `coincellassemblyworkstation_device.yaml` 文件中是否正确指向了 `CoinCellAssemblyWorkstation` 类:
|
||||
|
||||
```yaml
|
||||
module: unilabos.devices.workstation.coin_cell_assembly.coin_cell_assembly:CoinCellAssemblyWorkstation
|
||||
```
|
||||
|
||||
### 4.2 首次接入流程
|
||||
|
||||
首次新增设备(或资源)需要完整流程:
|
||||
1. ✅ 在网页端生成注册表信息
|
||||
2. ✅ 使用 `--complete_registry` 补全注册表
|
||||
3. ✅ 使用 `--upload_registry` 上传注册表信息
|
||||
|
||||
### 4.3 驱动更新流程
|
||||
|
||||
如果不是新增设备,仅修改了工站驱动的 `.py` 文件:
|
||||
1. ✅ 运行 `--complete_registry` 补全注册表
|
||||
2. ✅ 运行 `--upload_registry` 上传注册表
|
||||
3. ❌ 不需要在网页端重新生成注册表
|
||||
|
||||
### 4.4 PLC通信注意事项
|
||||
|
||||
- **握手机制**:若需参数下发,建议在 PLC 端设置标志寄存器并完成握手复位,避免粘连与竞争
|
||||
- **字节序**:FLOAT32 等多字节数据类型需要正确指定字节序(如 `WorderOrder.LITTLE`)
|
||||
- **寄存器映射**:确保 CSV 文件中的寄存器地址与 PLC 实际配置一致
|
||||
- **连接稳定性**:在初始化时检查 PLC 连接状态,建议添加重连机制
|
||||
|
||||
## 5. 扩展阅读
|
||||
|
||||
### 相关文档
|
||||
|
||||
- {doc}`../add_device` - 设备驱动编写通用指南
|
||||
- {doc}`../add_registry` - 注册表配置完整指南
|
||||
- {doc}`../workstation_architecture` - 工站架构详解
|
||||
|
||||
### 技术要点
|
||||
|
||||
- **Modbus TCP 通信**:PLC 通信协议和寄存器读写
|
||||
- **WorkstationBase**:工站基类的继承和使用
|
||||
- **寄存器映射**:CSV 格式的寄存器配置
|
||||
- **注册表生成**:自动化工具使用
|
||||
|
||||
## 6. 总结
|
||||
|
||||
通过本案例,你应该掌握:
|
||||
|
||||
1. ✅ 如何创建 PLC 控制的工站驱动
|
||||
2. ✅ Modbus TCP 通信和寄存器读写
|
||||
3. ✅ 使用可视化编辑器生成注册表
|
||||
4. ✅ 注册表的补全和上传流程
|
||||
5. ✅ 新增设备与更新驱动的区别
|
||||
|
||||
这个案例展示了完整的 PLC 设备接入流程,可以作为其他类似设备接入的参考模板。
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 428 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 310 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
409
docs/developer_guide/examples/materials_construction_guide.md
Normal file
409
docs/developer_guide/examples/materials_construction_guide.md
Normal file
@@ -0,0 +1,409 @@
|
||||
# 实例:物料构建指南
|
||||
|
||||
> **文档类型**:物料系统实战指南
|
||||
> **适用场景**:工作站物料系统构建、Deck/Warehouse/Carrier/Bottle 配置
|
||||
> **前置知识**:PyLabRobot 基础 | 资源管理概念
|
||||
|
||||
## 概述
|
||||
|
||||
在UniLab-OS系统中,任何工作站中所需要用到的物料主要包括四个核心组件:
|
||||
|
||||
1. **桌子(Deck)** - 工作台面,定义整个工作空间的布局
|
||||
2. **堆栈(Warehouse)** - 存储区域,用于放置载具和物料
|
||||
3. **载具(Carriers)** - 承载瓶子等物料的容器架
|
||||
4. **瓶子(Bottles)** - 实际的物料容器
|
||||
|
||||
本文档以BioYond工作站为例,详细说明如何构建这些物料组件。
|
||||
|
||||
## 文件结构
|
||||
|
||||
物料定义文件位于 `unilabos/resources/` 文件夹中:
|
||||
|
||||
```
|
||||
unilabos/resources/bioyond/
|
||||
├── decks.py # 桌子定义
|
||||
├── YB_warehouses.py # 堆栈定义
|
||||
├── YB_bottle_carriers.py # 载具定义
|
||||
└── YB_bottles.py # 瓶子定义
|
||||
```
|
||||
|
||||
对应的注册表文件位于 `unilabos/registry/resources/bioyond/` 文件夹中:
|
||||
|
||||
```
|
||||
unilabos/registry/resources/bioyond/
|
||||
├── deck.yaml # 桌子注册表
|
||||
├── YB_bottle_carriers.yaml # 载具注册表
|
||||
└── YB_bottle.yaml # 瓶子注册表
|
||||
```
|
||||
|
||||
## 1. 桌子(Deck)构建
|
||||
|
||||
桌子是整个工作站的基础,定义了工作空间的尺寸和各个组件的位置。
|
||||
|
||||
### 代码示例 (decks.py)
|
||||
|
||||
```python
|
||||
from pylabrobot.resources import Coordinate, Deck
|
||||
from unilabos.resources.bioyond.YB_warehouses import (
|
||||
bioyond_warehouse_2x2x1,
|
||||
bioyond_warehouse_3x5x1,
|
||||
bioyond_warehouse_20x1x1,
|
||||
bioyond_warehouse_3x3x1,
|
||||
bioyond_warehouse_10x1x1
|
||||
)
|
||||
|
||||
class BIOYOND_YB_Deck(Deck):
|
||||
def __init__(
|
||||
self,
|
||||
name: str = "YB_Deck",
|
||||
size_x: float = 4150, # 桌子X方向尺寸 (mm)
|
||||
size_y: float = 1400.0, # 桌子Y方向尺寸 (mm)
|
||||
size_z: float = 2670.0, # 桌子Z方向尺寸 (mm)
|
||||
category: str = "deck",
|
||||
setup: bool = False
|
||||
) -> None:
|
||||
super().__init__(name=name, size_x=4150.0, size_y=1400.0, size_z=2670.0)
|
||||
if setup:
|
||||
self.setup() # 当在工作站配置中setup为True时,自动创建并放置所有预定义的堆栈
|
||||
|
||||
def setup(self) -> None:
|
||||
# 定义桌子上的各个仓库区域
|
||||
self.warehouses = {
|
||||
"自动堆栈-左": bioyond_warehouse_2x2x1("自动堆栈-左"),
|
||||
"自动堆栈-右": bioyond_warehouse_2x2x1("自动堆栈-右"),
|
||||
"手动堆栈-左": bioyond_warehouse_3x5x1("手动堆栈-左"),
|
||||
"手动堆栈-右": bioyond_warehouse_3x5x1("手动堆栈-右"),
|
||||
"粉末加样头堆栈": bioyond_warehouse_20x1x1("粉末加样头堆栈"),
|
||||
"配液站内试剂仓库": bioyond_warehouse_3x3x1("配液站内试剂仓库"),
|
||||
"试剂替换仓库": bioyond_warehouse_10x1x1("试剂替换仓库"),
|
||||
}
|
||||
|
||||
# 定义各个仓库在桌子上的坐标位置
|
||||
self.warehouse_locations = {
|
||||
"自动堆栈-左": Coordinate(-100.3, 171.5, 0.0),
|
||||
"自动堆栈-右": Coordinate(3960.1, 155.9, 0.0),
|
||||
"手动堆栈-左": Coordinate(-213.3, 804.4, 0.0),
|
||||
"手动堆栈-右": Coordinate(3960.1, 807.6, 0.0),
|
||||
"粉末加样头堆栈": Coordinate(415.0, 1301.0, 0.0),
|
||||
"配液站内试剂仓库": Coordinate(2162.0, 437.0, 0.0),
|
||||
"试剂替换仓库": Coordinate(1173.0, 802.0, 0.0),
|
||||
}
|
||||
|
||||
# 将仓库分配到桌子的指定位置
|
||||
for warehouse_name, warehouse in self.warehouses.items():
|
||||
self.assign_child_resource(warehouse, location=self.warehouse_locations[warehouse_name])
|
||||
```
|
||||
|
||||
### 在工作站配置中的使用
|
||||
|
||||
当在工作站配置文件中定义桌子时,可以通过`setup`参数控制是否自动建立所有堆栈:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "YB_Bioyond_Deck",
|
||||
"name": "YB_Bioyond_Deck",
|
||||
"children": [],
|
||||
"parent": "bioyond_cell_workstation",
|
||||
"type": "deck",
|
||||
"class": "BIOYOND_YB_Deck",
|
||||
"config": {
|
||||
"type": "BIOYOND_YB_Deck",
|
||||
"setup": true
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
```
|
||||
|
||||
**重要说明**:
|
||||
- 当 `"setup": true` 时,系统会自动调用桌子的 `setup()` 方法
|
||||
- 这将创建并放置所有预定义的堆栈到桌子上的指定位置
|
||||
- 如果 `"setup": false` 或省略该参数,则只创建空桌子,需要手动添加堆栈
|
||||
|
||||
### 关键要点注释
|
||||
|
||||
- `size_x`, `size_y`, `size_z`: 定义桌子的物理尺寸
|
||||
- `warehouses`: 字典类型,包含桌子上所有的仓库区域
|
||||
- `warehouse_locations`: 定义每个仓库在桌子坐标系中的位置
|
||||
- `assign_child_resource()`: 将仓库资源分配到桌子的指定位置
|
||||
- `setup()`: 可选的自动设置方法,初始化时可调用
|
||||
|
||||
## 2. 堆栈(Warehouse)构建
|
||||
|
||||
堆栈定义了存储区域的规格和布局,用于放置载具。
|
||||
|
||||
### 代码示例 (YB_warehouses.py)
|
||||
|
||||
```python
|
||||
from unilabos.resources.warehouse import WareHouse, YB_warehouse_factory
|
||||
|
||||
def bioyond_warehouse_1x4x4(name: str) -> WareHouse:
|
||||
"""创建BioYond 1x4x4仓库
|
||||
|
||||
Args:
|
||||
name: 仓库名称
|
||||
|
||||
Returns:
|
||||
WareHouse: 仓库对象
|
||||
"""
|
||||
return YB_warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=1, # X方向位置数量
|
||||
num_items_y=4, # Y方向位置数量
|
||||
num_items_z=4, # Z方向位置数量(层数)
|
||||
dx=10.0, # X方向起始偏移
|
||||
dy=10.0, # Y方向起始偏移
|
||||
dz=10.0, # Z方向起始偏移
|
||||
item_dx=137.0, # X方向间距
|
||||
item_dy=96.0, # Y方向间距
|
||||
item_dz=120.0, # Z方向间距(层高)
|
||||
category="warehouse",
|
||||
)
|
||||
|
||||
def bioyond_warehouse_2x2x1(name: str) -> WareHouse:
|
||||
"""创建BioYond 2x2x1仓库(自动堆栈)"""
|
||||
return YB_warehouse_factory(
|
||||
name=name,
|
||||
num_items_x=2,
|
||||
num_items_y=2,
|
||||
num_items_z=1, # 单层
|
||||
dx=10.0,
|
||||
dy=10.0,
|
||||
dz=10.0,
|
||||
item_dx=137.0,
|
||||
item_dy=96.0,
|
||||
item_dz=120.0,
|
||||
category="YB_warehouse",
|
||||
)
|
||||
```
|
||||
|
||||
### 关键要点注释
|
||||
|
||||
- `num_items_x/y/z`: 定义仓库在各个方向的位置数量
|
||||
- `dx/dy/dz`: 第一个位置的起始偏移坐标
|
||||
- `item_dx/dy/dz`: 相邻位置之间的间距
|
||||
- `category`: 仓库类别,用于分类管理
|
||||
- `YB_warehouse_factory`: 统一的仓库创建工厂函数
|
||||
|
||||
## 3. 载具(Carriers)构建
|
||||
|
||||
载具是承载瓶子的容器架,定义了瓶子的排列方式和位置。
|
||||
|
||||
### 代码示例 (YB_bottle_carriers.py)
|
||||
|
||||
```python
|
||||
from pylabrobot.resources import create_homogeneous_resources, Coordinate, ResourceHolder, create_ordered_items_2d
|
||||
from unilabos.resources.itemized_carrier import Bottle, BottleCarrier
|
||||
from unilabos.resources.bioyond.YB_bottles import YB_pei_ye_xiao_Bottle
|
||||
|
||||
def YB_peiyepingxiaoban(name: str) -> BottleCarrier:
|
||||
"""配液瓶(小)板 - 4x2布局,8个位置
|
||||
|
||||
Args:
|
||||
name: 载具名称
|
||||
|
||||
Returns:
|
||||
BottleCarrier: 载具对象,包含8个配液瓶位置
|
||||
"""
|
||||
|
||||
# 载具物理尺寸 (mm)
|
||||
carrier_size_x = 127.8
|
||||
carrier_size_y = 85.5
|
||||
carrier_size_z = 65.0
|
||||
|
||||
# 瓶位参数
|
||||
bottle_diameter = 35.0 # 瓶子直径
|
||||
bottle_spacing_x = 42.0 # X方向瓶子间距
|
||||
bottle_spacing_y = 35.0 # Y方向瓶子间距
|
||||
|
||||
# 计算起始位置 (居中排列)
|
||||
start_x = (carrier_size_x - (4 - 1) * bottle_spacing_x - bottle_diameter) / 2
|
||||
start_y = (carrier_size_y - (2 - 1) * bottle_spacing_y - bottle_diameter) / 2
|
||||
|
||||
# 创建瓶位布局:4列x2行
|
||||
sites = create_ordered_items_2d(
|
||||
klass=ResourceHolder,
|
||||
num_items_x=4, # 4列
|
||||
num_items_y=2, # 2行
|
||||
dx=start_x,
|
||||
dy=start_y,
|
||||
dz=5.0, # 瓶子底部高度
|
||||
item_dx=bottle_spacing_x,
|
||||
item_dy=bottle_spacing_y,
|
||||
size_x=bottle_diameter,
|
||||
size_y=bottle_diameter,
|
||||
size_z=carrier_size_z,
|
||||
)
|
||||
|
||||
# 为每个瓶位设置名称
|
||||
for k, v in sites.items():
|
||||
v.name = f"{name}_{v.name}"
|
||||
|
||||
# 创建载具对象
|
||||
carrier = BottleCarrier(
|
||||
name=name,
|
||||
size_x=carrier_size_x,
|
||||
size_y=carrier_size_y,
|
||||
size_z=carrier_size_z,
|
||||
sites=sites,
|
||||
model="YB_peiyepingxiaoban",
|
||||
)
|
||||
|
||||
# 设置载具布局参数
|
||||
carrier.num_items_x = 4
|
||||
carrier.num_items_y = 2
|
||||
carrier.num_items_z = 1
|
||||
|
||||
# 定义瓶子排列顺序
|
||||
ordering = ["A1", "A2", "A3", "A4", "B1", "B2", "B3", "B4"]
|
||||
|
||||
# 为每个位置创建瓶子实例
|
||||
for i in range(8):
|
||||
carrier[i] = YB_pei_ye_xiao_Bottle(f"{name}_bottle_{ordering[i]}")
|
||||
|
||||
return carrier
|
||||
```
|
||||
|
||||
### 关键要点注释
|
||||
|
||||
- `carrier_size_x/y/z`: 载具的物理尺寸
|
||||
- `bottle_diameter`: 瓶子的直径,用于计算瓶位大小
|
||||
- `bottle_spacing_x/y`: 瓶子之间的间距
|
||||
- `create_ordered_items_2d`: 创建二维排列的瓶位
|
||||
- `sites`: 瓶位字典,存储所有瓶子位置信息
|
||||
- `ordering`: 定义瓶位的命名规则(如A1, A2, B1等)
|
||||
|
||||
## 4. 瓶子(Bottles)构建
|
||||
|
||||
瓶子是最终的物料容器,定义了容器的物理属性。
|
||||
|
||||
### 代码示例 (YB_bottles.py)
|
||||
|
||||
```python
|
||||
from unilabos.resources.itemized_carrier import Bottle
|
||||
|
||||
def YB_pei_ye_xiao_Bottle(
|
||||
name: str,
|
||||
diameter: float = 35.0, # 瓶子直径 (mm)
|
||||
height: float = 60.0, # 瓶子高度 (mm)
|
||||
max_volume: float = 30000.0, # 最大容量 (μL) - 30mL
|
||||
barcode: str = None, # 条码
|
||||
) -> Bottle:
|
||||
"""创建配液瓶(小)
|
||||
|
||||
Args:
|
||||
name: 瓶子名称
|
||||
diameter: 瓶子直径
|
||||
height: 瓶子高度
|
||||
max_volume: 最大容量(微升)
|
||||
barcode: 条码标识
|
||||
|
||||
Returns:
|
||||
Bottle: 瓶子对象
|
||||
"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_pei_ye_xiao_Bottle",
|
||||
)
|
||||
|
||||
def YB_ye_Bottle(
|
||||
name: str,
|
||||
diameter: float = 40.0,
|
||||
height: float = 70.0,
|
||||
max_volume: float = 50000.0, # 最大容量
|
||||
barcode: str = None,
|
||||
) -> Bottle:
|
||||
"""创建液体瓶"""
|
||||
return Bottle(
|
||||
name=name,
|
||||
diameter=diameter,
|
||||
height=height,
|
||||
max_volume=max_volume,
|
||||
barcode=barcode,
|
||||
model="YB_ye_Bottle",
|
||||
)
|
||||
```
|
||||
|
||||
### 关键要点注释
|
||||
|
||||
- `diameter`: 瓶子直径,影响瓶位大小计算
|
||||
- `height`: 瓶子高度,用于碰撞检测和移液计算
|
||||
- `max_volume`: 最大容量,单位为微升(μL)
|
||||
- `barcode`: 条码标识,用于瓶子追踪
|
||||
- `model`: 型号标识,用于区分不同类型的瓶子
|
||||
|
||||
## 5. 注册表配置
|
||||
|
||||
创建完物料定义后,需要在注册表中注册这些物料,使系统能够识别和使用它们。
|
||||
|
||||
在 `unilabos/registry/resources/bioyond/` 目录下创建:
|
||||
|
||||
- `deck.yaml` - 桌子注册表
|
||||
- `YB_bottle_carriers.yaml` - 载具注册表
|
||||
- `YB_bottle.yaml` - 瓶子注册表
|
||||
|
||||
### 5.1 桌子注册表 (deck.yaml)
|
||||
|
||||
```yaml
|
||||
BIOYOND_YB_Deck:
|
||||
category:
|
||||
- deck # 前端显示的分类存放
|
||||
class:
|
||||
module: unilabos.resources.bioyond.decks:BIOYOND_YB_Deck # 定义桌子的类的路径
|
||||
type: pylabrobot
|
||||
description: BIOYOND_YB_Deck # 描述信息
|
||||
handles: []
|
||||
icon: 配液站.webp # 图标文件
|
||||
init_param_schema: {}
|
||||
registry_type: resource # 注册类型
|
||||
version: 1.0.0 # 版本号
|
||||
```
|
||||
|
||||
### 5.2 载具注册表 (YB_bottle_carriers.yaml)
|
||||
|
||||
```yaml
|
||||
YB_peiyepingxiaoban:
|
||||
category:
|
||||
- yb3
|
||||
- YB_bottle_carriers
|
||||
class:
|
||||
module: unilabos.resources.bioyond.YB_bottle_carriers:YB_peiyepingxiaoban
|
||||
type: pylabrobot
|
||||
description: YB_peiyepingxiaoban
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
```
|
||||
|
||||
### 5.3 瓶子注册表 (YB_bottle.yaml)
|
||||
|
||||
```yaml
|
||||
YB_pei_ye_xiao_Bottle:
|
||||
category:
|
||||
- yb3
|
||||
- YB_bottle
|
||||
class:
|
||||
module: unilabos.resources.bioyond.YB_bottles:YB_pei_ye_xiao_Bottle
|
||||
type: pylabrobot
|
||||
description: YB_pei_ye_xiao_Bottle
|
||||
handles: []
|
||||
icon: ''
|
||||
init_param_schema: {}
|
||||
registry_type: resource
|
||||
version: 1.0.0
|
||||
```
|
||||
|
||||
### 注册表关键要点注释
|
||||
|
||||
- `category`: 物料分类,用于在云端(网页界面)中的分类中显示
|
||||
- `module`: Python模块路径,格式为 `模块路径:类名`
|
||||
- `type`: 框架类型,通常为 `pylabrobot`(默认即可)
|
||||
- `description`: 描述信息,显示在用户界面中
|
||||
- `icon`: (名称唯一自动匹配后端上传的图标文件名,显示在云端)
|
||||
- `registry_type`: 固定为 `resource`
|
||||
- `version`: 版本号,用于版本管理
|
||||
413
docs/developer_guide/examples/materials_tutorial.md
Normal file
413
docs/developer_guide/examples/materials_tutorial.md
Normal file
@@ -0,0 +1,413 @@
|
||||
# 实例:物料教程(Resource)
|
||||
|
||||
> **文档类型**:物料系统完整教程
|
||||
> **适用场景**:物料格式转换、多系统物料对接、资源结构理解
|
||||
> **前置知识**:Python 基础 | JSON 数据结构
|
||||
|
||||
本教程面向 Uni-Lab-OS 的开发者,讲解"物料"的核心概念、3种物料格式(UniLab、PyLabRobot、奔耀Bioyond)及其相互转换方法,并说明4种 children 结构表现形式及使用场景。
|
||||
|
||||
---
|
||||
|
||||
## 1. 物料是什么
|
||||
|
||||
- **物料(Resource)**:指实验工作站中的实体对象,包括设备(device)、操作甲板 (deck)、试剂、实验耗材,也包括设备上承载的具体物料或者包含的容器(如container/plate/well/瓶/孔/片等)。
|
||||
- **物料基本信息**(以 UniLab list格式为例):
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"id": "plate", // 某一类物料的唯一名称
|
||||
"name": "50ml瓶装试剂托盘", // 在云端显示的名称
|
||||
"sample_id": null, // 同类物料的不同样品
|
||||
"children": [
|
||||
"50ml试剂瓶" // 表示托盘上有一个 50ml 试剂瓶
|
||||
],
|
||||
"parent": "deck", // 此物料放置在 deck 上
|
||||
"type": "plate", // 物料类型
|
||||
"class": "plate", // 物料对应的注册/类名
|
||||
"position": {
|
||||
"x": 0, // 初始放置位置
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"config": { // 固有配置(尺寸、旋转等)
|
||||
"size_x": 400.0,
|
||||
"size_y": 400.0,
|
||||
"size_z": 400.0,
|
||||
"rotation": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"type": "Rotation"
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"bottle_number": 1 // 动态数据(可变化)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 3种物料格式概览(UniLab、PyLabRobot、奔耀Bioyond)
|
||||
|
||||
### 2.1 UniLab 物料格式(云端/项目内通用)
|
||||
|
||||
- 结构特征:顶层通常是 `nodes` 列表;每个节点是扁平字典,`children` 是子节点 `id` 列表;`parent` 为父节点 `id` 或 `null`。
|
||||
- 用途:
|
||||
- 云端数据存储、前端可视化、与图结构算法互操作
|
||||
- 在上传/下载/部署配置时作为标准交换格式
|
||||
|
||||
示例片段(UniLab 物料格式):
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"nodes": [
|
||||
|
||||
{
|
||||
"id": "a",
|
||||
"name": "name_a",
|
||||
"sample_id": 1,
|
||||
"type": "deck",
|
||||
"class": "deck",
|
||||
"parent": null,
|
||||
"children": ["b1"],
|
||||
"position": {"x": 0, "y": 0, "z": 0},
|
||||
"config": {},
|
||||
"data": {}
|
||||
},
|
||||
{
|
||||
|
||||
"id": "b1",
|
||||
"name": "name_b1",
|
||||
"sample_id": 1,
|
||||
"type": "plate",
|
||||
"class": "plate",
|
||||
"parent": "a1",
|
||||
"children": [],
|
||||
"position": {"x": 0, "y": 0, "z": 0},
|
||||
"config": {},
|
||||
"data": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 PyLabRobot(PLR)物料格式(实验流程运行时)
|
||||
|
||||
- 结构特征:严格的层级树,`children` 为“子资源字典列表”(每个子节点本身是完整对象)。
|
||||
- 用途:
|
||||
- 实验流程执行与调度,PLR 运行时期望的资源对象格式
|
||||
- 通过 `Resource.deserialize/serialize`、`load_all_state/serialize_all_state` 与对象交互
|
||||
|
||||
示例片段(PRL 物料格式)::
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "deck",
|
||||
"type": "Deck",
|
||||
"category": "deck",
|
||||
"location": {"x": 0, "y": 0, "z": 0, "type": "Coordinate"},
|
||||
"rotation": {"x": 0, "y": 0, "z": 0, "type": "Rotation"},
|
||||
"parent_name": null,
|
||||
"children": [
|
||||
{
|
||||
"name": "plate_1",
|
||||
"type": "Plate",
|
||||
"category": "plate_96",
|
||||
"location": {"x": 100, "y": 0, "z": 0, "type": "Coordinate"},
|
||||
"rotation": {"x": 0, "y": 0, "z": 0, "type": "Rotation"},
|
||||
"parent_name": "deck",
|
||||
"children": [
|
||||
{
|
||||
"name": "A1",
|
||||
"type": "Well",
|
||||
"category": "well",
|
||||
"location": {"x": 0, "y": 0, "z": 0, "type": "Coordinate"},
|
||||
"rotation": {"x": 0, "y": 0, "z": 0, "type": "Rotation"},
|
||||
"parent_name": "plate_1",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 2.3 奔耀 Bioyond 物料格式(第三方来源)
|
||||
一般是厂商自己定义的json格式和字段,信息需要提取和对应。以下为示例说明。
|
||||
|
||||
- 结构特征:顶层 `data` 列表,每项包含 `typeName`、`code`、`barCode`、`name`、`quantity`、`unit`、`locations`(仓位 `whName`、`x/y/z`)、`detail`(细粒度内容,如瓶内液体或孔位物料)。
|
||||
- 用途:
|
||||
- 第三方 WMS/设备的物料清单输入
|
||||
- 需要自定义映射表将 `typeName` → PLR 类名,对 `locations`/`detail` 进行落位/赋值
|
||||
|
||||
示例片段(奔耀Bioyond 物料格式):
|
||||
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "3a1b5c10-d4f3-01ac-1e64-5b4be2add4b1",
|
||||
"typeName": "液",
|
||||
"code": "0006-00014",
|
||||
"barCode": "",
|
||||
"name": "EMC",
|
||||
"quantity": 50,
|
||||
"lockQuantity": 2.057,
|
||||
"unit": "瓶",
|
||||
"status": 1,
|
||||
"isUse": false,
|
||||
"locations": [
|
||||
{
|
||||
"id": "3a19da43-57b5-5e75-552f-8dbd0ad1075f",
|
||||
"whid": "3a19da43-57b4-a2a8-3f52-91dbbeb836db",
|
||||
"whName": "配液站内试剂仓库",
|
||||
"code": "0003-0003",
|
||||
"x": 1,
|
||||
"y": 3,
|
||||
"z": 1,
|
||||
"quantity": 0
|
||||
}
|
||||
],
|
||||
"detail": [
|
||||
{
|
||||
"code": "0006-00014-01",
|
||||
"name": "EMC-瓶-1",
|
||||
"x": 1,
|
||||
"y": 3,
|
||||
"z": 1,
|
||||
"quantity": 500.0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"code": 1,
|
||||
"message": "",
|
||||
"timestamp": 0
|
||||
}
|
||||
```
|
||||
### 2.4 3种物料格式关键字段对应(UniLab、PyLabRobot、奔耀Bioyond)
|
||||
|
||||
| 含义 | UniLab | PyLabRobot (PLR) | 奔耀 Bioyond |
|
||||
| - | - | - | - |
|
||||
| 节点唯一名 | `id` | `name` | `name` |
|
||||
| 父节点引用 | `parent` | `parent_name` | `locations` 坐标(无直接父名,需映射坐标下的物料) |
|
||||
| 子节点集合 | `children`(id 列表或对象列表,视结构而定) | `children`(对象列表) | `detail`(明细,非严格树结构,需要自定义映射) |
|
||||
| 类型(抽象类别) | `type`(device/container/plate/deck/…) | `category`(plate/well/…),以及类名 `type` | `typeName`(厂商自定义,如“液”、“加样头(大)”) |
|
||||
| 运行/业务数据 | `data` | 通过 `serialize_all_state()`/`load_all_state()` 管理的状态 | `quantity`、`lockQuantity` 等业务数值 |
|
||||
| 固有配置 | `config`(size_x/size_y/size_z/model/ordering…) | 资源字典中的同名键(反序列化时按构造签名取用) | 厂商自定义字段(需映射入 PLR/UniLab 的 `config` 或 `data`) |
|
||||
| 空间位置 | `position`(x/y/z) | `location`(Coordinate) + `rotation`(Rotation) | `locations`(whName、x/y/z),不含旋转 |
|
||||
| 条码/标识 | `config.barcode`(可选) | 常放在配置键中(如 `barcode`) | `barCode` |
|
||||
| 数量单位 | 无固定键,通常在 `data` | 无固定键,通常在配置或状态中 | `unit` |
|
||||
| 物料编码 | 通常在 `config` 或 `data` 自定义 | 通常在配置中自定义 | `code` |
|
||||
|
||||
说明:
|
||||
- Bioyond 不提供显式的树形父子关系,通常通过 `locations` 将物料落位到某仓位/坐标。用 `detail` 表示子级明细。
|
||||
|
||||
---
|
||||
|
||||
## 3. children 的四种结构表示
|
||||
|
||||
- **list(扁平列表)**:每个节点是扁平字典,`children` 为子节点 `id` 数组。示例:UniLab `nodes` 中的单个节点。
|
||||
|
||||
```json
|
||||
{
|
||||
"nodes": [
|
||||
{ "id": "root", "parent": null, "children": ["child1"] },
|
||||
{ "id": "child1", "parent": "root", "children": [] }
|
||||
]
|
||||
}
|
||||
```
|
||||
- **dict(嵌套字典)**:节点的 `children` 是 `{ child_id: child_node_dict }` 字典。
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "root",
|
||||
"parent": null,
|
||||
"children": {
|
||||
"child1": { "id": "child1", "parent": "root", "children": {} }
|
||||
}
|
||||
}
|
||||
```
|
||||
- **tree(树形列表)**:顶层是 `[root_node, ...]`,每个 `node.children` 是“子节点对象列表”(而非 id 列表)。
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "root",
|
||||
"parent": null,
|
||||
"children": [
|
||||
{ "id": "child1", "parent": "root", "children": [] }
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
- **nestdict(顶层嵌套字典)**:顶层是 `{root_id: root_node, ...}`,或者根节点自身带 `children: {id: node}` 形态。
|
||||
|
||||
```json
|
||||
{
|
||||
"root": {
|
||||
"id": "root",
|
||||
"parent": null,
|
||||
"children": {
|
||||
"child1": { "id": "child1", "parent": "root", "children": {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这些结构之间可使用 `graphio.py` 中的工具函数互转(见下一节)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 转换函数及调用
|
||||
|
||||
核心代码文件:`unilabos/resources/graphio.py`
|
||||
|
||||
### 4.1 结构互转(list/dict/tree/nestdict)
|
||||
|
||||
代码引用:
|
||||
|
||||
```217:239:unilabos/resources/graphio.py
|
||||
def dict_to_tree(nodes: dict, devices_only: bool = False) -> list[dict]:
|
||||
# ... 由扁平 dict(id->node)生成树(children 为对象列表)
|
||||
```
|
||||
|
||||
```241:267:unilabos/resources/graphio.py
|
||||
def dict_to_nested_dict(nodes: dict, devices_only: bool = False) -> dict:
|
||||
# ... 由扁平 dict 生成嵌套字典(children 为 {id:node})
|
||||
```
|
||||
|
||||
```270:273:unilabos/resources/graphio.py
|
||||
def list_to_nested_dict(nodes: list[dict]) -> dict:
|
||||
# ... 由扁平列表(children 为 id 列表)转嵌套字典
|
||||
```
|
||||
|
||||
```275:286:unilabos/resources/graphio.py
|
||||
def tree_to_list(tree: list[dict]) -> list[dict]:
|
||||
# ... 由树形列表转回扁平列表(children 还原为 id 列表)
|
||||
```
|
||||
|
||||
```289:337:unilabos/resources/graphio.py
|
||||
def nested_dict_to_list(nested_dict: dict) -> list[dict]:
|
||||
# ... 由嵌套字典转回扁平列表
|
||||
```
|
||||
|
||||
常见路径:
|
||||
|
||||
- UniLab 扁平列表 → 树:`dict_to_tree({r["id"]: r for r in resources})`
|
||||
- 树 → UniLab 扁平列表:`tree_to_list(resources_tree)`
|
||||
- 扁平列表 ↔ 嵌套字典:`list_to_nested_dict` / `nested_dict_to_list`
|
||||
|
||||
### 4.2 UniLab ↔ PyLabRobot(PLR)
|
||||
|
||||
高层封装:
|
||||
|
||||
```339:368:unilabos/resources/graphio.py
|
||||
def convert_resources_to_type(resources_list: list[dict], resource_type: Union[type, list[type]], *, plr_model: bool = False):
|
||||
# UniLab -> (NestedDict or PLR)
|
||||
```
|
||||
|
||||
```371:395:unilabos/resources/graphio.py
|
||||
def convert_resources_from_type(resources_list, resource_type: Union[type, list[type]], *, is_plr: bool = False):
|
||||
# (NestedDict or PLR) -> UniLab 扁平列表
|
||||
```
|
||||
|
||||
底层转换:
|
||||
|
||||
```398:441:unilabos/resources/graphio.py
|
||||
def resource_ulab_to_plr(resource: dict, plr_model=False) -> "ResourcePLR":
|
||||
# UniLab 单节点(树根) -> PLR Resource 对象
|
||||
```
|
||||
|
||||
```443:481:unilabos/resources/graphio.py
|
||||
def resource_plr_to_ulab(resource_plr: "ResourcePLR", parent_name: str = None, with_children=True):
|
||||
# PLR Resource -> UniLab 单节点(dict)
|
||||
```
|
||||
|
||||
示例:
|
||||
|
||||
```python
|
||||
from unilabos.resources.graphio import convert_resources_to_type, convert_resources_from_type
|
||||
from pylabrobot.resources.resource import Resource as ResourcePLR
|
||||
|
||||
# UniLab 扁平列表 -> PLR 根资源对象
|
||||
plr_root = convert_resources_to_type(resources_list=ulab_list, resource_type=ResourcePLR)
|
||||
|
||||
# PLR 资源对象 -> UniLab 扁平列表(用于保存/上传)
|
||||
ulab_flat = convert_resources_from_type(resources_list=plr_root, resource_type=ResourcePLR)
|
||||
```
|
||||
|
||||
可选项:
|
||||
|
||||
- `plr_model=True`:保留 `model` 字段(默认会移除)。
|
||||
- `with_children=False`:`resource_plr_to_ulab` 仅转换当前节点。
|
||||
|
||||
### 4.3 奔耀(Bioyond)→ PLR(及进一步到 UniLab)
|
||||
|
||||
转换入口:
|
||||
|
||||
```483:527:unilabos/resources/graphio.py
|
||||
def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: dict = {}, deck: Any = None) -> list[dict]:
|
||||
# Bioyond 列表 -> PLR 资源列表,并可根据 deck.warehouses 将资源落位
|
||||
```
|
||||
|
||||
使用示例:
|
||||
|
||||
```python
|
||||
import json
|
||||
from unilabos.resources.graphio import resource_bioyond_to_plr, convert_resources_from_type
|
||||
from pylabrobot.resources.resource import Resource as ResourcePLR
|
||||
|
||||
resp = json.load(open("unilabos/devices/workstation/bioyond_cell/bioyond_test_yibin.json", encoding="utf-8"))
|
||||
materials = resp["data"]
|
||||
|
||||
# 将第三方类型name映射到 PLR 资源类名(需根据现场定义)
|
||||
type_mapping = {
|
||||
"液": "RegularContainer",
|
||||
"加样头(大)": "RegularContainer"
|
||||
}
|
||||
|
||||
plr_list = resource_bioyond_to_plr(materials, type_mapping=type_mapping, deck=None)
|
||||
|
||||
# 如需上传云端(UniLab 扁平格式):
|
||||
ulab_flat = convert_resources_from_type(plr_list, [ResourcePLR])
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `type_mapping` 必须由开发者根据设备/物料种类人工维护。
|
||||
- 如传入 `deck`,且 `deck.warehouses` 命名与 `whName` 对应,可将物料安放到仓库坐标(x/y/z)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 何时使用哪种格式
|
||||
|
||||
- **云端/持久化**:使用 UniLab 物料格式(扁平 `nodes` 列表,children 为 id 列表)。便于版本化、可视化与网络传输。
|
||||
- **实验工作流执行**:使用 PyLabRobot(PLR)格式。PLR 运行时依赖严格的树形资源结构与对象 API。
|
||||
- **第三方设备/系统(Bioyond)输入**:保持来源格式不变,使用 `resource_bioyond_to_plr` + 人工 `type_mapping` 将其转换为 PLR(必要时再转 UniLab)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 常见问题与注意事项
|
||||
|
||||
- **children 形态不一致**:不同函数期望不同 children 形态,注意在进入转换前先用“结构互转”工具函数标准化形态。
|
||||
- **devices_only**:`dict_to_tree/dict_to_nested_dict` 支持仅保留 `type == device` 的节点。
|
||||
- **模型/类型字段**:PLR 对象序列化参数有所差异,`resource_ulab_to_plr` 内部会根据构造签名移除不兼容字段(如 `category`)。
|
||||
- **驱动初始化**:`initialize_resource(s)` 支持从注册表/类路径创建 PLR/UniLab 资源或列表。
|
||||
|
||||
参考代码:
|
||||
|
||||
```530:577:unilabos/resources/graphio.py
|
||||
def initialize_resource(resource_config: dict, resource_type: Any = None) -> Union[list[dict], ResourcePLR]:
|
||||
# 从注册类/模块反射创建资源,或将 UniLab 字典包装为列表
|
||||
```
|
||||
|
||||
```580:597:unilabos/resources/graphio.py
|
||||
def initialize_resources(resources_config) -> list[dict]:
|
||||
# 批量初始化
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
782
docs/developer_guide/examples/workstation_architecture.md
Normal file
782
docs/developer_guide/examples/workstation_architecture.md
Normal file
@@ -0,0 +1,782 @@
|
||||
# 实例:工作站模板架构设计与对接指南
|
||||
|
||||
> **文档类型**:架构设计指南与实战案例
|
||||
> **适用场景**:大型工作站接入、子设备管理、物料系统集成
|
||||
> **前置知识**:{doc}`../add_device` | {doc}`../add_registry`
|
||||
|
||||
## 0. 问题简介
|
||||
|
||||
我们可以从以下几类例子,来理解对接大型工作站需要哪些设计。本文档之后的实战案例也将由这些组成。
|
||||
|
||||
### 0.1 自研常量有机工站:最重要的是子设备管理和通信转发
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
这类工站由开发者自研,组合所有子设备和实验耗材、希望让他们在工作站这一级协调配合;
|
||||
|
||||
1. 工作站包含大量已经注册的子设备,可能各自通信组态很不相同;部分设备可能会拥有同一个通信设备作为出口,如2个泵共用1个串口、所有设备共同接入PLC等。
|
||||
2. 任务系统是统一实现的 protocols,protocols 中会将高层指令处理成各子设备配合的工作流 json并管理执行、同时更改物料信息
|
||||
3. 物料系统较为简单直接,如常量有机化学仅为工作站内固定的瓶子,初始化时就已固定;随后在任务执行过程中,记录试剂量更改信息
|
||||
|
||||
### 0.2 移液工作站:物料系统和工作流模板管理
|
||||
|
||||

|
||||
|
||||
1. 绝大多数情况没有子设备,有时候选配恒温震荡等模块时,接口也由工作站提供
|
||||
2. 所有任务系统均由工作站本身实现并下发指令,有统一的抽象函数可实现(pick_up_tips, aspirate, dispense, transfer 等)。有时需要将这些指令组合、转化为工作站的脚本语言,再统一下发。因此会形成大量固定的 protocols。
|
||||
3. 物料系统为固定的板位系统:台面上有多个可摆放位置,摆放标准孔板。
|
||||
|
||||
### 0.3 厂家开发的定制大型工站
|
||||
|
||||

|
||||
|
||||
由厂家开发,具备完善的物料系统、任务系统甚至调度系统;由 PLC 或 OpenAPI TCP 协议统一通信
|
||||
|
||||
1. 在监控状态时,希望展现子设备的状态;但子设备仅为逻辑概念,通信由工作站上位机接口提供;部分情况下,子设备状态是被记录在文件中的,需要读取
|
||||
2. 工作站有自己的工作流系统甚至调度系统;可以通过脚本/PLC连续读写来配置工作站可用的工作流;
|
||||
3. 部分拥有完善的物料入库、出库、过程记录,需要与 Uni-Lab-OS 物料系统对接
|
||||
|
||||
## 1. 整体架构图
|
||||
|
||||
### 1.1 工作站核心架构
|
||||
|
||||
```{mermaid}
|
||||
graph TB
|
||||
subgraph "工作站模板组成"
|
||||
WB[WorkstationBase<br/>工作流状态管理]
|
||||
RPN[ROS2WorkstationNode<br/>Protocol执行引擎]
|
||||
WB -.post_init关联.-> RPN
|
||||
end
|
||||
|
||||
subgraph "物料管理系统"
|
||||
DECK[Deck<br/>PLR本地物料系统]
|
||||
RS[ResourceSynchronizer<br/>外部物料同步器]
|
||||
WB --> DECK
|
||||
WB --> RS
|
||||
RS --> DECK
|
||||
end
|
||||
|
||||
subgraph "通信与子设备管理"
|
||||
HW[hardware_interface<br/>硬件通信接口]
|
||||
SUBDEV[子设备集合<br/>pumps/grippers/sensors]
|
||||
WB --> HW
|
||||
RPN --> SUBDEV
|
||||
HW -.代理模式.-> RPN
|
||||
end
|
||||
|
||||
subgraph "工作流任务系统"
|
||||
PROTO[Protocol定义<br/>LiquidHandling/PlateHandling]
|
||||
WORKFLOW[Workflow执行器<br/>步骤管理与编排]
|
||||
RPN --> PROTO
|
||||
RPN --> WORKFLOW
|
||||
WORKFLOW --> SUBDEV
|
||||
end
|
||||
```
|
||||
|
||||
### 1.2 外部系统对接关系
|
||||
|
||||
```{mermaid}
|
||||
graph LR
|
||||
subgraph "Uni-Lab-OS工作站"
|
||||
WS[WorkstationBase + ROS2WorkstationNode]
|
||||
DECK2[物料系统<br/>Deck]
|
||||
HW2[通信接口<br/>hardware_interface]
|
||||
HTTP[HTTP服务<br/>WorkstationHTTPService]
|
||||
end
|
||||
|
||||
subgraph "外部物料系统"
|
||||
BIOYOND[Bioyond物料管理]
|
||||
LIMS[LIMS系统]
|
||||
WAREHOUSE[第三方仓储]
|
||||
end
|
||||
|
||||
subgraph "外部硬件系统"
|
||||
PLC[PLC设备]
|
||||
SERIAL[串口设备]
|
||||
ROBOT[机械臂/机器人]
|
||||
end
|
||||
|
||||
subgraph "云端系统"
|
||||
CLOUD[UniLab云端<br/>资源管理]
|
||||
MONITOR[监控与调度]
|
||||
end
|
||||
|
||||
BIOYOND <-->|RPC双向同步| DECK2
|
||||
LIMS -->|HTTP报送| HTTP
|
||||
WAREHOUSE <-->|API对接| DECK2
|
||||
|
||||
PLC <-->|Modbus TCP| HW2
|
||||
SERIAL <-->|串口通信| HW2
|
||||
ROBOT <-->|SDK/API| HW2
|
||||
|
||||
WS -->|ROS消息| CLOUD
|
||||
CLOUD -->|任务下发| WS
|
||||
MONITOR -->|状态查询| WS
|
||||
```
|
||||
|
||||
### 1.3 具体实现示例
|
||||
|
||||
```{mermaid}
|
||||
graph TB
|
||||
subgraph "工作站基类"
|
||||
BASE[WorkstationBase<br/>抽象基类]
|
||||
end
|
||||
|
||||
subgraph "Bioyond集成工作站"
|
||||
BW[BioyondWorkstation]
|
||||
BW_DECK[Deck + Warehouses]
|
||||
BW_SYNC[BioyondResourceSynchronizer]
|
||||
BW_HW[BioyondV1RPC]
|
||||
BW_HTTP[HTTP报送服务]
|
||||
|
||||
BW --> BW_DECK
|
||||
BW --> BW_SYNC
|
||||
BW --> BW_HW
|
||||
BW --> BW_HTTP
|
||||
end
|
||||
|
||||
subgraph "纯协议节点"
|
||||
PN[ProtocolNode]
|
||||
PN_SUB[子设备集合]
|
||||
PN_PROTO[Protocol工作流]
|
||||
|
||||
PN --> PN_SUB
|
||||
PN --> PN_PROTO
|
||||
end
|
||||
|
||||
subgraph "PLC控制工作站"
|
||||
PW[PLCWorkstation]
|
||||
PW_DECK[Deck物料系统]
|
||||
PW_PLC[Modbus PLC客户端]
|
||||
PW_WF[工作流定义]
|
||||
|
||||
PW --> PW_DECK
|
||||
PW --> PW_PLC
|
||||
PW --> PW_WF
|
||||
end
|
||||
|
||||
BASE -.继承.-> BW
|
||||
BASE -.继承.-> PN
|
||||
BASE -.继承.-> PW
|
||||
```
|
||||
|
||||
## 2. 类关系图
|
||||
|
||||
```{mermaid}
|
||||
classDiagram
|
||||
class WorkstationBase {
|
||||
<<abstract>>
|
||||
+_ros_node: ROS2WorkstationNode
|
||||
+deck: Deck
|
||||
+plr_resources: Dict[str, PLRResource]
|
||||
+resource_synchronizer: ResourceSynchronizer
|
||||
+hardware_interface: Union[Any, str]
|
||||
+current_workflow_status: WorkflowStatus
|
||||
+supported_workflows: Dict[str, WorkflowInfo]
|
||||
|
||||
+post_init(ros_node)*
|
||||
+set_hardware_interface(interface)
|
||||
+call_device_method(method, *args, **kwargs)
|
||||
+get_device_status()
|
||||
+is_device_available()
|
||||
|
||||
+get_deck()
|
||||
+get_all_resources()
|
||||
+find_resource_by_name(name)
|
||||
+find_resources_by_type(type)
|
||||
+sync_with_external_system()
|
||||
|
||||
+execute_workflow(name, params)
|
||||
+stop_workflow(emergency)
|
||||
+workflow_status
|
||||
+is_busy
|
||||
}
|
||||
|
||||
class ROS2WorkstationNode {
|
||||
+device_id: str
|
||||
+children: Dict[str, Any]
|
||||
+sub_devices: Dict
|
||||
+protocol_names: List[str]
|
||||
+_action_clients: Dict
|
||||
+_action_servers: Dict
|
||||
+resource_tracker: DeviceNodeResourceTracker
|
||||
|
||||
+initialize_device(device_id, config)
|
||||
+create_ros_action_server(action_name, mapping)
|
||||
+execute_single_action(device_id, action, kwargs)
|
||||
+update_resource(resources)
|
||||
+transfer_resource_to_another(resources, target, sites)
|
||||
+_setup_hardware_proxy(device, comm_device, read, write)
|
||||
}
|
||||
|
||||
%% 物料管理相关类
|
||||
class Deck {
|
||||
+name: str
|
||||
+children: List
|
||||
+assign_child_resource()
|
||||
}
|
||||
|
||||
class ResourceSynchronizer {
|
||||
<<abstract>>
|
||||
+workstation: WorkstationBase
|
||||
+sync_from_external()*
|
||||
+sync_to_external(plr_resource)*
|
||||
+handle_external_change(change_info)*
|
||||
}
|
||||
|
||||
class BioyondResourceSynchronizer {
|
||||
+bioyond_api_client: BioyondV1RPC
|
||||
+sync_interval: int
|
||||
+last_sync_time: float
|
||||
|
||||
+initialize()
|
||||
+sync_from_external()
|
||||
+sync_to_external(resource)
|
||||
+handle_external_change(change_info)
|
||||
}
|
||||
|
||||
%% 硬件接口相关类
|
||||
class HardwareInterface {
|
||||
<<interface>>
|
||||
}
|
||||
|
||||
class BioyondV1RPC {
|
||||
+base_url: str
|
||||
+api_key: str
|
||||
+stock_material()
|
||||
+add_material()
|
||||
+material_inbound()
|
||||
}
|
||||
|
||||
%% 服务类
|
||||
class WorkstationHTTPService {
|
||||
+workstation: WorkstationBase
|
||||
+host: str
|
||||
+port: int
|
||||
+server: HTTPServer
|
||||
+running: bool
|
||||
|
||||
+start()
|
||||
+stop()
|
||||
+_handle_step_finish_report()
|
||||
+_handle_sample_finish_report()
|
||||
+_handle_order_finish_report()
|
||||
+_handle_material_change_report()
|
||||
+_handle_error_handling_report()
|
||||
}
|
||||
|
||||
%% 具体实现类
|
||||
class BioyondWorkstation {
|
||||
+bioyond_config: Dict
|
||||
+workflow_mappings: Dict
|
||||
+workflow_sequence: List
|
||||
|
||||
+post_init(ros_node)
|
||||
+transfer_resource_to_another()
|
||||
+resource_tree_add(resources)
|
||||
+append_to_workflow_sequence(name)
|
||||
+get_all_workflows()
|
||||
+get_bioyond_status()
|
||||
}
|
||||
|
||||
class ProtocolNode {
|
||||
+post_init(ros_node)
|
||||
}
|
||||
|
||||
%% 核心关系
|
||||
WorkstationBase o-- ROS2WorkstationNode : post_init关联
|
||||
WorkstationBase o-- WorkstationHTTPService : 可选服务
|
||||
|
||||
%% 物料管理侧
|
||||
WorkstationBase *-- Deck : deck
|
||||
WorkstationBase *-- ResourceSynchronizer : 可选组合
|
||||
ResourceSynchronizer <|-- BioyondResourceSynchronizer
|
||||
|
||||
%% 硬件接口侧
|
||||
WorkstationBase o-- HardwareInterface : hardware_interface
|
||||
HardwareInterface <|.. BioyondV1RPC : 实现
|
||||
BioyondResourceSynchronizer --> BioyondV1RPC : 使用
|
||||
|
||||
%% 继承关系
|
||||
BioyondWorkstation --|> WorkstationBase
|
||||
ProtocolNode --|> WorkstationBase
|
||||
ROS2WorkstationNode --|> BaseROS2DeviceNode : 继承
|
||||
```
|
||||
|
||||
## 3. 工作站启动时序图
|
||||
|
||||
```{mermaid}
|
||||
sequenceDiagram
|
||||
participant APP as Application
|
||||
participant WS as WorkstationBase
|
||||
participant DECK as PLR Deck
|
||||
participant SYNC as ResourceSynchronizer
|
||||
participant HW as HardwareInterface
|
||||
participant ROS as ROS2WorkstationNode
|
||||
participant HTTP as HTTPService
|
||||
|
||||
APP->>WS: 创建工作站实例(__init__)
|
||||
WS->>DECK: 初始化PLR Deck
|
||||
DECK->>DECK: 创建Warehouse等子资源
|
||||
DECK-->>WS: Deck创建完成
|
||||
|
||||
WS->>HW: 创建硬件接口(如BioyondV1RPC)
|
||||
HW->>HW: 建立连接(PLC/RPC/串口等)
|
||||
HW-->>WS: 硬件接口就绪
|
||||
|
||||
WS->>SYNC: 创建ResourceSynchronizer(可选)
|
||||
SYNC->>HW: 使用hardware_interface
|
||||
SYNC->>SYNC: 初始化同步配置
|
||||
SYNC-->>WS: 同步器创建完成
|
||||
|
||||
WS->>SYNC: sync_from_external()
|
||||
SYNC->>HW: 查询外部物料系统
|
||||
HW-->>SYNC: 返回物料数据
|
||||
SYNC->>DECK: 转换并添加到Deck
|
||||
SYNC-->>WS: 同步完成
|
||||
|
||||
Note over WS: __init__完成,等待ROS节点
|
||||
|
||||
APP->>ROS: 初始化ROS2WorkstationNode
|
||||
ROS->>ROS: 初始化子设备(children)
|
||||
ROS->>ROS: 创建Action客户端
|
||||
ROS->>ROS: 设置硬件接口代理
|
||||
ROS-->>APP: ROS节点就绪
|
||||
|
||||
APP->>WS: post_init(ros_node)
|
||||
WS->>WS: self._ros_node = ros_node
|
||||
WS->>ROS: update_resource([deck])
|
||||
ROS->>ROS: 上传物料到云端
|
||||
ROS-->>WS: 上传完成
|
||||
|
||||
WS->>HTTP: 创建WorkstationHTTPService(可选)
|
||||
HTTP->>HTTP: 启动HTTP服务器线程
|
||||
HTTP-->>WS: HTTP服务启动
|
||||
|
||||
WS-->>APP: 工作站完全就绪
|
||||
```
|
||||
|
||||
## 4. 工作流执行时序图(Protocol模式)
|
||||
|
||||
```{mermaid}
|
||||
sequenceDiagram
|
||||
participant CLIENT as 客户端
|
||||
participant ROS as ROS2WorkstationNode
|
||||
participant WS as WorkstationBase
|
||||
participant HW as HardwareInterface
|
||||
participant DECK as PLR Deck
|
||||
participant CLOUD as 云端资源管理
|
||||
participant DEV as 子设备
|
||||
|
||||
CLIENT->>ROS: 发送Protocol Action请求
|
||||
ROS->>ROS: execute_protocol回调
|
||||
ROS->>ROS: 从Goal提取参数
|
||||
ROS->>ROS: 调用protocol_steps_generator
|
||||
ROS->>ROS: 生成action步骤列表
|
||||
|
||||
ROS->>WS: 更新workflow_status = RUNNING
|
||||
|
||||
loop 执行每个步骤
|
||||
alt 调用子设备
|
||||
ROS->>ROS: execute_single_action(device_id, action, params)
|
||||
ROS->>DEV: 发送Action Goal(通过Action Client)
|
||||
DEV->>DEV: 执行设备动作
|
||||
DEV-->>ROS: 返回Result
|
||||
else 调用工作站自身
|
||||
ROS->>WS: call_device_method(method, *args)
|
||||
alt 直接模式
|
||||
WS->>HW: 调用hardware_interface方法
|
||||
HW->>HW: 执行硬件操作
|
||||
HW-->>WS: 返回结果
|
||||
else 代理模式
|
||||
WS->>ROS: 转发到子设备
|
||||
ROS->>DEV: 调用子设备方法
|
||||
DEV-->>ROS: 返回结果
|
||||
ROS-->>WS: 返回结果
|
||||
end
|
||||
WS-->>ROS: 返回结果
|
||||
end
|
||||
|
||||
ROS->>DECK: 更新本地物料状态
|
||||
DECK->>DECK: 修改PLR资源属性
|
||||
end
|
||||
|
||||
ROS->>CLOUD: 同步物料到云端(可选)
|
||||
CLOUD-->>ROS: 同步完成
|
||||
|
||||
ROS->>WS: 更新workflow_status = COMPLETED
|
||||
ROS-->>CLIENT: 返回Protocol Result
|
||||
```
|
||||
|
||||
## 5. HTTP报送处理时序图
|
||||
|
||||
```{mermaid}
|
||||
sequenceDiagram
|
||||
participant EXT as 外部工作站/LIMS
|
||||
participant HTTP as HTTPService
|
||||
participant WS as WorkstationBase
|
||||
participant DECK as PLR Deck
|
||||
participant SYNC as ResourceSynchronizer
|
||||
participant CLOUD as 云端
|
||||
|
||||
EXT->>HTTP: POST /report/step_finish
|
||||
HTTP->>HTTP: 解析请求数据
|
||||
HTTP->>HTTP: 验证LIMS协议字段
|
||||
HTTP->>WS: process_step_finish_report(request)
|
||||
|
||||
WS->>WS: 增加接收计数(_reports_received_count++)
|
||||
WS->>WS: 记录步骤完成事件
|
||||
WS->>DECK: 更新相关物料状态(可选)
|
||||
DECK->>DECK: 修改PLR资源状态
|
||||
|
||||
WS->>WS: 保存报送记录到内存
|
||||
|
||||
WS-->>HTTP: 返回处理结果
|
||||
HTTP->>HTTP: 构造HTTP响应
|
||||
HTTP-->>EXT: 200 OK + acknowledgment_id
|
||||
|
||||
Note over EXT,CLOUD: 类似处理sample_finish, order_finish等报送
|
||||
|
||||
alt 物料变更报送
|
||||
EXT->>HTTP: POST /report/material_change
|
||||
HTTP->>WS: process_material_change_report(data)
|
||||
WS->>DECK: 查找或创建物料
|
||||
WS->>SYNC: sync_to_external(resource)
|
||||
SYNC->>SYNC: 同步到外部系统(如Bioyond)
|
||||
SYNC-->>WS: 同步完成
|
||||
WS->>CLOUD: update_resource(通过ROS节点)
|
||||
CLOUD-->>WS: 上传完成
|
||||
WS-->>HTTP: 返回结果
|
||||
HTTP-->>EXT: 200 OK
|
||||
end
|
||||
```
|
||||
|
||||
## 6. 错误处理时序图
|
||||
|
||||
```{mermaid}
|
||||
sequenceDiagram
|
||||
participant DEV as 子设备/外部系统
|
||||
participant ROS as ROS2WorkstationNode
|
||||
participant WS as WorkstationBase
|
||||
participant HW as HardwareInterface
|
||||
participant HTTP as HTTPService
|
||||
participant LOG as 日志系统
|
||||
|
||||
alt 设备错误(ROS Action失败)
|
||||
DEV->>ROS: Action返回失败结果
|
||||
ROS->>ROS: 记录错误信息
|
||||
ROS->>WS: 更新workflow_status = ERROR
|
||||
ROS->>LOG: 记录错误日志
|
||||
else 外部系统错误报送
|
||||
DEV->>HTTP: POST /report/error_handling
|
||||
HTTP->>WS: handle_external_error(error_data)
|
||||
WS->>WS: 记录错误历史
|
||||
WS->>LOG: 记录错误日志
|
||||
end
|
||||
|
||||
alt 关键错误需要停止
|
||||
WS->>ROS: stop_workflow(emergency=True)
|
||||
ROS->>ROS: 取消所有进行中的Action
|
||||
ROS->>HW: 调用emergency_stop()(如果支持)
|
||||
HW->>HW: 执行紧急停止
|
||||
WS->>WS: 更新workflow_status = ERROR
|
||||
else 可恢复错误
|
||||
WS->>WS: 标记步骤失败
|
||||
WS->>ROS: 触发重试逻辑(可选)
|
||||
ROS->>DEV: 重新发送Action
|
||||
end
|
||||
|
||||
WS-->>HTTP: 返回错误处理结果
|
||||
HTTP-->>DEV: 200 OK + 处理状态
|
||||
```
|
||||
|
||||
## 7. 典型工作站实现示例
|
||||
|
||||
### 7.1 Bioyond集成工作站实现
|
||||
|
||||
```python
|
||||
class BioyondWorkstation(WorkstationBase):
|
||||
def __init__(self, bioyond_config: Dict, deck: Deck, *args, **kwargs):
|
||||
# 初始化deck
|
||||
super().__init__(deck=deck, *args, **kwargs)
|
||||
|
||||
# 设置硬件接口为Bioyond RPC客户端
|
||||
self.hardware_interface = BioyondV1RPC(bioyond_config)
|
||||
|
||||
# 创建资源同步器
|
||||
self.resource_synchronizer = BioyondResourceSynchronizer(self)
|
||||
|
||||
# 从Bioyond同步物料到本地deck
|
||||
self.resource_synchronizer.sync_from_external()
|
||||
|
||||
# 配置工作流
|
||||
self.workflow_mappings = bioyond_config.get("workflow_mappings", {})
|
||||
|
||||
def post_init(self, ros_node: ROS2WorkstationNode):
|
||||
"""ROS节点就绪后的初始化"""
|
||||
self._ros_node = ros_node
|
||||
|
||||
# 上传deck(包括所有物料)到云端
|
||||
ROS2DeviceNode.run_async_func(
|
||||
self._ros_node.update_resource,
|
||||
True,
|
||||
resources=[self.deck]
|
||||
)
|
||||
|
||||
def resource_tree_add(self, resources: List[ResourcePLR]):
|
||||
"""添加物料并同步到Bioyond"""
|
||||
for resource in resources:
|
||||
self.deck.assign_child_resource(resource, location)
|
||||
self.resource_synchronizer.sync_to_external(resource)
|
||||
```
|
||||
|
||||
### 7.2 纯协议节点实现
|
||||
|
||||
```python
|
||||
class ProtocolNode(WorkstationBase):
|
||||
"""纯协议节点,不需要物料管理和外部通信"""
|
||||
|
||||
def __init__(self, deck: Optional[Deck] = None, *args, **kwargs):
|
||||
super().__init__(deck=deck, *args, **kwargs)
|
||||
# 不设置hardware_interface和resource_synchronizer
|
||||
# 所有功能通过子设备协同完成
|
||||
|
||||
def post_init(self, ros_node: ROS2WorkstationNode):
|
||||
self._ros_node = ros_node
|
||||
# 不需要上传物料或其他初始化
|
||||
```
|
||||
|
||||
### 7.3 PLC直接控制工作站
|
||||
|
||||
```python
|
||||
class PLCWorkstation(WorkstationBase):
|
||||
def __init__(self, plc_config: Dict, deck: Deck, *args, **kwargs):
|
||||
super().__init__(deck=deck, *args, **kwargs)
|
||||
|
||||
# 设置硬件接口为Modbus客户端
|
||||
from pymodbus.client import ModbusTcpClient
|
||||
self.hardware_interface = ModbusTcpClient(
|
||||
host=plc_config["host"],
|
||||
port=plc_config["port"]
|
||||
)
|
||||
self.hardware_interface.connect()
|
||||
|
||||
# 定义支持的工作流
|
||||
self.supported_workflows = {
|
||||
"battery_assembly": WorkflowInfo(
|
||||
name="电池组装",
|
||||
description="自动化电池组装流程",
|
||||
estimated_duration=300.0,
|
||||
required_materials=["battery_cell", "connector"],
|
||||
output_product="battery_pack",
|
||||
parameters_schema={"quantity": int, "model": str}
|
||||
)
|
||||
}
|
||||
|
||||
def execute_workflow(self, workflow_name: str, parameters: Dict):
|
||||
"""通过PLC执行工作流"""
|
||||
workflow_id = self._get_workflow_id(workflow_name)
|
||||
|
||||
# 写入PLC寄存器启动工作流
|
||||
self.hardware_interface.write_register(100, workflow_id)
|
||||
self.hardware_interface.write_register(101, parameters["quantity"])
|
||||
|
||||
self.current_workflow_status = WorkflowStatus.RUNNING
|
||||
return True
|
||||
```
|
||||
|
||||
## 8. 核心接口说明
|
||||
|
||||
### 8.1 WorkstationBase核心属性
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
| --------------------------- | ----------------------- | ----------------------------- |
|
||||
| `_ros_node` | ROS2WorkstationNode | ROS节点引用,由post_init设置 |
|
||||
| `deck` | Deck | PyLabRobot Deck,本地物料系统 |
|
||||
| `plr_resources` | Dict[str, PLRResource] | 物料资源映射 |
|
||||
| `resource_synchronizer` | ResourceSynchronizer | 外部物料同步器(可选) |
|
||||
| `hardware_interface` | Union[Any, str] | 硬件接口或代理字符串 |
|
||||
| `current_workflow_status` | WorkflowStatus | 当前工作流状态 |
|
||||
| `supported_workflows` | Dict[str, WorkflowInfo] | 支持的工作流定义 |
|
||||
|
||||
### 8.2 必须实现的方法
|
||||
|
||||
- `post_init(ros_node)`: ROS节点就绪后的初始化,必须实现
|
||||
|
||||
### 8.3 硬件接口相关方法
|
||||
|
||||
- `set_hardware_interface(interface)`: 设置硬件接口
|
||||
- `call_device_method(method, *args, **kwargs)`: 统一设备方法调用
|
||||
- 支持直接模式: 直接调用hardware_interface的方法
|
||||
- 支持代理模式: hardware_interface="proxy:device_id"通过ROS转发
|
||||
- `get_device_status()`: 获取设备状态
|
||||
- `is_device_available()`: 检查设备可用性
|
||||
|
||||
### 8.4 物料管理方法
|
||||
|
||||
- `get_deck()`: 获取PLR Deck
|
||||
- `get_all_resources()`: 获取所有物料
|
||||
- `find_resource_by_name(name)`: 按名称查找物料
|
||||
- `find_resources_by_type(type)`: 按类型查找物料
|
||||
- `sync_with_external_system()`: 触发外部同步
|
||||
|
||||
### 8.5 工作流控制方法
|
||||
|
||||
- `execute_workflow(name, params)`: 执行工作流
|
||||
- `stop_workflow(emergency)`: 停止工作流
|
||||
- `workflow_status`: 获取工作流状态(属性)
|
||||
- `is_busy`: 检查是否忙碌(属性)
|
||||
- `workflow_runtime`: 获取运行时间(属性)
|
||||
|
||||
### 8.6 可选的HTTP报送处理方法
|
||||
|
||||
- `process_step_finish_report()`: 步骤完成处理
|
||||
- `process_sample_finish_report()`: 样本完成处理
|
||||
- `process_order_finish_report()`: 订单完成处理
|
||||
- `process_material_change_report()`: 物料变更处理
|
||||
- `handle_external_error()`: 错误处理
|
||||
|
||||
### 8.7 ROS2WorkstationNode核心方法
|
||||
|
||||
- `initialize_device(device_id, config)`: 初始化子设备
|
||||
- `create_ros_action_server(action_name, mapping)`: 创建Action服务器
|
||||
- `execute_single_action(device_id, action, kwargs)`: 执行单个动作
|
||||
- `update_resource(resources)`: 同步物料到云端
|
||||
- `transfer_resource_to_another(...)`: 跨设备物料转移
|
||||
|
||||
## 9. 配置参数说明
|
||||
|
||||
### 9.1 工作站初始化配置
|
||||
|
||||
```python
|
||||
# 示例1: Bioyond集成工作站
|
||||
bioyond_config = {
|
||||
"base_url": "http://192.168.1.100:8080",
|
||||
"api_key": "your_api_key",
|
||||
"sync_interval": 600, # 同步间隔(秒)
|
||||
"workflow_mappings": {
|
||||
"样品制备": "workflow_uuid_1",
|
||||
"质检流程": "workflow_uuid_2"
|
||||
},
|
||||
"material_type_mappings": {
|
||||
"plate": "板",
|
||||
"tube": "试管"
|
||||
},
|
||||
"warehouse_mapping": {
|
||||
"冷藏区": {
|
||||
"uuid": "warehouse_uuid_1",
|
||||
"locations": {...}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 创建Deck
|
||||
from pylabrobot.resources import Deck
|
||||
deck = Deck(name="main_deck", size_x=1000, size_y=800, size_z=200)
|
||||
|
||||
workstation = BioyondWorkstation(
|
||||
bioyond_config=bioyond_config,
|
||||
deck=deck
|
||||
)
|
||||
```
|
||||
|
||||
### 9.2 子设备配置(children)
|
||||
|
||||
```python
|
||||
# 在devices.json中配置
|
||||
{
|
||||
"bioyond_workstation": {
|
||||
"type": "protocol", # 表示这是工作站节点
|
||||
"protocol_type": ["LiquidHandling", "PlateHandling"],
|
||||
"children": {
|
||||
"pump_1": {
|
||||
"type": "device",
|
||||
"driver": "TricontInnovaDriver",
|
||||
"communication": "serial_1",
|
||||
"config": {...}
|
||||
},
|
||||
"gripper_1": {
|
||||
"type": "device",
|
||||
"driver": "RobotiqGripperDriver",
|
||||
"communication": "io_modbus_1",
|
||||
"config": {...}
|
||||
},
|
||||
"serial_1": {
|
||||
"type": "communication",
|
||||
"protocol": "serial",
|
||||
"port": "/dev/ttyUSB0",
|
||||
"baudrate": 9600
|
||||
},
|
||||
"io_modbus_1": {
|
||||
"type": "communication",
|
||||
"protocol": "modbus_tcp",
|
||||
"host": "192.168.1.101",
|
||||
"port": 502
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9.3 HTTP服务配置
|
||||
|
||||
```python
|
||||
from unilabos.devices.workstation.workstation_http_service import WorkstationHTTPService
|
||||
|
||||
# 创建HTTP服务(可选)
|
||||
http_service = WorkstationHTTPService(
|
||||
workstation_instance=workstation,
|
||||
host="0.0.0.0", # 监听所有网卡
|
||||
port=8081
|
||||
)
|
||||
http_service.start()
|
||||
```
|
||||
|
||||
## 10. 架构设计特点总结
|
||||
|
||||
这个简化后的架构设计具有以下特点:
|
||||
|
||||
### 10.1 清晰的职责分离
|
||||
|
||||
- **WorkstationBase**: 负责物料管理(deck)、硬件接口(hardware_interface)、工作流状态管理
|
||||
- **ROS2WorkstationNode**: 负责子设备管理、Protocol执行、云端物料同步
|
||||
- **ResourceSynchronizer**: 可选的外部物料系统同步(如Bioyond)
|
||||
- **WorkstationHTTPService**: 可选的HTTP报送接收服务
|
||||
|
||||
### 10.2 灵活的硬件接口模式
|
||||
|
||||
1. **直接模式**: hardware_interface是具体对象(如BioyondV1RPC、ModbusClient)
|
||||
2. **代理模式**: hardware_interface="proxy:device_id",通过ROS节点转发到子设备
|
||||
3. **混合模式**: 工作站有自己的接口,同时管理多个子设备
|
||||
|
||||
### 10.3 统一的物料系统
|
||||
|
||||
- 基于PyLabRobot Deck的标准化物料表示
|
||||
- 通过ResourceSynchronizer实现与外部系统(如Bioyond、LIMS)的双向同步
|
||||
- 通过ROS2WorkstationNode实现与云端的物料状态同步
|
||||
|
||||
### 10.4 Protocol驱动的工作流
|
||||
|
||||
- ROS2WorkstationNode负责Protocol的执行和步骤管理
|
||||
- 支持子设备协同(通过Action Client调用)
|
||||
- 支持工作站直接控制(通过hardware_interface)
|
||||
|
||||
### 10.5 可选的HTTP报送服务
|
||||
|
||||
- 基于LIMS协议规范的统一报送接口
|
||||
- 支持步骤完成、样本完成、任务完成、物料变更等多种报送类型
|
||||
- 与工作站解耦,可独立启停
|
||||
|
||||
### 10.6 简化的初始化流程
|
||||
|
||||
```
|
||||
1. __init__: 创建deck、设置hardware_interface、创建resource_synchronizer
|
||||
2. 从外部系统同步物料(如果有)
|
||||
3. ROS节点初始化子设备
|
||||
4. post_init: 关联ROS节点、上传物料到云端
|
||||
5. (可选)启动HTTP服务
|
||||
```
|
||||
|
||||
这种设计既保持了灵活性,又避免了过度抽象,更适合实际的工作站对接场景。
|
||||
Reference in New Issue
Block a user