ESP32 ve Arduino

ESP-IDF MQTT Örneği: Sıfırdan Bir esp-mqtt İstemcisi Oluşturun

esp-mqtt bileşeniyle boş bir klasörden bir ESP-IDF projesi oluşturun: Wi-Fi station kurulumu, MQTT olay işleyicisi, publish ve subscribe, MQTT 5 özellikleri ve sertifika paketiyle TLS.

Bir ESP-IDF MQTT örneği projesinde ESP32'nin yanında idf.py monitor çalıştıran terminal
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

Bu ESP-IDF MQTT örneği, boş bir klasörden çalışan bir esp-mqtt istemcisi oluşturur: bir Wi-Fi station, olay güdümlü bir MQTT işleyicisi, publish ve subscribe, MQTT 5 bağlantı özellikleri ve son olarak TLS. Sonunda broker.connectmq.com ile konuşan bir firmware'e ve esp-mqtt bileşeninin nasıl yapılandırıldığına dair net bir resme sahip olacaksınız.

Neden Arduino Yerine ESP-IDF?

PubSubClient gibi Arduino kütüphaneleriyle başlamak hızlıdır, ancak bunlar yalnızca MQTT 3.1.1 destekler ve çoğunlukla engelleyicidir. ESP-IDF'in esp-mqtt bileşeni Espressif tarafından bakımı yapılır, kendi FreeRTOS görevinde çalışır, MQTT 3.1, 3.1.1 ve 5.0'ı destekler ve TCP, TLS, WebSocket ve secure WebSocket taşımalarını tek bir URI üzerinden yönetir. Yeniden bağlanma, keepalive ve outbox yönetimi yerleşiktir; kodunuz yoklama yapmak yerine olaylara tepki verir.

Bunun bedeli daha fazla kurulumdur: CMake, menuconfig ve açık Wi-Fi başlatma. Bu rehber bu kurulumu olabildiğince küçük tutar. Yapılandırma struct'ının .broker.address.uri gibi iç içe alanlar kullandığı ESP-IDF 5.x'i hedefler. ESP-IDF 4.x için yazılan kod düz alanlar kullanıyordu ve değiştirilmeden derlenmez. Yetkili API referansı ESP-IDF MQTT dokümantasyonudur.

Proje Yapısı

Şu yapıyı oluşturun:

connectmq-idf/
  CMakeLists.txt
  sdkconfig.defaults
  main/
    CMakeLists.txt
    main.c

Üst seviye CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(connectmq_idf)

main/CMakeLists.txt:

idf_component_register(SRCS "main.c" INCLUDE_DIRS ".")

main bileşeni otomatik olarak tüm framework bileşenlerine bağımlıdır; bu yüzden mqtt, esp_wifi veya nvs_flash'ı açıkça listelemeniz gerekmez. ESP-IDF 5.x'te esp-mqtt framework ile birlikte gelir. ESP-IDF sürümünüz onu bileşen kayıt defterine taşıdıysa idf.py add-dependency "espressif/mqtt" ile ekleyin.

sdkconfig.defaults, varsayılan olarak kapalı olan MQTT 5 desteğini etkinleştirir:

CONFIG_MQTT_PROTOCOL_5=y

Eksiksiz ESP-IDF MQTT Örneği

İşte tam main/main.c. Wi-Fi'a bağlanır, MQTT istemcisini broker.connectmq.com'un public alanına karşı başlatır, bir komut topic'ine abone olur ve her beş saniyede bir sayaç yayınlar.

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_mac.h"
#include "nvs_flash.h"
#include "mqtt_client.h"

#define WIFI_SSID      "YOUR_WIFI"
#define WIFI_PASS      "YOUR_WIFI_PASSWORD"
#define BROKER_URI     "mqtt://broker.connectmq.com:1883"
#define TOPIC_PREFIX   "connectmq/demo/c19d44"

static const char *TAG = "connectmq";
static EventGroupHandle_t wifi_events;
#define WIFI_CONNECTED_BIT BIT0

static esp_mqtt_client_handle_t client;
static bool mqtt_connected = false;

static void wifi_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    if (base == WIFI_EVENT && id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) {
        xEventGroupClearBits(wifi_events, WIFI_CONNECTED_BIT);
        ESP_LOGW(TAG, "Wi-Fi lost, reconnecting");
        esp_wifi_connect();
    } else if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t *ev = (ip_event_got_ip_t *)data;
        ESP_LOGI(TAG, "Got IP " IPSTR, IP2STR(&ev->ip_info.ip));
        xEventGroupSetBits(wifi_events, WIFI_CONNECTED_BIT);
    }
}

static void wifi_init(void)
{
    wifi_events = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_handler, NULL));

    wifi_config_t wc = { 0 };
    strncpy((char *)wc.sta.ssid, WIFI_SSID, sizeof(wc.sta.ssid));
    strncpy((char *)wc.sta.password, WIFI_PASS, sizeof(wc.sta.password));
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wc));
    ESP_ERROR_CHECK(esp_wifi_start());

    xEventGroupWaitBits(wifi_events, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
}

static void mqtt_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
    esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)data;

    switch ((esp_mqtt_event_id_t)id) {
    case MQTT_EVENT_CONNECTED:
        ESP_LOGI(TAG, "MQTT connected");
        mqtt_connected = true;
        esp_mqtt_client_subscribe(client, TOPIC_PREFIX "/cmd", 1);
        esp_mqtt_client_publish(client, TOPIC_PREFIX "/status", "online", 0, 1, 1);
        break;
    case MQTT_EVENT_DISCONNECTED:
        ESP_LOGW(TAG, "MQTT disconnected");
        mqtt_connected = false;
        break;
    case MQTT_EVENT_SUBSCRIBED:
        ESP_LOGI(TAG, "Subscribed, msg_id=%d", event->msg_id);
        break;
    case MQTT_EVENT_DATA:
        ESP_LOGI(TAG, "Topic %.*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, errno=%d",
                     event->error_handle->esp_transport_sock_errno);
        } else if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) {
            ESP_LOGE(TAG, "Connection refused, code=0x%x",
                     event->error_handle->connect_return_code);
        }
        break;
    default:
        break;
    }
}

static void mqtt_start(void)
{
    static char client_id[32];
    uint8_t mac[6];
    esp_read_mac(mac, ESP_MAC_WIFI_STA);
    snprintf(client_id, sizeof(client_id), "idf-%02x%02x%02x", mac[3], mac[4], mac[5]);

    esp_mqtt_client_config_t cfg = {
        .broker.address.uri = BROKER_URI,
        .credentials.client_id = client_id,
        .session.protocol_ver = MQTT_PROTOCOL_V_5,
        .session.keepalive = 30,
        .session.last_will = {
            .topic = TOPIC_PREFIX "/status",
            .msg = "offline",
            .qos = 1,
            .retain = 1,
        },
    };

    client = esp_mqtt_client_init(&cfg);

    esp_mqtt5_connection_property_config_t conn_props = {
        .session_expiry_interval = 600,   /* seconds */
    };
    esp_mqtt5_client_set_connect_property(client, &conn_props);

    esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_handler, NULL);
    esp_mqtt_client_start(client);
}

void app_main(void)
{
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK(err);

    wifi_init();
    mqtt_start();

    int counter = 0;
    char payload[64];
    while (true) {
        if (mqtt_connected) {
            int len = snprintf(payload, sizeof(payload), "{\"count\":%d}", counter++);
            esp_mqtt_client_publish(client, TOPIC_PREFIX "/telemetry", payload, len, 0, 0);
        }
        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

Derleyin, yükleyin ve monitörü açın:

idf.py set-target esp32
idf.py build flash monitor

Ardından trafiği bilgisayarınızdan izleyin:

mosquitto_sub -h broker.connectmq.com -p 1883 -i pc-c19d44 -t 'connectmq/demo/c19d44/#' -v
mosquitto_pub -h broker.connectmq.com -p 1883 -i pc-c19d44-pub -t connectmq/demo/c19d44/cmd -m hello

ESP32 logu, hello komutunun MQTT_EVENT_DATA üzerinden geldiğini gösterir.

esp-mqtt Olay Modeli Nasıl Çalışır?

İstemci, esp_mqtt_client_start() tarafından oluşturulan kendi görevinde çalışır. Size bildirmek istediği her şey, kaydettiğiniz işleyiciye bir olay olarak ulaşır:

Olay Ne zaman tetiklenir Tipik eylem
MQTT_EVENT_CONNECTED CONNACK alındığında Abone olun, doğum mesajını yayınlayın
MQTT_EVENT_DISCONNECTED Bağlantı koptuğunda Durumu güncelleyin; istemci otomatik olarak yeniden bağlanır
MQTT_EVENT_SUBSCRIBED SUBACK alındığında Abonelikleri doğrulayın
MQTT_EVENT_PUBLISHED QoS 1/2 için PUBACK/PUBCOMP Teslimatı takip edin
MQTT_EVENT_DATA Mesaj alındığında Payload'ı kopyalayıp yönlendirin
MQTT_EVENT_ERROR Taşıma veya protokol hatası Hata türünü ve kodlarını loglayın

Üç ayrıntı insanları şaşırtır:

  1. Topic ve veri null ile sonlanmaz. Yukarıdaki %.*s biçiminin yaptığı gibi her zaman topic_len ve data_len kullanın.
  2. Büyük mesajlar parçalar halinde gelir. Bir payload istemcinin tamponundan (.buffer.size, varsayılan 1024 bayt) büyükse birkaç MQTT_EVENT_DATA olayı alırsınız. event->current_data_offset ve event->total_data_len değerlerini kontrol edin ve parçaları birleştirin.
  3. MQTT_EVENT_CONNECTED içinde abone olun. Clean session ile abonelikler yeniden bağlanmadan sonra kaybolur. Bunları connected işleyicisinde yapmak, her seferinde yeniden kurulmalarını sağlar.

İşleyici MQTT görevinde çalışır, bu yüzden kısa tutun. Ağır işler, ESP32 FreeRTOS MQTT rehberinde anlatıldığı gibi bir kuyrukla beslenen ayrı bir göreve aittir.

Publish ile Enqueue Arasındaki Fark

esp_mqtt_client_publish() engelleyicidir: paketi çağıran görevden gönderir ve QoS 1 ve 2 için onaylanana kadar outbox'ta tutar. Olay işleyicisinin içinden çağırmak sorun değildir. Zaman açısından kritik bir görevden yayın yapıyorsanız esp_mqtt_client_enqueue(client, topic, data, len, qos, retain, true) mesajı outbox'a yerleştirir ve hemen döner; MQTT görevi onu daha sonra gönderir. Her ikisi de bir mesaj ID'si ya da hata durumunda negatif bir değer döndürür; bu yüzden kontrol edin.

MQTT 5 Özelliklerini Kullanmak

.session.protocol_ver = MQTT_PROTOCOL_V_5 ayarlamak ve CONFIG_MQTT_PROTOCOL_5'i etkinleştirmek istemciyi MQTT 5'e geçirir. Örnek, esp_mqtt5_client_set_connect_property() aracılığıyla zaten bir oturum sona erme aralığı ayarlar. broker.connectmq.com'un public alanında oturum sona erme süresi en fazla 30 dakika olabilir; burada kullanılan 10 dakika rahatça sığar.

Yayın özellikleri publish çağrısından önce ayarlanır:

esp_mqtt5_publish_property_config_t pub_props = {
    .message_expiry_interval = 60,
    .content_type = "application/json",
};
esp_mqtt5_client_set_publish_property(client, &pub_props);
esp_mqtt_client_publish(client, TOPIC_PREFIX "/telemetry", payload, len, 1, 0);

Mesaj sona erme süresi, çok daha sonra geri gelen bir aboneye eski ölçümlerin teslim edilmesini engeller. Bu bileşendeki response topic'ler, user property'ler ve reason code'lar hakkında daha derin bir inceleme için esp-mqtt ve MQTT 5 yazısına, protokol tarafı için de MQTT 5 özellikleri yazısına bakın.

QoS, Retain ve Keepalive Seçimi

Örnek bilerek üç farklı yayın ayarı kullanır. Rutin telemetri retain olmadan QoS 0 ile gönderilir: bir ölçüm kaybolursa bir sonraki beş saniye sonra gelir ve hiçbir şey onay beklemez. Durum mesajı retain ile QoS 1 kullanır; böylece broker tarafından onaylanır ve daha sonra abone olan herhangi bir dashboard cihazın çevrimiçi olup olmadığını görür. Last Will bu durum topic'ini offline ile yansıtır; cihaz düzgün bir bağlantı kesme olmadan kaybolursa broker bunu yayınlar.

30 saniyelik keepalive makul bir orta noktadır. esp-mqtt PINGREQ paketlerini kendisi gönderir; bu yüzden Arduino kütüphanelerinin aksine oturumu canlı tutmak için bir loop fonksiyonu çağırmanız gerekmez. Daha kısa bir keepalive ölü cihazları daha hızlı tespit eder ama daha fazla radyo süresine mal olur; daha uzun bir keepalive pilli düğümlere karşı daha naziktir. Teslimat garantilerinde yeniyseniz MQTT QoS seviyeleri yazısı her seviyenin hat üzerindeki maliyetini adım adım anlatır.

8883 Portunda TLS'e Geçiş

Bir demonun ötesindeki her şey için bağlantıyı şifreleyin. ESP-IDF, ConnectMQ'nun kullandığı Let's Encrypt kökü dahil yaygın kök CA'ları içeren bir sertifika paketiyle gelir; bu yüzden bir PEM dosyası yapıştırmanız gerekmez. URI şemasını değiştirin ve paketi ekleyin:

#include "esp_crt_bundle.h"

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 = "YOUR_CLIENT_ID",
    .credentials.username = "YOUR_USERNAME",
    .credentials.authentication.password = "YOUR_PASSWORD",
    .session.protocol_ver = MQTT_PROTOCOL_V_5,
};

CONFIG_MBEDTLS_CERTIFICATE_BUNDLE'ın etkin olduğundan emin olun (varsayılan olarak etkindir). Portalda broker'ınız için oluşturulan client ID, kullanıcı adı ve parolayı kullanın; client ID bu kimlik bilgilerine bağlıdır, bu yüzden uydurulmuş bir ID reddedilir. Aynı URI yaklaşımı WebSocket taşımaları için de çalışır: CONFIG_MQTT_TRANSPORT_WEBSOCKET etkinse ws://broker.connectmq.com:8083/mqtt ve wss://broker.connectmq.com:8084/mqtt.

Sorun Giderme Kontrol Listesi

  1. .uri veya .host ile ilgili derleme hataları: ESP-IDF 4.x ve 5.x yapılandırma sözdizimini karıştırıyorsunuz. Burada gösterilen iç içe alanları kullanın.
  2. MQTT 5 fonksiyonları tanımsız: CONFIG_MQTT_PROTOCOL_5 ayarlı değil. sdkconfig.defaults'u ekledikten sonra sdkconfig'i silin veya idf.py menuconfig içinde ayarlayın.
  3. Başlatmadan hemen sonra MQTT_ERROR_TYPE_TCP_TRANSPORT: Wi-Fi henüz bağlı değil, DNS başarısız oldu veya şema ile port eşleşmiyor (mqtts:// 8883 gerektirir).
  4. Kimlik bilgileriyle bağlantı reddediliyor: portaldaki client ID'yi birebir kontrol edin ve parola alanının .credentials.authentication.password olduğundan emin olun.
  5. Payload'lar kesiliyor: .buffer.size'ı artırın veya parçalı MQTT_EVENT_DATA olaylarını birleştirin.
  6. MQTT görevinde stack taşması: özellikle TLS ve ayrıntılı loglama ile .task.stack_size'ı yükseltin.

Genel olarak bağlantı hatalarıyla uğraşıyorsanız, ESP32 bir MQTT broker'a bağlanamadığında ne yapılacağına dair rehber ESP-IDF için de geçerli olan ağ seviyesindeki kontrolleri ele alır.

esp-mqtt Firmware'inizi ConnectMQ ile Üretime Taşıyın

Bu ESP-IDF MQTT örneğini az önce herkese açık MQTT test broker'ı üzerinde çalıştırdınız. Özel kimlik bilgilerine hazır olduğunuzda ConnectMQ size MQTT 5, 8883 portunda TLS ve ESP-IDF paketinin zaten güvendiği bir Let's Encrypt sertifikasıyla barındırılan bir MQTT broker sunar. app.connectmq.com adresinde kredi kartı gerekmeden ücretsiz Serverless planında bir tane oluşturun ve yapılandırmanızda yalnızca URI'yi ve kimlik bilgilerini değiştirin. Cihaz projelerinin genel resmi için ESP32 IoT eğitimine bakın.

Sık Sorulan Sorular

esp-mqtt otomatik olarak yeniden bağlanır mı?

Evet. Bağlantı kesildikten sonra istemci .network.reconnect_timeout_ms kullanarak kendi kendine yeniden dener. Aboneliklerin bağlantıyla birlikte geri gelmesi için MQTT_EVENT_CONNECTED içinde yeniden abone olun.

Bu örnek hangi ESP-IDF sürümünü gerektirir?

İstemci yapılandırmasının .broker.address.uri gibi iç içe alanlar kullandığı ESP-IDF 5.x'i hedefler. ESP-IDF 4.x düz bir struct kullanıyordu ve küçük değişiklikler gerektirir.

Ücretsiz ConnectMQ planında MQTT 5 kullanabilir miyim?

Evet. Platformdaki her broker MQTT 3.1.1 ve 5.0'ı destekler. Ücretsiz Serverless planı QoS 1'e kadar destekler; bu yüzden orada yayınlarınızda QoS 0 veya 1 kullanın.

Aldığım topic neden sonunda anlamsız karakterlerle yazdırılıyor?

MQTT_EVENT_DATA içindeki topic ve veri işaretçileri null ile sonlanmaz. Bunları %.*s ve uzunluk alanlarıyla yazdırın veya sonlandırılmış bir tampona kopyalayın.

İ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.