Skip to content

MQTT

If you end up deploying a fleet of ESP32s in your home, it can quickly become painful to go to each device to update settings.

Any standard MQTT client works for inspecting and updating settings — MQTT Explorer for a GUI, or the mosquitto_sub / mosquitto_pub CLI tools (they ship with the Mosquitto project but talk to any MQTT broker).

Terminal window
mosquitto_sub -h homeassistant.local -u <username> -P <password> -v -t "espresense/rooms/kitchen/#"

The firmware uses one base topic prefix, espresense, hard-coded as the CHANNEL. Everything below that is built up from your room slug:

espresense/rooms/<room>/<key> # node-side state / settings (retained)
espresense/rooms/<room>/<key>/set # write a setting (publish to this)
espresense/rooms/<room>/telemetry # JSON metrics (non-retained)
espresense/rooms/<room>/status # online / offline (LWT, retained)
espresense/devices/<id>/<room> # per-device distance (when pub_devices=on)
espresense/settings/<id>/config # global device-config (Companion writes here)

<room> is the room name, slugified (uppercase/spaces are normalised to a slug).

A <room> of * matches every node and is the recommended way to roll a setting out to a fleet. Publish with the retain flag set on * topics and newly-joining nodes will also pick up that setting at startup.

When a node connects, it publishes a retained snapshot of its current settings under espresense/rooms/<room>/.... Subscribe to the room to see them:

espresense/rooms/study/status online
espresense/rooms/study/name Study
espresense/rooms/study/max_distance 16.0
espresense/rooms/study/absorption 2.7
espresense/rooms/study/tx_ref_rssi -59
espresense/rooms/study/rx_adj_rssi 0
espresense/rooms/study/query
espresense/rooms/study/include apple:aaaayyyy iBeacon:232323
espresense/rooms/study/exclude sonos:xxxx sonos:yyyy
espresense/rooms/study/known_macs aabbccddeeff 112233445566
espresense/rooms/study/known_irks
espresense/rooms/study/count_ids
espresense/rooms/study/auto_update OFF
espresense/rooms/study/prerelease OFF
espresense/rooms/study/arduino_ota OFF

Sensor and GPIO state (when configured) also appears under the same room prefix — see Published topics below.

Publish to <key>/set to change a setting on one node:

Terminal window
mosquitto_pub -h homeassistant.local -u <username> -P <password> \
-t "espresense/rooms/kitchen/auto_update/set" -m "ON" -d

Use a room of * to update every ESPresense node at once. Add -r (retain) and new nodes joining later will apply the same value on first boot:

Terminal window
mosquitto_pub -h homeassistant.local -u <username> -P <password> \
-t "espresense/rooms/*/absorption/set" -m "3.0" -r
SettingTypeDefaultDescription
max_distancefloat (0–100)16.0Maximum distance (m) to report. Devices computed beyond this are dropped.
absorptionfloat (1–5)2.7Environmental absorption / path-loss factor. Higher = signal attenuates faster.
skip_distancefloat (0–10)0.5Report immediately if the beacon has moved more than this (m).
skip_msinteger (0–3000000)5000Skip reporting if the message is younger than this (ms). Reduces MQTT chatter.
max_divisorinteger (2–10)10Maximum divisor for the report interval. Larger movements divide skip_ms to report sooner.
forget_msinteger (0–3000000)150000Forget (drop) a fingerprint if not seen for this long (ms). Boot only — not in the Command() dispatcher; writes to <room>/forget_ms/set are ignored. Set in the captive portal / Settings page and reboot.
max_fingerprintsinteger (16–2048)100 (200 on ESP32-S3/C3/C6)Maximum number of tracked BLE fingerprints. Boot only.
SettingTypeDefaultDescription
ref_rssiinteger (-100 to 100)-65RSSI expected from a 0 dBm transmitter at 1 m. Not used for iBeacon / Eddystone (those carry their own calibrated RSSI).
tx_ref_rssiinteger (-100 to 0)-59RSSI expected from this node’s iBeacon transmit power at 1 m.
rx_adj_rssiinteger (-100 to 100)0*Per-node receive RSSI adjustment. Use only when this board has a known-weak (or known-strong) antenna.
*Default 20 on bare ESP32-S3 builds; 0 on M5STICK and M5ATOM (even when S3-based — those board defines are matched before ESP32S3 in include/defaults.h:79-95) and 0 on every other variant.

See Calibration for the full procedure.

SettingTypeDefaultDescription
includestring""Whitespace-separated allow-list of device id prefixes. If set, ONLY matching ids are published. Example: apple:iphone10-6 apple:iphone13-2.
excludestring""Whitespace-separated deny-list of device id prefixes. Filtered out before publish. Example: exp:20 apple:iphone10-6.
known_macsstring""Whitespace-separated BLE MACs (no colons, lowercase) treated as stable ids. Useful for devices that advertise a random-but-static MAC. Published as known:<mac>.
known_irksstring""Whitespace-separated 32-hex-character IRKs for resolving Apple random-resolvable MACs into a stable id. See Apple devices for how to extract them.
querystring""Whitespace-separated id prefixes to actively connect to over BLE and ask for Room Assistant / model / name characteristics. Example: flora: for Mi Flora plant sensors.
requery_msinteger seconds (30–3600)300How often to re-issue active queries against matched devices. Boot only.
connect_allON / OFFOFFAllow active BLE connections to every device, bypassing the query filter. Intended for advanced debugging only — connecting to everything is expensive and can starve the scanner.

The count feature publishes a sensor with the number of unique devices currently within the count window. Useful when you can’t fingerprint individuals but the population count itself is informative (e.g. exp:20 COVID exposure apps, generic apple:).

Only count_ids is live-settable over MQTT. The three hysteresis knobs are read once at boot — set them in the captive portal / Settings page and reboot.

SettingTypeDefaultDescription
count_idsstring""Whitespace-separated id prefixes to count.
count_enterfloat (0–100)2.0Start counting a device once it’s closer than this (m). Boot only.
count_exitfloat (0–100)4.0Stop counting once it’s farther than this (m). Higher than count_enter creates hysteresis to prevent flapping. Boot only.
count_msinteger ms (0–3000000)10000Stop counting a device if its last advertisement is older than this. Boot only.

These are configured at boot (over the captive portal / Network page) and cannot be set live over MQTT — restart-flashing only.

SettingTypeDefaultDescription
wifi-ssidstring(empty)Wi-Fi SSID. Can also be re-written via MQTT (<room>/wifi-ssid/set).
wifi-passwordstring(empty)Wi-Fi password. Same MQTT setter as above.
wifi_timeoutinteger seconds60Seconds to wait for Wi-Fi before falling back to the captive portal. -1 = wait forever.
portal_timeoutinteger seconds300Seconds to keep the captive portal up before rebooting and retrying Wi-Fi.
mqtt_hoststring(build default)MQTT broker hostname or IP. SSL is not supported.
mqtt_portinteger1883MQTT broker port.
discoverycheckboxONPublish Home Assistant MQTT-discovery payloads on connect.
discovery_prefixstringhomeassistantHome Assistant discovery prefix. Only change if your HA install uses a non-default prefix.
pub_telecheckboxONPublish to <room>/telemetry (uptime, free heap, scan counters, etc.).
pub_devicescheckboxONPublish per-device distances to espresense/devices/<id>/<room> (the flat per-device topic shape).
SettingTypeDefaultDescription
auto_updateON / OFFOFFCheck GitHub for new firmware on a 15-minute interval and auto-flash if available.
prereleaseON / OFFOFFInclude pre-release builds when auto-checking.
arduino_otaON / OFFOFFEnable the Arduino OTA protocol (espota / PlatformIO). Keep off for less RAM use and a smaller attack surface.
updatestring URL""If set, the node fetches firmware from this URL on next boot. Cleared after a successful update.

Pin assignments live on the Hardware page; the timeouts can be tuned at runtime.

SettingTypeDefaultDescription
pir_timeoutfloat seconds (0–300)0.5PIR debounce / hold time after the last trigger.
radar_timeoutfloat seconds (0–300)0.5Radar debounce / hold time after the last trigger.
switch_1_timeoutfloat seconds (0–300)0.5Switch One debounce.
switch_2_timeoutfloat seconds (0–300)0.5Switch Two debounce.
button_1_timeoutfloat seconds (0–300)0.5Button One debounce.
button_2_timeoutfloat seconds (0–300)0.5Button Two debounce.
TopicPayloadAction
<room>/restart/set (or reboot/set)(any)Restart the node immediately.
<room>/name/setstringRename the room. Slugified to form the device id. Empty payload resets to the MAC.
<room>/enroll/setid|name or name or PRESSEnter BLE enrollment mode for 2 minutes. See Apple devices → Enrollment.
<room>/cancelEnroll/set(any)Leave enrollment mode immediately.

All topics under espresense/rooms/<room>/... are retained unless noted.

TopicPayloadNotes
statusonline / offlineLWT — offline is published automatically on disconnect.
namestringCurrent room display name.
telemetryJSONNon-retained. Includes uptime, freeHeap, scan counters, and count when count_ids is set.

Republished retained when the node comes back online so a fresh subscriber sees the current values:

max_distance, absorption, tx_ref_rssi, rx_adj_rssi, query, include, exclude, known_macs, known_irks, count_ids, plus updater state (auto_update, prerelease, arduino_ota) and per-input timeouts when those sensors are enabled.

Published when the corresponding pin is configured (Hardware):

TopicPayloadNotes
pirON / OFFRaw PIR state.
radarON / OFFRaw radar state.
motionON / OFFLogical OR of pir + radar. Use this in Home Assistant automations.
switch_1, switch_2ON / OFFIndividual switch state.
switchON / OFFLogical OR of both switches.
button_1, button_2ON / OFFIndividual button state.
buttonON / OFFLogical OR of both buttons.

When a configured LED has LED Control = MQTT (Hardware → MQTT LED control):

TopicPayloadNotes
led_1, led_2, led_3JSON statePublished when the LED changes.
led_1/set, led_2/set, led_3/setJSON commandSee the LED control example below.

When pub_devices is on (default), each tracked device is published to a flat topic under espresense/devices/:

espresense/devices/<device-id>/<room> # JSON: id, distance, rssi, name, …
espresense/devices/<device-id>/<room>/<report> # individual sub-reports (rare)

Payloads are non-retained. The top-level topic carries a single JSON object — the same shape GET /json/devices returns for one device. <device-id> is whatever the node settled on (apple:iphone15-3, irk:…, known:…, iBeacon:…, etc.). This is the topic shape Home Assistant’s mqtt_room integration consumes.

The firmware subscribes to three topics on connect:

TopicUsed for
espresense/rooms/*/+/setFleet-wide setting writes (the * wildcard).
espresense/rooms/<this-room>/+/setPer-node setting writes.
espresense/settings/+/configDevice-level enrollment configs published by the Companion (or by POST /json/configs). The + is the device id; the payload is the same JSON used by REST /json/configs.
Terminal window
mosquitto_pub -h homeassistant.local -u <username> -P <password> \
-t "espresense/rooms/kitchen/include/set" -m "apple:"
Terminal window
mosquitto_pub -h homeassistant.local -u <username> -P <password> \
-t "espresense/rooms/kitchen/max_distance/set" -m "10.0"

Enable auto-update on every node (and new joiners)

Section titled “Enable auto-update on every node (and new joiners)”
Terminal window
mosquitto_pub -h homeassistant.local -u <username> -P <password> \
-t "espresense/rooms/*/auto_update/set" -m "ON" -r

Adjust RSSI for a node with a weak antenna

Section titled “Adjust RSSI for a node with a weak antenna”
Terminal window
mosquitto_pub -h homeassistant.local -u <username> -P <password> \
-t "espresense/rooms/kitchen/rx_adj_rssi/set" -m "10"
Terminal window
mosquitto_pub -h homeassistant.local -u <username> -P <password> \
-t "espresense/rooms/kitchen/enroll/set" -m "myphone:abcdef|My Phone"
Terminal window
mosquitto_pub -h homeassistant.local -u <username> -P <password> \
-t "espresense/rooms/*/absorption/set" -m "3.0" -r
Terminal window
mosquitto_pub -h homeassistant.local -u <username> -P <password> \
-t "espresense/rooms/kitchen/led_1/set" \
-m '{"state":"ON","brightness":64,"color":{"r":255,"g":128,"b":0}}'

Accepted JSON keys: state (ON/OFF), brightness (0–255), color.r/g/b, white_value (0–255), color_temp, and effect. Supported keys depend on the LED’s color mode.


Last verified against firmware v4.0.6 (main @ d9a1765, 2026-05-10).