// Included libraries
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHTesp.h>
#include <WiFi.h>
#include <time.h>
// OLED Parameters
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
// Pin Configurations for Components
#define BUZZER 5
#define LED_1 15 // Used to Alert the user for medicine time
#define LED_2 2 // Used to warn user for about temperature & humidity
#define CANCEL 34 // Push button for cancel the menu selection and stop alarm when ringing
#define OK 32 // Push button to open menu selection & confirm the selections
#define UP 35
#define DOWN 33
#define DHTPIN 12
// Time parameters
#define NTP_SERVER "pool.ntp.org"
#define UTC_OFFSET 0
#define UTC_OFFSET_DST 0
// Delay time after print message in display
#define MESSAGE_DELAY 1000
// Delay time for push buttons debounce & other common wait
#define DELAY 200
// Object parameters for display & sensor
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
DHTesp dhtSensor;
// Time parameters to keep hours & minutes to check alarms
int days = 0;
int hours = 0;
int minutes = 0;
int seconds = 0;
// UTC Offset parameter in seconds
long utc_offset_sec = 0; //stores the UTC offset in seconds, allowing for proper time synchronization.
// Alarm parameters
struct alarm {
int hours;
int minutes;
bool triggered;
};
bool alarm_enabled = false; //indicates whether alarms are currently active.
int n_alarms = 3; //specifies the total number of alarms.
int alarm_hours[] = {0, 0};
int alarm_minutes[] = {1, 10};
bool alarm_triggered[] = {false, false}; //keeps track of whether each alarm has been triggered.
struct alarm alarms[] = {
{0, 0, true},
{0, 0, true},
{0, 0, true}
};
// Buzzer tone parameters
int n_notes = 8;
int C = 262;
int D = 294;
int E = 330;
int F = 348;
int G = 392;
int A = 440;
int B = 494;
int C_H = 523;
int notes[] = {C, D, E, F, G, A, B, C_H}; //represents the total number of notes available.
// Menu parameters
int current_mode = 0; //tracks the current mode or menu option selected by the user.
int max_modes = 5;
String modes[] = { //stores the names of each menu option as strings.
"1 - Set Timezone",
"2 - Set Alarm 1",
"3 - Set Alarm 2",
"4 - Set Alarm 3",
"5 - Disable Alarms"
};
void setup() {
Serial.begin(9600);
// Pin configurations
pinMode(BUZZER, OUTPUT);
pinMode(LED_1, OUTPUT);
pinMode(LED_2, OUTPUT);
pinMode(CANCEL, INPUT);
pinMode(OK, INPUT);
pinMode(UP, INPUT);
pinMode(DOWN, INPUT);
// Initialize Temp & Humidity sensor
dhtSensor.setup(DHTPIN, DHTesp::DHT22);
// Initialize the OLED Display
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
display.display();
delay(MESSAGE_DELAY);
display.clearDisplay();
print_line("Welcome to Medibox!", 10, 20, 2, true);
delay(MESSAGE_DELAY);
// Initialize the WIFI
WiFi.begin("Wokwi-GUEST", "", 6);
while (WiFi.status() != WL_CONNECTED) {
delay(DELAY);
display.clearDisplay();
print_line("Connecting to WIFI", 0, 0, 2, true);
}
display.clearDisplay();
print_line("Connected to WIFI", 0, 0, 2, true);
// Configure Initial Time using WIFI network
configTime(UTC_OFFSET, UTC_OFFSET_DST, NTP_SERVER);
}
void loop() {
// Check for menu button pressed
update_time_with_check_alarm();
if (digitalRead(CANCEL) == LOW) {
delay(DELAY);
Serial.println("Menu");
go_to_menu();
}
update_time();
update_time_with_check_alarm();
check_temperature_humidity();
}
/**
Function update time and print in OLED display (HH:MM:SS)
*/
void update_time(void) {
struct tm timeinfo;
getLocalTime(&timeinfo);
char timeDay[3];
char timeHour[3];
char timeMinute[3];
char timeSecond[3];
char fullTime[9];
strftime(timeDay, 3, "%d", &timeinfo);
strftime(timeHour, 3, "%H", &timeinfo);
strftime(timeMinute, 3, "%M", &timeinfo);
strftime(timeSecond, 3, "%S", &timeinfo);
strftime(fullTime, 9, "%T", &timeinfo);
print_line(String(fullTime), 15, 0, 2, true);
hours = atoi(timeHour);
minutes = atoi(timeMinute);
days = atoi(timeDay);
seconds = atoi(timeSecond);
}
/**
Function to check alarms and ring if any.
*/
void update_time_with_check_alarm() {
display.clearDisplay();
update_time(); //function to refresh the current time information.
if (alarm_enabled) { //Checks if the alarm system is enabled.
for (int i = 0; i < n_alarms; i++) {
if (alarm_triggered[i] ==false && hours == alarm_hours[i] && minutes == alarm_minutes[i]) {
ring_alarm();
alarm_triggered[i] = true; //Sets the corresponding alarm as triggered to prevent it from being triggered repeatedly.
}
}
}
}
/**
Function to ring alarm to notify user
*/
void ring_alarm() {
display.clearDisplay();
print_line("Medicine Time!", 0, 0, 2, true);
// light up LED 1
digitalWrite(LED_1, HIGH);
//Buzzer ring alarm until user press cancel button
while (digitalRead(CANCEL) == HIGH) {
for (int i = 0; i < n_notes; i++) {
if (digitalRead(CANCEL) == LOW) {
delay(200);
break;
}
tone(BUZZER, notes[i]);
delay(500);
noTone(BUZZER);
delay(2);
}
}
delay(200);
digitalWrite(LED_1, LOW);
display.clearDisplay();
}
/**
Function to check menu and run based on user selection
*/
void go_to_menu() {
while (digitalRead(CANCEL) == HIGH) {
display.clearDisplay();
print_line(modes[current_mode], 0, 0, 2, true);
int pressed = wait_for_button_press();
if (pressed == UP) {
current_mode += 1;
current_mode %= max_modes;
delay(200);
}
else if (pressed == DOWN) {
current_mode -= 1;
if (current_mode < 0) {
current_mode = max_modes - 1;
}
delay(DELAY);
}
else if (pressed == OK) {
Serial.println(current_mode);
delay(200);
run_mode(current_mode);
}
else if (pressed == CANCEL) {
delay(DELAY);
break;
}
}
}
/**
Function to run selected menu
*/
void run_mode(int mode) {
if (mode == 0) {
set_time();
}
else if (mode == 1 || mode == 2 || mode == 3) {
set_alarm(mode - 1);
}
else if (mode == 4) {
disable_alarms();
}
}
/**
Function set timezone and config based on user input
*/
void set_time() {
int current_offset_hours = utc_offset_sec / 3600;
int current_offset_minutes = (utc_offset_sec % 3600) / 60;
int offset_hours = read_value("Enter offset hours: ", current_offset_hours, 14, -12);
int offset_minutes = read_value("Enter offset minutes: ", current_offset_minutes, 59, 0);
utc_offset_sec = offset_hours * 3600 + offset_minutes * 60;
configTime(utc_offset_sec, UTC_OFFSET_DST, NTP_SERVER);
print_line("Timezone set to " + String(offset_hours) + ":" + String(offset_minutes), 0, 0, 2, true);
delay(MESSAGE_DELAY);
}
/**
Function to set alarm based on user input
alarm - Alarm index
*/
void set_alarm(int alarm) {
alarms[alarm].hours = read_value("Enter hour: ", alarms[alarm].hours, 23, 0);
alarms[alarm].minutes = read_value("Enter minute: ", alarms[alarm].minutes, 59, 0);
alarms[alarm].triggered = false;
if (!alarm_enabled) {
alarm_enabled = true;
}
print_line("Alarm " + String(alarm) + " is set to " + String(alarms[alarm].hours) + ":" + String(alarms[alarm].minutes), 0, 0, 2, true);
delay(MESSAGE_DELAY);
}
/**
Function to disable all the alarms and set to default values
*/
void disable_alarms() {
alarm_enabled = false;
// Clear all the alarms
for (int i = 0; i < n_alarms; i++) {
alarms[i] = {0, 0, true};
}
print_line("Alarms are disabled", 0, 0, 2, true);
delay(MESSAGE_DELAY);
}
/**
Function to check temperature and humidity and warn if exceeded recommended limit
*/
void check_temperature_humidity() {
TempAndHumidity data = dhtSensor.getTempAndHumidity();
bool warning = false;
if (data.temperature > 32) {
warning = true;
print_line("Temperature HIGH", 0, 40, 1, false);
}
else if (data.temperature < 26) {
warning = true;
print_line("Temperature LOW", 0, 40, 1, false);
}
if (data.humidity > 80) {
warning = true;
print_line("Humidity HIGH", 0, 50, 1, false);
}
else if (data.humidity < 60) {
warning = true;
print_line("Humidity LOW", 0, 50, 1, false);
}
if (warning) {
digitalWrite(LED_2, HIGH);
delay(DELAY);
digitalWrite(LED_2, LOW);
}
}
/**Common Functions**/
/**
Function to print message in OLED Display
text - Text Message
column - Column position in display
row - Row position in display
text_size - Text size
clear - Clear the display before print the text
*/
void print_line(String text, int column, int row, int text_size, bool clear) {
if (clear) {
display.clearDisplay();
}
display.setTextSize(text_size);
display.setTextColor(SSD1306_WHITE);
display.setCursor(column, row);
display.println(text);
display.display();
}
/**
Function to read user input value using push buttons.
return int value based on user input
text - Text message for user
init_value - Initial Value
max_value - Maximum allowed value
min_value - Minimum allowed value
*/
int read_value(String text, int init_value, int max_value, int min_value) {
int temp_value = init_value;
while (true) {
print_line(text + String(temp_value), 0, 0, 2, true);
int pressed = wait_for_button_press();
if (pressed == UP) {
delay(DELAY);
temp_value += 1;
if (temp_value > max_value) {
temp_value = min_value;
}
}
else if (pressed == DOWN) {
delay(DELAY);
temp_value -= 1;
if (temp_value < min_value) {
temp_value = max_value;
}
}
else if (pressed == OK) {
delay(DELAY);
return temp_value;
}
else if (pressed == CANCEL) {
delay(DELAY);
break;
}
}
return init_value;
}
/**
Function to wait for user to press push button.
return Pin no for each button when button pressed by user.
*/
int wait_for_button_press() {
while (true) {
if (digitalRead(UP) == LOW) {
delay(DELAY);
return UP;
}
else if (digitalRead(DOWN) == LOW) {
delay(DELAY);
return DOWN;
}
else if (digitalRead(OK) == LOW) {
delay(DELAY);
return OK;
}
else if (digitalRead(CANCEL) == LOW) {
delay(DELAY);
return CANCEL;
}
}
}