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.

You can use tools like MQTT explorer or if you are using mosquitto (default for Home Assistant), the mosquitto_sub and mosquitto_pub tools to view and manage the settings.

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).