#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <time.h>
#include <DHTesp.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <math.h>
#include <ESP32Servo.h>
// OLED display setup
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
DHTesp dhtSensor;
#define NTP_SERVER "pool.ntp.org"
#define UTC_OFFSET 0
#define UTC_OFFSET_DST 0
#define BUZZER 15
#define LED_1 32
#define LED_2 33
#define PB_CANCEL 34
#define OK 5
#define UP 18
#define DOWN 17
#define DHT 14
#define LDR_PIN 35
#define servoPin 26
// Time variables
unsigned long timeNow = 0, timeLast = 0;
int seconds = 0, minutes = 0, hours = 0, days = 0;
const int n_alarms = 2;
bool alarm_en[n_alarms + 1] = {true, true, false}; // +1 for snooze
bool alarm_triggered[n_alarms + 1] = {false, false, false}; // +1 for snooze
int alarm_hours[n_alarms + 1] = {0, 1, 0}; // +1 for snooze
int alarm_minutes[n_alarms + 1] = {1, 10, 0}; // +1 for snooze
int UTC_OFFSET_HOUR = 0;
int UTC_OFFSET_MIN = 0;
int n_notes = 8;
int C=262, D=294, E=330, F=349, G=392, A=440, B=494, C_H=523;
int notes[]={C, D, E, F, G, A, B, C_H};
int current_mode = 0;
int max_modes = 4;
String options[]={"1-Set Time Zone","2-Set Alarm 1", "3-Set Alarm 2", "4- View & Change Alarms"};
const char* mqtt_server = "test.mosquitto.org";
WiFiClient espClient;
PubSubClient client(espClient);
unsigned long READING_INTERVAL = 5000; // Default: 5 seconds
int READINGS_PER_AVERAGE = 24; // Default: 2 minutes (5s * 24)
float ldrSum = 0;
int readingCount = 0;
unsigned long lastReadingTime = 0;
unsigned long lastTempPublishTime = 0;
const unsigned long TEMP_PUBLISH_INTERVAL = 120000; // 2 minutes
float theta_offset = 30;
float gammaval = 0.75;
float tmed = 30; // default ideal temperature
Servo myServo;
int t_s = 5;
int t_u = 120;
int T = 25;
float I = 0.5;
// Function to print a line of text on the OLED display
void print_line(String text, int text_size, int row, int column) {
display.setTextSize(text_size);
display.setTextColor(SSD1306_WHITE);
display.setCursor(column, row);
display.println(text);
display.display();
}
// Function to display the current time in DD:HH:MM:SS format
void print_time_now(void) {
display.clearDisplay();
print_line(String(days), 2, 0, 50);
print_line(String(hours), 2, 32, 0);
print_line(":", 2, 32, 20);
print_line(String(minutes), 2, 32, 30);
print_line(":", 2, 32, 50);
print_line(String(seconds), 2, 32, 60);
}
// Function to update time using WIFI
void update_time() {
struct tm timeinfo;
getLocalTime(&timeinfo);
char day_str[8], hour_str[8], min_str[8], sec_str[8];
strftime(day_str, 8, "%d", &timeinfo);
strftime(hour_str, 8, "%H", &timeinfo);
strftime(min_str, 8, "%M", &timeinfo);
strftime(sec_str, 8, "%S", &timeinfo);
days = atoi(day_str);
hours = atoi(hour_str);
minutes = atoi(min_str);
seconds = atoi(sec_str);
// Apply UTC Offset
hours += UTC_OFFSET_HOUR;
minutes += UTC_OFFSET_MIN;
if (minutes >= 60) {
minutes -= 60;
hours += 1;
} else if (minutes < 0) {
minutes += 60;
hours -= 1;
}
if (hours >= 24) {
hours -= 24;
days += 1;
} else if (hours < 0) {
hours += 24;
days -= 1;
}
}
//Function to ring alarm
void ring_alarm() {
display.clearDisplay();
print_line("Medicine Time", 2, 0, 0);
print_line("Click OK to snooze", 1, 40, 0);
digitalWrite(LED_1, HIGH);
bool break_happened = false;
unsigned long lastToneChange = 0;
int currentNote = 0;
bool isPlaying = false;
while (!break_happened) {
if (digitalRead(PB_CANCEL) == LOW) {
delay(200);
break_happened = true;
}
if (digitalRead(OK) == LOW) {
delay(200);
snooze_alarm();
break_happened = true;
}
unsigned long currentTime = millis();
if (currentTime - lastToneChange > (isPlaying ? 500 : 2)) {
if (isPlaying) {
noTone(BUZZER);
isPlaying = false;
currentNote = (currentNote + 1) % n_notes;
} else {
tone(BUZZER, notes[currentNote]);
isPlaying = true;
}
lastToneChange = currentTime;
}
delay(10);
}
noTone(BUZZER);
digitalWrite(LED_1, LOW);
}
void update_time_with_check_alarm() {
display.clearDisplay();
update_time();
print_time_now();
for (int i = 0; i <= n_alarms; i++) {
if (alarm_en[i] && !alarm_triggered[i] &&
hours == alarm_hours[i] && minutes == alarm_minutes[i]) {
ring_alarm(); // Call the ringing function
alarm_triggered[i] = true;
}
}
}
// Function to wait for button press in the menu
int wait_for_button_press() {
while (true) {
if (digitalRead(UP) == LOW) {
delay(200);
return UP;
}
else if (digitalRead(DOWN) == LOW) {
delay(200);
return DOWN;
}
else if (digitalRead(PB_CANCEL) == LOW) {
delay(200);
return PB_CANCEL;
}
else if (digitalRead(OK) == LOW) {
delay(200);
return OK;
}
update_time();
}
}
// Function to navigate through the menu
void go_to_menu() {
while (digitalRead(PB_CANCEL) == HIGH) {
display.clearDisplay();
print_line(options[current_mode], 2, 0, 0);
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(200);
}
else if (pressed == OK) {
Serial.println(current_mode);
delay(200);
run_mode(current_mode);
}
else if (pressed == PB_CANCEL){
delay(200);
break;
}
}
}
/*void set_time() {
int temp_hour = hours;
while (true) {
display.clearDisplay();
print_line("Enter hour: " + String(temp_hour), 2, 0, 2);
int pressed = wait_for_button_press();
if (pressed == UP) {
delay(200);
temp_hour += 1;
temp_hour = temp_hour % 24;
}
else if (pressed == DOWN) {
delay(200);
temp_hour -= 1;
if (temp_hour < 0) {
temp_hour = 23;
}
}
else if (pressed == OK) {
delay(200);
hours = temp_hour;
break;
}
else if (pressed == PB_CANCEL) {
delay(200);
break;
}
}
int temp_minute = minutes;
while (true) {
display.clearDisplay();
print_line("Enter minute: " + String(temp_minute), 2, 0, 2);
int pressed = wait_for_button_press();
if (pressed == UP) {
delay(200);
temp_minute += 1;
temp_minute = temp_minute % 60;
}
else if (pressed == DOWN) {
delay(200);
temp_minute -= 1;
if (temp_minute < 0) {
temp_minute = 59;
}
}
else if (pressed == OK) {
delay(200);
minutes = temp_minute;
break;
}
else if (pressed == PB_CANCEL) {
delay(200);
break;
}
}
display.clearDisplay();
print_line("Time is set", 0, 0, 2);
delay(1000);
}*/
void set_alarm(int alarm) {
// Set hour
alarm_en[alarm]= true;
int temp_hour = alarm_hours[alarm];
while (true) {
display.clearDisplay();
print_line("Enter hour: " + String(temp_hour), 2, 0, 2);
int pressed = wait_for_button_press();
if (pressed == UP) {
delay(200);
temp_hour += 1;
temp_hour = temp_hour % 24;
}
else if (pressed == DOWN) {
delay(200);
temp_hour -= 1;
if (temp_hour < 0) {
temp_hour = 23;
}
}
else if (pressed == OK) {
delay(200);
alarm_hours[alarm] = temp_hour;
break;
}
else if (pressed == PB_CANCEL) {
delay(200);
break;
}
}
int temp_minute = alarm_minutes[alarm];
while (true) {
display.clearDisplay();
print_line("Enter minute: " + String(temp_minute), 2, 0, 2);
int pressed = wait_for_button_press();
if (pressed == UP) {
delay(200);
temp_minute += 1;
temp_minute = temp_minute % 60;
}
else if (pressed == DOWN) {
delay(200);
temp_minute -= 1;
if (temp_minute < 0) {
temp_minute = 59;
}
}
else if (pressed == OK) {
delay(200);
alarm_minutes[alarm] = temp_minute;
break;
}
else if (pressed == PB_CANCEL) {
delay(200);
break;
}
}
display.clearDisplay();
print_line("Alarm is set", 0, 0, 2);
delay(1000);
}
void run_mode(int mode) {
if (mode == 0) {
set_timezone();
}
else if (mode == 1 || mode == 2) {
set_alarm(mode - 1);
}
else if (mode == 3) {
Toggle_alarms();
}
}
void check_temp(void) {
TempAndHumidity data = dhtSensor.getTempAndHumidity();
bool all_good = true;
if (data.temperature > 32) {
all_good = false;
digitalWrite(LED_2, HIGH);
print_line("TEMP HIGH", 1, 50, 55);
}
else if (data.temperature < 24) {
all_good = false;
digitalWrite(LED_2, HIGH);
print_line("TEMP LOW", 1, 50, 55);
}
if (data.humidity > 80) {
all_good = false;
digitalWrite(LED_2, HIGH);
print_line("HUM HIGH", 1, 50, 0);
}
else if (data.humidity < 65) {
all_good = false;
digitalWrite(LED_2, HIGH);
print_line("HUM LOW", 1, 50, 0);
}
if (all_good) {
digitalWrite(LED_2, LOW);
}
}
void Toggle_alarms() {
int selected_alarm = 0; // 0 for Alarm 1, 1 for Alarm 2
while (true) {
display.clearDisplay();
print_line("Alarm " + String(selected_alarm + 1), 2, 0, 2);
print_line("Status:" + String(alarm_en[selected_alarm] ? "ON" : "OFF"), 2, 40, 2);
int pressed = wait_for_button_press();
if (pressed == UP || pressed == DOWN) {
delay(200);
selected_alarm = 1 - selected_alarm; // Toggle between 0 and 1
}
else if (pressed == OK) {
delay(200);
alarm_en[selected_alarm] = false; // Disable selected alarm
display.clearDisplay();
print_line("Alarm " + String(selected_alarm+1), 2, 0, 2);
print_line("Disabled!",2,30,0);
delay(1000);
break;
}
else if (pressed == PB_CANCEL) {
delay(200);
break;
}
}
}
void set_timezone() {
int temp_offset_hour = UTC_OFFSET_HOUR;
int temp_offset_minute = UTC_OFFSET_MIN;
while (true) {
display.clearDisplay();
// Correct string handling
display.clearDisplay();
print_line("Hours: " + String(temp_offset_hour), 2, 0, 2);
int pressed = wait_for_button_press();
if (pressed == UP) {
delay(200);
temp_offset_hour += 1;
if (temp_offset_hour > 14) temp_offset_hour = -12;
}
else if (pressed == DOWN) {
delay(200);
temp_offset_hour -= 1;
if (temp_offset_hour < -12) temp_offset_hour = 14;
}
else if (pressed == OK) {
delay(200);
break;
}
}
while (true) {
display.clearDisplay();
print_line("Minutes:" + String(temp_offset_minute), 2, 0, 2);
int pressed = wait_for_button_press();
if (pressed == UP) {
delay(200);
temp_offset_minute += 15;
if (temp_offset_minute >= 60) temp_offset_minute = 0;
}
else if (pressed == DOWN) {
delay(200);
temp_offset_minute -= 15;
if (temp_offset_minute < 0) temp_offset_minute = 45;
}
else if (pressed == OK) {
delay(200);
UTC_OFFSET_HOUR = temp_offset_hour;
UTC_OFFSET_MIN = temp_offset_minute;
break;
}
else if (pressed == PB_CANCEL) {
delay(200);
break;
}
}
display.clearDisplay();
print_line("UTC Offset Set!", 2, 0, 2);
delay(1000);
}
void snooze_alarm() {
// Show message
display.clearDisplay();
print_line("Alarm", 2, 0, 0);
print_line("Snoozed",2,30,0);
digitalWrite(LED_1, LOW);
noTone(BUZZER);
int snooze_minutes = minutes + 5;
int snooze_hours = hours;
if (snooze_minutes >= 60) {
snooze_minutes -= 60;
snooze_hours += 1;
if (snooze_hours >= 24) {
snooze_hours -= 24;
}
}
// Create a temporary alarm for the snooze
int snooze_index = n_alarms;
// Store the snooze alarm time
alarm_hours[snooze_index] = snooze_hours;
alarm_minutes[snooze_index] = snooze_minutes;
alarm_triggered[snooze_index] = false;
alarm_en[snooze_index] = true;
delay(2000);
}
void reconnectMQTT() {
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
String clientId = "ESP32Client-" + String(random(0xffff), HEX);
if (client.connect(clientId.c_str())) {
Serial.println("connected");
client.subscribe("ENTC-220169V/interval");
client.subscribe("ENTC-220169V/averageCount");
client.subscribe("ENTC-220169V/config");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" retrying in 5 seconds");
delay(5000);
}
}
}
void manageLDRReadings() {
static unsigned long lastAverageTime = 0;
if (millis() - lastReadingTime >= READING_INTERVAL) {
lastReadingTime = millis();
float newReading = 1.0 - (analogRead(LDR_PIN) / 4095.0);
ldrSum += newReading;
readingCount++;
I = newReading;
Serial.print("New reading: ");
Serial.println(newReading, 4);
}
if (readingCount >= READINGS_PER_AVERAGE) {
float average = ldrSum / readingCount;
char payload[10];
snprintf(payload, sizeof(payload), "%.4f", average);
if (client.publish("ENTC-220169V", payload)) {
Serial.print("Published 2-min avg: ");
Serial.println(payload);
}
ldrSum = 0;
readingCount = 0;
lastAverageTime = millis();
}
}
void callback(char* topic, byte* payload, unsigned int length) {
payload[length] = '\0'; // Null-terminate payload
String payloadStr = String((char*)payload);
// Handle config JSON
if (strcmp(topic, "ENTC-220169V/config") == 0) {
StaticJsonDocument<200> doc;
DeserializationError err = deserializeJson(doc, payloadStr);
if (!err) {
if (doc.containsKey("theta_offset")) {
theta_offset = doc["theta_offset"];
Serial.print("Updated thetaoffset: "); Serial.println(theta_offset);
}
if (doc.containsKey("gammaval")) {
gammaval = doc["gammaval"];
Serial.print("Updated gammaval: "); Serial.println(gammaval);
}
if (doc.containsKey("tmed")) {
tmed = doc["tmed"];
Serial.print("Updated Tmed: "); Serial.println(tmed);
}
} else {
Serial.println("JSON parse error");
}
return;
}
// Handle interval and averageCount topics
int value = payloadStr.toInt();
if (strcmp(topic, "ENTC-220169V/interval") == 0) {
if (value >= 1 && value <= 60) { // Limit 1s to 60s
READING_INTERVAL = value * 1000;
t_s=value;
Serial.print("Updated READING_INTERVAL to: ");
Serial.println(READING_INTERVAL);
}
} else if (strcmp(topic, "ENTC-220169V/averageCount") == 0) {
if (value >= 1 && value <= 100) {
t_u=value;
READINGS_PER_AVERAGE = round((value * 1000.0) / READING_INTERVAL);
Serial.print("Updated READINGS_PER_AVERAGE to: ");
Serial.println(READINGS_PER_AVERAGE);
}
}
}
void publishTempData() {
if (millis() - lastTempPublishTime >= TEMP_PUBLISH_INTERVAL) {
lastTempPublishTime = millis();
TempAndHumidity data = dhtSensor.getTempAndHumidity();
T = data.temperature;
// Convert temperature to string
char payload[16];
snprintf(payload, sizeof(payload), "%.2f", data.temperature);
// Publish to MQTT
if (client.publish("ENTC-220169V/Temp", payload)) {
Serial.print("Published temperature: ");
Serial.println(payload);
} else {
Serial.println("Failed to publish temperature.");
}
}
}
float computeTheta() {
float ln_term = fabs(log(t_s / t_u)); // natural log
float theta = theta_offset + (180 - theta_offset) * I * gammaval * (T / tmed);
return constrain(theta, 0, 180); // servo only accepts 0–180
}
void setup() {
pinMode(BUZZER, OUTPUT);
pinMode(LED_1, OUTPUT);
pinMode(LED_2, OUTPUT);
pinMode(PB_CANCEL, INPUT);
pinMode(UP, INPUT);
pinMode(DOWN, INPUT);
pinMode(OK, INPUT);
pinMode(LDR_PIN, INPUT);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
display.clearDisplay();
print_line("Welcome to Medibox", 2, 0, 0);
delay(2000);
display.clearDisplay();
WiFi.begin("Wokwi-GUEST", "", 6);
while (WiFi.status() != WL_CONNECTED) {
delay(250);
display.clearDisplay();
print_line("Connecting to WIFI", 0, 0, 2);
}
display.clearDisplay();
print_line("Connected to WIFI", 0, 0, 2);
client.setServer(mqtt_server, 1883);
client.setCallback(callback); // set MQTT callback
configTime(UTC_OFFSET_HOUR * 3600 + UTC_OFFSET_MIN * 60, 0, NTP_SERVER);
Serial.begin(115200);
dhtSensor.setup(DHT, DHTesp::DHT22);
myServo.setPeriodHertz(50); // Standard 50 Hz servo
myServo.attach(servoPin, 500, 2400);
}
void loop() {
update_time_with_check_alarm();
if (digitalRead(OK)== LOW){
delay(200);
go_to_menu();
}
check_temp();
delay(200);
if (!client.connected()) {
reconnectMQTT();
}
client.loop();
manageLDRReadings();
publishTempData();
float theta = computeTheta();
Serial.println(theta);
myServo.write(theta);
}