Skip to content

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:

# Inside Docker
mosquitto -c /etc/mosquitto/mosquitto.conf -d

# Or via Docker Compose (if configured)
docker compose up -d mosquitto

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, not Temperature or temp-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:

{
  "value": 25.5,
  "unit": "celsius",
  "timestamp": 1713427200
}

Simple format (alternative):

25.5

Humidity

Field Value
Topic workshop/{device_id}/sensor/humidity
Direction ESP32 → Broker → Dashboard
QoS 0
Retain No

Payload format:

{
  "value": 60.0,
  "unit": "percent",
  "timestamp": 1713427200
}

Combined DHT11 Reading

Field Value
Topic workshop/{device_id}/sensor/dht11
Direction ESP32 → Broker → Dashboard
QoS 0
Retain No

Payload format:

{
  "temperature": 25.5,
  "humidity": 60.0,
  "timestamp": 1713427200
}


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:

{
  "action": "on",
  "color": "red",
  "brightness": 100
}

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:

{
  "mode": "deep_sleep",
  "duration_sec": 60,
  "boot_count": 42,
  "reason": "timer"
}

Person Detection Alert (Module 16)

Field Value
Topic workshop/{device_id}/alert/person_detected
Direction ESP32 → Broker → Phone/Dashboard
QoS 1
Retain No

Payload:

{
  "detected": true,
  "confidence": 0.87,
  "timestamp": 1713427200
}


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.

  1. Download and install MQTT Explorer
  2. Connect to localhost:1883
  3. Browse the topic tree under workshop/
  4. Publish commands by clicking on a topic and typing the JSON payload

Using IoT MQTT Panel (Android/iOS)

For controlling LEDs from your phone:

  1. Install IoT MQTT Panel from Play Store / App Store
  2. Add broker: localhost:1883 (or your computer's IP on the same WiFi)
  3. Create a panel with:
  4. Text display subscribed to workshop/xiao-01/sensor/temperature
  5. Button publishing to workshop/xiao-01/led/control with 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