// ===================== 1. KREDENSIAL BLYNK & WI-FI ====================
// Ganti tiga baris ini dengan milik Anda (lihat Blynk > Device > Firmware Configuration)
#define BLYNK_TEMPLATE_ID "TMPL6OqbcItot"
#define BLYNK_TEMPLATE_NAME "Project IOT"
#define BLYNK_AUTH_TOKEN "pjWDKo0VoGoyy55OZ27P4tMBaotyeCRD"
#define BLYNK_PRINT Serial // tampilkan log koneksi Blynk di Serial Monitor
#include <WiFi.h>
#include <BlynkSimpleEsp32.h>
#include <LiquidCrystal_I2C.h>
char ssid[] = "Wokwi-GUEST"; // Wi-Fi bawaan Wokwi
char pass[] = "";
// ===================== 2. PIN ==========================================
const int PIN_SENSOR_ARUS = 34; // potensiometer (ADC1, aman dipakai bersama Wi-Fi)
const int PIN_RELAY = 26; // relay beban non-esensial
const int PIN_BUZZER = 27;
// Jika relay di Wokwi bereaksi terbalik, ubah HIGH menjadi LOW
const int RELAY_AKTIF = HIGH;
// ===================== 3. PARAMETER SISTEM =============================
const float DAYA_MAKS_W = 2500.0; // potensiometer penuh = 2500 W
const float BEBAN_ESENSIAL_W = 200.0; // daya sisa saat beban non-esensial diputus
const float TOKEN_AWAL_KWH = 12.0; // saldo awal saat ESP32 menyala
const float TOKEN_MAKS_KWH = 50.0; // batas atas saldo (sesuai batas datastream)
const float BATAS_WASPADA_KWH = 10.0;
const float BATAS_KRITIS_KWH = 3.0;
// Percepatan waktu agar demo singkat: 1 detik nyata = 600 detik simulasi (10 menit).
// Untuk pemakaian sungguhan, ubah menjadi 1.0
const float FAKTOR_PERCEPATAN = 600.0;
const unsigned long INTERVAL_HITUNG_MS = 1000; // hitung & logika tiap 1 detik
const unsigned long INTERVAL_KIRIM_MS = 3000; // kirim ke Blynk tiap 3 detik (hemat kuota pesan)
const unsigned long DURASI_ALARM_MS = 15000; // buzzer berbunyi 15 detik tiap level naik
// ===================== 4. VARIABEL =====================================
enum Status { AMAN, WASPADA, KRITIS, HABIS };
const char* NAMA_STATUS[] = { "AMAN", "WASPADA", "KRITIS", "HABIS" };
float sisaKwh = TOKEN_AWAL_KWH;
float dayaTerukur = 0; // daya yang benar-benar mengalir (W)
float dayaRata = 0; // rata-rata bergerak, dipakai untuk estimasi
bool bebanOn = true; // status relay beban non-esensial
Status statusToken = AMAN;
unsigned long alarmSampai = 0;
unsigned long waktuHitungTerakhir = 0;
BlynkTimer timer;
LiquidCrystal_I2C lcd(0x27, 16, 2);
// ===================== 5. BUZZER =======================================
// Kode ini jalan di Arduino-ESP32 versi 2.x maupun 3.x
void siapkanBuzzer() {
#if ESP_ARDUINO_VERSION_MAJOR >= 3
ledcAttach(PIN_BUZZER, 2000, 8);
#else
ledcSetup(0, 2000, 8);
ledcAttachPin(PIN_BUZZER, 0);
#endif
}
void setNada(int frekuensi) { // 0 = diam
#if ESP_ARDUINO_VERSION_MAJOR >= 3
ledcWriteTone(PIN_BUZZER, frekuensi);
#else
ledcWriteTone(0, frekuensi);
#endif
}
// Pola bunyi berbeda tiap level, tanpa delay() agar program tidak tersendat
void perbaruiBuzzer() {
static int frekuensiSekarang = -1;
int target = 0;
unsigned long t = millis();
if ((long)(alarmSampai - t) > 0) {
switch (statusToken) {
case WASPADA: if (t % 3000 < 150) target = 1500; break; // bip pendek tiap 3 detik
case KRITIS: if (t % 600 < 300) target = 2500; break; // bip cepat
case HABIS: if (t % 1000 < 800) target = 900; break; // nada rendah panjang
default: break;
}
}
if (target != frekuensiSekarang) {
setNada(target);
frekuensiSekarang = target;
}
}
// ===================== 6. SENSOR & PERHITUNGAN =========================
// Membaca daya beban (W). Di dunia nyata: ganti dengan pembacaan ACS712/PZEM.
float bacaDayaBeban() {
long total = 0;
for (int i = 0; i < 10; i++) total += analogRead(PIN_SENSOR_ARUS); // rata-rata 10 sampel
float adc = total / 10.0;
float watt = adc * DAYA_MAKS_W / 4095.0;
return (watt < 5.0) ? 0.0 : watt; // abaikan derau kecil
}
Status hitungStatus(float kwh) {
if (kwh <= 0) return HABIS;
if (kwh <= BATAS_KRITIS_KWH) return KRITIS;
if (kwh <= BATAS_WASPADA_KWH) return WASPADA;
return AMAN;
}
// "Jika beban sekarang terus menyala, token habis dalam ...". Satuan waktu = waktu rumah sungguhan.
String formatEstimasi(float kwh, float watt) {
if (watt < 1.0) return ">30 hari";
float jam = kwh / (watt / 1000.0);
if (jam > 720.0) return ">30 hari";
int j = (int)jam;
if (j >= 24) return String(j / 24) + " hari " + String(j % 24) + " jam";
int m = (int)((jam - j) * 60.0);
return String(j) + " jam " + String(m) + " mnt";
}
String teksStatusDashboard() {
if (statusToken == HABIS) return "HABIS | isi token sekarang!";
return String(NAMA_STATUS[statusToken]) + " | habis dlm " + formatEstimasi(sisaKwh, dayaRata);
}
// ===================== 7. AKTUATOR & TAMPILAN ==========================
void terapkanRelay() {
digitalWrite(PIN_RELAY, bebanOn ? RELAY_AKTIF : !RELAY_AKTIF);
}
void perbaruiLcd() {
char baris1[17], baris2[17];
snprintf(baris1, sizeof(baris1), "Token %6.2f kWh", sisaKwh);
snprintf(baris2, sizeof(baris2), "%4.0fW %-7s", dayaTerukur, NAMA_STATUS[statusToken]);
lcd.setCursor(0, 0); lcd.print(baris1);
lcd.setCursor(0, 1); lcd.print(baris2);
}
// ===================== 8. KIRIM DATA KE BLYNK (monitoring) =============
void kirimKeBlynk() {
Blynk.virtualWrite(V0, (int)dayaTerukur);
Blynk.virtualWrite(V1, round(sisaKwh * 100.0) / 100.0);
Blynk.virtualWrite(V2, teksStatusDashboard());
}
// ===================== 9. LOGIKA STATUS BERTINGKAT =====================
void tanganiPerubahanStatus(Status lama, Status baru) {
Serial.printf("[STATUS] %s -> %s (sisa %.2f kWh)\n", NAMA_STATUS[lama], NAMA_STATUS[baru], sisaKwh);
if (baru > lama) {
// Level memburuk: bunyikan alarm dan kirim notifikasi (sekali per perubahan)
alarmSampai = millis() + DURASI_ALARM_MS;
String sisa = String(sisaKwh, 1);
if (baru == WASPADA) Blynk.logEvent("token_menipis", "Sisa token " + sisa + " kWh. Segera isi ulang.");
if (baru == KRITIS) Blynk.logEvent("token_kritis", "Sisa token " + sisa + " kWh. Beban non-esensial diputus.");
if (baru == HABIS) Blynk.logEvent("token_habis", "Token habis! Isi token sekarang.");
} else {
alarmSampai = 0; // membaik (setelah isi token): hentikan alarm
}
// Masuk KRITIS/HABIS: putus beban non-esensial. Keluar dari KRITIS/HABIS: sambung lagi.
if (baru >= KRITIS && lama < KRITIS) {
bebanOn = false;
Blynk.virtualWrite(V4, 0);
} else if (baru < KRITIS && lama >= KRITIS) {
bebanOn = true;
Blynk.virtualWrite(V4, 1);
}
terapkanRelay();
}
void evaluasiStatus() {
Status baru = hitungStatus(sisaKwh);
if (baru != statusToken) {
Status lama = statusToken;
statusToken = baru;
tanganiPerubahanStatus(lama, baru);
}
}
// ===================== 10. LOOP UTAMA: dipanggil tiap 1 detik ==========
void hitungDanEvaluasi() {
unsigned long sekarang = millis();
float dtDetik = (sekarang - waktuHitungTerakhir) / 1000.0;
waktuHitungTerakhir = sekarang;
// 1) Baca sensor
float dayaBeban = bacaDayaBeban();
// 2) Daya yang benar-benar mengalir. Jika beban non-esensial diputus, daya turun.
// (Di rangkaian nyata ini terjadi sendiri karena sensor arus membaca arus yang turun.)
dayaTerukur = bebanOn ? dayaBeban : ((dayaBeban < BEBAN_ESENSIAL_W) ? dayaBeban : BEBAN_ESENSIAL_W);
static bool pertama = true;
if (pertama) { dayaRata = dayaTerukur; pertama = false; }
else { dayaRata = 0.8 * dayaRata + 0.2 * dayaTerukur; }
// 3) Kurangi token: kWh = kW x jam (jam dipercepat oleh FAKTOR_PERCEPATAN)
float kwhPakai = (dayaTerukur / 1000.0) * (dtDetik * FAKTOR_PERCEPATAN / 3600.0);
sisaKwh -= kwhPakai;
if (sisaKwh < 0) sisaKwh = 0;
// 4) Cek level, picu aktuator & notifikasi bila berubah
evaluasiStatus();
// 5) Tampilkan lokal
perbaruiLcd();
Serial.printf("[%s] daya=%.0f W | token=%.2f kWh | beban=%s\n",
NAMA_STATUS[statusToken], dayaTerukur, sisaKwh, bebanOn ? "ON" : "OFF");
}
// ===================== 11. BLYNK: TERIMA PERINTAH (kontrol) ============
BLYNK_CONNECTED() {
// ESP32 adalah sumber kebenaran saat menyala: samakan tampilan dashboard
Blynk.virtualWrite(V4, bebanOn ? 1 : 0);
kirimKeBlynk();
}
// V3: Numeric Input "Isi Token" -> menambah saldo
BLYNK_WRITE(V3) {
float tambah = param.asFloat();
if (tambah <= 0) return;
sisaKwh += tambah;
if (sisaKwh > TOKEN_MAKS_KWH) sisaKwh = TOKEN_MAKS_KWH;
Serial.printf("[TOKEN] +%.2f kWh -> saldo %.2f kWh\n", tambah, sisaKwh);
evaluasiStatus(); // langsung cek ulang: bisa menyambung beban & menghentikan alarm
perbaruiLcd();
kirimKeBlynk();
}
// V4: Switch "Beban Non-Esensial"
BLYNK_WRITE(V4) {
bool minta = (param.asInt() == 1);
if (minta && statusToken >= KRITIS) {
// Ditolak: tidak boleh menyalakan beban saat token kritis/habis
Serial.println("[BEBAN] Ditolak: token kritis. Isi token dulu.");
bebanOn = false;
Blynk.virtualWrite(V4, 0);
} else {
bebanOn = minta;
}
terapkanRelay();
perbaruiLcd();
}
// ===================== 12. SETUP & LOOP ================================
void setup() {
Serial.begin(115200);
Serial.println("Pemantau Token Listrik - mulai");
pinMode(PIN_RELAY, OUTPUT);
terapkanRelay();
siapkanBuzzer();
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0); lcd.print("Token Listrik");
lcd.setCursor(0, 1); lcd.print("Menyambung...");
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass); // menunggu Wi-Fi & Blynk tersambung
lcd.clear();
waktuHitungTerakhir = millis();
timer.setInterval(INTERVAL_HITUNG_MS, hitungDanEvaluasi);
timer.setInterval(INTERVAL_KIRIM_MS, kirimKeBlynk);
}
void loop() {
Blynk.run();
timer.run();
perbaruiBuzzer(); // dipanggil terus-menerus agar pola bip tepat waktu
}
Loading
esp32-devkit-c-v4
esp32-devkit-c-v4