ESP32 Smart School Timetable Bell System with OLED, Web UI & RTC

This ESP32 School Period Alarm System is a smart, customizable bell scheduler designed to automate school periods, recesses, and end-of-day notifications. With built-in real-time clock (RTC), web-based configuration, and OLED status display, it eliminates the need for manual bell ringing or mechanical timers. This project is ideal for schools, coaching centers, and even factories where regular timed alerts are essential, all built using affordable and readily available components.

โœ… Key Benefits

  • โฑ Accurate Scheduling: Uses a DS3231 RTC to keep precise time.
  • ๐Ÿ“ฑ Easy Configuration: Web interface allows dynamic editing of periods, recess, and off-time via a smartphone.
  • ๐Ÿ” Per-Day Customization: Different schedules for each weekday make it ideal for schools with varied timetables.
  • ๐Ÿ’พ Non-Volatile Memory: Stores schedule in EEPROM โ€” settings remain saved after power loss.
  • ๐Ÿ”Š Automated Alerts: Buzzer rings automatically, reducing human error and increasing consistency.
  • ๐Ÿ“Ÿ Clear Display: OLED screen shows real-time status and upcoming period alerts.

The ESP32 School Period Alarm System works as a smart scheduler and bell system for schools. Here’s a breakdown of how it functions:

๐Ÿง  Core Functionality

  • ESP32 is the brain of the system, controlling timing, alarms, and the web interface.
  • DS3231 RTC Module keeps accurate real-time clock data, even if the ESP32 resets.
  • OLED 128×64 Display shows:
    • Current time in 12-hour format
    • Recess and off-time
    • Upcoming periods (e.g., 1 - 08:00 AM, 2 - 08:30 AM, etc.)
  • Buzzer rings:
    • Short beeps indicating the period number (e.g., 3 beeps for 3rd period)
    • Long continuous buzz for 30 seconds at recess and off time

๐Ÿ“ฒ Web Interface (via Smartphone)

  • Accessed via ESP32โ€™s Wi-Fi hotspot (ESP32-Alarm)
  • From a browser, you can:
    • View and set recess time
    • Set off-time
    • Dynamically add/edit up to 12 periods per day
    • Save settings to EEPROM
  • JavaScript enables adding more period fields on the fly.

๐Ÿ“… Per-Day Scheduling

  • Each day of the week has its own independent set of up to 12 periods
  • Settings are remembered and retrieved daily based on RTCโ€™s day-of-week

๐Ÿ”˜ Physical Controls

  • Three buttons:
    • ADD: Adds the current time as a new period
    • REMOVE: Removes the last period
    • ADJUST: Cycles through periods to adjust time
  • Optionally, rocker switch replaces a button for on/off or manual override

๐Ÿ’พ Persistent Memory

  • All period times, recess, and off-time are stored in EEPROM
  • Even after power loss, the system retains its schedule

๐Ÿ” Daily Operation

  1. RTC gives current time.
  2. At scheduled times, the buzzer rings.
  3. Display updates with the next period info.
  4. User can update schedule anytime via the web interface.

Here’s a complete outline and implementation plan for your ESP32-based School Lecture Period Alarm System with:

โœ… Key Features:

  • ESP32 microcontroller
  • 128×64 I2C OLED display (SSD1306)
  • Buzzer
  • Push buttons to manually add/remove/adjust
  • Web interface to configure periods remotely
  • EEPROM to save and load period schedule
  • RTC optional (ESP32 can use its own RTC + NTP sync if desired)

๐Ÿงฐ Hardware Components

ComponentNotes
ESP32 Dev BoardWi-Fi support
OLED 128×64 I2CSSD1306
BuzzerActive buzzer to GPIO
3 Push ButtonsFor Add / Remove / Adjust
Optional RTCDS3231 (if no internet time)

๐Ÿงฑ Wiring Summary

ComponentESP32 Pin
OLED SDAGPIO 21
OLED SCLGPIO 22
BuzzerGPIO 27
Add ButtonGPIO 32
Remove ButtonGPIO 33
Adjust ButtonGPIO 25

๐Ÿงพ Libraries Required

Install these in Arduino IDE:

  • Adafruit_SSD1306
  • Adafruit_GFX
  • WiFi
  • WebServer
  • EEPROM (built-in)
  • time.h (for NTP time)

๐Ÿง  Program Structure

  1. Boot ESP32
  2. Load period schedule from EEPROM
  3. Sync time using NTP
  4. Start web server for configuration
  5. Continuously:
    • Display time and next period on OLED
    • Ring buzzer at period start
    • Respond to button input
    • Save changes to EEPROM

๐Ÿ’ป Web Interface

  • ESP32 hosts a simple web form:
    • Add/Remove periods
    • Set start times (HH:MM)
    • Save to EEPROM

This sketch includes:

  • โœ… DS3231 RTC integration
  • โœ… 128×64 OLED time + period display
  • โœ… EEPROM saving/loading for 12 periods
  • โœ… Buzzer beeping period number times
  • โœ… Add/Remove/Adjust buttons
  • โœ… 12-hour format
  • โœ… Web interface to view/edit times

๐Ÿ’ก Tip:

Use an external pull-down resistor (~10kฮฉ) on each button if you get unreliable readings, or ensure clean debouncing if needed.

โœ… Arduino School Periods System (complete sketch)

// ESP32 School Period Alarm System with Recess, Off-Time & Day Scheduling
// Features: DS3231 RTC, 128x64 OLED, EEPROM, Web Interface, Buzzer, Per-Day Scheduling

#include <Wire.h>
#include <RTClib.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <WiFi.h>
#include <WebServer.h>
#include <EEPROM.h>

// ==== CONFIGURATION ====
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET    -1
#define I2C_SDA       21
#define I2C_SCL       22
#define BUZZER_PIN    27
#define BTN_ADD       32
#define BTN_REM       33
#define BTN_ADJ       25
#define MAX_PERIODS   12
#define DAYS          7
#define EEPROM_SIZE   (MAX_PERIODS * 2 * DAYS + 4) // 4 extra bytes for recess and off times

const char* ssid = "ESP32-Alarm";
const char* password = "12345678";

RTC_DS3231 rtc;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
WebServer server(80);
uint8_t periods[DAYS][MAX_PERIODS][2];
int periodCounts[DAYS];
bool alarmTriggered[DAYS][MAX_PERIODS];
bool recessTriggered = false;
bool offTriggered = false;
uint8_t recessTime[2];
uint8_t offTime[2];
unsigned long lastDebounce = 0;
int selectedPeriod = 0;
unsigned long lastPageChange = 0;
int currentPage = 0;

void loadPeriodsFromEEPROM();
void savePeriodsToEEPROM();
void ringBuzzer(int times);
void ringBuzzerLong();
void handleWebRoot();
void handleWebSubmit();
void updateDisplay();
void checkPeriodTrigger(DateTime now);
void handleButtons(DateTime now);
void sortPeriods();

void setup() {
  Serial.begin(115200);
  Wire.begin(I2C_SDA, I2C_SCL);
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(BTN_ADD, INPUT_PULLUP);
  pinMode(BTN_REM, INPUT_PULLUP);
  pinMode(BTN_ADJ, INPUT_PULLUP);
  EEPROM.begin(EEPROM_SIZE);

  if (!rtc.begin()) while (1);
  if (rtc.lostPower()) rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) while (1);

  loadPeriodsFromEEPROM();
  for (int d = 0; d < DAYS; d++) sortPeriods(d);
  for (int d = 0; d < DAYS; d++) for (int i = 0; i < MAX_PERIODS; i++) alarmTriggered[d][i] = false;

  WiFi.softAP(ssid, password);
  server.on("/", handleWebRoot);
  server.on("/submit", handleWebSubmit);
  server.begin();
}

void loop() {
  DateTime now = rtc.now();
  updateDisplay();
  checkPeriodTrigger(now);
  handleButtons(now);
  server.handleClient();
  delay(200);
}

void updateDisplay() {
  DateTime now = rtc.now();
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(WHITE);

  char buf[20];
  int hr = now.hour();
  bool pm = false;
  if (hr >= 12) pm = true;
  if (hr == 0) hr = 12;
  else if (hr > 12) hr -= 12;
  sprintf(buf, "Time: %02d:%02d %s", hr, now.minute(), pm ? "PM" : "AM");
  display.setCursor(0, 0);
  display.print(buf);

  display.setCursor(0, 10);
  sprintf(buf, "Recess: %02d:%02d", recessTime[0], recessTime[1]);
  display.print(buf);

  display.setCursor(0, 20);
  sprintf(buf, "Off:    %02d:%02d", offTime[0], offTime[1]);
  display.print(buf);

  // Update page every 5 seconds
  if (millis() - lastPageChange > 5000) {
    currentPage = (currentPage + 1) % ((periodCount + 5) / 6); // 6 per page
    lastPageChange = millis();
  }

  // Show up to 6 periods per page
  int startIdx = currentPage * 6;
  int endIdx = min(startIdx + 6, periodCount);
  for (int i = startIdx, y = 30; i < endIdx; i++, y += 6) {
    sprintf(buf, "P%d - %02d:%02d", i + 1, periods[i][0], periods[i][1]);
    display.setCursor(0, y);
    display.print(buf);
  }

  display.display();
}

void checkPeriodTrigger(DateTime now) {
  int d = now.dayOfTheWeek();
  for (int i = 0; i < periodCounts[d]; i++) {
    if (now.hour() == periods[d][i][0] && now.minute() == periods[d][i][1] && now.second() == 0) {
      if (!alarmTriggered[d][i]) {
        ringBuzzer(i + 1);
        alarmTriggered[d][i] = true;
      }
    } else if (now.minute() != periods[d][i][1]) {
      alarmTriggered[d][i] = false;
    }
  }
  if (now.hour() == recessTime[0] && now.minute() == recessTime[1] && now.second() == 0 && !recessTriggered) {
    ringBuzzerLong(); recessTriggered = true;
  } else if (now.minute() != recessTime[1]) recessTriggered = false;

  if (now.hour() == offTime[0] && now.minute() == offTime[1] && now.second() == 0 && !offTriggered) {
    ringBuzzerLong(); offTriggered = true;
  } else if (now.minute() != offTime[1]) offTriggered = false;
}

void ringBuzzer(int times) {
  for (int i = 0; i < times; i++) {
    digitalWrite(BUZZER_PIN, HIGH); delay(200);
    digitalWrite(BUZZER_PIN, LOW); delay(200);
  }
}

void ringBuzzerLong() {
  for (int i = 0; i < 30; i++) {
    digitalWrite(BUZZER_PIN, HIGH); delay(500);
    digitalWrite(BUZZER_PIN, LOW); delay(500);
  }
}

void loadPeriodsFromEEPROM() {
  int addr = 0;
  for (int d = 0; d < DAYS; d++) {
    periodCounts[d] = 0;
    for (int i = 0; i < MAX_PERIODS; i++) {
      uint8_t h = EEPROM.read(addr++);
      uint8_t m = EEPROM.read(addr++);
      if (h < 24 && m < 60) {
        periods[d][periodCounts[d]][0] = h;
        periods[d][periodCounts[d]][1] = m;
        periodCounts[d]++;
      }
    }
  }
  recessTime[0] = EEPROM.read(addr++);
  recessTime[1] = EEPROM.read(addr++);
  offTime[0] = EEPROM.read(addr++);
  offTime[1] = EEPROM.read(addr++);
}

void savePeriodsToEEPROM() {
  int addr = 0;
  for (int d = 0; d < DAYS; d++) {
    for (int i = 0; i < MAX_PERIODS; i++) {
      if (i < periodCounts[d]) {
        EEPROM.write(addr++, periods[d][i][0]);
        EEPROM.write(addr++, periods[d][i][1]);
      } else {
        EEPROM.write(addr++, 0xFF);
        EEPROM.write(addr++, 0xFF);
      }
    }
  }
  EEPROM.write(addr++, recessTime[0]);
  EEPROM.write(addr++, recessTime[1]);
  EEPROM.write(addr++, offTime[0]);
  EEPROM.write(addr++, offTime[1]);
  EEPROM.commit();
  for (int d = 0; d < DAYS; d++) sortPeriods(d);
}

void sortPeriods(int d) {
  for (int i = 0; i < periodCounts[d] - 1; i++) {
    for (int j = 0; j < periodCounts[d] - i - 1; j++) {
      if (periods[d][j][0] > periods[d][j + 1][0] ||
          (periods[d][j][0] == periods[d][j + 1][0] && periods[d][j][1] > periods[d][j + 1][1])) {
        uint8_t tmpH = periods[d][j][0];
        uint8_t tmpM = periods[d][j][1];
        periods[d][j][0] = periods[d][j + 1][0];
        periods[d][j][1] = periods[d][j + 1][1];
        periods[d][j + 1][0] = tmpH;
        periods[d][j + 1][1] = tmpM;
      }
    }
  }
}

void handleButtons(DateTime now) {
  int d = now.dayOfTheWeek();
  if (millis() - lastDebounce < 300) return;
  if (digitalRead(BTN_ADD) == LOW && periodCounts[d] < MAX_PERIODS) {
    periods[d][periodCounts[d]][0] = now.hour();
    periods[d][periodCounts[d]][1] = (now.minute() + 1) % 60;
    periodCounts[d]++;
    savePeriodsToEEPROM();
    lastDebounce = millis();
  }
  if (digitalRead(BTN_REM) == LOW && periodCounts[d] > 0) {
    periodCounts[d]--;
    savePeriodsToEEPROM();
    lastDebounce = millis();
  }
  if (digitalRead(BTN_ADJ) == LOW && periodCounts[d] > 0) {
    selectedPeriod = (selectedPeriod + 1) % periodCounts[d];
    periods[d][selectedPeriod][0] = now.hour();
    periods[d][selectedPeriod][1] = now.minute();
    savePeriodsToEEPROM();
    lastDebounce = millis();
  }
}

void handleWebRoot() {
  String html = "<html><body><h2>Set Period Times</h2><form action='/submit' method='post'>";
  int d = rtc.now().dayOfTheWeek();
  for (int i = 0; i < periodCounts[d]; i++) {
    html += "Period " + String(i + 1) + ": <input name='p" + String(i) + "' value='" +
            String(periods[d][i][0]) + ":" + String(periods[d][i][1]) + "'><br>";
  }
  html += "<br>Recess Time: <input name='recess' value='" + String(recessTime[0]) + ":" + String(recessTime[1]) + "'><br>";
  html += "Off Time: <input name='off' value='" + String(offTime[0]) + ":" + String(offTime[1]) + "'><br><br>";
  html += "<input type='submit' value='Save'></form></body></html>";
  server.send(200, "text/html", html);
}

void handleWebSubmit() {
  int d = rtc.now().dayOfTheWeek();
  for (int i = 0; i < MAX_PERIODS; i++) {
    if (server.hasArg("p" + String(i))) {
      String val = server.arg("p" + String(i));
      int colon = val.indexOf(":");
      if (colon > 0) {
        int h = val.substring(0, colon).toInt();
        int m = val.substring(colon + 1).toInt();
        if (h < 24 && m < 60) {
          periods[d][i][0] = h;
          periods[d][i][1] = m;
        }
      }
    } else {
      if (i < periodCounts[d]) {
        periods[d][i][0] = 0xFF;
        periods[d][i][1] = 0xFF;
      }
    }
  }
  periodCounts[d] = 0;
  for (int i = 0; i < MAX_PERIODS; i++) {
    if (periods[d][i][0] < 24 && periods[d][i][1] < 60) periodCounts[d]++;
  }
  if (server.hasArg("recess")) {
    String val = server.arg("recess");
    int colon = val.indexOf(":");
    if (colon > 0) {
      recessTime[0] = val.substring(0, colon).toInt();
      recessTime[1] = val.substring(colon + 1).toInt();
    }
  }
  if (server.hasArg("off")) {
    String val = server.arg("off");
    int colon = val.indexOf(":");
    if (colon > 0) {
      offTime[0] = val.substring(0, colon).toInt();
      offTime[1] = val.substring(colon + 1).toInt();
    }
  }
  savePeriodsToEEPROM();
  server.sendHeader("Location", "/");
  server.send(303);
}

โœ… Conclusion

The ESP32 School Period Alarm System offers an efficient, modern solution for automating bell schedules in educational institutions. With real-time accuracy, day-wise flexibility, smartphone-based control, and reliable EEPROM storage, it simplifies school management and ensures punctuality.

Its intuitive interface and visual OLED feedback make it user-friendly for staff, while its customizable nature ensures it fits any school’s unique timetable. By integrating smart electronics into a practical application, this project showcases how technology can streamline daily operations and enhance discipline in academic environments.

๐Ÿ™Œ Help Us Spread the Word!

If you found this ESP32 School Period Alarm System useful or inspiring, please consider sharing it with your friends, colleagues, or fellow makers on social media. Your support helps promote practical tech solutions and encourages more open-source innovation in education!

๐Ÿ“ฑ๐Ÿ’ก Share on:

  • ๐Ÿ”— Facebook
  • ๐Ÿฆ Twitter
  • ๐Ÿ“ธ Instagram
  • ๐Ÿ“Œ Pinterest
  • ๐Ÿ’ฌ WhatsApp

Together, letโ€™s make schools smarter and more efficient! ๐ŸŽ“๐Ÿ“š๐Ÿ””

Feel free to include a picture of your own build and tag your post with #ESP32SchoolAlarm or #SmartSchoolBell!

๐ŸŽฏ Search-Intent Specific Keywords

  1. ESP32 Smart School Bell System with OLED, Web UI & RTC
  2. DIY School Period Alarm Using ESP32 and DS3231 RTC
  3. Automatic School Bell System with ESP32 and OLED Display
  4. Build a Wi-Fi School Bell Timer with ESP32 + Web Control
  5. ESP32-Based Timetable Alarm with Day Scheduling & Recess Alerts
  1. ESP32 Period Scheduler with Smartphone Web Interface
  2. Smart School Alarm with Recess & Off-Time Alerts
  3. Customizable School Bell System with EEPROM & RTC
  4. OLED Display Bell Timer with ESP32 and Web Management
  5. Create a Daily School Bell System with ESP32 (12 Periods Max)
  1. How to Make an Automatic School Period Alarm with ESP32
  2. ESP32 School Bell Project for Class Period Scheduling
  3. Web-Controlled School Alarm Using ESP32 and OLED
  4. ESP32 Wi-Fi Bell Timer with EEPROM Settings and RTC
  5. ESP32 Class Bell Scheduler with Day-of-Week Configuration

Relatest posts

Leave Comments

Top