// Blynk credentials
#define BLYNK_TEMPLATE_ID "TMPL6TIKJj-JU"
#define BLYNK_TEMPLATE_NAME "Shade Guardian Enhanced Curtain Automation System"
#define BLYNK_AUTH_TOKEN "UYKVaC4OmJ2wi2WyZWGxDhRpI-014vv_"
#include <LiquidCrystal_I2C.h>
#include <Stepper.h>
#include "RTClib.h"
#include <WiFi.h>
#include <BlynkSimpleEsp32.h>
#include <WiFiClient.h>
char auth[] = BLYNK_AUTH_TOKEN;
char ssid[] = "Wokwi-GUEST";
char pass[] = "";
#define LIGHT_SENSOR_PIN 34 // ESP32 pin connected to LDR
#define LED_PIN 23 // ESP32 pin connected to LED
#define BUZZER_PIN 19 // ESP32 pin connected to BUZZER
#define ENCODER_CLK 13 // ESP32 pin connected to encoder channel A
#define ENCODER_DT 12 // ESP32 pin connected to encoder channel B
// LDR Characteristics
const float GAMMA = 0.7;
const float RL10 = 33;
const int stepsPerRevolution1 = 50;
const int stepsPerRevolution2 = 100;
const int maxSteps1 = 200; // Define the maximum steps
const int maxSteps2 = 400;
LiquidCrystal_I2C lcd(0x27, 16, 2); // I2C address 0x27, 16 column and 2 rows
RTC_DS1307 rtc;
class CustomStepper : public Stepper {
public:
CustomStepper(int steps, int pin1, int pin2, int pin3, int pin4) : Stepper(steps, pin1, pin2, pin3, pin4) {}
void stop() {
step(0);
}
};
CustomStepper myStepper1(stepsPerRevolution1, 15, 2, 0, 4);
CustomStepper myStepper2(stepsPerRevolution2, 15, 2, 0, 4);
int stepsTaken1 = 0;
int stepsTaken2 = 0;
unsigned long buzzerStartTime = 0;
int snoozeCount = 0;
volatile long encoderPosition = 0;
volatile bool lastEncoderCLKState = false;
volatile bool encoderMoved = false;
String lastCondition = ""; // Keep track of the last condition
BlynkTimer timer;
void IRAM_ATTR handleEncoderCLK() {
bool currentCLKState = digitalRead(ENCODER_CLK);
bool currentDTState = digitalRead(ENCODER_DT);
if (currentCLKState != lastEncoderCLKState) {
if (currentDTState != currentCLKState) {
encoderPosition++;
} else {
encoderPosition--;
}
encoderMoved = true;
}
lastEncoderCLKState = currentCLKState;
}
void setup() {
pinMode(LIGHT_SENSOR_PIN, INPUT);
pinMode(LED_PIN, OUTPUT); // set ESP32 pin to output mode
pinMode(BUZZER_PIN, OUTPUT); // set ESP32 pin to output mode
pinMode(ENCODER_CLK, INPUT);
pinMode(ENCODER_DT, INPUT);
lcd.init(); // initialize the lcd
lcd.backlight();
myStepper1.setSpeed(100);
myStepper2.setSpeed(100);
Serial.begin(115200);
if (!rtc.begin()) {
Serial.println("Couldn't find RTC");
Serial.flush();
abort();
}
if (!rtc.isrunning()) {
Serial.println("RTC is NOT running, let's set the time!");
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), handleEncoderCLK, CHANGE);
Blynk.begin(auth, ssid, pass);
}
void sendNotification(String condition) {
if (lastCondition != condition) {
// Log event for sending notifications in Blynk 2.0
Blynk.logEvent("current_condition", "Current Condition: " + condition);
lastCondition = condition;
}
}
void loop() {
Blynk.run(); // Run Blynk
timer.run();
int analogValue = analogRead(LIGHT_SENSOR_PIN); // read the value on analog pin
float voltage = analogValue / 4096.0 * 3.3;
float resistance = 2000 * voltage / (1 - voltage / 3.3);
float lux = pow(RL10 * 1e3 * pow(10, GAMMA) / resistance, (1 / GAMMA));
Serial.println(lux);
noTone(BUZZER_PIN);
DateTime time = rtc.now();
lcd.setCursor(0, 0);
lcd.print(String("Time:") + time.timestamp(DateTime::TIMESTAMP_TIME));
int currentHour = time.hour();
// Trigger notifications based on conditions
if (lux > 1000 && currentHour >= 11 && currentHour <= 15) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(String("Time:") + time.timestamp(DateTime::TIMESTAMP_TIME));
lcd.setCursor(0, 1);
lcd.print("BRIGHT!");
digitalWrite(LED_PIN, LOW); // turn off LED
Serial.println("LED IS OFF");
sendNotification("Bright");
// CURTAIN CLOSE HALF
if (stepsTaken1 < maxSteps1) {
myStepper1.step(stepsPerRevolution1);
stepsTaken1 += stepsPerRevolution1;
if (stepsTaken1 >= maxSteps1) {
myStepper1.stop();
stepsTaken1 = maxSteps1;
}
}
snoozeCount = 0; // Reset snooze count when it's too bright
}
else if (lux <= 400 && currentHour >= 11 && currentHour <= 15) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(String("Time:") + time.timestamp(DateTime::TIMESTAMP_TIME));
lcd.setCursor(0, 1);
lcd.print("CLOUDY!");
digitalWrite(LED_PIN, HIGH); // turn on LED
Serial.println("LED IS ON");
sendNotification("Cloudy");
// CURTAIN CLOSE FULL
if (stepsTaken2 < maxSteps2) {
myStepper2.step(stepsPerRevolution2);
stepsTaken2 += stepsPerRevolution2;
if (stepsTaken2 >= maxSteps2) {
myStepper2.stop();
stepsTaken2 = maxSteps2;
}
}
snoozeCount = 0; // Reset snooze count when it's raining
}
else if (lux >= 300 && lux <= 1000 && currentHour >= 6 && currentHour <= 8) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(String("Time:") + time.timestamp(DateTime::TIMESTAMP_TIME));
lcd.setCursor(0, 1);
lcd.print("MORNING!");
digitalWrite(LED_PIN, LOW); // turn off LED
Serial.println("LED IS OFF");
sendNotification("Morning");
// Start the buzzer for 10 seconds
if (snoozeCount < 3) {
if (millis() - buzzerStartTime < 10000) {
tone(BUZZER_PIN, 1000);
} else {
noTone(BUZZER_PIN);
if (millis() - buzzerStartTime > 15000) {
buzzerStartTime = millis();
snoozeCount++; // Increment snooze count after 15 seconds
}
}
} else {
noTone(BUZZER_PIN);
if (millis() - buzzerStartTime > 30000) {
buzzerStartTime = millis();
snoozeCount = 0; // Reset snooze count
}
}
// CURTAIN OPEN FULL
if (stepsTaken2 > -maxSteps2) {
myStepper2.step(-stepsPerRevolution2);
stepsTaken2 -= stepsPerRevolution2;
if (stepsTaken2 <= -maxSteps2) {
myStepper2.stop();
stepsTaken2 = -maxSteps2;
}
}
}
else if (lux <= 300 || (currentHour >= 19 || currentHour <= 2)) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(String("Time:") + time.timestamp(DateTime::TIMESTAMP_TIME));
lcd.setCursor(0, 1);
lcd.print("DARK!");
digitalWrite(LED_PIN, HIGH); // turn on LED
Serial.println("LED IS ON");
sendNotification("Dark");
// CURTAIN CLOSE FULL
if (stepsTaken2 < maxSteps2) {
myStepper2.step(stepsPerRevolution2);
stepsTaken2 += stepsPerRevolution2;
if (stepsTaken2 >= maxSteps2) {
myStepper2.stop();
stepsTaken2 = maxSteps2;
}
}
snoozeCount = 0; // Reset snooze count when it's dark
}
else {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(String("Time:") + time.timestamp(DateTime::TIMESTAMP_TIME));
snoozeCount = 0; // Reset snooze count
}
// Use encoder position to control the stepper motor
if (encoderMoved) {
if (encoderPosition > 0 && stepsTaken1 < maxSteps1) {
myStepper1.step(stepsPerRevolution1);
encoderPosition--;
stepsTaken1 += stepsPerRevolution1;
if (stepsTaken1 >= maxSteps1) {
myStepper1.stop();
stepsTaken1 = maxSteps1;
}
} else if (encoderPosition < 0 && stepsTaken1 > -maxSteps1) {
myStepper1.step(-stepsPerRevolution1);
encoderPosition++;
stepsTaken1 -= stepsPerRevolution1;
if (stepsTaken1 <= -maxSteps1) {
myStepper1.stop();
stepsTaken1 = -maxSteps1;
}
}
encoderMoved = false; // Reset encoder moved flag
}
delay(1000);
}