Skip to Content
DevelopersDecision records0007 HAL contract v1

ADR 0007: HAL contract v1 — MQTT + HTTP, versioned JSON, HA discovery from the HAL

Status: Accepted Date: 2026-10-30

Context

Core and hald are separate processes, possibly on separate machines (ADR-0004). The interface between them must be small, stable, discoverable, and usable by Home Assistant with no core present.

Decision

Prefix seedypod/<unit-id>/. Payloads JSON, SI units, RFC 3339 timestamps. unit-id is derived from the Pi serial (never a shared default).

TopicDirRetainedPayload
availabilityhald→yesonline / offline (LWT)
systemhald→yesSystemInfo — model, version, caps, limits
sensor/<id>hald→noReading {ts, value, unit, quality}
sensor/<id>/calibrate→hald—CalibrationCommand {point, value?} (added 2026-10-31; EZO probes)
sensor/<id>/calibrationhald→yesCalibrationResult {ts, point, ok, points, detail}
deviceshald→yesDeviceInventory {sensors, addable} — configured devices with status, and SensorKind descriptors (label, ids, bus, default address, calibration steps) for what can be added (added 2026-10-31)
devices/set→hald—DeviceCommand {requestId, op: add∣remove, kind, addr}; only app-added sensors can be removed
devices/resulthald→noDeviceResult {requestId, ok, detail}
actuator/<id>/statehald→yesActuatorState {on, level, since, fault, remainingS}
actuator/<id>/set→hald—ActuatorCommand {on?, level?, durationS?}; hald clamps or refuses
event/buttonhald→no{press: single∣double∣long}
alert/<kind>hald→yesAlert {kind, active, ts, detail}
alert/<kind>/ack→hald—{} clears a latched fault
HTTP GET /health, /system, /camera/{upper∣lower}.jpg

Home Assistant discovery (homeassistant/<component>/<unit-id>_<id>/config, retained) is published by hald itself: light, pump as fan with percentage, sensors with device_class, binary_sensor water_low, event for the button.

The typed contract lives twice — hal/crates/hal-core/src/contract.rs and src/hardware/contract.ts — and hal/contract/fixtures/*.json are golden samples both test suites must parse. The version is a field in system.

Consequences

  • Core’s MqttHalClient (Phase 3.1) and SimHal implement the same HalClient interface; nothing above it knows which is live.
  • Adding a sensor (pH, EC, water temperature) is a new SensorId, a fixture, and a driver — no topic changes.

Authors: @ng


Sources: sleepypod/core (conventions and ADR precedents)  · iot-root/garden-of-eden (reference implementation, GPL-3; no code copied) 

Imported from docs/adr/0007-hal-contract.md in seedypod/core. Edit it there, not here.

Last updated on