Pendahuluan

Di artikel BME280 (#13) kita sudah membaca suhu, kelembaban, dan tekanan lewat I2C, lalu mengirimnya ke broker MQTT. Data itu hanya terlihat di Serial Monitor atau aplikasi subscriber — tidak ada feedback langsung di hardware.

Artikel ini menambahkan layar OLED SSD1306 0,96″ (128×64 piksel) ke node yang sama. Pembaca sensor bisa melihat angka di lokasi tanpa membuka laptop. Karena OLED juga memakai I2C, BME280 dan OLED berbagi bus SDA/SCL — hanya alamat perangkat yang berbeda.

Ini lanjutan Jalur A (hardware & sensor) setelah deep sleep (#11), NVS (#12), dan BME280 (#13).

Prasyarat: Sudah paham GPIO dasar (#3), DHT22 (#5), MQTT (#7), dan I2C + BME280 (#13). Untuk WiFi/NVS/broker auth, baca #12 dan #16.

Yang Kamu Butuhkan

  • ESP32 DevKit
  • Modul sensor BME280 (I2C) — dari artikel #13
  • Modul OLED 0,96″ SSD1306 (I2C, 128×64, 3.3V) — ±Rp 20.000–35.000
  • Breadboard + kabel jumper
  • Broker MQTT — disarankan Mosquitto pribadi (#16) (boleh test.mosquitto.org hanya untuk uji hardware/OLED)

Mengapa OLED SSD1306?

AspekSerial Monitor saja+ OLED SSD1306
Feedback di lapanganButuh laptop/USBLangsung di modul
Konsumsi dayaMinimal±20–40 mA saat layar menyala
ProtokolUARTI2C — satu bus dengan BME280
Use caseDebug developmentPanel sensor dinding, greenhouse, gudang

Catatan daya: Untuk node deep sleep baterai (#11), matikan backlight OLED saat tidur atau pakai layar hanya saat ada interaksi — OLED boros dibanding ESP32 tidur.

Dua Perangkat, Satu Bus I2C

I2C mendukung banyak slave di kabel yang sama. Setiap modul punya alamat unik:

  • BME280 — biasanya 0x76 atau 0x77
  • SSD1306 OLED — biasanya 0x3C (kadang 0x3D)

Karena alamat berbeda, BME280 dan OLED bisa dirakit paralel ke GPIO 21 (SDA) dan GPIO 22 (SCL) tanpa konflik.

ESP32 — Wire + WiFiManager (#12) Wire.begin(21, 22) · PubSubClient · Preferences NVS I2C bus · SDA 21 / SCL 22 BME280 (#13) alamat 0x76 / 0x77 suhu · RH · tekanan OLED SSD1306 alamat 0x3C / 0x3D 128×64 · panel lokal MQTT publish JSON Mosquitto (#16) kodingindonesia/esp32/bme280/data OLED panel angka di lokasi fisik Serial Monitor debug development MQTT Explorer subscriber laptop BME280 + OLED paralel di I2C → ESP32 tampilkan + publish MQTT
Satu bus I2C: BME280 (#13) + OLED SSD1306 → publish ke Mosquitto (#16).

Komponen & Wiring

Hubungkan kedua modul ke ESP32 (paralel di breadboard):

ESP32 DevKit 3.3V GND GPIO 21 SDA GPIO 22 SCL BME280 I2C 0x76 / 0x77 · 3.3V VCC GND SDA SCL OLED SSD1306 I2C 0x3C · 128×64 · 3.3V VCC GND SDA SCL 3.3V → VCC GND → GND GPIO 21 → SDA GPIO 22 → SCL (paralel kedua modul)
Wiring paralel pin-ke-pin: 3.3V→VCC, GND→GND, GPIO 21→SDA, GPIO 22→SCL ke BME280 (#13) dan OLED.
  • Pastikan modul OLED 3.3V — beberapa modul punya jumper VCC/3V3
  • Pin RST OLED boleh tidak di-wire jika library pakai OLED_RESET -1
  • Panjang kabel prototype <30 cm biasanya aman; untuk kabel panjang pertimbangkan pull-up 4.7kΩ di SDA/SCL

Install Library

Pastikan Arduino IDE dan board ESP32 sudah terpasang — ikuti tutorial install Arduino IDE & Board Manager (#2) jika belum.

Arduino IDE → Sketch → Include Library → Manage Libraries:

  • Adafruit SSD1306
  • Adafruit GFX Library (dependency OLED)
  • Adafruit BME280 Library + Adafruit Unified Sensor
  • PubSubClient (Nick O'Leary)
  • WiFiManager (tzapu)

Board: esp32 by Espressif (v3.x). Library Wire, Preferences, dan WiFi sudah built-in.

Topic MQTT (sama #13): kodingindonesia/esp32/bme280/data

Payload JSON: {"suhu":28.5,"kelembaban":65.2,"tekanan":1013.25}

Kode Lengkap: BME280 + OLED + WiFiManager + MQTT

Sketch membaca BME280, menggambar suhu/kelembaban/tekanan di OLED, lalu publish JSON ke broker — pola NVS seperti #12 dan #16.

#include <Wire.h>
#include <WiFi.h>
#include <WiFiManager.h>
#include <Preferences.h>
#include <PubSubClient.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define I2C_SDA 21
#define I2C_SCL 22
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C

const char* NS_KINDO = "kindo";
const int MQTT_PORT = 1883;

Adafruit_BME280 bme;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
WiFiClient espClient;
PubSubClient mqttClient(espClient);
Preferences prefs;

String mqttHost, mqttUser, mqttPass, topicSensor;

WiFiManagerParameter pHost("mqtt_host", "MQTT broker IP", "192.168.1.50", 64);
WiFiManagerParameter pUser("mqtt_user", "MQTT username", "kindo_esp32", 32);
WiFiManagerParameter pPass("mqtt_pass", "MQTT password", "", 48);
WiFiManagerParameter pTopic("mqtt_topic", "MQTT topic", "kodingindonesia/esp32/bme280/data", 64);

bool initI2C() {
  Wire.begin(I2C_SDA, I2C_SCL);
  delay(50);
  return true;
}

bool initBME280() {
  if (bme.begin(0x76)) return true;
  if (bme.begin(0x77)) return true;
  Serial.println("BME280 tidak ditemukan");
  return false;
}

bool initOLED() {
  if (display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) return true;
  if (display.begin(SSD1306_SWITCHCAPVCC, 0x3D)) return true;
  Serial.println("OLED SSD1306 tidak ditemukan");
  return false;
}

void tampilkanOLED(float suhu, float kelembaban, float tekanan) {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println(F("Koding Indonesia"));
  display.println(F("BME280 + OLED"));
  display.drawLine(0, 18, 128, 18, SSD1306_WHITE);
  display.setCursor(0, 24);
  display.printf("Suhu:    %.1f C", suhu);
  display.printf("\nRH:      %.1f %%", kelembaban);
  display.printf("\nTekanan: %.0f hPa", tekanan);
  display.display();
}

void muatMqttDariNvs() {
  prefs.begin(NS_KINDO, true);
  mqttHost    = prefs.getString("mqtt_host", "192.168.1.50");
  mqttUser    = prefs.getString("mqtt_user", "kindo_esp32");
  mqttPass    = prefs.getString("mqtt_pass", "");
  topicSensor = prefs.getString("mqtt_topic", "kodingindonesia/esp32/bme280/data");
  prefs.end();
}

void simpanMqttKeNvs() {
  prefs.begin(NS_KINDO, false);
  prefs.putString("mqtt_host", pHost.getValue());
  prefs.putString("mqtt_user", pUser.getValue());
  prefs.putString("mqtt_pass", pPass.getValue());
  prefs.putString("mqtt_topic", pTopic.getValue());
  prefs.end();
}

bool setupWiFiManager() {
  WiFiManager wm;
  wm.setConfigPortalTimeout(180);
  wm.addParameter(&pHost);
  wm.addParameter(&pUser);
  wm.addParameter(&pPass);
  wm.addParameter(&pTopic);

  muatMqttDariNvs();
  pHost.setValue(mqttHost.c_str(), 64);
  pUser.setValue(mqttUser.c_str(), 32);
  pPass.setValue(mqttPass.c_str(), 48);
  pTopic.setValue(topicSensor.c_str(), 64);

  if (!wm.autoConnect("KindoESP32-Setup")) return false;
  simpanMqttKeNvs();
  return true;
}

bool koneksiMQTT() {
  mqttClient.setServer(mqttHost.c_str(), MQTT_PORT);
  mqttClient.setBufferSize(512);
  String clientId = "ESP32-OLED-" + String(random(0xffff), HEX);
  if (!mqttClient.connect(clientId.c_str(), mqttUser.c_str(), mqttPass.c_str())) {
    Serial.print("MQTT gagal, rc=");
    Serial.println(mqttClient.state());
    return false;
  }
  return true;
}

void bacaDanTampilkan() {
  float suhu = bme.readTemperature();
  float kelembaban = bme.readHumidity();
  float tekanan = bme.readPressure() / 100.0F;

  if (isnan(suhu) || isnan(kelembaban) || isnan(tekanan)) {
    Serial.println("BME280 baca gagal");
    return;
  }

  tampilkanOLED(suhu, kelembaban, tekanan);

  char payload[128];
  snprintf(payload, sizeof(payload),
    "{\"suhu\":%.1f,\"kelembaban\":%.1f,\"tekanan\":%.2f}",
    suhu, kelembaban, tekanan);

  mqttClient.loop();
  if (mqttClient.publish(topicSensor.c_str(), payload, false)) {
    Serial.print("Publish OK → ");
    Serial.println(payload);
  } else {
    Serial.println("Publish gagal");
  }
}

void setup() {
  Serial.begin(115200);
  delay(500);

  initI2C();
  if (!initBME280() || !initOLED()) {
    while (true) delay(1000);
  }

  tampilkanOLED(0, 0, 0);
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 24);
  display.println(F("Menghubungkan..."));
  display.display();

  if (!setupWiFiManager()) ESP.restart();
  if (!koneksiMQTT()) ESP.restart();

  bacaDanTampilkan();
}

void loop() {
  mqttClient.loop();
  delay(5000);
  bacaDanTampilkan();
}

Penjelasan Bagian Kritis

  • Wire.begin(21, 22) sekali — BME280 dan OLED berbagi objek Wire yang sama
  • display.begin(SSD1306_SWITCHCAPVCC, 0x3C) — coba 0x3D jika layar kosong
  • display.display() — wajib dipanggil setelah menggambar; tanpa ini layar tidak berubah
  • display.clearDisplay() — hapus buffer sebelum menggambar frame baru (hindari ghosting)
  • Urutan initWire.begin → BME280 → OLED; jika salah satu gagal, hentikan agar mudah debug
  • mqttClient.loop() — tetap dipanggil di loop() meski fokus artikel ini adalah layar

Uji Coba (Step-by-Step)

  1. Rakit BME280 + OLED paralel di breadboard, upload sketch, Serial Monitor 115200
  2. Pastikan OLED menampilkan teks Menghubungkan... lalu angka sensor
  3. Portal KindoESP32-Setup — isi WiFi + kredensial broker (#16)
  4. Verifikasi angka di OLED ≈ Serial Monitor / MQTT Explorer
  5. Subscribe topic di laptop:
mosquitto_sub -h 192.168.1.50 -p 1883 \
  -u kindo_esp32 -P 'GANTI_PASSWORD_MQTT' \
  -t "kodingindonesia/esp32/bme280/data" -v

Pro tip: Untuk demo, tiup hangat ke BME280 — suhu di OLED naik dalam 2–3 detik. Topic unik per unit, misalnya kodingindonesia/anton/esp32/bme280/data.

Scanner I2C (Opsional)

Jika salah satu modul tidak terdeteksi, jalankan sketch Scan I2C dari artikel #13. Kamu harus melihat dua alamat (misalnya 0x76 + 0x3C).

Tips & Troubleshooting

  • Layar putih/kosong: Cek alamat 0x3C vs 0x3D, wiring 3.3V, dan panggilan display.display()
  • Hanya BME280 terdeteksi: OLED mungkin modul SPI — pastikan beli varian I2C (4 pin: GND VCC SCL SDA)
  • Teks terpotong: Resolusi 128×64 — pakai setTextSize(1); untuk font besar kurangi jumlah baris
  • Ghosting/berbayang: Selalu clearDisplay() sebelum menggambar ulang
  • BME280 gagal setelah pasang OLED: Cek beban 3.3V — dua modul + ESP32; gunakan USB port yang stabil
  • Portal WiFi tidak muncul: wm.resetSettings() atau buka http://192.168.4.1 — sama seperti #12
  • MQTT gagal (rc=-2): Broker tidak terjangkau — cek IP, firewall port 1883, satu jaringan WiFi
  • MQTT auth (rc=5): Lihat troubleshooting broker #16
  • Compile error WiFiManager: Update library tzapu ke 2.x; pastikan board esp32 v3.x
  • Compile error GFX: Install Adafruit GFX versi terbaru sebelum SSD1306
  • WiFi 2.4 GHz: ESP32 tidak support jaringan WiFi 5 GHz saja

Keamanan & Produksi

  • Jangan commit password MQTT ke Git — simpan lewat portal WiFiManager + NVS seperti artikel #12
  • OLED menampilkan data sensor di lokasi fisik — hindari menampilkan kredensial atau token di layar
  • Gunakan broker Mosquitto pribadi + auth (#16) — jangan andalkan test.mosquitto.org untuk data produksi

Langkah Selanjutnya (Seri 2)

Dengan OLED di bus I2C yang sama, node sensormu punya antarmuka lokal selain MQTT cloud. Lanjutkan di halaman artikel Koding Indonesia.