Skip to content

Quickstart

Run the demo of an agent watching a simulated 3D print, then a Hub of your own with a simulated Arduino plant waterer. Nothing here touches real hardware.

Install

On macOS and Linux, run this command in a terminal.

curl -LsSf https://sohanbhat.me/OpenHAL/install.sh | sh

On Windows, run this command in PowerShell.

irm https://sohanbhat.me/OpenHAL/install.ps1 | iex

Install also covers uv, pipx and a clone.

Run the demo

$ openhal demo

The demo connects an agent to a hub with a simulated Klipper printer and camera, then prints each message.

The agent reads a frame from the printer's camera to check how the print is starting.

  1. Request
    hal/read { "device": "printer-cam", "channel": "frame" }
  2. Response
    result { "value": { "mimeType": "image/jpeg", "bytes": 19409, "width": 640, "height": 480, "data": "…" } }

A corner of the print is lifting off the bed, so the agent sends a request to raise the bed target. The new target is inside the bed limit, so the hub passes the request to the printer.

  1. Request
    hal/write { "device": "printer", "channel": "bed_target", "value": 70, "unit": "degC" }
  2. Response
    result { "status": "done" }

A hotter nozzle might help the corner stick, so the agent sends a request for a higher nozzle temperature. The request is above the nozzle limit, so the hub blocks the request.

  1. Request
    hal/write { "device": "printer", "channel": "extruder_target", "value": 300, "unit": "degC" }
  2. Response
    error -32010 LIMIT_EXCEEDED extruder_target 300 degC exceeds the limit of 260 degC (printer.hal.yaml, confirmed by owner 2026-10-24)

Later the agent reads another frame to check the print again.

  1. Request
    hal/read { "device": "printer-cam", "channel": "frame" }
  2. Response
    result { "value": { "mimeType": "image/jpeg", "bytes": 23567, "width": 640, "height": 480, "data": "…" } }

The print has come loose, so the agent pauses the print.

  1. Request
    hal/action { "device": "printer", "action": "pause" }
  2. Response
    result { "status": "done" }

The agent sends a request to cancel the print. A cancel can't be undone, so the hub holds the request until the owner approves the request.

  1. Request
    hal/action { "device": "printer", "action": "cancel" }
  2. Response
    result { "status": "pending", "requestId": "REQUEST_ID", "expiresInS": 300 }

The owner approves the cancel with the human token over the same protocol, and the cancel runs.

  1. Request
    hal/confirmations/approve { "requestId": "REQUEST_ID" }
  2. Response
    result { "status": "done" }

The full example shows the same run with the audit log.

Run the whole system

$ openhal demo --full
  soil_moisture rises from 31 % to 46 %
<- error -32010 LIMIT_EXCEEDED

--full starts from an empty hub, discovers the simulated plant waterer board and drafts the board's Device File with the driver writer. A script fills in the limits interview, and an agent connects through openhal mcp and drafts a routine that a person enables. The duration limit blocks the agent's request to water for 120 s before the request reaches the board. e2e.yaml records every step.

Run the hub yourself

init creates ~/.openhal with a human token and a Device File for the simulated plant waterer, a medium risk board that takes commands at once within the board's own range. openhal risk show plant-waterer lists each level.

$ openhal init
sample device  ~/.openhal/devices/plant-waterer.hal.yaml created, risk 2 medium, every one takes commands
$ openhal start &
$ openhal status
devices 1 of 1 connected

The hub listens on 127.0.0.1:7420 only. A high risk channel such as a heater stays read-only until openhal file confirm, and a running hub loads each confirmed file with no restart.

Your first device

Plug in a board running the OpenHAL firmware, or an ESPHome node, and run openhal init again. Low and medium channels such as an LED or a pump take commands at once. A high risk channel opens the limits interview, one question per limit, where Enter keeps the suggestion and you type confirm once at the end. The Inspector's Review limits action shows the same questions.

To raise a limit later, run openhal limits set DEVICE.CHANNEL --max VALUE and type confirm. A narrower limit applies at once. A wider limit from a script or an agent is held in openhal confirm list until you approve.

Send two commands

$ openhal action plant-waterer.water duration=120s
blocked above_max  Blocked. The duration limit is 30 s (max). Requested 120 s.
$ openhal action plant-waterer.water duration=20s
done  Done. water ran.

The first duration is above the limit, so the Safety Kernel blocked the command before the command reached the board. Over the protocol, the same command returns error -32010 LIMIT_EXCEEDED.

Stop

Any client may stop every device. Lifting the stop takes the human token and the typed phrase unlock.

$ openhal stop
plant-waterer  safe
$ openhal unlock

Next