7. ünite / 10
Uzaktan eyleyiciler ve canlı pano
Röle, MOSFET ve servoyu MQTT ile güvenle sür: idempotent komut, komut kimliği, cihaz gölgesi; telemetriyi canlı panoda çiz.
Bu ünitenin sonunda
- "Çevir" ile "ayarla" komutlarının yinelenen teslimde farkını göstermek
- Komut kimliği ve kalıcı durum konusuyla istenen/bildirilen gölgeyi kurmak
- Halka tampon ve min/max seyreltmeyle sabit bellekli canlı grafik çizmek
- Histerezisli eşik uyarısının neden titreşmediğini açıklamak
Ders notları
Sensör okumak tek yönlüdür; bir fanı, pencereyi ya da pompayı uzaktan sürmek ise iki yönlü ve risklidir. Komut kaybolabilir, iki kez gelebilir, cihaz o anda kapalı olabilir. Bu ünitede komutları bu durumların hepsine dayanıklı yapacağız.
Eyleyici donanımı
- MOSFET (IRF540): DC fan ya da LED şeridi için düşük taraf anahtar. IRF540 3,3 V kapı geriliminde tam açılmaz; mantık seviyeli bir MOSFET ya da sürücü transistör kullan, endüktif yükte flyback diyot koy.
- Röle modülü: şebeke yükü için yalıtım sağlar ama mekaniktir; ömrü anahtarlama sayısıyla sınırlıdır.
- Servo (SG90): 50 Hz PWM, 0,5–2,5 ms darbe; pencere açısı gibi mutlak konum komutları için doğaldır.
İdempotent komut
QoS 1 "en az bir kez" teslim eder: PUBACK kaybolursa mesaj ikinci kez gelir. {"cmd":"toggle"} iki kez uygulanınca fan istenen durumun tersinde kalır. {"on":true} ise kaç kez gelirse gelsin aynı sonucu verir: buna idempotent denir. Bir adım ileri: her komuta artan bir id ver, cihaz son uyguladığı kimliği saklasın ve küçük ya da eşit kimliği yok saysın.
Cihaz gölgesi iki durum tutar: pano "istenen"i (sera/01/fan/set) yazar, cihaz gerçekten uyguladığını "bildirilen" olarak kalıcı (retained) bir konuya (sera/01/fan/state) yayımlar. Fark boş değilse komut henüz uygulanmamıştır; pano düğmeyi "bekliyor" gösterir, "açık" değil.
Canlı pano
Cihazda ya da tarayıcıda sınırsız dizi tutulamaz. Halka tampon sabit kapasitelidir, en eski değer ezilir. Ekran 300 piksel genişse 10 000 noktayı çizmek anlamsızdır; min/max seyreltme her kovadan en küçük ve en büyük değeri alır, böylece kısa bir tepe kaybolmaz (basit ortalama onu siler). Uyarı için histerezis kullan: 30 °C üstünde aç, 28 °C altında kapat; tek eşikte değer 30 civarında gezinirken uyarı saniyede birkaç kez açılıp kapanır. Gerçek projede bu panoyu Node-RED ya da Grafana ile de kurabilirsin; ilkeler aynıdır.
Simülasyonlar
Uzaktan eyleyici: idempotent komut ve cihaz gölgesi
Kayıplı bir bağlantıda röleye 20 komut gönder. "Çevir" (toggle) komutu yinelenen teslimde durumu bozar; "ayarla" (set) ve komut kimliği bozmaz. Altta istenen/bildirilen gölge farkını gör.
| Komut | Son röle | Beklenen | Uygulanan | Sonuç |
|---|---|---|---|---|
| toggle ("çevir") | KAPALI | KAPALI | 24 | doğru |
| set {"on": true/false} | KAPALI | KAPALI | 24 | doğru |
| set + komut kimliği | KAPALI | KAPALI | 20 | doğru |
Cihaz gölgesi
Canlı pano: halka tampon, seyreltme, uyarı
Üç cihazdan gelen telemetriyi canlı çiz. Halka tampon boyutunu ve ekrana çizilecek nokta sayısını değiştir; min/max seyreltmenin tepeleri koruduğunu ve histerezisli eşik uyarısını izle.
Kod
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
const int RELAY = 26; // IRF540 kapısı ya da röle modülü girişi
WiFiClient net;
PubSubClient mqtt(net);
uint32_t lastCmdId = 0; // aynı kimliği ikinci kez uygulama
void report(bool on) {
// Bildirilen durum kalıcı (retained): yeni bağlanan pano hemen görür
mqtt.publish("sera/01/fan/state", on ? "{\"on\":true}" : "{\"on\":false}", true);
}
void onMsg(char* topic, byte* data, unsigned int len) {
JsonDocument doc;
if (deserializeJson(doc, data, len)) return; // bozuk JSON: yok say
uint32_t id = doc["id"] | 0;
if (id != 0 && id <= lastCmdId) return; // yinelenen ya da eski komut
lastCmdId = id;
bool on = doc["on"] | false; // SET: mutlak değer, toggle değil
digitalWrite(RELAY, on ? HIGH : LOW);
report(on); // gerçekten uygulanan durumu bildir
}
void setup() {
pinMode(RELAY, OUTPUT);
digitalWrite(RELAY, LOW); // güvenli varsayılan: kapalı
mqtt.setCallback(onMsg);
}
void loop() {
if (!mqtt.connected() && mqtt.connect("sera-01")) mqtt.subscribe("sera/01/fan/set", 1);
mqtt.loop();
}import json
from machine import Pin, PWM, I2C
from umqtt.simple import MQTTClient
import ssd1306
servo = PWM(Pin(18), freq=50) # SG90: 50 Hz, 0,5 ile 2,5 ms darbe
oled = ssd1306.SSD1306_I2C(128, 64, I2C(0, scl=Pin(22), sda=Pin(21)))
buf, cap, head = [0.0] * 128, 128, 0 # halka tampon: sabit bellek
def on_msg(topic, msg):
global head
m = json.loads(msg)
if topic == b"sera/01/pencere/set":
aci = max(0, min(90, m["aci"])) # sınırla: mekanik sonu zorlama
servo.duty_ns(500_000 + aci * 2_000_000 // 180)
elif topic == b"sera/01/telemetri":
buf[head] = m["t"]; head = (head + 1) % cap # en eski değer ezilir
oled.fill(0)
for x in range(cap):
v = buf[(head + x) % cap]
oled.pixel(x, 63 - int((v - 10) * 2), 1) # 10..40 °C ekrana
oled.show()
c = MQTTClient("pano-01", "broker.local")
c.set_callback(on_msg); c.connect()
c.subscribe(b"sera/01/#")
while True:
c.wait_msg()Kendin dene
Eyleyici simülasyonunda kayıp oranını %30 yap ve "çevir" komutuyla 20 komut gönder: son durum beklenenle aynı mı, kaç yinelenen teslim oldu? Aynısını "ayarla" ve "ayarla + kimlik" ile tekrarla. Pano simülasyonunda halka tamponu küçült ve çizilen nokta sayısını 30'a indir: tepe hâlâ görünüyor mu? Uyarı kapanma eşiğini açma eşiğine yaklaştır ve titreşimi izle. Wokwi'de ESP32 + LED + servo kur ve komutu MQTT ile gönder.
Sık yapılan hatalar
- QoS 1 ile "toggle" komutu göndermek: tek bir yinelenen teslim durumu tersine çevirir.
- Panoda düğmeyi komut gönderilir gönderilmez "açık" göstermek: cihaz uygulamadan durum değişmiş görünür.
- Bildirilen durumu kalıcı (retained) yayımlamamak: sonradan açılan pano cihazın durumunu bilemez.
- IRF540'ı doğrudan 3,3 V GPIO ile sürmek: tam açılmaz, ısınır.
- Grafikte seyreltme için ortalama almak: kısa süreli tepeler ve arızalar görünmez olur.
Yazılım mühendisliği bağlantısı
Uzaktan komut, dağıtık sistemlerdeki "tam olarak bir kez" sorununun küçük bir örneğidir. Ağ bunu garanti etmez; uygulama idempotentlikle sağlar.
- Komut işleyicisini saf bir fonksiyon olarak yaz: (durum, komut) → yeni durum. "Aynı komutu iki kez uygula = bir kez uygula" bir özellik testidir.
- Pano ile cihaz arasındaki konu adları ve yük şeması bir
READMEya da AsyncAPI belgesinde yazılı olsun; iki taraf da ona göre test edilir.
Kendini sına
QoS 1 ile gönderilen "toggle" komutunun PUBACK'i kayboldu. Ne olur?
Cihaz gölgesinde "istenen" fan=1, "bildirilen" fan=0. Panoda ne gösterilmeli?
Ekranda 10 000 noktayı 300 kovaya indirirken kısa bir sıcaklık tepesini korumak için hangisi?
Uyarı 30 °C'de açılıyor. Kapanma eşiği neden 28 °C gibi daha düşük seçilir?
Bu üniteden sonra bitirme projesinde
- Uçtan uca ilk sürüm: Sensör düğümü yayımlıyor, eyleyici düğümü idempotent komutla röle ve servoyu sürüyor, pano canlı çiziyor (Wokwi ya da gerçek kart).
