ESP32 ve Arduino

ESP32 PlatformIO MQTT Projesi: Ortama Göre Broker Ayarlarıyla Temiz Bir Yapı

Bir ESP32 PlatformIO MQTT projesini ayrı dev ve production ortamları, derleme anında eklenen broker ayarları, sürüm kontrolü dışında tutulan gizli bilgiler ve sabitlenmiş kütüphane sürümleriyle yapılandırın.

Bir ESP32 PlatformIO MQTT projesi için dev ve production ortamlarını gösteren PlatformIO yüklü VS Code
Bunu kendi MQTT broker’ınızda çalıştırınFree planla özel, yönetilen bir MQTT broker alın ve hazır kod örnekleriyle başlayın.
Ücretsiz deneyin Kod örnekleri Kart gerekmez

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.ini iç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, kod src/ içinde dosyalara bölünmüş, yeniden kullanılabilir modüller lib/ içinde ve testler test/ içinde.
  • Komut satırından derleme. pio run VS 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ı

  1. Tırnakları kaçış karakteriyle yazılmamış string makroları. -D MQTT_HOST=broker.connectmq.com bir string değil çıplak bir token tanımlar ve derleme başarısız olur. Gösterildiği gibi \"...\" kullanın.
  2. Flag'leri genişletmek yerine üzerine yazmak. Bir ortamda ${env.build_flags} olmadan build_flags = yazmak, Wi-Fi kimlik bilgileri dahil paylaşılan flag'leri düşürür.
  3. 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.z ile sabitleyin.
  4. 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.
  5. 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.
  6. Çökmeleri çözümlemek. Backtrace'lerin dosya ve satır göstermesi için monitor_filters = esp32_exception_decoder ayarı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.

İlgili yazılar

ESP32 NTP MQTT Zaman Damgası: Her Mesaja Doğru Zaman Ekleyin

ESP32 saatini NTP ile eşitleyin, ISO 8601 ve epoch milisaniye zaman damgalarını biçimlendirin ve bunları MQTT payload'larına ekleyin; böylece ölçümler geç yayınlansa bile gerçek alınma zamanlarını korur.

6 dk okuma

Tüm yazılar →

Özel MQTT broker’ınız birkaç tık uzağınızda

Hesabınızı oluşturun, broker kimlik bilgilerinizi alın ve ilk cihazınızı bağlayın — kurulacak, yamalanacak ya da ölçeklendirilecek sunucu yok.