Menu

ESP32 Minimalist SDK

WiFi, MQTT, and device ID. The fastest path to a connected ESP32.

Overview

The Minimalist SDK is three modules: wifi_tools, mqtt, and device_id. Together they handle WiFi with auto-reconnect, MQTT with TLS and auto-reconnect, persistent device identity, and topic-based pub/sub — all with automatic re-subscription on reconnect.

The standard loop pattern looks like this:

void loop() {
    if (wifi_tools.is_connected) {
        mqtt.maintain();
        // your publishing logic here
    } else {
        wifi_tools.reconnect();
        mqtt.report_disconnect();
    }
}

Prerequisites

An IDE — either PlatformIO (VS Code extension or CLI) or the Arduino IDE. The examples on this page show the PlatformIO project layout; for Arduino IDE setup, see Connect Your ESP32 (Arduino IDE).

An ESP32 dev board — any standard board. The default config targets esp32dev.

An OhioIoT account — with active subscription and MQTT credentials. See Getting Started.

Your broker CA certificate — for TLS. See TLS & Security.

Project structure

your-project/
├── platformio.ini
├── config/
│   └── config/
│       ├── credentials.h        ← your WiFi + MQTT secrets
│       └── ca_cert.h            ← broker CA certificate (TLS)
├── ohioiot_lib/
│   ├── device_id/
│   │   ├── device_id.h
│   │   └── device_id.cpp
│   ├── wifi_tools/
│   │   ├── wifi_tools.h
│   │   ├── wifi_tools.cpp
│   │   └── wifi_tools_debug.cpp
│   └── mqtt/
│       ├── mqtt.h
│       ├── mqtt.cpp
│       ├── mqtt_handler.cpp
│       ├── mqtt_publish.cpp
│       └── mqtt_subscribe.cpp
└── src/
    └── main.cpp

PlatformIO config

[env:dev]
platform      = espressif32
board         = esp32dev
framework     = arduino
monitor_speed = 115200

lib_deps =
    knolleary/PubSubClient@^2.8

lib_extra_dirs =
    config
    ohioiot_lib

build_flags =
    ; -DALLOW_INSECURE_MQTT

Uncomment -DALLOW_INSECURE_MQTT only for local development. See TLS & Security.

Credentials

// config/config/credentials.h

#define WIFI_SSID   "your_wifi_name"
#define WIFI_PASS   "your_wifi_password"

#define MQTT_HOST   "mqtt.ohioiot.com"
#define MQTT_PORT   8883
#define MQTT_USER   "your_mqtt_username"
#define MQTT_PASS   "your_mqtt_password"
Add credentials.h and ca_cert.h to your .gitignore immediately.

device_id

Generates and persists an 8-character alphanumeric device ID in ESP32 NVS.

CallDescription
device_id.get_or_set(buffer)Reads the stored device ID into buffer (a char[9]). If no ID exists yet, generates one, stores it, and copies it into the buffer. Call once in setup().
char DEVICE_ID[9];
device_id.get_or_set(DEVICE_ID);
// DEVICE_ID is now "a8f3k2m1" (or whatever was generated/stored)

See Device Identity for details on persistence, custom IDs, and erasing.

wifi_tools

Manages WiFi connection with event-driven state tracking and automatic reconnect.

CallDescription
wifi_tools.begin(ssid, pass)Starts the WiFi connection. Disables the ESP32's built-in auto-reconnect (the SDK handles this itself). Registers an event handler that updates is_connected automatically.
wifi_tools.is_connectedbool — true when WiFi has an IP address. Updated automatically by the event handler. Check this in your loop to gate MQTT and publishing logic.
wifi_tools.reconnect()Call in your loop's else branch (when not connected). Attempts reconnection every 10 seconds. Does not attempt reconnect after a user-initiated disconnect.
wifi_tools.log_events()Enables detailed WiFi event logging to Serial. Call once in setup() if you want to see every WiFi state change — useful for debugging connection issues.
wifi_tools.log_status()Prints WiFi status and RSSI to Serial every second. Call in your loop for continuous monitoring. Useful for diagnosing signal strength issues.
// minimal usage
wifi_tools.begin(WIFI_SSID, WIFI_PASS);

// with debug logging
wifi_tools.begin(WIFI_SSID, WIFI_PASS);
wifi_tools.log_events();  // see every WiFi event on Serial

mqtt

MQTT client with TLS, automatic reconnect, ~/ and ~/~/ topic prefix rewriting, and subscription management.

Setup

CallDescription
mqtt.setup(host, port, device_id, username, password)Configures the MQTT client. Does not connect — connection happens on the first call to maintain(). All arguments are const char* except port (int).
mqtt.maintain()Call every loop iteration when WiFi is connected. If MQTT is connected, runs the event loop (processes incoming messages). If disconnected, attempts reconnection every 3 seconds.
mqtt.report_disconnect()Call when WiFi drops. Sets is_connected to false so the SDK knows not to attempt MQTT operations until WiFi is back.
mqtt.is_connectedbool — true when connected to the broker. Check before publishing.

Publishing

CallDescription
mqtt.publish(topic, "string")Publish with a string payload. Both args are const char *.
mqtt.publish(topic, 72.5)Publish with a float payload. Converted to a string with 2 decimal places.
mqtt.publish(topic, 14)Publish with an integer payload. Converted to a string.
mqtt.publish(topic)Publish with an empty payload.
A leading ~/~/ in the topic expands to username/deviceID/; a leading ~/ expands to username/. No prefix means the topic is sent verbatim (the broker requires it to start with your username). See Publishing Data.

Subscribing

CallDescription
mqtt.set_subscriptions(topics)Pass a null-terminated const char ** array. The SDK subscribes to all topics on connect and re-subscribes on every reconnect. Call once in setup().
mqtt.set_callback(handler)Register a message handler. Signature: void fn(char * topic, char * message). The topic is the full broker topic as delivered by the broker — e.g. larry/a8f3k2m1/command, regardless of whether you subscribed using ~/~/command or the full string.
static const char * my_topics[] = {
    "~/~/command",           // → larry/a8f3k2m1/command
    "~/~/config/setpoint",   // → larry/a8f3k2m1/config/setpoint
    "~/fleet/alert",         // → larry/fleet/alert
    nullptr
};

void on_message(char * topic, char * message) {
    Serial.print("topic: ");    Serial.println(topic);
    Serial.print("message: ");  Serial.println(message);
}

void setup() {
    // ... device_id, wifi, mqtt.setup() ...
    mqtt.set_subscriptions(my_topics);
    mqtt.set_callback(on_message);
}

Full example

A complete sketch that publishes a simulated temperature every 5 seconds, reports firmware version as device status, subscribes to commands, and handles reconnection:

#include <Arduino.h>
#include "credentials.h"
#include "device_id.h"
#include "wifi_tools.h"
#include "mqtt.h"

char DEVICE_ID[9];
unsigned long last_publish = 0;
bool status_sent = false;

void on_message(char * topic, char * message) {
    Serial.print("  topic:   ");  Serial.println(topic);
    Serial.print("  message: ");  Serial.println(message);

    if (strstr(topic, "/command") != NULL) {
        if (strcmp(message, "reboot") == 0)   ESP.restart();
        if (strcmp(message, "identify") == 0) Serial.println("  I'm here!");
    }
}

static const char * my_topics[] = {
    "~/~/command",
    nullptr
};

void setup() {
    Serial.begin(115200);
    Serial.println("\n\n  booting...\n");

    device_id.get_or_set(DEVICE_ID);
    wifi_tools.begin(WIFI_SSID, WIFI_PASS);
    mqtt.setup(MQTT_HOST, MQTT_PORT, DEVICE_ID, MQTT_USER, MQTT_PASS);
    mqtt.set_subscriptions(my_topics);
    mqtt.set_callback(on_message);
}

void loop() {

    if (wifi_tools.is_connected) {

        mqtt.maintain();

        if (mqtt.is_connected) {

            // report status once after connecting
            if (!status_sent) {
                mqtt.publish("~/~/status/firmware", "v1.0.0");
                mqtt.publish("~/~/status/board", "esp32dev");
                status_sent = true;
            }

            // publish sensor data every 5 seconds
            if (millis() - last_publish > 5000) {
                float temp = 68.0 + random(-30, 30) / 10.0;
                mqtt.publish("~/~/numb/temperature/temp", temp);
                mqtt.publish("~/~/bool/climate/heating", temp < 70.0 ? "true" : "false");
                last_publish = millis();
            }
        }

    } else {

        wifi_tools.reconnect();
        mqtt.report_disconnect();
        status_sent = false;  // re-send status after reconnect

    }
}

Troubleshooting

MQTT keeps printing "failed to connect"

Check credentials.h — username and password must match what's in the OhioIoT dashboard exactly. In TLS mode, confirm your ca_cert.h has the correct certificate. Try -DALLOW_INSECURE_MQTT with port 1883 temporarily to rule out a TLS issue.

WiFi connects but MQTT doesn't

Make sure port 8883 (TLS) or 1883 (insecure) isn't blocked by your network. Some corporate and university WiFi networks block outbound MQTT. Try a mobile hotspot to test.

Not receiving subscribed messages

Confirm mqtt.maintain() is called every loop iteration — long delay() calls will cause missed messages. Topics are case-sensitive with no trailing slash. Remember that the callback receives the full broker topic (e.g. larry/a8f3k2m1/command), not the short form you passed to set_subscriptions.

Device keeps reconnecting

Two devices with the same device ID (or same MQTT client ID) will kick each other off the broker in a loop. Check that your device IDs are unique. See Device Identity.

Data doesn't appear on the dashboard

Make sure the third segment of your topic is a recognized type: numb, bool, status, or config. Check the Serial Monitor — the SDK prints the rewritten topic on every publish. If you used a ~/~/ prefix, the printed topic should be username/deviceID/type/graph/field. If you passed a bare topic, the SDK sent it verbatim — verify it starts with your username and has the type in the right segment.