4. ünite / 10
MQTT temelleri: broker, konular ve jokerler
Yayımla-abone ol modeli, broker, istemci kimliği, konu tasarımı, + ve # jokerleri, $SYS ve PubSubClient ile umqtt.simple.
Bu ünitenin sonunda
- Yayımla-abone ol modelini istek-yanıt modeliyle karşılaştırmak
- Bir cihaz filosu için tutarlı bir konu ağacı tasarlamak
- + ve # jokerlerinin hangi konularla eşleştiğini önceden söylemek
- ESP32'de MQTT ile yayımlayan ve komut dinleyen bir istemci yazmak
Ders notları
HTTP'de istemci sunucuya sorar. Peki bir sensör yüz pano ekranına veri göndermek isterse ya da telefon, ağın arkasındaki bir röleye komut vermek isterse? MQTT bunun için tasarlanmış hafif bir protokoldür. Ortada bir broker vardır; cihazlar birbirini tanımaz, yalnız broker'a bağlanır. Yayımlayan bir konuya mesaj gönderir, o konuya abone olan herkes mesajı alır.
Parçalar
- Broker: Mosquitto, EMQX, HiveMQ gibi bir sunucu. Varsayılan port 1883 (açık metin), TLS ile 8883.
- İstemci kimliği: her bağlantının broker'daki adı. Benzersiz olmalı: aynı kimlikle ikinci bir istemci bağlanırsa broker eskisini düşürür.
- Konu: eğik çizgiyle ayrılmış seviyeler, örneğin
sera/01/sicaklik. Önceden tanımlanmaz; ilk yayında var olur. - Yük: bayt dizisi. Metin, JSON ya da ikili olabilir; broker içine bakmaz.
Konu tasarımı
Konu ağacı, filonun adres defteridir. Genelden özele git: yer/cihaz/ölçüm. Komutları ayrı bir dalda tut (sera/01/led/set), durumu ayrı dalda (sera/01/led). Küçük harf, boşluksuz ve başında eğik çizgi olmayan adlar kullan: /sera ile sera farklı konulardır.
Jokerler
Abonelik süzgeci joker içerebilir, yayın konusu içeremez.
+tam olarak bir seviyenin yerini tutar ve seviyenin tamamı olmalıdır:sera/0+geçersizdir.#kalan tüm seviyeleri kapsar, yalnız sonda ve tek başına yazılır; üst seviyenin kendisiyle de eşleşir.$ile başlayan konular ($SYS/broker/uptime) baştaki bir jokerle eşleşmez; broker istatistiklerini bilerek istemelisin.
Açık broker'lar
test.mosquitto.org ve broker.hivemq.com denemek için ücretsizdir ama herkese açıktır: konunu bilen herkes okur ve yazar. Ders denemeleri için benzersiz bir önek seç, gerçek veri ve komut için kendi broker'ını kimlik doğrulama ve TLS ile kur (9. ünite).
Simülasyonlar
Tarayıcıda MQTT broker'ı
Üç simüle cihaz ve bir pano aynı broker'a bağlı. Joker karakterli (+ ve #) abonelikler kur, mesaj yayımla, kalıcı (retained) mesajı ve bir cihazın kablosunu çekince yayımlanan son vasiyeti (LWT) izle.
sera/01/led/set Q1sera/02/fan/set Q1- –Bir şey yayımla ya da cihazlara telemetri gönderttir.
3B akıllı sera: cihazlar, broker ve MQTT paketleri
Seranın 24 saatini kural motoruyla oynat. Sensör düğümü her 10 dakikada sıcaklık yayımlar, eyleyici düğümü durum değiştirince kalıcı durum mesajı yollar; her paket broker'dan geçerek abonelerine uçar. Panodan elle komut gönder, pencere, fan, ısıtıcı ve sulamanın sahnede değiştiğini izle.
3B sahne yükleniyor…
Sürükle: döndür · iki parmak / tekerlek: yakınlaş · sağ tuş: kaydır
- telemetri
- komut
- durum (retained)
- 11:00
sera/01/sicaklik26.8 sensor → broker → actuator, dashboard - 10:50
sera/01/sicaklik26.2 sensor → broker → actuator, dashboard - 10:40
sera/01/sicaklik25.7 sensor → broker → actuator, dashboard - 10:30
sera/01/sicaklik25.1 sensor → broker → actuator, dashboard
Kod
#include <WiFi.h>
#include <PubSubClient.h>
#include "secrets.h" // WIFI_SSID, WIFI_PASS, MQTT_HOST
WiFiClient net;
PubSubClient mqtt(net);
const char* ID = "sera-01"; // broker'da benzersiz olmalı
uint32_t lastPub = 0, nextTry = 0;
void onMessage(char* topic, byte* payload, unsigned int len) {
String msg((char*)payload, len);
if (String(topic) == "sera/01/led/set") digitalWrite(2, msg == "1");
}
void mqttTask() {
if (mqtt.connected()) { mqtt.loop(); return; } // loop() gelen mesajları ve PING'i işler
if (millis() < nextTry) return;
if (mqtt.connect(ID)) mqtt.subscribe("sera/01/+/set");
else nextTry = millis() + 5000; // bloklamadan sonra yeniden dene
}
void setup() {
pinMode(2, OUTPUT);
WiFi.begin(WIFI_SSID, WIFI_PASS);
mqtt.setServer(MQTT_HOST, 1883); // 1883 açık metin; gerçek kurulumda 8883 + TLS
mqtt.setCallback(onMessage);
}
void loop() {
if (WiFi.status() != WL_CONNECTED) return;
mqttTask();
if (mqtt.connected() && millis() - lastPub > 10000) {
lastPub = millis();
mqtt.publish("sera/01/sicaklik", String(temperatureRead(), 1).c_str()); // çip sıcaklığı; DHT22 sonraki ünitelerde
}
}from umqtt.simple import MQTTClient
from machine import Pin
import time, esp32
from secrets import MQTT_HOST
led = Pin(2, Pin.OUT)
def on_msg(topic, msg): # konu ve yük bytes olarak gelir
if topic == b'sera/01/led/set':
led.value(msg == b'1')
c = MQTTClient('sera-01', MQTT_HOST, keepalive=30)
c.set_callback(on_msg)
c.connect()
c.subscribe(b'sera/01/+/set') # + tek seviye: led/set, fan/set…
last = 0
while True:
c.check_msg() # bloklamaz; bekleyen mesaj varsa on_msg çağrılır
if time.ticks_diff(time.ticks_ms(), last) > 10000:
last = time.ticks_ms()
t = (esp32.raw_temperature() - 32) / 1.8
c.publish(b'sera/01/sicaklik', str(round(t, 1)))
time.sleep_ms(50)Kendin dene
Simülasyonda panoyu sera/+/sicaklik konusuna abone et ve üç cihazın yayınlarını izle; sonra süzgeci sera/# yap: neler eklendi? sera/01/kat/sicaklik konusuna elle yayın yap: + süzgeci yakaladı mı? Geçersiz bir süzgeç dene (sera/#/nem). Bir cihazın kablosunu çek ve son vasiyetin hangi abonelere gittiğini gör. Ardından Wokwi'de PubSubClient kodunu test.mosquitto.org ile çalıştır, kendi benzersiz önekini kullan ve bir MQTT istemcisinden sera/01/led/set konusuna 1 gönder.
Sık yapılan hatalar
- İki cihaza aynı istemci kimliğini vermek: biri bağlanınca öbürü düşer, sonsuz bir bağlan-düş döngüsü oluşur.
- Konunun başına eğik çizgi koymak (/sera/01): sera/01 ile aynı konu değildir, abonelik eşleşmez.
- Üretimde # ile her şeye abone olmak: istemci gereksiz trafikle boğulur ve başka cihazların verisini görür.
- Açık bir test broker'ına gerçek veri ya da komut göndermek: herkes okuyup yazabilir.
- mqtt.loop() çağırmayı unutmak: gelen mesajlar işlenmez ve keepalive PING'i gitmez, bağlantı kopar.
Yazılım mühendisliği bağlantısı
Yayımla-abone ol modeli gevşek bağlılık sağlar: sensör, verisini kimin kullandığını bilmez.
- Konu ağacını bir arayüz belgesi gibi yönet: her konunun yükü, birimi, sıklığı ve sahibi yazılı olsun. Konu adını değiştirmek, bir API'den alan silmek kadar kırıcıdır.
- Yeni bir tüketici (veri kaydedici, ML modeli, pano) eklemek cihaz yazılımına dokunmadan olur: yalnız abone olur.
- Simülasyondaki
topicMatchessaf bir fonksiyondur; joker kurallarını tablo tabanlı testlerle sınamak kolaydır.
Kendini sına
sera/+/sicaklik süzgeci hangi konuyla eşleşir?
Hangi abonelik süzgeci geçersizdir?
İki cihaz aynı istemci kimliğiyle aynı broker'a bağlanıyor. Ne olur?
# süzgeciyle abone olan bir istemci $SYS/broker/uptime mesajlarını alır mı?
