Automatic School Bell System v2.0 with Arduino & Bluetooth | P10 LED Display Integration
Introducing the Automatic School Bell System v2.0, a powerful yet easy-to-build project using Arduino, Bluetooth, and a P10 LED Matrix. This upgraded version automates bell ringing, displays real-time schedules, and supports wireless control — ideal for schools, institutions, or exam halls.
Components and supplies
1
Active Buzzer
1
1 relay module 5 Vdc 10A (assembled)
1
DS1307 RTC module
1
P10 32x16 LED Display Matrix
1
Fire Alarm, Vibrating Bell
1
Arduino Uno Rev3
1
Modulo Bluetooth HC05
Tools and machines
1
wires single stranded
1
Wire stipper
1
The open-source Arduino Software (IDE)
1
Digital Multimeter
Apps and platforms
1
Arduino IDE 2.0 (beta)
Project description
Code
Automatic School Bell System v2.0
c
Automatic School Bell System v2.0
1#include <SoftwareSerial.h> 2#include <SPI.h> 3#include <DMD.h> 4#include <TimerOne.h> 5#include <EEPROM.h> 6#include "SystemFont5x7.h" 7#include <Wire.h> 8#include "RTClib.h" 9 10#define ROW 1 11#define COLUMN 1 12#define DEFAULT_FONT SystemFont5x7 // Use a smaller font by default 13#define MAX_BELLS 20 // Max number of bells, impacts RAM for breakIndexes array 14#define BUZZER_PIN 4 15#define RELAY_PIN 5 16 17// EEPROM Addresses for Bell and Break Data 18#define EEPROM_TOTAL_BELLS_ADDR 0 19#define EEPROM_BELL_TIMES_START_ADDR 1 // Each bell time takes 2 bytes (hour, minute) 20#define EEPROM_LUNCH_BELL_ADDR 50 // Store lunch bell index 21#define EEPROM_BREAK_COUNT_ADDR 100 22#define EEPROM_BREAK_INDEXES_START_ADDR 101 // Each break index takes 1 byte 23 24DMD led_module(ROW, COLUMN); 25SoftwareSerial BTSerial(3, 2); // RX, TX 26RTC_DS1307 rtc; 27 28int totalBells = 0; 29int lunchBell = -1; 30int breakCount = 0; 31int breakIndexes[MAX_BELLS]; // Array to hold break bell indexes 32int lastTriggeredMinute = -1; 33unsigned long lastDisplay = 0; 34int currentBellIndex = -1; // Stores the index of the bell that just rang for display 35bool bellActive = false; // Controls whether clock display should show or not 36 37// --- New Global Variables for Non-Blocking Bell Ringing --- 38unsigned long bellRingStartTime = 0; 39bool bellNeedsToRing = false; // Flag set by checkBellTimes to signal a ring 40bool bellIsRinging = false; // Flag indicating if buzzer/relay are currently HIGH 41unsigned long bellDisplayDelayTime = 0; // Timer to delay display message after ring 42char currentBellMessage[30]; // Buffer to store the message for delayed display 43// --- End New Global Variables --- 44 45// Function Prototypes 46void loadBellAndBreakData(); 47void saveBellAndBreakData(); 48void scan_module(); 49void checkBellTimes(DateTime now); 50void getBellMessage(int index, int hh, int mm, char* buffer, size_t bufferSize); // Modified to use char array 51void displayNextBellInfo(DateTime now); 52bool isBreak(int index); 53 54// --- IMPORTANT: Overloaded displayMessage functions --- 55void displayMessage(const char* msg); // Original: takes C-string from RAM 56void displayMessage(const __FlashStringHelper* msg); // NEW: takes F() macro strings from PROGMEM 57// --- End of Overloaded displayMessage functions --- 58 59void displayDate(DateTime now); 60void displayTime(DateTime now); 61void showMenu(); 62void startExamCountdown(); 63void updateRTCFromBluetooth(); 64void inputBellTimes(); 65void inputBreaks(); 66void clearEEPROM(); 67void showBellInfo(); 68void manualBellTrigger(); 69 70 71void scan_module() { 72 led_module.scanDisplayBySPI(); 73} 74 75void setup() { 76 Serial.begin(9600); 77 BTSerial.begin(9600); // Bluetooth module communication 78 Timer1.initialize(2000); // 2000 microseconds = 2ms 79 Timer1.attachInterrupt(scan_module); 80 led_module.clearScreen(true); 81 82 pinMode(BUZZER_PIN, OUTPUT); 83 pinMode(RELAY_PIN, OUTPUT); 84 digitalWrite(BUZZER_PIN, LOW); 85 digitalWrite(RELAY_PIN, LOW); 86 Serial.println(F("SETUP: Buzzer and Relay pins initialized LOW.")); // Debug 87 88 // Initialize RTC 89 if (!rtc.begin()) { 90 Serial.println(F("ERROR: Couldn't find RTC")); 91 BTSerial.println(F("Error: RTC not found!")); 92 while (1); // Halt if RTC is not found 93 } 94 95 if (!rtc.isrunning()) { 96 Serial.println(F("WARNING: RTC is NOT running!")); 97 BTSerial.println(F("Warning: RTC not running, time may be inaccurate.")); 98 // Optionally set initial time if RTC is not running 99 // rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); 100 } 101 102 // Load saved bell and break data from EEPROM 103 loadBellAndBreakData(); 104 Serial.println(F("SETUP: Bell System Ready.")); // Debug 105} 106 107void loop() { 108 DateTime now = rtc.now(); 109 110 // --- Non-Blocking Bell Ringing Management --- 111 // Step 1: Check if a bell needs to START ringing 112 if (bellNeedsToRing && !bellIsRinging) { 113 Serial.println(F("LOOP: Initiating bell ring (non-blocking).")); 114 digitalWrite(BUZZER_PIN, HIGH); 115 digitalWrite(RELAY_PIN, HIGH); 116 bellRingStartTime = millis(); // Record when the ring started 117 bellIsRinging = true; // Mark as currently ringing 118 bellNeedsToRing = false; // Consume the request to ring 119 } 120 121 // Step 2: Check if a bell that is ringing needs to STOP 122 if (bellIsRinging && (millis() - bellRingStartTime >= 3000)) { // Ring duration: 3 seconds 123 Serial.println(F("LOOP: Bell ring duration finished.")); 124 digitalWrite(BUZZER_PIN, LOW); 125 digitalWrite(RELAY_PIN, LOW); 126 bellIsRinging = false; // Mark as no longer ringing 127 bellDisplayDelayTime = millis(); // Set timer to display message AFTER ring ends (optional short pause) 128 } 129 130 // Step 3: Check if the bell message needs to be displayed after ringing 131 // A small delay (e.g., 500ms) can be added here if you want a pause between sound ending and text appearing. 132 if (bellDisplayDelayTime != 0 && (millis() - bellDisplayDelayTime >= 500)) { // Display message 0.5 seconds after ring stops 133 Serial.print(F("LOOP: Displaying bell message after ring: ")); 134 Serial.println(currentBellMessage); 135 displayMessage(currentBellMessage); // Display the message that was prepared 136 bellDisplayDelayTime = 0; // Reset the display timer 137 lastDisplay = millis(); // Reset clock display timer after showing bell message 138 bellActive = true; // Keep active for a moment to prevent immediate clock display 139 } 140 // --- End Non-Blocking Bell Ringing Management --- 141 142 // Check bell times for triggers (this now only sets flags, doesn't block) 143 checkBellTimes(now); 144 145 // Bluetooth input 146 if (BTSerial.available()) { 147 String command = BTSerial.readStringUntil('\n'); // Read into a temporary String 148 Serial.print(F("BT Command received: ")); // Debug print to Serial Monitor 149 Serial.println(command); 150 BTSerial.print(F("Command received: ")); // Echo to Bluetooth 151 BTSerial.println(command); 152 command.trim(); 153 154 if (command.equalsIgnoreCase(F("menu"))) { 155 showMenu(); 156 } 157 } 158 159 // Display clock every 10 seconds if no bell event is active OR currently ringing 160 if (!bellIsRinging && !bellActive && (millis() - lastDisplay > 10000)) { 161 Serial.println(F("LOOP: Displaying Current Time.")); // Debug 162 displayTime(now); 163 lastDisplay = millis(); 164 } 165 // Reset bellActive if not ringing and not in a countdown, to allow clock to display again 166 if (!bellIsRinging && bellActive && (millis() - lastDisplay > 5000)) { // 5 seconds after last bell/countdown display, allow clock 167 bellActive = false; 168 } 169 170 171 delay(10); // Very small delay to prevent busy-waiting and allow other tasks 172} 173 174// --- EEPROM Management Functions --- 175 176/** 177 * @brief Loads bell times, total bells, lunch bell, break count, and break indexes from EEPROM. 178 * 179 * This function reads the total number of bells from EEPROM_TOTAL_BELLS_ADDR (address 0), 180 * then iterates to read each bell's hour and minute from EEPROM_BELL_TIMES_START_ADDR (address 1) onwards. 181 * It also loads the lunch bell index from EEPROM_LUNCH_BELL_ADDR (address 50), 182 * the break count from EEPROM_BREAK_COUNT_ADDR (address 100), 183 * and the individual break bell indexes from EEPROM_BREAK_INDEXES_START_ADDR (address 101) onwards. 184 */ 185void loadBellAndBreakData() { 186 // Load total bells 187 totalBells = EEPROM.read(EEPROM_TOTAL_BELLS_ADDR); 188 if (totalBells < 0 || totalBells > MAX_BELLS) { // Basic validation 189 totalBells = 0; // Reset if invalid 190 Serial.println(F("EEPROM: Invalid totalBells in EEPROM, resetting to 0.")); 191 } 192 193 // Load bell times (already handled in checkBellTimes and displayNextBellInfo) 194 // EEPROM.read(i * 2) and EEPROM.read(i * 2 + 1) directly in those functions. 195 196 // Load lunch bell index 197 lunchBell = EEPROM.read(EEPROM_LUNCH_BELL_ADDR); 198 if (lunchBell < -1 || lunchBell >= totalBells) { // Validate lunchBell index 199 lunchBell = -1; // Reset to no lunch bell if invalid 200 } 201 202 // Load break count 203 breakCount = EEPROM.read(EEPROM_BREAK_COUNT_ADDR); 204 if (breakCount < 0 || breakCount > MAX_BELLS) { // Basic validation 205 breakCount = 0; // Reset if invalid 206 Serial.println(F("EEPROM: Invalid breakCount in EEPROM, resetting to 0.")); 207 } 208 209 // Load break indexes 210 for (int i = 0; i < breakCount; i++) { 211 breakIndexes[i] = EEPROM.read(EEPROM_BREAK_INDEXES_START_ADDR + i); 212 if (breakIndexes[i] < 0 || breakIndexes[i] >= totalBells) { 213 // Handle invalid break index, perhaps set to a default or skip 214 breakIndexes[i] = -1; // Mark as invalid 215 } 216 } 217 218 Serial.println(F("EEPROM: Bell and Break data loaded from EEPROM.")); 219 showBellInfo(); // Display loaded info for verification 220} 221 222/** 223 * @brief Saves bell times, total bells, lunch bell, break count, and break indexes to EEPROM. 224 * 225 * This function is called after `inputBellTimes()` and `inputBreaks()` to persist the data. 226 * It writes the `totalBells` at address 0, then `hour` and `minute` for each bell. 227 * It stores `lunchBell` index at address 50. 228 * It stores `breakCount` at address 100, then individual break `indexes` from address 101 onwards. 229 */ 230void saveBellAndBreakData() { 231 // Save total bells 232 EEPROM.write(EEPROM_TOTAL_BELLS_ADDR, totalBells); 233 234 // Save bell times (this logic is within inputBellTimes, but can be a dedicated function too) 235 // For simplicity, inputBellTimes already writes directly to EEPROM. 236 237 // Save lunch bell index 238 EEPROM.write(EEPROM_LUNCH_BELL_ADDR, lunchBell); 239 240 // Save break count 241 EEPROM.write(EEPROM_BREAK_COUNT_ADDR, breakCount); 242 243 // Save break indexes 244 for (int i = 0; i < breakCount; i++) { 245 EEPROM.write(EEPROM_BREAK_INDEXES_START_ADDR + i, breakIndexes[i]); 246 } 247 Serial.println(F("EEPROM: Bell and Break data saved to EEPROM.")); 248} 249 250// --- Bell Time and Display Functions --- 251void checkBellTimes(DateTime now) { 252 // Reset the minute tracker if we’ve moved to a new minute 253 static int lastCheckedMinute = -1; 254 if (now.minute() != lastCheckedMinute) { 255 lastCheckedMinute = now.minute(); 256 lastTriggeredMinute = -1; // Allow a new bell to trigger 257 } 258 259 // Only check for bell triggers if not ringing or waiting for message 260 if (!bellIsRinging && bellDisplayDelayTime == 0) { 261 bellActive = false; 262 263 for (int i = 0; i < totalBells; i++) { 264 int hh = EEPROM.read(EEPROM_BELL_TIMES_START_ADDR + i * 2); 265 int mm = EEPROM.read(EEPROM_BELL_TIMES_START_ADDR + i * 2 + 1); 266 267 // 🔔 Trigger bell if time matches and not already triggered this minute 268 if (now.hour() == hh && now.minute() == mm && now.second() <= 15 && now.minute() != lastTriggeredMinute) { 269 Serial.print(F("CHECK_BELL: Bell matched for index ")); 270 Serial.print(i); 271 Serial.print(F(" at ")); 272 Serial.print(hh); 273 Serial.print(":"); 274 Serial.print(mm); 275 Serial.println(); 276 277 getBellMessage(i, hh, mm, currentBellMessage, sizeof(currentBellMessage)); 278 bellNeedsToRing = true; 279 currentBellIndex = i; 280 lastTriggeredMinute = now.minute(); // Prevent duplicate rings 281 return; 282 } 283 284 // 🕒 Show countdown in last 15 seconds 285 if (now.hour() == hh && now.minute() == mm && now.second() >= 45 && now.second() < 60) { 286 bellActive = true; 287 char countdownBuf[20]; 288 sprintf(countdownBuf, "Bell in %ds", (60 - now.second())); 289 displayMessage(countdownBuf); 290 } 291 } 292 293 // 📢 Show next upcoming bell if no bell is active 294 if (!bellActive) { 295 displayNextBellInfo(now); 296 } 297 } 298} 299 300 301// Modified to write directly to a provided char buffer 302void getBellMessage(int index, int hh, int mm, char* buffer, size_t bufferSize) { 303 // Count regular bells before the current index 304 int regularBellCount = 0; 305 for (int i = 0; i < index; i++) { 306 if (i != 0 && // Not the Prayer bell 307 i != totalBells - 1 && // Not the End bell 308 i != lunchBell && 309 !isBreak(i)) { 310 regularBellCount++; 311 } 312 } 313 314 if (index == 0) sprintf(buffer, "Prayer @%d:%02d", hh, mm); 315 else if (index == totalBells - 1) sprintf(buffer, "End @%d:%02d", hh, mm); 316 else if (index == lunchBell) sprintf(buffer, "Lunch @%d:%02d", hh, mm); 317 else if (isBreak(index)) sprintf(buffer, "Break @%d:%02d", hh, mm); 318 else sprintf(buffer, "Bell %d @%d:%02d", regularBellCount + 1, hh, mm); // Display bell number starting from 1 319} 320 321 322void displayNextBellInfo(DateTime now) { 323 char msgBuffer[40]; // Increased buffer size for potentially longer "Next Bell in XX min" messages 324 325 // Find the next upcoming bell 326 for (int i = 0; i < totalBells; i++) { 327 int hh = EEPROM.read(EEPROM_BELL_TIMES_START_ADDR + i * 2); 328 int mm = EEPROM.read(EEPROM_BELL_TIMES_START_ADDR + i * 2 + 1); 329 330 // If the bell hour is greater than current hour, or same hour but minutes are greater 331 if (hh > now.hour() || (hh == now.hour() && mm > now.minute())) { 332 char tempBellMsg[20]; // Temporary buffer for just the "Bell X @ HH:MM" part 333 getBellMessage(i, hh, mm, tempBellMsg, sizeof(tempBellMsg)); 334 335 // Calculate time difference in minutes 336 long currentMins = now.hour() * 60 + now.minute(); 337 long bellMins = hh * 60 + mm; 338 long minsLeft = bellMins - currentMins; 339 340 if (minsLeft > 0) { 341 sprintf(msgBuffer, "Next %s in %ld min", tempBellMsg, minsLeft); 342 } else { 343 // This case should ideally not happen if logic is perfect, but as a fallback 344 sprintf(msgBuffer, "Next %s", tempBellMsg); 345 } 346 displayMessage(msgBuffer); 347 break; // Found the next bell, so exit the loop 348 } 349 } 350} 351 352/** 353 * @brief Checks if a given bell index is marked as a break bell. 354 * 355 * This function iterates through the `breakIndexes` array, which holds 356 * the indices of bells designated as breaks. 357 * 358 * @param index The bell index to check. 359 * @return True if the index corresponds to a break bell, false otherwise. 360 */ 361bool isBreak(int index) { 362 for (int i = 0; i < breakCount; i++) { 363 if (breakIndexes[i] == index) return true; 364 } 365 return false; 366} 367 368// --- Implementation of Overloaded displayMessage functions --- 369 370// Function to display a message from RAM (const char*) 371void displayMessage(const char* msg) { 372 led_module.selectFont(DEFAULT_FONT); // Use the default smaller font 373 led_module.clearScreen(true); 374 // drawMarquee expects a char* 375 led_module.drawMarquee((char*)msg, strlen(msg), 32 * COLUMN, 0); 376 long start = millis(); 377 long timing = start; 378 bool done = false; 379 while (!done) { 380 if ((timing + 70) < millis()) { // Controls marquee speed 381 done = led_module.stepMarquee(-1, 0); 382 timing = millis(); 383 } 384 yield(); // Allow background tasks (like Timer1 interrupt) to run 385 } 386 // No extra pause after marquee, the loop will handle delays for clock or next bell 387} 388 389// Overloaded function to display a message from PROGMEM (const __FlashStringHelper*) 390void displayMessage(const __FlashStringHelper* msg) { 391 led_module.selectFont(DEFAULT_FONT); 392 led_module.clearScreen(true); 393 // For PROGMEM strings, cast to (const char PROGMEM *) and use strlen_P 394 led_module.drawMarquee((const char PROGMEM *)msg, strlen_P((const char PROGMEM *)msg), 32 * COLUMN, 0); 395 long start = millis(); 396 long timing = start; 397 bool done = false; 398 while (!done) { 399 if ((timing + 70) < millis()) { // Controls marquee speed 400 done = led_module.stepMarquee(-1, 0); 401 timing = millis(); 402 } 403 yield(); // Allow background tasks (like Timer1 interrupt) to run 404 } 405 // No extra pause after marquee, the loop will handle delays for clock or next bell 406} 407 408// --- End of Overloaded displayMessage functions --- 409 410 411void displayDate(DateTime now) { 412 char dateStr[17]; 413 sprintf(dateStr, "Date:%02d/%02d/%04d", now.day(), now.month(), now.year()); 414 displayMessage(dateStr); // Pass char array directly 415} 416 417void displayTime(DateTime now) { 418 char timeStr[17]; 419 sprintf(timeStr, "Time:%02d:%02d:%02d", now.hour(), now.minute(), now.second()); 420 displayMessage(timeStr); // Pass char array directly 421} 422 423// --- Menu and Input Functions --- 424 425void showMenu() { 426 BTSerial.println(F("\n===== MENU =====")); 427 BTSerial.println(F("(1) Update Date and Time")); 428 BTSerial.println(F("(2) Enter Bell Times")); 429 BTSerial.println(F("(3) Enter Breaks")); 430 BTSerial.println(F("(4) Clear EEPROM")); 431 BTSerial.println(F("(5) Show Bell Info")); 432 BTSerial.println(F("(6) Trigger Bell Now")); 433 BTSerial.println(F("(7) Start Exam Timer")); 434 BTSerial.println(F("(8) Quit")); 435 BTSerial.println(F("Enter your choice (1-8):")); 436 437 while (true) { 438 // Wait for input 439 while (!BTSerial.available()) { 440 delay(10); // Prevent busy-waiting 441 } 442 443 // Read and clean input 444 String choice = BTSerial.readStringUntil('\n'); // Read into a temporary String 445 choice.trim(); 446 447 // Ignore empty input 448 if (choice.length() == 0) { 449 BTSerial.println(F("No input received. Try again.")); 450 continue; 451 } 452 453 int option = choice.toInt(); 454 BTSerial.print(F("You selected: ")); 455 BTSerial.println(option); 456 457 switch (option) { 458 case 1: updateRTCFromBluetooth(); break; 459 case 2: inputBellTimes(); break; 460 case 3: inputBreaks(); break; 461 case 4: clearEEPROM(); break; 462 case 5: showBellInfo(); break; 463 case 6: manualBellTrigger(); break; 464 case 7: startExamCountdown(); break; 465 case 8: // Quit option 466 BTSerial.println(F("Exiting menu...")); 467 return; 468 default: 469 BTSerial.println(F("Invalid input. Please enter a number between 1 and 8.")); 470 break; 471 } 472 473 // After an option is handled, re-display the menu and prompt for next choice 474 BTSerial.println(F("\n===== MENU =====")); 475 BTSerial.println(F("(1) Update Date and Time")); 476 BTSerial.println(F("(2) Enter Bell Times")); 477 BTSerial.println(F("(3) Enter Breaks")); 478 BTSerial.println(F("(4) Clear EEPROM")); 479 BTSerial.println(F("(5) Show Bell Info")); 480 BTSerial.println(F("(6) Trigger Bell Now")); 481 BTSerial.println(F("(7) Start Exam Timer")); 482 BTSerial.println(F("(8) Quit")); 483 BTSerial.println(F("Enter your choice (1-8):")); 484 } 485} 486//////////////////////////////////////////// 487 488//////////////////////////////////////////////////////////////// 489 490void startExamCountdown() { 491 int examDurationSeconds = 0; 492 int hh = 0, mm = 0, ss = 00; 493 char hhStr[3], mmStr[3], ssStr[3], colonChar[2] = ":"; 494 bool showColon = true; 495 496 // Ask user for HH:MM input 497 BTSerial.println(F("Enter Exam Duration in HH:MM format:")); 498 while (!BTSerial.available()) delay(10); 499 String durationInput = BTSerial.readStringUntil('\n'); 500 durationInput.trim(); 501 502 // Parse HH and MM from input 503 int colonIndex = durationInput.indexOf(':'); 504 if (colonIndex == -1) { 505 BTSerial.println(F("Invalid format. Use HH:MM.")); 506 return; 507 } 508 509 hh = durationInput.substring(0, colonIndex).toInt(); 510 mm = durationInput.substring(colonIndex + 1).toInt(); 511 examDurationSeconds = (hh * 3600) + (mm * 60) + ss; 512 513 BTSerial.print(F("Duration set to: ")); 514 BTSerial.print(hh); 515 BTSerial.print(F("h ")); 516 BTSerial.print(mm); 517 //BTSerial.println(F("m")); 518 519 led_module.selectFont(SystemFont5x7); 520 displayMessage(("Exam started")); 521 delay(100); 522 523 // Start buzzer/relay 524 digitalWrite(BUZZER_PIN, HIGH); 525 digitalWrite(RELAY_PIN, HIGH); 526 delay(2000); 527 digitalWrite(BUZZER_PIN, LOW); 528 digitalWrite(RELAY_PIN, LOW); 529 530 while (examDurationSeconds >= 0) { 531 // Check for "stop" 532 if (BTSerial.available()) { 533 String cmd = BTSerial.readStringUntil('\n'); 534 cmd.trim(); 535 if (cmd.equalsIgnoreCase("stop")) { 536 BTSerial.println(F("Exam timer stopped.")); 537 displayMessage(("Timer Stopped")); 538 return; 539 } 540 } 541 542 // Convert time 543 int dispHH = examDurationSeconds / 3600; 544 int dispMM = (examDurationSeconds % 3600) / 60; 545 int dispSS = examDurationSeconds % 60; 546 547 sprintf(hhStr, "%02d", dispHH); 548 sprintf(mmStr, "%02d", dispMM); 549 sprintf(ssStr, "%02d", dispSS); 550 551 led_module.clearScreen(true); 552 553 // Draw HH centered on top row (approx x=13 on 32x16 display) 554 led_module.drawString(13, 0, hhStr, strlen(hhStr), GRAPHICS_NORMAL); 555 556 // Draw MM and SS on bottom row 557 led_module.drawString(0, 8, mmStr, strlen(mmStr), GRAPHICS_NORMAL); // MM left 558 if (showColon) { 559 led_module.drawString(13, 8, colonChar, strlen(colonChar), GRAPHICS_NORMAL); // Blinking colon 560 } else { 561 led_module.drawString(13, 8, " ", 1, GRAPHICS_NORMAL); 562 } 563 led_module.drawString(20, 8, ssStr, strlen(ssStr), GRAPHICS_NORMAL); // SS right 564 565 showColon = !showColon; 566 delay(1000); 567 examDurationSeconds--; 568 } 569 570 // Exam over 571 displayMessage(("Exam. Time Over")); 572 digitalWrite(BUZZER_PIN, HIGH); 573 digitalWrite(RELAY_PIN, HIGH); 574 delay(5000); 575 digitalWrite(BUZZER_PIN, LOW); 576 digitalWrite(RELAY_PIN, LOW); 577} 578 579 580//////////////////////////// 581 582 583void updateRTCFromBluetooth() { 584 BTSerial.println(F("Enter Date (DD/MM/YYYY): ")); 585 String dateStr = ""; 586 while (dateStr.length() == 0) { 587 if (BTSerial.available()) { 588 dateStr = BTSerial.readStringUntil('\n'); 589 dateStr.trim(); 590 } 591 } 592 593 BTSerial.println(F("Enter Time (HH:MM:SS - 24hr format): ")); 594 String timeStr = ""; 595 while (timeStr.length() == 0) { 596 if (BTSerial.available()) { 597 timeStr = BTSerial.readStringUntil('\n'); 598 timeStr.trim(); 599 } 600 } 601 602 // Parse date and time strings 603 int day = dateStr.substring(0, 2).toInt(); 604 int month = dateStr.substring(3, 5).toInt(); 605 int year = dateStr.substring(6, 10).toInt(); 606 int hour = timeStr.substring(0, 2).toInt(); 607 int minute = timeStr.substring(3, 5).toInt(); 608 int second = timeStr.substring(6, 8).toInt(); 609 610 rtc.adjust(DateTime(year, month, day, hour, minute, second)); 611 BTSerial.println(F("RTC updated successfully.")); 612 displayMessage(F("RTC Updated!")); // Now calls the overloaded function 613 614} 615 616/** 617 * @brief Prompts user for bell times via Bluetooth and saves them to EEPROM. 618 * 619 * This function handles user input for the total number of bells, and then 620 * for each bell, its hour and minute. It stores this data directly into EEPROM. 621 * It also asks for the lunch bell index and saves it. 622 */ 623void inputBellTimes() { 624 BTSerial.print(F("Enter total number of bells (max ")); 625 BTSerial.print(MAX_BELLS); 626 BTSerial.println(F("):")); 627 while (!BTSerial.available()) delay(10); // Wait for input 628 totalBells = BTSerial.readStringUntil('\n').toInt(); 629 630 if (totalBells < 0 || totalBells > MAX_BELLS) { 631 BTSerial.println(F("Invalid number of bells. Setting to 0.")); 632 totalBells = 0; 633 } 634 635 // Save total bells to EEPROM 636 EEPROM.write(EEPROM_TOTAL_BELLS_ADDR, totalBells); 637 638 for (int i = 0; i < totalBells; i++) { 639 BTSerial.print(F("Time for bell ")); 640 BTSerial.print(i + 1); // Display bell number starting from 1 641 BTSerial.println(F(" (HH:MM - 24hr format):")); 642 while (!BTSerial.available()) delay(10); // Wait for input 643 String time = BTSerial.readStringUntil('\n'); // Read into a temporary String 644 time.trim(); 645 646 int hh = time.substring(0, 2).toInt(); 647 int mm = time.substring(3, 5).toInt(); 648 649 // Basic validation 650 if (hh < 0 || hh > 23 || mm < 0 || mm > 59) { 651 BTSerial.println(F("Invalid time. Setting to 00:00 for this bell.")); 652 hh = 0; 653 mm = 0; 654 } 655 656 // Store hour and minute in EEPROM 657 EEPROM.write(EEPROM_BELL_TIMES_START_ADDR + i * 2, hh); 658 EEPROM.write(EEPROM_BELL_TIMES_START_ADDR + i * 2 + 1, mm); 659 } 660 661 BTSerial.print(F("Which bell is lunch? Enter its number (1 to ")); 662 BTSerial.print(totalBells); 663 BTSerial.println(F("), or -1 for none:")); 664 while (!BTSerial.available()) delay(10); // Wait for input 665 lunchBell = BTSerial.readStringUntil('\n').toInt(); 666 // Adjust for 0-indexed array if user inputs 1-indexed number 667 if (lunchBell > 0 && lunchBell <= totalBells) { 668 lunchBell--; // Convert to 0-indexed 669 } else { 670 lunchBell = -1; // Invalid input or no lunch bell 671 } 672 EEPROM.write(EEPROM_LUNCH_BELL_ADDR, lunchBell); 673 674 BTSerial.println(F("Bell times entered and saved.")); 675 saveBellAndBreakData(); // Ensure all related data is saved 676} 677 678/** 679 * @brief Prompts user for break bell indexes and saves them to EEPROM. 680 * 681 * This function handles user input for the number of short breaks and then 682 * the specific bell index (0-indexed) for each break. It stores this data 683 * in the `breakIndexes` array and then persists it to EEPROM. 684 */ 685void inputBreaks() { 686 BTSerial.print(F("How many short breaks (max ")); 687 BTSerial.print(MAX_BELLS); 688 BTSerial.println(F(")?")); 689 while (!BTSerial.available()) delay(10); // Wait for input 690 breakCount = BTSerial.readStringUntil('\n').toInt(); 691 692 if (breakCount < 0 || breakCount > MAX_BELLS) { 693 BTSerial.println(F("Invalid number of breaks. Setting to 0.")); 694 breakCount = 0; 695 } 696 697 // Save break count to EEPROM 698 EEPROM.write(EEPROM_BREAK_COUNT_ADDR, breakCount); 699 700 for (int i = 0; i < breakCount; i++) { 701 BTSerial.print(F("Enter bell number for break #")); 702 BTSerial.print(i + 1); 703 BTSerial.print(F(" (1 to ")); 704 BTSerial.print(totalBells); 705 BTSerial.println(F("):")); 706 while (!BTSerial.available()) delay(10); // Wait for input 707 int bellNum = BTSerial.readStringUntil('\n').toInt(); 708 709 // Convert 1-indexed user input to 0-indexed internal array 710 if (bellNum > 0 && bellNum <= totalBells) { 711 breakIndexes[i] = bellNum - 1; 712 } else { 713 BTSerial.println(F("Invalid bell number. Setting to -1 for this break.")); 714 breakIndexes[i] = -1; // Mark as invalid 715 } 716 // Save break index to EEPROM 717 EEPROM.write(EEPROM_BREAK_INDEXES_START_ADDR + i, breakIndexes[i]); 718 } 719 BTSerial.println(F("Break times entered and saved.")); 720 saveBellAndBreakData(); // Ensure all related data is saved 721} 722 723/** 724 * @brief Clears relevant EEPROM addresses and resets bell/break counts. 725 * 726 * This function effectively "wipes" the stored bell and break data 727 * by writing 0 to the respective count locations and a range of addresses 728 * where bell and break data might reside. 729 */ 730void clearEEPROM() { 731 totalBells = 0; 732 lunchBell = -1; 733 breakCount = 0; 734 735 // Clear relevant EEPROM sections by writing 0s 736 // Clear total bells and bell times (up to MAX_BELLS * 2 bytes) 737 for (int i = EEPROM_TOTAL_BELLS_ADDR; i < EEPROM_BELL_TIMES_START_ADDR + MAX_BELLS * 2; i++) { 738 EEPROM.write(i, 0); 739 } 740 // Clear lunch bell address 741 EEPROM.write(EEPROM_LUNCH_BELL_ADDR, 0); // Or -1 if that's a preferred clear state for lunchBell 742 743 // Clear break count and break indexes (up to MAX_BELLS bytes) 744 for (int i = EEPROM_BREAK_COUNT_ADDR; i < EEPROM_BREAK_INDEXES_START_ADDR + MAX_BELLS; i++) { 745 EEPROM.write(i, 0); 746 } 747 748 BTSerial.println(F("EEPROM cleared for bell and break data.")); 749 displayMessage(F("EEPROM Cleared!")); // Now calls the overloaded function 750} 751 752/** 753 * @brief Displays the currently loaded bell and break information via Bluetooth. 754 * 755 * This function reads the stored data (from the global variables `totalBells`, 756 * `lunchBell`, `breakCount`, and `breakIndexes`) and then iterates to read 757 * each bell's hour and minute directly from EEPROM for verification. 758 */ 759void showBellInfo() { 760 BTSerial.println(F("\n--- Current Bell Info ---")); 761 BTSerial.print(F("Total Bells: ")); 762 BTSerial.println(totalBells); 763 764 if (totalBells > 0) { 765 BTSerial.println(F("Bell Times:")); 766 int currentRegularBellNumber = 1; // Start counting regular bells from 1 767 for (int i = 0; i < totalBells; i++) { 768 int hh = EEPROM.read(EEPROM_BELL_TIMES_START_ADDR + i * 2); 769 int mm = EEPROM.read(EEPROM_BELL_TIMES_START_ADDR + i * 2 + 1); 770 char bellTimeStr[40]; // Buffer for the full message 771 772 if (i == 0) { 773 sprintf(bellTimeStr, "Prayer @%d:%02d", hh, mm); 774 } else if (i == totalBells - 1) { 775 sprintf(bellTimeStr, "End @%d:%02d", hh, mm); 776 } else if (i == lunchBell) { 777 sprintf(bellTimeStr, "Lunch @%d:%02d", hh, mm); 778 } else if (isBreak(i)) { 779 sprintf(bellTimeStr, "Break @%d:%02d", hh, mm); 780 } else { 781 // This is a regular bell, use the currentRegularBellNumber 782 sprintf(bellTimeStr, "Bell %d @%d:%02d", currentRegularBellNumber, hh, mm); 783 currentRegularBellNumber++; // Increment for the next regular bell 784 } 785 BTSerial.print(F(" Index ")); 786 BTSerial.print(i); // Display the actual internal index 787 BTSerial.print(F(": ")); 788 BTSerial.println(bellTimeStr); // Print the formatted string 789 } 790 } else { 791 BTSerial.println(F("No bell times set.")); 792 } 793 794 BTSerial.print(F("Lunch Bell Index (0-indexed): ")); 795 BTSerial.println(lunchBell); 796 797 BTSerial.print(F("Short Breaks Count: ")); 798 BTSerial.println(breakCount); 799 if (breakCount > 0) { 800 BTSerial.println(F("Break Bell Indexes (0-indexed):")); 801 for (int i = 0; i < breakCount; i++) { 802 BTSerial.print(F(" Break ")); 803 BTSerial.print(i + 1); 804 BTSerial.print(F(": Bell Index ")); // Clarify it's the index 805 BTSerial.println(breakIndexes[i]); 806 } 807 } else { 808 BTSerial.println(F("No short breaks set.")); 809 } 810 BTSerial.println(F("-------------------------")); 811} 812 813void manualBellTrigger() { 814 led_module.selectFont(SystemFont5x7); // Use SystemFont5x7 for clearer digits 815 displayMessage("Emergency"); // Display message AFTER the ring 816 led_module.clearScreen(true); 817 delay(100); 818 Serial.println(F("MANUAL_BELL: Manual bell trigger requested.")); // Debug 819 digitalWrite(BUZZER_PIN, HIGH); 820 digitalWrite(RELAY_PIN, HIGH); 821 Serial.println(F("MANUAL_BELL: Buzzer/Relay HIGH.")); // Debug 822 delay(2000); // Ring for 2 seconds (blocking delay is acceptable for manual trigger) 823 digitalWrite(BUZZER_PIN, LOW); 824 digitalWrite(RELAY_PIN, LOW); 825 Serial.println(F("MANUAL_BELL: Buzzer/Relay LOW.")); // Debug 826 led_module.clearScreen(true); 827 delay(100); 828 displayMessage("Manual Bell Triggered!"); // Display message AFTER the ring 829 led_module.clearScreen(true); 830 delay(100); 831 BTSerial.println(F("Manual bell triggered.")); 832}
Comments
Only logged in users can leave comments