MQTT Commands & Usage Guide¶
MQTT (Message Queuing Telemetry Transport) is the messaging protocol we use throughout the workshop to send and receive data between the ESP32-S3 and other devices. This page documents the MQTT topics, commands, and message formats used in the workshop modules.
What Is MQTT?¶
MQTT works like a bulletin board system:
- Broker — The bulletin board itself (a server that routes messages). We use Mosquitto or HiveMQ as our broker.
- Publisher — Someone who pins a message on the board (the ESP32 sending sensor data)
- Subscriber — Someone who reads messages from the board (your phone, a dashboard, another ESP32)
- Topic — The category on the board (like "room1/temperature" or "led/control")
Publisher (ESP32) ──→ Broker ──→ Subscriber (Phone/Dashboard)
"room1/temp: 25.5" "room1/temp" → sees 25.5
A single message can be received by multiple subscribers — one sensor reading can update a phone app, a web dashboard, and a database all at once.
Broker Configuration¶
The workshop Docker image includes a Mosquitto broker. Default settings:
| Setting | Value |
|---|---|
| Host | localhost (or mosquitto inside Docker) |
| Port | 1883 (non-TLS) |
| Username | (none by default) |
| Password | (none by default) |
| WebSocket Port | 9001 |
Start the broker:
For quick testing without a local broker:
| Setting | Value |
|---|---|
| Host | broker.hivemq.com |
| Port | 1883 |
| WebSocket | 8000 |
Public broker
The HiveMQ public broker is for testing only. Anyone can see your messages. Never send sensitive data to a public broker.
Used in Module 17 for the cloud pipeline. Requires mutual TLS authentication.
| Setting | Value |
|---|---|
| Endpoint | your-thing-prefix-ats.iot.your-region.amazonaws.com |
| Port | 8883 (TLS) |
| Auth | Mutual TLS (client certificate + private key) |
Topic Structure¶
We use a hierarchical topic structure. Think of it like folders on your computer:
workshop/ ← Root prefix (avoids collisions on shared brokers)
├── {device_id}/ ← Each ESP32 has a unique ID (e.g., "xiao-01")
│ ├── sensor/ ← Sensor data published by the ESP32
│ │ ├── temperature ← Temperature readings
│ │ ├── humidity ← Humidity readings
│ │ └── dht11 ← Combined DHT11 reading (temp + humidity)
│ ├── led/ ← LED control (published by phone, subscribed by ESP32)
│ │ └── control ← ON/OFF commands
│ ├── status/ ← ESP32 status reports
│ │ └── sleep ← Sleep mode notifications
│ └── alert/ ← Alert messages from the ESP32
│ └── person_detected ← Person detection alert (Module 16)
└── broadcast/ ← Messages to all devices
└── ota ← Over-the-air commands (advanced)
Topic Naming Rules¶
- Use
/to separate levels (like folders) - No wildcards in published topics (only in subscriptions)
- Keep topics short — every byte counts on a microcontroller
- Use lowercase with underscores —
temperature, notTemperatureortemp-reading
Sensor Data Topics (ESP32 → Broker)¶
These topics are published by the ESP32 and subscribed by dashboards/phones.
Temperature¶
| Field | Value |
|---|---|
| Topic | workshop/{device_id}/sensor/temperature |
| Direction | ESP32 → Broker → Dashboard |
| QoS | 0 (fire and forget — sensor data is frequent, losing one is fine) |
| Retain | No |
Payload format:
Simple format (alternative):
Humidity¶
| Field | Value |
|---|---|
| Topic | workshop/{device_id}/sensor/humidity |
| Direction | ESP32 → Broker → Dashboard |
| QoS | 0 |
| Retain | No |
Payload format:
Combined DHT11 Reading¶
| Field | Value |
|---|---|
| Topic | workshop/{device_id}/sensor/dht11 |
| Direction | ESP32 → Broker → Dashboard |
| QoS | 0 |
| Retain | No |
Payload format:
LED Control Topic (Phone → ESP32)¶
This topic is published by your phone/dashboard and subscribed by the ESP32 to control LEDs remotely.
| Field | Value |
|---|---|
| Topic | workshop/{device_id}/led/control |
| Direction | Phone/Dashboard → Broker → ESP32 |
| QoS | 1 (at least once — we don't want to miss a command) |
| Retain | No |
Payload format:
LED Commands¶
| Action | Color | Brightness | Effect |
|---|---|---|---|
on |
red, green, yellow, blue, white |
0–255 (PWM duty cycle) | Turn on LED at specified brightness |
off |
(any or omitted) | (ignored) | Turn off LED |
toggle |
(last color or red) |
(last brightness or 255) | Toggle LED on/off |
blink |
red |
255 | Blink LED at 1Hz (firmware handles timing) |
pulse |
green |
255 | Fade LED in/out (PWM ramp up and down) |
Examples:
// Turn on red LED at full brightness
{"action": "on", "color": "red", "brightness": 255}
// Turn on green LED at 50% brightness
{"action": "on", "color": "green", "brightness": 128}
// Turn off all LEDs
{"action": "off"}
// Toggle the LED
{"action": "toggle", "color": "red"}
// Blink the red LED
{"action": "blink", "color": "red"}
// Pulse (fade in/out) the green LED
{"action": "pulse", "color": "green"}
Simple format (alternative)
For quick testing, you can also send plain text commands:
| Command | Effect |
|---|---|
on |
Turn on LED (default: red, full brightness) |
off |
Turn off LED |
toggle |
Toggle LED |
on green |
Turn on green LED |
on blue 128 |
Turn on blue LED at 50% |
Status & Alert Topics¶
Sleep Mode Notification¶
| Field | Value |
|---|---|
| Topic | workshop/{device_id}/status/sleep |
| Direction | ESP32 → Broker |
| QoS | 1 |
| Retain | Yes (so new subscribers see the last known state) |
Payload:
Person Detection Alert (Module 16)¶
| Field | Value |
|---|---|
| Topic | workshop/{device_id}/alert/person_detected |
| Direction | ESP32 → Broker → Phone/Dashboard |
| QoS | 1 |
| Retain | No |
Payload:
QoS Levels — When to Use What¶
| QoS | Name | Guarantee | Overhead | When to Use |
|---|---|---|---|---|
| 0 | At most once | Fire and forget — message may be lost | Lowest | Frequent sensor data (losing one reading is fine) |
| 1 | At least once | Message is delivered at least once (may arrive twice) | Medium | Commands (LED control, alerts) — must arrive, duplicates are OK |
| 2 | Exactly once | Message is delivered exactly once | Highest | Rarely needed on microcontrollers — use QoS 1 instead |
Rule of thumb:
- Sensor readings → QoS 0 (sent every 5 seconds, losing one doesn't matter)
- Commands and alerts → QoS 1 (must arrive, but a duplicate LED "on" is harmless)
Wildcard Subscriptions¶
You can subscribe to multiple topics at once using wildcards:
| Wildcard | Meaning | Example | Matches |
|---|---|---|---|
+ |
Single level | workshop/+/sensor/temperature |
workshop/xiao-01/sensor/temperature, workshop/xiao-02/sensor/temperature |
# |
All remaining levels | workshop/xiao-01/# |
All topics under workshop/xiao-01/ |
# |
Everything | # |
Every single message on the broker |
Useful wildcard subscriptions:
# Subscribe to all temperature readings from all devices
workshop/+/sensor/temperature
# Subscribe to everything from your device
workshop/xiao-01/#
# Subscribe to all sensor data from all devices
workshop/+/sensor/+
# Subscribe to all alerts from all devices
workshop/+/alert/#
Testing MQTT Commands¶
Using Mosquitto CLI Tools¶
# Subscribe to all workshop topics
mosquitto_sub -h localhost -p 1883 -t "workshop/#" -v
# Subscribe to a specific device's sensor data
mosquitto_sub -h localhost -p 1883 -t "workshop/xiao-01/sensor/dht11" -v
# Publish an LED control command
mosquitto_pub -h localhost -p 1883 -t "workshop/xiao-01/led/control" \
-m '{"action":"on","color":"red","brightness":255}'
# Publish a simple LED command
mosquitto_pub -h localhost -p 1883 -t "workshop/xiao-01/led/control" -m "on green"
# Publish with QoS 1 (for commands)
mosquitto_pub -h localhost -p 1883 -t "workshop/xiao-01/led/control" \
-q 1 -m '{"action":"toggle","color":"red"}'
# Publish with retain (last message is stored for new subscribers)
mosquitto_pub -h localhost -p 1883 -t "workshop/xiao-01/status/sleep" \
-r -q 1 -m '{"mode":"deep_sleep","duration_sec":60}'
Using MQTT Explorer (GUI)¶
MQTT Explorer is a graphical client that lets you browse topics and publish messages visually.
- Download and install MQTT Explorer
- Connect to
localhost:1883 - Browse the topic tree under
workshop/ - Publish commands by clicking on a topic and typing the JSON payload
Using IoT MQTT Panel (Android/iOS)¶
For controlling LEDs from your phone:
- Install IoT MQTT Panel from Play Store / App Store
- Add broker:
localhost:1883(or your computer's IP on the same WiFi) - Create a panel with:
- Text display subscribed to
workshop/xiao-01/sensor/temperature - Button publishing to
workshop/xiao-01/led/controlwith payload{"action":"on","color":"red","brightness":255}
ESP-IDF MQTT Code Reference¶
Publishing Sensor Data (Module 10)¶
#include "mqtt_client.h"
#include "esp_log.h"
static const char *TAG = "MQTT_PUB";
// Called when MQTT event occurs
static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data) {
esp_mqtt_event_handle_t event = event_data;
switch (event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "MQTT Connected!");
// Subscribe to LED control topic when connected
esp_mqtt_client_subscribe(event->client,
"workshop/xiao-01/led/control", 1);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "Received: %.*s", event->data_len, event->data);
// Parse and handle LED command here
break;
default:
break;
}
}
// Initialize MQTT client
void mqtt_app_start(void) {
esp_mqtt_client_config_t cfg = {
.broker.uri = "mqtt://localhost:1883",
};
esp_mqtt_client_handle_t client = esp_mqtt_client_init(&cfg);
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID,
mqtt_event_handler, NULL);
esp_mqtt_client_start(client);
}
// Publish temperature reading
void publish_temperature(esp_mqtt_client_handle_t client, float temp) {
char payload[128];
snprintf(payload, sizeof(payload),
"{\"value\":%.1f,\"unit\":\"celsius\",\"timestamp\":%lld}",
temp, (long long)time(NULL));
int msg_id = esp_mqtt_client_publish(client,
"workshop/xiao-01/sensor/temperature",
payload, 0, 0, 0); // QoS 0, retain=0
ESP_LOGI(TAG, "Published temp=%.1f, msg_id=%d", temp, msg_id);
}
Subscribing and Handling Commands (Module 10)¶
// Inside mqtt_event_handler, handle MQTT_EVENT_DATA:
case MQTT_EVENT_DATA: {
char topic[64] = {0};
char data[128] = {0};
strncpy(topic, event->topic, event->topic_len);
strncpy(data, event->data, event->data_len);
ESP_LOGI(TAG, "Topic: %s, Data: %s", topic, data);
// Check if this is an LED control command
if (strcmp(topic, "workshop/xiao-01/led/control") == 0) {
// Parse JSON and control LED
// Simple text parsing example:
if (strstr(data, "\"action\":\"on\"") != NULL) {
gpio_set_level(BLINK_GPIO, 1); // Turn on LED
} else if (strstr(data, "\"action\":\"off\"") != NULL) {
gpio_set_level(BLINK_GPIO, 0); // Turn off LED
} else if (strstr(data, "\"action\":\"toggle\"") != NULL) {
int level = gpio_get_level(BLINK_GPIO);
gpio_set_level(BLINK_GPIO, !level); // Toggle LED
}
}
break;
}
Quick Reference — All Workshop Topics¶
| Topic | Direction | QoS | Retain | Payload | Module |
|---|---|---|---|---|---|
workshop/{id}/sensor/temperature |
ESP32 → Broker | 0 | No | {"value":25.5,...} |
10 |
workshop/{id}/sensor/humidity |
ESP32 → Broker | 0 | No | {"value":60.0,...} |
10 |
workshop/{id}/sensor/dht11 |
ESP32 → Broker | 0 | No | {"temperature":25.5,"humidity":60.0,...} |
10 |
workshop/{id}/led/control |
Phone → ESP32 | 1 | No | {"action":"on","color":"red",...} |
10 |
workshop/{id}/status/sleep |
ESP32 → Broker | 1 | Yes | {"mode":"deep_sleep",...} |
14 |
workshop/{id}/alert/person_detected |
ESP32 → Broker | 1 | No | {"detected":true,"confidence":0.87,...} |
16 |