/*
Version 0.2
Date : 21 March 2023
Programmer-Engr Usama
[email protected]
*/
/* Comment this out to disable prints and save space */
#define BLYNK_PRINT Serial
/* Fill in information from Blynk Device Info here */
/* Fill in information from Blynk Device Info here */
#define BLYNK_TEMPLATE_ID "TMPL2iKU1S9ee"
#define BLYNK_TEMPLATE_NAME "Shower1"
#define BLYNK_AUTH_TOKEN "JVaw4jC1qXOWsuFrjAG27n3Y2srxLwL6"
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>
#include <TimeLib.h>
#include <WidgetRTC.h>
#include "RTClib.h"
RTC_Millis rtcSoft;
// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "Wokwi-GUEST";
char pass[] = "";
const byte physicalButton = 15; // physical button pin
const byte valveRelay = 2; // Relay 1 pin
BlynkTimer timer;
WidgetRTC rtc;
#define DEBUG 1 // Set Debug level 0-1 to prints result on serial monitor (Set it to 0 when you don't need serial prints)
#if DEBUG == 1
#define debug(x) Serial.print(x)
#define debugln(x) Serial.println(x)
#else
#define debug(x)
#define debugln(x)
#endif
#define BLYNK_SHOWER_BUTTON V0 // Cold shower button virtual pin
#define BLYNK_TOTAL_DURATION V3 // Cold shower button virtual pin
#define BLYNK_DELAY_SEC V4 // Cold shower button virtual pin
#define BLYNK_DELAY_MIN V7 // Cold shower button virtual pin
#define BLYNK_TIME_START_HOUR V5 // Start Time in hour for session
#define BLYNK_TIME_START_MINUTE V9 // Start Time in hour for session
#define BLYNK_TIME V6 // Cold shower button virtual pin
#define BLYNK_REMAIN_TIME V1 // remain time label virtual pin
#define BLYNK_SESSION_TIME V2 // Session Time
#define BLYNK_RESET_TIME V8 // Button to Reset time
#define BLYNK_RESET_TIME_TEXT V10 // Button to Reset time
long adjustOnlineTime = 0; // Set this with time difference in online time update (Use +/- seconds to adjust the time dispalyed online)
int enteredDuration = 0; // Variable to hold user entered duration
int delaySec = 0; // variable to hold user Entered delay (sec)
long delayMin = 0; // variable to hold user Entered delay (Min)
int startTimeHour = 0; // variable to hold user Entered time off-set
int startTimeMinute = 0; // variable to hold user Entered time off-set
int sessionTimeMinute = 0; // Session time in minute
int remainingTime = 0; // variable to store remaining time
unsigned long showerStartTime = 0; // variable to hold shower start time
unsigned long showerStopTime = 0; // variable to hold shower stop time
unsigned long totalOnTime = 0;
unsigned long allowedDuration = 0; // variable to hold total allowed duration for current shower
int debounceTime = 1000; // debounce time for physical button to avoid taking wrong presses
int secondToMillis = 1000; // factor to convert seconds to millis (Used to convert user enterd duration into millis)
//int minutesToMillis = secondToMillis * 60; // factor to convert minutes to millis
int minutesToMillis = secondToMillis * 60; // factor to convert minutes to millis
bool showerEnabled = false; // flag to be set when shower button was turned on
bool takingShower = false; // variable to be set when user selected to take shower
bool lastShowerUsed = false; // flag to be set when shower was in use last time (will be false for first time use)
bool relayLogic = LOW; // relay working logic change this with HIGH if relay with HIGH logic (HIGH/LOW)
bool newStart = true; // flag to check if it's a new start
bool timeOver = false; // flag to keep check if timeOver or not
bool lastButtonState = LOW; // Variable to hold last button state
bool nextCycleReset = false; // Flag to check if next cycle start then reset
bool resetForNewShower = false;
bool showerOnButtonStatus = false;
bool longWaitOccur = false; // In case of shower was stopped for long set the flag to add delay in time
bool longWaitTimeAdded = false; // Flag will be set when long time wait has occured and delay time was added
bool recalculateNextSessionTime = true; // Set the flag to recalculate next session time
void turnOffValve(void);
void turnOnValve(void);
void updateTimeOnServer(void);
void showDate(const char* txt, const DateTime& dt) {
debug(txt);
debug(' ');
debug(dt.year());
debug('/');
debug(dt.month());
debug('/');
debug(dt.day());
debug(' ');
debug(dt.hour());
debug(':');
debug(dt.minute());
debug(':');
debug(dt.second());
debug(" = ");
debug(dt.unixtime());
debug("s / ");
debug(dt.unixtime() / 86400L);
debug("d since 1970");
debugln();
}
DateTime nextSessionTime(2009, 12, 27, 0, 0, 0);
void clockDisplay() {
// You can call hour(), minute(), ... at any time
// Please see Time library examples for details
// setTime(adjustOnlineTime);
String currentTime = String(hour()) + ":" + minute() + ":" + second();
String currentDate = String(day()) + " " + month() + " " + year();
rtcSoft.adjust(DateTime(year(), month(), day(), hour(), minute(), second()));
DateTime now = rtcSoft.now();
// char buf3[] = "Today is DDD, MMM DD YYYY";
// debugln(now.toString(buf3));
debug("Next Reset time:");
debug(nextSessionTime.minute());
debug(" ");
debugln(now.minute());
if (nextSessionTime.minute() == now.minute() && nextSessionTime.hour() == now.hour() && resetForNewShower == false) {
resetForNewShower = true;
if (takingShower) { // If shower was on stop it and note current shower stop time
showerStopTime = millis(); // note current time when shower was started
lastShowerUsed = true; // set the flag to indicate shower was used
}
allowedDuration = enteredDuration;
debugln("Set New Shower time");
resetShower(); // reset other variables
if (longWaitOccur) {
debugln("Long wait occured");
allowedDuration += delaySec; // set new working duration
longWaitTimeAdded = true;
}
turnOffValve(); // turn off shower
// Blynk.virtualWrite(BLYNK_REMAIN_TIME, 0); // Reset remain time
Blynk.virtualWrite(BLYNK_REMAIN_TIME, allowedDuration / 1000); // Update allowed duration on server
Blynk.virtualWrite(BLYNK_REMAIN_TIME, allowedDuration / 1000); // Update allowed duration on server
nextSessionTime = nextSessionTime + TimeSpan(0, 0, sessionTimeMinute, 0); // after adding session time
showDate("Next Session", nextSessionTime);
} else if (nextSessionTime.minute() != now.minute() && resetForNewShower) {
resetForNewShower = false;
}
// Send time to the App
Blynk.virtualWrite(BLYNK_TIME, currentTime);
debugln(currentTime);
// debugln(currentDate);
}
void resetShower() { // For next shower reset all variables
allowedDuration = enteredDuration;
showerStartTime = 0;
// showerStopTime = 0;
timeOver = false; // reset timeover flag
takingShower = false; // reset flag
lastButtonState = digitalRead(physicalButton); // Read button state
}
BLYNK_CONNECTED() {
// Synchronize time on connection
rtc.begin();
Blynk.syncAll();
calculateNextSessionFunction();
}
void setup() {
// Debug console
Serial.begin(9600);
// pinMode(physicalButton, INPUT_PULLUP); // Set physical button pin as input with pullup
pinMode(physicalButton, INPUT); // Set physical button pin as input
pinMode(valveRelay, OUTPUT);
turnOffValve(); // initially turn off valve
Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass, "blynk.cloud", 8080);
setSyncInterval(10 * 60); // sync interval in seconds (10 minutes)
timer.setInterval(1000L, clockDisplay); // updat clock on Blynk after every 2.5 seconds
timer.setInterval(900L, checkRemainTime); // check remaining time after every 1 second
timer.setInterval(1000L, checkLongWait); // check Long wait
timer.setInterval(100L, checkButtonStatus); // check Button status
lastButtonState = digitalRead(physicalButton); // Read button state
allowedDuration = enteredDuration + delaySec; // Add delay (second into allowed duration)
customDelay(5000); // wait for few seconds to synchronize with server
updateTimeOnServer(); // update time on server
calculateNextSessionFunction();
}
void checkRemainTime() {
if (timeOver == false && takingShower) { // check if time was not over and shower was on
unsigned long totalOnTime = millis() - showerStartTime; // calculate how long (in seconds) shower was on
debug("Total On Time:");
debug(totalOnTime);
remainingTime = allowedDuration - totalOnTime; // subtract on time from remaining time
debug(" | Remaining Time:");
debugln(remainingTime);
if (remainingTime <= 0) {
timeOver = true; // set time over flag
turnOffValve(); // turn off valve
showerStopTime = millis(); // note current time when shower was started
lastShowerUsed = true; // set the flag to indicate shower was used
}
Blynk.virtualWrite(V1, remainingTime / 1000);
}
}
void loop() {
Blynk.run();
timer.run();
}
void checkButtonStatus() {
bool currentButtonState = digitalRead(physicalButton); // Note current button state
if (showerEnabled && currentButtonState != lastButtonState && timeOver == false) { // check conditions only if shower button is ON
lastButtonState = currentButtonState;
if (takingShower == false) { // Check if perviously shower was off
turnOnValve();
takingShower = true; // set the flag to show shower is in use
lastShowerUsed = false; // reset last shower use flag
showerStartTime = millis(); // note current time when shower was started
} else if (takingShower == true) { // Check if previously shower was on
debugln("Stop Shower on user button press");
turnOffValve(); // turn off valve
takingShower = false; // set the flag
showerStopTime = millis(); // note current time when shower was started
allowedDuration -= (showerStopTime - showerStartTime); // subtract on time from remaining time
Serial.print("Allowed Duration:");
Serial.println(allowedDuration);
lastShowerUsed = true; // set the flag to indicate shower was used
}
customDelay(debounceTime); // wait for button debounce time
longWaitTimeAdded = false; // Reset long wait time flags
longWaitOccur = false; // Reset long wait time flags
}
}
void calculateNextSessionFunction() {
DateTime now = rtcSoft.now();
DateTime dt6(now.year(), now.month(), now.day(), startTimeHour, startTimeMinute, 0);
char buf2[] = "YYMMDD-hh:mm:ss";
Serial.println(dt6.toString(buf2));
dt6 = dt6 - TimeSpan(1, 0, 0, 0); // after adding session time
nextSessionTime = dt6 + TimeSpan(0, 0, sessionTimeMinute, 0); // after adding session time
while (nextSessionTime.unixtime() < now.unixtime()) {
nextSessionTime = nextSessionTime + TimeSpan(0, 0, sessionTimeMinute, 0); // after adding session time
}
showDate("Next Session", nextSessionTime);
}
void checkLongWait() {
if (lastShowerUsed && ((millis() - showerStopTime) > delayMin) && longWaitOccur == false) {
longWaitOccur = true;
if (longWaitTimeAdded == false) {
// allowedDuration += delaySec;
updateTimeOnServer(); // update time on server
longWaitTimeAdded = true;
}
debugln("shower was stopped for more delay min time");
}
}
void customDelay(unsigned long waitTime) {
// this is a custom function that will keep blynk online when we have to add delay
unsigned long cTime = millis(); // Note current time
while ((millis() - cTime) < waitTime) { // wait until wait time ends
Blynk.run();
timer.run();
}
}
void updateTimeOnServer() { // Update time on server
Serial.print("Allowed Duration in server:");
Serial.println(allowedDuration);
if (longWaitOccur && !longWaitTimeAdded)
allowedDuration += delaySec;
else if (newStart)
allowedDuration = enteredDuration + delaySec; // Add delay (second into allowed duration)
Serial.print("New Allowed Duration in server:");
Serial.println(allowedDuration);
debug("Update time on server: ");
debugln(allowedDuration);
Blynk.virtualWrite(BLYNK_REMAIN_TIME, allowedDuration / 1000); // Update allowed duration on server
}
BLYNK_WRITE(BLYNK_SHOWER_BUTTON) { // this command is listening when something is written to V1
int pinValue = param.asInt(); // assigning incoming value from pin V1 to a variable
if (pinValue == 1) { // Enable shower on button press
debugln("Turn on shower");
lastButtonState = digitalRead(physicalButton); // Read button state
showerEnabled = true; // turn on shower functionality
// showerOnButtonStatus = true;
} else if (pinValue == 0) { // Disable shower
debugln("Turn off shower");
lastButtonState = digitalRead(physicalButton); // Read button state
// showerOnButtonStatus = false;
showerEnabled = false; // turn off shower functionality
turnOffValve(); // turn off valve
resetShower(); // Reset shower
}
}
BLYNK_WRITE(BLYNK_RESET_TIME) { // this command is listening when user wants to add more time in current run
int pinValue = param.asInt(); // assigning incoming value from pin V1 to a variable
if (pinValue == 1) {
timeOver = false; // If time over has occured reset it
allowedDuration = enteredDuration;
if (longWaitOccur) {
allowedDuration += delaySec;
}
showerStartTime = millis();
Blynk.virtualWrite(BLYNK_REMAIN_TIME, allowedDuration / 1000); // Update allowed duration on server
turnOffValve(); // turn off valve
takingShower = false; // set the flag
}
debug("New Duration:");
debugln(allowedDuration);
Blynk.virtualWrite(BLYNK_RESET_TIME, LOW); // Reset button status
}
BLYNK_WRITE(BLYNK_TOTAL_DURATION) { // this command is listening when something is written to V1
int Value = param.asInt(); // assigning incoming value from pin V1 to a variable
enteredDuration = Value * secondToMillis; // save the value
debug("Set Duration Value:");
debugln(enteredDuration);
updateTimeOnServer();
}
BLYNK_WRITE(BLYNK_DELAY_SEC) { // this command is listening when something is written to V1
int Value = param.asInt(); // assigning incoming value from pin V1 to a variable
delaySec = Value * secondToMillis; // save the value
debug("Delay Sec Value:");
debugln(delaySec);
updateTimeOnServer();
}
BLYNK_WRITE(BLYNK_DELAY_MIN) { // this command is listening when something is written to V1
int Value = param.asInt(); // assigning incoming value from pin V1 to a variable
delayMin = Value * minutesToMillis; // save the value and convert it into millis
debug("Delay MIN Value:");
debugln(delayMin);
}
BLYNK_WRITE(BLYNK_TIME_START_MINUTE) { // this command is listening when something is written to V1
int Value = param.asInt(); // assigning incoming value from pin V1 to a variable
startTimeMinute = Value; // save the value
debug("Start Time Minute:");
debugln(startTimeMinute);
calculateNextSessionFunction();
}
BLYNK_WRITE(BLYNK_TIME_START_HOUR) { // this command is listening when something is written to V1
int Value = param.asInt(); // assigning incoming value from pin V1 to a variable
startTimeHour = Value; // save the value
debug("Start Time Hour:");
debugln(startTimeHour);
calculateNextSessionFunction();
}
BLYNK_WRITE(BLYNK_SESSION_TIME) { // this command is listening when something is written to V1
int Value = param.asInt(); // assigning incoming value from pin V1 to a variable
sessionTimeMinute = Value; // save the value
debug("Session Time:");
debugln(sessionTimeMinute);
calculateNextSessionFunction();
}
void turnOffValve() { // function to turn off valve
digitalWrite(valveRelay, relayLogic); // Turn on relay
}
void turnOnValve() { // Functiont to turn on valve
digitalWrite(valveRelay, !relayLogic); // turn off valve
}