跳到主要内容

MHS 开发指南

七天实战

Driver 与编排实战见 7 天学会 MHS Day 4–5 及 Driver 工作台(LAB.05)。

延伸阅读

开源依赖选型见 GitHub 项目导读;生产安全见 最佳实践

开发指南定位

本文档面向需要实现 MHS Driver、编写 Device Description、设计闭环编排器的工程师。不涉及 mhs_frontend 站点部署;聚焦协议语义与可复用实现模式。

Driver 最小接口

每个 MHS Driver 必须实现:

方法职责
initialize()连接硬件、加载 Description、自检
read()返回完整或增量状态向量
write(cmd)执行物理操作,内部做边界校验
get_description()返回 Device Description
shutdown()安全关闭,尽量进入 safe state
safe_state()故障时进入已知安全配置
from abc import ABC, abstractmethod

class MHSDriver(ABC):
@abstractmethod
def read(self) -> StateVector: ...

@abstractmethod
def write(self, cmd: dict) -> WriteResult: ...

@abstractmethod
def get_description(self) -> DeviceDescription: ...

def write_with_verify(self, cmd, tolerance):
self.write(cmd)
state = self.read()
if not verify(state, cmd, tolerance):
raise VerifyFailed(state)
return state

协议适配模式

模式适用优点缺点
轮询 PollingModbus、简单串口实现简单总线占用高
事件驱动CAN、OPC-UA 订阅低延迟、省带宽状态合并复杂
混合EtherCAT + 慢传感器兼顾实时与完整Driver 复杂度高

状态缓存策略:高频 read 通道可本地缓存 50–200 ms,但须在 Description 声明 max_staleness_ms,编排器据此判断是否信任缓存。

从厂商 SDK 封装

典型步骤:

  1. 逆向协议文档:提取寄存器/命令与物理量对应关系。
  2. 单位标准化:mA → A,mmHg → Pa,厂商 enum → 字符串 literal。
  3. 异步回调 → 状态向量:SDK 回调更新内部 StateVector 草稿,read 时返回快照。
  4. 边界映射:把 SDK 的 SetPower(w) 包装为带 hard_limit 检查的 write。

Genentech 蛋白质处理案例表明:厂商 SDK 往往不区分「配置错误」与「物理故障(气泡)」—— Driver 层应增加 qualityanomaly 通道供 Agent 解读。

Device Description 工程

Schema 核心区块

device: { vendor, model, firmware, serial }
capabilities: { channel_name: { access, range, unit, verify, ... } }
safety: { hard_limits, soft_limits, interlocks, modes }
interface: { protocol, address, baud, timeout_ms }
metadata: { calibration_date, environment, maintenance }

互锁表达

interlocks:
- id: laser_cooling
when: "power_w > 0"
requires:
cooling_lpm: { min: 2.0 }
shutter_closed: { equals: false }
on_violation: reject # reject | safe_state | human

互锁应可单元测试:给定 mock 状态向量,断言 check 结果。

从 Description 生成 MCP Tool Schema

Day 2 课纲对比示例:Description 中 power_w 的 range 与 hard_limits 自动裁剪 JSON Schema 的 maximum。扩展字段 x-mhs 携带 verify 策略,Bridge 层读取后执行闭环。

多协议 Driver 示例矩阵

协议Python 库read 映射write 映射
Modbusmodbus-tk读寄存器→缩放写寄存器
CAN-FDpython-can解码 DBC编码帧
VISA/GPIBPyVISASCPI querySCPI write
OPC-UAasyncua读节点写节点
HTTPhttpxGET /statusPOST /cmd

并发与多 Agent 访问

同一设备可能被多个 Agent 或人机界面并发访问。Driver 应实现:

  • 写队列:write 串行化,避免竞态;
  • 租约 Lease:长时间独占操作(如离心机运行)需 acquire lease;
  • 读并发:read 可并行,返回一致快照或带版本号。
class DeviceLock:
async def acquire(self, holder, ttl_s=300): ...
async def release(self, holder): ...

编排器设计

闭环编排核心循环

for step in plan:
mhs.write(step.device, step.cmd)
s = mhs.read(step.device)
if not step.accept(s):
action = policy.decide(step, s) # retry|skip|abort|human
log.audit(step, s, action)
if action == "abort":
mhs.safe_state(all_devices)
break

任务分解

高层目标「制备样品」→ 子目标「加热到 37°C 并稳定 5 min」→ 设备步「write setpoint → read until stable → hold timer」。

Janelia 成像流水线:3 相机 + 1 激光 + 2 位移台,编排器用同步屏障:所有设备 read 满足 ready_for_capture 才触发相机 write。

慢操作与进度状态

机械臂 move 30 s 完成期间,Driver 应在状态向量暴露 progress_pctphase: moving|idle,编排器 poll read 而非阻塞 blindly。

HIL 测试(硬件在环)

测试类内容
边界min/max/超界/非法类型
通信断线、超时、半包
互锁各互锁组合 true/false
性能read P95 延迟、write 执行时间
回归固件升级前后状态向量一致

可用 Gazebo / 设备模拟器作 HIL 前半段,真实设备作后半段。

MCP 映射实现

Bridge responsibilities:

  1. tool 参数 → MHS write dict
  2. 执行 verify 策略
  3. 更新 resource URI(如 mhs://laser-001/state
  4. 将 StateVector 序列化为 MCP resource JSON

6 轴机械臂 Driver 要点(Day 4 实战)

  • read:joint_angles_deg[6]pose_xyz_rpymotion_complete
  • write:joint_targetscartesian_move(带速度上限)
  • safety:工作空间硬边界、碰撞互锁(若有力矩传感)
  • verify:read motion_complete == true 且位置误差 < ε

审计与可观测性

每次 write 记录:caller、cmd、pre_state、post_state、verify_result、duration_ms。越界尝试单独标记 severity: warning。对接 OpenTelemetry 时,span 名建议 mhs.write / mhs.read,属性带 device.idchannel

版本与热插拔

  • 设备断开:Driver 进入 degraded,read 返回 quality: fault,write 拒绝;
  • 重连:重新 initialize,对比 Description 版本;
  • 固件升级:Description 中 firmware 范围约束,不匹配则拒绝 write 并告警。

常见开发陷阱

  1. 只映射 write 不映射 read → Agent 盲飞。
  2. 忽略 confidence → 噪声大时误判成功。
  3. 互锁只写在 Agent 提示词里 → 必须写在 Description + Driver。
  4. MCP tool 不声明 x-mhs verify → 桥接层开环化,丧失 MHS 价值。

下一步

Modbus Driver 完整 walkthrough

寄存器映射文档化

在 Device Description 的 interface.register_map 中声明映射,便于审计:

interface:
protocol: modbus_tcp
host: 192.168.1.50
port: 502
unit_id: 1
register_map:
temperature_c:
register: 0x0001
type: int16
scale: 0.1
access: read
setpoint_c:
register: 0x0002
type: int16
scale: 0.1
access: write

错误恢复

Modbus 异常码 02(非法地址)与 04(设备故障)应映射为不同异常类型,编排器对后者直接 abort 并 safe_state。

CAN-FD Driver 要点

  • 使用 DBC 文件定义信号 → 状态向量通道;
  • 多帧组装:长状态拆包时 read 返回 complete: false 直到收齐;
  • 总线负载:Description 声明 max_frame_rate_hz

USB-CDC / SCPI 仪器

PyVISA 示例路径:

import pyvisa

class VisaOscilloscopeDriver(MHSDriver):
def read(self):
vpp = float(self.inst.query("MEAS:VPP? CH1"))
freq = float(self.inst.query("MEAS:FREQ? CH1"))
return StateVector(
ch1_vpp=StateValue(vpp, "V", 0.92, now()),
ch1_freq=StateValue(freq, "Hz", 0.90, now()),
)

SCPI 命令错误应捕获 VisaIOError 并标记 communication fault。

OPC-UA 桥接

  • read:批量读 NodeId 列表,统一时间戳;
  • write:写前读 StatusCode;
  • 订阅:Subscription 回调更新缓存,read 返回最新 cached 向量。

MHS 与 OPC-UA 分工:OPC-UA 不替代 verify 语义,Bridge 仍在 MHS 层做 accept 谓词。

编排策略模式

模式场景
Sequential线性实验步骤
Parallel fork-join多孔板同时加热,全部 stable 后进入下一步
State machine模式切换 idle→run→maintenance
Human gate不可逆操作前等待 confirm token

QuEra 案例工程化解读

激光锁频 99.3% 成功率依赖:

  1. 高频 read 相位/功率;
  2. 小步 write 修正而非大步跳跃;
  3. 硬边界防止烧样品;
  4. 失败时 rollback 到上一个 stable 锁定点。

编排器 accept 谓词示例:abs(error_hz) < 1e6confidence > 0.95

CMU 药物实验:故障拦截表

故障 ID检测动作
F1温度超软边界暂停 + 通知
F2泵压为零abort + safe_state
F3通信超时 3 次human
F4剂量偏差 >5%retry write 一次
F5未知试剂 IDreject write
F6计时器漂移重新 sync 时钟

此类表应版本化,与 Description 一并评审。

测试金字塔

单元测试不依赖硬件;HIL 用模拟器;E2E 用完整 lab 场景脚本。

CI 集成建议

# 概念性 CI 片段
- name: Driver unit tests
run: pytest tests/drivers/ --cov
- name: Description lint
run: mhs-desc-lint descriptions/*.yaml
- name: Sim HIL smoke
run: pytest tests/hil_sim/ -m smoke

性能基准参考

指标实验室仪器运动控制
read P95小于 50 ms小于 5 ms
write+verify小于 500 ms小于 100 ms
并发 read QPS10–100100–1000

具体 SLO 写入 Description performance 区块。

开源 Driver 贡献规范

社区 Driver 仓库建议包含:Description YAML、HIL 测试、协议文档链接、安全分析报告模板。类似 CUPS 打印机驱动模型。

与 ROS 2 桥接

ROS 2 Action /move_to_pose 可在 Driver write 内调用,read 映射 action feedback → progress。MHS 编排器不直接发 ROS topic,保持 Agent 侧协议统一。

文档与代码同步

Description 变更必须 semver;Breaking change 升级 major,Driver 在 initialize 检查 desc.version 兼容性。

Janelia 多设备同步实现笔记

脑成像流水线 11h→23min 的关键是消除固定 sleep:传统脚本 move(); sleep(5); capture() 浪费大量 wall-clock。MHS 编排改为:

async def wait_all(devices, predicate, timeout=120):
deadline = time.time() + timeout
while time.time() < deadline:
states = await asyncio.gather(*[mhs.read(d) for d in devices])
if predicate(states):
return states
await asyncio.sleep(0.05)
raise SyncTimeout(devices)

predicate 示例:位移台 stable 且激光 power_w 在容差内且三台相机 ready

Genentech 案例:异常通道设计

为帮助 Agent 区分物理与软件故障,Description 增加:

capabilities:
sample_quality:
access: read_only
type: enum
values: [ok, bubble_suspected, precipitate, unknown]
last_error:
access: read_only
type: string

Driver 结合视觉或浊度传感更新 sample_quality,LLM 读 state 而非猜日志。

EtherCAT 与实时性

运动控制 Driver 可能 bypass 标准 poll,使用 cyclic read;须在 Description 标注 realtime: truecycle_us。Agent 编排层仍用 read/write,但调度器需高优先级线程。

MQTT IoT Driver

主题约定:devices/{id}/state JSON → StateVector;devices/{id}/cmd ← write。保留 LWT 检测离线。QoS1 用于 write,QoS0 用于高频 read 流。

安全代码审查清单

  • 所有 write 路径是否调用 boundary check?
  • safe_state 是否幂等?
  • 审计是否含 pre/post state?
  • MCP Bridge 是否默认 verify?
  • 并发 write 是否串行化?

数字孪生同步 Driver 模式

read 时双写:一路更新 StateVector,一路 push 到孪生 MQTT/WebSocket。write 前可从孪生预演(若世界模型可用)。Description 字段 twin_endpoint 声明同步目标。

批量 read 优化

编排器需要多设备 snapshot 时使用 mhs.read_many([ids]),Driver 层合并总线事务(如 Modbus 连续寄存器读),降低往返延迟。Janelia 级同步依赖此优化。

国际化与单位

Description 强制 SI;UI 可显示友好单位。Driver 内统一转换,禁止 Agent 侧自行换算导致双倍误差。

附录:WriteResult 结构

@dataclass
class WriteResult:
accepted: bool
rejected_reason: str | None
pre_state_hash: str
post_state_pending: bool # 慢操作

附录:Description Lint 规则

  • 每个 read_write 通道必须有 range 或 enum
  • hard_limits 必须严于 range 或相等
  • interlock 表达式必须可解析
  • 必填 device.vendor/model

结语

Driver 与编排的开发质量,最终体现在物理世界是否可预测、可验证、可审计。优先保证 read 可信与边界不可绕过,再优化延迟与 UX。