openhal-firmware
tested on simulator
| Tier | Tier 1 |
|---|---|
| Protocol | OpenHAL firmware protocol 1 over USB serial or UART |
| Covers | Arduino boards, ESP32 boards, maker projects that run the OpenHAL library |
| Hazard default | low |
| Conformance | full |
| Tested with | protocol simulator, and the real Arduino library built for the host computer |
Estimated reach. Any board the Arduino toolchain supports, once it runs the library. The library is plain C++ with no heap and no board-specific code, so it builds wherever the Arduino core provides Stream and millis. It is compile-checked for arduino:avr:uno and esp32:esp32. Coverage grows with every maker who adds it to a sketch, so there is no fixed model count.
The openhal-firmware driver talks to boards running the OpenHAL Arduino library (firmware/arduino/OpenHAL). The board describes its readings, outputs and actions, enforces its own limits and falls to its safe state when heartbeats stop, and the protocol is in spec/FIRMWARE-PROTOCOL.md.
Device file
This device file is the plant waterer from examples/plant-waterer/, for a real Uno.
device: Arduino plant waterer
id: plant-waterer
match:
firmware: { product: plant-waterer }
connect: { openhal-firmware: auto, boot_s: 2 }
read:
soil_moisture: { unit: "%", every: 1 s, range: [0, 100] }
tank: { type: enum, values: [empty, ok], every: 1 s }
actions:
water:
description: Run the pump for a number of seconds.
args:
duration: { unit: s, min: 1, max: 30 }
require:
- condition: "tank == ok"
message: "pump can't run while tank is empty"
max_runs: { count: 3, per: 1 h }
stop:
description: Turn the pump off now.
safe_state: { action: stop }
watchdog: { heartbeat: 1 s, on_loss: safe_state }
hazard: low| Parameter | Default | Meaning |
|---|---|---|
port | auto | A serial device path, a pyserial URL, auto (the first known Arduino or ESP32 USB id) or sim:<name> for the built-in simulator |
baud | 115200 | Serial speed |
timeout_s | 1.0 | How long to wait for each reply |
boot_s | 0 | Wait after opening the port, for boards that reset when the port opens (set 2 for an Uno) |
Install the optional dependency with uv pip install "openhal-hub[openhal-firmware]" (pyserial).
Behavior
- Connect sends
HELLO, checks the protocol version, readsDESC, and starts a heartbeat at a third of the board's timeout. - Reads use a streamed
EVTvalue when the value is less than 5 s old, and sendREADotherwise. - Channel changes send
WRITEand return the value the board applied, and actions sendACTwith every argument in the board's unit. A board refusal (ERR limit,ERR interlock) becomes a DriverError, and the kernel marks what the command touched unknown until a readback. safe_state_supportis native, so a stop sendsSAFEand the kernel also applies the device file's safe state.describe()turnsDESCinto device file sections with every limit marked suggested, so the file stays read-only until a person confirms the file.- Discovery only probes ports whose USB vendor id is Arduino, WCH CH340, Silicon Labs CP210x, Espressif, Adafruit or FTDI, and only sends
HELLO.
Quirks
- Because many boards reset when the port opens, set
boot_s, or the firstHELLOtimes out. - A sketch that registers channels wrongly (
misconfigured()) returnsERR busy misconfiguredfor everyWRITEandACT, but reads still work. - On an ESP32 the USB CDC port may enumerate under Espressif (303a) or a bridge chip id, so discovery probes both.
Tests
tests/drivers/test_openhal_firmware.py covers the codec, the simulator and the driver through the kernel. tests/firmware/test_arduino_host.py builds the real library and every example for the host computer and runs the conformance suite against it. Hardware steps are in docs/hardware-tests/openhal-firmware.md.