Uygulamalı Eğitimler

ESP32'de ESP-IDF (esp-mqtt) ile MQTT: Yapılandırma, Event'ler ve TLS

ESP32'de ESP-IDF esp-mqtt istemcisini kullanın: esp_mqtt_client_config_t, event handler, publish ve subscribe, Last Will, 8883'te TLS ve hepsini test etme.

Güncellendi · 9 dk okuma

ESP32'de ESP-IDF (esp-mqtt) ile MQTT: Yapılandırma, Event'ler ve TLS

esp-mqtt, Espressif'in ESP-IDF framework'üyle birlikte gelen MQTT istemcisidir. Kendi FreeRTOS task'ında çalışır, otomatik olarak yeniden bağlanır, MQTT 3.1.1 ve MQTT 5'i destekler; TCP, TLS, WebSocket ve güvenli WebSocket'i tek bir URI ile konuşur. Tek bir esp_mqtt_client_config_t struct'ı ile yapılandırır, bir event handler kaydeder ve esp_mqtt_client_start() çağırırsınız.

Bu eğitim adım adım eksiksiz bir ESP32 uygulaması kuruyor: önce şifresiz TCP, ardından event'ler, publish, abonelikler, bir Last Will ve son olarak 8883 portunda TLS. Her aşamada sonucu tarayıcıdan doğrularsınız; böylece bir şey bozulduğunda sorunun testinizde değil cihazda olduğunu bilirsiniz.

Projeyi hazırlama

ESP-IDF 5.x kurulu olmalı (idf.py --version terminalinizde çalışmalı). Eğitimin MQTT'ye odaklanabilmesi için resmi örneklerin kullandığı Wi-Fi yardımcısından başlayın:

Create the project
idf.py create-project mqtt_demo
cd mqtt_demo
idf.py set-target esp32

Örnek Wi-Fi bileşenini kök dizindeki CMakeLists.txt içinde görünür yapın (include(...project.cmake) satırından önce) ve main bileşeninin bağımlılıklarını tanımlayın. mqtt bileşeni ESP-IDF'in parçasıdır; bu yüzden 5.x sürümlerinde #include "mqtt_client.h" ek indirme gerekmeden çalışır.

CMakeLists.txt (project root)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
main/CMakeLists.txt
idf_component_register(SRCS "main.c"
                       INCLUDE_DIRS "."
                       PRIV_REQUIRES mqtt esp_wifi esp_netif nvs_flash protocol_examples_common)

Ardından idf.py menuconfig çalıştırın, Example Connection Configuration menüsünü açın ve Wi-Fi SSID'nizi ve şifrenizi girin.

İstemci yapılandırması

En küçük yapılandırma bir broker URI'sidir. Şema, transport'u belirler:

URITransportPort
mqtt://broker.connectmq.com:1883TCP1883
mqtts://broker.connectmq.com:8883TLS8883
ws://broker.connectmq.com:8083/mqttWebSocket8083
wss://broker.connectmq.com:8084/mqttGüvenli WebSocket8084

Gerçekçi bir yapılandırma ayrıca client ID, kimlik bilgileri, keep alive ve bir Last Will tanımlar:

main/main.c — configuration
#include "esp_log.h"
#include "mqtt_client.h"

static const char *TAG = "mqtt_demo";
static esp_mqtt_client_handle_t client;

#define BASE_TOPIC "testmqtt/espidf/dev01"

static esp_mqtt_client_handle_t mqtt_create(void)
{
    const esp_mqtt_client_config_t cfg = {
        .broker.address.uri = "mqtt://broker.connectmq.com:1883",
        .credentials.client_id = "espidf-dev01",
        .credentials.username = "username",
        .credentials.authentication.password = "password",
        .session.keepalive = 30,
        .session.last_will = {
            .topic = BASE_TOPIC "/status",
            .msg = "offline",
            .qos = 1,
            .retain = 1,
        },
        .network.reconnect_timeout_ms = 5000,
    };
    return esp_mqtt_client_init(&cfg);
}

Her istemcinin benzersiz bir client ID'si olmalıdır: aynı ID'ye sahip iki cihaz sonsuz bir döngüde birbirini broker'dan atar. Production'da ID'yi MAC adresinden türetin, test ederken de client ID üretici ile bir tane oluşturun. Kimlik bilgilerini Kconfig.projbuild seçeneklerine (CONFIG_MQTT_USER, CONFIG_MQTT_PASS) taşıyıp menuconfig veya sdkconfig.defaults içinde ayarlayarak sürüm kontrolünün dışında tutun.

Event'leri işleme

esp-mqtt her şeyi ESP event loop üzerinden bildirir. Tek bir handler tüm event'leri alır; event_data bir esp_mqtt_event_t gösterir:

main/main.c — event handler
static void mqtt_event_handler(void *args, esp_event_base_t base,
                               int32_t event_id, void *event_data)
{
    esp_mqtt_event_handle_t event = event_data;

    switch ((esp_mqtt_event_id_t)event_id) {
    case MQTT_EVENT_CONNECTED:
        ESP_LOGI(TAG, "connected");
        esp_mqtt_client_publish(client, BASE_TOPIC "/status", "online", 0, 1, 1);
        esp_mqtt_client_subscribe(client, BASE_TOPIC "/cmd/#", 1);
        break;
    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGW(TAG, "disconnected, will retry");
        break;
    case MQTT_EVENT_SUBSCRIBED:
        ESP_LOGI(TAG, "subscribed, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_PUBLISHED:
        ESP_LOGI(TAG, "broker acked msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_DATA:
        // topic ve data null ile SONLANMAZ
        ESP_LOGI(TAG, "%.*s = %.*s", event->topic_len, event->topic,
                 event->data_len, event->data);
        break;
    case MQTT_EVENT_ERROR:
        if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
            ESP_LOGE(TAG, "transport error: esp-tls 0x%x, tls stack 0x%x, errno %d",
                     event->error_handle->esp_tls_last_esp_err,
                     event->error_handle->esp_tls_stack_err,
                     event->error_handle->esp_transport_sock_errno);
        } else if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) {
            ESP_LOGE(TAG, "refused, CONNACK code %d",
                     event->error_handle->connect_return_code);
        }
        break;
    default:
        break;
    }
}

Neredeyse herkesin takıldığı üç ayrıntı var:

  • start() sonrasında değil, MQTT_EVENT_CONNECTED içinde subscribe olun. İstemci defalarca yeniden bağlanabilir; her bağlantıda subscribe olmak, broker oturumu saklamamış olsa bile aboneliklerinizi geri getirir.
  • topic ve data C string'i değildir. Her zaman _len alanlarını kullanın. Büyük mesajlar birden fazla MQTT_EVENT_DATA parçası hâlinde gelir; event->current_data_offset ve event->total_data_len ile birleştirebilir ya da .buffer.size ile buffer'ı büyütebilirsiniz.
  • Handler MQTT task'ında çalışır. İçinde bloklamayın. Uzun işleri bir FreeRTOS kuyruğu ile başka bir task'a devredin.

Her şeyi app_main içinden başlatın:

main/main.c — app_main
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "protocol_examples_common.h"

void app_main(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    ESP_ERROR_CHECK(example_connect());          // Wi-Fi IP alana kadar bloklar

    client = mqtt_create();
    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
    esp_mqtt_client_start(client);
}

idf.py flash monitor ile yükleyip log'u izleyin. Önce connected, ardından subscribed görmelisiniz.

Telemetri yayınlama

esp_mqtt_client_publish(client, topic, data, len, qos, retain) bir mesaj ID'si döndürür (QoS 0'da 0, hata durumunda -1). Metin payload'larda len = 0 verirseniz kütüphane strlen() çağırır. QoS 1 ve 2'de çağrı, çağıran task'tan yazar ve ağı bekleyebilir; esp_mqtt_client_enqueue() aynı argümanlara ek olarak bir store bayrağı alır ve gönderimi MQTT task'ına bırakır:

Telemetry task
static void telemetry_task(void *arg)
{
    char payload[64];
    for (int i = 0;; i++) {
        int len = snprintf(payload, sizeof payload,
                           "{\"seq\":%d,\"heap\":%lu}", i,
                           (unsigned long)esp_get_free_heap_size());
        int id = esp_mqtt_client_enqueue(client, BASE_TOPIC "/telemetry",
                                         payload, len, 1, 0, true);
        if (id < 0) ESP_LOGW(TAG, "outbox full or not connected");
        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}
// app_main içinde, esp_mqtt_client_start() sonrasında:
// xTaskCreate(telemetry_task, "telemetry", 3072, NULL, 5, NULL);

İstemci çevrimdışıyken kuyruğa alınan mesajlar outbox'ta kalır (CONFIG_MQTT_OUTBOX_EXPIRED_TIMEOUT_MS ve boş heap ile sınırlı) ve yeniden bağlanınca gönderilir. QoS'u bilinçli seçin: her seviyenin maliyeti için MQTT QoS seviyeleri yazısına bakın.

Tarayıcıdan doğrulama

Online MQTT istemcisini açın, broker.connectmq.com adresine bağlanın (WebSocket 8084, path /mqtt) ve testmqtt/espidf/dev01/# topic'ine subscribe olun. Şunları görmelisiniz:

  1. testmqtt/espidf/dev01/status = online, retained olarak işaretli.
  2. Her beş saniyede bir telemetri mesajı.

Şimdi tarayıcıdan testmqtt/espidf/dev01/cmd/system topic'ine reboot yayınlayın: ESP32 log'u topic'i ve payload'u yazdırır. Son olarak güç kablosunu çekin. Yaklaşık 1,5 × keep alive süresinden sonra (burada 45 saniye) broker Last Will'i yayınlar ve durum offline olur. Bu tek test bağlantının, publish'in, subscribe'ın ve will'in çalıştığını kanıtlar. Zamanlamayı Last Will nasıl çalışır yazısı açıklıyor.

ESP32'nizi online istemcide izleyin

8883 portunda TLS ekleme

TLS'e geçmek iki değişiklik demektir: bir mqtts:// URI'si ve sunucu sertifikasını doğrulamanın bir yolu. En basiti, yaygın genel kök CA'ları içeren ve varsayılan olarak açık gelen ESP x509 certificate bundle'dır (CONFIG_MBEDTLS_CERTIFICATE_BUNDLE):

TLS with the certificate bundle
#include "esp_crt_bundle.h"

const esp_mqtt_client_config_t cfg = {
    .broker.address.uri = "mqtts://broker.connectmq.com:8883",
    .broker.verification.crt_bundle_attach = esp_crt_bundle_attach,
    .credentials.client_id = "espidf-dev01",
    .credentials.username = "username",
    .credentials.authentication.password = "password",
};

Broker'ınız özel bir CA kullanıyorsa ya da belirli bir kökü sabitlemek istiyorsanız PEM dosyasını gömün:

main/CMakeLists.txt
idf_component_register(SRCS "main.c" INCLUDE_DIRS "."
                       PRIV_REQUIRES mqtt esp_wifi esp_netif nvs_flash protocol_examples_common
                       EMBED_TXTFILES "ca.pem")
Embedded CA certificate
extern const uint8_t ca_pem_start[] asm("_binary_ca_pem_start");

const esp_mqtt_client_config_t cfg = {
    .broker.address.uri = "mqtts://broker.connectmq.com:8883",
    .broker.verification.certificate = (const char *)ca_pem_start,
};

TLS, sertifikanın geçerliliğini kontrol etmek için doğru saate ve handshake sırasında yaklaşık 30–40 KB ek heap'e ihtiyaç duyar; yoğun bir ESP32'de bağlantılar başarısız olduğunda ilk bakılacak yer burasıdır. ESP32 TLS rehberimiz karşılıklı TLS için istemci sertifikalarını (.credentials.authentication.certificate ve .key) anlatıyor.

Sık görülen esp-mqtt hataları

Log satırıSebepÇözüm
esp-tls: couldn't get hostname for :broker.connectmq.com:DNS başarısız, genellikle Wi-Fi henüz bağlı değilMQTT'yi yalnızca IP alındıktan sonra başlatın
mbedtls_ssl_handshake returned -0x2700Sertifika doğrulaması başarısızBundle'ı veya doğru CA'yı kullanın; sistem saatini kontrol edin
mbedtls_ssl_setup returned -0x7F00TLS için bellek yetersizHeap boşaltın, diğer buffer'ları küçültün
Error transport connectPort engelli veya şema/port eşleşmesi yanlışmqtts:// ile 8883, mqtt:// ile 1883 kullandığınızı kontrol edin
MQTT_ERROR_TYPE_CONNECTION_REFUSED, kod 5YetkisizKullanıcı adı, şifre ve ACL'leri kontrol edin

Sayısal CONNACK kodları ve MQTT 5 reason code'ları reason code arama aracında listeleniyor. Bağlanıp birkaç dakikada bir kopan cihazlar için ESP32 Wi-Fi ve MQTT kopmaları yazısını okuyun.

MQTT 5'i etkinleştirme

menuconfig'de CONFIG_MQTT_PROTOCOL_5 seçeneğini açın (Component config → ESP-MQTT Configurations) ve .session.protocol_ver = MQTT_PROTOCOL_V_5 ayarlayın. Temel publish ve subscribe çağrıları aynı kalır; user property, message expiry ve response topic gibi MQTT 5 özellikleri publish çağrısından önce esp_mqtt5_client_set_publish_property() gibi fonksiyonlarla ayarlanır. Reddedilen bağlantılar artık MQTT 3.1.1'in altı dönüş kodu yerine kesin reason code'lar bildirir; bu da kimlik doğrulama hatalarını ayıklamayı çok kolaylaştırır.

esp-mqtt çok az kodla production seviyesinde bir istemci sunar: bir yapılandırma struct'ı, bir event handler ve URI ile seçilen transport. Önce şifresiz TCP üzerinde çalıştırın, publish, subscribe ve will'i online istemciden kanıtlayın, ardından mqtts://'e geçin ve her firmware sürümünden önce aynı testleri regresyon kontrolü olarak koruyun.

Sıkça sorulan sorular

ESP-IDF'de MQTT için ek bir kütüphane gerekir mi?

Çoğu proje için hayır. esp-mqtt istemcisi ESP-IDF ile birlikte gelir (mqtt_client.h başlığı). Bazı yeni ESP-IDF sürümleri onu component registry üzerinden espressif/mqtt olarak dağıtır; başlık bulunamazsa sürümünüzün dokümantasyonuna bakın.

esp_mqtt_client_publish neden bloklanıyor?

QoS 1 ve 2'de, çağıran task'tan doğrudan socket'e yazar ve ağı bekleyebilir. esp_mqtt_client_enqueue ile mesajı outbox'a koyup gönderimi MQTT task'ına bırakın; zaman açısından kritik kodda bu daha güvenlidir.

esp-mqtt MQTT 5'i destekliyor mu?

Evet. menuconfig'de MQTT 5'i açın (Component config → ESP-MQTT Configurations) ve session.protocol_ver değerini MQTT_PROTOCOL_V_5 yapın. Böylece reason code'lara, user property'lere ve diğer MQTT 5 özelliklerine ayrı API'ler üzerinden erişirsiniz.