#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "DHTesp.h"
#include <WiFi.h>
#include <PubSubClient.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <ESP32Servo.h>
//Define OLED parameters
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
// Snooze duration in minutes
#define snoozing_minutes 5
// Define hardware pins
#define BUZZER 5
#define LED_1 15
#define PB_CANCEL 34
#define PB_OK 18
#define PB_UP 33
#define PB_DOWN 35
#define DHT_PIN 12 // Pin for DHT22 sensor
// Define NTP server details for time synchronization
#define NTP_SERVER "pool.ntp.org"
#define UTC_OFFSET 0
#define UTC_OFFSET_DST 0
#define LDR 32
#define SERVO_PIN 17 // Servo motor pin
// Create display object
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
WiFiClient espClient;
PubSubClient mqttClient(espClient);
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP);
// Global variables for timekeeping
int days = 0;
int hours = 0;
int minutes = 0;
int seconds = 0;
unsigned long timeNow = 0;
unsigned long timeLast = 0;
// Alarm configuration
bool alarm_enabled = true;
int n_alarms = 2; // Number of alarms
int alarm_hours[] = {0, 1}; // Alarm hours
int alarm_minutes[] = {1, 10}; // Alarm minutes
bool alarm_triggered[] = {false, false}; // Alarm status (triggered or not)
bool snoozed[] = {false, false}; // Snooze status for each alarm
int snooze_hours[] = {0, 0};
int snooze_minutes[] = {0, 0};
// Musical notes for alarm
int n_notes = 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[]={C,D,E,F,G,A,B,C_H};
// Menu settings
int current_mode = 0;
int max_modes = 5;
String modes[] = {"1 - Set Time", "2 - Set Alarm 1", "3 - Set Alarm 2", "4 - View Active Alarms", "5 - Delete Alarms"};
// User-defined UTC offset
int user_utc_offset_hours = 0;
int user_utc_offset_minutes = 0;
char tempArr[6];
//char ldrArr[6];
DHTesp dhtSensor;
Servo servo;
bool isScheduledON = false;
unsigned long scheduledOnTime;
unsigned long lastSampleTime = 0;
unsigned long lastSendTime = 0;
int ts = 5; // Light sampling interval in seconds (time between each LDR reading)
int tu = 120; // Light publishing interval in seconds (time to send average light via MQTT)
int sampleCount = 0; // Counter for number of light samples taken within one publishing cycle
float sampleSum = 0; // Accumulator for normalized light values used to calculate average
float thetaOffset = 30.0; // Minimum angle
float gamma_val = 0.75; // Controlling factor
float T_ideal = 30.0; // Ideal temperature
float avgLightIntensity = 0.1; // Default fallback value
void setup() {
// Initialize pins
pinMode(BUZZER,OUTPUT);
pinMode(LED_1,OUTPUT);
pinMode(PB_CANCEL,INPUT);
pinMode(PB_OK,INPUT);
pinMode(PB_UP,INPUT);
pinMode(PB_DOWN,INPUT);
pinMode(LDR, INPUT);
// Reset alarm states
for (int i = 0; i < n_alarms; i++) {
alarm_hours[i] = 0;
alarm_minutes[i] = 0;
alarm_triggered[i] = false;
snoozed[i] = false;
snooze_hours[i] = 0;
snooze_minutes[i] = 0;
}
// Initialize DHT sensor
dhtSensor.setup(DHT_PIN, DHTesp::DHT22);
// Start serial communication
Serial.begin(115200);
// Initialize OLED display
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
display.display();
delay(500);
// 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);
}
display.clearDisplay();
print_line("Connected to WIFI", 0, 0, 2);
Serial.println("WiFi connected"); //delete later
Serial.println("IP address"); //delete later
setupMqtt(); //setup the mqtt client
timeClient.begin();
timeClient.setTimeOffset(5.5*3600);
servo.attach(SERVO_PIN);
// Apply time zone offset and sync time
int total_offset_seconds = (user_utc_offset_hours * 3600) + (user_utc_offset_minutes * 60);
configTime(total_offset_seconds, 0, NTP_SERVER);
// Welcome message
display.clearDisplay();
print_line("Welcome to Medibox!", 10, 20, 2);
delay(500);
display.clearDisplay();
}
void loop() {
if(!mqttClient.connected()){
Serial.println("Reconnecting to MQTT Broker");
delay(2000); //delete later
connectToBroker();
}
mqttClient.loop();
// Check temperature and humidity conditions
check_temp();
mqttClient.publish("MECH-ADM-TEMP", tempArr);//publish the temperature array
checkSchedule();
delay(200);
handleLightSampling();
delay(1000);
//publish LDR Array
//mqttClient.publish("MT-LIGHT", ldrArr);
//delay(100);
// Continuously update time and check alarms
update_time_with_check_alarm();
// If OK button is pressed, open menu
if (digitalRead(PB_OK) == LOW) {
delay(200);
go_to_menu();
}
}
// Function to print text on OLED display
void print_line(String text, int column, int row, int text_size){
display.setTextSize(text_size); // Set the size of the text
display.setTextColor(SSD1306_WHITE); // Set the color of the text
display.setCursor(column,row); //(column, row)// Set the position
display.println(text);
display.display(); //shows this msg on the display
}
void print_time_now(void){
display.clearDisplay();
print_line(String(days),0,0,2); // Print the current day
print_line(":",20,0,2);
print_line(String(hours),30,0,2); // Print the current hour
print_line(":",50,0,2);
print_line(String(minutes),60,0,2); // Print the current minute
print_line(":",80,0,2);
print_line(String(seconds),90,0,2); // Print the current second
}
// Function to update the current time from NTP server
void update_time() {
struct tm timeinfo; // Declare a structure to hold time information
// Calculate the total UTC offset in seconds
int total_offset_seconds = (user_utc_offset_hours * 3600) + (user_utc_offset_minutes * 60);
// Set the system time using NTP with the provided UTC offset
configTime(total_offset_seconds, 0, NTP_SERVER);
// Retrieve the current local time and store it in the timeinfo structure
getLocalTime(&timeinfo);
// Extract and store the current hour from the time structure
char timeHour[3];
strftime(timeHour, 3, "%H", &timeinfo); // Format the hour as two digits
hours = atoi(timeHour); // Convert the string to 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);
}
void update_time_with_check_alarm(void) {
update_time();
print_time_now();
// Check if alarms are enabled
if (alarm_enabled == true) {
for (int i = 0; i < n_alarms; i++) {
// Check if an alarm is set and matches the current time
if (alarm_triggered[i] == false && alarm_hours[i] == hours && alarm_minutes[i] == minutes) {
ring_alarm(i); // Trigger the alarm
alarm_triggered[i] = true; // Mark it as triggered to prevent re-ringing within the same minute
}
// Check if a snoozed alarm should ring
if (snoozed[i] && snooze_hours[i] == hours && snooze_minutes[i] == minutes) {
ring_alarm(i); // Trigger the snoozed alarm
snoozed[i] = false; // Reset the snooze status
}
}
}
}
int wait_for_button_press() {
while (true) { // Infinite loop to wait until a button is pressed
if (digitalRead(PB_UP) == LOW) {
delay(200); // Debounce delay to avoid multiple detections
return PB_UP; // Return the corresponding button identifier
}
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) {
return PB_CANCEL;
}
update_time(); // Continuously update time while waiting for a button press
}
}
// Function to navigate menu options
void go_to_menu() {
// While the cancel button is **not pressed**, keep displaying the menu
while (digitalRead(PB_CANCEL) == HIGH) {
display.clearDisplay(); // Clear the display
print_line(modes[current_mode], 0, 0, 2); // Show the current menu option
int pressed = wait_for_button_press(); // Wait for a button press
if (pressed == PB_UP) { // Navigate up in the menu
delay(200);
current_mode += 1;
current_mode = current_mode % max_modes; // Ensure it loops within valid mode range
}
else if (pressed == PB_DOWN) { // Navigate down in the menu
delay(200);
current_mode -= 1;
if (current_mode < 0) {
current_mode = max_modes - 1; // Wrap around to the last mode
}
}
else if (pressed == PB_OK) { // Select the current mode
delay(200);
run_mode(current_mode); // Execute the selected mode
}
else if (pressed == PB_CANCEL) { // Exit the menu
delay(200);
break; // Exit the while loop and return to the main program
}
}
}
void set_time() {
int temp_offset_hours = user_utc_offset_hours; // Store current UTC hour offset temporarily
// Loop to set the UTC hour offset
while (true) {
display.clearDisplay(); // Clear the display
print_line("UTC Offset Hours: " + String(temp_offset_hours), 0, 0, 2); // Show current offset
int pressed = wait_for_button_press(); // Wait for button press
if (pressed == PB_UP) {
delay(200);
temp_offset_hours += 1;
if (temp_offset_hours > 14) temp_offset_hours = 14; // Maximum UTC offset allowed
}
else if (pressed == PB_DOWN) {
delay(200);
temp_offset_hours -= 1;
if (temp_offset_hours < -12) temp_offset_hours = -12; // Minimum UTC offset allowed
}
else if (pressed == PB_OK) {
delay(200);
user_utc_offset_hours = temp_offset_hours; // Save selected offset
break; // Exit loop
}
else if (pressed == PB_CANCEL) {
delay(200);
return; // Exit function without saving
}
}
int temp_offset_minutes = user_utc_offset_minutes; // Store current UTC minute offset temporarily
// Loop to set the UTC minute offset
while (true) {
display.clearDisplay(); // Clear the display
print_line("UTC Offset Minutes: " + String(temp_offset_minutes), 0, 0, 2); // Show current offset
int pressed = wait_for_button_press(); // Wait for button press
if (pressed == PB_UP) {
delay(200);
temp_offset_minutes += 1;
if (temp_offset_minutes >= 60) temp_offset_minutes = 0; // Reset to 0 if exceeding 59 minutes
}
else if (pressed == PB_DOWN) {
delay(200);
temp_offset_minutes -= 1;
if (temp_offset_minutes < 0) temp_offset_minutes = 59; // Loop back to 59 if below 0
}
else if (pressed == PB_OK) {
delay(200);
user_utc_offset_minutes = temp_offset_minutes; // Save selected offset
break; // Exit loop
}
else if (pressed == PB_CANCEL) {
delay(200);
return; // Exit function without saving
}
}
// Display confirmation message
display.clearDisplay();
print_line("UTC Offset Set", 0, 0, 2);
delay(1000);
// Convert hours and minutes to seconds for time configuration
int total_offset_seconds = (user_utc_offset_hours * 3600) + (user_utc_offset_minutes * 60);
configTime(total_offset_seconds, 0, NTP_SERVER); // Apply the new UTC offset
}
void set_alarm(int alarm) {
// Reset the alarm when setting a new one
alarm_triggered[alarm] = false; // Ensure the alarm is marked as active
// Temporary variable to store user input for hours
int temp_hour = alarm_hours[alarm];
// Loop to set the hour value
while (true) {
display.clearDisplay(); // Clear the OLED display
print_line("Enter hour: " + String(temp_hour), 0, 0, 2); // Display current hour selection
int pressed = wait_for_button_press(); // Wait for user input
if (pressed == PB_UP) {
delay(200);
temp_hour = (temp_hour + 1) % 24; // Increment hour (looping back to 0 after 23)
}
else if (pressed == PB_DOWN) {
delay(200);
temp_hour = (temp_hour - 1 + 24) % 24; // Decrement hour (looping back to 23 after 0)
}
else if (pressed == PB_OK) {
delay(200);
alarm_hours[alarm] = temp_hour; // Save the selected hour
break; // Exit loop
}
else if (pressed == PB_CANCEL) {
delay(200);
return; // Exit function without saving
}
}
// Temporary variable to store user input for minutes
int temp_minute = alarm_minutes[alarm];
// Loop to set the minute value
while (true) {
display.clearDisplay(); // Clear the OLED display
print_line("Enter minute: " + String(temp_minute), 0, 0, 2); // Display current minute selection
int pressed = wait_for_button_press(); // Wait for user input
if (pressed == PB_UP) {
delay(200);
temp_minute = (temp_minute + 1) % 60; // Increment minute (looping back to 0 after 59)
}
else if (pressed == PB_DOWN) {
delay(200);
temp_minute = (temp_minute - 1 + 60) % 60; // Decrement minute (looping back to 59 after 0)
}
else if (pressed == PB_OK) {
delay(200);
alarm_minutes[alarm] = temp_minute; // Save the selected minute
break; // Exit loop
}
else if (pressed == PB_CANCEL) {
delay(200);
return; // Exit function without saving
}
}
// Confirm alarm has been set
display.clearDisplay();
print_line("Alarm is set", 0, 0, 2);
delay(1000); // Display confirmation message for 1 second
}
void view_active_alarms() {
display.clearDisplay(); // Clear the OLED display before showing alarms
int y_offset = 0; // Variable to control vertical spacing of text on display
bool any_active_alarms = false; // Flag to check if there are active alarms
// Loop through all alarms and display only active ones
for (int i = 0; i < n_alarms; i++) {
if (!alarm_triggered[i]) {
print_line("Alarm " + String(i + 1) + ": " + String(alarm_hours[i]) + ":" + String(alarm_minutes[i]), 0, y_offset, 2);
y_offset += 35; // Move to the next line for the next alarm
any_active_alarms = true; // Mark that at least one alarm is active
}
}
// If no active alarms are found, display a message
if (!any_active_alarms) {
print_line("No any active alarms", 0, 20, 2);
display.display();
delay(3000);
return;
}
display.display(); // Update the display with active alarms
// If there are active alarms, wait until the user presses the OK button
while (digitalRead(PB_OK) != LOW) {
delay(50); // Small delay to avoid high CPU usage in the loop
}
delay(200); // Debounce delay to prevent accidental double presses
}
void delete_alarm() {
bool has_active_alarms = false;
// Check if there are any active alarms
for (int i = 0; i < n_alarms; i++) {
if (!alarm_triggered[i]) { // If an alarm is NOT triggered, it's still active
has_active_alarms = true;
break;
}
}
// If no active alarms exist, display a message and return
if (!has_active_alarms) {
display.clearDisplay();
print_line("No active alarms to delete", 0, 0, 2);
display.display();
delay(2000);
return;
}
// Display a message asking the user to select an alarm to delete
display.clearDisplay();
print_line("Select alarm to delete", 0, 20, 2);
display.display();
delay(1000);
int selected_alarm = 0; // Keeps track of the currently selected alarm
while (true) {
display.clearDisplay();
int y_offset = 0; // Vertical spacing for displaying alarms
int valid_alarm_count = 0; // Counts the number of active alarms
// Display active alarms in a list
for (int i = 0; i < n_alarms; i++) {
if (!alarm_triggered[i]) { // Only show active alarms
if (valid_alarm_count == selected_alarm) {
// Highlight the selected alarm
print_line("> Alarm " + String(i + 1) + ": " + String(alarm_hours[i]) + ":" + String(alarm_minutes[i]), 0, y_offset, 2);
} else {
// Display non-selected alarms normally
print_line(" Alarm " + String(i + 1) + ": " + String(alarm_hours[i]) + ":" + String(alarm_minutes[i]), 0, y_offset, 2);
}
valid_alarm_count++;
y_offset += 35; // Adjust y-offset for next alarm
}
}
display.display();
int pressed = wait_for_button_press(); // Wait for button input
// Navigate the alarm list
if (pressed == PB_UP) {
if (valid_alarm_count > 0) {
selected_alarm = (selected_alarm + 1) % valid_alarm_count; // Move selection up
}
}
if (pressed == PB_DOWN) {
if (valid_alarm_count > 0) {
selected_alarm = (selected_alarm - 1 + valid_alarm_count) % valid_alarm_count; // Move selection down
}
}
if (pressed == PB_OK) { // If OK button is pressed, delete the selected alarm
int alarm_index = -1, count = 0;
// Find the actual index of the selected alarm
for (int i = 0; i < n_alarms; i++) {
if (!alarm_triggered[i]) {
if (count == selected_alarm) {
alarm_index = i;
break;
}
count++;
}
}
// Delete the selected alarm
if (alarm_index != -1) {
alarm_triggered[alarm_index] = true; // Mark alarm as triggered (inactive)
alarm_hours[alarm_index] = 0; // Reset alarm hours
alarm_minutes[alarm_index] = 0; // Reset alarm minutes
// Display deletion confirmation
display.clearDisplay();
print_line("Alarm " + String(alarm_index + 1) + " deleted", 0, 20, 2);
display.display();
delay(1000);
}
// Check if any active alarms remain
has_active_alarms = false;
for (int i = 0; i < n_alarms; i++) {
if (!alarm_triggered[i]) {
has_active_alarms = true;
break;
}
}
// If no alarms remain, show a message and return to the main menu
if (!has_active_alarms) {
display.clearDisplay();
print_line("No more alarms active", 0, 20, 2);
display.display();
delay(3000);
return;
}
}
// If the user presses the CANCEL button, exit the function without deleting anything
if (pressed == PB_CANCEL) {
return;
}
}
}
void ring_alarm(int alarm_index) {
// Clear the display and show an alert message
display.clearDisplay();
print_line("MEDICINE TIME!", 0, 0, 2);
// Turn on the LED to indicate alarm activation
digitalWrite(LED_1, HIGH);
bool break_happened = false; // Flag to check if a button is pressed
// Loop to keep ringing the alarm until a button is pressed
while (!break_happened) {
for (int i = 0; i < n_notes; i++) { // Play alarm tones
if (digitalRead(PB_CANCEL) == LOW) {
delay(200); // Debounce delay
snooze_alarm(alarm_index); // Snooze alarm (default snooze time)
break_happened = true;
break;
} else if (digitalRead(PB_OK) == LOW) {
delay(200); // Debounce delay
alarm_triggered[alarm_index] = false; // Turn off alarm
break_happened = true;
break;
}
tone(BUZZER, notes[i]); // Play a tone from the notes array
delay(500);
noTone(BUZZER); // Stop the tone
delay(2);
}
}
// Turn off LED when alarm is dismissed or snoozed
digitalWrite(LED_1, LOW);
// Clear the display after stopping the alarm
display.clearDisplay();
}
void snooze_alarm(int alarm_index) {
// Mark the alarm as snoozed
snoozed[alarm_index] = true;
// Set the snooze time by adding the predefined snoozing minutes (5 minutes)
snooze_minutes[alarm_index] = alarm_minutes[alarm_index] + snoozing_minutes;
snooze_hours[alarm_index] = alarm_hours[alarm_index];
// Handle minute overflow (if snooze minutes exceed 60)
if (snooze_minutes[alarm_index] >= 60) {
snooze_minutes[alarm_index] -= 60; // Adjust minutes to stay within valid range
snooze_hours[alarm_index] = (snooze_hours[alarm_index] + 1) % 24; // Adjust hours (wrap around if needed)
}
// Display a message indicating the alarm has been snoozed
display.clearDisplay();
print_line("Snoozed for 5 min", 0, 0, 2);
delay(1000); // Show message for 1 second
}
void run_mode(int mode){
if (mode==0){
set_time(); //go to set_time function
}
else if (mode == 1 || mode == 2){
set_alarm(mode-1); //go to set_alarm function
}
else if (mode == 3){
view_active_alarms();// go to view alarm function
}
else if (mode == 4){
delete_alarm(); // go to delete alarm function
}
}
void setupMqtt() {
// Set the MQTT broker address and port
mqttClient.setServer("broker.hivemq.com", 1883);
// Set the function to handle incoming messages
mqttClient.setCallback(recieveCallback);
}
void recieveCallback(char* topic, byte* payload, unsigned int length) {
// Print received topic
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
// Convert payload to null-terminated string
char payloadCharAr[length + 1];
for (int i = 0; i < length; i++) payloadCharAr[i] = (char)payload[i];
payloadCharAr[length] = '\0';
Serial.println();
// Handle buzzer ON/OFF
if (strcmp(topic, "MECH-ADM-MAIN-ON-OFF") == 0) {
buzzerOn(payloadCharAr[0] == '1');
// Handle scheduled ON time
} else if (strcmp(topic, "MECH-ADM-SCH-ON") == 0) {
if (payloadCharAr[0] == 'N') {
isScheduledON = false;
} else {
unsigned long timeFromPayload = atol(payloadCharAr);
unsigned long now = getTime();
if (timeFromPayload > now) {
scheduledOnTime = timeFromPayload;
isScheduledON = true;
Serial.printf("Schedule set for: %lu (current: %lu)\n", scheduledOnTime, now);
Serial.println();
} else {
Serial.println("Received scheduled time is in the past. Ignored.");
Serial.println();
isScheduledON = false;
}
}
// Handle light sampling interval (ts)
} else if (strcmp(topic, "MECH-LIGHT-CONFIG-TS") == 0) {
int val = atoi(payloadCharAr);
if (val > 0 && val <= 60) {
ts = val;
Serial.print("Updated ts (sample interval): ");
Serial.println(ts);
Serial.println();
}
// Handle light sending interval (tu)
} else if (strcmp(topic, "MECH-LIGHT-CONFIG-TU") == 0) {
int val = atoi(payloadCharAr);
if (val >= 10 && val <= 600) {
tu = val;
Serial.print("Updated tu (send interval): ");
Serial.println(tu);
Serial.println();
}
// Handle shade control offset angle
} else if (strcmp(topic, "MECH-SHADE-THETA_OFFSET") == 0) {
thetaOffset = constrain(atoi(payloadCharAr), 0, 120);
Serial.print("Updated thetaOffset: ");
Serial.println(thetaOffset);
Serial.println();
// Handle gamma adjustment
} else if (strcmp(topic, "MECH-SHADE-GAMMA") == 0) {
gamma_val = atof(payloadCharAr);
gamma_val = constrain(gamma_val, 0, 1);
Serial.print("Updated gamma_val: ");
Serial.println(gamma_val);
Serial.println();
// Handle ideal temperature setting
} else if (strcmp(topic, "MECH-SHADE-T_IDEAL") == 0) {
T_ideal = atof(payloadCharAr);
T_ideal = constrain(T_ideal, 10, 40);
Serial.print("Updated T_ideal: ");
Serial.println(T_ideal);
Serial.println();
}
}
void connectToBroker() {
// Try to connect to MQTT broker
while (!mqttClient.connected()) {
Serial.print("Attempting MQTT connection...");
if (mqttClient.connect("ESP32-MECH-COMBO")) {
// Subscribe to all required control topics
mqttClient.subscribe("MECH-ADM-MAIN-ON-OFF");
mqttClient.subscribe("MECH-ADM-SCH-ON");
mqttClient.subscribe("MECH-LIGHT-CONFIG-TS");
mqttClient.subscribe("MECH-LIGHT-CONFIG-TU");
mqttClient.subscribe("MECH-SHADE-THETA_OFFSET");
mqttClient.subscribe("MECH-SHADE-GAMMA");
mqttClient.subscribe("MECH-SHADE-T_IDEAL");
} else {
Serial.print("Failed To connect to MQTT Broker");
Serial.print(mqttClient.state());
delay(5000); // Wait before retrying
}
}
}
unsigned long getTime() {
timeClient.update(); // Sync time from NTP
return timeClient.getEpochTime(); // Return epoch time
}
void buzzerOn(bool on) {
// Turn buzzer on or off
if (on) tone(BUZZER, 256);
else noTone(BUZZER);
}
void checkSchedule() {
// Check if scheduled buzzer time is reached
if (isScheduledON) {
unsigned long currentTime = getTime();
if (currentTime > scheduledOnTime) {
buzzerOn(true);
isScheduledON = false;
mqttClient.publish("MECH-ADM-MAIN-ON-OFF-ESP", "1");
mqttClient.publish("MECH-ADM-SCH-ESP-ON", "0");
}
}
}
void check_temp()
{
// Read temperature and humidity data from the DHT sensor
TempAndHumidity data= dhtSensor.getTempAndHumidity();
String(data.temperature, 2).toCharArray(tempArr, 6);
// Check if the temperature is too high (>35°C)
if(data.temperature>35){
display.clearDisplay();
print_line("TEMP HIGH",0,40,1);
}
// Check if the temperature is too low (<25°C)
else if(data.temperature<25){
display.clearDisplay();
print_line("TEMP LOW",0,40,1);
}
// Check if the humidity is too high (>40%)
if(data.humidity>40){
display.clearDisplay();
print_line("HUMIDITY HIGH",0,50,1);
}
// Check if the humidity is too low (<20%)
else if(data.humidity<20){
display.clearDisplay();
print_line("HUMIDITY LOW",0,50,1);
}
}
void handleLightSampling() {
unsigned long currentMillis = millis();
// Sample light every ts seconds
if (currentMillis - lastSampleTime >= ts * 1000UL) {
lastSampleTime = currentMillis;
float lightVal = analogRead(LDR);
float normLight = 1.0 - (lightVal / 4095.0); // Normalize
sampleSum += normLight;
sampleCount++;
}
// Send average light every tu seconds
if (currentMillis - lastSendTime >= tu * 1000UL) {
lastSendTime = currentMillis;
if (sampleCount > 0) {
avgLightIntensity = sampleSum / sampleCount;
avgLightIntensity = roundf(avgLightIntensity * 1000.0) / 1000.0; // Round to 3 dp
char msg[10];
dtostrf(avgLightIntensity, 1, 3, msg); // Format to string
mqttClient.publish("MECH-LIGHT-AVG", msg);
handleShadeControl(); // Adjust shade based on light
}
sampleSum = 0;
sampleCount = 0;
}
}
void handleShadeControl() {
float lightIntensity = avgLightIntensity;
float T = atof(tempArr); // Convert temp to float
if (T == 0) return;
// Calculate new shade angle based on formula
float delta = (180 - thetaOffset) * lightIntensity * gamma_val * log((float)ts / tu) * (T / T_ideal);
float angle = thetaOffset + delta;
float servoAngle = constrain(angle, 0, 180); // Constrain to 0–180°
Serial.printf("Calculated Shade Angle: %.2f°\n", angle);
Serial.printf("Applied Servo Angle (constrained 0-180): %.2f°\n", servoAngle);
Serial.println();
servo.write((int)servoAngle); // Set servo to calculated angle
}