Skip to content

mqtt

tested on simulator

Tier Tier 1
Protocol MQTT 3.1.1
Covers Tasmota devices, Zigbee2MQTT bridged devices, ESPHome and Shelly devices with MQTT enabled, DIY sensor boards, home automation relays and plugs
Hazard default medium
Conformance control
Tested with real paho-mqtt client against an in-process MQTT 3.1.1 broker with a simulated fan

Estimated reach. At least thousands of models. Any device that publishes state and accepts commands on topics can be described in a device file. Tasmota alone lists over two thousand supported device templates, and Zigbee2MQTT lists several thousand Zigbee devices that it exposes over MQTT with JSON payloads. The driver covers raw and JSON payloads, so devices with binary or protobuf payloads are not covered.

The mqtt driver reads devices that publish their state on MQTT topics and controls devices that take commands on topics, through a broker such as Mosquitto. The driver uses paho-mqtt and MQTT 3.1.1. Each channel's map names the state topic, the command topic and how the payload is written, so Tasmota, Zigbee2MQTT, ESPHome, Shelly and homemade boards all work without device-specific code.

What it supports

  • Read channels of type number, bool, string and enum from raw payloads (21.5, ON) or JSON payloads with a JSON path.
  • Write channels that publish raw words, numbers, JSON built from a path, or a JSON template.
  • Retained messages seed reads right after connecting, and live messages replace the retained ones as they arrive.
  • QoS 0 or 1 for commands, and an optional retain flag.
  • Username and password, with the password from an environment variable, and TLS.
  • describe() listens on the broker for a moment and proposes read channels from the topics that carry messages.

Setup

uv tool install --compile-bytecode "openhal-hub[mqtt] @ git+https://github.com/sohan-bhat/OpenHAL"

You need a broker the hub can reach, and Mosquitto works well and runs on a Raspberry Pi.

Parameters

ParameterDefaultMeaning
hostrequiredBroker address. The shorthand mqtt: 192.168.1.10 sets it
port1883Broker port. TLS brokers usually use 8883
client_idoh- and the device id with a short hashAt most 23 characters, as MQTT 3.1.1 servers must accept. Each device needs its own, because a broker drops the older session when two clients share an id
usernamenoneBroker user name
token_envnoneName of an environment variable holding the broker password
tls, ca_envfalse, noneUse TLS. ca_env names an environment variable that holds the path of a CA bundle, since a device file never names a path
keepalive30Keep-alive interval in seconds
timeout5 sConnect and acknowledgement deadline
qos1Default QoS for commands
prefix""Prepended to every topic in the maps
retain_commandsfalseDefault retain flag for commands
discover_topic#Topic filter describe() listens on, under prefix

Because the device file loader rejects any parameter whose name contains password, the password parameter is token_env rather than password_env.

Channel maps

read:
  temperature: { unit: degC, stream: true, map: { state: zigbee2mqtt/sensor, format: json, json: temperature } }
  door: { type: bool, map: { state: home/door, on: open, off: closed } }
write:
  relay:
    type: bool
    map: { state: stat/plug/POWER, command: cmnd/plug/POWER }
  brightness:
    unit: percent
    min: 0
    max: 100
    map: { state: zigbee2mqtt/lamp, command: zigbee2mqtt/lamp/set, format: json, json: brightness, scale: 0.3937 }
KeyDefaultMeaning
staterequired for readsExact topic the device publishes on. Wildcards are not allowed
commandrequired for writesExact topic the driver publishes commands to
formatrawraw text or json
jsonnoneJSON path to the value, such as $.ENERGY.Power. For commands, the JSON is built from the path
templatenoneA JSON template for commands, such as { state: "{value}", transition: 2 }
on, offON and OFF for raw commandsWords for bool channels. Reads accept a list
scale, offset1, 0channel value = payload value × scale + offset. Commands send the inverse, and results within one millionth of a whole number go out as integers
qos, retainfrom the parametersPer-command override

A write channel without a state topic reads back the last value the driver sent, or no value before the first write.

How reads and writes behave

  • Because MQTT only pushes, the driver subscribes to every state topic and keeps the newest message per topic, and a read returns that message with the time the message arrived. A topic that has had no message since connecting reads as no value (None), which the kernel treats as unknown.
  • Commands are never queued, so a write while the broker is down fails, and paho's automatic reconnect is off so nothing is replayed later on a new connection. The hub's heartbeat reconnects with a fresh client.
  • A QoS 1 command waits for the broker's PUBACK. Without the PUBACK the write fails with TimeoutError and the driver drops the connection, so the broker can't deliver the command late. A PUBACK proves that the broker has the message but not that the device acted, so watch the state topic to see whether the device acted.

Example

openhal/drivers/mqtt/examples/tasmota-plug.hal.yaml reads power and energy from a Tasmota plug and switches its relay. Set PowerRetain 1 on the plug so the relay state is retained.

Errors

  • Unreachable broker, refused credentials and a missing password variable raise DriverError.
  • A payload that is not a number, not JSON, missing the JSON field, not an on or off word, or an enum value outside the list raises DriverError on read.
  • No CONNACK or PUBACK within timeout raises TimeoutError or DriverError with the topic.

Limitations

  • The driver speaks MQTT 3.1.1 only and doesn't use MQTT 5 properties.
  • The driver doesn't support binary or protobuf payloads or image channels.
  • The driver has no actions, so model each command topic as a write channel.
  • describe() only finds topics that carry messages while describe() listens, so describe() proposes reads only, all dimensionless until you set the units.

Hazard default and safe state

The default hazard is medium, because MQTT topics can switch anything. MQTT has no emergency behavior of its own, so safe_state_support is file. The hub publishes the device file's safe_state like any other command, which needs both the broker and the device to be up. Use the device's own power-on and connection-loss behavior (Tasmota PowerOnState, for example) as the real fallback.

Sources