اتصال ESP8266 به بروکر MQTT با TLS
راهنمای کامل اتصال NodeMCU و Wemos D1 (ESP8266) به بروکر MQTT روی پورت 8883 با BearSSL و PubSubClient: گواهی ریشه، تنظیم ساعت و کد کامل.
پیشنیازها#
- برد ESP8266 مثل NodeMCU یا Wemos D1 mini، و Arduino IDE با پکیج ESP8266 (یا PlatformIO)
- کتابخانه PubSubClient نوشته Nick O'Leary از Library Manager
- یک اکانت MQTT و یک دستگاه ثبتشده در داشبورد، مثلاً با شناسه
esp8266-01(اتصال اولین دستگاه)
اگر ESP32 دارید، راهنمای ESP32 را ببینید؛ کتابخانه TLS این دو برد متفاوت است.
TLS روی ESP8266: دو نکته که فرق دارد#
ESP8266 برای TLS از کتابخانه BearSSL استفاده میکند و دو چیز لازم دارد:
- گواهی ریشه: گواهی بروکر از Let's Encrypt است و زنجیره آن به ISRG Root X1 میرسد. متن PEM این گواهی را از صفحه رسمی letsencrypt.org/certificates بردارید و در کد بگذارید.
- ساعت درست: BearSSL تاریخ اعتبار گواهی را بررسی میکند و ESP8266 ساعت ندارد. قبل از اتصال باید زمان را از NTP بگیرید؛ وگرنه اتصال TLS با خطا رد میشود.
به جای گواهی، از اثر انگشت (setFingerprint) استفاده نکنید. گواهیهای Let's Encrypt هر چند ماه تمدید میشوند و با هر تمدید اثر انگشت عوض میشود و همه دستگاهها از کار میافتند؛ گواهی ریشه سالها ثابت میماند. setInsecure() هم بررسی را کاملاً خاموش میکند و فقط برای آزمایش اولیه مناسب است.
کد کامل#
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <time.h>
const char* WIFI_SSID = "your-wifi";
const char* WIFI_PASS = "your-wifi-password";
const char* MQTT_HOST = "mqtt.mqttcloud.ir";
const int MQTT_PORT = 8883;
const char* MQTT_USER = "alice"; // your MQTT username
const char* MQTT_PASS = "YOUR_PASSWORD";
const char* CLIENT_ID = "esp8266-01"; // registered in the dashboard
// ISRG Root X1, PEM from letsencrypt.org/certificates
static const char ROOT_CA[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
)EOF";
BearSSL::WiFiClientSecure net;
PubSubClient client(net);
void syncClock() {
configTime(0, 0, "pool.ntp.org", "time.google.com");
Serial.print("Waiting for NTP time");
while (time(nullptr) < 1700000000) { // any date after 2023 means synced
delay(500);
Serial.print(".");
}
Serial.println(" ok");
}
void onMessage(char* topic, byte* payload, unsigned int length) {
Serial.printf("%s: %.*s\n", topic, length, (char*)payload);
}
void connectMqtt() {
while (!client.connected()) {
if (client.connect(CLIENT_ID, MQTT_USER, MQTT_PASS)) {
client.subscribe("alice/esp8266-01/cmd");
} else {
Serial.printf("MQTT connect failed, state=%d\n", client.state());
delay(5000);
}
}
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
syncClock();
static BearSSL::X509List caCert(ROOT_CA);
net.setTrustAnchors(&caCert);
client.setServer(MQTT_HOST, MQTT_PORT);
client.setCallback(onMessage);
client.setBufferSize(512); // default is 256 bytes
}
unsigned long lastSend = 0;
void loop() {
if (!client.connected()) connectMqtt();
client.loop();
if (millis() - lastSend > 10000) {
lastSend = millis();
client.publish("alice/esp8266-01/temp", "{\"c\":24.8}");
}
}اگر وصل نشد#
state()برابر-2معمولاً یعنی اتصال TCP یا TLS برقرار نشد: ساعت (پیام NTP در Serial)، گواهی ریشه و آدرسmqtt.mqttcloud.irرا بررسی کنید. اتصال با IP خطای تطابق نام گواهی میدهد.state()برابر5یعنی بروکر احراز هویت را رد کرده است: نام کاربری، رمز اکانت MQTT و شناسه دستگاه را بررسی کنید.- برای دیدن دلیل دقیق خطای TLS میتوانید
net.getLastSSLError()را چاپ کنید. - TLS حافظه زیادی میگیرد. اگر برد ریست میشود، رشتههای بزرگ و بافرهای غیرضروری را کم کنید و با
ESP.getFreeHeap()حافظه آزاد را ببینید.
فهرست کامل دلیلهای رایج را در عیبیابی اتصال MQTT ببینید.
قدم بعدی#
حلقه while در connectMqtt تا وصل شدن، بقیه برنامه را متوقف میکند. برای دستگاهی که باید در قطعی اینترنت هم کارش را بکند، نسخه بدون توقف با فاصلهگذاری تدریجی را در اتصال مجدد پایدار MQTT ببینید و برای نمایش وضعیت آنلاین دستگاه، Last Will را اضافه کنید.
