//
===================================================
// 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();
}