BLYNK
HOME       📲 GETTING STARTED       📗 DOCS       ❓HELP CENTER       👉 SKETCH BUILDER

2 way lighting circuit control with blynk and feedback updates


#182

I take that back ,it hasn’t worked!!! initially when I reload the setproperty data it sets correctly then after a few mins (? next update) they revert back to the old data for the setproperty from 15/11 ???


#183

any 2nd thoughts on this @Dmitriy?

Cheers

Kev


#184

guys, I tried the code from @newdos. Everything is the same except I change the RELAY1_PIN from pin 2 to pin 8 and AC_Sens1_pin from pin 3 to pin 9 becuase I’m using WiFi Shield. Pin 2 and 3 are occupied.

After upload the code, i introduce 5V to pin 9. Nothing change. ? Is there something I did wrong? Theoretically, it should detect the voltage, and I can see the relay being energized. But nothing happens, even in blynk app.


#185

I would need to see your code please and then I can help


#186

oh. Sorry.

Here’s the code:

#define BLYNK_PRINT Serial    // Comment this out to disable prints and save space
#include <ESP8266_Lib.h>
#include <BlynkSimpleShieldEsp8266.h>
#include<SimpleTimer.h>
#include <TimeLib.h>            // Used by WidgetRTC.h
SimpleTimer timer;

// Lighting control pins and AC feedback
int RELAY1_PIN = 8;       // Relay 1 pin
int AC_Sens1_pin = 7;     // Relay 1 ac sens pin
int feedback1;            // AC feedback variable
int feedback2;            // AC feedback variable

char auth[] = "...";
char ssid[] = "...";
char pass[] = "...";

// Software Serial on Uno, Nano...
#include <SoftwareSerial.h>
SoftwareSerial EspSerial(2, 3); // RX, TX

// Your ESP8266 baud rate:
#define ESP8266_BAUD 9600

ESP8266 wifi(&EspSerial);

void setup()
{
  Serial.begin(9600);
  delay(10);
  // Set ESP8266 baud rate
  EspSerial.begin(ESP8266_BAUD);
  delay(50);
  Blynk.begin(auth, wifi, ssid, pass);
  pinMode(RELAY1_PIN, OUTPUT);
  pinMode(AC_Sens1_pin, INPUT);

  // Default ALL relays to OFF before reinstating stored states
  digitalWrite(RELAY1_PIN, 0);

  //Lets sync up all the store states for the V pins and load the appropriate variables listed below
  Blynk.syncVirtual(V3);             // Light 1 Relay state

  timer.setInterval(300, AC_detect);      // Check AC_detect for AC presence every 50 milliseconds
}

void loop()
{
  Blynk.run();
  timer.run();
}

void AC_detect()
{
  feedback1 = digitalRead(AC_Sens1_pin); // Read the digital pin for an AC input

  // update SWITCH widget state only if AC input has changed since last check
  // this prevents widget switch bounce on the app device screen
  if (feedback1 != feedback2) Blynk.virtualWrite(V2, !feedback1);
  feedback2 = feedback1;
}

// Sync up all of the virtual pin data
BLYNK_WRITE(V2)  // Runs every-time switch widget is toggled - For Light 1
{
  digitalWrite(RELAY1_PIN, !digitalRead(RELAY1_PIN)); // Toggle relay1
  Blynk.virtualWrite(V3, digitalRead(RELAY1_PIN));    // Store Light 1 relay state in V3
}

BLYNK_WRITE(V3)
{
  int pinData = param.asInt();
  digitalWrite(RELAY1_PIN, pinData); // and reinstate relay state
}


#187

Looking at your code I think the AC_DETECT void is wrong.
it should look like this - try this and let me know how it goes

feedback2= digitalRead(AC_Sens1_pin);

if (feedback1 != feedback2) {
Blynk.virtualWrite(V2, feedback2);
feedback1 = feedback2;
}

Cheers

Kev


#188

https://www.amazon.com/gp/product/B073XL983V/ref=s9u_simh_gw_i2?ie=UTF8&fpl=fresh&pd_rd_i=B073XL983V&pd_rd_r=4056a9bc-fa23-11e7-bf0b-2fcaf32ef7bc&pd_rd_w=U4Ifk&pd_rd_wg=4ez6f&pf_rd_m=ATVPDKIKX0DER&pf_rd_s=&pf_rd_r=DC0CAJ62JB50XGWSXJP9&pf_rd_t=36701&pf_rd_p=0d036f2a-db56-478a-b511-f59f2469d474&pf_rd_i=desktop

what about using this device to sence current and return this current to arduino board in form of DC voltage which indicate in mobile Whether the load works or not ???


#189

Yes you could use these if you want to go down the current sensing route


#190

I do not understand if you can explain, thx for reply


#191

These are current sensing modules. I did it by sensing voltage not current.

hope that helps

Cheers

kev


#192

OK Guys, been a while since I started this and now the project is finished so I am going to share a write up with you all.

The project set out to control two way lighting for my shed build. One switch for the two circuit is virtual via the Blynk app screens, and the other switch is a physical wall switch. The key to the early design was getting the physical switch to feedback to the Blynk app so the widget updated it’s status. This was achieved. Also all of the light circuits are controlled via Alexa using IFFFT as well - very handy!!

The app has further been developed and also controls the heating in the shed and a sauna in the corner of the it, so I wll split the write up into two halves.

Firstly about the shed. It is my pinball playroom(which has sauna in corner) and a pinball workshop, so essentially split into two rooms.

Here is a pic of it complete - I designed and built the whole thing from scratch myself.

So to the lighting control then. The pinroom has 5 circuits in it arranged in 3 rows of individually controlled downlighters in each row, an outside set of 6 downlighters(on one circuit) in the soffit, and a centre starscape display which is yet to be completed.

The workshop has 4 circuits, again 3 rows of individually controlled downlighters and an external LED floodlight behind the shed for security.

Here is the Blynk control panel for these - red widgets for pinroom - blue for workshop…

![IMG_4305|281x500]
(upload://9flRy6CB6B5frUuSzbDRrm9Lxsr.png)

You will note in the 2nd screen shot there is another button that appears when you slide the screen up - Emergency button - this switches all 9 lighting circuits on (or off) at once. Useful if I hear noises in the garden.

The blynk app controls an Arduino Mega linked to 2 banks of 8ch relays shown below

You can see these laid out in the pic pre wiring done. The black SSR relays in the middle were for the heating but proved useles so were replaced with 30A mechanical relays - more on this later.

here are a few pics of it now fully loaded. The left set of relays is for the pinroom 5 lighting circuits and 1 for sauna on/off. Right set of relays for workshop 4 lighting circuits. You will note 4 relays are free in the workshop side for future devlopments and 2 in the pinroom side.

The lighting circuits to the physical switches are low voltage connected directly to an Arduino input pin. The physical switches either pulls the relevant pin up to +5v or down to Gnd. These input cables come in the top of the box in the pics via the connector strips.

The power goes out through the left side of the box for pinroom and through right for the workshop(via each relay output). you can also see on each side of the box a row of neutral and Earth connectors for each circuit, and at the bottom a +5v and Gnd busbars(connectors) for the switch inputs.

Reason there are two sets of neutral and earths, is because each set of relays has its own MCB in the distribution board, and it is easy and neater for the wiring.

So the arduino software for this. Its quite simple but there is a lot of it as mutilplied up by the number of circuits. Each arduino input pin(physical switch) is monitored for a change of state and simply switches on the appropriate relay and updates the widget on the app. Similarily a press of the blynk app widget button does the same, ie changes the state of the switch to the opposite it was.

Yhe software is reasonable clever protecting the states of the widgets in case of power loss. The software uses a lot of virtual pins to store the states of them and in the vent the power is lost it reinstate the state of each output/switch widget whenh the power is restored.

You will need to look through the software listing to understand all of this.

Ok I think for the first half that is it. I will do another for the heating control separately.

Please feel free to ask any questions.

Here are few pics of the rooms within the shed for clarity. Pinroom(note wall switches in some shots) Sauna and workshop and finshed control box above dis board

Cheers

Kev


#193

OK So now for part two the heating control for the two room sin the shed via the Blynk app.

Each room has a 1.5kw oil filled radiator in it(see pic in part 1), a DHT22 temp and hunmidty sensor and a PIR occupancy detector. THere is also an externaL DHT22 mounted externally to monitor outside temp and humidity as well. Each of the radiators is controlled by a 30A mechanical relay which can be seen in the centre of the control box - the board with two of them on.

The blynk app displays temp and humidty in each room and outside. It also shows todays runtime for the heating, and yesterdays runtime, and the current status of the heating ie on or off.

This is all shown in the first screen below.

The clever bit of this is the occupancy control. On the next screen are the thermostat settings for each room. There are two sliders per heater- one to set the desired temp and one for the away temp. The software works by sensing if there is anyone present in the room via the PIR occupancy sensor(shown below red circle)

Here is the blynk screen for this…

Blynk will set the temp back to the away temp when no movement is detected in each of the rooms for one hour, and inversley it remembers the required temp for each room, so as someone enters the room it will automatically turn the heating back up to the required temp, set originally by the temp slider.
ie room set to 20c away temp set to 15c, after 1 hour of inactivity pinshack temp slider will drop back to what the away temp slider says, ie 15c, and if someone re-enters the room the pinshack temp slider will jump back up to 20c heating the room.

The principal behind this is to save energy when you leave the room, and to turn heating off or down if you forget to do it yourself. It also gives a constant background temp(away temp) to keep the rooms stable. Of course if you don’t want to wait until you enter the room to heat it up you can just move the temp slider up to what you want to preheat the room, but if you dont enter within the hour it will go back to the away temp!

This works a treat and has proved economical

Also on the temp screen I record and display the Max and Min temps for each DHT22 sensor (ignore the slider for my lounge light!! thats another project)

Finally I have another screen that displays stats and has a hidden slide up again to be abel to reset various values.

Here is the screen…

So on this screen I have a grpah that displays on it, Pinshack temp and Hunmidity, Outside temp and Workshop temp.
The widgets underneath show electricity consumption per room and a total. So we have KwH of elec used per room, Todays elec cost per room, Yesterdays cost per room, Each rooms total cost since last reset and finally the total cost to heat the whole shed.

All of these stats can be indivdually reset by sliding up this screen to reveal the buttons shown below…

On here I also have the ability to remotely switch the sauna on and display it’s on time(runtime).
I have widgets to reset pinshack runtime stats, workshop runtime stats, maxmin temps reset and finally a button to reset all the elec stats back to zero. I also hav hidden the amil and timer widgets here as well.

Thats it !!!

For those interested here is a pinout spreadsheet for all the inputs and outputs to Arduino Mega.

And here is the software - be warned it’s huge, but well commented!!!

codes too big not sure how I add it??? shout if anyone wants it

Cheers

kev


#194

You can try to break it into 1/2 or 1/3 and paste into multiple posts (I have done that before)… or just use dropbox or equivalent and post the link.

PS This is incredible… lots of hard work, and looks absolutely great for it!


#195

Cheers @Gunner will stick the code on in two sections.

Cheers

kev


#196

Here is the code for those interested split into two parts…

PS for get to mention there is a section that uses email alerts for various functions.
one part sends daily elec usgae emails and the other part sends email alerts if the temp of a room is above or below what it should be by xx degrees - see code for this

/*
  2 Ch Heating SSR on digital pins 36 and 37
  8 Ch Pinshack relay board on digital pins 40, 41,42, 43, 44, 45, 46, and 47
  8 Ch Workshop relay board on digital pins 22, 23, 24, 25, 26, 27, 28 and 29
  8 Ch Pinshack input on analog 8 to 15 inc which is digital 62, 63, 64, 65, 66, 67, 68 and 69
  8 Ch Workshop  input on digital 5, 6, 7, 8, 9, 11, 12 and 13
  Pinshack DHT22 D67 PIR  D68
  Workshop DHT22 D9  PIR  D11
  Outside  DHT22 D12
  Sauna on Ch 8 of PINSHACK relay board ie V47

  06/12/2017 V1.0.0  - Release
  06/12/2017 V1.0.1  - Reduce hysteresis value to 0.2
  06/12/2017 v1.0.2  - Add Email alarms for temp over/under range
  07/12/2017 v1.0.3  - Don't send email alerts if setpoint turned off ie 0c
  08/12/2017 v1.0.4  - Fix Totalcost bug
  11/12/2017 v1.0.5  - Broaden email alert. Set TempAlert to +/- 2.0c
  12/12/2017 v1.0.6  - Add values to Email alerts. Change hysteresis val back to 0.3
  13/12/2017 v1.0.7  - Add monthly total emails for elec
  20/12/2017 v1.0.8  - reset Pinshackcost_prev = 0 and WorkshopCost_prev = 0 on V3 toggle  ie elec cost reset widget
  04/01/2018 v1.0.9  - Only read DHT22 every 2 seconds and error check them
  09/01/2018 v1.0.10 - Change heating switch on to 5 sec checks(prevents bounce from poor DHT22 reads)
                       and removed cyclemin vrbls
  09/01/2018 v1.0.11 - Change hysteresis val to 0.5 as 0.3 too tight
  11/01/2018 v1.0.12 - Reduce timed calls to DHT and heating control
  13/02/2018 v1.0.13 - Add TimerFlag and 7 day programmer - change away check to 1hr
  23/02/2018 v1.0.14 - Add V127 to switch ALL lights ON/OFF
  14/03/2018 v1.0.15 - Bug fix midnight switch overs for stats
  21/03/2018 v1.0.16 - Bug fix to TOTALCOST routine - now calculating correctly
  25/05/2018 v1.0.17 - Mod to only email alerts if heating on and over temp or heating off and under temp
  09/11/2018 v1.0.18 - Fix reset Away_flag(s) in check_away routine

*/

#define BLYNK_PRINT Serial      // enable monitor
#include <BlynkSimpleEthernet.h>
#include <TimeLib.h>            // Used by WidgetRTC.h
#include <WidgetRTC.h>          // Blynk's RTC

// Temp vrbls and setup etc
#include "DHT.h"
#define DHTTYPE DHT22
#define PINSHACK_PIN 67 // Pinshack DHT signal in pin
#define WORKSHOP_PIN 9  // Workshop DHT signal in pin
#define OUTSIDE_PIN 12  // External DHT signal in pin
DHT dht1(PINSHACK_PIN, DHTTYPE);
DHT dht2(WORKSHOP_PIN, DHTTYPE);
DHT dht3(OUTSIDE_PIN, DHTTYPE);
float t1; //Temp variable
float h1; //Humidity variable
float t2; //Temp variable
float h2; //Humidity variable
float t3; //Temp variable
float h3; //Humidity variable
float MaxOut  = 0.0;
float MaxPin  = 0.0;
float MaxWork = 0.0;   //  Max Min temp vrbls
float MinOut  = 100.0;
float MinPin  = 100.0;
float MinWork = 100.0;

BlynkTimer timer;
WidgetRTC rtc;

// Heating control pins and variables
int Heat1_pin = 36;                     // Pin for Heating PINSHACK rad Relay.
int PIR1_pin = 68;                      // Input pin for PINSHACK PIR occupancy sensor
int Heat2_pin = 37;                     // Pin for Heating WORKSHOP rad Relay.
int PIR2_pin = 11;                      // Input pin for WORKSHOP PIR occupancy sensor
int Heat1_Status;                       // Status of heating relay 1 at reset/startup etc also stored on V30
int SetPoint1;                          // Temp setting for PINSHACK monitored by DHT22
int Away_Temp1;                         // Temp to set heating to if on but no occupance ie Away
int Heat2_Status;                       // Status of relay 3 at reset/startup etc also stored on V32
int SetPoint2;                          // Temp setting for WORKSHOP monitored by DHT22
int Away_Temp2;                         // Temp to set heating to if on but no occupance ie Away
float Hysteresis1_Val = 0.5;            // Allows for 0.5c below the setpoints to stop 'twitching' around the setpoint
int Activity1 = 0;                      // PINSHACK PIR occupancy counter
int Activity2 = 0;                      // WORKSHOP PIR occupancy counter

int todaysDate;                         // Sets today's date related to things that reset at EOD.
int todaysMonth;                        // Sets today's Month
float PinMonthCost;                     // Keep a monthly total of cost for Pinshack
float WorkMonthCost;                    // Keep a monthly total of cost for Workshop
float TotalCost;                        // Keep a running total of cost for Pinshack+Workshop
String currentTimeDate;                 // Time formatted as "0:00AM on 0/0"
bool TimerFlag = false;                 // TimerFlag set whne heating on according to 7 day programmer
bool DayFlag = false;                   // Flag when day changes at midnight

// PINSHACK
bool resetFlag = true;                  // TRUE when the hardware has been reset.
bool Away_flag = false;                 // TRUE when Away_Temp1 has been instigated.
bool startFlag = false;                 // TRUE when the heating is on
bool stopFlag = false;                  // TRUE when the heating is off.
bool daySet = false;
int Occupied_Temp;                      // Copy of setpoint used in AWAY mode
long todaysAccumRuntimeSec;             // Today's accumulated runtime in seconds - displayed in app as minutes.
int currentCycleSec;                    // If Heating is running, the duration of the current cycle (non-accumulating).
int yesterdayRuntime;                   // Sum of yesterday's runtime in minutes - displays in app.
int yesterdayCount;                     // Sum of yesterday's cycle count to display in app.
int heatingTodaysStartCount;            // Today's number of heating cycles
String startTimeDate, stopTimeDate, resetTimeDate;
String currentStatus;                   // What displays in app. Used solely for sync and recover purposes.
float PinshackKWH;                      // How many Kw hours used used by Pinshack heating (1.5 Kw Heater)
float PinshackCost;                     // Daily Pinshack running costs at £0.13 per Elec unit
float PinshackCost_prev;                // Previous day total Pinshack running costs at £0.13 per Elec unit
float PinTotalCost;                     // Total running costs at £0.13 per Elec unit

//WORKSHOP
bool resetFlag2 = true;                 // TRUE when the hardware has been reset.
bool Away_flag2 = false;                // TRUE when Away_Temp1 has been instigated.
bool startFlag2 = false;                // TRUE when the heating is on
bool stopFlag2 = false;                 // TRUE when the heating is off.
int Occupied_Temp2;                     // Copy of setpoint used in AWAY mode
long todaysAccumRuntimeSec2;            // Today's accumulated runtime in seconds - displayed in app as minutes.
int currentCycleSec2;                   // If Heating is running, the duration of the current cycle (non-accumulating).
int yesterdayRuntime2;                  // Sum of yesterday's runtime in minutes - displays in app.
int yesterdayCount2;                    // Sum of yesterday's cycle count to display in app.
int heatingTodaysStartCount2;           // Today's number of heating cycles
String startTimeDate2, stopTimeDate2, resetTimeDate2;
String currentStatus2;                  // What displays in app. Used solely for sync and recover purposes.
float WorkshopKWH;                      // How many Kw hours uded used by Workshop heating (1 Kw heater)
float WorkshopCost;                     // Daily Workshop running costs at £0.13 per Elec unit
float WorkshopCost_prev;                // Previous day total Workshop running costs at £0.13 per Elec unit
float WorkTotalCost;                    // Total running costs at £0.13 per Elec unit

// Lighting control pins and switch feedback vrbls
int RELAY1_PIN = 40;      // Relay 1 pin
int Switch1_pin = 62;     // Digital pin for switch 1 input
int RELAY2_PIN = 41;      // Relay 2 pin
int Switch2_pin = 63;     // Digital pin for switch 2 input
int RELAY3_PIN = 42;      // Relay 3 pin
int Switch3_pin = 64;     // Digital pin for switch 3 input
int RELAY4_PIN = 43;      // Relay 4 pin
int Switch4_pin = 65;     // Digital pin for switch 4 input
int RELAY5_PIN = 44;      // Relay 5 pin
int Switch5_pin = 66;     // Digital pin for switch 5 input
int RELAY8_PIN = 47;      // Relay 8 pin (SAUNA)
int feedback;             // feedback
int feedback1;            // Switch 1 feedback
int feedback2;            // Switch 2 feedback
int feedback3;            // Switch 3 feedback
int feedback4;            // Switch 4 feedback
int feedback5;            // Switch 5 feedback

//WORKSHOP

int RELAYW1_PIN = 22;     // Relay 1 pin
int SwitchW1_pin = 5;     // Digital pin for switch 1 input
int RELAYW2_PIN = 23;     // Relay 2 pin
int SwitchW2_pin = 6;     // Digital pin for switch 2 input
int RELAYW3_PIN = 24;     // Relay 3 pin
int SwitchW3_pin = 7;     // Digital pin for switch 3 input
int RELAYW4_PIN = 25;     // Relay 4 pin
int SwitchW4_pin = 8;     // Digital pin for switch 4 input
int feedbackW1;           // Switch 1 feedback
int feedbackW2;           // Switch 2 feedback
int feedbackW3;           // Switch 3 feedback
int feedbackW4;           // Switch 4 feedback

//Alarm vrbls and values
float TempAlert = 5.0;     // Alarm +/- temp ranges for emails
bool PinOverFlag = false;  // Flags to prevent multiple emails being sent - reset every hour
bool PinUnderFlag = false;
bool WorkOverFlag = false;
bool WorkUnderFlag = false;

char auth[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";

void setup()
{
  Serial.begin(9600);
  Blynk.begin(auth);
  dht1.begin();
  dht2.begin();
  dht3.begin();
  rtc.begin();
  // Setup all of the pins and default ALL relays to OFF before reinstating stored states
  pinMode(RELAY1_PIN, OUTPUT); //Relay 1 pin output
  pinMode(RELAY2_PIN, OUTPUT); //Relay 2 pin output
  pinMode(RELAY3_PIN, OUTPUT); //Relay 3 pin output
  pinMode(RELAY4_PIN, OUTPUT); //Relay 4 pin output
  pinMode(RELAY5_PIN, OUTPUT); //Relay 5 pin output
  pinMode(RELAY8_PIN, OUTPUT); //Relay 8 pin output

  pinMode(Switch1_pin, INPUT); //Switch input 1
  pinMode(Switch2_pin, INPUT); //Switch input 2
  pinMode(Switch3_pin, INPUT); //Switch input 3
  pinMode(Switch4_pin, INPUT); //Switch input 4
  pinMode(Switch5_pin, INPUT); //Switch input 5

  //WORKSHOP
  pinMode(RELAYW1_PIN, OUTPUT); //Relay 1 pin output
  pinMode(RELAYW2_PIN, OUTPUT); //Relay 2 pin output
  pinMode(RELAYW3_PIN, OUTPUT); //Relay 3 pin output
  pinMode(RELAYW4_PIN, OUTPUT); //Relay 4 pin output

  pinMode(SwitchW1_pin, INPUT); //Switch input 1
  pinMode(SwitchW2_pin, INPUT); //Switch input 2
  pinMode(SwitchW3_pin, INPUT); //Switch input 3
  pinMode(SwitchW4_pin, INPUT); //Switch input 4

  pinMode(Heat1_pin, OUTPUT);
  pinMode(PIR1_pin, INPUT_PULLUP); // Pull this pin HIGH, PIR switch pulls it to LOW
  pinMode(Heat2_pin, OUTPUT);
  pinMode(PIR2_pin, INPUT_PULLUP);

  // Switch everything OFF to start with
  digitalWrite(Heat1_pin, 0);
  digitalWrite(Heat2_pin, 0);
  digitalWrite(RELAY1_PIN, 1);
  digitalWrite(RELAY2_PIN, 1);
  digitalWrite(RELAY3_PIN, 1);
  digitalWrite(RELAY4_PIN, 1);
  digitalWrite(RELAY5_PIN, 1);
  digitalWrite(RELAY8_PIN, 1);

  //WORKSHOP
  digitalWrite(RELAYW1_PIN, 1);
  digitalWrite(RELAYW2_PIN, 1);
  digitalWrite(RELAYW3_PIN, 1);
  digitalWrite(RELAYW4_PIN, 1);

  //Lets sync up all the store states for the V pins and load the appropriate variables listed below
  Blynk.syncVirtual(V31);            // CurrentStatus
  Blynk.syncVirtual(V32);            // CurrentStatus2
  Blynk.syncVirtual(V37);            // Heat1_pin state
  Blynk.syncVirtual(V38);            // Heat2_pin state
  Blynk.syncVirtual(V50);            // Light 1 Relay state
  Blynk.syncVirtual(V51);            // Light 2 Relay state
  Blynk.syncVirtual(V52);            // Light 3 Relay state
  Blynk.syncVirtual(V53);            // Light 4 Relay state
  Blynk.syncVirtual(V54);            // Light 5 Relay state
  Blynk.syncVirtual(V60);            // Workshop Light 1 Relay state
  Blynk.syncVirtual(V61);            // Workshop Light 2 Relay state
  Blynk.syncVirtual(V62);            // Workshop Light 3 Relay state
  Blynk.syncVirtual(V63);            // Workshop Light 4 Relay state
  Blynk.syncVirtual(V80);            // SetPoint1
  Blynk.syncVirtual(V81);            // Away_Temp1
  Blynk.syncVirtual(V82);            // SetPoint2
  Blynk.syncVirtual(V83);            // Away_Temp2
  Blynk.syncVirtual(V90);            // MaxPin Temp
  Blynk.syncVirtual(V91);            // MinPin Temp
  Blynk.syncVirtual(V92);            // MaxWork Temp
  Blynk.syncVirtual(V93);            // MinWork Temp
  Blynk.syncVirtual(V94);            // MaxOut Temp
  Blynk.syncVirtual(V95);            // MinOut Temp
  Blynk.syncVirtual(V99);            // Pinshack 7 Workshop total elec cost
  Blynk.syncVirtual(V100);           // Pinshack KwH
  Blynk.syncVirtual(V101);           // Workshop KwH
  Blynk.syncVirtual(V102);           // Pinshack Elec cost
  Blynk.syncVirtual(V103);           // Workshop Elec cost
  Blynk.syncVirtual(V104);           // Total elec cost
  Blynk.syncVirtual(V105);           // Pinshack previous day total elec cost
  Blynk.syncVirtual(V106);           // Workshop previous day total elec cost
  Blynk.syncVirtual(V107);           // Workshop total elec cost
  Blynk.syncVirtual(V110);           // heatingTodaysStartCount
  Blynk.syncVirtual(V111);           // todaysAccumRuntimeSec
  Blynk.syncVirtual(V112);           // yesterdayRuntime
  Blynk.syncVirtual(V113);           // yesterdayCount
  Blynk.syncVirtual(V120);           // heatingTodaysStartCount2
  Blynk.syncVirtual(V121);           // todaysAccumRuntimeSec2
  Blynk.syncVirtual(V122);           // yesterdayRuntime2
  Blynk.syncVirtual(V123);           // yesterdayCount2

  if (resetFlag == true) // we need this to stop a switch detect after a reset/power failure
  {
    feedback =  digitalRead(Switch1_pin);
    feedback1 = feedback;
    feedback =  digitalRead(Switch2_pin);
    feedback2 = feedback;
    feedback =  digitalRead(Switch3_pin);
    feedback3 = feedback;
    feedback =  digitalRead(Switch4_pin);
    feedback4 = feedback;
    feedback =  digitalRead(Switch5_pin);
    feedback5 = feedback;
    //WORKSHOP
    feedback =  digitalRead(SwitchW1_pin);
    feedbackW1 = feedback;
    feedback =  digitalRead(SwitchW2_pin);
    feedbackW2 = feedback;
    feedback =  digitalRead(SwitchW3_pin);
    feedbackW3 = feedback;
    feedback =  digitalRead(SwitchW4_pin);
    feedbackW4 = feedback;
  }
  //  Here we set all the timer trigger intervals.
  timer.setInterval(200,  Switch_detect); // Check to see if any physical switches have been toggled
  timer.setInterval(975, countRuntime);   // Counts heating runtime for daily accumulation displays.
  timer.setInterval(975, totalRuntime);   // Counts heating runtime.
  timer.setInterval(1000, setClockTime);  // Creates a current time with leading zeros.
  timer.setInterval(1000, sendStatus);    // update the app with all the data
  timer.setInterval(1250, Programmer);    // 7 day programmer
  timer.setInterval(1500, TempControl);   // Check temp control and action
  timer.setInterval(8000, Occupancy);     // Check if the rooms are occupied via PIR sensor every 8 seconds
  timer.setInterval(10000, Check_Alarms); // Do all the alarm checks and action if reqd.
  timer.setInterval(20000, readTemps);    // Read DHT22 temps every 20 seconds
  timer.setInterval(3600000, Check_Away); // Check if we need to go into AWAY mode every hour
  timer.setInterval(3600000, ResetFlags); // Rest all alarm/email flags every hour
}

void Switch_detect()
{
  feedback = digitalRead(Switch1_pin);
  if (feedback != feedback1) { // ? switch state has changed
    digitalWrite(RELAY1_PIN, !digitalRead(RELAY1_PIN)); // Toggle relay 1
    Blynk.virtualWrite(V40, !digitalRead(RELAY1_PIN));  // Update widget on App
    Blynk.virtualWrite(V50, digitalRead(RELAY1_PIN));   // Store Light 1 relay state in V50
    feedback1 = feedback;
  }

  feedback = digitalRead(Switch2_pin);
  if (feedback != feedback2) { // ? switch state has changed
    digitalWrite(RELAY2_PIN, !digitalRead(RELAY2_PIN)); // Toggle relay 2
    Blynk.virtualWrite(V41, !digitalRead(RELAY2_PIN));
    Blynk.virtualWrite(V51, digitalRead(RELAY2_PIN));   // Store Light 2 relay state in V51
    feedback2 = feedback;
  }

  feedback = digitalRead(Switch3_pin);
  if (feedback != feedback3) { // ? switch state has changed
    digitalWrite(RELAY3_PIN, !digitalRead(RELAY3_PIN)); // Toggle relay 3
    Blynk.virtualWrite(V42, !digitalRead(RELAY3_PIN));
    Blynk.virtualWrite(V52, digitalRead(RELAY3_PIN));   // Store Light 3 relay state in V52
    feedback3 = feedback;
  }

  feedback = digitalRead(Switch4_pin);
  if (feedback != feedback4) { // ? switch state has changed
    digitalWrite(RELAY4_PIN, !digitalRead(RELAY4_PIN)); // Toggle relay 4
    Blynk.virtualWrite(V43, !digitalRead(RELAY4_PIN));
    Blynk.virtualWrite(V53, digitalRead(RELAY4_PIN));   // Store Light 4 relay state in V53
    feedback4 = feedback;
  }

  feedback = digitalRead(Switch5_pin);
  if (feedback != feedback5) { // ? switch state has changed
    digitalWrite(RELAY5_PIN, !digitalRead(RELAY5_PIN)); // Toggle relay 5
    Blynk.virtualWrite(V44, !digitalRead(RELAY5_PIN));
    Blynk.virtualWrite(V54, digitalRead(RELAY5_PIN));   // Store Light 5 relay state in V54
    feedback5 = feedback;
  }
  //WORKSHOP
  feedback = digitalRead(SwitchW1_pin);
  if (feedback != feedbackW1) { // ? switch state has changed
    digitalWrite(RELAYW1_PIN, !digitalRead(RELAYW1_PIN));// Toggle relay 1
    Blynk.virtualWrite(V22, !digitalRead(RELAYW1_PIN));  // Update widget on App
    Blynk.virtualWrite(V60, digitalRead(RELAYW1_PIN));   // Store Light 1 relay state in V60
    feedbackW1 = feedback;
  }

  feedback = digitalRead(SwitchW2_pin);
  if (feedback != feedbackW2) { // ? switch state has changed
    digitalWrite(RELAYW2_PIN, !digitalRead(RELAYW2_PIN));// Toggle relay 2
    Blynk.virtualWrite(V23, !digitalRead(RELAYW2_PIN));
    Blynk.virtualWrite(V61, digitalRead(RELAYW2_PIN));   // Store Light 2 relay state in V61
    feedbackW2 = feedback;
  }

  feedback = digitalRead(SwitchW3_pin);
  if (feedback != feedbackW3) { // ? switch state has changed
    digitalWrite(RELAYW3_PIN, !digitalRead(RELAYW3_PIN));// Toggle relay 3
    Blynk.virtualWrite(V24, !digitalRead(RELAYW3_PIN));
    Blynk.virtualWrite(V62, digitalRead(RELAYW3_PIN));   // Store Light 3 relay state in V62
    feedbackW3 = feedback;
  }

  feedback = digitalRead(SwitchW4_pin);
  if (feedback != feedbackW4) { // ? switch state has changed
    digitalWrite(RELAYW4_PIN, !digitalRead(RELAYW4_PIN));// Toggle relay 4
    Blynk.virtualWrite(V25, !digitalRead(RELAYW4_PIN));
    Blynk.virtualWrite(V63, digitalRead(RELAYW4_PIN));   // Store Light 4 relay state in V63
    feedbackW4 = feedback;
  }
}

void countRuntime()
{
  if (year() != 1970) {                                       // Doesn't start until RTC is set correctly.
    if (daySet == false) {                                    // Sets the date (once per hardware restart) now that RTC is correct.
      todaysDate = day();
      todaysMonth = month();
      daySet = true;
    }
    if (todaysDate != day() && daySet == true) DayFlag = true;  // Set dayflag if day has changed at midnight

    //PINSHACK
    if (digitalRead(Heat1_pin) == 1 && DayFlag == false) {    // Accumulates seconds PINSHACK heating is on today.
      ++todaysAccumRuntimeSec;                                // Counts today's Heat1 on time in seconds.
      Blynk.virtualWrite(V111, todaysAccumRuntimeSec);
    }
    else if (DayFlag == true) {                               // day changed so do the business

      String EmailString = String("runtime mins = ") + String(todaysAccumRuntimeSec / 60) + String("    Runs = ") + String(heatingTodaysStartCount);
      Blynk.email("Subject: pinshack summary costs", EmailString);

      yesterdayRuntime = (todaysAccumRuntimeSec / 60);        // Moves today's runtime to yesterday for the app display.
      Blynk.virtualWrite(V112, yesterdayRuntime);             // Record yesterday's runtime to V112
      todaysAccumRuntimeSec = 0;                              // Reset today's sec timer.
      Blynk.virtualWrite(V111, todaysAccumRuntimeSec);
      yesterdayCount = heatingTodaysStartCount;               // set yesterdays runtime cycles variable for display
      Blynk.virtualWrite(V113, yesterdayCount);               // Record yesterday's no of cycles to V113
      heatingTodaysStartCount = 0;                            // Reset how many times heating cycle has started today.
    }
    if (yesterdayRuntime < 1) {  // Displays yesterday's runtime in app, or 'None' is there's none.
      Blynk.virtualWrite(V14, "None");
    }
    else {
      Blynk.virtualWrite(V14, String(yesterdayRuntime) + " mins (" + (yesterdayCount) + " runs)");
    }
    if (todaysAccumRuntimeSec == 0) { // Displays today's runtime in app, or 'None' if there's none.
      Blynk.virtualWrite(V15, "None");
    }
    else if (todaysAccumRuntimeSec > 0 && heatingTodaysStartCount == 0) {
      Blynk.virtualWrite(V15, String(todaysAccumRuntimeSec / 60) + " minutes");
    }
    else if (todaysAccumRuntimeSec > 0 && heatingTodaysStartCount > 0) {
      Blynk.virtualWrite(V15, String(todaysAccumRuntimeSec / 60) + " mins (" + (heatingTodaysStartCount) + " runs)");
    }

    //WORKSHOP
    if (digitalRead(Heat2_pin) == 1 && DayFlag == false) {     // Accumulates seconds WORKSHOP heating is on today.
      ++todaysAccumRuntimeSec2;                                // Counts today's Heat2 on time in seconds.
      Blynk.virtualWrite(V121, todaysAccumRuntimeSec2);
    }
    else if (DayFlag == true) {                                // day changed so do the business

      String EmailString = String("runtime mins = ") + String(todaysAccumRuntimeSec2 / 60) + String("    Runs = ") + String(heatingTodaysStartCount2);
      Blynk.email("Subject: Workshop summary costs", EmailString);

      yesterdayRuntime2 = (todaysAccumRuntimeSec2 / 60);       // Moves today's runtime to yesterday for the app display.
      Blynk.virtualWrite(V122, yesterdayRuntime2);             // Record yesterday's runtime to V122
      todaysAccumRuntimeSec2 = 0;                              // Reset today's sec timer.
      Blynk.virtualWrite(V121, todaysAccumRuntimeSec2);
      yesterdayCount2 = heatingTodaysStartCount2;              // set yesterdays runtime cycles variable for display
      Blynk.virtualWrite(V123, yesterdayCount2);               // Record yesterday's no of cycles to V123
      heatingTodaysStartCount2 = 0;                            // Reset how many times heating cycle has started today.
    }
    if (yesterdayRuntime2 < 1) {  // Displays yesterday's runtime in app, or 'None' is there's none.
      Blynk.virtualWrite(V16, "None");
    }
    else {
      Blynk.virtualWrite(V16, String(yesterdayRuntime2) + " mins (" + (yesterdayCount2) + " runs)");
    }
    if (todaysAccumRuntimeSec2 == 0) { // Displays today's runtime in app, or 'None' if there's none.
      Blynk.virtualWrite(V17, "None");
    }
    else if (todaysAccumRuntimeSec2 > 0 && heatingTodaysStartCount2 == 0) {
      Blynk.virtualWrite(V17, String(todaysAccumRuntimeSec2 / 60) + " minutes");
    }
    else if (todaysAccumRuntimeSec2 > 0 && heatingTodaysStartCount2 > 0) {
      Blynk.virtualWrite(V17, String(todaysAccumRuntimeSec2 / 60) + " mins (" + (heatingTodaysStartCount2) + " runs)");
    }

    // Set date since start of data collection for elec(if it has been reset by app widget)
    if (PinTotalCost == 0) Blynk.setProperty(V104, "label", String("Pinshack cost since ") + currentTimeDate);
    if (WorkTotalCost == 0) Blynk.setProperty(V107, "label", String("Workshop cost since ") + currentTimeDate);
    if (TotalCost == 0) Blynk.setProperty(V99, "label", String("TOTAL cost since ") + currentTimeDate);

    // OK Lets tot up elec costings here
    if (DayFlag == true) {                             // day changed so do the business
      PinshackCost_prev = PinshackCost;                // Moves today's cost to yesterday for the app display.
      Blynk.virtualWrite(V105, PinshackCost_prev);     // Record yesterday's PINSHACK cost to V105
      WorkshopCost_prev = WorkshopCost;                // Moves today's cost to yesterday for the app display.
      Blynk.virtualWrite(V106, WorkshopCost_prev);     // Record yesterday's WORKSHOP cost to V106
      PinTotalCost = PinTotalCost + PinshackCost;      // Summary cost since last reset
      Blynk.virtualWrite(V104, PinTotalCost);
      PinMonthCost = PinMonthCost + PinTotalCost;
      Blynk.virtualWrite(V108, PinMonthCost);
      WorkTotalCost = WorkTotalCost + WorkshopCost;
      Blynk.virtualWrite(V107, WorkTotalCost);
      WorkMonthCost = WorkMonthCost + WorkTotalCost;
      Blynk.virtualWrite(V109, WorkMonthCost);
      TotalCost = PinTotalCost + WorkTotalCost;
      Blynk.virtualWrite(V99, TotalCost);

      String EmailString = String("Pinshack Total cost = ") + String(PinTotalCost) + String("    Pin MonthCost = ") + String(PinMonthCost);
      Blynk.email("Subject: Elecs summary costs", EmailString);

      todaysDate = day();  // Set todays date to the new date after midnight elapses
      DayFlag = false;     // Reset dayflag
    }
    if (todaysMonth != month()) {                       // Let do the total Elec monthly usage stats via email
      String EmailString = String("Month No. = ") + String(month()) + String(" Pinshack Cost = ") + String(PinMonthCost) + String("  Workshop cost = ") + String(WorkMonthCost);
      Blynk.email("Subject: Electricity costs", EmailString);
      PinMonthCost = 0;
      WorkMonthCost = 0;
      todaysMonth = month();
    }
    PinshackKWH  = (todaysAccumRuntimeSec / 3600.0) * 1.5; // Work out Kw per hour used (1.5KW heater in Pinshack)
    Blynk.virtualWrite(V100, PinshackKWH);
    WorkshopKWH = (todaysAccumRuntimeSec2 / 3600.0) * 1.5; // Work out Kw per hour used (1.5Kw heater in Workshop)
    Blynk.virtualWrite(V101, WorkshopKWH);
    PinshackCost = (PinshackKWH * 0.13);                   // Actual Pinshack Elec costs calc here at £0.13 per unit (KwH)
    Blynk.virtualWrite(V102, PinshackCost);
    WorkshopCost = (WorkshopKWH * 0.13);                   // Actual Workshop Elec costs calc here at £0.13 per unit (KwH)
    Blynk.virtualWrite(V103, WorkshopCost);
  }
}

void totalRuntime()
{
  //PINSHACK
  // If PINSHACK heating is on accumulate the running time in sec
  if (digitalRead(Heat1_pin) == 1) ++currentCycleSec;
  else if (digitalRead(Heat1_pin) == 0 && currentCycleSec > 0) // if PINSHACK heating is off but recorded some runtime...
  {
    Blynk.virtualWrite(V110, ++heatingTodaysStartCount);       // Stores how many times the unit has started today... adds one start.
    currentCycleSec = 0;                                       // Reset the current cycle clocks
  }
  //WORKSHOP
  // If WORKSHOP heating is on...// accumulate the running time, however...
  if (digitalRead(Heat2_pin) == 1) ++currentCycleSec2;
  else if (digitalRead(Heat2_pin) == 0 && currentCycleSec2 > 0)// if WORKSHOP heating is off but recorded some runtime...
  {
    Blynk.virtualWrite(V120, ++heatingTodaysStartCount2);      // Stores how many times the unit has started today... adds one start.
    currentCycleSec2 = 0;                                      // Reset the current cycle clocks
  }
}

void setClockTime()
{
  // Below returns leading zeros on minutes and AM/PM.

  if (minute() > 9 && hour() > 11) {
    currentTimeDate = String(hourFormat12()) + ":" + minute() + "PM on " + day() + "/" + month();
  }
  else if (minute() < 10 && hour() > 11) {
    currentTimeDate = String(hourFormat12()) + ":0" + minute() + "PM on " + day() + "/" + month();
  }
  else if (minute() > 9 && hour() < 12) {
    currentTimeDate = String(hourFormat12()) + ":" + minute() + "AM on " + day() + "/" + month();
  }
  else if (minute() < 10 && hour() < 12) {
    currentTimeDate = String(hourFormat12()) + ":0" + minute() + "AM on " + day() + "/" + month();
  }
}

#197

And part two…

void sendStatus()
{
  // has Arduino been reset or just started.
  if (resetFlag == true && year() != 1970)
  {
    resetTimeDate = currentTimeDate;
    Blynk.virtualWrite(V31, currentStatus); //PINSHACK
    Blynk.setProperty(V31, "label", String("Current Status:                    SYSTEM RESET at ") + resetTimeDate);
    Blynk.virtualWrite(V32, currentStatus2); //WORKSHOP
    Blynk.setProperty(V32, "label", String("Current Status:                    SYSTEM RESET at ") + resetTimeDate);
  }

  //PINSHACK
  if (Heat1_Status == 1 && resetFlag == true)   // Runs once following reset/start if Heat is ON.
  {
    startFlag = true;
    resetFlag = false;
  }
  else if (Heat1_Status == 0 && resetFlag == true) // Runs once following Arduino reset/start if heat is OFF.
  {
    stopFlag = true;
    resetFlag = false;
  }

  // Display all the data on the app first PINSHACK
  // Have we passed threshold of heat limit? if so turn it off
  if (digitalRead(Heat1_pin) == 0 && startFlag == true) // now OFF, but was on
  {
    // Purpose: To swap between ON and OFF display once heating state change.
    stopTimeDate = currentTimeDate;                                   // Set off time.
    Blynk.setProperty(V31, "color", "#23C48E");   // Green
    Blynk.virtualWrite(V31, String("Heat OFF since ") + stopTimeDate); // Write to app.
    currentStatus = String("Heat OFF since ") + stopTimeDate;
    stopFlag = true;   // Ready flag when heat turns on.
    startFlag = false; // Keep everything locked out until the next cycle.
  }
  else if (digitalRead(Heat1_pin) == 1 && stopFlag == true) // PINSHACK heating now ON, but was off
  {
    startTimeDate = currentTimeDate;
    Blynk.setProperty(V31, "color", "#D3435C");   // Red
    Blynk.virtualWrite(V31, String("Heat ON since ") + startTimeDate);
    currentStatus = String("Heat ON since ") + startTimeDate;
    startFlag = true;
    stopFlag = false;
  }
  //WORKSHOP
  if (Heat2_Status == 1 && resetFlag2 == true)   // Runs once following reset/start if Heat is ON.
  {
    startFlag2 = true;
    resetFlag2 = false;
  }
  else if (Heat2_Status == 0 && resetFlag2 == true) // Runs once following Arduino reset/start if heat is OFF.
  {
    stopFlag2 = true;
    resetFlag2 = false;
  }

  // Display all the data on the app now WORKSHOP
  // Have we passed threshold of heat limit? if so turn it off
  if (digitalRead(Heat2_pin) == 0 && startFlag2 == true) // now OFF, but was on
  {
    // Purpose: To swap between ON and OFF display once heating state change.
    stopTimeDate2 = currentTimeDate;                                   // Set off time.
    Blynk.setProperty(V32, "color", "#23C48E");   // Green
    Blynk.virtualWrite(V32, String("Heat OFF since ") + stopTimeDate2); // Write to app.
    currentStatus2 = String("Heat OFF since ") + stopTimeDate2;
    stopFlag2 = true;   // Ready flag when heat turns on.
    startFlag2 = false; // Keep everything locked out until the next cycle.
  }
  else if (digitalRead(Heat2_pin) == 1 && stopFlag2 == true) // WORKSHOP heating now ON, but was off
  {
    startTimeDate2 = currentTimeDate;
    Blynk.setProperty(V32, "color", "#D3435C");   // Red
    Blynk.virtualWrite(V32, String("Heat ON since ") + startTimeDate2);
    currentStatus2 = String("Heat ON since ") + startTimeDate2;
    startFlag2 = true;
    stopFlag2 = false;
  }
}

void Programmer()
{
}


void TempControl()
{
  // OK lets action if PINSHACK heating needs to be turned ON or OFF
  // ON first

  if (t1 <= (SetPoint1 - Hysteresis1_Val)) // Only turn ON when below setpoint1 minus Hysteresis1_Val
  {
    digitalWrite(Heat1_pin, 1);  // Turn PINSHACK rad ON
    Blynk.virtualWrite(V37, 1);  // update V37 with Heat1_pin current status
  }
  // Turn OFF then
  else if (t1 >= SetPoint1)  // Only turn OFF when above setpoint1
  {
    digitalWrite(Heat1_pin, 0); // Turn PINSHACK rad OFF
    Blynk.virtualWrite(V37, 0); // update V37 with current status
  }

  // OK lets action if WORKSHOP heating needs to be turned ON or OFF
  // ON first
  if (t2 <= (SetPoint2 - Hysteresis1_Val)) // Only turn ON when below setpoint2 minus Hysteresis1_Val
  {
    digitalWrite(Heat2_pin, 1);  // Turn WORKSHOP rad ON
    Blynk.virtualWrite(V38, 1);  // update V38 with Heat2_pin current status
  }
  // Turn OFF then
  else if (t2 >= SetPoint2) // Only turn OFF when above setpoint2
  {
    digitalWrite(Heat2_pin, 0); // Turn WORKSHOP rad OFF
    Blynk.virtualWrite(V38, 0); // update V38 with current status
  }
}


void Occupancy () // Is there anybody in there!!
{
  if (digitalRead(PIR1_pin) == 0) {       // Pin goes LOW if movement detected
    Activity1 = Activity1 + 1;
    // Count movement activity every 5 seconds via PIR and maintain Setpoint1
    if (Away_flag == true) {
      //  Movement detected and we are in away mode so turn heating back on to SetPoint1
      Away_flag = false;
      SetPoint1 = Occupied_Temp;
      Blynk.virtualWrite(V80, SetPoint1); // Set slider on V80 to remembered SetPoint1
    }
  }
  if (digitalRead(PIR2_pin) == 0) {       // Pin goes LOW if movement detected
    Activity2 = Activity2 + 1;
    // Count movement activity every 5 seconds via PIR and maintain Setpoint2
    if (Away_flag2 == true) {
      //  Movement detected and we are in away mode so turn heating back on to SetPoint2
      Away_flag2 = false;
      SetPoint2 = Occupied_Temp2;
      Blynk.virtualWrite(V82, SetPoint2); // Set slider on V82 to remembered SetPoint2
    }
  }
}

void Check_Alarms()
{
  //OK Lets do all the alarm check and action accordingly.  No alarms if setpoints at 0 ie turned off or heating is not actually switched on

  if (t1 >= (SetPoint1 + TempAlert) && PinOverFlag == false && SetPoint1 != 0 && Heat1_pin == 1) {
    String EmailString = String("WARNING!!! Pinshack OVER Temp Setpoint = ") + String(SetPoint1) + String(" - Actual Temp = ") + String(t1);
    Blynk.email("Subject: Pinshack OVER Temp", EmailString);
    PinOverFlag = true;
  }
  if (t1 <= (SetPoint1 - TempAlert) && PinUnderFlag == false && SetPoint1 != 0 && Heat1_pin == 0) {
    String EmailString = String("WARNING!!! Pinshack UNDER Temp Setpoint = ") + String(SetPoint1) + String(" - Actual Temp = ") + String(t1);
    Blynk.email("Subject: Pinshack UNDER Temp", EmailString);
    PinUnderFlag = true;
  }
  if (t2 >= (SetPoint2 + TempAlert) && WorkOverFlag == false && SetPoint2 != 0 && Heat2_pin == 1) {
    String EmailString = String("WARNING!!! Workshop OVER Temp Setpoint = ") + String(SetPoint2) + String(" - Actual Temp = ") + String(t2);
    Blynk.email("Subject: Workshop OVER Temp", EmailString);
    WorkOverFlag = true;
  }
  if (t2 <= (SetPoint2 - TempAlert) && WorkUnderFlag == false && SetPoint2 != 0 && Heat2_pin == 0) {
    String EmailString = String("WARNING!!! Pinshack UNDER Temp Setpoint = ") + String(SetPoint2) + String(" - Actual Temp = ") + String(t2);
    Blynk.email("Subject: Workshop UNDER Temp", EmailString);
    WorkUnderFlag = true;
  }
}

void readTemps()
{
  //Read temp and humidity from PINSHACK DHT22
  t1 = dht1.readTemperature();
  h1 = dht1.readHumidity();
  // Check if any reads failed and exit early (to try again).
  if (isnan(t1) || isnan(h1)) {
    return;
  }
  // write values to V70 and V71
  Blynk.virtualWrite(V70, t1);
  Blynk.virtualWrite(V71, h1);

  //Read temp and humidity from WORKSHOP DHT22
  t2 = dht2.readTemperature();
  h2 = dht2.readHumidity();
  if (isnan(t2) || isnan(h2)) {
    return;
  }
  // write values to V72 and V73
  Blynk.virtualWrite(V72, t2);
  Blynk.virtualWrite(V73, h2);

  //Read temp and humidity from OUTSIDE DHT22
  t3 = dht3.readTemperature();
  h3 = dht3.readHumidity();
  if (isnan(t3) || isnan(h3)) {
    return;
  }
  // write values to V74 and V75
  Blynk.virtualWrite(V74, t3);
  Blynk.virtualWrite(V75, h3);

  // Update Max Min readings
  if (t1 > MaxPin) {
    MaxPin = t1;
    Blynk.setProperty(V90, "label", String("MaxPin Temp at ") + currentTimeDate);
    Blynk.virtualWrite(V90, MaxPin);
  }
  else if (t1 < MinPin) {
    MinPin = t1;
    Blynk.setProperty(V91, "label", String("MinPin Temp at ") + currentTimeDate);
    Blynk.virtualWrite(V91, MinPin);
  }
  if (t2 > MaxWork) {
    MaxWork = t2;
    Blynk.setProperty(V92, "label", String("MaxWork Temp at ") + currentTimeDate);
    Blynk.virtualWrite(V92, MaxWork);
  }
  else if (t2 < MinWork) {
    MinWork = t2;
    Blynk.setProperty(V93, "label", String("MinWork Temp at ") + currentTimeDate);
    Blynk.virtualWrite(V93, MinWork);
  }
  if (t3 > MaxOut) {
    MaxOut = t3;
    Blynk.setProperty(V94, "label", String("MaxOut Temp at ") + currentTimeDate);
    Blynk.virtualWrite(V94, MaxOut);
  }
  else if (t3 < MinOut) {
    MinOut = t3;
    Blynk.setProperty(V95, "label", String("MinOut Temp at ") + currentTimeDate);
    Blynk.virtualWrite(V95, MinOut);
  }
}

void Check_Away ()
{
  if (Activity1 == 0 and Away_flag == false) {  // No one present in Pinshack so drop setpoint temp to Away_temp1
    Away_flag = true;
    Occupied_Temp = SetPoint1;                  // store current setpoint1 in Occupied_Temp
    SetPoint1 = Away_Temp1;                     // Make sure SetPoint1 = Away_temp1 for TempControl loop to work ok
    Blynk.virtualWrite(V80, Away_Temp1);        // Set slider on V80 to Away_temp1
  }
  else {
    Activity1 = 0; 
    Away_flag = false;
    // presence has been detected so reset activity counter and start again
  }
  if (Activity2 == 0 and Away_flag2 == false) { // Repeat for Workshop side
    Away_flag2 = true;
    Occupied_Temp2 = SetPoint2;
    SetPoint2 = Away_Temp2;
    Blynk.virtualWrite(V82, Away_Temp2);
  }
  else {
    Activity2 = 0;
    Away_flag2 = false;
  }
}

void ResetFlags()
{ //  Hourly reset of all alarm and email flags
  PinOverFlag = false;
  PinUnderFlag = false;
  WorkOverFlag = false;
  WorkUnderFlag = false;
}

// Sync up all of the virtual pin data ///////////////////////////////////////////////////////////

BLYNK_WRITE(V0)
{
  // V0 Button to reset todays and yesterdays stats
  //PINSHACK
  yesterdayCount = 0;
  yesterdayRuntime = 0;
  heatingTodaysStartCount = 0;
  todaysAccumRuntimeSec = 0;
  Blynk.virtualWrite(V110, 0);
  Blynk.virtualWrite(V111, 0);
  Blynk.virtualWrite(V112, 0);
  Blynk.virtualWrite(V113, 0);
}
BLYNK_WRITE(V1)
{
  // V1 Button to reset todays and yesterdays stats
  //WORKSHOP
  yesterdayCount2 = 0;
  yesterdayRuntime2 = 0;
  heatingTodaysStartCount2 = 0;
  todaysAccumRuntimeSec2 = 0;
  Blynk.virtualWrite(V120, 0);
  Blynk.virtualWrite(V121, 0);
  Blynk.virtualWrite(V122, 0);
  Blynk.virtualWrite(V123, 0);
}

BLYNK_WRITE(V2)
{
  // V2 Button to reset Max Min settings
  MaxOut = 0;
  MaxPin = 0;
  MaxWork = 0;
  MinOut = 100;
  MinPin = 100;
  MinWork = 100;
  Blynk.virtualWrite(V90, 0);
  Blynk.virtualWrite(V91, 0);
  Blynk.virtualWrite(V92, 0);
  Blynk.virtualWrite(V93, 0);
  Blynk.virtualWrite(V94, 0);
  Blynk.virtualWrite(V95, 0);
}
BLYNK_WRITE(V3)
{
  // V3 to reset total Elec cost
  PinTotalCost = 0;
  Blynk.virtualWrite(V104, 0);
  PinshackCost_prev = 0;
  Blynk.virtualWrite(V105, 0);
  WorkshopCost_prev = 0;
  Blynk.virtualWrite(V106, 0);
  WorkTotalCost = 0;
  Blynk.virtualWrite(V107, 0);
  TotalCost = 0;
  Blynk.virtualWrite(V99, 0);
}
BLYNK_WRITE(V22)  // Runs every-time switch 1 widget V22 is toggled.
{
  digitalWrite(RELAYW1_PIN, !digitalRead(RELAYW1_PIN)); // Toggle relay 1
  Blynk.virtualWrite(V60, digitalRead(RELAYW1_PIN));    // Store Light 1 relay state in V60
}
BLYNK_WRITE(V23)  // Runs every-time switch 2 widget V23 is toggled.
{
  digitalWrite(RELAYW2_PIN, !digitalRead(RELAYW2_PIN)); // Toggle relay 2
  Blynk.virtualWrite(V61, digitalRead(RELAYW2_PIN));    // Store Light 2 relay state in V61
}
BLYNK_WRITE(V24)  // Runs every-time switch 3 widget V24 is toggled.
{
  digitalWrite(RELAYW3_PIN, !digitalRead(RELAYW3_PIN)); // Toggle relay 3
  Blynk.virtualWrite(V62, digitalRead(RELAYW3_PIN));    // Store Light 3 relay state in V62
}
BLYNK_WRITE(V25)  // Runs every-time switch 4 widget V25 is toggled.
{
  digitalWrite(RELAYW4_PIN, !digitalRead(RELAYW4_PIN)); // Toggle relay 4
  Blynk.virtualWrite(V63, digitalRead(RELAYW4_PIN));   // Store Light 4 relay state in V63
}
BLYNK_WRITE(V31)
{
  currentStatus = param.asStr();
}
BLYNK_WRITE(V32)
{
  currentStatus2 = param.asStr();
}
BLYNK_WRITE(V37)
{
  Heat1_Status = param.asInt();
  digitalWrite(Heat1_pin, Heat1_Status); // and reinstate PINSHACK heating condition
}
BLYNK_WRITE(V38)
{
  Heat2_Status = param.asInt();
  digitalWrite(Heat2_pin, Heat2_Status); // and reinstate WORKSHOP heating condition
}
BLYNK_WRITE(V40)  // Runs every-time switch 1 widget V40 is toggled.
{
  digitalWrite(RELAY1_PIN, !digitalRead(RELAY1_PIN)); // Toggle relay 1
  Blynk.virtualWrite(V50, digitalRead(RELAY1_PIN));   // Store Light 1 relay state in V50
}
BLYNK_WRITE(V41)  // Runs every-time switch 2 widget V41 is toggled.
{
  digitalWrite(RELAY2_PIN, !digitalRead(RELAY2_PIN)); // Toggle relay 2
  Blynk.virtualWrite(V51, digitalRead(RELAY2_PIN));   // Store Light 2 relay state in V51
}
BLYNK_WRITE(V42)  // Runs every-time switch 3 widget V42 is toggled.
{
  digitalWrite(RELAY3_PIN, !digitalRead(RELAY3_PIN)); // Toggle relay 3
  Blynk.virtualWrite(V52, digitalRead(RELAY3_PIN));   // Store Light 3 relay state in V52
}
BLYNK_WRITE(V43)  // Runs every-time switch 4 widget V43 is toggled.
{
  digitalWrite(RELAY4_PIN, !digitalRead(RELAY4_PIN)); // Toggle relay 4
  Blynk.virtualWrite(V53, digitalRead(RELAY4_PIN));   // Store Light 4 relay state in V53
}
BLYNK_WRITE(V44)  // Runs every-time switch 5 widget V44 is toggled.
{
  digitalWrite(RELAY5_PIN, !digitalRead(RELAY5_PIN)); // Toggle relay 5
  Blynk.virtualWrite(V54, digitalRead(RELAY5_PIN));   // Store Light 5 relay state in V54
}
BLYNK_WRITE(V47)  // Runs every-time Sauna(Ch 8) widget V47 is pressed.
{
  digitalWrite(RELAY8_PIN, 0); // Relay 8 SAUNA
  delay(500);
  digitalWrite(RELAY8_PIN, 1);
  Blynk.virtualWrite(V47, 0);
}
BLYNK_WRITE(V50)
{
  int V50_Data = param.asInt();
  digitalWrite(RELAY1_PIN, V50_Data);
  Blynk.virtualWrite(V40, !digitalRead(RELAY1_PIN));
}
BLYNK_WRITE(V51)
{
  int V51_Data = param.asInt();
  digitalWrite(RELAY2_PIN, V51_Data);
  Blynk.virtualWrite(V41, !digitalRead(RELAY2_PIN));
}
BLYNK_WRITE(V52)
{
  int V52_Data = param.asInt();
  digitalWrite(RELAY3_PIN, V52_Data);
  Blynk.virtualWrite(V42, !digitalRead(RELAY3_PIN));
}
BLYNK_WRITE(V53)
{
  int V53_Data = param.asInt();
  digitalWrite(RELAY4_PIN, V53_Data);
  Blynk.virtualWrite(V43, !digitalRead(RELAY4_PIN));
}
BLYNK_WRITE(V54)
{
  int V54_Data = param.asInt();
  digitalWrite(RELAY5_PIN, V54_Data);
  Blynk.virtualWrite(V44, !digitalRead(RELAY5_PIN));
}
BLYNK_WRITE(V60)
{
  int V60_Data = param.asInt();
  digitalWrite(RELAYW1_PIN, V60_Data);
  Blynk.virtualWrite(V22, !digitalRead(RELAYW1_PIN));
}
BLYNK_WRITE(V61)
{
  int V61_Data = param.asInt();
  digitalWrite(RELAYW2_PIN, V61_Data);
  Blynk.virtualWrite(V23, !digitalRead(RELAYW2_PIN));
}
BLYNK_WRITE(V62)
{
  int V62_Data = param.asInt();
  digitalWrite(RELAYW3_PIN, V62_Data);
  Blynk.virtualWrite(V24, !digitalRead(RELAYW3_PIN));
}
BLYNK_WRITE(V63)
{
  int V63_Data = param.asInt();
  digitalWrite(RELAYW4_PIN, V63_Data);
  Blynk.virtualWrite(V25, !digitalRead(RELAYW4_PIN));
}
BLYNK_WRITE(V80)  // Input from slider in app to set SetPoint1
{
  SetPoint1 = param.asInt();
  if (Away_Temp1 > SetPoint1) { // Don't let away temp be higher than Setpoint1 temp
    Away_Temp1 = SetPoint1;
    Blynk.virtualWrite(V81, Away_Temp1);
  }
  if (Occupied_Temp != SetPoint1 and Away_flag == true) { // Temp must of been manually changed whilst in away_mode so cancel it
    Away_flag = false;
    Occupied_Temp = SetPoint1;
  }
}
BLYNK_WRITE(V81)  // Input from slider in app to set Away_Temp1
{
  Away_Temp1 = param.asInt();
}
BLYNK_WRITE(V82)  // Input from slider in app to set SetPoint2
{
  SetPoint2 = param.asInt();
  if (Away_Temp2 > SetPoint2) { // Don't let away temp be higher than Setpoint2 temp
    Away_Temp2 = SetPoint2;
    Blynk.virtualWrite(V83, Away_Temp2);
  }
  if (Occupied_Temp2 != SetPoint2 and Away_flag2 == true) { // Temp must of been manually changed whilst in away_mode so cancel it
    Away_flag2 = false;
    Occupied_Temp2 = SetPoint2;
  }
}
BLYNK_WRITE(V83)  // Input from slider in app to set Away_Temp1
{
  Away_Temp2 = param.asInt();
}
BLYNK_WRITE(V90)
{
  MaxPin = param.asInt();
}
BLYNK_WRITE(V91)
{
  MinPin = param.asInt();
}
BLYNK_WRITE(V92)
{
  MaxWork = param.asInt();
}
BLYNK_WRITE(V93)
{
  MinWork = param.asInt();
}
BLYNK_WRITE(V94)
{
  MaxOut = param.asInt();
}
BLYNK_WRITE(V95)
{
  MinOut = param.asInt();
}
BLYNK_WRITE(V99)
{
  TotalCost = param.asInt();
}
BLYNK_WRITE(V100)
{
  PinshackKWH = param.asInt();
}
BLYNK_WRITE(V101)
{
  WorkshopKWH = param.asInt();
}
BLYNK_WRITE(V102)
{
  PinshackCost = param.asInt();
}
BLYNK_WRITE(V103)
{
  WorkshopCost = param.asInt();
}
BLYNK_WRITE(V104)
{
  PinTotalCost = param.asInt();
}
BLYNK_WRITE(V105)
{
  PinshackCost_prev = param.asInt();
}
BLYNK_WRITE(V106)
{
  WorkshopCost_prev = param.asInt();
}
BLYNK_WRITE(V107)
{
  WorkTotalCost = param.asInt();
}
BLYNK_WRITE(V108)
{
  PinMonthCost = param.asInt();
}
BLYNK_WRITE(V109)
{
  WorkMonthCost = param.asInt();
}
BLYNK_WRITE(V110)
{
  heatingTodaysStartCount = param.asInt();
}
BLYNK_WRITE(V111)
{
  todaysAccumRuntimeSec = param.asLong();
}
BLYNK_WRITE(V112)
{
  yesterdayRuntime = param.asInt();
}
BLYNK_WRITE(V113)
{
  yesterdayCount = param.asInt();
}
BLYNK_WRITE(V120)
{
  heatingTodaysStartCount2 = param.asInt();
}
BLYNK_WRITE(V121)
{
  todaysAccumRuntimeSec2 = param.asLong();
}
BLYNK_WRITE(V122)
{
  yesterdayRuntime2 = param.asInt();
}
BLYNK_WRITE(V123)
{
  yesterdayCount2 = param.asInt();
}

BLYNK_WRITE(V127)  // Lets switch ALL lights on or off.
{
  // Pinshack side
  digitalWrite(RELAY1_PIN, !digitalRead(RELAY1_PIN)); // Toggle relay 1
  Blynk.virtualWrite(V40, !digitalRead(RELAY1_PIN));   // Update widget
  Blynk.virtualWrite(V50, digitalRead(RELAY1_PIN));   // Store Light 1 relay state in V50
  digitalWrite(RELAY2_PIN, !digitalRead(RELAY2_PIN)); // Toggle relay 2
  Blynk.virtualWrite(V41, !digitalRead(RELAY2_PIN));   // Update widget
  Blynk.virtualWrite(V51, digitalRead(RELAY2_PIN));   // Store Light 2 relay state in V51
  digitalWrite(RELAY3_PIN, !digitalRead(RELAY3_PIN)); // Toggle relay 3
  Blynk.virtualWrite(V42, !digitalRead(RELAY3_PIN));   // Update widget
  Blynk.virtualWrite(V52, digitalRead(RELAY3_PIN));   // Store Light 3 relay state in V52
  digitalWrite(RELAY4_PIN, !digitalRead(RELAY4_PIN)); // Toggle relay 4
  Blynk.virtualWrite(V43, !digitalRead(RELAY4_PIN));   // Update widget
  Blynk.virtualWrite(V53, digitalRead(RELAY4_PIN));   // Store Light 4 relay state in V53
  digitalWrite(RELAY5_PIN, !digitalRead(RELAY5_PIN)); // Toggle relay 5
  Blynk.virtualWrite(V44, !digitalRead(RELAY5_PIN));   // Update widget
  Blynk.virtualWrite(V54, digitalRead(RELAY5_PIN));   // Store Light 5 relay state in V54

  // Workshop side
  digitalWrite(RELAYW1_PIN, !digitalRead(RELAYW1_PIN)); // Toggle relay 1
  Blynk.virtualWrite(V22, !digitalRead(RELAYW1_PIN));    // Update widget
  Blynk.virtualWrite(V60, digitalRead(RELAYW1_PIN));    // Store Light 1 relay state in V60
  digitalWrite(RELAYW2_PIN, !digitalRead(RELAYW2_PIN)); // Toggle relay 2
  Blynk.virtualWrite(V23, !digitalRead(RELAYW2_PIN));    // Update widget
  Blynk.virtualWrite(V61, digitalRead(RELAYW2_PIN));    // Store Light 2 relay state in V61
  digitalWrite(RELAYW3_PIN, !digitalRead(RELAYW3_PIN)); // Toggle relay 3
  Blynk.virtualWrite(V24, !digitalRead(RELAYW3_PIN));    // Update widget
  Blynk.virtualWrite(V62, digitalRead(RELAYW3_PIN));    // Store Light 3 relay state in V62
  digitalWrite(RELAYW4_PIN, !digitalRead(RELAYW4_PIN)); // Toggle relay 4
  Blynk.virtualWrite(V25, !digitalRead(RELAYW4_PIN));    // Update widget
  Blynk.virtualWrite(V63, digitalRead(RELAYW4_PIN));    // Store Light 4 relay state in V63
}

void loop()
{
  Blynk.run();
  timer.run();
}```