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.