9.5 KiB
name, description
| name | description |
|---|---|
| add-protocol | Guide for adding new experiment protocols to Uni-Lab-OS (添加新实验操作协议). Walks through ROS Action definition, Pydantic model creation, protocol generator implementation, and registration. Use when the user wants to add a new protocol, create a compile function, implement an experiment operation, or mentions 协议/protocol/编译/compile/实验操作. |
添加新实验操作协议(Protocol)
Protocol 是对实验有意义的完整动作(如泵转移、过滤、溶解),需要多设备协同。compile/ 中的生成函数根据设备连接图将抽象操作"编译"为设备指令序列。
添加一个 Protocol 需修改 6 个文件,按以下流程执行。
第一步:确认协议信息
向用户确认:
| 信息 | 示例 |
|---|---|
| 协议英文名 | MyNewProtocol |
| 操作描述 | 将固体样品研磨至目标粒径 |
| Goal 参数(必需 + 可选) | vessel: dict, time: float = 300.0 |
| Result 字段 | success: bool, message: str |
| 需要哪些设备协同 | 研磨器、搅拌器 |
第二步:创建 ROS Action 定义
路径:unilabos_msgs/action/<ActionName>.action
三段式结构(Goal / Result / Feedback),用 --- 分隔:
# Goal
Resource vessel
float64 time
string mode
---
# Result
bool success
string return_info
---
# Feedback
string status
string current_device
builtin_interfaces/Duration time_spent
builtin_interfaces/Duration time_remaining
类型映射:
| Python 类型 | ROS 类型 | 说明 |
|---|---|---|
dict |
Resource |
容器/设备引用,自定义消息类型 |
float |
float64 |
|
int |
int32 |
|
str |
string |
|
bool |
bool |
Resource是unilabos_msgs/msg/Resource.msg中定义的自定义消息类型。
第三步:注册 Action 到 CMakeLists
在 unilabos_msgs/CMakeLists.txt 的 set(action_files ...) 块中添加:
"action/MyNewAction.action"
调试时需编译:
cd unilabos_msgs && colcon build && source ./install/local_setup.sh && cd ..PR 合并后 CI/CD 自动发布,mamba update ros-humble-unilabos-msgs即可。
第四步:创建 Pydantic 模型
在 unilabos/messages/__init__.py 中添加(位于 # Start Protocols 和 # End Protocols 之间):
class MyNewProtocol(BaseModel):
# === 必需参数 ===
vessel: dict = Field(..., description="目标容器")
# === 可选参数 ===
time: float = Field(300.0, description="操作时间 (秒)")
mode: str = Field("default", description="操作模式")
def model_post_init(self, __context):
"""参数验证和修正"""
if self.time <= 0:
self.time = 300.0
规则:
- 参数名必须与
.action文件中 Goal 字段完全一致 dict类型对应.action中的Resource- 将类名加入文件末尾的
__all__列表
第五步:实现协议生成函数
路径:unilabos/compile/<protocol_name>_protocol.py
import networkx as nx
from typing import List, Dict, Any
def generate_my_new_protocol(
G: nx.DiGraph,
vessel: dict,
time: float = 300.0,
mode: str = "default",
**kwargs,
) -> List[Dict[str, Any]]:
"""将 MyNewProtocol 编译为设备动作序列。
Args:
G: 设备连接图(NetworkX),节点为设备/容器,边为物理连接
vessel: 目标容器 {"id": "reactor_1"}
time: 操作时间(秒)
mode: 操作模式
Returns:
动作列表,每个元素为:
- dict: 单步动作
- list[dict]: 并行动作
"""
from unilabos.compile.utils.vessel_parser import get_vessel
vessel_id, vessel_data = get_vessel(vessel)
actions = []
# 查找相关设备(通过图的连接关系)
# 生成动作序列
actions.append({
"device_id": "target_device_id",
"action_name": "some_action",
"action_kwargs": {"param": "value"}
})
# 等待
actions.append({
"action_name": "wait",
"action_kwargs": {"time": time}
})
return actions
动作字典格式
# 单步动作(发给子设备)
{"device_id": "pump_1", "action_name": "set_position", "action_kwargs": {"position": 10.0}}
# 发给工作站自身
{"device_id": "self", "action_name": "my_action", "action_kwargs": {...}}
# 等待
{"action_name": "wait", "action_kwargs": {"time": 5.0}}
# 并行动作(列表嵌套)
[
{"device_id": "pump_1", "action_name": "set_position", "action_kwargs": {"position": 10.0}},
{"device_id": "stirrer_1", "action_name": "start_stir", "action_kwargs": {"stir_speed": 300}}
]
关于 vessel 参数类型
现有协议的 vessel 参数类型不统一:
- 新协议趋势:使用
dict(如{"id": "reactor_1"}) - 旧协议:使用
str(如"reactor_1") - 兼容写法:
Union[str, dict]
建议新协议统一使用 dict 类型,通过 get_vessel() 兼容两种输入。
公共工具函数(unilabos/compile/utils/)
| 函数 | 用途 |
|---|---|
get_vessel(vessel) |
解析容器参数为 (vessel_id, vessel_data),兼容 dict 和 str |
find_solvent_vessel(G, solvent) |
根据溶剂名查找容器(精确→命名规则→模糊→液体类型) |
find_reagent_vessel(G, reagent) |
根据试剂名查找容器(支持固体和液体) |
find_connected_stirrer(G, vessel) |
查找与容器相连的搅拌器 |
find_solid_dispenser(G) |
查找固体加样器 |
协议内专属查找函数
许多协议在自己的文件内定义了专属的 find_* 函数(不在 utils/ 中)。编写新协议时,优先复用 utils/ 中的公共函数;如需特殊查找逻辑,在协议文件内部定义即可:
def find_my_special_device(G: nx.DiGraph, vessel: str) -> str:
"""查找与容器相关的特殊设备"""
for node in G.nodes():
if 'my_device_type' in G.nodes[node].get('class', '').lower():
return node
raise ValueError("未找到特殊设备")
复用已有协议
复杂协议通常组合已有协议:
from unilabos.compile.pump_protocol import generate_pump_protocol_with_rinsing
actions.extend(generate_pump_protocol_with_rinsing(
G, from_vessel=solvent_vessel, to_vessel=vessel, volume=volume
))
图查询模式
# 查找与容器相连的特定类型设备
for neighbor in G.neighbors(vessel_id):
node_data = G.nodes[neighbor]
if "heater" in node_data.get("class", ""):
heater_id = neighbor
break
# 查找最短路径(泵转移)
path = nx.shortest_path(G, source=from_vessel_id, target=to_vessel_id)
第六步:注册协议生成函数
在 unilabos/compile/__init__.py 中:
- 顶部添加导入:
from .my_new_protocol import generate_my_new_protocol
- 在
action_protocol_generators字典中添加映射:
action_protocol_generators = {
# ... 已有协议
MyNewProtocol: generate_my_new_protocol,
}
第七步:配置图文件
在工作站的图文件中,将协议名加入 protocol_type:
{
"id": "my_station",
"class": "workstation",
"config": {
"protocol_type": ["PumpTransferProtocol", "MyNewProtocol"]
}
}
第八步:验证
# 1. 模块可导入
python -c "from unilabos.messages import MyNewProtocol; print(MyNewProtocol.model_fields)"
# 2. 生成函数可导入
python -c "from unilabos.compile import action_protocol_generators; print(list(action_protocol_generators.keys()))"
# 3. 启动测试(可选)
unilab -g <graph>.json --complete_registry
工作流清单
协议接入进度:
- [ ] 1. 确认协议名、参数、涉及设备
- [ ] 2. 创建 .action 文件 (unilabos_msgs/action/<Name>.action)
- [ ] 3. 注册到 CMakeLists.txt
- [ ] 4. 创建 Pydantic 模型 (unilabos/messages/__init__.py) + 更新 __all__
- [ ] 5. 实现生成函数 (unilabos/compile/<name>_protocol.py)
- [ ] 6. 注册到 compile/__init__.py
- [ ] 7. 配置图文件 protocol_type
- [ ] 8. 验证
高级模式
实现复杂协议时,详见 reference.md:协议运行时数据流、mock graph 测试模式、单位解析工具(unit_parser.py)、复杂协议组合模式(以 dissolve 为例)。
现有协议速查
| 协议 | Pydantic 类 | 生成函数 | 核心参数 |
|---|---|---|---|
| 泵转移 | PumpTransferProtocol |
generate_pump_protocol_with_rinsing |
from_vessel, to_vessel, volume |
| 简单转移 | TransferProtocol |
generate_pump_protocol |
from_vessel, to_vessel, volume |
| 加样 | AddProtocol |
generate_add_protocol |
vessel, reagent, volume |
| 过滤 | FilterProtocol |
generate_filter_protocol |
vessel, filtrate_vessel |
| 溶解 | DissolveProtocol |
generate_dissolve_protocol |
vessel, solvent, volume |
| 加热/冷却 | HeatChillProtocol |
generate_heat_chill_protocol |
vessel, temp, time |
| 搅拌 | StirProtocol |
generate_stir_protocol |
vessel, time |
| 分离 | SeparateProtocol |
generate_separate_protocol |
from_vessel, separation_vessel, solvent |
| 蒸发 | EvaporateProtocol |
generate_evaporate_protocol |
vessel, pressure, temp, time |
| 清洗 | CleanProtocol |
generate_clean_protocol |
vessel, solvent, volume |
| 离心 | CentrifugeProtocol |
generate_centrifuge_protocol |
vessel, speed, time |
| 抽气充气 | EvacuateAndRefillProtocol |
generate_evacuateandrefill_protocol |
vessel, gas |