#include <Arduino.h> // For Arduino functions in VS Code using PlatformIO for Wokwi Simulator for ESP32
// Including Libraries
#include <Adafruit_GFX.h> // For OLED Display
#include <Adafruit_SSD1306.h> // For OLED Display
#include <DHTesp.h> // For DHT22 Sensor We can't use DHT11 in Wokwi Simulator
#include <WiFi.h> // For Wifi connectivity
#include <WiFiUdp.h> // For NTPClient UDP
#include <NTPClient.h> // For Time via NTP
// Definitions for NTP and WiFi
#define NTP_SERVER "pool.ntp.org"
float timezone_offset = 5.5; // Sri Lanka time (+5.5 hours)
#define UTC_OFFSET_DST 0 // Daylight saving offset
// Define OLED display parameters
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
// Define pins for various components
#define BUZZER 5
#define LED_1 15
#define LED_2 2
#define CANCEL 16
#define UP 4
#define DOWN 0
#define OK 2
#define DHT_PIN 12
// Create display and sensor objects
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
DHTesp dhtSensor;
// NTPClient for schedule
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, NTP_SERVER, timezone_offset * 3600, 60000);
// Musical note frequencies for alarm melody
int C = 262;
int D = 294;
int E = 330;
int F = 349;
int G = 392;
int A = 440;
int B = 494;
int C_H = 523;
int n_notes = 8;
int notes[] = {C, D, E, F, G, A, B, C_H};
// Global time variables (unchanged)
int seconds = 0, minutes = 0, hours = 0, days = 0;
// Alarm settings (unchanged)
bool alarm_enabled = true;
const int n_alarms = 2;
int alarm_hours[n_alarms] = {0, 0};
int alarm_minutes[n_alarms] = {1, 10};
bool alarm_triggered[n_alarms] = {false, false};
bool alarm_active[n_alarms] = {true, true};
// Menu variables (unchanged)
int current_mode = 0;
const int max_modes = 5;
String options[] = {"1 - Set Time Zone", "2 - Set Alarm 1", "3 - Set Alarm 2", "4 - View Alarms", "5 - Delete Alarm"};
// Original assignment1 function prototypes (unchanged)
void print_line(String text, int text_size = 1, int column = 0, int row = 0);
void update_time_wifi(void);
void print_time_now(void);
void update_time_with_check_alarm();
void ring_alarm(int alarmIndex);
int wait_for_button_press();
void go_to_menu(void);
void run_mode(int mode);
void set_time_zone();
void set_alarm(int alarm);
void view_active_alarms();
void delete_alarm();
void check_temp(void);
// Interrupt-related variables
volatile bool cancelPressed = false;
volatile bool inMenu = false;
volatile bool alarmActive = false;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50;
//------------------------------------------------------------------------
// Function : cancelButtonISR
// Purpose : Adding interrupt service routine for menu button (CANCEL)
// -----------------------------------------------------------------------
void IRAM_ATTR cancelButtonISR()
{
unsigned long currentTime = millis();
if ((currentTime - lastDebounceTime) > debounceDelay)
{
cancelPressed = true;
if (alarmActive) {
alarmActive = false;
}
lastDebounceTime = currentTime;
}
}
// ======================================================================================================================================================== //
// ------------------------------------------ Assignment 2 - functions for MediBox (Implementing Advanced Featuers) --------------------------------------- //
// ======================================================================================================================================================== //
// Name: Kularathna A. K. D. D. D.
// Index No: 220332P
// Including Libraries
#include <PubSubClient.h> // For MQTT
#include <ESP32Servo.h> // For Servo motor control
// Define pins for LDR and Servo motor
#define LDR_PIN 34 // Analog pin for Light Dependent Resistor
#define SERVO_PIN 13 // Pin for Servo motor control
// Create objects for servo motor
Servo slideServo; // Servo motor for sliding window
// WiFi credentials
const char *ssid = "Wokwi-GUEST";
const char *password = "";
// Create WiFi
WiFiClient espClient;
// MQTT Broker settings
// const char *mqtt_server = "test.mosquitto.org"; // Public MQTT broker
// const char *mqtt_server = "broker.hivemq.com"; // Public MQTT broker
const char *mqtt_server = "broker.emqx.io"; // Public MQTT broker
const int mqtt_port = 1883;
// const char *mqtt_username = "Esp32001_220332P";
// const char *mqtt_password = "MYESP321234-220-332P";
// Create MQTT client
PubSubClient client(espClient);
// Publish topics
const char *mqtt_temp = "esp32/temperature/dht22-234"; // Latest DHT11(we don't have 11 so we use 22) temperature reading
const char *mqtt_light_intensity = "esp32/light/332p222"; // Average light intensity over last tu seconds
const char *mqtt_servo_angle = "esp32/servo/332p"; // Computed window‐shade angle
// Subscribe topics
const char *mqtt_ts = "medibox/sampling_interval/332p222"; // Sampling interval for LDR (default 5 s)
const char *mqtt_tu = "medibox/averaging_interval/332p222"; // Averaging/sending interval for LDR (default 120 s or 2 min)
const char *mqtt_theta_offset = "medibox/theta_offset/332p222"; // Minimum servo angle
const char *mqtt_gamma = "medibox/gamma/332p222"; // Controlling factor
const char *mqtt_T_med = "medibox/T_med/332p222"; // Ideal storage temperature
// Parameters for calculating servo angle and averaging light intensity
float ts = 5000; // sampling interval in milliseconds (5 seconds default)
float tu = 120000; // sending interval in milliseconds (2 minutes default)
float theta_offset = 30.0; // minimum servo angle
float gammaValue = 0.75; // controlling factor
float T_med = 30.0; // ideal storage temperature
float currentLightIntensity = 0.0;
float currentTemp = 0.0;
float currentAvg = 0.0;
unsigned long lastSampleTime = 0;
unsigned long lastSendTime = 0;
float sumReadings = 0;
int numReadings = 0;
//-------------------------------------------------------
// Function : setup_wifi
// Purpose : Setting up WiFi connection
// --------------------------------------------------------
void setup_wifi()
{
delay(10);
Serial.println("Connecting to WiFi...");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
//----------------------------------------------------------------------------------------------------------------------
// Function : callback
// Purpose : Callback function to handle incoming MQTT messages, Use the callback function to properly handle messages
//----------------------------------------------------------------------------------------------------------------------
void callback(char *topic, byte *payload, unsigned int length)
{
char message[length + 1];
for (int i = 0; i < length; i++)
{
message[i] = (char)payload[i];
}
message[length] = '\0';
Serial.println("\n=== Received MQTT Message ===");
Serial.print("Topic: ");
Serial.println(topic);
Serial.print("Message: ");
Serial.println(message);
if (strcmp(topic, mqtt_ts) == 0)
{
ts = atof(message) * 1000; // Convert seconds to milliseconds
Serial.print("Updated sampling interval (ts) to: ");
Serial.print(ts / 1000);
Serial.println(" seconds");
}
else if (strcmp(topic, mqtt_tu) == 0)
{
tu = atof(message) * 1000;
Serial.print("Updated averaging interval (tu) to: ");
Serial.print(tu / 1000);
Serial.println(" seconds");
}
else if (strcmp(topic, mqtt_theta_offset) == 0)
{
theta_offset = atof(message);
Serial.print("Updated theta offset to: ");
Serial.print(theta_offset);
Serial.println(" degrees");
}
else if (strcmp(topic, mqtt_gamma) == 0)
{
gammaValue = atof(message);
Serial.print("Updated gamma value to: ");
Serial.println(gammaValue);
}
else if (strcmp(topic, mqtt_T_med) == 0)
{
T_med = atof(message);
Serial.print("Updated median temperature to: ");
Serial.print(T_med);
Serial.println("°C");
}
Serial.println("============================\n");
}
//-----------------------------------------------------------
// Function : setupMqtt
// Purpose : Setting up MQTT connection
// -----------------------------------------------------------
void setupMqtt()
{
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
}
//-----------------------------------------------------------
// Function : subscribeToTopics
// Purpose : Subscribe to MQTT topics
// -----------------------------------------------------------
void subscribeToTopics(const char *topic)
{
// Subscribe to the topic
if (client.subscribe(topic))
{
char topic[50];
strcpy(topic, topic);
callback(topic, NULL, 0); // Call the callback function with non-const char*
// Serial.println("Subscribed to topic: " + String(topic));
}
else
{
Serial.println("Failed to subscribe to topic");
}
}
//-----------------------------------------------------------
// Function : reconnect
// Purpose : Reconnect to MQTT broker if connection is lost
// -----------------------------------------------------------
void reconnect()
{
while (!client.connected())
{
Serial.print("Attempting MQTT connection...");
String clientId = "ESP32Client-332P";
clientId += String(random(0xffff), HEX);
if (client.connect(clientId.c_str()))
{
Serial.println("connected");
subscribeToTopics(mqtt_ts);
subscribeToTopics(mqtt_tu);
subscribeToTopics(mqtt_theta_offset);
subscribeToTopics(mqtt_gamma);
subscribeToTopics(mqtt_T_med);
}
else
{
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" retrying in 5 seconds");
delay(5000);
}
}
}
//-----------------------------------------------------------
// Function : readLightIntensity
// Purpose : Read light intensity from LDR
// -----------------------------------------------------------
float readLightIntensity()
{
int rawValue = analogRead(LDR_PIN);
return (float)rawValue / 4095.0; // Normalize to 0-1 range
}
//--------------------------------------------------------------------------------------------------------------------
// Function : calculateServoAngle
// Purpose : Calculate the servo angle based on light intensity, temperature, gamma, T_med, theta_offset, ts and tu
// -------------------------------------------------------------------------------------------------------------------
float calculateServoAngle()
{
float I = currentLightIntensity;
float T = currentTemp;
// Implement the formula to calculate the servo angle
float angle = theta_offset + (180 - theta_offset) * I * gammaValue * log(ts / tu) * (T / T_med);
return constrain(angle, theta_offset, 180);
}
//-----------------------------------------------------------
// Function : handleLightMonitoring
// Purpose : Handle light monitoring and publish data
// -----------------------------------------------------------
void handleLightMonitoring()
{
if (alarmActive) {return;}
unsigned long currentTime = millis();
// Sample at ts interval
if (currentTime - lastSampleTime >= ts)
{
float lightValue = readLightIntensity();
currentLightIntensity = lightValue;
sumReadings += lightValue;
numReadings++;
lastSampleTime = currentTime;
// Debug print for sampling
Serial.println("----------------------------------------");
Serial.print("Sample Reading - Light Intensity: ");
Serial.println(lightValue, 3);
Serial.print("Number of readings: ");
Serial.println(numReadings);
// Read temperature
TempAndHumidity data = dhtSensor.getTempAndHumidity();
if (isnan(data.temperature))
{
Serial.println("Error: Failed to read DHT sensor!");
}
else
{
currentTemp = data.temperature;
Serial.print("Current Temperature: ");
Serial.print(currentTemp, 2);
Serial.println("°C");
}
Serial.println("----------------------------------------");
}
// Publish at tu interval
if (currentTime - lastSendTime >= tu)
{
if (numReadings > 0)
{
// Calculate average light intensity
currentAvg = sumReadings / numReadings;
// Calculate and set servo angle
float angle = calculateServoAngle();
Serial.println(angle);
slideServo.write(angle);
// Create JSON format messages
char lightBuf[64];
snprintf(lightBuf, sizeof(lightBuf), "{\"light\":%.3f}", currentAvg);
char tempBuf[64];
snprintf(tempBuf, sizeof(tempBuf), "{\"temperature\":%.2f}", currentTemp);
char angleBuf[64];
snprintf(angleBuf, sizeof(angleBuf), "{\"angle\":%.1f}", angle);
Serial.println("\n=== Publishing Data ===");
bool success = true;
// Publish with error checking
if (!client.publish(mqtt_light_intensity, lightBuf))
{
Serial.println("Error: Failed to publish light intensity!");
success = false;
}
else
{
Serial.print("Published Light Intensity: ");
Serial.println(lightBuf);
}
if (!client.publish(mqtt_temp, tempBuf))
{
Serial.println("Error: Failed to publish temperature!");
success = false;
}
else
{
Serial.print("Published Temperature: ");
Serial.println(tempBuf);
}
Serial.println("=====================\n");
// Publish servo angle
if (!client.publish(mqtt_servo_angle, angleBuf))
{
Serial.println("Error: Failed to publish servo angle!");
success = false;
}
else
{
Serial.print("Published Servo Angle: ");
Serial.println(angleBuf);
Serial.print("Servo set to: ");
Serial.print(angle);
Serial.println(" degrees");
}
if (success) {
Serial.printf("Published - Light: %s, Temp: %s, Angle: %s\n", lightBuf, tempBuf, angleBuf);
}
// Reset averaging variables
sumReadings = 0;
numReadings = 0;
lastSendTime = currentTime;
}
}
}
void setup()
{
Serial.begin(115200);
while (!Serial)
{
; // Wait for Serial to be ready
}
Serial.println("MediBox starting...");
// ------------------------------------------------------ Assignment 2 - Setup --------------------------------------------------------- //
// Initialize WiFi first
setup_wifi();
// Initialize MQTT
setupMqtt();
// Initialize Servo
slideServo.attach(SERVO_PIN);
slideServo.write(theta_offset);
// ------------------------------------------------------------------------------------------------------------------------------------- //
// Initialize DHT sensor
dhtSensor.setup(DHT_PIN, DHTesp::DHT22);
// Setup original pins
pinMode(BUZZER, OUTPUT);
pinMode(LED_1, OUTPUT);
pinMode(LED_2, OUTPUT);
pinMode(CANCEL, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(CANCEL), cancelButtonISR, FALLING);
pinMode(UP, INPUT);
pinMode(DOWN, INPUT);
pinMode(OK, INPUT);
pinMode(LDR_PIN, INPUT); // Setup LDR pin as input
// Initialize LDR
pinMode(LDR_PIN, INPUT);
// OLED init (unchanged)
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS))
{
Serial.println(F("SSD1306 allocation Failed"));
for (;;)
;
}
display.clearDisplay();
// Welcome message (unchanged)
print_line("Welcome to MediBox", 2, 0, 0);
delay(3000);
display.clearDisplay();
// Original WiFi confirmation
print_line("Connected to WiFi", 2, 0, 0);
delay(1000);
display.clearDisplay();
// Original NTP for display/time functions
configTime((int)(timezone_offset * 3600), UTC_OFFSET_DST, NTP_SERVER);
Serial.println("All components initialized");
}
void loop()
{
if (cancelPressed)
{
go_to_menu();
cancelPressed = false;
}
update_time_with_check_alarm();
check_temp();
// --------------------------------------------------------- Assignment 2 - loop --------------------------------------------------- //
if (!client.connected())
{
reconnect();
}
client.loop();
handleLightMonitoring();
delay(10); // delay to prvent cpu overloading
// -------------------------------------------------------------------------------------------------------------------------------- //
}
// --------------------------------------------------------- Assignment 1 - functions for MediBox --------------------------------------------------------- //
void print_line(String text, int text_size, int column, int row)
{
display.setTextSize(text_size);
display.setTextColor(SSD1306_WHITE, SSD1306_BLACK); // White text on black background
display.setCursor(column, row);
display.println(text);
display.display();
}
void update_time_wifi(void)
{
struct tm timeinfo;
if (!getLocalTime(&timeinfo))
{
Serial.println("Failed to obtain time");
return;
}
hours = timeinfo.tm_hour;
minutes = timeinfo.tm_min;
seconds = timeinfo.tm_sec;
}
void print_time_now(void)
{
// Clear only the time display area (e.g., top part of the screen)
// Assuming time is displayed at the top, adjust height as needed (e.g., 16 pixels for size 2 text)
display.fillRect(0, 0, SCREEN_WIDTH, 16, SSD1306_BLACK);
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE); // No need to specify background if cleared
display.setCursor(0, 0);
display.printf("%02d:%02d:%02d", hours, minutes, seconds);
display.display(); // Update only this part of the display if possible, or full display
}
void update_time_with_check_alarm()
{
update_time_wifi();
print_time_now();
if (alarm_enabled)
{
for (int i = 0; i < n_alarms; i++)
{
// Reset the alarm_triggered flag if the time has passed the set minute or hour has changed
if (alarm_triggered[i] && ((hours != alarm_hours[i]) || (minutes != alarm_minutes[i])))
{
alarm_triggered[i] = false;
}
if (alarm_active[i] && !alarm_triggered[i] && (hours == alarm_hours[i]) && (minutes == alarm_minutes[i]))
{
ring_alarm(i);
alarm_triggered[i] = true;
}
}
}
}
void ring_alarm(int alarmIndex)
{
alarmActive = true;
display.clearDisplay();
print_line("Medicine Time", 2, 0, 0);
digitalWrite(LED_1, HIGH);
bool alarmActiveLoop = true;
while (alarmActiveLoop)
{
// Check for CANCEL or OK before playing the notes
if (digitalRead(CANCEL) == LOW || cancelPressed)
{
delay(200); // Debounce
alarmActiveLoop = false; // Exit the loop if CANCEL is pressed
break;
}
// Check for OK button to Snooze
if (digitalRead(OK) == LOW)
{
delay(200); // Debounce
// Snooze: add 5 minutes to the current alarm time
alarm_minutes[alarmIndex] += 5;
if (alarm_minutes[alarmIndex] >= 60)
{
alarm_minutes[alarmIndex] %= 60;
alarm_hours[alarmIndex] = (alarm_hours[alarmIndex] + 1) % 24;
}
// Display the snooze message
display.clearDisplay();
print_line("Alarm Snoozed", 2, 0, 0);
delay(2000);
alarmActiveLoop = false; // Exit the loop after snoozing
break;
}
// Loop through each note and play it
for (int i = 0; i < n_notes && alarmActiveLoop; i++)
{
if (digitalRead(CANCEL) == LOW || cancelPressed)
{
delay(200);
alarmActiveLoop = false;
break;
}
if (digitalRead(OK) == LOW)
{
delay(200);
alarm_minutes[alarmIndex] += 5;
if (alarm_minutes[alarmIndex] >= 60)
{
alarm_minutes[alarmIndex] %= 60;
alarm_hours[alarmIndex] = (alarm_hours[alarmIndex] + 1) % 24;
}
display.clearDisplay();
print_line("Alarm Snoozed", 2, 0, 0);
delay(2000);
alarmActiveLoop = false;
break;
}
tone(BUZZER, notes[i]);
delay(500); // Note duration
noTone(BUZZER);
delay(2); // Short pause between notes
}
if (!alarmActiveLoop)
break; // Exit main while loop if button pressed
delay(500); // Pause between melody repeats if no button is pressed
}
alarmActive = false;
cancelPressed = false;
noTone(BUZZER); // Ensure buzzer is off
digitalWrite(LED_1, LOW); // Turn off LED when alarm stops
display.clearDisplay(); // Clear "Medicine Time" or "Snoozed" message
// update_time_with_check_alarm(); // Go back to showing time immediately
}
int wait_for_button_press()
{
while (true)
{
if (digitalRead(UP) == LOW)
{
delay(200); // Debounce
return UP;
}
else if (digitalRead(DOWN) == LOW)
{
delay(200); // Debounce
return DOWN;
}
else if (digitalRead(CANCEL) == LOW)
{
delay(200); // Debounce
return CANCEL;
}
else if (digitalRead(OK) == LOW)
{
delay(200); // Debounce
return OK;
}
yield(); // Allow background tasks
}
}
void go_to_menu(void)
{
if (inMenu) return;
inMenu = true;
cancelPressed = false;
while (inMenu) // Loop while CANCEL has not been pressed to exit menu
{
display.clearDisplay();
// Make text slightly smaller if it overflows or adjust content
print_line(options[current_mode], 1, 0, 10); // Example: Size 1, y=10
print_line("OK: Select, CANCEL: Exit", 1, 0, 40);
int pressed = wait_for_button_press();
if (pressed == UP)
{
current_mode = (current_mode + 1) % max_modes;
}
else if (pressed == DOWN)
{
current_mode = (current_mode - 1 + max_modes) % max_modes;
}
else if (pressed == OK)
{
Serial.print("Selected mode: ");
Serial.println(current_mode);
run_mode(current_mode);
// After run_mode returns, stay in menu unless run_mode itself decided to exit
// If run_mode handles its own exit from menu state, this loop needs adjustment
}
else if (pressed == CANCEL)
{
inMenu = false; // Exit menu
cancelPressed = false;
delay(debounceDelay);
break;
}
}
display.clearDisplay(); // Clear menu options before returning to main loop display
}
void run_mode(int mode)
{
display.clearDisplay(); // Clear previous menu option
if (mode == 0)
{
set_time_zone();
}
else if (mode == 1 || mode == 2)
{
set_alarm(mode - 1); // alarm index 0 or 1
}
else if (mode == 3)
{
view_active_alarms();
}
else if (mode == 4)
{
delete_alarm();
}
// After action, it will return to go_to_menu's loop
}
void set_time_zone()
{
float temp_offset = timezone_offset;
while (true)
{
display.clearDisplay();
print_line("TZ offset: " + String(temp_offset, 1), 1, 0, 2);
print_line("UP/DOWN: +/-0.5 hr", 1, 0, 20);
print_line("OK: set, CANCEL: back", 1, 0, 40);
int pressed = wait_for_button_press();
if (pressed == UP)
{
temp_offset += 0.5;
}
else if (pressed == DOWN)
{
temp_offset -= 0.5;
}
else if (pressed == OK)
{
timezone_offset = temp_offset;
// Update NTP client and system time
timeClient.setTimeOffset((long)(timezone_offset * 3600));
configTime((long)(timezone_offset * 3600), UTC_OFFSET_DST, NTP_SERVER);
display.clearDisplay();
print_line("TZ set: " + String(timezone_offset, 1), 1, 0, 2);
delay(1500);
break;
}
else if (pressed == CANCEL)
{
break;
}
}
}
void set_alarm(int alarm_idx) // Use alarm_idx to avoid confusion
{
int temp_hour = alarm_hours[alarm_idx];
// Setting Hours
while (true)
{
display.clearDisplay();
print_line("Set Alarm " + String(alarm_idx + 1) + " Hr: " + String(temp_hour), 1, 0, 2);
print_line("UP/DOWN: change", 1, 0, 20);
print_line("OK: next, CANCEL: back", 1, 0, 40);
int pressed = wait_for_button_press();
if (pressed == UP)
{
temp_hour = (temp_hour + 1) % 24;
}
else if (pressed == DOWN)
{
temp_hour = (temp_hour - 1 + 24) % 24; // Handles negative wrap-around
}
else if (pressed == OK)
{
alarm_hours[alarm_idx] = temp_hour;
break; // Proceed to set minutes
}
else if (pressed == CANCEL)
{
return; // Exit set_alarm
}
}
int temp_minute = alarm_minutes[alarm_idx];
// Setting Minutes
while (true)
{
display.clearDisplay();
print_line("Set Alarm " + String(alarm_idx + 1) + " Min: " + String(temp_minute), 1, 0, 2);
print_line("UP/DOWN: change", 1, 0, 20);
print_line("OK: save, CANCEL: back", 1, 0, 40);
int pressed = wait_for_button_press();
if (pressed == UP)
{
temp_minute = (temp_minute + 1) % 60;
}
else if (pressed == DOWN)
{
temp_minute = (temp_minute - 1 + 60) % 60; // Handles negative wrap-around
}
else if (pressed == OK)
{
alarm_minutes[alarm_idx] = temp_minute;
alarm_active[alarm_idx] = true; // Activate the alarm
alarm_triggered[alarm_idx] = false; // Reset triggered status
display.clearDisplay();
print_line("Alarm " + String(alarm_idx + 1) + " Set!", 1, 0, 2);
delay(1500);
break;
}
else if (pressed == CANCEL)
{
return; // Exit set_alarm without saving minute if hour was already set
}
}
}
void view_active_alarms()
{
display.clearDisplay();
print_line("Active Alarms:", 1, 0, 0);
for (int i = 0; i < n_alarms; i++)
{
String status = alarm_active[i] ? "ON" : "OFF";
char buffer[30];
sprintf(buffer, "A%d: %02d:%02d %s", i + 1, alarm_hours[i], alarm_minutes[i], status.c_str());
print_line(buffer, 1, 0, (i + 1) * 10 + 5);
}
print_line("OK or CANCEL to exit", 1, 0, 50);
wait_for_button_press(); // Wait for any button to exit
}
void delete_alarm()
{
int alarm_to_delete = 0; // Start with the first alarm
while (true)
{
display.clearDisplay();
String status = alarm_active[alarm_to_delete] ? "ON" : "OFF";
char buffer[30];
sprintf(buffer, "Alarm %d (%02d:%02d %s)", alarm_to_delete + 1, alarm_hours[alarm_to_delete], alarm_minutes[alarm_to_delete], status.c_str());
print_line("Delete " + String(buffer), 1, 0, 2);
print_line("UP/DOWN: select", 1, 0, 20);
print_line("OK: del, CANCEL: back", 1, 0, 40);
int pressed = wait_for_button_press();
if (pressed == UP)
{
alarm_to_delete = (alarm_to_delete + 1) % n_alarms;
}
else if (pressed == DOWN)
{
alarm_to_delete = (alarm_to_delete - 1 + n_alarms) % n_alarms;
}
else if (pressed == OK)
{
alarm_active[alarm_to_delete] = false; // "Delete" by deactivating
display.clearDisplay();
print_line("Alarm " + String(alarm_to_delete + 1) + " Deactivated", 1, 0, 2);
delay(1500);
break;
}
else if (pressed == CANCEL)
{
break;
}
}
}
void check_temp(void)
{
TempAndHumidity data = dhtSensor.getTempAndHumidity();
bool issue_found = false; // Use a more descriptive variable name
// Clear previous temperature/humidity warnings area
// Assuming this is below the time, e.g. rows 20-40
display.fillRect(0, 20, SCREEN_WIDTH, 30, SSD1306_BLACK);
if (isnan(data.temperature) || isnan(data.humidity))
{
Serial.println("Failed to read from DHT sensor!");
print_line("DHT Error", 1, 40, 20); // Display error on OLED
digitalWrite(LED_2, HIGH); // Indicate error with LED
return; // Exit function if sensor read failed
}
// Temperature checks
if (data.temperature > 32)
{
issue_found = true;
print_line("TEMP HIGH", 1, 40, 20);
}
else if (data.temperature < 24)
{
issue_found = true;
print_line("TEMP LOW", 1, 40, 20);
}
// Humidity checks
if (data.humidity > 80)
{
issue_found = true;
print_line("HUMD HIGH", 1, 40, 30);
}
else if (data.humidity < 65)
{
issue_found = true;
print_line("HUMD LOW", 1, 40, 30);
}
digitalWrite(LED_2, issue_found); // LED_2 ON if any issue, OFF otherwise
if (issue_found)
{
display.display(); // Update display only if there's a message to show
}
}