Build

Lab nodes

The two-computer setup most labs already have: instruments wired to a bench PC, an agent on your laptop. One command on each, and they find each other over the network.

MachineWhat you runWhat it does
The computer wired to your instrumentsnpx openmhp-node setupserves each instrument over HTTP, advertises them on the LAN, keeps doing it after a reboot
Your laptopnpx openmhp-cli setupthe agent harness and the MCP bridge; finds nodes and operates what they serve

They can be the same machine. If your agent runs on the bench PC itself, install both and skip the network entirely — nothing else about the setup changes.

On the instrument computer

This is the machine with the USB cable, the vendor software, or the network link to the instrument. It never needs an agent on it.

npx openmhp-node setup

It asks two questions — which folder holds your device packages (default ~/.openmhp/devices) and which port to start counting from (default 18900) — then installs a background service and starts it:

[openmhp] found 3 package(s): balance-02, hotplate-01, pump-03
  wrote /home/lab/.config/systemd/user/openmhp-node.service
  systemctl --user daemon-reload: ok
  systemctl --user enable --now openmhp-node.service: ok
installed. running now, and at every login.

From then on the machine answers on its own. Two commands manage it:

npx openmhp-node status    # installed? running?
npx openmhp-node stop      # remove the service

The service is a systemd --user unit on Linux, a LaunchAgent on macOS, and a Startup-folder entry on Windows. On Windows that means it starts once someone logs in, not before; on a headless Linux bench PC where nobody stays logged in, also run loginctl enable-linger $USER so the unit survives logout.

One computer, several instruments

Common case: a USB hub with a hotplate, a balance and a syringe pump on it. One node process serves all of them — you do not run one per instrument. What the node needs is one device package per instrument in that folder, because it serves packages, not USB ports. It does not scan the bus and guess what is plugged in; each package says which port its instrument is on, along with its limits and how to stop it.

~/.openmhp/devices/
├── balance-02/      DEVICE.md + descriptor.yaml  (serial.port: /dev/ttyUSB1)
├── hotplate-01/     DEVICE.md + descriptor.yaml  (serial.port: /dev/ttyUSB0)
└── pump-03/         DEVICE.md + descriptor.yaml  (serial.port: /dev/ttyUSB2)

Each gets its own port, counting up from the base, and each is advertised separately, so they arrive in a scan as three instruments rather than one computer:

Package folderSerial portServed at
balance-02/dev/ttyUSB1http://bench-pc:18900
hotplate-01/dev/ttyUSB0http://bench-pc:18901
pump-03/dev/ttyUSB2http://bench-pc:18902

Ports follow the folder names alphabetically, not the order things are plugged in — which is why the balance on ttyUSB1 is first. One consequence worth knowing: adding a package whose name sorts earlier shifts the ones after it by a port. Discovery re-announces either way, so a fresh scan is always correct, but an entry you added by hand to your lab may need adding again.

Keep the port names stable

On Linux, /dev/ttyUSB0 is assigned in the order devices appear, so two instruments on one hub can swap after a reboot — and a driver that opens the wrong one will happily send a hotplate's command to a pump. Use the stable path instead:

serial:
  port: /dev/serial/by-id/usb-FTDI_FT232R_A50285BI-if00-port0
  baud: 9600

Run ls /dev/serial/by-id/ with the instrument plugged in to find its name. On Windows the COM number is remembered per adapter, so COM3 generally stays with the same instrument; confirm in Device Manager.

On your laptop

npx openmhp-cli setup

Then open your agent and say "find the instruments on my network", or do it from the shell:

npx openmhp-cli scan
[
  { "target": "http://10.0.0.41:18900", "id": "balance-02", "class": "balance", "state": "idle" },
  { "target": "http://10.0.0.41:18901", "id": "hotplate-01", "class": "hotplate", "state": "idle" },
  { "target": "http://10.0.0.41:18902", "id": "pump-03", "class": "pump", "state": "idle" }
]

Add the ones you want, and tell the lab where they stand, since a package cannot know that:

npx openmhp-cli add http://10.0.0.41:18901 --location "bay 3, fume hood 2"

From then on they are in mhp_find results for every agent pointed at that bridge, with their owner's operating instructions and safety envelope attached.

When scan finds nothing

scan works two ways at once: it listens for mDNS adverts (_mhp._tcp) and probes ports 18900 to 18939 on localhost. Across machines it is the mDNS half that does the work, and multicast is the part most likely to be blocked — by a corporate network, a guest VLAN, or the two machines simply being on different subnets. Name the host directly and it probes that instead:

npx openmhp-cli scan 10.0.0.41              # or bench-pc.local
npx openmhp-cli add http://10.0.0.41:18901  # or skip scanning; the address is enough

Other things worth checking, in order: the node is running (npx openmhp-node status on the bench PC), the firewall allows inbound on those ports, and the instruments sit inside 18900–18939 if you want the plain probe to find them — a base port outside that range still works, you just have to name the address yourself.

Next