Specification · section 14
Bridging existing ecosystems
MHP is deliberately thin enough to sit on top of the control layers labs already run. The reference implementation ships an adapter for each, built on one mechanism: bindings. A binding pairs an MHP name with a callable into the foreign layer, and BoundDriver turns three lists of bindings into a complete MHP driver with every gate, tier, job and notification inherited.
from openmhp.adapters import BoundDriver, Signal, Setting, Action
dev = BoundDriver(device={"id": "hotplate-01", "class": "hotplate", "notes": "Fume hood 2."},
signals=[Signal("plate_temperature", read=lambda: plc.read(0x10), unit="degC")],
settings=[Setting("target_temperature", write=lambda v: plc.write(0x20, v),
limits={"min": 20, "max": 300})],
actions=[Action("shutdown", run=lambda job, p: plc.write(0x21, 0), approval="confirm")],
estop=lambda: plc.write(0x21, 0))
| Layer | Adapter | Mapping |
|---|---|---|
| OPC UA / Modbus | opcua_device(url, ...) |
variable node → signal or setting; method node → action with positional args; abort method → estop. Modbus uses BoundDriver with two register callables. |
| ROS 2 | ros2_device(node, ...) |
topic subscription → signal; topic publication → setting; action server → action with feedback → progress and cancel, reporting the goal's terminal status (aborted is a failure); signals older than max_age_s read as no value; Trigger service → estop, with its result checked. |
| SiLA 2 | sila_device(host, port, ...) |
property → signal; one-parameter unobservable command → setting; command → action, observable commands stream progress and accept cancel; any stop command → estop. Leases replace LockController. |
| MADSci | madsci_node(url, ...) |
self-describing: /info actions → actions with params and notes; /state keys → signals; /status busy → ping state; /action + polling → jobs; cancel is sent to the node and the job ends only on a terminal status, and a node that keeps running after a cancel faults the device; /admin/safety_stop → estop. No binding map needed. |
| PyLabRobot | plr_device(machine, ...) |
every public coroutine on a Machine → action with params from its signature and enforced limits; setup() on start, stop() on estop; coroutines run on a private event loop. Actions cannot be cancelled mid-move (use e-stop); an operation that times out is cancelled and faults the device. |
| MQTT | mqtt_device(url, ...) |
subscribed topic → signal (last message cached with its arrival time; stale after max_age_s, default 5 s); published topic → setting; a command topic paired with a reply topic → action, correlated by a job field in both payloads; without a reply topic an action is fire-and-forget and returns once the broker has acknowledged the publish, which is not the same as the device having acted. Estop publishes at QoS 2; like the action case, that confirms the broker received it, not that the device stopped — pair it with a signal on a state topic to verify. |
Each adapter is a few lines per device; the openmhp-adapt-fleet skill (§14.1) walks a harness through a whole fleet. Adapters MUST implement the safety gates in §7 themselves; wrapping a layer that lacks limits does not exempt the MHP server from enforcing them. The reference adapters get this for free from BoundDriver. They are tested against fakes of each client library; no vendor system has been exercised yet.
14.1 Agent Skills
MHP ships three Agent Skills so that any skills-capable harness can bring hardware under the protocol and operate it without bespoke prompting:
| Skill | When it activates | What it does |
|---|---|---|
openmhp-onboard-device |
one instrument to connect | interviews the owner, writes descriptor + driver, validates, serves, registers |
openmhp-adapt-fleet |
OPC UA / ROS 2 / SiLA 2 / MADSci / PyLabRobot fleet | one adapter file per device, manifest, directory, mhp-mcp config, proof checklist |
openmhp-operate |
any task on connected hardware | the find → describe → check → dry-run → act → verify loop; refusal handling; e-stop rules |
Skills follow the progressive-disclosure design of the protocol itself: a harness holds only their names and descriptions until a task matches.