Skip to content

homeassistant

tested on simulator

Tier Tier 1
Protocol Home Assistant REST API, Home Assistant WebSocket API
Covers lights, switches and smart plugs, fans, number and select helpers, sensors and binary sensors, Zigbee, Z-Wave, Matter and Wi-Fi devices that Home Assistant integrates
Hazard default low
Conformance control
Tested with Home Assistant simulator over an HTTP mock transport and a real local WebSocket server

Estimated reach. Tens of thousands of device models, through more than two thousand integrations. Home Assistant lists over two thousand integrations, and each brand integration covers many models. Every device Home Assistant shows as a light, switch, fan, number, select, sensor or binary sensor entity works through this bridge with no extra code. Zigbee, Z-Wave and Matter devices are reached the same way.

The homeassistant driver is a bridge that exposes a few Home Assistant entities, selected by the device file, as one OpenHAL device, so the safety kernel's limits, interlocks and confirmations apply to those entities. Everything else in the Home Assistant instance stays out of reach.

Driver namehomeassistant
Librarieshttpx 0.28.1 (BSD-3-Clause), websockets 17.3 (BSD-3-Clause)
Extrauv tool install --compile-bytecode "openhal-hub[homeassistant] @ git+https://github.com/sohan-bhat/OpenHAL"
Hazard defaultlow (set the file's hazard to what the entities really drive)
Safe statefrom the device file only
Self-describeyes, the selected entities with their units and Home Assistant's own limits as suggestions

Which entities are exposed

  • entity names one entity, or entities lists several, and a channel's map.entity must be one of the selected entities, otherwise the file is refused when it loads.
  • With neither, the entities named in channel maps are the selection.
  • match.homeassistant is a glob, and every selected entity must match it.
  • The device file's limits apply even when Home Assistant has its own limits, so a number entity's min, max and step only appear in describe() as suggested values.

Channel roles

A channel's role comes from the entity's domain and from the channel name or map.attribute. With one selected entity no map is needed, but with several entities, give map.entity and name the channel after the role, or end the channel's name with the role (lamp_brightness).

DomainChannel name or attributeTypeReadsWrites
lightpower (any other name)boolstate on or offlight.turn_on, light.turn_off
lightbrightnessnumber, percentbrightness 0 to 255, 0 % when offlight.turn_on with brightness_pct
lightcolor_temp (color_temp_kelvin)number, Kcolor_temp_kelvinlight.turn_on with color_temp_kelvin
switch, input_booleanpowerboolstateturn_on, turn_off
fanpowerboolstatefan.turn_on, fan.turn_off
fanspeed (percentage)number, percentpercentagefan.set_percentage
fanpreset (preset_mode)enumpreset_modefan.set_preset_mode
number, input_numbervaluenumberstate, converted from unit_of_measurementset_value, converted to it
select, input_selectoptionenumstateselect_option
sensoranynumber or stringstate, converted from unit_of_measurementread-only
binary_sensoranyboolstateread-only
anymap: { attribute: name }anythat attributeread-only

Units from Home Assistant (°C, °F, %, kWh, hPa and the rest) are converted to the channel's unit. A sensor whose unit can't convert fails its read with a clear message.

An entity in state unavailable fails its reads, and health() reports the device disconnected, while an entity in state unknown reads as no value.

Transports

  • transport: rest, the default, sends GET /api/states/<entity_id> per read and POST /api/services/<domain>/<service> per write.
  • With transport: websocket, the driver authenticates, sends subscribe_entities for the selected entities only and keeps their states in a cache, so reads cost no round trip, and writes use call_service.

Both transports send the long-lived access token from the environment variable named in token_env (default HA_TOKEN).

Setup

  1. Install the extra with uv tool install --compile-bytecode "openhal-hub[homeassistant] @ git+https://github.com/sohan-bhat/OpenHAL".
  2. In Home Assistant, open your profile, Security, and create a long-lived access token. A non-admin user that can see only the needed entities is the safest choice.
  3. Export the token, for example export HA_TOKEN=..., where the hub runs.
  4. Start from openhal/drivers/homeassistant/examples/office-climate.hal.yaml, or from describe().
  5. Validate with openhal file validate, then confirm the limits by hand.

Parameters

KeyDefaultMeaning
hostrequiredHome Assistant host name or address
port8123Port
schemehttphttp or https (and ws or wss)
token_envHA_TOKENEnvironment variable that holds the access token
entitynoneThe one selected entity
entitiesnoneThe selected entities, up to 64
transportrestrest or websocket
timeout5 sDeadline for each request

Example device file

device: Desk lamp
id: desk-lamp
match: { homeassistant: light.desk_lamp }
connect: { homeassistant: homeassistant.local, port: 8123, token_env: HA_TOKEN, entity: light.desk_lamp }
write:
  power: { type: bool }
  brightness: { unit: percent, min: 1, max: 100, step: 1 }
safe_state: { power: false }
hazard: low

Known limitations

  • Climate, cover, lock, vacuum, media player and other domains are read-only through map.attribute for now, because locks and covers move things and need their own careful mapping.
  • Because the driver has only what Home Assistant reports, a device that an integration marks available while the device is not available still passes the health check.
  • Home Assistant automations can still change the same entities, because OpenHAL limits only what goes through OpenHAL.
  • Rate limits and interlocks act on OpenHAL commands only, so a person at a wall switch bypasses them by design.

Hazard defaults

The default hazard is low, because lights, fans and sensors are the usual case, but a switch that powers a heater or a pump deserves medium or high in the device file.

Sources