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