02 · Connecting to a Cloud IoT Platform¶
Not flashed to hardware
Reasoned through against Adafruit IO's publicly documented MQTT API
(host io.adafruit.com, port 8883 TLS / 1883 plain, feed-based
topic naming <username>/feeds/<feedname>) and the PubSubClient
library's documented API. Not compiled or flashed to physical
hardware in this environment.
From a local broker to the internet¶
Level 2's first module used a broker on your LAN. Real IoT projects usually publish to a cloud IoT platform instead, so data is reachable from anywhere and you get dashboards, storage, and alerting for free. Adafruit IO is a good learning platform: free tier, plain MQTT support, and a simple feed model — every value you publish belongs to a named "feed," and each feed gets an auto-generated web dashboard.
Getting credentials¶
- Create a free account at Adafruit IO and note your username and AIO Key (Adafruit IO's API key, found under "My Key").
- Create a feed named
temperaturein the dashboard — Adafruit IO auto- creates feeds on first publish too, but creating one explicitly first makes topic naming obvious.
Publishing to Adafruit IO over MQTT¶
// cloud-publish-adafruitio.ino
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
const char* WIFI_SSID = "your-ssid";
const char* WIFI_PASS = "your-password";
const char* AIO_SERVER = "io.adafruit.com";
const int AIO_PORT = 1883; // plain MQTT; use 8883 + WiFiClientSecure for TLS
const char* AIO_USERNAME = "your-aio-username";
const char* AIO_KEY = "your-aio-key";
// Adafruit IO's documented topic convention: <username>/feeds/<feedname>
String feedTopic = String(AIO_USERNAME) + "/feeds/temperature";
WiFiClient espClient;
PubSubClient mqtt(espClient);
void connectWiFi() {
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
}
void connectMQTT() {
mqtt.setServer(AIO_SERVER, AIO_PORT);
while (!mqtt.connected()) {
// Adafruit IO authenticates via MQTT username/password fields, not
// a separate handshake -- username is your AIO username, password
// is your AIO key.
if (mqtt.connect("esp-node-01", AIO_USERNAME, AIO_KEY)) {
Serial.println("Adafruit IO connected");
} else {
Serial.printf("failed, rc=%d\n", mqtt.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
connectWiFi();
connectMQTT();
}
void loop() {
if (!mqtt.connected()) connectMQTT();
mqtt.loop();
static unsigned long last = 0;
if (millis() - last > 15000) { // Adafruit IO's free tier rate-limits publishes
last = millis();
float tempC = 23.4;
char payload[16];
dtostrf(tempC, 4, 1, payload);
mqtt.publish(feedTopic.c_str(), payload);
Serial.printf("Published %s to %s\n", payload, feedTopic.c_str());
}
}
The 15-second publish interval isn't arbitrary — Adafruit IO's free tier documents a rate limit (roughly 30 data points/minute), and publishing faster gets throttled or dropped.
Subscribing to a feed for remote control¶
The same feed topic works both ways — publish sensor data on one feed,
subscribe to a different feed (e.g. led) so the dashboard's toggle
switch can control the device:
// cloud-subscribe-adafruitio.ino
void onMessage(char* topic, byte* payload, unsigned int length) {
String message;
for (unsigned int i = 0; i < length; i++) message += (char)payload[i];
Serial.printf("[%s] %s\n", topic, message.c_str());
digitalWrite(LED_BUILTIN, message == "ON" ? LOW : HIGH);
}
// In setup(), after connectMQTT():
// mqtt.setCallback(onMessage);
// mqtt.subscribe((String(AIO_USERNAME) + "/feeds/led").c_str());
How It Actually Works¶
Under an SDK call like "publish to my IoT platform," the chip is doing real TLS work: a full handshake (ClientHello/ServerHello, certificate exchange, key exchange, Finished messages) negotiating a symmetric session key via ECDHE, then every subsequent MQTT/HTTPS byte is encrypted with AES-GCM using that session key before hitting the TCP layer. On ESP32 this handshake is accelerated by dedicated AES/SHA/RSA hardware blocks (the chip literally has silicon that computes AES rounds and SHA compression in a handful of clock cycles rather than software loops), which is the concrete reason ESP32 completes a TLS handshake in a few hundred milliseconds while ESP8266 (software-only crypto via mbedTLS/BearSSL) can take multiple seconds and consume most of its ~50KB free heap during the handshake — a common cause of Failed to allocate crashes on ESP8266 platform integrations that "worked fine on ESP32."
Device authentication to the cloud platform is typically an X.509 client certificate check: the platform's TLS layer verifies your device cert's signature chain against a root CA it trusts, and separately your device verifies the platform's server certificate against a root CA baked into your firmware — get the embedded root CA wrong or let it expire (root CAs do rotate) and the handshake fails at the certificate-verify step with no application-level error message, only a TLS alert, which is why "IoT cloud connection suddenly stopped working" is so often a root CA staleness issue rather than a code bug.
(These examples were written and reasoned through at the register/protocol level but were not flashed to a physical board for this pass — verify timing-sensitive details against your exact chip datasheet before relying on them in production.)
Exercise¶
- Sign up for Adafruit IO, create a
temperaturefeed, and confirm the publish sketch's values appear on its dashboard chart. - Add a toggle-switch block to an
ledfeed in the dashboard and wire up the subscribe sketch so flipping it (conceptually) drives an LED. - Deliberately use a wrong AIO key and confirm
mqtt.state()reports a non-zero code consistent with an authorization failure (Adafruit IO and PubSubClient both document this behavior). - Note where this design is insecure (
AIO_PORT 1883, plain-text key over the network) — Level 3's TLS module addresses it directly.