ESP32 MQTT SSL, bir hobi sketch'ini gerçek bir ağda bırakabileceğiniz bir cihaza dönüştüren adımdır: kimlik bilgileri ve payload'lar şifrelenir, ESP32 de gerçek broker ile konuştuğunu doğrular. Bu eğitimde bir ESP32'yi WiFiClientSecure ve PubSubClient ile 8883 portunda broker.connectmq.com'a bağlayacak, sunucuyu doğru kök sertifikayla doğrulayacak, saati NTP ile eşitleyecek ve TLS'in gerçekte ne kadar bellek tükettiğini öğreneceksiniz. Ayrıca neredeyse herkesin en az bir kez karşılaştığı el sıkışma hataları için bir kontrol listesi de bulacaksınız.
TLS Neler Ekler, Neler Eklemez?
MQTT üzerinde TLS üç şey yapar: bağlantıyı şifreler, böylece Wi-Fi'da veya broker'a giden yolda kimse kullanıcı adınızı, parolanızı ya da payload'larınızı okuyamaz; mesajları kurcalanmaya karşı korur; doğru yapılandırırsanız da sunucunun kimliğini doğrular, böylece sahte bir erişim noktası broker'ınızın kılığına giremez.
Çoğu eğitimin atladığı kısım bu sonuncusudur. setInsecure() çağırmak size kimlik doğrulama olmadan şifreleme sağlar. Beş dakikalık bir deney için sorun değildir, ancak üretimde gerçek bir zayıflıktır; çünkü DNS'i veya ağı ele geçirebilen bir saldırgan herhangi bir sertifika sunabilir ve kimlik bilgilerinizi toplayabilir.
TLS kimlik doğrulamanın yerini tutmaz. Broker tarafında her cihazın yine de kendi kullanıcı adı, parolası ve client ID'si olmalıdır. Daha geniş bir bakış için MQTT güvenliği en iyi uygulamaları rehberine bakın.
| Port | Protokol | Kullanım |
|---|---|---|
| 1883 | TCP üzerinden MQTT | Public alanda test için; kimlik bilgileriyle asla |
| 8883 | TLS üzerinden MQTT | Üretimdeki ESP32 ve diğer cihazlar |
| 8083 | WebSocket üzerinden MQTT | Tarayıcıda test |
| 8084 | Secure WebSocket üzerinden MQTT (/mqtt) |
Üretimdeki tarayıcı uygulamaları |
ESP32'nin Hangi Kök Sertifikaya İhtiyacı Var?
broker.connectmq.com, ISRG Root X1 köküne zincirlenen ve Let's Encrypt tarafından verilmiş bir sertifika kullanır. Bunu OpenSSL yüklü herhangi bir bilgisayardan kendiniz kontrol edebilirsiniz:
openssl s_client -connect broker.connectmq.com:8883 -servername broker.connectmq.com -showcerts </dev/null
Çıktıda broker.connectmq.com için sunucu sertifikası, onu veren Let's Encrypt ara sertifikası ve ISRG Root X1 ile biten bir zincir listelenir; sisteminiz bu köke güveniyorsa ardından Verify return code: 0 (ok) gelir.
Önemli kural: ESP32'ye sunucu sertifikasını değil, kök sertifikayı verin. Let's Encrypt sunucu sertifikaları kısa ömürlüdür ve otomatik olarak yenilenir; ara sertifikalar da zamanla değişir. Uç (leaf) sertifikayı veya parmak izini sabitleyen bir cihaz bir sonraki yenilemeden sonra bağlanamaz hale gelir. ISRG Root X1 sertifikası 2035'e kadar geçerlidir; dolayısıyla onu gömmek, firmware'inizin her rutin yenilemeyi atlatmasını sağlar.
Ön Koşullar
- Arduino IDE 2.x'te Espressif'in esp32 kart paketi yüklü bir ESP32 kartı.
- Library Manager'dan Nick O'Leary'nin PubSubClient kütüphanesi.
- Bir ConnectMQ broker: kaydolun, bir plan seçin, bir broker oluşturun ve portaldan oluşturulan kullanıcı adını, parolayı ve client ID'yi kopyalayın. Ücretsiz Serverless planı yeterlidir.
Bu ilk ESP32 MQTT sketch'inizse ESP32 IoT eğitimi önce kart kurulumunu ve düz MQTT'yi anlatır; TLS'siz sürümün çalıştığını bildiğinizde TLS'te hata ayıklamak daha kolaydır.
Tam ESP32 MQTT SSL Sketch'i
Aşağıdaki sketch Wi-Fi'a bağlanır, saati eşitler, kök sertifikayı yükler, kimlik bilgileriyle 8883 portuna bağlanır, bir komut topic'ine abone olur ve her on saniyede bir telemetri yayınlar.
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <time.h>
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 8883;
const char* MQTT_CLIENT_ID = "YOUR_CLIENT_ID";
const char* MQTT_USERNAME = "YOUR_USERNAME";
const char* MQTT_PASSWORD = "YOUR_PASSWORD";
const char* TOPIC_TELEMETRY = "devices/esp32-01/telemetry";
const char* TOPIC_COMMAND = "devices/esp32-01/cmd";
// ISRG Root X1 (Let's Encrypt), valid until 2035-06-04
static const char ROOT_CA[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
)EOF";
WiFiClientSecure net;
PubSubClient mqtt(net);
unsigned long lastAttempt = 0, lastPublish = 0;
void syncTime() {
configTime(0, 0, "pool.ntp.org", "time.google.com");
Serial.print("Waiting for NTP time");
time_t now = time(nullptr);
unsigned long start = millis();
while (now < 1700000000 && millis() - start < 20000) { // any date after 2023
delay(250);
Serial.print(".");
now = time(nullptr);
}
Serial.printf("\nUTC time: %s", ctime(&now));
}
void onMessage(char* topic, byte* payload, unsigned int length) {
Serial.printf("Command on %s: %.*s\n", topic, (int)length, (const char*)payload);
}
bool connectMqtt() {
Serial.printf("Free heap before TLS: %u bytes\n", ESP.getFreeHeap());
if (!mqtt.connect(MQTT_CLIENT_ID, MQTT_USERNAME, MQTT_PASSWORD)) {
char err[128];
int code = net.lastError(err, sizeof(err));
Serial.printf("MQTT state=%d, TLS error %d: %s\n", mqtt.state(), code, err);
return false;
}
Serial.printf("Connected over TLS. Free heap: %u bytes\n", ESP.getFreeHeap());
mqtt.subscribe(TOPIC_COMMAND, 1);
return true;
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) { delay(250); Serial.print("."); }
Serial.printf("\nWi-Fi connected, IP %s\n", WiFi.localIP().toString().c_str());
syncTime();
net.setCACert(ROOT_CA);
net.setHandshakeTimeout(15); // seconds
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setKeepAlive(60);
mqtt.setSocketTimeout(10);
mqtt.setBufferSize(512);
}
void loop() {
if (!mqtt.connected()) {
if (millis() - lastAttempt > 10000) {
lastAttempt = millis();
connectMqtt();
}
return;
}
mqtt.loop();
if (millis() - lastPublish > 10000) {
lastPublish = millis();
char json[96];
snprintf(json, sizeof(json), "{\"uptime\":%lu,\"heap\":%u,\"rssi\":%d}",
millis() / 1000, ESP.getFreeHeap(), WiFi.RSSI());
mqtt.publish(TOPIC_TELEMETRY, json);
}
}
Üç kimlik bilgisi yer tutucusunu portaldaki değerlerle değiştirin. ConnectMQ her client ID'yi birlikte verildiği kimlik bilgilerine bağladığı için kendiniz bir tane uydurmak yerine oluşturulan client ID'yi kullanın ve aynı seti iki cihazda çalıştırmayın. Mesajların gelişini izlemek için portalın canlı topic gezginini veya tarayıcı içi MQTT istemcisini açın ve devices/# topic'ine abone olun.
ESP32 MQTT SSL İçin Saat Eşitlemesi Neden Önemli?
Bir sertifika yalnızca "not before" ve "not after" tarihleri arasında geçerlidir. ESP32'nin pille desteklenen bir saati yoktur; bu yüzden her açılıştan sonra tarihin 1 Ocak 1970 olduğunu düşünür. Arduino core sürümünüzde mbedTLS'in nasıl yapılandırıldığına bağlı olarak sertifika tarihleri ya bu saate göre kontrol edilir (bu durumda her sertifika "henüz geçerli değil" görünür ve el sıkışma başarısız olur) ya da hiç kontrol edilmez (bu durumda süresi dolmuş bir sertifika sessizce kabul edilir). İkisi de istediğiniz şey değildir.
İlk TLS bağlantısından önce NTP ile eşitlemek her iki durumu da çözer. Açılışta bir iki saniyeye mal olur ve giden UDP 123 portunu gerektirir. NTP'yi engelleyen ağlarda configTime()'ı güvenlik duvarınızın izin verdiği yerel NTP sunucusuna yönlendirin. Doğru saat başlı başına da faydalıdır: MQTT mesajları için ESP32 NTP zaman damgaları yazısında anlatıldığı gibi telemetrinize gerçek zaman damgaları ekleyebilirsiniz.
Bellek: TLS'in ESP32'deki Maliyeti
TLS, tipik bir ESP32 sketch'inin yaptığı en çok bellek tüketen iştir. El sıkışma sırasında mbedTLS gelen ve giden kayıtlar, ayrıştırılan sertifika zinciri ve anahtar değişimi için tamponlar ayırır. Sketch bağlantı öncesi ve sonrasında boş heap'i yazdırır; böylece etkisini kendi kartınızda ve core sürümünüzde görebilirsiniz.
| Etken | Etki | Ne yapmalı |
|---|---|---|
| TLS kayıt tamponları | Bağlantı süresince onlarca kilobayt tutulur | Bütçenize dahil edin; kısıtlı kartlarda aynı anda ikinci bir TLS istemcisinden kaçının (örneğin MQTT bağlıyken HTTPS OTA) |
| Sertifika zinciri ayrıştırma | El sıkışma sırasında geçici tepe | RAM kısıtlıysa büyük bir paket yerine yalnızca ihtiyaç duyduğunuz kökü gömün |
| Heap parçalanması | Uzun çalışma süresi ve çok sayıda String ayırması büyük bitişik blok bırakmayabilir |
Sabit char tamponlarını tercih edin, daha seyrek yeniden bağlanın |
| PSRAM'li kartlar (WROVER, birçok S3) | Daha fazla pay | Büyük tamponlar core'un ayırıcısı tarafından PSRAM'e yerleştirilebilir |
| PubSubClient tamponu | setBufferSize(512) 512 bayta mal olur |
En büyük mesajınıza göre boyutlandırın, bkz. PubSubClient tampon boyutu |
Pratik bir uyarı işareti: bağlantılar saatler veya günler sonra lastError() içinde bellek ayırma hatalarıyla başarısız olmaya başlıyor ve yeniden başlatma sorunu çözüyorsa, karşınızdaki şey parçalanmadır. Boş heap ile birlikte ESP.getMaxAllocHeap() değerini loglamak, hâlâ kullanılabilir olan en büyük bloğu gösterir.
Sorun Giderme Kontrol Listesi
X509 - Certificate verification failed. Yanlış kök sertifika, PEM'de bir yazım hatası veya saat ayarlanmamış. PEM'iBEGINveENDsatırları dahil yeniden kopyalayın ve seri monitörde yazdırılan NTP saatini doğrulayın.- El sıkışma zaman aşımına uğruyor. Ağ 8883 portunu engelliyor; kurumsal ve misafir Wi-Fi ağlarında yaygındır. Aynı ağdaki bir dizüstü bilgisayardan yukarıdaki OpenSSL komutuyla test edin.
- Başarılı el sıkışmadan sonra
state=4veyastate=5. TLS sorunsuz; kimlik bilgileri veya client ID yanlış. Portaldan kopyalanan sondaki boşlukları kontrol edin. lastError()içinde bellek ayırma hataları. Yeterli bitişik heap yok. Bağlanmadan önce bellek boşaltın, eş zamanlı TLS oturumlarından kaçının,Stringkullanımını azaltın.- 1883'te çalışıyor ama 8883'te çalışmıyor. Hâlâ
WiFiClientSecureyerineWiFiClientkullanıyorsunuz veyasetServer()1883 portunu gösteriyor. - Bağlanıyor, sonra yaklaşık her dakika kopuyor. Genellikle TLS'ten çok keep-alive veya yinelenen client ID sorunudur; ESP32 MQTT yeniden bağlanma mantığı yazısındaki kalıplar burada yardımcı olur.
Tekrarlanan başarısız girişler broker'da IP engellemesini tetikleyebilir; bu nedenle bir cihazı hızlı bir yeniden deneme döngüsünde bırakmadan önce kimlik bilgisi hatalarını düzeltin.
Tek Bir Kökü Gömmenin Alternatifleri
ISRG Root X1'i gömmek tek bir broker için en basit sağlam seçenektir. İki alternatif vardır. Arduino core, setCACertBundle() aracılığıyla bir CA paketini destekler; bu, flash alanı ve biraz kurulum karşılığında tek bir firmware'in birçok genel köke güvenmesini sağlar ve aynı cihaz birden fazla HTTPS ve MQTTS servisiyle konuştuğunda kullanışlıdır. Alternatif olarak PEM'i bir dosya sistemi bölümünde saklayabilirsiniz; böylece kod yeniden yüklenmeden güncellenebilir. Otomatik olarak yenilenen sertifikalar kullanan hiçbir broker için sertifika parmak izi sabitlemeye başvurmayın.
Üretim Notları
Şifreleme bağlantıyı korur; broker'ı güvenli şekilde işletmek ise işin diğer yarısıdır. Tamamen yönetilen bir MQTT servisi sertifika yenilemeyi, çok kiracılı izolasyonu ve kaba kuvvet korumasını sizin için halleder; böylece firmware'in yalnızca kök sertifikaya ve kimlik bilgilerine ihtiyacı olur. ConnectMQ, Let's Encrypt sertifikalarıyla 8883 portunda TLS ve 8084 portunda Secure WebSocket sunar; aynı kimlik bilgileri ESP32'nizden, bir Python backend'den veya bir tarayıcı dashboard'undan çalışır. Kendi sunucunuzda barındırmayı bir bulut MQTT broker ile kıyaslıyorsanız, sertifika yönetimi ikinci günden itibaren en büyük farklardan biridir; bir MQTT broker'ın ne yaptığını anlatan rehber de geri kalan bileşenleri açıklar.
ConnectMQ ile Başlayın
app.connectmq.com adresinde kredi kartı gerekmeden ücretsiz bir Serverless broker oluşturun. Oluşturulan kimlik bilgilerini yukarıdaki sketch'e kopyalayın; ESP32'niz birkaç dakika içinde 8883 portunda TLS ile konuşmaya başlar.
Sık Sorulan Sorular
TLS üzerinden ESP32 MQTT için setInsecure() kabul edilebilir mi?
Yalnızca hızlı testler için. Trafiği şifreler ama sunucu doğrulamasını atlar; bu yüzden trafiğinizi yönlendirebilen bir saldırgan kimlik bilgilerini ele geçirebilir. Dağıttığınız her şeyde broker'ın kök sertifikasıyla setCACert() kullanın.
broker.connectmq.com için hangi sertifikayı kullanmalıyım?
ISRG Root X1 kök sertifikasını kullanın, çünkü broker'ın sertifikası Let's Encrypt tarafından verilir. Sunucu sertifikasının kendisini gömmeyin; otomatik olarak yenilenir ve cihazınız bağlanamaz hale gelir.
ESP32'de TLS için NTP gerekli mi?
Bağlanmadan önce saati eşitlemelisiniz. Aksi halde core yapılandırmasına bağlı olarak sertifika tarihleri ya 1970 saatine göre karşılaştırılıp başarısız olur ya da hiç kontrol edilmez.
TLS bir ESP32'de ne kadar RAM gerektirir?
Bir TLS bağlantısı açıkken onlarca kilobayt heap, el sıkışma sırasında ise geçici olarak daha yüksek bir tepe bekleyin. Sketch'in yaptığı gibi bağlanmadan önce ve sonra ESP.getFreeHeap() yazdırarak kendi kartınızda ölçün.



