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.cppPlatformIO 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_MQTTUncomment -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"credentials.h and ca_cert.h to your .gitignore immediately.device_id
Generates and persists an 8-character alphanumeric device ID in ESP32 NVS.
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.
// 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 Serialmqtt
MQTT client with TLS, automatic reconnect, ~/ and ~/~/ topic prefix rewriting, and subscription management.
Setup
Publishing
~/~/ 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
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.
