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).
mosquitto_sub -h homeassistant.local -u <username> -P <password> -v -t "espresense/rooms/kitchen/#"Topic shape
Section titled “Topic shape”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.
Reading current settings
Section titled “Reading current settings”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 onlineespresense/rooms/study/name Studyespresense/rooms/study/max_distance 16.0espresense/rooms/study/absorption 2.7espresense/rooms/study/tx_ref_rssi -59espresense/rooms/study/rx_adj_rssi 0espresense/rooms/study/queryespresense/rooms/study/include apple:aaaayyyy iBeacon:232323espresense/rooms/study/exclude sonos:xxxx sonos:yyyyespresense/rooms/study/known_macs aabbccddeeff 112233445566espresense/rooms/study/known_irksespresense/rooms/study/count_idsespresense/rooms/study/auto_update OFFespresense/rooms/study/prerelease OFFespresense/rooms/study/arduino_ota OFFSensor and GPIO state (when configured) also appears under the same room prefix — see Published topics below.
Updating settings
Section titled “Updating settings”Publish to <key>/set to change a setting on one node:
mosquitto_pub -h homeassistant.local -u <username> -P <password> \ -t "espresense/rooms/kitchen/auto_update/set" -m "ON" -dUse 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:
mosquitto_pub -h homeassistant.local -u <username> -P <password> \ -t "espresense/rooms/*/absorption/set" -m "3.0" -rSettings reference
Section titled “Settings reference”Core BLE
Section titled “Core BLE”| Setting | Type | Default | Description |
|---|---|---|---|
max_distance | float (0–100) | 16.0 | Maximum distance (m) to report. Devices computed beyond this are dropped. |
absorption | float (1–5) | 2.7 | Environmental absorption / path-loss factor. Higher = signal attenuates faster. |
skip_distance | float (0–10) | 0.5 | Report immediately if the beacon has moved more than this (m). |
skip_ms | integer (0–3000000) | 5000 | Skip reporting if the message is younger than this (ms). Reduces MQTT chatter. |
max_divisor | integer (2–10) | 10 | Maximum divisor for the report interval. Larger movements divide skip_ms to report sooner. |
forget_ms | integer (0–3000000) | 150000 | Forget (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_fingerprints | integer (16–2048) | 100 (200 on ESP32-S3/C3/C6) | Maximum number of tracked BLE fingerprints. Boot only. |
Calibration (RSSI)
Section titled “Calibration (RSSI)”| Setting | Type | Default | Description |
|---|---|---|---|
ref_rssi | integer (-100 to 100) | -65 | RSSI expected from a 0 dBm transmitter at 1 m. Not used for iBeacon / Eddystone (those carry their own calibrated RSSI). |
tx_ref_rssi | integer (-100 to 0) | -59 | RSSI expected from this node’s iBeacon transmit power at 1 m. |
rx_adj_rssi | integer (-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.
Scanning / filtering
Section titled “Scanning / filtering”| Setting | Type | Default | Description |
|---|---|---|---|
include | string | "" | Whitespace-separated allow-list of device id prefixes. If set, ONLY matching ids are published. Example: apple:iphone10-6 apple:iphone13-2. |
exclude | string | "" | Whitespace-separated deny-list of device id prefixes. Filtered out before publish. Example: exp:20 apple:iphone10-6. |
known_macs | string | "" | 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_irks | string | "" | Whitespace-separated 32-hex-character IRKs for resolving Apple random-resolvable MACs into a stable id. See Apple devices for how to extract them. |
query | string | "" | 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_ms | integer seconds (30–3600) | 300 | How often to re-issue active queries against matched devices. Boot only. |
connect_all | ON / OFF | OFF | Allow 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. |
Counting
Section titled “Counting”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.
| Setting | Type | Default | Description |
|---|---|---|---|
count_ids | string | "" | Whitespace-separated id prefixes to count. |
count_enter | float (0–100) | 2.0 | Start counting a device once it’s closer than this (m). Boot only. |
count_exit | float (0–100) | 4.0 | Stop counting once it’s farther than this (m). Higher than count_enter creates hysteresis to prevent flapping. Boot only. |
count_ms | integer ms (0–3000000) | 10000 | Stop counting a device if its last advertisement is older than this. Boot only. |
Network / MQTT
Section titled “Network / MQTT”These are configured at boot (over the captive portal / Network page) and cannot be set live over MQTT — restart-flashing only.
| Setting | Type | Default | Description |
|---|---|---|---|
wifi-ssid | string | (empty) | Wi-Fi SSID. Can also be re-written via MQTT (<room>/wifi-ssid/set). |
wifi-password | string | (empty) | Wi-Fi password. Same MQTT setter as above. |
wifi_timeout | integer seconds | 60 | Seconds to wait for Wi-Fi before falling back to the captive portal. -1 = wait forever. |
portal_timeout | integer seconds | 300 | Seconds to keep the captive portal up before rebooting and retrying Wi-Fi. |
mqtt_host | string | (build default) | MQTT broker hostname or IP. SSL is not supported. |
mqtt_port | integer | 1883 | MQTT broker port. |
discovery | checkbox | ON | Publish Home Assistant MQTT-discovery payloads on connect. |
discovery_prefix | string | homeassistant | Home Assistant discovery prefix. Only change if your HA install uses a non-default prefix. |
pub_tele | checkbox | ON | Publish to <room>/telemetry (uptime, free heap, scan counters, etc.). |
pub_devices | checkbox | ON | Publish per-device distances to espresense/devices/<id>/<room> (the flat per-device topic shape). |
Updates / OTA
Section titled “Updates / OTA”| Setting | Type | Default | Description |
|---|---|---|---|
auto_update | ON / OFF | OFF | Check GitHub for new firmware on a 15-minute interval and auto-flash if available. |
prerelease | ON / OFF | OFF | Include pre-release builds when auto-checking. |
arduino_ota | ON / OFF | OFF | Enable the Arduino OTA protocol (espota / PlatformIO). Keep off for less RAM use and a smaller attack surface. |
update | string URL | "" | If set, the node fetches firmware from this URL on next boot. Cleared after a successful update. |
Motion / Switch / Button
Section titled “Motion / Switch / Button”Pin assignments live on the Hardware page; the timeouts can be tuned at runtime.
| Setting | Type | Default | Description |
|---|---|---|---|
pir_timeout | float seconds (0–300) | 0.5 | PIR debounce / hold time after the last trigger. |
radar_timeout | float seconds (0–300) | 0.5 | Radar debounce / hold time after the last trigger. |
switch_1_timeout | float seconds (0–300) | 0.5 | Switch One debounce. |
switch_2_timeout | float seconds (0–300) | 0.5 | Switch Two debounce. |
button_1_timeout | float seconds (0–300) | 0.5 | Button One debounce. |
button_2_timeout | float seconds (0–300) | 0.5 | Button Two debounce. |
Commands
Section titled “Commands”| Topic | Payload | Action |
|---|---|---|
<room>/restart/set (or reboot/set) | (any) | Restart the node immediately. |
<room>/name/set | string | Rename the room. Slugified to form the device id. Empty payload resets to the MAC. |
<room>/enroll/set | id|name or name or PRESS | Enter BLE enrollment mode for 2 minutes. See Apple devices → Enrollment. |
<room>/cancelEnroll/set | (any) | Leave enrollment mode immediately. |
Published topics
Section titled “Published topics”All topics under espresense/rooms/<room>/... are retained unless noted.
Status
Section titled “Status”| Topic | Payload | Notes |
|---|---|---|
status | online / offline | LWT — offline is published automatically on disconnect. |
name | string | Current room display name. |
telemetry | JSON | Non-retained. Includes uptime, freeHeap, scan counters, and count when count_ids is set. |
Settings snapshot (on connect)
Section titled “Settings snapshot (on connect)”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.
GPIO sensors
Section titled “GPIO sensors”Published when the corresponding pin is configured (Hardware):
| Topic | Payload | Notes |
|---|---|---|
pir | ON / OFF | Raw PIR state. |
radar | ON / OFF | Raw radar state. |
motion | ON / OFF | Logical OR of pir + radar. Use this in Home Assistant automations. |
switch_1, switch_2 | ON / OFF | Individual switch state. |
switch | ON / OFF | Logical OR of both switches. |
button_1, button_2 | ON / OFF | Individual button state. |
button | ON / OFF | Logical OR of both buttons. |
When a configured LED has LED Control = MQTT (Hardware → MQTT LED control):
| Topic | Payload | Notes |
|---|---|---|
led_1, led_2, led_3 | JSON state | Published when the LED changes. |
led_1/set, led_2/set, led_3/set | JSON command | See the LED control example below. |
Per-device topics
Section titled “Per-device topics”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.
Subscribed topics
Section titled “Subscribed topics”The firmware subscribes to three topics on connect:
| Topic | Used for |
|---|---|
espresense/rooms/*/+/set | Fleet-wide setting writes (the * wildcard). |
espresense/rooms/<this-room>/+/set | Per-node setting writes. |
espresense/settings/+/config | Device-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. |
Examples
Section titled “Examples”Filter to only track Apple devices
Section titled “Filter to only track Apple devices”mosquitto_pub -h homeassistant.local -u <username> -P <password> \ -t "espresense/rooms/kitchen/include/set" -m "apple:"Set maximum distance to 10 meters
Section titled “Set maximum distance to 10 meters”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)”mosquitto_pub -h homeassistant.local -u <username> -P <password> \ -t "espresense/rooms/*/auto_update/set" -m "ON" -rAdjust RSSI for a node with a weak antenna
Section titled “Adjust RSSI for a node with a weak antenna”mosquitto_pub -h homeassistant.local -u <username> -P <password> \ -t "espresense/rooms/kitchen/rx_adj_rssi/set" -m "10"Start enrollment for a new device
Section titled “Start enrollment for a new device”mosquitto_pub -h homeassistant.local -u <username> -P <password> \ -t "espresense/rooms/kitchen/enroll/set" -m "myphone:abcdef|My Phone"Update absorption everywhere
Section titled “Update absorption everywhere”mosquitto_pub -h homeassistant.local -u <username> -P <password> \ -t "espresense/rooms/*/absorption/set" -m "3.0" -rControl an MQTT-mode LED
Section titled “Control an MQTT-mode LED”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).