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ı
- 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.
- 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. - Yuvarlamayı ekranda değil, yayıncıda yapın.
23.41678yerine23.4göndermek bayttan ve ekran alanından tasarruf sağlar. - 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.
- 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üzdenconnectmq/demo/oled-5d20ab/sensors/tempeş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.



