#include <WiFi.h> // Library to connect ESP32 to WiFi
#include "time.h" // Library to get time via NTP
#include <Adafruit_GFX.h> // Graphics library for OLED
#include <Adafruit_SSD1306.h> // OLED display driver library
#include "DHT.h" // DHT temperature and humidity sensor library
#include <Keypad.h> // Library for keypad input
// =================================================================
// OLED Display configuration
#define SCREEN_WIDTH 128 // OLED display width in pixels
#define SCREEN_HEIGHT 64 // OLED display height in pixels
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); // Create OLED object
// =================================================================
// DHT Sensor configuration
#define DHTPIN 15 // Pin connected to DHT22 sensor
#define DHTTYPE DHT22 // Define DHT sensor type
DHT dht(DHTPIN, DHTTYPE); // Create DHT object
// =================================================================
// Hardware pins
#define BUZZER_PIN 32 // Pin connected to buzzer
#define LED_PIN 2 // Pin connected to LED
#define BUTTON_PIN 4 // Pin connected to alarm button
// =================================================================
// WiFi and NTP configuration
const char* ssid = "Wokwi-GUEST"; // WiFi SSID
const char* password = ""; // WiFi password
const char* ntpServer = "pool.ntp.org"; // NTP server
long gmtOffset_sec = 0; // Timezone offset in seconds
int daylightOffset_sec = 0; // Daylight saving offset
bool healthWarningActive = false; // Flag for health warnings (temp/humidity)
const unsigned long debounceDelay = 50; // Button debounce delay in ms
// =================================================================
// Alarm states (unique names to avoid conflicts)
enum AlarmState { ALARM_IDLE, ALARM_RINGING, ALARM_SNOOZED, ALARM_STOPPED };
// Struct to hold alarm info
struct Alarm {
int hour; // Hour of alarm
int minute; // Minute of alarm
bool active; // True if alarm is set
AlarmState state; // Current state of the alarm
};
// Create two alarms
Alarm alarm1 = {-1, -1, false, ALARM_IDLE};
Alarm alarm2 = {-1, -1, false, ALARM_IDLE};
// Snooze tracking
bool snoozeActive = false; // True if snooze is active
int snoozeHour = 0; // Snooze target hour
int snoozeMinute = 0; // Snooze target minute
// =================================================================
// Keypad configuration
const byte ROWS = 4; // Number of rows
const byte COLS = 3; // Number of columns
char keys[ROWS][COLS] = { // Key mapping
{'1','2','3'},
{'4','5','6'},
{'7','8','9'},
{'*','0','#'}
};
byte rowPins[ROWS] = {12,13,14,27}; // Row pins connected to ESP32
byte colPins[COLS] = {26,25,33}; // Column pins connected to ESP32
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS); // Create keypad object
// =================================================================
// Function declarations
void showMenu(); // Display main menu
void setTimeZone(); // Set UTC offset/timezone
void setAlarm(Alarm &alarm); // Set a specific alarm
void viewAlarms(); // Show active alarms
void deleteAlarm(); // Delete an alarm
void checkAlarms(); // Check if alarm should ring
void ringAlarm(); // Start alarm (buzzer + LED)
void handleAlarmButton(); // Handle snooze/stop via button
void checkHealth(); // Check temperature/humidity warning
void displayCurrentTime(); // Display current time on OLED
// =========================================================
// Function to display current time at bottom of OLED
void displayCurrentTime() {
struct tm timeinfo; // Struct to store current time
if (!getLocalTime(&timeinfo)) return; // Exit if failed to get time
int hour = timeinfo.tm_hour; // Get hour
int minute = timeinfo.tm_min; // Get minute
int second = timeinfo.tm_sec; // Get second
int y = SCREEN_HEIGHT - 8; // Y position for bottom row
display.fillRect(0, y, SCREEN_WIDTH, 8, SSD1306_BLACK); // Clear previous time
display.setCursor(0, y); // Set cursor
display.setTextSize(1); // Text size
display.setTextColor(SSD1306_WHITE); // Text color
display.printf("Time: %02d:%02d:%02d", hour, minute, second); // Print time
display.display(); // Update OLED
}
// =========================================================
// Setup function (runs once)
void setup() {
Serial.begin(9600); // Start serial for debugging
pinMode(BUZZER_PIN, OUTPUT); // Set buzzer as output
pinMode(LED_PIN, OUTPUT); // Set LED as output
pinMode(BUTTON_PIN, INPUT_PULLUP); // Set button as input with pullup
// Initialize OLED display
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("SSD1306 allocation failed");
for (;;) ; // Stop if OLED fails
}
display.clearDisplay(); // Clear OLED
display.setTextSize(1); // Set default text size
display.setTextColor(SSD1306_WHITE); // Set text color
dht.begin(); // Initialize DHT sensor
// Connect WiFi
WiFi.begin(ssid,password);
display.println("Connecting WiFi...");
display.display();
while(WiFi.status() != WL_CONNECTED){
delay(500); Serial.print("."); // Wait until connected
}
Serial.println("WiFi connected");
display.println("WiFi connected");
display.display();
displayCurrentTime(); // Show current time
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // Initialize NTP
delay(1000);
showMenu(); // Display main menu
}
// =========================================================
// Main loop
void loop() {
checkAlarms(); // Check if alarms should ring
checkHealth(); // Check temp/humidity warnings
displayCurrentTime(); // Always show current time
char key = keypad.getKey(); // Get pressed key
if(key){
switch(key){
case '1': setTimeZone(); break; // Set timezone
case '2': setAlarm(alarm1); break; // Set Alarm 1
case '3': setAlarm(alarm2); break; // Set Alarm 2
case '4': viewAlarms(); break; // View alarms
case '5': deleteAlarm(); break; // Delete alarm
default: showMenu(); break; // Show menu for other keys
}
}
// Handle alarm button if any alarm is ringing
if(alarm1.state == ALARM_RINGING || alarm2.state == ALARM_RINGING){
handleAlarmButton();
}
}
// =========================================================
// Display main menu
void showMenu() {
display.clearDisplay(); // Clear OLED
display.setCursor(0,0); // Set cursor top-left
display.println("==== Medibox ====");
display.println("1. Set Time Zone");
display.println("2. Set Alarm 1");
display.println("3. Set Alarm 2");
display.println("4. View Alarms");
display.println("5. Delete Alarm");
display.display(); // Show menu
displayCurrentTime(); // Show time at bottom
}
// =========================================================
// Set timezone function
void setTimeZone() {
display.clearDisplay();
display.setCursor(0,0);
display.println("Enter UTC offset HHMM (e.g., 0530) and # to confirm:");
display.display();
displayCurrentTime();
String input = ""; // Store keypad input
while(true){
char key = keypad.getKey(); // Read key
if(key){
if(key=='#') break; // Confirm input
if((key >= '0' && key <= '9') || key == '-') {
input += key; // Append input
display.clearDisplay();
display.setCursor(0,0);
display.println("UTC offset HHMM:");
display.println(input);
display.display();
displayCurrentTime();
}
}
}
// Validate input length
if(input.length() == 4 || input.length() == 5){
bool negative = false;
if(input.startsWith("-")){
negative = true; input.remove(0,1); // Handle negative offset
}
int hours = input.substring(0,2).toInt(); // Extract hours
int minutes = input.substring(2,4).toInt(); // Extract minutes
gmtOffset_sec = hours * 3600 + minutes * 60; // Convert to seconds
if(negative) gmtOffset_sec = -gmtOffset_sec;
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // Update NTP
display.clearDisplay();
display.setCursor(0,0);
display.println("Time zone updated");
display.display();
displayCurrentTime();
} else {
display.clearDisplay();
display.setCursor(0,0);
display.println("Invalid input!");
display.display();
displayCurrentTime();
}
}
// =========================================================
// Set alarm function
void setAlarm(Alarm &alarm){
display.clearDisplay();
display.setCursor(0,0);
display.println("Enter HHMM and # to confirm:");
display.display();
displayCurrentTime();
String input = ""; // Store keypad input
while(true){
char key = keypad.getKey(); // Read key
if(key){
if(key=='#') break; // Confirm input
if(key=='*'){ // Clear input
input="";
display.clearDisplay();
display.setCursor(0,0);
display.println("Enter HHMM:");
display.display();
displayCurrentTime();
continue;
}
input += key; // Append input
display.clearDisplay();
display.setCursor(0,0);
display.println("Alarm HHMM:");
display.println(input);
display.display();
displayCurrentTime();
}
}
if(input.length()==4){ // Validate HHMM
alarm.hour = input.substring(0,2).toInt();
alarm.minute = input.substring(2,4).toInt();
alarm.active = true; // Activate alarm
alarm.state = ALARM_IDLE; // Set state idle
display.clearDisplay();
display.setCursor(0,0);
display.printf("Alarm set %02d:%02d\n", alarm.hour, alarm.minute);
display.display();
displayCurrentTime();
}
}
// =========================================================
// View alarms
void viewAlarms(){
display.clearDisplay();
display.setCursor(0,0);
display.println("Active Alarms:");
if(alarm1.active) display.printf("A1: %02d:%02d\n", alarm1.hour, alarm1.minute);
if(alarm2.active) display.printf("A2: %02d:%02d\n", alarm2.hour, alarm2.minute);
if(!alarm1.active && !alarm2.active) display.println("None");
display.display();
displayCurrentTime();
}
// =========================================================
// Delete alarm
void deleteAlarm(){
display.clearDisplay();
display.setCursor(0,0);
display.println("Delete Alarm 1 or 2, # confirm:");
display.display();
displayCurrentTime();
String input = ""; // Store input
while(true){
char key = keypad.getKey();
if(key){
if(key=='#') break; // Confirm
if(key=='*'){input=""; continue;} // Clear input
input = key; // Store selection
}
}
// Delete chosen alarm
if(input=="1"){ alarm1.active=false; alarm1.hour=-1; alarm1.minute=-1; alarm1.state=ALARM_IDLE;}
else if(input=="2"){ alarm2.active=false; alarm2.hour=-1; alarm2.minute=-1; alarm2.state=ALARM_IDLE;}
display.clearDisplay();
display.setCursor(0,0);
display.println("Alarm deleted");
display.display();
displayCurrentTime();
}
// =========================================================
// Check if alarms should ring
void checkAlarms() {
struct tm timeinfo;
// Create a tm struct to hold current time
if (!getLocalTime(&timeinfo)) return;
// Try to get the local time from NTP
// If failed, exit the function early
int h = timeinfo.tm_hour;
// Extract the current hour from timeinfo
int m = timeinfo.tm_min;
// Extract the current minute from timeinfo
// -----------------------------
// Handle snooze alarm
// -----------------------------
static bool snoozeTriggered = false;
// Static flag to prevent snooze from triggering multiple times in the same minute
if (snoozeActive && !snoozeTriggered) {
// If a snooze is active and it hasn't been triggered yet
if (h == snoozeHour && m == snoozeMinute) {
// If current time matches the snooze target time
if (alarm1.state == ALARM_SNOOZED) alarm1.state = ALARM_RINGING;
// If Alarm 1 was snoozed, set it to ringing
if (alarm2.state == ALARM_SNOOZED) alarm2.state = ALARM_RINGING;
// If Alarm 2 was snoozed, set it to ringing
ringAlarm();
// Trigger the alarm: LED + buzzer + display message
snoozeActive = false;
// Snooze has been processed, deactivate snooze
snoozeTriggered = true;
// Prevent the snooze from retriggering in the same minute
}
}
if (!snoozeActive) snoozeTriggered = false;
// Reset the snoozeTriggered flag if no snooze is active
// -----------------------------
// Check Alarm 1
// -----------------------------
if(alarm1.active && alarm1.state == ALARM_IDLE && h==alarm1.hour && m==alarm1.minute){
// If Alarm 1 is active, currently idle, and time matches
alarm1.state = ALARM_RINGING;
// Set state to ringing
ringAlarm();
// Trigger the alarm: LED + buzzer + display message
}
// -----------------------------
// Check Alarm 2
// -----------------------------
if(alarm2.active && alarm2.state == ALARM_IDLE && h==alarm2.hour && m==alarm2.minute){
// If Alarm 2 is active, currently idle, and time matches
alarm2.state = ALARM_RINGING;
// Set state to ringing
ringAlarm();
// Trigger the alarm: LED + buzzer + display message
}
}
// =========================================================
// Trigger alarm (LED + buzzer)
void ringAlarm(){
digitalWrite(LED_PIN, HIGH); // Turn on LED
tone(BUZZER_PIN, 1000); // Start buzzer at 1kHz
display.clearDisplay();
display.setCursor(0,0);
if(alarm1.state == ALARM_RINGING) display.println("Alarm 1 ringing!");
if(alarm2.state == ALARM_RINGING) display.println("Alarm 2 ringing!");
display.display();
displayCurrentTime(); // Show time
}
// =========================================================
// Handle alarm button press for stop/snooze
void handleAlarmButton() {
// -----------------------------
// Static variables retain their values between calls
// -----------------------------
static int lastStableState = HIGH; // Last stable (debounced) button state
static int lastReading = HIGH; // Last raw reading from the button
static unsigned long lastDebounceTime = 0; // Timestamp of last state change
static bool buttonPressed = false; // True if button is currently being held down
static unsigned long pressStartTime = 0; // Timestamp when button was first pressed
// -----------------------------
// Read the current button state
// -----------------------------
int reading = digitalRead(BUTTON_PIN); // Read the digital pin connected to the button
// -----------------------------
// Debounce logic: ignore brief state changes due to noise
// -----------------------------
if (reading != lastReading) lastDebounceTime = millis(); // Update last debounce time if reading changed
lastReading = reading; // Update last reading
if ((millis() - lastDebounceTime) > debounceDelay) { // Only proceed if stable for debounce period
if (reading != lastStableState) { // If the stable state has changed
lastStableState = reading; // Update last stable state
// -----------------------------
// Button pressed
// -----------------------------
if (lastStableState == LOW) { // LOW means button pressed (INPUT_PULLUP)
buttonPressed = true; // Mark as pressed
pressStartTime = millis(); // Record when the press started
}
// -----------------------------
// Button released
// -----------------------------
else if (buttonPressed) { // Button was being pressed and now released
buttonPressed = false; // Clear pressed flag
unsigned long pressDuration = millis() - pressStartTime; // Calculate duration of press
// Stop alarm (LED + buzzer) immediately
digitalWrite(LED_PIN, LOW); // Turn off LED
noTone(BUZZER_PIN); // Stop buzzer sound
display.clearDisplay(); // Clear OLED screen
display.setCursor(0,0); // Reset cursor to top-left
// -----------------------------
// Long press → snooze
// -----------------------------
if (pressDuration >= 3000) { // Press held for 3 seconds or more
struct tm timeinfo;
if (getLocalTime(&timeinfo)) { // Get current time
timeinfo.tm_min += 5; // Add 5 minutes for snooze
mktime(&timeinfo); // Normalize time (handle hour overflow)
snoozeHour = timeinfo.tm_hour; // Store snooze hour
snoozeMinute = timeinfo.tm_min; // Store snooze minute
snoozeActive = true; // Activate snooze
}
// Mark alarms as snoozed
if (alarm1.state == ALARM_RINGING) alarm1.state = ALARM_SNOOZED;
if (alarm2.state == ALARM_RINGING) alarm2.state = ALARM_SNOOZED;
display.println("Alarm snoozed (5 min)"); // Show message on OLED
}
// -----------------------------
// Short press → stop alarm
// -----------------------------
else { // Press less than 3 seconds
if (alarm1.state == ALARM_RINGING) alarm1.state = ALARM_STOPPED;
if (alarm2.state == ALARM_RINGING) alarm2.state = ALARM_STOPPED;
display.println("Alarm stopped"); // Show message on OLED
}
display.display(); // Push message to OLED
displayCurrentTime(); // Also display current time
}
}
}
}
// =========================================================
// Check health status (temperature/humidity)
void checkHealth() {
float t = dht.readTemperature(); // Read temp
float h = dht.readHumidity(); // Read humidity
if (isnan(t) || isnan(h)) return; // Exit if read failed
bool tempBad = (t < 24 || t > 32); // Temp warning
bool humBad = (h < 65 || h > 80); // Humidity warning
bool warningNow = tempBad || humBad; // True if any warning
// Show warning
if (warningNow && alarm1.state != ALARM_RINGING && alarm2.state != ALARM_RINGING) {
if (!healthWarningActive) {
healthWarningActive = true;
digitalWrite(LED_PIN, HIGH);
display.clearDisplay();
display.setCursor(0,0);
display.println("Warning!");
if (tempBad) display.printf("Temp: %.1f C\n", t);
if (humBad) display.printf("Hum: %.1f %%\n", h);
display.display();
displayCurrentTime();
}
}
// Clear warning
else if (!warningNow && healthWarningActive) {
healthWarningActive = false;
digitalWrite(LED_PIN, LOW);
showMenu();
}
}
Loading
ssd1306
ssd1306