Belajar Elektro

Random post

Belajar Elektro

Powered By Blogger

Monday, 20 October 2025

BEL SEKOLAH VERSI 2

 

// ===================================================

// BEL OTOMATIS ESP32 - KODE LENGKAP SIAP DIGUNAKAN (FIXED)

// ===================================================

 

#include <WiFi.h>

#include <ESPAsyncWebServer.h>

#include <AsyncTCP.h>

#include <LiquidCrystal_I2C.h>

#include <Wire.h>

#include "RTClib.h"

#include <HardwareSerial.h>

#include <DFRobotDFPlayerMini.h>

#include <SPIFFS.h>

#include <ArduinoJson.h>

#include <NTPClient.h>

#include <WiFiUdp.h>

#include <functional> // Ditambahkan untuk meningkatkan kompatibilitas C++ modern

 

// ===== Konfigurasi WiFi =====

const char* ssid = "Tes123"; // GANTI dengan SSID Anda

const char* password = "1234Dcba12"; // GANTI dengan Password Anda

 

// ===== NTP Client (GMT+7) =====

WiFiUDP ntpUDP;

NTPClient timeClient(ntpUDP, "pool.ntp.org", 7 * 3600, 60000);

 

// ===== Web Server =====

AsyncWebServer server(80);

const char* www_username = "admin";

const char* www_password = "1234";

const char* JSON_SCHEDULE_FILE = "/schedule.json";

 

// ===== LCD 16x2 (Ubah alamat I2C jika perlu) =====

LiquidCrystal_I2C lcd(0x27, 16, 2);

 

// ===== RTC (DS3231/DS1307) =====

RTC_DS3231 rtcDS3231;

RTC_DS1307 rtcDS1307;

bool rtcFound = false;

bool useDS3231 = false;

bool useDS1307 = false;

 

// ===== DFPlayer Mini (Pin UART2 ESP32) =====

HardwareSerial dfSerial(2);

DFRobotDFPlayerMini dfPlayer;

const int dfRxPin = 16;

const int dfTxPin = 17;

const int busyPin = 4;  

int dfVolume = 25;

bool dfPlayerReady = false;

 

// ===== Tombol =====

const int btnUp = 12;

const int btnDown = 13;

const int btnOk = 14;

const int btnBack = 15;

unsigned long lastDebounceTime = 0;

const unsigned long debounceDelay = 50;

const int MAIN_MENU_ITEMS = 4;

 

// ===== Konfigurasi Waktu & Cek Bel =====

const unsigned long NTP_UPDATE_INTERVAL = 3600000;

unsigned long lastBellCheck = 0;

const unsigned long BELL_CHECK_INTERVAL = 1000;

const int SCHEDULE_PER_PAGE = 2;

 

// ===== Jadwal Bel - Struktur Data =====

#define MAX_BEL_PER_DAY 20

struct Bell {

  int hour;

  int minute;

  int track;

  int duration; // detik

  bool enabled;

};

 

Bell scheduleMinggu[MAX_BEL_PER_DAY];

Bell scheduleSenin[MAX_BEL_PER_DAY];

Bell scheduleSelasa[MAX_BEL_PER_DAY];

Bell scheduleRabu[MAX_BEL_PER_DAY];

Bell scheduleKamis[MAX_BEL_PER_DAY];

Bell scheduleJumat[MAX_BEL_PER_DAY];

Bell scheduleSabtu[MAX_BEL_PER_DAY];

 

int totalBells[7] = {0};

Bell* schedulePointers[7] = {

  scheduleMinggu, scheduleSenin, scheduleSelasa, scheduleRabu,

  scheduleKamis, scheduleJumat, scheduleSabtu

};

 

// ===== Variabel Waktu & Sistem =====

const String daysOfWeek[7] = {"Minggu", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"};

const String daysOfWeekShort[7] = {"Min", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab"};

 

int currentHour = 0;

int currentMinute = 0;

int currentSecond = 0;

int currentDay = 0;

int currentDate = 1;

int currentMonth = 1;

int currentYear = 2024;

 

unsigned long lastNTPUpdate = 0;

unsigned long lastActivityTime = 0;

bool lcdBacklightOn = true;

bool wifiConnected = false;

bool spiffsMounted = false;

bool bellIsPlaying = false;

unsigned long bellStartTime = 0;

 

// ===== Variabel Menu =====

enum MenuState {

  MAIN_DISPLAY, MENU_MAIN, MENU_SET_TIME, MENU_SET_DATE, MENU_TEST_BELL,

  MENU_SYSTEM_INFO, MENU_RTC_SETTINGS, MENU_SELECT_DAY, MENU_VIEW_SCHEDULE,

  MENU_EDIT_BELL_ENTRY

};

MenuState currentMenuState = MAIN_DISPLAY;

int menuPosition = 0;

int cursorPosition = 0;

bool editing = false;

bool blinkState = false;

unsigned long lastBlinkTime = 0;

int selectedDay = 1;

int schedulePage = 0;

 

// ===================================================

// DEKLARASI FUNGSI

// ===================================================

void initButtons();

void initLCD();

void initWiFi();

void detectRTC();

void setupWebServer();

void loadSchedule();

void saveSchedule();

void addBell(int day, int hour, int minute, int track, int duration, bool enabled);

void readTimeFromRTC();

void updateTimeFromNTP();

void handleBellPlaying();

void updateBlink();

void checkPowerSaving();

void toggleBacklight();

void logSystemEvent(String event);

String formatTime(int h, int m, int s);

bool initDFPlayer();

void saveRTCTime(int hour, int minute, int date, int month, int year);

void handleButtons();

void updateLCDDisplay();

void displayScheduleForDay();

String getProcessor(const String& var);

 

// ===================================================

// SETUP

// ===================================================

void setup() {

  Serial.begin(115200);

 

  vTaskDelay(pdMS_TO_TICKS(500));

 

  initButtons();

  initLCD();

 

  Wire.begin();

  detectRTC();

 

  if(SPIFFS.begin(true)){

    spiffsMounted = true;

    loadSchedule();

  } else {

    logSystemEvent("SPIFFS GAGAL terpasang.");

  }

 

  initWiFi();

  initDFPlayer();

  setupWebServer();

 

  if (wifiConnected) {

    timeClient.begin();

    updateTimeFromNTP();

  } else if (rtcFound) {

    readTimeFromRTC();

  }

 

  logSystemEvent("Sistem Bel Otomatis siap.");

}

 

// ===================================================

// LOOP

// ===================================================

void loop() {

    vTaskDelay(pdMS_TO_TICKS(1));

 

    if (wifiConnected && (millis() - lastNTPUpdate >= NTP_UPDATE_INTERVAL)) {

        updateTimeFromNTP();

        lastNTPUpdate = millis();

    }

   

    readTimeFromRTC();

 

    if (millis() - lastBellCheck >= BELL_CHECK_INTERVAL) {

        handleBellPlaying();

        lastBellCheck = millis();

    }

 

    handleButtons();

    updateLCDDisplay();

    updateBlink();

    checkPowerSaving();

   

    yield();

}

 

// ===================================================

// IMPLEMENTASI FUNGSI UTAMA

// ===================================================

 

void readTimeFromRTC() {

    if (!rtcFound) return;

   

    DateTime now;

    if (useDS3231) {

        now = rtcDS3231.now();

    } else if (useDS1307) {

        now = rtcDS1307.now();

    }

 

    currentHour = now.hour();

    currentMinute = now.minute();

    currentSecond = now.second();

    currentDay = now.dayOfTheWeek();

    currentDate = now.day();

    currentMonth = now.month();

    currentYear = now.year();

}

 

void saveRTCTime(int hour, int minute, int date, int month, int year) {

    if (!rtcFound) return;

    DateTime newTime(year, month, date, hour, minute, 0);

 

    if (useDS3231) {

        rtcDS3231.adjust(newTime);

    } else if (useDS1307) {

        rtcDS1307.adjust(newTime);

    }

    logSystemEvent("Waktu RTC diperbarui.");

}

 

void updateTimeFromNTP() {

  if (!wifiConnected || !rtcFound) {

    logSystemEvent("Gagal sync: Cek WiFi atau RTC.");

    return;

  }

 

  if (!timeClient.update()) {

    logSystemEvent("Gagal update waktu dari NTP.");

    return;

  }

 

  unsigned long epochTime = timeClient.getEpochTime();

  DateTime now(epochTime);

 

  saveRTCTime(now.hour(), now.minute(), now.day(), now.month(), now.year());

  logSystemEvent("RTC berhasil disinkronkan dengan NTP.");

}

 

bool initDFPlayer() {

  logSystemEvent("Mencoba inisialisasi DFPlayer...");

 

  dfSerial.begin(9600, SERIAL_8N1, dfRxPin, dfTxPin);

  delay(100);

 

  if (!dfPlayer.begin(dfSerial, false, false)) {

    logSystemEvent("Gagal inisialisasi DFPlayer.");

    dfPlayerReady = false;

    return false;

  }

 

  dfPlayer.setTimeOut(500);

  dfPlayer.volume(dfVolume);

  dfPlayer.outputDevice(DFPLAYER_DEVICE_SD);

  pinMode(busyPin, INPUT_PULLUP);

 

  dfPlayer.play(1);

  delay(500);

  dfPlayer.pause();

 

  dfPlayerReady = true;

  logSystemEvent("DFPlayer siap.");

  return true;

}

 

void handleBellPlaying() {

    if (!dfPlayerReady) return;

 

    if (bellIsPlaying) {

        if (digitalRead(busyPin) == HIGH || (millis() - bellStartTime > (60000 * 2))) {

            dfPlayer.pause();

            bellIsPlaying = false;

            logSystemEvent("Bel selesai atau dihentikan.");

        }

        return;

    }

 

    if (currentSecond != 0) return;

 

    Bell* currentSchedule = schedulePointers[currentDay];

    int currentTotal = totalBells[currentDay];

 

    for (int i = 0; i < currentTotal; i++) {

        Bell& bell = currentSchedule[i];

        if (bell.enabled && bell.hour == currentHour && bell.minute == currentMinute) {

            logSystemEvent("MEMUTAR BEL: Track " + String(bell.track) + ".");

           

            dfPlayer.play(bell.track);

            bellStartTime = millis();

            bellIsPlaying = true;

            break;

        }

    }

}

 

void saveSchedule() {

    if (!spiffsMounted) { logSystemEvent("Gagal simpan: SPIFFS tidak terpasang."); return; }

 

    File file = SPIFFS.open(JSON_SCHEDULE_FILE, FILE_WRITE);

    if (!file) { logSystemEvent("Gagal membuka file JSON untuk ditulis."); return; }

 

    const size_t CAPACITY = JSON_ARRAY_SIZE(7) + 7 * JSON_ARRAY_SIZE(MAX_BEL_PER_DAY) + 7 * MAX_BEL_PER_DAY * JSON_OBJECT_SIZE(5);

    DynamicJsonDocument doc(CAPACITY);

    JsonArray root = doc.to<JsonArray>();

 

    for (int d = 0; d < 7; d++) {

        JsonArray dayArray = root.createNestedArray();

        int currentTotal = totalBells[d];

        Bell* currentSchedule = schedulePointers[d];

       

        for (int i = 0; i < currentTotal; i++) {

            Bell& bell = currentSchedule[i];

            JsonObject bellObj = dayArray.createNestedObject();

            bellObj["h"] = bell.hour;

            bellObj["m"] = bell.minute;

            bellObj["t"] = bell.track;

            bellObj["d"] = bell.duration;

            bellObj["e"] = bell.enabled;

        }

    }

 

    if (serializeJson(doc, file) == 0) {

        logSystemEvent("Gagal menulis ke file JSON.");

    } else {

        logSystemEvent("Jadwal berhasil disimpan ke SPIFFS.");

    }

    file.close();

}

 

void loadSchedule() {

    if (!spiffsMounted) return;

 

    File file = SPIFFS.open(JSON_SCHEDULE_FILE, FILE_READ);

    if (!file) {

        logSystemEvent("File jadwal tidak ditemukan. Membuat data default.");

        addBell(1, 7, 0, 1, 15, true);

        addBell(1, 12, 0, 2, 20, true);

        saveSchedule();

        return;

    }

 

    const size_t CAPACITY = JSON_ARRAY_SIZE(7) + 7 * JSON_ARRAY_SIZE(MAX_BEL_PER_DAY) + 7 * MAX_BEL_PER_DAY * JSON_OBJECT_SIZE(5);

    DynamicJsonDocument doc(CAPACITY);

 

    DeserializationError error = deserializeJson(doc, file);

    file.close();

 

    if (error) {

        logSystemEvent("Gagal mem-parsing JSON: " + String(error.c_str()));

        return;

    }

 

    JsonArray root = doc.as<JsonArray>();

    int dayIndex = 0;

 

    for (JsonArray dayArray : root) {

        if (dayIndex < 7) {

            totalBells[dayIndex] = 0;

            int bellIndex = 0;

            for (JsonObject bellObj : dayArray) {

                if (bellIndex < MAX_BEL_PER_DAY) {

                    Bell& bell = schedulePointers[dayIndex][bellIndex];

                    bell.hour = bellObj["h"].as<int>();

                    bell.minute = bellObj["m"].as<int>();

                    bell.track = bellObj["t"].as<int>();

                    bell.duration = bellObj["d"].as<int>();

                    bell.enabled = bellObj["e"].as<bool>();

                    bellIndex++;

                }

            }

            totalBells[dayIndex] = bellIndex;

        }

        dayIndex++;

    }

 

    logSystemEvent("Jadwal berhasil dimuat dari SPIFFS.");

}

 

void addBell(int day, int hour, int minute, int track, int duration, bool enabled) {

    if (day >= 0 && day < 7 && totalBells[day] < MAX_BEL_PER_DAY) {

        Bell& bell = schedulePointers[day][totalBells[day]];

        bell.hour = hour;

        bell.minute = minute;

        bell.track = track;

        bell.duration = duration;

        bell.enabled = enabled;

        totalBells[day]++;

    }

}

 

void handleButtons() {

    unsigned long currentTime = millis();

   

    bool okState = (digitalRead(btnOk) == LOW);

    bool upState = (digitalRead(btnUp) == LOW);

    bool downState = (digitalRead(btnDown) == LOW);

    bool backState = (digitalRead(btnBack) == LOW);

 

    if ((okState || upState || downState || backState) && (currentTime - lastDebounceTime > debounceDelay)) {

        lastDebounceTime = currentTime;

        lastActivityTime = currentTime;

        if (!lcdBacklightOn) toggleBacklight();

 

        if (okState) {

            if (currentMenuState == MAIN_DISPLAY) {

                currentMenuState = MENU_MAIN;

                menuPosition = 0;

            }

            else if (editing) {

                if (currentMenuState == MENU_EDIT_BELL_ENTRY) {

                    if (cursorPosition < 4) {

                        cursorPosition++;

                    } else {

                        editing = false;

                        cursorPosition = 0;

                        saveSchedule();

                        lcd.clear();

                        lcd.print("Jadwal Disimpan!");

                        delay(1500);

                        currentMenuState = MENU_VIEW_SCHEDULE;

                    }

                }

            }

            else {

                if (currentMenuState == MENU_MAIN) {

                    if (menuPosition == 0) { currentMenuState = MENU_SELECT_DAY; menuPosition = selectedDay; }

                    // Tambahkan switch/case untuk menu 1, 2, 3...

                }

                else if (currentMenuState == MENU_SELECT_DAY) {

                    selectedDay = menuPosition;

                    currentMenuState = MENU_VIEW_SCHEDULE;

                    schedulePage = 0;

                    menuPosition = 0;

                }

                else if (currentMenuState == MENU_VIEW_SCHEDULE) {

                    int bellIndex = schedulePage * SCHEDULE_PER_PAGE + menuPosition;

                    int currentTotalBells = totalBells[selectedDay];

                    if (bellIndex < currentTotalBells) {

                        currentMenuState = MENU_EDIT_BELL_ENTRY;

                        menuPosition = bellIndex;

                        cursorPosition = 0;

                        editing = true;

                    }

                    else if (menuPosition == SCHEDULE_PER_PAGE && bellIndex == currentTotalBells && currentTotalBells < MAX_BEL_PER_DAY) {

                        addBell(selectedDay, 7, 0, 1, 10, true);

                        menuPosition = totalBells[selectedDay] - 1;

                        currentMenuState = MENU_EDIT_BELL_ENTRY;

                        cursorPosition = 0;

                        editing = true;

                    }

                }

            }

        }

       

        else if (upState || downState) {

            int step = upState ? -1 : 1;

 

            if (!editing) {

                if (currentMenuState == MENU_MAIN) {

                    menuPosition = (menuPosition + step + MAIN_MENU_ITEMS) % MAIN_MENU_ITEMS;

                } else if (currentMenuState == MENU_SELECT_DAY) {

                    menuPosition = (menuPosition + step + 7) % 7;

                } else if (currentMenuState == MENU_VIEW_SCHEDULE) {

                    int maxMenuPos = min(SCHEDULE_PER_PAGE, totalBells[selectedDay] - schedulePage * SCHEDULE_PER_PAGE);

                    if (totalBells[selectedDay] < MAX_BEL_PER_DAY) maxMenuPos++;

 

                    if (maxMenuPos > 0) {

                        int newPos = menuPosition + step;

                        if (newPos >= maxMenuPos) {

                            schedulePage++;

                            menuPosition = 0;

                        } else if (newPos < 0) {

                            schedulePage--;

                            if (schedulePage < 0) {

                                schedulePage = 0;

                                menuPosition = 0;

                            } else {

                                menuPosition = SCHEDULE_PER_PAGE - 1;

                            }

                        } else {

                            menuPosition = newPos;

                        }

                    }

                }

            }

            else if (editing && currentMenuState == MENU_EDIT_BELL_ENTRY) {

                Bell* currentSchedule = schedulePointers[selectedDay];

                Bell& bell = currentSchedule[menuPosition];

               

                if (cursorPosition == 0) bell.hour = (bell.hour + step + 24) % 24;

                else if (cursorPosition == 1) bell.minute = (bell.minute + step + 60) % 60;

                else if (cursorPosition == 2) bell.track = (bell.track + step + 100) % 100; if (bell.track == 0) bell.track = 1;

                else if (cursorPosition == 3) bell.duration = (bell.duration + step + 61) % 61; if (bell.duration == 0) bell.duration = 1;

                else if (cursorPosition == 4) bell.enabled = !bell.enabled;

            }

        }

       

        else if (backState) {

            if (editing) {

                editing = false;

                cursorPosition = 0;

                if (currentMenuState == MENU_EDIT_BELL_ENTRY) {

                    loadSchedule();

                    currentMenuState = MENU_VIEW_SCHEDULE;

                } else {

                    currentMenuState = MAIN_DISPLAY;

                }

            } else {

                if (currentMenuState == MENU_VIEW_SCHEDULE) {

                    currentMenuState = MENU_SELECT_DAY;

                    menuPosition = selectedDay;

                } else if (currentMenuState == MENU_SELECT_DAY || currentMenuState == MENU_MAIN) {

                    currentMenuState = MAIN_DISPLAY;

                }

            }

        }

    }

}

 

void displayScheduleForDay() {

    lcd.clear();

    lcd.setCursor(0, 0);

    lcd.print(daysOfWeek[selectedDay] + " (" + String(totalBells[selectedDay]) + " entri)");

 

    Bell* currentSchedule = schedulePointers[selectedDay];

    int startIdx = schedulePage * SCHEDULE_PER_PAGE;

 

    for (int i = 0; i < SCHEDULE_PER_PAGE; i++) {

        int idx = startIdx + i;

        lcd.setCursor(0, i + 1);

 

        if (idx < totalBells[selectedDay]) {

            Bell& bell = currentSchedule[idx];

            String status = bell.enabled ? "A" : "X";

            String timeStr = formatTime(bell.hour, bell.minute, -1);

            String trackStr = (bell.track < 10 ? "0" : "") + String(bell.track);

           

            lcd.print(status + " " + timeStr + " T:" + trackStr);

           

            if (i == menuPosition % SCHEDULE_PER_PAGE) {

                lcd.setCursor(0, i + 1);

                if (blinkState) lcd.print(">");

                else lcd.print(" ");

            }

        } else if (idx == totalBells[selectedDay] && totalBells[selectedDay] < MAX_BEL_PER_DAY) {

            if (i == menuPosition % SCHEDULE_PER_PAGE) {

                 lcd.print((blinkState ? ">" : " ") + String(" + Tambah Bel"));

            } else {

                 lcd.print("  + Tambah Bel");

            }

        }

    }

}

 

void updateLCDDisplay() {

   

    if (currentMenuState == MAIN_DISPLAY) {

        lcd.setCursor(0, 0);

        lcd.print(daysOfWeek[currentDay] + ", " + String(currentDate) + "/" + String(currentMonth));

        lcd.setCursor(0, 1);

        lcd.print(formatTime(currentHour, currentMinute, currentSecond) + " (" + (wifiConnected ? "W" : "-") + (rtcFound ? "R" : "-") + ")");

    }

    else if (currentMenuState == MENU_MAIN) {

        lcd.clear();

        lcd.setCursor(0, 0); lcd.print("MENU UTAMA");

        lcd.setCursor(0, 1);

 

        String menuItems[] = {"1. Edit Jadwal", "2. Set Waktu", "3. Tes Bel", "4. Info Sistem"};

        lcd.print(menuItems[menuPosition]);

 

        if (blinkState) { lcd.setCursor(0, 1); lcd.print(">"); }

        else { lcd.setCursor(0, 1); lcd.print(" "); }

    }

    else if (currentMenuState == MENU_SELECT_DAY) {

        lcd.clear();

        lcd.setCursor(0, 0); lcd.print("PILIH HARI:");

        lcd.setCursor(0, 1);

        lcd.print(daysOfWeek[menuPosition]);

 

        if (blinkState) { lcd.setCursor(0, 1); lcd.print(">"); }

        else { lcd.setCursor(0, 1); lcd.print(" "); }

    }

    else if (currentMenuState == MENU_VIEW_SCHEDULE) {

        displayScheduleForDay();

    }

   

    else if (currentMenuState == MENU_EDIT_BELL_ENTRY) {

        Bell* currentSchedule = schedulePointers[selectedDay];

        Bell& bell = currentSchedule[menuPosition];

       

        lcd.clear();

        lcd.setCursor(0, 0);

       

        String statusText = bell.enabled ? "AKTIF" : "NONAKTIF";

        lcd.print(daysOfWeekShort[selectedDay] + " #" + String(menuPosition + 1) + " S:" + statusText);

 

        String timeStr = formatTime(bell.hour, bell.minute, -1);

        String trackStr = (bell.track < 10 ? "0" : "") + String(bell.track);

        String durationStr = String(bell.duration) + "s";

       

        lcd.setCursor(0, 1);

        lcd.print(timeStr + " T:" + trackStr + " D:" + durationStr);

       

        if (editing && blinkState) {

            if (cursorPosition == 0) { lcd.setCursor(0, 1); lcd.print("  "); }

            else if (cursorPosition == 1) { lcd.setCursor(3, 1); lcd.print("  "); }

            else if (cursorPosition == 2) { lcd.setCursor(7, 1); lcd.print("  "); }

            else if (cursorPosition == 3) { lcd.setCursor(11, 1); lcd.print("  "); }

            else if (cursorPosition == 4) {

                lcd.setCursor(10, 0);

                lcd.print("      ");

            }

        }

    }

}

 

// ---------------------------------------------------------------------------------------

// FUNGSI PEMBANTU LAIN

// ---------------------------------------------------------------------------------------

 

void initButtons() { pinMode(btnUp, INPUT_PULLUP); pinMode(btnDown, INPUT_PULLUP); pinMode(btnOk, INPUT_PULLUP); pinMode(btnBack, INPUT_PULLUP); }

void initLCD() { lcd.init(); lcd.backlight(); }

 

void initWiFi() {

    WiFi.begin(ssid, password);

    lcd.clear(); lcd.print("Connecting to WiFi");

   

    int attempts = 0;

    while (WiFi.status() != WL_CONNECTED && attempts < 30) {

        delay(500);

        lcd.print(".");

        attempts++;

    }

    if (WiFi.status() == WL_CONNECTED) {

        wifiConnected = true;

        lcd.clear(); lcd.print("Connected!"); lcd.setCursor(0, 1); lcd.print(WiFi.localIP());

        logSystemEvent("WiFi Connected. IP: " + WiFi.localIP().toString());

    } else {

        wifiConnected = false;

        lcd.clear(); lcd.print("WiFi GAGAL.");

        logSystemEvent("WiFi GAGAL.");

    }

    delay(2000);

}

 

void detectRTC() {

    if (rtcDS3231.begin()) {

        useDS3231 = true; rtcFound = true; logSystemEvent("RTC DS3231 ditemukan.");

    }

    else if (rtcDS1307.begin()) {

        useDS1307 = true; rtcFound = true; logSystemEvent("RTC DS1307 ditemukan.");

    }

   

    if (!rtcFound) { logSystemEvent("RTC TIDAK ditemukan!"); lcd.clear(); lcd.print("RTC ERROR!"); }

}

 

void checkPowerSaving() {

    const unsigned long IDLE_TIMEOUT = 30000;

    if (lcdBacklightOn && (millis() - lastActivityTime > IDLE_TIMEOUT)) {

        toggleBacklight();

    }

}

 

void toggleBacklight() {

    lcdBacklightOn = !lcdBacklightOn;

    lcdBacklightOn ? lcd.backlight() : lcd.noBacklight();

}

 

void updateBlink() {

    if (millis() - lastBlinkTime >= 500) {

        blinkState = !blinkState;

        lastBlinkTime = millis();

    }

}

 

void logSystemEvent(String event) { Serial.println("[LOG] " + event); }

 

String formatTime(int h, int m, int s) {

    String timeStr = (h < 10 ? "0" : "") + String(h) + ":" + (m < 10 ? "0" : "") + String(m);

    if (s != -1) {

        timeStr = timeStr + ":" + (s < 10 ? "0" : "") + String(s);

    }

    return timeStr;

}

 

// ---------------------------------------------------------------------------------------

// FUNGSI WEB SERVER

// ---------------------------------------------------------------------------------------

 

void setupWebServer() {

    // Otentikasi dan penyajian index.html

    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {

        if (!request->authenticate(www_username, www_password)) {

            return request->requestAuthentication();

        }

        request->send(SPIFFS, "/index.html", "text/html", false, getProcessor);

    });

   

    // API untuk mendapatkan jadwal dalam format JSON

    server.on("/api/schedule", HTTP_GET, [](AsyncWebServerRequest *request) {

        if (!request->authenticate(www_username, www_password)) { return request->requestAuthentication(); }

 

        const size_t CAPACITY = JSON_ARRAY_SIZE(7) + 7 * JSON_ARRAY_SIZE(MAX_BEL_PER_DAY) + 7 * MAX_BEL_PER_DAY * JSON_OBJECT_SIZE(5);

        DynamicJsonDocument doc(CAPACITY);

        JsonArray root = doc.to<JsonArray>();

 

        for (int d = 0; d < 7; d++) {

            JsonArray dayArray = root.createNestedArray();

            int currentTotal = totalBells[d];

            Bell* currentSchedule = schedulePointers[d];

           

            for (int i = 0; i < currentTotal; i++) {

                Bell& bell = currentSchedule[i];

                JsonObject bellObj = dayArray.createNestedObject();

                bellObj["h"] = bell.hour; bellObj["m"] = bell.minute;

                bellObj["t"] = bell.track; bellObj["d"] = bell.duration;

                bellObj["e"] = bell.enabled;

            }

        }

 

        String jsonResponse;

        serializeJson(doc, jsonResponse);

        request->send(200, "application/json", jsonResponse);

    });

 

    // API untuk menyimpan jadwal (POST)

    server.on("/api/save", HTTP_POST, [](AsyncWebServerRequest *request){

        if (!request->authenticate(www_username, www_password)) { return request->requestAuthentication(); }

        request->send(200, "text/plain", "Jadwal berhasil diterima dan disimpan.");

    }, NULL, [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total){

        if (index == 0) {

            for(int i=0; i<7; i++) totalBells[i] = 0;

        }

 

        const size_t CAPACITY = JSON_ARRAY_SIZE(7) + 7 * JSON_ARRAY_SIZE(MAX_BEL_PER_DAY) + 7 * MAX_BEL_PER_DAY * JSON_OBJECT_SIZE(5);

        DynamicJsonDocument doc(CAPACITY);

 

        if (deserializeJson(doc, (const char*)data) == DeserializationError::Ok) {

            JsonArray root = doc.as<JsonArray>();

            int dayIndex = 0;

 

            for (JsonArray dayArray : root) {

                if (dayIndex < 7) {

                    int bellIndex = 0;

                    for (JsonObject bellObj : dayArray) {

                        if (bellIndex < MAX_BEL_PER_DAY) {

                            addBell(dayIndex, bellObj["h"].as<int>(), bellObj["m"].as<int>(), bellObj["t"].as<int>(), bellObj["d"].as<int>(), bellObj["e"].as<bool>());

                            bellIndex++;

                        }

                    }

                    totalBells[dayIndex] = bellIndex;

                }

                dayIndex++;

            }

            saveSchedule();

        }

    });

 

    server.serveStatic("/", SPIFFS, "/");

    server.begin();

    logSystemEvent("Web Server dimulai.");

}

 

String getProcessor(const String& var){

    if(var == "IP_ADDRESS"){

        return WiFi.localIP().toString();

    }

    return String();

}

0 comments: