Bir ESP32 PlatformIO MQTT projesi, kimlik bilgileri koda gömülü tek bir sketch'ten çok daha fazlası olabilir. Bu rehberde broker.connectmq.com'u hedefleyen bir geliştirme ortamı ile TLS ve özel kimlik bilgileri kullanan bir production ortamına sahip bir PlatformIO projesi kuracaksınız; gizli bilgiler Git dışında tutulacak ve tekrarlanabilir derlemeler için kütüphane sürümleri sabitlenecek.
MQTT Firmware'i İçin Neden PlatformIO
Arduino IDE ilk sketch için gayet yeterlidir. Ancak bir projede birden fazla dosya, birden fazla hedef veya birden fazla geliştirici olduğu anda PlatformIO kendini amorti eder:
- Tekrarlanabilir derlemeler. Platform ve kütüphane sürümleri
platformio.iniiçinde yer alır; böylece bir meslektaşınız veya CI sunucusu tam olarak sizin derlediğinizi derler. - Birden fazla ortam. Tek bir proje, aynı kaynak koddan bir test broker'ı ve bir production broker'ı için ya da farklı kartlar için firmware üretebilir.
- Gerçek proje yapısı. Header'lar
include/içinde, kodsrc/içinde dosyalara bölünmüş, yeniden kullanılabilir modüllerlib/içinde ve testlertest/içinde. - Komut satırından derleme.
pio runVS Code'da, bir terminalde ve bir CI pipeline'ında aynı şekilde çalışır.
Proje Düzeni
Arduino framework ile bir ESP32 DevKit için yeni bir proje oluşturun, ardından onu şu şekle getirin:
esp32-mqtt/
platformio.ini
secrets.ini (not committed)
secrets.example.ini (committed template)
.gitignore
include/
config.h
src/
main.cpp
mqtt_link.cpp
mqtt_link.h
.gitignore, yerel gizli bilgileri ve derleme çıktısını depodan uzak tutar:
.pio/
secrets.ini
Dev ve Production Ortamlarıyla platformio.ini
[platformio]
default_envs = dev
extra_configs = secrets.ini
[env]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
lib_deps =
knolleary/PubSubClient @ ^2.8
bblanchon/ArduinoJson @ ^7.0.0
build_flags =
-D WIFI_SSID=\"${secrets.wifi_ssid}\"
-D WIFI_PASS=\"${secrets.wifi_pass}\"
-D FW_VERSION=\"1.0.0\"
[env:dev]
build_flags =
${env.build_flags}
-D MQTT_HOST=\"broker.connectmq.com\"
-D MQTT_PORT=1883
-D MQTT_USE_TLS=0
-D TOPIC_PREFIX=\"connectmq/demo/6d1f3c\"
-D CORE_DEBUG_LEVEL=3
[env:prod]
build_flags =
${env.build_flags}
-D MQTT_HOST=\"broker.connectmq.com\"
-D MQTT_PORT=8883
-D MQTT_USE_TLS=1
-D MQTT_CLIENT_ID=\"${secrets.mqtt_client_id}\"
-D MQTT_USER=\"${secrets.mqtt_user}\"
-D MQTT_PASS=\"${secrets.mqtt_pass}\"
-D TOPIC_PREFIX=\"factory1/line1/sensor01\"
-D CORE_DEBUG_LEVEL=1
Ortak [env] bölümü tüm paylaşılan ayarları tutar. Her adlandırılmış ortam, build_flags değerini kendi broker ayarlarıyla genişletir. extra_configs = secrets.ini ikinci bir dosyayı yapılandırmaya ekler; böylece ${secrets.wifi_ssid} asla sürüm kontrolüne girmeyen bir değere çözümlenir.
secrets.ini dosyası:
[secrets]
wifi_ssid = YOUR_WIFI
wifi_pass = YOUR_WIFI_PASSWORD
mqtt_client_id = YOUR_CLIENT_ID
mqtt_user = YOUR_USERNAME
mqtt_pass = YOUR_PASSWORD
Yeni katkıda bulunanların neyi doldurmaları gerektiğini bilmeleri için aynı anahtarları ve yer tutucu değerleri içeren secrets.example.ini dosyasını commit edin. Production değerleri, her broker için otomatik oluşturulan bir client ID, kullanıcı adı ve parola sağlayan ConnectMQ portalından gelir.
CI'da dosyayı atlayıp bunun yerine ${sysenv.MQTT_PASS} ile ortam değişkenlerini okuyabilirsiniz. Build flag'lerinin binary içinde string olarak yer aldığını, dolayısıyla firmware dosyasına sahip herkesin bunları çıkarabileceğini unutmayın. Üçüncü taraflara dağıtılan cihazlar için, örneğin ESP32 WiFiManager MQTT portalı ile çalışma anında sağlanan cihaza özel kimlik bilgilerini tercih edin.
config.h: Varsayılanlar İçin Tek Bir Yer
Header her ayara güvenli bir varsayılan değer verir ve temel bir şey eksikse derlemeyi başarısız kılar:
#pragma once
#ifndef MQTT_HOST
#define MQTT_HOST "broker.connectmq.com"
#endif
#ifndef MQTT_PORT
#define MQTT_PORT 1883
#endif
#ifndef MQTT_USE_TLS
#define MQTT_USE_TLS 0
#endif
#ifndef TOPIC_PREFIX
#error "TOPIC_PREFIX must be set in platformio.ini"
#endif
#if MQTT_USE_TLS && !defined(MQTT_PASS)
#error "Production builds need MQTT credentials from secrets.ini"
#endif
#ifndef FW_VERSION
#define FW_VERSION "dev"
#endif
Bir #error, derlenen, sahaya çıkan ve sonra sessizce yanlış yere bağlanan bir firmware'den çok daha iyidir.
MQTT Modülü
MQTT mantığını kendi dosyasında tutmak main.cpp dosyasını kısa tutar ve modülü projeler arasında yeniden kullanmanızı sağlar.
src/mqtt_link.h:
#pragma once
#include <Arduino.h>
namespace mqtt_link {
void begin();
void loop();
bool publish(const char* subtopic, const char* payload, bool retain = false);
bool connected();
}
src/mqtt_link.cpp:
#include "mqtt_link.h"
#include "config.h"
#include <WiFi.h>
#include <PubSubClient.h>
#if MQTT_USE_TLS
#include <WiFiClientSecure.h>
#include "isrg_root_x1.h" // const char ISRG_ROOT_X1[] = R"EOF(...PEM...)EOF";
#endif
namespace {
#if MQTT_USE_TLS
WiFiClientSecure net;
#else
WiFiClient net;
#endif
PubSubClient client(net);
String clientId;
String statusTopic;
unsigned long nextAttempt = 0;
unsigned long backoffMs = 1000;
void onMessage(char* topic, byte* payload, unsigned int length) {
Serial.printf("[mqtt] %s: %.*s\n", topic, (int)length, (const char*)payload);
}
void tryConnect() {
bool ok;
#if MQTT_USE_TLS
ok = client.connect(MQTT_CLIENT_ID, MQTT_USER, MQTT_PASS,
statusTopic.c_str(), 1, true, "offline");
#else
ok = client.connect(clientId.c_str(), statusTopic.c_str(), 1, true, "offline");
#endif
if (ok) {
client.publish(statusTopic.c_str(), "online", true);
client.subscribe((String(TOPIC_PREFIX) + "/cmd").c_str());
backoffMs = 1000;
Serial.println("[mqtt] connected");
} else {
Serial.printf("[mqtt] failed, state=%d\n", client.state());
nextAttempt = millis() + backoffMs;
backoffMs = min(backoffMs * 2, 60000UL);
}
}
} // namespace
namespace mqtt_link {
void begin() {
#if MQTT_USE_TLS
net.setCACert(ISRG_ROOT_X1);
clientId = MQTT_CLIENT_ID;
#else
clientId = "esp32-pio-" + String((uint32_t)ESP.getEfuseMac(), HEX);
#endif
statusTopic = String(TOPIC_PREFIX) + "/status";
client.setServer(MQTT_HOST, MQTT_PORT);
client.setBufferSize(512);
client.setSocketTimeout(5);
client.setCallback(onMessage);
}
void loop() {
if (WiFi.status() != WL_CONNECTED) return;
if (client.connected()) {
client.loop();
} else if (millis() >= nextAttempt) {
tryConnect();
}
}
bool publish(const char* subtopic, const char* payload, bool retain) {
String topic = String(TOPIC_PREFIX) + "/" + subtopic;
return client.publish(topic.c_str(), payload, retain);
}
bool connected() { return client.connected(); }
} // namespace mqtt_link
Let's Encrypt'in ISRG Root X1 sertifikasını PEM string'i olarak içeren include/isrg_root_x1.h dosyasını oluşturun. Bu dosya yalnızca production derlemelerine dahil edilir.
src/main.cpp:
#include <Arduino.h>
#include <WiFi.h>
#include <ArduinoJson.h>
#include "config.h"
#include "mqtt_link.h"
unsigned long lastPublish = 0;
void setup() {
Serial.begin(115200);
Serial.printf("Firmware %s, broker %s:%d\n", FW_VERSION, MQTT_HOST, MQTT_PORT);
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
WiFi.begin(WIFI_SSID, WIFI_PASS);
mqtt_link::begin();
}
void loop() {
mqtt_link::loop();
if (mqtt_link::connected() && millis() - lastPublish > 10000) {
lastPublish = millis();
JsonDocument doc;
doc["fw"] = FW_VERSION;
doc["uptime"] = millis() / 1000;
doc["rssi"] = WiFi.RSSI();
char buf[128];
serializeJson(doc, buf, sizeof(buf));
mqtt_link::publish("telemetry", buf);
}
}
Derleme, Yükleme ve İzleme
# herkese açık test alanına karşı geliştirme derlemesi
pio run -e dev -t upload
pio device monitor -e dev
# TLS ve portal kimlik bilgileriyle production derlemesi
pio run -e prod -t upload
# CI'ın yapacağı gibi tüm ortamları derle
pio run
Dev derlemesi çalışırken topic'lerini bilgisayarınızdan izleyin:
mosquitto_sub -h broker.connectmq.com -p 1883 -i pc-6d1f3c -t 'connectmq/demo/6d1f3c/#' -v
Bir Bakışta Ortam Ayarları
| Ayar | dev | prod |
|---|---|---|
| Broker | broker.connectmq.com herkese açık alanı | Özel ConnectMQ broker'ınız |
| Port ve aktarım | 1883, düz TCP | 8883, ISRG Root X1 ile TLS |
| Client ID | MAC adresinden türetilir | Portalda verilen client ID |
| Kimlik bilgileri | Yok | secrets.ini içindeki kullanıcı adı ve parola |
| Topic öneki | connectmq/demo/<random> |
ISA-95 yolu gibi kendi hiyerarşiniz |
| Log seviyesi | Info | Yalnızca hatalar |
Bu Yapı Neden Kazandırır
Kodu bu şekilde bölmek düzen uğruna düzen değildir. Proje masanızdan çıktığında her parçanın gerçek bir fark yaratan bir görevi vardır.
Ortamlar niyeti kodlar. Kimsenin bir sürümden önce portu değiştirmeyi, WiFiClient yerine WiFiClientSecure koymayı ve kimlik bilgilerini yapıştırmayı hatırlaması gerekmez. prod seçmek tüm bunları tutarlı biçimde yapar ve config.h içindeki #error kontrolleri yarım yapılandırılmış bir derlemeyi cihaza ulaşmadan durdurur. Aynı fikir kolayca genişler: ikinci bir özel broker'ı hedefleyen bir staging ortamı veya farklı bir board = satırıyla her kart tipi için bir ortam ekleyin.
MQTT modülü aktarım katmanını gizler. main.cpp düz TCP mi yoksa TLS üzerinden mi konuştuğunu ya da hangi client ID'nin kullanıldığını asla bilmez. İleride PubSubClient'ı asenkron bir istemciyle değiştirdiğinizde veya topic'leri yeni bir hiyerarşiye taşıdığınızda değişiklik tek bir dosyanın içinde kalır. Modül yeniden bağlanma backoff'unu da üstlenir; böylece onu yeniden kullanan her proje bloklamayan yeniden bağlanmayı bedavaya elde eder.
Status topic'i ve Last Will hazır gelir. tryConnect() her zaman retained bir offline will kaydettiği ve bağlandıktan sonra online yayınladığı için, bu şablondan derlenen her cihaz ek kod olmadan erişilebilirlik durumunu bildirir.
Sabitlenmiş bağımlılıklar CI'ı anlamlı kılar. Her commit'te pio run çalıştıran bir pipeline, ancak geliştiricilerin kullandığı kütüphane sürümleriyle derlediğinde bir şey kanıtlar. ^2.8 gibi caret aralıklarıyla hata düzeltme güncellemelerini alırsınız ama uyumluluğu bozan major sürümleri almazsınız.
Derlemenin ötesinde otomatik kontroller istiyorsanız, PlatformIO'nun birim test çalıştırıcısı testleri kartın kendisinde veya native ortamıyla host üzerinde yürütebilir. Topic oluşturma veya payload biçimlendirme gibi saf mantık, kendi modülünde yer aldığında native olarak kolayca test edilir.
Yaygın PlatformIO Tuzakları
- Tırnakları kaçış karakteriyle yazılmamış string makroları.
-D MQTT_HOST=broker.connectmq.combir string değil çıplak bir token tanımlar ve derleme başarısız olur. Gösterildiği gibi\"...\"kullanın. - Flag'leri genişletmek yerine üzerine yazmak. Bir ortamda
${env.build_flags}olmadanbuild_flags =yazmak, Wi-Fi kimlik bilgileri dahil paylaşılan flag'leri düşürür. - Sabitlenmemiş kütüphaneler. Yalın bir kütüphane adı en yeni ne varsa onu çeker ve ArduinoJson 6'dan 7'ye gibi bir major güncelleme derlemenizi bir gecede bozabilir.
@ ^x.y.zile sabitleyin. - Eksik
secrets.ini. Tanımsız bir bölüme yapılan referanslar kafa karıştırıcı biçimlerde başarısız olur. Commit edilmiş örnek dosya bunu önler. - Flag değişikliklerinden sonra eski derlemeler. PlatformIO genellikle flag'ler değiştiğinde yeniden derler, ancak davranış yanlış görünüyorsa
pio run -t cleançalıştırın. - Çökmeleri çözümlemek. Backtrace'lerin dosya ve satır göstermesi için
monitor_filters = esp32_exception_decoderayarını koruyun; bu, ESP32 task watchdog sıfırlamaları gibi sorunlarda paha biçilmezdir.
Sonraki Adımlar
Temiz bir yapı oluşturduktan sonra özellik eklemek kolaydır. ESP32 ArduinoJson MQTT rehberini izleyerek JSON işlemeyi kendi modülüne taşıyın, MQTT üzerinden ESP32 OTA güncellemeleri ile uzaktan firmware dağıtımı ekleyin ve cihazdan dashboard'a eksiksiz proje için ESP32 IoT eğitimine göz atın. Dev ve prod broker'larının neden farklı davrandığını anlamak için ücretsiz herkese açık MQTT broker ve sınırları hakkında okuyun.
PlatformIO Firmware'inizi ConnectMQ ile Yayına Alın
Dev ortamı herkese açık test alanında hemen çalışır. Prod ortamı için ConnectMQ üzerinde özel bir broker oluşturun: MQTT 3.1.1 ve 5.0, 8883 portunda TLS ve bir Let's Encrypt sertifikası sunan tam yönetilen bir MQTT servisi. app.connectmq.com adresinden kredi kartı gerektirmeyen ücretsiz Serverless planına kaydolun, oluşturulan kimlik bilgilerini secrets.ini dosyasına kopyalayın ve pio run -e prod -t upload çalıştırın.
Sık Sorulan Sorular
PlatformIO'da MQTT parolalarını Git dışında nasıl tutarım?
Onları .gitignore içinde listelenen ve extra_configs ile birleştirilen bir secrets.ini dosyasına koyun ya da CI'da ${sysenv.NAME} ile ortam değişkenlerinden okuyun.
Tek bir PlatformIO projesi birden fazla broker için derleme yapabilir mi?
Evet. Her broker veya dağıtım aşaması için bir ortam tanımlayın; her biri paylaşılan build_flags değerini kendi host, port ve kimlik bilgileriyle genişletsin. Ardından pio run -e <name> ile seçin.
Build flag'lerindeki kimlik bilgileri firmware içinde güvende mi?
Binary içine düz string olarak derlenirler, bu yüzden dosyaya sahip herkes onları çıkarabilir. Başkalarına teslim ettiğiniz cihazlar için kimlik bilgilerini çalışma anında sağlayın ve bunun yerine NVS'de saklayın.
PlatformIO'da MQTT için hangi kütüphaneleri kullanmalıyım?
PubSubClient en yaygın kullanılan senkron istemcidir; AsyncMqttClient ise olay güdümlü bir alternatiftir. Hangisini seçerseniz lib_deps içine sabitlenmiş bir sürümle ekleyin.



