Way smarter lights

This project is the evolution of an older Blynk project: Amazon Echo ESP8266 control with natural speech commands.

That project was focused on opening up our IoT devices to a variety of inputs (manual, voice, Blynk, IFTTT). But over time, I’ve found it more useful to simply teach my devices to make informed decisions on my behalf. For lighting, this includes knowing if the sun is shining, and when I’m home. For many months I used Stringify to combine these conditions and trigger my lights. With the demise of Stringify, I’ve internalized the Sunrise / Sunset calculation.

The lights now come on only when it’s actually getting dark outside, and only if I’m at home. If I get up before sunrise, and have turned on the lights, they’ll turn themselves off automatically as it’s getting light out, or when I leave the house. Both sunrise and sunset times have offsets to account for the fact that actually gets dark before dusk, and isn’t immediately light out at sunrise. Of course, I can still control the lights with the methods enabled in my original project. But at this point, they just seem to magically “know” when they’re needed, and when they’re not. The only time I actively control them (usually by voice) is when I go to bed.

Since Blynk lacks a GPS trigger for iOS, I do still use IFTTT for location tracking; but Android users can further simplify by using Blynk to set the “Home” variable.

Enjoy:

/* Hue emulation for ESP8266 control with Alexa, Blynk and IFTTT.
 *  
 *  Auto mode uses a combination of "Home" and "Night" variables to 
 *    A) turn lights on when arriving home after sunset
 *    B) turn light on if sunset occurs when the user is already home
 *    C) turn off lights at sunrise
 *    D) turn off lights whenever the user leaves home
 *    
 * Sunrise and sunset are calculated using current date from NTP Server.  
 * Since it actually gets dark a bit before the sun sets, and remains
 * so a little after sunrise, "NightShift" and "DayShift" offsets compensate.
 * Default activation is 45 minutes before sunset and after sunrise.
 * 
 * https://github.com/tzapu/WiFiManager
 * https://github.com/Aircoookie/Espalexa
 * https://www.blynk.cc/
 * https://github.com/SensorsIot/NTPtimeESP
 * https://github.com/dmkishi/Dusk2Dawn/blob/master/Dusk2Dawn.h
 * 
 * For IFTTT, use the Maker Channel with the following settings:
 *    URL: http://blynk-cloud.com:8080/YOUR_TOKEN/V1       Substitute your own token and ButtonVPIN / HomeVPIN
 *    Method: PUT
 *    Content type: application/json
 *    Body: {"1"]                                          ButtonVPIN -> 0 = OFF, 1 = ON, 2 = toggle
 *                                                         HomeVPIN -> Home = 1, Away = 0
 */

#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h> 
#include <Espalexa.h>
#include <ArduinoOTA.h>
#include <BlynkSimpleEsp8266.h>
#include <NTPtimeESP.h>
#include <Dusk2Dawn.h>
#include <EEPROM.h>


////////////////////////////////////  Device customizations  /////////////////////////////////////////////////////
#define ButtonVPIN V5                 //Use a unique virtual pin for each device sharing the same Blynk token
char auth[] = "YOUR_TOKEN";   //Get a token from Blynk
char DeviceName[] = "SwitchOne";  // How you want the switch to be named
const int OutputPin = 12;        // Output pin to a relay or PWM device. The onboard relay on a Sonoff is pin 12.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#define AutoVPIN V10              //Toggle automatic on/off at sunrise and sunset (with offsets)
#define HomeVPIN V11              //Flag to track home/away status
#define ON 1
#define OFF 0
#define TacSwitch  0      //Pin for hardware momentary switch. Pin 0 on Sonoff
#define LED 13           //ON / OFF indicator LED. Onboard LED is 13 on Sonoff


NTPtime NTPch("ch.pool.ntp.org");   // Choose server pool as required
strDateTime dateTime;
BlynkTimer timer;
         
bool Home = true;

//Time oriented variables
bool AutoMode;
bool Night = false;
uint8_t NightShift = 45;    //Minutes before sunset to trigger "night" mode
uint8_t DayShift = 45;      //Minutes after sunrise to turn off "night" mode
byte LastRecordedDay = 0;       //Used to determine change of date (midnight)
Dusk2Dawn Chicago(41.8781, -87.6298, -6);  

Espalexa espalexa;

boolean LampState = 1;
boolean SwitchReset = true;   //Flag indicating that the hardware button has been released

void setup(){ 
  Serial.begin(9600);
  EEPROM.begin(1);
  AutoMode = EEPROM.read(0);  //Get Auto mode setting from memory

  pinMode(OutputPin, OUTPUT); 
  digitalWrite(OutputPin, HIGH);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW); 
  pinMode(TacSwitch, INPUT_PULLUP);  


  WiFi.hostname(DeviceName);
  WiFiManager wifi; 
  wifi.autoConnect(DeviceName); // Connect to this access point the first time you use the device

  // Initialize the new device
  espalexa.addDevice(DeviceName, UpdateSwitch1); //Parameters: (device name, callback function)
  espalexa.begin();

  Blynk.config(auth); 
  Blynk.virtualWrite(ButtonVPIN, LOW); 
  ArduinoOTA.begin();

  timer.setInterval(100, ButtonCheck);
  timer.setInterval(300000L, GetTime); //Check NTP time clock this often
}
 
void loop(){
   espalexa.loop();
   Blynk.run();
   ArduinoOTA.handle();
   timer.run();
}

//------------ Callback functions. Level can be set from 0-255. -------------
void UpdateSwitch1(uint8_t level) {   // Espalexa callback
  SetNewLevel(&level);  
}

// Handle blynk widget.
BLYNK_WRITE(ButtonVPIN){
  uint8_t level = param.asInt();
  if(level > 0){
    if(level == 2){   //Toggle command received
      if(LampState == 0){
        TurnLight(ON);
      }
      else{
        TurnLight(OFF);
      }
    }
    else{
      TurnLight(ON);  //On command received
    }
  }  
  else{               
    TurnLight(OFF);   //Off command received  
  } 
}

BLYNK_WRITE(AutoVPIN){
  if(param.asInt()){
    AutoMode = false;
  }
  else{
    AutoMode = true;
  }
  EEPROM.write(0, AutoMode);
}

BLYNK_WRITE(HomeVPIN){
  if(param.asInt()){
    Home = true;
    if(Night && AutoMode){  //Turn on lights if arriving home after sunset
      TurnLight(ON);
    }
  }
  else{
    Home = false;
    //TurnLight(OFF);         //Turn off lights when leaving home
  }
}

void TurnLight(uint8_t level){
  SetNewLevel(&level);
}

void SetNewLevel(uint8_t * pLevel){
  if (*pLevel > 0) {
    digitalWrite(OutputPin, HIGH); 
    Blynk.virtualWrite(ButtonVPIN, HIGH); 
    digitalWrite(LED, HIGH);
    LampState = 1;
  }
  else  {
    digitalWrite(OutputPin, LOW); 
    Blynk.virtualWrite(ButtonVPIN, LOW);
    digitalWrite(LED, LOW);
    LampState = 0;
  }
}

void GetTime(){
  if(AutoMode){
    uint8_t level;
    uint8_t Attempts = 0;
    do{
      dateTime = NTPch.getNTPtime(-6.0, 0); //Parameter 1: Time zone; Parameter 2: 1=Europe summer time, 2=US daylight saving, 0=no DST adjustment
      delay(100);
    }while(!dateTime.valid && Attempts < 5);
  
    if(dateTime.valid){
      byte actualHour = dateTime.hour;
      byte actualMinute = dateTime.minute;
      int actualYear = dateTime.year;
      byte actualMonth = dateTime.month;
      byte actualDay =dateTime.day;
  
      int MinutesAfterMidnight = (actualHour * 60) + actualMinute;
      Serial.print("Minutes after midnight: "); Serial.println(MinutesAfterMidnight);
  
      int ChicagoSunset = Chicago.sunset(actualYear, actualMonth, actualDay,false);
      int ChicagoSunrise = Chicago.sunrise(actualYear, actualMonth, actualDay,false);
  
      Serial.print("Sunrise is at "); Serial.print(ChicagoSunrise); Serial.println(" minutes after midnight today.");
      Serial.print("Sunset is at "); Serial.print(ChicagoSunset); Serial.println(" minutes after midnight today.");
      Serial.println();
      
      //Sunset triggerred (NightShift is the number of minutes before sunset to trigger "Night")
      if((MinutesAfterMidnight > ChicagoSunset - NightShift) && !Night){
        Night = true;
        Serial.println("Sunset");
        if(Home){
          TurnLight(ON);
        }
      }
      //Sunrise triggerred
      else if((MinutesAfterMidnight > ChicagoSunrise + DayShift) && Night && LastRecordedDay != actualDay){
        Night = false;
        LastRecordedDay = actualDay;
        Serial.println("Sunrise");
        TurnLight(OFF);
      }
    }
    else{
      Serial.println("Failed to retrieve time."); Serial.println();
    }
  }
}

// Handle hardware button press
void ButtonCheck(){
  Serial.println("check");
  boolean SwitchState = (digitalRead(TacSwitch));

  // toggle the switch if there's a new button press
  if (SwitchState == LOW && SwitchReset == true){
    Serial.println("Tac switch activated");
    if(LampState){
      TurnLight(OFF);
    }
    else{
      TurnLight(ON);
    }
    SwitchReset = false;  // Flag that indicates the physical button hasn't been released
    delay(50);            //debounce
  }
  else if (SwitchState){
    SwitchReset = true;   // reset flag the physical button release
  }
}
2 Likes

Wow, this is very cool, @chrome1000!

This is something we are also working on for the upcoming Blynk version, however I really like you run the calculations on device. And also incorporated the offset timing. Well thought through! :clap::clap::clap:

1 Like

Hello, chrome1000, I’m Android user, do you have one example on how to use “Home” variable for simplify your sketch?

does anyone know how to query a GET on Alexa echo under AWS LAMBDA with Node.js 8.10
because I want Alexa to tell me the temperature but I can not stand it

Hi @alessiocalosi . If the GPS trigger works as I think it does, you just have to add a GPS trigger widget to your Blynk dashboard, and assign it to virtual pin V31. The default behavior for this widget is to set the pin HIGH when entering your home area, which is exactly what you want. You shouldn’t have to change anything in the code.

Thanks this code works great for what I’m doing.

FYI: There is a bug in the code where Attempts variable is not incrementing in the do/while loop.

I also added an addition “else if” for if the esp is reset after midnight but before sunrise that sets night to true.

Thanks! Could you post the code changes you made?

My code is a little different because I’m saving things like latitude, longitude, daylight savings variable, and utc offset as extra wifimanager parameters in spiffs. But here is my GetTime() function.

void GetTime(){
  uint8_t level;
  uint8_t attempts = 0;
  Dusk2Dawn MyCity(atof(latitude), atof(longitude), atof(UTC_offset));
  do{
    dateTime = NTPch.getNTPtime(atof(UTC_offset), atoi(daylight_savings)); //Parameter 1: Time zone; Parameter 2: 1=Europe summer time, 2=US daylight saving, 0=no DST adjustment
    delay(100);
    attempts++; //Increment attempts
  }while(!dateTime.valid && attempts < 5);
  
  if(dateTime.valid){
    byte actualHour = dateTime.hour;
    byte actualMinute = dateTime.minute;
    int actualYear = dateTime.year;
    byte actualMonth = dateTime.month;
    byte actualDay =dateTime.day;
  
    int MinutesAfterMidnight = (actualHour * 60) + actualMinute;
    #ifdef DEBUG
      Serial.print("Minutes after midnight: "); Serial.println(MinutesAfterMidnight);
    #endif

    if (atoi(daylight_savings) == 2) {
      DST = true;
    }
    
    int Sunset = MyCity.sunset(actualYear, actualMonth, actualDay, DST);
    int Sunrise = MyCity.sunrise(actualYear, actualMonth, actualDay, DST);

    #ifdef DEBUG
      Serial.print("Sunrise is at "); Serial.print(Sunrise); Serial.println(" minutes after midnight today.");
      Serial.print("Sunset is at "); Serial.print(Sunset); Serial.println(" minutes after midnight today.");
      Serial.println();
    #endif
      
    //Sunset triggerred (NightShift is the number of minutes before sunset to trigger "Night")
    if((MinutesAfterMidnight > Sunset - NightShift) && !Night){
      Night = true;
      #ifdef DEBUG
        Serial.println("Sunset");
      #endif
    }
    //If program is reset after midnight, but it's not quite sunrise then set Night mode to true.
    else if((MinutesAfterMidnight < Sunrise + DayShift) && !Night){
      Night = true;
    }
    //Sunrise triggerred
    else if((MinutesAfterMidnight > Sunrise + DayShift) && Night && LastRecordedDay != actualDay){
      Night = false;
      LastRecordedDay = actualDay;
      #ifdef DEBUG
        Serial.println("Sunrise");
      #endif
    }
  }
  else{
    #ifdef DEBUG
      Serial.println("Failed to retrieve time."); Serial.println();
    #endif
  }
}
2 Likes