ESP32 ve Arduino

ESP32 OLED MQTT: Canlı MQTT Mesajlarını SSD1306 Ekranda Gösterin

Bir ESP32 ve ucuz bir SSD1306 OLED'i, herhangi bir cihazdan gelen sensör değerlerini ve mesajları gösteren, eskimiş okumaları işaretleyen ve kendi bağlantı durumunu gösteren canlı bir MQTT ekranına dönüştürün.

Bir esp32 oled mqtt ekranında sıcaklık değerlerini gösteren, ESP32'ye bağlı küçük SSD1306 ekran
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

Bir ESP32 OLED MQTT ekranı, telefon veya tarayıcı olmadan bir masaya, atölye tezgâhına veya makine paneline canlı veri koymanın en basit yoludur. Bu eğitimde broker.connectmq.com üzerinde birkaç topic'e abone olacak, en son değerleri 128×64 bir SSD1306 ekranda gösterecek, bir sensör rapor vermeyi bıraktığında okumaları eskimiş olarak işaretleyecek ve ekranın kendi Wi-Fi ve MQTT durumunu göstereceksiniz. Aynı sketch özel kimlik bilgilerine geçtiğinizde değişmeden çalışır.

Ekran Neyi Gösterir?

İyi bir mini dashboard bir bakışta üç soruyu yanıtlar: değer nedir, ne kadar eskidir ve ekranın kendisi bağlı mı? Burada kullanılan düzen, üçünü de 0,96 inçlik bir ekrana sığdırır:

+--------------------------------+
| MQTT  OK          RSSI -58     |
| Temp      23.4 C               |
| Humidity  48 %                 |
| Door      CLOSED       (12s)   |
| Msg: Coffee is ready           |
+--------------------------------+

Her satır bir topic'e bağlıdır. Ekran tek bir wildcard ile abone olur ve gelen mesajları son topic seviyesini eşleştirerek satırlara yönlendirir.

Topic Satır Örnek payload
connectmq/demo/<id>/temp Temp 23.4
connectmq/demo/<id>/humidity Humidity 48
connectmq/demo/<id>/door Door CLOSED
connectmq/demo/<id>/msg Msg Herhangi bir kısa metin

Gerçek bir kurulumda değerler başka cihazlardan gelir: sıcaklık ve nem yayınlayan DHT22 sensörlü bir ESP32, bir kapı kontağı veya bildirim gönderen bir betik. Ekran kimin yayınladığıyla ilgilenmez; publish/subscribe modelinin ana gücü de budur. Bu ayrışma sizin için yeniyse başlamadan önce MQTT broker'ın ne yaptığını okuyun.

Donanım ve Kablolama

  • ESP32 geliştirme kartı.
  • 128×64 piksel, I2C sürümü SSD1306 OLED modülü (dört pin: GND, VCC, SCL, SDA).
  • Dört jumper kablo.
OLED pini ESP32 pini
GND GND
VCC 3V3
SCL GPIO 22
SDA GPIO 21

GPIO 21 ve 22, klasik ESP32 DevKit kartlarında varsayılan I2C pinleridir. ESP32-S3 veya C3 gibi diğer varyantlar farklı varsayılanlar kullanır; bu yüzden sketch'in yaptığı gibi pinlerinizi Wire.begin(SDA, SCL) fonksiyonuna açıkça verin. Modüllerin çoğu 0x3C I2C adresini kullanır; birkaçı 0x3D kullanır. Ekran siyah kalırsa adresi doğrulamak için bir I2C tarayıcı sketch'i çalıştırın.

Kütüphaneler

Arduino IDE'den daha önce hiç ESP32 yüklemediyseniz önce ESP32 IoT eğitimindeki kart kurulumunu izleyin; esp32 kart paketini kurmayı, doğru kartı ve portu seçmeyi ve seri monitörü okumayı kapsar, bunların hepsine burada ihtiyacınız olacak. Ardından Arduino Library Manager üzerinden kurun:

  • Nick O'Leary'nin PubSubClient kütüphanesi.
  • Adafruit GFX Library ve Adafruit BusIO kütüphanelerini de getiren Adafruit'in Adafruit SSD1306 kütüphanesi.

ESP32 OLED MQTT Sketch'i

Temel tasarım kuralı: MQTT callback'i içinde asla çizim yapmayın. Callback yalnızca değeri ve bir zaman damgasını saklar ve bir bayrak ayarlar. Çizim loop() içinde sabit bir hızda yapılır. Bu, callback'i hızlı tutar, yarım güncellenmiş ekranları önler ve hiç mesaj gelmediğinde bile ekranın "yaş" sayaçlarını yenilemesini sağlar.

#include <WiFi.h>
#include <Wire.h>
#include <PubSubClient.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

const char* WIFI_SSID     = "YOUR_WIFI";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST     = "broker.connectmq.com";
const uint16_t MQTT_PORT  = 1883;
const char* CLIENT_ID     = "oled-5d20ab";             // benzersiz
const char* BASE          = "connectmq/demo/oled-5d20ab";

const unsigned long STALE_MS = 120000;                  // 2 dakika

Adafruit_SSD1306 display(128, 64, &Wire, -1);
WiFiClient net;
PubSubClient mqtt(net);

struct Row {
  const char* key;       // son topic seviyesi
  const char* label;
  const char* unit;
  String value;
  unsigned long at;      // alındığı andaki millis(), 0 = hiç
};

Row rows[] = {
  {"temp",     "Temp",     " C", "", 0},
  {"humidity", "Humidity", " %", "", 0},
  {"door",     "Door",     "",   "", 0},
  {"msg",      "Msg:",     "",   "", 0},
};
const int ROW_COUNT = sizeof(rows) / sizeof(rows[0]);
bool dirty = true;
unsigned long lastDraw = 0, lastAttempt = 0;

void onMessage(char* topic, byte* payload, unsigned int length) {
  const char* leaf = strrchr(topic, '/');
  if (!leaf) return;
  leaf++;
  for (int i = 0; i < ROW_COUNT; i++) {
    if (strcmp(leaf, rows[i].key) == 0) {
      String v;
      for (unsigned int j = 0; j < length && j < 40; j++) v += (char)payload[j];
      rows[i].value = v;
      rows[i].at = millis();
      dirty = true;
      return;
    }
  }
}

String ageText(unsigned long at) {
  unsigned long s = (millis() - at) / 1000;
  if (s < 60) return String(s) + "s";
  if (s < 3600) return String(s / 60) + "m";
  return String(s / 3600) + "h";
}

void draw() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  // Durum çubuğu
  display.setCursor(0, 0);
  display.print("MQTT ");
  display.print(mqtt.connected() ? "OK" : (WiFi.status() == WL_CONNECTED ? "..." : "NO WIFI"));
  if (WiFi.status() == WL_CONNECTED) {
    display.setCursor(80, 0);
    display.print("RSSI ");
    display.print(WiFi.RSSI());
  }
  display.drawFastHLine(0, 10, 128, SSD1306_WHITE);

  int y = 14;
  for (int i = 0; i < ROW_COUNT; i++) {
    display.setCursor(0, y);
    display.print(rows[i].label);
    display.setCursor(i == 3 ? 30 : 60, y);
    if (rows[i].at == 0) {
      display.print("--");
    } else {
      bool stale = millis() - rows[i].at > STALE_MS;
      String text = rows[i].value + rows[i].unit;
      if (i == 3) text = text.substring(0, 16);         // mesaj satırına sığdır
      display.print(text);
      if (stale && i != 3) {
        display.setCursor(104, y);
        display.print(ageText(rows[i].at));             // yaşı yalnızca eskidiğinde göster
      }
    }
    y += 12;
  }
  display.display();
}

bool connectMqtt() {
  if (!mqtt.connect(CLIENT_ID)) {
    Serial.printf("MQTT failed, state=%d\n", mqtt.state());
    return false;
  }
  String filter = String(BASE) + "/+";
  mqtt.subscribe(filter.c_str(), 1);
  Serial.println("Subscribed to " + filter);
  return true;
}

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("SSD1306 not found");
    while (true) delay(1000);
  }
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.print("Connecting...");
  display.display();

  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
  mqtt.setSocketTimeout(5);
}

void loop() {
  if (WiFi.status() == WL_CONNECTED) {
    if (!mqtt.connected()) {
      if (millis() - lastAttempt > 5000) { lastAttempt = millis(); connectMqtt(); }
    } else {
      mqtt.loop();
    }
  }
  // Yeni veri geldiğinde ve durum ile yaşları güncellemek için saniyede bir yeniden çiz
  if (dirty || millis() - lastDraw > 1000) {
    draw();
    dirty = false;
    lastDraw = millis();
  }
}

Retained message'lar bir ekran için neden önemli?

ESP32 açıldığında hiçbir şey bilmez. Sensörler okumalarını retained message olarak yayınlarsa, broker ekran abone olduğu anda her topic'in en son değerini ona iletir ve ekran bir saniye içinde dolar. Retained message'lar olmadan satırlar, her sensörün bir sonraki yayınına kadar -- gösterir; bu da pilli bir cihaz için dakikalar sürebilir. Yayıncıları siz kontrol ediyorsanız sıcaklık, kapı durumu ve son bildirim gibi yavaş değişen değerleri retained yapın.

Komut Satırından Test Edin

Sensörlermişsiniz gibi birkaç değer yayınlayın:

mosquitto_pub -h broker.connectmq.com -p 1883 -i pub-5d20ab -r -t "connectmq/demo/oled-5d20ab/temp" -m "23.4"
mosquitto_pub -h broker.connectmq.com -p 1883 -i pub-5d20ab -r -t "connectmq/demo/oled-5d20ab/humidity" -m "48"
mosquitto_pub -h broker.connectmq.com -p 1883 -i pub-5d20ab -r -t "connectmq/demo/oled-5d20ab/door" -m "CLOSED"
mosquitto_pub -h broker.connectmq.com -p 1883 -i pub-5d20ab -t "connectmq/demo/oled-5d20ab/msg" -m "Coffee is ready"

Şimdi ESP32'nin reset düğmesine basın. İlk üçü -r ile gönderildiği için yeniden bağlandıktan hemen sonra tekrar görünürler; mesaj satırı ise retained olmadığı için boş kalır. Yayın yapmadan iki dakika bekleyin; her eskimiş değerin yanında yaş göstergesi belirir.

Retained bir değeri temizlemek için o topic'e boş bir retained mesaj yayınlayın:

mosquitto_pub -h broker.connectmq.com -p 1883 -i pub-5d20ab -r -n -t "connectmq/demo/oled-5d20ab/door"

Herkese açık MQTT test broker'ındaki retained veriler abone olan herkes tarafından görülebilir; bu yüzden orada zararsız demo değerleriyle sınırlı kalın.

Paylaşılan Bir Ekran İçin Topic Seçimi

Demo, örneği kendi içinde tutmak için her satırı ekranın kendi öneki altına koyar. Gerçek bir kurulumda topic'ler genellikle sensörlere aittir; örneğin bir cihazdan home/livingroom/temp, başka birinden home/garage/door. İki seçeneğiniz vardır. Birincisi, her topic'e açıkça abone olmak ve tam topic string'lerini satırlara eşlemektir; bu en öngörülebilir yaklaşımdır ve topic'ler ağacın farklı bölümlerinden geldiğinde kullanılmalıdır. İkincisi, sensörlerin veya küçük bir Node-RED akışının yeniden yayınladığı bir display/ dalı gibi bir kural üzerinde anlaşmaktır. Bu, ekran firmware'ini genel tutar ve bir satır eklemek kod değişikliği yerine bir yapılandırma değişikliği olur. Hangisini seçerseniz seçin, herkese açık alanın client başına 10 aboneliğe izin verdiğini unutmayın; birkaç açık topic sorun değildir, ama onlarcası olur.

Küçük Ekranlarda Okunabilirlik İpuçları

  1. En önemli tek sayı için 2 boyutlu metin kullanın. 128×64 bir ekran 2 boyutunda yaklaşık dört satır metin alır; bu da büyük bir okuma ve bir durum satırı için yeterlidir.
  2. Sayıları sağa hizalayın; böylece değerler değiştikçe rakamlar yer değiştirmez. Metin genişliğini display.getTextBounds() ile ölçün.
  3. Yuvarlamayı ekranda değil, yayıncıda yapın. 23.41678 yerine 23.4 göndermek bayttan ve ekran alanından tasarruf sağlar.
  4. Eskimeyi açıkça gösterin. Güncel görünen donmuş bir değer boş bir değerden daha kötüdür. Yaş sayacı en ucuz dürüst göstergedir.
  5. Ekran yanmasına dikkat edin. OLED pikselleri kullanımla yaşlanır. Sürekli açık paneller için display.dim(true) ile kontrastı düşürün veya düzeni birkaç dakikada bir bir piksel kaydırın.

Sorun Giderme

  • Siyah ekran, hata yok. Yanlış I2C adresi veya SDA/SCL yer değiştirmiş. Bir I2C tarayıcı çalıştırın.
  • S3 veya C3 kartında "SSD1306 not found". Varsayılan pinler farklıdır; doğru olanları Wire.begin() fonksiyonuna verin.
  • Kaymış görüntüyle bozuk metin. 128×32 bir modülünüz olabilir; constructor'daki yüksekliği değiştirin.
  • Durum OK gösteriyor ama değerler hiç güncellenmiyor. Topic uyuşmazlığı. + wildcard'ı tam olarak bir seviyeyle eşleşir, bu yüzden connectmq/demo/oled-5d20ab/sensors/temp eşleşmez. Hata ayıklarken # ile abone olun.
  • Ekran saniyelerce donuyor. Engelleyici bir yeniden bağlanma. Soket zaman aşımını düşürün veya ESP32 MQTT yeniden bağlanma mantığı yazısındaki engellemeyen geri çekilme desenini kullanın.

Demo Panelinden Kalıcı Ekrana

Atölye duvarında asılı duran bir ekran herkese açık bir test broker'ına bağlı olmamalıdır. ConnectMQ portalında bir broker oluşturun, üretilen kullanıcı adı, parola ve client ID ile 8883 portunda TLS üzerinden bağlanın ve tüm sensörlerinizi aynı uç noktaya yönlendirin. Bir bulut MQTT broker, evdeki panelin hiçbir port yönlendirmesi olmadan şehrin öbür ucundaki bir seradan okumaları göstermesini sağlar.

ConnectMQ hata ayıklamayı da kolaylaştırır: canlı topic gezgini hangi topic'lerin var olduğunu ve her birinde neyin retained olduğunu tam olarak gösterir; bu da "bu satır neden boş?" sorusunu genellikle saniyeler içinde yanıtlar. Metnin yanında daha görsel bir çıktı için OLED'i renk kodlu bir durum ışığı olarak MQTT ile kontrol edilen bir WS2812B LED şeritle birleştirin.

ConnectMQ ile Başlayın

app.connectmq.com adresini açın, ücretsiz Serverless planı seçin (kredi kartı gerekmez) ve bir broker oluşturun. Ekranınız, sensörleriniz ve telefonunuz böylece tek bir güvenli uç noktayı paylaşabilir ve siz geliştirirken her mesajı portalda izleyebilirsiniz.

Sık Sorulan Sorular

OLED ekran diğer cihazlardan gelen topic'lere abone olabilir mi?

Evet. MQTT'nin amacı da budur: herhangi bir cihaz bir topic'e yayın yapabilir ve bu ekran dahil istediğiniz sayıda abone onu alır. Yalnızca topic adlarının eşleşmesi gerekir.

OLED'e neden MQTT callback'i içinde çizim yapmamalıyım?

I2C üzerinden çizim birkaç milisaniye sürer ve birkaç mesaj bir anda art arda gelebilir. Değerleri callback'te saklayıp loop() içinde sabit bir hızda çizmek, client'ı duyarlı ve ekranı tutarlı tutar.

Bu SH1106 ekranlarla çalışır mı?

1,3 inçlik modüller genellikle bunun yerine SH1106 denetleyicisini kullanır. U8g2 veya Adafruit sürücüsünün bir SH1106 fork'u gibi onu destekleyen bir kütüphane kullanın; sketch'in MQTT kısmı aynı kalır.

JSON payload'daki bir değeri nasıl gösteririm?

Payload'u callback içinde ArduinoJson ile ayrıştırın, ihtiyacınız olan alanı çıkarın ve satır değeri olarak saklayın. Callback'teki ayrıştırmayı kısa, çizimi ise loop() içinde tutun.

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