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.

Komponen & Wiring

Hubungkan kedua modul ke ESP32 (paralel di breadboard):

ESP32 DevKit          BME280 + OLED (paralel)
─────────────         ─────────────────────
3.3V          ─────── VCC (kedua modul)
GND           ─────── GND (kedua modul)
GPIO 21 (SDA) ─────── SDA (kedua modul)
GPIO 22 (SCL) ─────── SCL (kedua modul)
  • 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 'KindoMQTT2026!' \
  -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)

  • OTA update firmware — update tanpa kabel USB (butuh #12 WiFiManager)
  • Gabung deep sleep (#11) + BME280 + OLED untuk node lapangan (matikan OLED saat tidur)
  • Subscriber Python → MySQL (#18) untuk histori + OLED sebagai panel lokal
  • Capstone greenhouse (#39) — sensor + layar + pompa relay

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