ESP-IDF'teki esp-mqtt MQTT 5 desteği, bir ESP32'nin mesajlara meta veri eklemesine, son kullanma süreleri belirlemesine, topic'leri alias'larla kısaltmasına ve kendi kurallarınızı icat etmeden request/response akışları kurmasına olanak tanır. Bu eğitimde bir ESP-IDF projesinde MQTT 5.0'ı etkinleştirecek, broker.connectmq.com'a bağlanacak ve her özelliği bugün yükleyebileceğiniz çalışan C koduyla kullanacaksınız.
Bir Mikrodenetleyicide Neden MQTT 5
MQTT 3.1.1 bir ESP32'de gayet kullanılabilir ve çoğu Arduino kütüphanesi orada durur. MQTT 5.0 ise gerçek cihaz sorunlarını çözen özellikler ekler:
| Özellik | Cihazda çözdüğü sorun |
|---|---|
| User properties | JSON payload'ı şişirmeden firmware sürümü, birimler veya şema sürümü gönderme |
| Message expiry interval | Bayatlamış komutların veya ölçümlerin saatler sonra teslim edilmesini önleme |
| Topic alias | Tekrarlanan publish'lerde uzun bir topic string'ini 2 baytlık bir sayıyla değiştirme |
| Session expiry interval | Abonelikleri sonsuza kadar tutmadan kısa Wi-Fi kopmaları boyunca koruma |
| Response topic ve correlation data | Standart request/response; böylece bir backend bir cihazı çağırıp yanıtı eşleştirebilir |
| Reason code'lar | Bir connect, publish veya subscribe işleminin neden reddedildiğini tam olarak öğrenme |
ESP-IDF ile birlikte gelen MQTT istemcisi esp-mqtt, bunların tümünü derleme zamanı bir seçeneğin arkasında uygular. MQTT 5.0 konuşan bir broker'a da ihtiyacınız vardır. ConnectMQ her listener'da hem 3.1.1'i hem de 5.0'ı destekler; böylece aynı uç nokta eski ve yeni firmware için çalışır. Protokolün arka planı için MQTT 5 özellikleri yazısı her özelliği derinlemesine ele alır.
Ön Koşullar
- Terminalinizde
idf.pyçalışır durumda, kurulu ESP-IDF 5.x. - ESP32 ailesinden herhangi bir kart (ESP32, S3, C3, C6).
- Bilgisayarınızda Mosquitto 2.x istemcileri;
mosquitto_subMQTT 5 özelliklerini yazdırabilir. - Temel bir ESP-IDF MQTT projesine aşinalık. Henüz bir tane oluşturmadıysanız ESP-IDF MQTT örneğiyle başlayın ve sonra geri dönün.
Proje Kurulumu ve MQTT 5.0'ı Etkinleştirme
Şu düzende bir proje klasörü oluşturun:
mqtt5_connectmq/
CMakeLists.txt
sdkconfig.defaults
main/
CMakeLists.txt
main.c
Resmi örneklerin kullandığı Wi-Fi yardımcısını dahil eden üst düzey CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(mqtt5_connectmq)
main/CMakeLists.txt:
idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
PRIV_REQUIRES mqtt nvs_flash esp_netif esp_event protocol_examples_common)
sdkconfig.defaults, esp-mqtt'deki MQTT 5 kod yollarını açar. Bu seçenek olmadan esp_mqtt5_* fonksiyonları derlemeye dahil edilmez:
CONFIG_MQTT_PROTOCOL_5=y
Ardından Wi-Fi kimlik bilgilerinizi idf.py menuconfig ile Example Connection Configuration altında ayarlayın. ESP-IDF sürümünüz mqtt bileşenini artık ağaç içinde sunmuyorsa onu idf.py add-dependency espressif/mqtt ile ekleyin.
esp-mqtt MQTT 5 Örnek Kodu
PREFIX değerinin rastgele kısmını ve client ID'yi kendi değerlerinizle değiştirin.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "esp_log.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "nvs_flash.h"
#include "esp_random.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "protocol_examples_common.h"
#include "mqtt_client.h"
#define PREFIX "connectmq/demo/idf5m7"
#define CLIENT_ID "idf5-demo-m7q2"
static const char *TAG = "mqtt5";
static esp_mqtt_client_handle_t client;
/* CONNECT içinde bir kez gönderilen özellikler */
static esp_mqtt5_connection_property_config_t connect_property = {
.session_expiry_interval = 600, /* bağlantı kesildikten sonra oturumu 10 dk tut */
.maximum_packet_size = 4096,
.receive_maximum = 16,
.topic_alias_maximum = 4, /* broker'dan kabul ettiğimiz alias sayısı */
.request_resp_info = true,
.request_problem_info = true,
.will_delay_interval = 15, /* will'i yayınlamadan önce 15 sn bekle */
.payload_format_indicator = true,
.message_expiry_interval = 3600,
.content_type = "text/plain",
};
static esp_mqtt5_user_property_item_t connect_user_props[] = {
{"model", "esp32-devkit"},
{"fw", "1.4.2"},
};
static esp_mqtt5_user_property_item_t telemetry_user_props[] = {
{"schema", "telemetry-v2"},
{"unit", "celsius"},
};
static void print_user_properties(mqtt5_user_property_handle_t props)
{
if (!props) return;
uint8_t count = esp_mqtt5_client_get_user_property_count(props);
if (!count) return;
esp_mqtt5_user_property_item_t *items = malloc(count * sizeof(*items));
if (items && esp_mqtt5_client_get_user_property(props, items, &count) == ESP_OK) {
for (int i = 0; i < count; i++) {
ESP_LOGI(TAG, " user property %s = %s", items[i].key, items[i].value);
free((char *)items[i].key);
free((char *)items[i].value);
}
}
free(items);
}
static void publish_telemetry(float temperature, int seq)
{
char payload[96];
int len = snprintf(payload, sizeof(payload),
"{\"seq\":%d,\"temperature\":%.2f}", seq, temperature);
esp_mqtt5_publish_property_config_t pub_prop = {
.payload_format_indicator = 1, /* UTF-8 payload */
.message_expiry_interval = 60, /* bir dakika sonra işe yaramaz */
.topic_alias = 1, /* bu topic'i alias 1'e eşle */
.content_type = "application/json",
};
esp_mqtt5_client_set_user_property(&pub_prop.user_property, telemetry_user_props,
sizeof(telemetry_user_props) / sizeof(telemetry_user_props[0]));
esp_mqtt5_client_set_publish_property(client, &pub_prop);
int msg_id = esp_mqtt_client_publish(client, PREFIX "/telemetry", payload, len, 1, 0);
ESP_LOGI(TAG, "published seq %d, msg_id %d", seq, msg_id);
esp_mqtt5_client_delete_user_property(pub_prop.user_property);
}
static void reply_to_request(esp_mqtt_event_handle_t event)
{
char response_topic[128];
int n = event->property->response_topic_len;
if (n <= 0 || n >= (int)sizeof(response_topic)) return;
memcpy(response_topic, event->property->response_topic, n);
response_topic[n] = '\0';
esp_mqtt5_publish_property_config_t pub_prop = {
.correlation_data = event->property->correlation_data,
.correlation_data_len = event->property->correlation_data_len,
.message_expiry_interval = 30,
};
esp_mqtt5_client_set_publish_property(client, &pub_prop);
char body[96];
int len = snprintf(body, sizeof(body), "{\"ok\":true,\"freeHeap\":%lu}",
(unsigned long)esp_get_free_heap_size());
esp_mqtt_client_publish(client, response_topic, body, len, 1, 0);
ESP_LOGI(TAG, "replied on %s", response_topic);
}
static void mqtt5_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, session present: %d", event->session_present);
esp_mqtt_client_publish(client, PREFIX "/status", "online", 0, 1, 1);
esp_mqtt5_subscribe_property_config_t sub_prop = {
.subscribe_id = 7,
.no_local_flag = true, /* kendi publish'lerimizi geri alma */
};
esp_mqtt5_client_set_subscribe_property(client, &sub_prop);
esp_mqtt_client_subscribe(client, PREFIX "/request", 1);
break;
}
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "data on %.*s: %.*s", event->topic_len, event->topic,
event->data_len, event->data);
print_user_properties(event->property->user_property);
if (event->property->response_topic_len > 0) {
reply_to_request(event);
}
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGW(TAG, "disconnected");
print_user_properties(event->property->user_property);
break;
case MQTT_EVENT_ERROR:
if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) {
ESP_LOGE(TAG, "connection refused, reason code 0x%02x",
event->error_handle->connect_return_code);
}
break;
default:
break;
}
}
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());
esp_mqtt_client_config_t cfg = {
.broker.address.uri = "mqtt://broker.connectmq.com:1883",
.session.protocol_ver = MQTT_PROTOCOL_V_5,
.credentials.client_id = CLIENT_ID,
.session.last_will = {
.topic = PREFIX "/status",
.msg = "offline",
.msg_len = 7,
.qos = 1,
.retain = 1,
},
};
client = esp_mqtt_client_init(&cfg);
esp_mqtt5_client_set_user_property(&connect_property.user_property, connect_user_props,
sizeof(connect_user_props) / sizeof(connect_user_props[0]));
esp_mqtt5_client_set_connect_property(client, &connect_property);
esp_mqtt5_client_delete_user_property(connect_property.user_property);
connect_property.user_property = NULL;
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt5_event_handler, NULL);
esp_mqtt_client_start(client);
for (int seq = 0;; seq++) {
vTaskDelay(pdMS_TO_TICKS(5000));
float temperature = 20.0f + (esp_random() % 500) / 100.0f;
publish_telemetry(temperature, seq);
}
}
Derleyin, yükleyin ve izleyin:
idf.py set-target esp32
idf.py build flash monitor
Özelliklerin Gelişini Görmek
Mosquitto 2.x, protokol sürümü 5 ve ayrıntılı çıktıyla subscribe olduğunuzda MQTT 5 özelliklerini yazdırır:
mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv5 -i viewer-idf5m7 \
-t "connectmq/demo/idf5m7/#" -F "%t %P %p"
%P biçim belirteci user properties'i yazdırır; böylece her telemetri satırı JSON'un yanında schema:telemetry-v2 unit:celsius gösterir. Şimdi request/response'u test edin. Bir terminalde yanıtı dinleyin, diğerinde bir response topic ve correlation data içeren bir istek gönderin:
mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv5 -i reply-idf5m7 \
-t "connectmq/demo/idf5m7/reply/laptop" -v
mosquitto_pub -h broker.connectmq.com -p 1883 -V mqttv5 -i req-idf5m7 \
-t "connectmq/demo/idf5m7/request" -m '{"cmd":"health"}' \
-D publish response-topic "connectmq/demo/idf5m7/reply/laptop" \
-D publish correlation-data "req-001"
ESP32 isteği loglar, correlation data'yı kopyalar ve yanıtı istek sahibinin seçtiği topic'e yayınlar. Bu, bir backend servisinin yanıt topic'lerini koda gömmeden binlerce cihazı çağırmak için kullandığı desenin ta kendisidir. Herkese açık alan paylaşımlıdır ve herkes tarafından okunabilir; bu yüzden onu yalnızca deneyler için kullanın. Ücretsiz herkese açık MQTT broker sayfası, 30 dakikayla sınırlanan session expiry gibi sınırlarını açıklar.
Her Özellik Nasıl Davranır
User properties
User properties, paket başlığındaki anahtar/değer string çiftleridir. Tüketicilerin payload'ı ayrıştırmadan filtreleyebileceği meta veriler için idealdir. Kısa tutun: her mesajla birlikte gönderilirler ve paket boyutuna dahil edilirler.
Message expiry interval
Çevrimdışı bir subscriber için kuyrukta bekleyen 60 saniyelik expiry'ye sahip bir telemetri mesajı, bir dakikadan eski olduğunda broker tarafından atılır. Teslim edildiğinde kalan ömür de iletilir. Asla geç yürütülmemesi gereken "vanayı aç" gibi komutlar için kısa expiry süreleri kullanın.
Topic alias
İstemci topic'i bir kez alias 1 ile gönderir, ardından ona numarasıyla atıfta bulunabilir. esp-mqtt bunun için kendi alias tablosunu tutar. Broker'ın CONNACK paketi kaç alias kabul ettiğini bildirir; bu sayıyı asla aşmayın. Kazanç en çok yavaş veya kotalı bağlantılardaki uzun ISA-95 tarzı topic'lerde önem taşır.
Session expiry ve will gecikmesi
session_expiry_interval = 600 ile broker, bağlantı kesildikten sonra aboneliklerinizi ve kuyruktaki QoS 1 mesajlarını on dakika boyunca tutar; CONNECTED olayındaki session_present bunu yapıp yapmadığını söyler. 15 saniyelik will gecikmesi, kısa bir Wi-Fi kesintisinin ardından yeniden bağlanmanın her dashboard'a anlık bir offline durumu göndermemesi anlamına gelir. ESP32 MQTT Last Will rehberi bunu MQTT 3.1.1 davranışıyla karşılaştırır.
Response topic ve correlation data
Yanıtın nereye gideceğine istek sahibi karar verir ve opak bir correlation değeri ekler; cihaz ise ikisini de basitçe geri yansıtır. Bu, firmware'i genel tutar: hangi servisin veya kullanıcının sorduğunu bilmesi asla gerekmez. Her istek sahibine kendi yanıt topic'ini verin ve correlation data'yı bir sayaç veya UUID gibi kısa tutun, çünkü cihaz onu yanıta kopyalar.
Reason code'lar
MQTT 5, 3.1.1'in birkaç dönüş kodunu publish, subscribe ve disconnect işlemlerini de kapsayan ayrıntılı bir listeyle değiştirir. Bunları loglayın. Loglarında "0x87 not authorized" veya "0x97 quota exceeded" bildiren bir cihaz, sessiz bir yeniden bağlanma döngüsüne kıyasla size saatler kazandırır ve yapılandırma hatalarını ağ sorunlarından ayırt etmenizi sağlar.
Sorun Giderme Kontrol Listesi
esp_mqtt5_*fonksiyonları için link hataları.CONFIG_MQTT_PROTOCOL_5ayarlanmamış.sdkconfigdosyasını silin,sdkconfig.defaultsdosyasını koruyun ve yeniden derleyin.- 0x85 reason code ile bağlantı reddi. Client identifier geçerli değil. Boş olmayan bir ID kullanın; production broker'da kimlik bilgilerinizle birlikte verilen client ID'yi birebir kullanın.
- Çok sayıda publish'ten sonra 0x97 reason code. Kota aşıldı. Yavaşlayın: herkese açık alan istemci başına saniyede 10 mesaja izin verir.
- Topic alias hataları ve bağlantı kopmaları. Broker'ın Topic Alias Maximum değerini aştınız. Daha az alias kullanın veya
topic_aliasdeğerini 0 yapın. - Topic'leri yazdırırken anlamsız karakterler.
event->topicveevent->datanull ile sonlandırılmaz; her zaman%.*sve uzunluk alanlarıyla yazdırın.
Kendi Kimlik Bilgilerinizle TLS'e Geçiş
Production için URI'yi mqtts://broker.connectmq.com:8883 olarak değiştirin, .broker.verification.crt_bundle_attach = esp_crt_bundle_attach ekleyin (esp_crt_bundle.h dahil edin) ve .credentials.username = "YOUR_USERNAME", .credentials.authentication.password = "YOUR_PASSWORD" ile .credentials.client_id = "YOUR_CLIENT_ID" ayarlarını yapın. Sertifika paketi Let's Encrypt kök sertifikasını zaten içerir; bu yüzden PEM dosyasına gerek yoktur. Tüm seçeneklerin listesi ESP-IDF MQTT belgelerinde yer alır.
MQTT 5 Cihazlarınızı ConnectMQ Üzerinde Çalıştırın
Özellikler ancak broker onları uçtan uca uyguladığında işe yarar. ConnectMQ; MQTT 5.0 session expiry, message expiry, topic alias, response topic ve anlaşılır reason code'lar sunan, ayrıca backend tüketicilerini ölçeklemek için ücretli Serverless planında shared subscription desteği bulunan tam yönetilen bir MQTT servisidir. app.connectmq.com adresinde kredi kartı gerektirmeden ücretsiz bir Serverless broker oluşturun ve bu projeyi ona yönlendirin. Daha fazla cihaz tarifi için ESP32 IoT eğitimi serinin tamamını bir araya getirir.
Sık Sorulan Sorular
esp-mqtt her ESP32 çipinde MQTT 5'i destekliyor mu?
Evet. MQTT 5 desteği çipin değil esp-mqtt bileşeninin bir parçasıdır; bu yüzden CONFIG_MQTT_PROTOCOL_5 etkinleştirildiğinde ESP32, S2, S3, C3, C6 ve ESP-IDF sürümünüzün desteklediği diğer hedeflerde çalışır.
Aynı broker'da MQTT 5 ve MQTT 3.1.1 istemcilerini birlikte kullanabilir miyim?
Evet. Her iki sürümü destekleyen bir broker mesajları aralarında iletir. MQTT 3.1.1 subscriber'ları MQTT 5 özelliklerini görmez, o kadar.
Her publish'ten önce özellikleri ayarlamam gerekir mi?
Publish özellikleri bir sonraki publish çağrısına uygulanır; bu yüzden onları her istediğinizde ayarlayın. Connect özellikleri ise istemciyi başlatmadan önce bir kez ayarlanır.
MQTT 5 desteği olan bir Arduino kütüphanesi var mı?
Popüler Arduino MQTT kütüphanelerinin çoğu yalnızca MQTT 3.1.1'i uygular. Bir ESP32'de MQTT 5'e ihtiyacınız varsa en eksiksiz seçenek esp-mqtt ile ESP-IDF'tir ve onu Arduino-as-a-component projelerinden de çağırabilirsiniz.



