/////////////////////////////////
#include <PubSubClient.h>
#include <WiFi.h>
#include <DHTesp.h>
#include <NTPClient.h> // Library for time synchronization over NTP
#include <WiFiUdp.h>
#include <ESP32Servo.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// OLED display properties
#define SCREEN_WIDTH 128 //OLEDcdisaplay width , in pixels
#define SCREEN_HEIGHT 64 // OLED dispaly height , in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C // OLED display I2C adress
//Declare objects
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
DHTesp dhtSensor;
////////////////////////////////
WiFiClient espClient;
PubSubClient mqttClient(espClient);
Servo servo;
WiFiUDP ntpUDP; // UDP instance for time synchronization
NTPClient timeClient(ntpUDP);// Time client for retrieving current time
////////////////////////////////
//Global Variables
int days = 0;//
int hours = 0;
int minutes = 0;
int seconds = 0;
/////////////////////////////
// Parameters for light intensity calculation
float gama = 0.75;
const float RL10 = 50;
float OffSetAngle;
float MaxIntensity = 0;
int angle = 0;
float ControllingFactor;
float D; // Factor determined by the LDR providing the maximum intensity
char tempAr[6];// Character array to store temperature data
/////////////////////////////
//unsigned timeNow = 0;
//unsigned timeLast = 0;
bool alarm_enabled = true;//turn on alarms
int n_alarms = 3;//alarm quntitiy to track the alarms
int alarm_hours[] = {0, 1, 2}; //initial alarm hours
int alarm_minutes[] = {1, 0, 0};// Initial alarm minutes
int alarm_triggered[] = {false, false,false}; // Track if alarm is triggered
// Pin definitions
#define Buzzer 5
#define LED_1 15
#define PB_CANCEL 34
#define PB_OK 32
#define PB_UP 33
#define PB_DOWN 35
#define DHTPIN 12
/////////////////////////////
#define LdrLeftPin 27
#define LdrRightPin 14
#define ServoMoter 18
////////////////////////////
#define NTP_SERVER "pool.ntp.org" // Time synchronisation using wifi
int UTC_OFFSET = 0;
#define UTC_OFFSET_DST 0
// Music notes_1 frequencies
int n_notes_1 = 8;
int n_notes_2 = 8;
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 notes_1[8] = {C, E, F, G, A, B, C_H};
int notes_2[8] = {C, E, G, B, F, A, C_H};
// Menu state variables
int current_mode = 0;
int max_modes = 5;
String modes[] = {"1 - Set UTC offset", "2 - Set Alarm 1", "3 - Set Alarm 2","4 - Set Alarm 3", "5 - Disable Alarms" };
void setup() {
// Setup pin modes
pinMode(Buzzer, OUTPUT);
pinMode(LED_1, OUTPUT);
pinMode(PB_CANCEL, INPUT);
pinMode(PB_OK, INPUT);
pinMode(PB_UP, INPUT);
pinMode(PB_DOWN, INPUT);
// Setup DHT sensor
dhtSensor.setup(DHTPIN, DHTesp::DHT22);
Serial.begin(115200);
// Initialize OLED display
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation faild"));
for (;;); // Halt if display initialization fails
}
//turn on oled display
display.display();
delay(2000); // Pause for 2 seconds
// Connect to WiFi
WiFi.begin("Wokwi-GUEST", "", 6);
while (WiFi.status() != WL_CONNECTED) {
delay(250);
display.clearDisplay();
print_line("Connecting to WIFI", 0, 0, 2);//while connecting to wifi
}
display.clearDisplay();
print_line("Connected to WIFI", 0, 0, 2);//when connected to wifi
// Configure time synchronization
configTime(UTC_OFFSET, UTC_OFFSET_DST, NTP_SERVER);
/// Display welcome message
display.clearDisplay();
print_line("Welcome to medi box!", 10, 20, 2);
for (int i = 0; i <= n_notes_1; i++) {
tone(Buzzer, notes_2[i]);//ring the Buzzer
delay(50);
noTone(Buzzer);
delay(2);
}
//delay(200);
display.clearDisplay();
///////////////////////////
setupWifi();// Connect to WiFi
setupMqtt();// Connect to MQTT broker
pinMode(LdrLeftPin, INPUT);
pinMode(LdrRightPin, INPUT);
servo.attach(ServoMoter, 500, 2400);// Attach the servo motor and specify pulse width range
dhtSensor.setup(DHTPIN, DHTesp::DHT22);
timeClient.begin();
timeClient.setTimeOffset(5.5 * 3600);// Set the time offset to GMT +5.5 hours
}
void loop() {
update_time_with_check_alarm();//continously cheak the time
if (digitalRead(PB_OK) == LOW) {
delay(200);
go_to_menu();
}
check_temp_and_humidity();//call function to cheak tem and humedity continoiusly
/////////////////////
//check whether we are connected to MQTT
if (!mqttClient.connected()) {
connectToBroker();
}
//loop through MQTT to check whether there are any incoming messages and all the handling happens
mqttClient.loop();//to handle the MQTT(for incoming messages)
updateTemperature(); // Update temperature data from DHT sensor
Serial.print("temp");
Serial.println(tempAr);
mqttClient.publish("MEDI-TEMP", tempAr);// Publish temperature data to MQTT topic
checkIntensity(); // Check light intensity
delay(1000);
}
void setupWifi() {
WiFi.begin("Wokwi-GUEST", "");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void setupMqtt() {
mqttClient.setServer("test.mosquitto.org", 1883);// Set MQTT server(server,port)
mqttClient.setCallback(receiveCallback); // Set up callback function for incoming messages
}
void connectToBroker() {
while (!mqttClient.connected()) {
Serial.print("Attempting MQTT connection...");
if (mqttClient.connect("ESP32-146398455225")) {//inside brackets should put id for esp32(an unique id) if you use authentication. otherwise just put som erandom numbers
Serial.println("connected");
//subscribe to a certain data
mqttClient.subscribe("MEDI-ON-OFF");// Subscribe to main on-off control topic
mqttClient.subscribe("Minimum angle");
mqttClient.subscribe("Controlling factor ");
} else {
Serial.println("failed");
Serial.println(mqttClient.state());//return the state if not connected with the reason
delay(5000);
}
}
}
void updateTemperature() {
TempAndHumidity data = dhtSensor.getTempAndHumidity();
String(data.temperature, 2).toCharArray(tempAr, 6); // Convert temperature to a character array
}
void receiveCallback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("]");
char payloadCharAr[length]; // Array to store the payload as characters for later use
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]); // Print each character of the payload
payloadCharAr[i] = (char)payload[i];
}
Serial.println();
// Compare incoming messages to subscribed topics
if (strcmp(topic, "MEDI-ON-OFF") == 0) { // Check if the message is about main on-off control
BuzzerOn(payloadCharAr[0] == '1'); // Turn the Buzzer on or off based on the received payload
} else if (strcmp(topic, "Minimum angle") == 0) {//check if set minimum angle
OffSetAngle = atof(payloadCharAr);
} else if (strcmp(topic, "Controlling factor") == 0) {
ControllingFactor = atof(payloadCharAr);
}
}
void BuzzerOn(bool on) {
if (on) {
tone(Buzzer, 256);
} else {
noTone(Buzzer);
}
}
unsigned long getTime() {//this time will present time in seconds passed from javascript fixdate
timeClient.update();
return timeClient.getEpochTime();
}
void checkIntensity() {
int ldrLeftValue = analogRead(LdrLeftPin);
int ldrRightValue = analogRead(LdrRightPin);
// Normalize LDR readings to a range of 0 to 1
float IntensityLeft = convertLDR(ldrLeftValue);
float IntensityRight = convertLDR(ldrRightValue);
Serial.print("left");
Serial.println(IntensityLeft);
Serial.print("right");
Serial.println(IntensityRight);
MaxIntensity = max(IntensityLeft, IntensityRight);
Serial.print("max");
Serial.println(MaxIntensity);
char MaxIntArray[10]; // Buffer to hold the converted float value
String(MaxIntensity).toCharArray(MaxIntArray, 6);
if (MaxIntensity == IntensityLeft) {
D = 1.5;
mqttClient.publish("Right_or_Left", "Left");
mqttClient.publish("Intensity", MaxIntArray);
} else {
D = 0.5;
mqttClient.publish("Right_or_Left", "Right");
mqttClient.publish("Intensity", MaxIntArray);
}
Servo();
delay(1000);
}
float convertLDR(int value) {
float voltage = value / 1024. * 5;
float resistance = 2000 * voltage / (1 - voltage / 5);
float maxlux = pow(RL10 * 1e3 * pow(10, gama) / 322.58, (1 / gama));
float lux = pow(RL10 * 1e3 * pow(10, gama) / resistance, (1 / gama)) / maxlux;
return lux;
}
void Servo() {
angle = min((int)(OffSetAngle * D + (180 - OffSetAngle) * MaxIntensity * ControllingFactor), 180);// Calculate servo motor angle
Serial.print("angle ");
Serial.print(angle);
Serial.print(",");
Serial.print(OffSetAngle);
Serial.print(",");
Serial.print(MaxIntensity);
Serial.print(",");
Serial.print(ControllingFactor);
servo.write(angle);
}
// Function to print a text line on the OLED display
void print_line(String text, int column, int row, int text_size) {
display.setTextSize(text_size); // Noraml 1:1 pixel scale
display.setTextColor(SSD1306_WHITE);
display.setCursor(column, row);
display.println(text);
display.display();
//delay(2000);
}
// Function to print the current time on the OLED display
void print_time_now(void) {
display.clearDisplay();
print_line(String(days), 0, 0, 2);
print_line(":", 20, 0, 2);
print_line(String(hours), 30, 0, 2);
print_line(":", 50, 0, 2);
print_line(String(minutes), 60, 0, 2);
print_line(":", 80, 0, 2);
print_line(String(seconds), 90, 0, 2);
}
// Function to update the time
void update_time() {
struct tm timeinfo;
getLocalTime(&timeinfo);
char timeHour[3];
strftime(timeHour, 3, "%H", &timeinfo);
hours = atoi(timeHour);// a to i function to convert string to integer
char timeMinute[3];
strftime(timeMinute, 3, "%M", &timeinfo);
minutes = atoi(timeMinute);// a to i function to convert string to integer
char timeSecond[3];
strftime(timeSecond, 3, "%S", &timeinfo);
seconds = atoi(timeSecond);// a to i function to convert string to integer
char timeDay[3];
strftime(timeDay, sizeof(timeDay), "%d", &timeinfo);
days = atoi(timeDay);
}
// Function to ring the alarm
void ring_alarm() {
display.clearDisplay();
print_line("MEDICINE TIME!", 0, 0, 2);//Display when alarm triggered
digitalWrite(LED_1, HIGH); //ligh up the LED
bool break_happen = false;//to stop while statment
//Ring the Buzzer
while (break_happen == false && digitalRead(PB_CANCEL) == HIGH) {
for (int i = 0; i <= n_notes_1; i++) {
if (digitalRead(PB_CANCEL) == LOW) {
delay(200);//prevent bouncing of the push button
break_happen = true;//stop alarm when push cancle
break;//stop for statement
}
tone(Buzzer, notes_1[i]);//ring the Buzzer
delay(500);
noTone(Buzzer);
delay(2);
}
}
digitalWrite(LED_1, LOW);
display.clearDisplay();
}
// Function to update time and check for alarm triggers
void update_time_with_check_alarm(void) {
update_time();
print_time_now();
if (alarm_enabled == true) {
for (int i = 0; i < n_alarms; i++) {
if (alarm_triggered[i] == false && alarm_hours[i] == hours && alarm_minutes[i] == minutes) {
ring_alarm();
alarm_triggered[i] = true;
}
}
}
}
// Function to wait for button press
int wait_for_button_press() {
while (true) {
if (digitalRead(PB_UP) == LOW) {
delay(200);//delay for debouncing
return PB_UP;
}
else if (digitalRead(PB_DOWN) == LOW) {
delay(200);//delay for debouncing
return PB_DOWN;
}
else if (digitalRead(PB_OK) == LOW) {
delay(200);//delay for debouncing
return PB_OK;
}
else if (digitalRead(PB_CANCEL) == LOW) {
delay(200);//delay for debouncing
return PB_CANCEL;
}
update_time();//inside this we have to keep updating the time
}
}
// Function to navigate through the menu
void go_to_menu() {
while (digitalRead(PB_CANCEL) == HIGH) { //cheak if the cancle is press to stay in memnu untill cancle
display.clearDisplay();
print_line(modes[current_mode], 0, 0, 2);//pass current mode to prinline function
int pressed = wait_for_button_press();
if ( pressed == PB_UP) {
delay(200);
current_mode += 1;
current_mode = current_mode % max_modes;//if the reach max put it back to zero
}
else if ( pressed == PB_DOWN) {
delay(200);
current_mode -= 1;
if (current_mode < 0) { //reach bello zero put it back to max mode
current_mode = max_modes - 1;
}
}
else if ( pressed == PB_OK) {
delay(200);
//Serial.println(current_mode);
run_mode(current_mode);//this function take current mode as the input
}
else if ( pressed == PB_CANCEL) {
delay(200);
break;//calcle button exit go to menu function
}
}
}
// Function to set the time
void set_UTC_offset() {
int input_hour = 0;
while (true) {// to set hours
display.clearDisplay();
print_line("Enter hour: " + String(input_hour), 0, 0, 2); //Enter hours
//change hrs up down key set - ok not changing global hours
int pressed = wait_for_button_press();
if ( pressed == PB_UP) {
delay(200);
input_hour += 1;
if (input_hour > 14){
input_hour = -12;
}
}
else if ( pressed == PB_DOWN) {
delay(200);
input_hour -= 1;
if (input_hour < -12) { //reach bellow zero put it back to max mode
input_hour = 14;
}
}
else if ( pressed == PB_OK) {
delay(200);
//Serial.println(current_mode);
hours = input_hour;//global varible set local varible value
break;//stop the loop
}
else if ( pressed == PB_CANCEL) {
delay(200);
break;//calcle button exit go to menu function
}
}
int input_minute = minutes;
while (true) {//to set minutes
display.clearDisplay();
print_line("Enter minute: " + String(input_minute), 0, 0, 2);
int pressed = wait_for_button_press();
if ( pressed == PB_UP) {
delay(200);
input_minute += 1;
input_minute = input_minute % 45;
}
else if ( pressed == PB_DOWN) {
delay(200);
input_minute -= 1;
if (input_minute < 0) { //reach bellow zero put it back to max mode
input_minute = 45;
}
}
else if ( pressed == PB_OK) {
delay(200);
//Serial.println(current_mode);
minutes = input_minute;
break;//stop the loop
}
else if ( pressed == PB_CANCEL) {
delay(200);
break;//calcle button exit go to menu function
}
}
UTC_OFFSET = 3600*input_hour + 60*input_minute;
configTime(UTC_OFFSET, UTC_OFFSET_DST, NTP_SERVER);
display.clearDisplay();//display the msg time set and exit
print_line("Time is set", 0, 0, 2);
delay(1000);
}
// Function to set an alarm
void set_alarm(int alarm) {
int temp_hour = alarm_hours[alarm];//take relavent alarm hrs
while (true) {
display.clearDisplay();
print_line("Enter hour: " + String(temp_hour), 0, 0, 2);
//change hrs up down key set - ok not changing global alarm_hours[alarm] we chnage it when ok play
int pressed = wait_for_button_press();
if (pressed == PB_UP) {
delay(200);
temp_hour += 1;
temp_hour = temp_hour % 24;//not to go over 24 hrs or negative numer
}
else if (pressed == PB_DOWN) {
delay(200);
temp_hour -= 1;
if (temp_hour < 0) { //reach bello zero put it back to max mode
temp_hour = 23;
}
}
else if (pressed == PB_OK) {
delay(200);
//Serial.println(current_mode);
alarm_hours[alarm] = temp_hour;
break;//stop the loop
}
else if (pressed == PB_CANCEL) {
delay(200);
break;//cancle button exit go to menu function
}
}
//This is for change mi minutes
int temp_minute = alarm_minutes[alarm];
while (true) {
display.clearDisplay();
print_line("Enter minute: " + String(temp_minute), 0, 0, 2);
//change hrs up down key set - ok not changing global alarm_minutes[alarm] we chnage it when ok play
int pressed = wait_for_button_press();
if ( pressed == PB_UP) {
delay(200);
temp_minute += 1;
temp_minute = temp_minute % 60;//not to go over 24 hrs or negative numer
}
else if ( pressed == PB_DOWN) {
delay(200);
temp_minute -= 1;
if (temp_minute < 0) { //reach bello zero put it back to max mode
temp_minute = 59;
}
}
else if ( pressed == PB_OK) {
delay(200);
//Serial.println(current_mode);
alarm_minutes[alarm] = temp_minute;
break;//stop the loop
}
else if ( pressed == PB_CANCEL) {
delay(1000);
break;//calcle button exit go to menu function
}
}
display.clearDisplay();//display the msg time set and exit
print_line("Alarm is set", 0, 0, 2);
delay(1000);
}
// Function to execute a mode based on the menu selection
void run_mode(int mode) { //add separate function to set time
if (mode == 0) {
set_UTC_offset();
}
//fucntion accept alarm number as input
else if (mode == 1 || mode == 2 ||mode ==3 ) {
set_alarm(mode - 1);//alarm number= current mode -1
}
else if (mode == 4) {//all alarm
alarm_enabled = false;
display.clearDisplay();
print_line("Disable all alarms", 0, 0, 2);
delay(200);
}
}
// Function to check temperature and humidity
void check_temp_and_humidity() {
TempAndHumidity data = dhtSensor.getTempAndHumidity();
display.clearDisplay();
if (data.temperature > 32 ) {
print_line(" TEMP HIGH", 3, 40, 1);
}
else if (data.temperature < 26 ) {
print_line(" TEMP LOW", 3, 40, 1);
}
if (data.humidity > 80 ) {
print_line(" Humidity HIGH", 3, 50, 1);
}
else if (data.humidity < 60 ) {
print_line(" Humidity LOW", 3, 50, 1);
}
delay(500);
}
//LED blink
void LED_Blink() {
digitalWrite(LED_1, HIGH);
delay(500);
digitalWrite(LED_1, LOW);
delay(500);
}