// Include libraries
// Libraries for the OLED display
#include <Wire.h> // communicatio with the device and display
#include <Adafruit_GFX.h> //This is a graphics library by Adafruit that provides a common set of graphics functions for different displays
#include <Adafruit_SSD1306.h> //This library is specific to the SSD1306 OLED display controller. It provides functions to interact with OLED
//dht sensor
#include <DHTesp.h>
//wifi library
#include <WiFi.h>
// for the mqtt connection
#include <PubSubClient.h>
// for servo motor
#include <ESP32Servo.h>
// Define OLED parameters
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1 //This constant is used to define the reset pin for the OLED display. The value -1 indicates that the reset pin is not connected
#define SCREEN_ADDRESS 0x3C //This constant specifies the I2C address of the OLED display. The I2C address is used to communicate with the display over the I2C bus
// for the buzzer
#define BUZZER 5
#define LED_1 15
// for the menu controlling
#define PB_cancel 34
#define PB_OK 32
#define PB_UP 33
#define PB_DOWN 35
//dht sensor pins
#define DHTPIN 12
//for wife
#define NTP_SERVER "pool.ntp.org" // NTP server that provides accurate time information. NTP is used to synchronize the device's clock with a reliable time source on the internet.
#define UTC_OFFSET_DST 0 // indicating that there is no daylight saving time (DST) offset applied.
int UTC_OFFSET = 0; // this should be updatable as we are setting the time zone taking the offset from UTC-time
// ESP32's pin GPIO36 connected to AO pin of the ldr module.
#define AO_PIN_Left 36
#define AO_PIN_Right 39
// for the servor motor functionality.
const int servoPin = 18;
// Declare objects
/*
..Adafruit_SSD1306: This is the class provided by the Adafruit_SSD1306 library. It encapsulates the functionality needed to communicate with and control SSD1306-based OLED displays.
..display: This is the name given to the object of the Adafruit_SSD1306 class
*/
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// making an instance of wificlient class.
// through this the mqttclient is initiated.
WiFiClient espClient;
PubSubClient mqttClient(espClient);
// creating an instance of servo motor.
Servo myServo;
//time related global variables
int days = 0;
int hours = 0;
int minutes = 0;
int seconds = 0;
//for the update time
unsigned long timeNow = 0;
unsigned long timeLast = 0;
//alarms
bool alarm_enabled = true;
int n_alarms = 3;
int alarm_hours[] = {0, 0, 0};
int alarm_minutes[] = {1, 10, 5};
bool alarm_triggered[] = {false, false, false};
//for the buzzer
int C = 262;
int D = 294;
int E = 330;
int F = 349;
int G = 392;
int A = 440;
int B = 194;
int C_H = 523;
int notes[] = {C, D, E, F, G, A, B, C_H};
int n_notes = 8;
// for the menu function
int current_mode = 0;
int max_modes = 5;
String modes[] = {"1 - Set Time", "2 - Set Alarm 1", "3 - Set Alarm 2", "4 - Disable Alarm", "5 - Set Time Zone"};
// declaring dht sensor object.
DHTesp dhtSensor;
// array to hold the LDR values
int ldrValueLeft;
int ldrValueRight;
char ldrLeftArr[6];
char ldrRightArr[6];
// for the motor angle control
int theta_offset = 30;
int gamma_servo = 0.75;
int D_servo;
float maxIntensityLight;
float ldrValueLeft_updated;
float ldrValueRight_updated;
// handling the angle of the servo arm.
int servoAngle = 0;
void setup() {
// put your setup code here, to run once:
//initialize the serial communication
Serial.begin(115200);
// initializing the object.
myServo.attach(servoPin, 500, 2400);
myServo.write(0);
setupMqtt();
// for the alarms
pinMode(BUZZER, OUTPUT);
pinMode(LED_1, OUTPUT);
// for the menu
pinMode(PB_cancel, INPUT);
pinMode(PB_OK, INPUT);
pinMode(PB_UP, INPUT);
pinMode(PB_DOWN, INPUT);
// initializes the dhtSensor object, telling it which pin is connected to the DHT sensor's data pin (DHTPIN) and the specific type of DHT sensor being used (DHT22)
dhtSensor.setup(DHTPIN, DHTesp::DHT22);
// initialize the OLED screen
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;); //dont proceed loop forever
}
display.display();
delay(500); //pause for 3 seconds
//for wifi connection
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);
// UTC_OFFSET: UTC offset in seconds. It represents the time difference between the local time and Coordinated Universal Time (UTC)
// UTC_OFFSET_DST: This parameter is likely related to Daylight Saving Time (DST). DST is a practice where clocks are set forward during warmer months to extend evening daylight.
// NTP_SERVER: This is the domain name or IP address of the NTP (Network Time Protocol) server that the device will use to synchronize its time
// configTime function initializes the system time on the ESP32 device using the specified parameters. It sets up the device to periodically synchronize its time with the NTP server
configTime(UTC_OFFSET, UTC_OFFSET_DST, NTP_SERVER);
//clear the buffer
display.clearDisplay(); //here we are starting the display
print_line("Welcome to the Medi Box!", 10, 20, 1.8);
delay(2500);
display.clearDisplay(); //here we are starting the display
}
void loop() {
// put your main code here, to run repeatedly:
Serial.println(ldrValueLeft);
// reading from the ldrs
getLdrValue();
// establish the connection to the brocker.
if (!mqttClient.connected()){
connectToBroker();
}
mqttClient.loop();
publishHighestLdr();
getMotorAngle();
myServo.write(servoAngle);
// we should update the time and check if there is any alarm set for the current time should do regularly.
update_time_with_check_alarms();
// we constantly chekc if the menu button is pressed or not.
if (digitalRead(PB_OK) == LOW) {
delay(200);
go_to_menu();
}
// we constantly check what is the temperature.
check_temp();
}
// get the motor angle
void getMotorAngle(){
float maxAngle = 180;
servoAngle = min((theta_offset*D_servo + (maxAngle-theta_offset)*maxIntensityLight*gamma_servo), maxAngle);
}
// publish the highest ldr value to dashboard.
// HIGHEST MEANS - light intensity highest.
void publishHighestLdr(){
// the voltage input to the pin is high when it is darker, means light intensity is lower.
if (ldrValueLeft > ldrValueRight){
maxIntensityLight = ldrValueRight_updated;
D_servo = 0.5;
mqttClient.publish("medibox2_LDR_HIGHEST_NAME", "Right-LDR-is high");
mqttClient.publish("medibox2_LDR_HIGHEST", ldrRightArr);
}
else{
maxIntensityLight = ldrValueLeft_updated;
D_servo = 1.5;
mqttClient.publish("medibox2_LDR_HIGHEST_NAME", "Left-LDR-is high");
mqttClient.publish("medibox2_LDR_HIGHEST", ldrLeftArr);
}
}
// accessing the ldr voltage value
void getLdrValue(){
int ldrValueLeftRaw = analogRead(AO_PIN_Left);
int ldrValueRightRaw = analogRead(AO_PIN_Right);
ldrValueLeft = (ldrValueLeftRaw);
ldrValueRight = (ldrValueRightRaw);
ldrValueLeft_updated = (float) (ldrValueLeftRaw - 32)/4031; // note that the max voltage for min intensity is 32, min voltage for mat intensity is 4063
ldrValueRight_updated = (float) (ldrValueRightRaw - 32)/4031;
String(ldrValueLeft_updated, 4).toCharArray(ldrLeftArr, 6);
String(ldrValueRight_updated, 4).toCharArray(ldrRightArr, 6);
}
// setting up the mqtt server
// setting up the receivecallback function to hand calling back from the broker.
void setupMqtt() {
mqttClient.setServer("test.mosquitto.org", 1883);
mqttClient.setCallback(receiveCallback);
}
// connecting to the brocker.
void connectToBroker(){
while (!mqttClient.connected()){
Serial.print("Attempting MQTT connection...");
if (mqttClient.connect("ESP32-4646")){
Serial.println("connected");
// if subscribing to topics from the brocker.
// subscribing to topics within the broker.
mqttClient.subscribe("minimum_angle_servo");
mqttClient.subscribe("controlling_factor_servo");
}else{
Serial.println("failed");
}
}
}
// handling things comming from the node-red.
void receiveCallback(char* topic, byte* payload, unsigned int length) {
Serial.print("message arrived [");
Serial.print(topic);
Serial.print("]");
char payloadCharAr[length];
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
payloadCharAr[i] = (char)payload[i];
}
// get content from the subscribed topics by filtering them
if (strcmp(topic, "minimum_angle_servo") == 0) {
// Convert byte array to integer
theta_offset = atoi((char*)payload);
}
// get content from the subscribed topics by filtering them
else if (strcmp(topic, "controlling_factor_servo") == 0) {
// Convert byte array to float
gamma_servo = atof((char*)payload);
}
}
//function to print anything on the screen
void print_line(String text, int column, int row, int text_size) {
display.setTextSize(text_size); //adding a text size for the display
display.setTextColor(SSD1306_WHITE); //adding a text color
display.setCursor(column, row);
display.println(text);
display.display();
}
//function to capture time and print on to the display using the print_line function
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);
}
void update_time() {
struct tm timeinfo; // declares a variable named timeinfo with a data type of struct tm
getLocalTime(&timeinfo); // Call the getLocalTime function to populate the timeinfo struct with the current local time
// &timeinfo - get the address of the timeinfo variable.
// char timeMinute[3]; declares an array named timeMinute with a size of 3 characters.
// timeMinute: This is the destination buffer where the formatted string will be stored.
// 3: This is the size of the destination buffer
// "%M": This is the format specifier string that defines how the time information should be formatted.
// &timeinfo: This is a pointer to a struct tm object (timeinfo), which contains the time information that you want to format.
// atoi(timeHour) converts the character array timeHour to an integer
char timeHour[3];
strftime(timeHour, 3, "%H", &timeinfo); // formats time information into a specified format string and stores it in the provided buffer.
hours = atoi(timeHour); // converts the string into an integer.
char timeMinute[3];
strftime(timeMinute, 3, "%M", &timeinfo);
minutes = atoi(timeMinute);
char timeSecond[3];
strftime(timeSecond, 3, "%S", &timeinfo);
seconds = atoi(timeSecond);
char timeDay[3];
strftime(timeDay, 3, "%d", &timeinfo);
days = atoi(timeDay);
}
void ring_alarm() {
display.clearDisplay();
print_line("MEDICINE TIME", 0, 0, 2);
digitalWrite(LED_1, HIGH);
bool break_happened = false;
//RING THE buzzer
while (break_happened == false && digitalRead(PB_cancel) == HIGH) {
for (int i = 0; i < n_notes; i++) {
//if clause is for stopping the for statement (buzzer) when the push button is pressed
//push button hasnt pressed > pin is at HIGH
//push button is pressed > pin is at LOW
if (digitalRead(PB_cancel) == LOW) {
delay(200);
break_happened = true;
break;
}
tone(BUZZER, notes[i]);
delay(500);
noTone(BUZZER);
delay(2);
}
}
digitalWrite(LED_1, LOW);
display.clearDisplay();
}
void update_time_with_check_alarms() {
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; //this will solve the problem that alarm is triggered again when push button is pressed in alarm minute.
//there is no method to make the alarm_triggered status to put back to false.
}
}
}
}
int wait_for_button_press() {
while (true) {
if (digitalRead(PB_UP) == LOW) {
delay(200);
return PB_UP;
}
else if (digitalRead(PB_DOWN) == LOW) {
delay(200);
return PB_DOWN;
}
else if (digitalRead(PB_OK) == LOW) {
delay(200);
return PB_OK;
}
else if (digitalRead(PB_cancel) == LOW) {
delay(200);
return PB_cancel;
}
update_time();
}
}
void go_to_menu() {
while (digitalRead(PB_cancel) == HIGH) {
display.clearDisplay();
print_line(modes[current_mode], 0, 0, 1);
int pressed = wait_for_button_press();
if (pressed == PB_UP) {
delay(200);
current_mode += 1;
current_mode = current_mode % max_modes;
}
else if (pressed == PB_DOWN) {
delay(200);
current_mode -= 1;
if (current_mode < 0) {
current_mode = max_modes - 1;
}
}
else if (pressed == PB_OK) {
delay(200);
Serial.println(current_mode);
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), 0, 0, 2);
int pressed = wait_for_button_press();
if (pressed == PB_UP) {
delay(200);
temp_hour += 1;
temp_hour = temp_hour % 24;
}
else if (pressed == PB_DOWN) {
delay(200);
temp_hour -= 1;
if (temp_hour < 0) {
temp_hour = 23;
}
}
else if (pressed == PB_OK) {
delay(200);
hours = temp_hour;
break;
}
else if (pressed == PB_cancel) {
delay(200);
break;
}
}
int temp_minutes = minutes;
while (true) {
display.clearDisplay();
print_line("Enter minutes:" + String(temp_minutes), 0, 0, 2);
int pressed = wait_for_button_press();
if (pressed == PB_UP) {
delay(200);
temp_minutes += 1;
temp_minutes = temp_minutes % 60;
}
else if (pressed == PB_DOWN) {
delay(200);
temp_minutes -= 1;
if (temp_minutes < 0) {
temp_minutes = 59;
}
}
else if (pressed == PB_OK) {
delay(200);
minutes = temp_minutes;
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) {
int temp_hour = alarm_hours[alarm];
while (true) {
display.clearDisplay();
print_line("Enter hour:" + String(temp_hour), 0, 0, 2);
int pressed = wait_for_button_press();
if (pressed == PB_UP) {
delay(200);
temp_hour += 1;
temp_hour = temp_hour % 24;
}
else if (pressed == PB_DOWN) {
delay(200);
temp_hour -= 1;
if (temp_hour < 0) {
temp_hour = 23;
}
}
else if (pressed == PB_OK) {
delay(200);
alarm_hours[alarm] = temp_hour;
break;
}
else if (pressed == PB_cancel) {
delay(200);
break;
}
}
int temp_minutes = alarm_minutes[alarm];
while (true) {
display.clearDisplay();
print_line("Enter minutes:" + String(temp_minutes), 0, 0, 2);
int pressed = wait_for_button_press();
if (pressed == PB_UP) {
delay(200);
temp_minutes += 1;
temp_minutes = temp_minutes % 60;
}
else if (pressed == PB_DOWN) {
delay(200);
temp_minutes -= 1;
if (temp_minutes < 0) {
temp_minutes = 59;
}
}
else if (pressed == PB_OK) {
delay(200);
alarm_minutes[alarm] = temp_minutes;
break;
}
else if (pressed == PB_cancel) {
delay(200);
break;
}
}
display.clearDisplay();
print_line("Alarm is set", 0, 0, 2);
}
void set_time_zone() {
int temp_offset = UTC_OFFSET;
while (true) {
display.clearDisplay();
print_line("Enter time zone offset:" + String(temp_offset), 0, 0, 2);
int pressed = wait_for_button_press();
if (pressed == PB_UP) {
delay(200);
temp_offset += 1;
}
else if (pressed == PB_DOWN) {
delay(200);
temp_offset -= 1;
}
else if (pressed == PB_OK) {
delay(200);
UTC_OFFSET = temp_offset;
configTime(UTC_OFFSET, UTC_OFFSET_DST, NTP_SERVER); // Update time with the new time zone
break;
}
else if (pressed == PB_cancel) {
delay(200);
break;
}
}
display.clearDisplay();
print_line("Time zone set", 0, 0, 2);
delay(1000);
}
void run_mode(int mode) {
if (mode == 0) {
set_time();
}
else if (mode == 1 || mode == 2) {
set_alarm(mode - 1);
}
else if (mode == 3) {
alarm_enabled = false;
}
else if (mode == 4) {
set_time_zone();
}
}
void check_temp() {
TempAndHumidity data = dhtSensor.getTempAndHumidity();
if (data.temperature > 32) {
display.clearDisplay();
print_line("TEMP HIGH", 0, 40, 1.5);
delay(700);
}
if (data.temperature < 26) {
display.clearDisplay();
print_line("TEMP LOW", 0, 40, 1.5);
delay(700);
}
if (data.humidity > 80) {
display.clearDisplay();
print_line("HUMIDITY HIGH", 0, 50, 1.5);
delay(700);
}
if (data.humidity < 60) {
display.clearDisplay();
print_line("HUMIDITY LOW", 0, 50, 1.5);
delay(700);
}
}