mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-03-27 00:49:45 +00:00
add:skill&agent
This commit is contained in:
207
.cursor/skills/add-protocol/reference.md
Normal file
207
.cursor/skills/add-protocol/reference.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# 协议高级参考
|
||||
|
||||
本文件是 SKILL.md 的补充,包含协议运行时数据流、测试模式、单位解析工具和复杂协议组合模式。Agent 在需要实现这些功能时按需阅读。
|
||||
|
||||
---
|
||||
|
||||
## 1. 协议运行时数据流
|
||||
|
||||
从图文件到协议执行的完整链路:
|
||||
|
||||
```
|
||||
实验图 JSON
|
||||
↓ graphio.read_node_link_json()
|
||||
physical_setup_graph (NetworkX DiGraph)
|
||||
↓ ROS2WorkstationNode._setup_protocol_names(protocol_type)
|
||||
为每个 protocol_name 创建 ActionServer
|
||||
↓ 收到 Action Goal
|
||||
_create_protocol_execute_callback()
|
||||
↓ convert_from_ros_msg_with_mapping(goal, mapping)
|
||||
protocol_kwargs (Python dict)
|
||||
↓ 向 Host 查询 Resource 类型参数的当前状态
|
||||
protocol_kwargs 更新(vessel 带上 children、data 等)
|
||||
↓ protocol_steps_generator(G=physical_setup_graph, **protocol_kwargs)
|
||||
List[Dict] 动作序列
|
||||
↓ 逐步 execute_single_action / 并行 create_task
|
||||
子设备 ActionClient 执行
|
||||
```
|
||||
|
||||
### `_setup_protocol_names` 核心逻辑
|
||||
|
||||
```python
|
||||
def _setup_protocol_names(self, protocol_type):
|
||||
if isinstance(protocol_type, str):
|
||||
self.protocol_names = [p.strip() for p in protocol_type.split(",")]
|
||||
else:
|
||||
self.protocol_names = protocol_type
|
||||
self.protocol_action_mappings = {}
|
||||
for protocol_name in self.protocol_names:
|
||||
protocol_type = globals()[protocol_name] # 从 messages 模块取 Pydantic 类
|
||||
self.protocol_action_mappings[protocol_name] = get_action_type(protocol_type)
|
||||
```
|
||||
|
||||
### `_create_protocol_execute_callback` 关键步骤
|
||||
|
||||
1. `convert_from_ros_msg_with_mapping(goal, action_value_mapping["goal"])` — ROS Goal → Python dict
|
||||
2. 对 `Resource` 类型字段,通过 `resource_get` Service 查询 Host 的最新资源状态
|
||||
3. `protocol_steps_generator(G=physical_setup_graph, **protocol_kwargs)` — 调用编译函数
|
||||
4. 遍历 steps:`dict` 串行执行,`list` 并行执行
|
||||
5. `execute_single_action` 通过 `_action_clients[device_id]` 向子设备发送 Action Goal
|
||||
6. 执行完毕后通过 `resource_update` Service 更新资源状态
|
||||
|
||||
---
|
||||
|
||||
## 2. 测试模式
|
||||
|
||||
### 2.1 协议文件内测试函数
|
||||
|
||||
许多协议文件末尾有 `test_*` 函数,主要测试参数解析工具:
|
||||
|
||||
```python
|
||||
def test_dissolve_protocol():
|
||||
"""测试溶解协议的各种参数解析"""
|
||||
volumes = ["10 mL", "?", 10.0, "1 L", "500 μL"]
|
||||
for vol in volumes:
|
||||
result = parse_volume_input(vol)
|
||||
print(f"体积解析: {vol} → {result}mL")
|
||||
|
||||
masses = ["2.9 g", "?", 2.5, "500 mg"]
|
||||
for mass in masses:
|
||||
result = parse_mass_input(mass)
|
||||
print(f"质量解析: {mass} → {result}g")
|
||||
```
|
||||
|
||||
### 2.2 使用 mock graph 测试协议生成器
|
||||
|
||||
推荐的端到端测试模式:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
import networkx as nx
|
||||
from unilabos.compile.stir_protocol import generate_stir_protocol
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def topology_graph():
|
||||
"""创建测试拓扑图"""
|
||||
G = nx.DiGraph()
|
||||
G.add_node("flask_1", **{"class": "flask", "type": "container"})
|
||||
G.add_node("stirrer_1", **{"class": "virtual_stirrer", "type": "device"})
|
||||
G.add_edge("stirrer_1", "flask_1")
|
||||
return G
|
||||
|
||||
|
||||
def test_generate_stir_protocol(topology_graph):
|
||||
"""测试搅拌协议生成"""
|
||||
actions = generate_stir_protocol(
|
||||
G=topology_graph,
|
||||
vessel="flask_1",
|
||||
time="5 min",
|
||||
stir_speed=300.0
|
||||
)
|
||||
assert len(actions) >= 1
|
||||
assert actions[0]["device_id"] == "stirrer_1"
|
||||
```
|
||||
|
||||
**要点:**
|
||||
- 用 `nx.DiGraph()` 构建最小拓扑
|
||||
- `add_node(id, **attrs)` 设置 `class`、`type`、`data` 等
|
||||
- `add_edge(src, dst)` 建立物理连接
|
||||
- 协议内的 `find_*` 函数依赖这些节点和边
|
||||
|
||||
---
|
||||
|
||||
## 3. 单位解析工具
|
||||
|
||||
路径:`unilabos/compile/utils/unit_parser.py`
|
||||
|
||||
| 函数 | 输入 | 返回 | 默认值 |
|
||||
|------|------|------|--------|
|
||||
| `parse_volume_input(input, default_unit)` | `"100 mL"`, `"2.5 L"`, `"500 μL"`, `10.0`, `"?"` | mL (float) | 50.0 |
|
||||
| `parse_mass_input(input)` | `"19.3 g"`, `"500 mg"`, `2.5`, `"?"` | g (float) | 1.0 |
|
||||
| `parse_time_input(input)` | `"30 min"`, `"1 h"`, `"300"`, `60.0`, `"?"` | 秒 (float) | 60.0 |
|
||||
|
||||
支持的单位:
|
||||
|
||||
- **体积**: mL, L, μL/uL, milliliter, liter, microliter
|
||||
- **质量**: g, mg, kg, gram, milligram, kilogram
|
||||
- **时间**: s/sec/second, min/minute, h/hr/hour, d/day
|
||||
|
||||
特殊值 `"?"`、`"unknown"`、`"tbd"` 返回默认值。
|
||||
|
||||
---
|
||||
|
||||
## 4. 复杂协议组合模式
|
||||
|
||||
以 `dissolve_protocol` 为例,展示如何组合多个子操作:
|
||||
|
||||
### 整体流程
|
||||
|
||||
```
|
||||
1. 解析参数 (parse_volume_input, parse_mass_input, parse_time_input)
|
||||
2. 设备发现 (find_connected_heatchill, find_connected_stirrer, find_solid_dispenser)
|
||||
3. 判断溶解类型 (液体 vs 固体)
|
||||
4. 组合动作序列:
|
||||
a. heat_chill_start / start_stir (启动加热/搅拌)
|
||||
b. wait (等待温度稳定)
|
||||
c. pump_protocol_with_rinsing (液体转移, 通过 extend 拼接)
|
||||
或 add_solid (固体加样)
|
||||
d. heat_chill / stir / wait (溶解等待)
|
||||
e. heat_chill_stop (停止加热)
|
||||
```
|
||||
|
||||
### 关键代码模式
|
||||
|
||||
**设备发现 → 条件组合:**
|
||||
|
||||
```python
|
||||
heatchill_id = find_connected_heatchill(G, vessel_id)
|
||||
stirrer_id = find_connected_stirrer(G, vessel_id)
|
||||
solid_dispenser_id = find_solid_dispenser(G)
|
||||
|
||||
actions = []
|
||||
|
||||
# 启动阶段
|
||||
if heatchill_id and temp > 25.0:
|
||||
actions.append({
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill_start",
|
||||
"action_kwargs": {"vessel": {"id": vessel_id}, "temp": temp}
|
||||
})
|
||||
actions.append({"action_name": "wait", "action_kwargs": {"time": 30}})
|
||||
elif stirrer_id:
|
||||
actions.append({
|
||||
"device_id": stirrer_id,
|
||||
"action_name": "start_stir",
|
||||
"action_kwargs": {"vessel": {"id": vessel_id}, "stir_speed": stir_speed}
|
||||
})
|
||||
|
||||
# 转移阶段(复用已有协议)
|
||||
pump_actions = generate_pump_protocol_with_rinsing(
|
||||
G=G, from_vessel=solvent_vessel, to_vessel=vessel_id, volume=volume
|
||||
)
|
||||
actions.extend(pump_actions)
|
||||
|
||||
# 等待阶段
|
||||
if heatchill_id:
|
||||
actions.append({
|
||||
"device_id": heatchill_id,
|
||||
"action_name": "heat_chill",
|
||||
"action_kwargs": {"vessel": {"id": vessel_id}, "temp": temp, "time": time}
|
||||
})
|
||||
else:
|
||||
actions.append({"action_name": "wait", "action_kwargs": {"time": time}})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 关键路径
|
||||
|
||||
| 内容 | 路径 |
|
||||
|------|------|
|
||||
| 协议执行回调 | `unilabos/ros/nodes/presets/workstation.py` |
|
||||
| ROS 消息映射 | `unilabos/ros/msgs/message_converter.py` |
|
||||
| 物理拓扑图 | `unilabos/resources/graphio.py` (`physical_setup_graph`) |
|
||||
| 单位解析 | `unilabos/compile/utils/unit_parser.py` |
|
||||
| 容器解析 | `unilabos/compile/utils/vessel_parser.py` |
|
||||
| 溶解协议(组合示例) | `unilabos/compile/dissolve_protocol.py` |
|
||||
Reference in New Issue
Block a user