feat: implement initial ESP32 school bell system with web UI, DFPlayer control, RTC, LittleFS, and OTA.

This commit is contained in:
2025-12-21 20:10:15 +08:00
parent ec8e66d458
commit cd4ece14b3

View File

@@ -1,15 +1,16 @@
// src/main.cpp
// Bel Sekolah ESP32 - final version
// Pins:
// RESET_PIN = 5 (hold >15s -> reset admin credentials)
// STATUS_LED_PIN = 6
// RELAY_PIN = 7 (active LOW)
// DFPLAYER_RX_PIN = 15
// DFPLAYER_TX_PIN = 16
// BUSY_PIN = 17 (LOW = busy)
//esp32 sda 21
//esp32 scl 22
//MCU adalah Wemos ESP32 mini
// TEST_BUTTON = 18 (click -> toggle relay, long press -> play test MP3)
// RESET_BUTTON = 0 (long press >15s -> reset admin credentials)
// STATUS_LED_PIN = 26
// RELAY_PIN = 19 (active LOW)
// DFPLAYER_RX_PIN = 16
// DFPLAYER_TX_PIN = 17
// BUSY_PIN = 5 (LOW = busy)
// esp32 sda 21
// esp32 scl 22
// MCU adalah Wemos ESP32 mini
#include <Arduino.h>
#include <WiFi.h>
@@ -35,7 +36,8 @@
#define DFPLAYER_TX_PIN 17 //2 df
#define RELAY_PIN 19
#define BUSY_PIN 5 //must high at boot (strapping pin)
#define RESET_PIN 18
#define TEST_BUTTON 18
#define RESET_BUTTON 0
#define BUZZER_PIN 23
#define RESET_HOLD_MS 15000UL
@@ -74,12 +76,19 @@ bool buzzerState = false;
bool adminResetBlinking = false;
unsigned long adminResetEndTime = 0;
// Button flags
volatile bool btnClickPending = false;
volatile bool btnLongPressPending = false;
// Button flags for TEST_BUTTON
volatile bool testBtnClickPending = false;
volatile bool testBtnLongPressPending = false;
// OneButton instance for RESET_PIN
OneButton button(RESET_PIN, true);
// Button flags for RESET_BUTTON
volatile bool resetBtnLongPressPending = false;
// Manual relay toggle state (separate from automatic relay control)
bool manualRelayOn = false;
// OneButton instances
OneButton testButton(TEST_BUTTON, true);
OneButton resetButton(RESET_BUTTON, true);
//////////////////////////////////////////////////////////////////////////
// WiFi Channel Selection
@@ -659,7 +668,8 @@ void setup(){
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, HIGH); // relay active LOW, so HIGH = off
pinMode(BUSY_PIN, INPUT_PULLUP); // busy pin input (LOW when busy)
pinMode(RESET_PIN, INPUT_PULLUP);
pinMode(TEST_BUTTON, INPUT_PULLUP);
pinMode(RESET_BUTTON, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
@@ -732,16 +742,19 @@ void setup(){
ArduinoOTA.begin();
// Setup OneButton callbacks
button.setPressMs(15000); // Set long press to 15 seconds
button.attachClick([]() {
// Just set flag, don't do heavy work here
btnClickPending = true;
// Setup TEST_BUTTON callbacks (click=toggle relay, long press=play test MP3)
testButton.setPressMs(2000); // 2 seconds for test MP3 play
testButton.attachClick([]() {
testBtnClickPending = true;
});
testButton.attachLongPressStart([]() {
testBtnLongPressPending = true;
});
button.attachLongPressStart([]() {
// Just set flag
btnLongPressPending = true;
// Setup RESET_BUTTON callbacks (long press=reset admin credentials)
resetButton.setPressMs(15000); // 15 seconds for admin reset
resetButton.attachLongPressStart([]() {
resetBtnLongPressPending = true;
});
@@ -754,21 +767,45 @@ void setup(){
void loop(){
ArduinoOTA.handle();
ws.cleanupClients();
// Handle OneButton instances
testButton.tick();
resetButton.tick();
// Handle OneButton
button.tick();
// Process button events outside of callback
if (btnClickPending) {
btnClickPending = false;
Serial.println("[BUTTON] Click detected: playing track 1000");
playTrack(1000, "Manual Click");
// Process TEST_BUTTON click: toggle relay manually
if (testBtnClickPending) {
testBtnClickPending = false;
manualRelayOn = !manualRelayOn;
if (manualRelayOn) {
digitalWrite(RELAY_PIN, LOW); // relay active LOW
relayOn = true;
Serial.println("[BUTTON] Manual relay ON");
DynamicJsonDocument d(256);
d["type"] = "log";
d["msg"] = "🔊 Relay dinyalakan manual";
sendJsonWs(d);
} else {
digitalWrite(RELAY_PIN, HIGH); // relay off
relayOn = false;
Serial.println("[BUTTON] Manual relay OFF");
DynamicJsonDocument d(256);
d["type"] = "log";
d["msg"] = "🔇 Relay dimatikan manual";
sendJsonWs(d);
}
broadcastStatus();
}
if (btnLongPressPending) {
btnLongPressPending = false;
Serial.println("[BUTTON] Long press detected: resetting admin credentials");
// Process TEST_BUTTON long press: play test MP3
if (testBtnLongPressPending) {
testBtnLongPressPending = false;
Serial.println("[BUTTON] Long press detected: playing track 1000");
playTrack(1000, "Manual Test");
}
// Process RESET_BUTTON long press: reset admin credentials
if (resetBtnLongPressPending) {
resetBtnLongPressPending = false;
Serial.println("[BUTTON] Reset button long press: resetting admin credentials");
resetAdminToDefault();
}
@@ -842,8 +879,8 @@ void loop(){
}
}
// release relay if conditions met
if (!busy && relayOn && millis() > relayHoldUntil) {
// release relay if conditions met (but not if manual mode is active)
if (!busy && relayOn && millis() > relayHoldUntil && !manualRelayOn) {
digitalWrite(RELAY_PIN, HIGH); // OFF
relayOn = false;
}