Easy way to setup and implement multiple 'Time Input' Widgets

After a lot of tinkering I’ve finally figures out how to setup and implement (and use) the Time Input Widgets. As this was certainly not trivial I decided to share my findings in a generalised piece of code.

I’ve started out with @Costas ezischeduler (check out that post to see the app side of the implementation) and had some exchange of thoughts with @Gunner.
The biggest difference in regard to other posts with examples (which used weekdays) that I’ve found on this forum is that I’ve split up the ‘schedule update’ and the ‘schedule check’ routines, which (in all examples) were both handled by the blynk_write() routine. The other change is the easy setup of multiple timers. Below the result

DISCLAIMER: I’ve extracted this from my current project and changed it so its more ‘general’ but I haven’t tested it. So should you find something, please let me know and I’ll update this post. It does compile and its derived from code that is tested (and works).

/****************************************************************************** 
  MultiScheduler.ino   by Wolph42, 6 6 2018

  Example use of the Timer Input Widget to schedule tasks
  Based on Costas EziScheduler.ino
  This code allows you to create upto [number of available virtual pins] timers.
  Stuff you can/should change (besides the usual auth/ssid/pass)
  
  - "MAX_SCHEDULE": sets the number of available timers (currently set to 2)
  - "START_TIMER_PIN": this is the FIRST vpin used for the widget input timers (the 
  second vpin in use will be START_TIMER_PIN + 1 (e.g. V10, V11, V12, etc.).
  Note: in case you didn't know 'V10' == '10'.
  - "//PUT YOUR START/STOP ACTIVITY CODE HERE!!": I guess this speaks for itself.
  
  I've added OTA as well as this makes life sooo much easier! But you can remove 
  those lines is not needed.
  
*******************************************************************************/

#define BLYNK_PRINT Serial 

#include <ESP8266WiFi.h>        
#include <BlynkSimpleEsp8266.h> 
#include <TimeLib.h>
#include <WidgetRTC.h>
#include <ArduinoOTA.h>

BlynkTimer timer;
WidgetRTC rtc;

char auth[]   = "**************"; 
char ssid[]   = "change these  ";                     
char pass[]   = "**************";                     
char server[] = "blynk-cloud.com"; 

const int MAX_SCHEDULE = 2;          //number of available schedules
const int START_TIMER_PIN = V10;      //START_TIMER_PIN is the virtual pin of  
unsigned long startseconds[MAX_SCHEDULE]; //the FIRST timer input pin. In case of  
unsigned long stopseconds[MAX_SCHEDULE];    //MAX_SCHEDULE = 3, the widget pins 
bool scheduleWeekDay[MAX_SCHEDULE][7];    //will be V10, V11 and V12

void setSchedule(const BlynkParam& param, int nSchedule) {         
  TimeInputParam t(param);
  startseconds[nSchedule] =  t.getStartHour()*3600 + t.getStartMinute()*60;
  stopseconds[nSchedule]  =  t.getStopHour()*3600  + t.getStopMinute()*60;
  for(int day = 1; day <=7; day++) { scheduleWeekDay[nSchedule][day%7] = t.isWeekdaySelected(day); } //Time library starts week on Sunday=1, Blynk on Monday=1, need to translate Blynk value to time value!! AND need to run from 0-6 instead of 1-7
}

void checkSchedule(){
  for (int nSchedule=0; nSchedule<MAX_SCHEDULE; nSchedule++){
    if( scheduleWeekDay[nSchedule][weekday()-1] ){    //Schedule is ACTIVE today
      unsigned long nowseconds =  hour()*3600 + minute()*60 + second();
  
      if(nowseconds >= startseconds[nSchedule] - 31 && nowseconds <= startseconds[nSchedule] + 31 ){  // 62s on 60s timer ensures 1 trigger command is sent
        //PUT YOUR START ACTIVITY CODE HERE!!
        Serial.println("Schedule started");
      //for example light up a LED at virtual pin 40
        Blynk.virtualWrite(V40, 200); //LED brightness runs from 0(off) to 255 (brightest)
      }
  
      if(nowseconds >= stopseconds[nSchedule] - 31 && nowseconds <= stopseconds[nSchedule] + 31 ){
        //PUT YOUR STOP ACTIVITY CODE HERE!!
        Serial.println("Schedule ended");
        Blynk.virtualWrite(V40, 0);
      }//seconds
    }//day
  }//for
}//checkSchedule

BLYNK_CONNECTED() {
  rtc.begin();
}

BLYNK_WRITE_DEFAULT(){  //Read widget updates coming in from APP
  int pin = request.pin;
  if(pin >= START_TIMER_PIN && pin <= (START_TIMER_PIN + MAX_SCHEDULE) ){
      int nSchedule = pin - START_TIMER_PIN;
      Serial.println(String("Schedule ") + nSchedule + "update");
      setSchedule(param,nSchedule);
  }
  //you can add more vpin X checks in this route OR use BLYNK_WRITE(VX) as usual
}

void setup() {
  //===BLYNK
  Serial.println("Booting");
  Blynk.begin(auth, ssid, pass, server);
  while (Blynk.connect() == false) {  }   // Wait until connected
  Serial.println("-------------");
  Serial.println(String("Blynk v") + BLYNK_VERSION + " started");
  Serial.println("-------------");
  //===BLYNK

  //===OTA
  ArduinoOTA.setHostname("MultiScheduler");       
  ArduinoOTA.begin();    
  //===OTA
  
  //===TIMERS
  timer.setInterval(60*1000, checkSchedule);  // check schedule every 1 minute
  //===TIMERS
}

void loop() {
  timer.run(); 
  if(Blynk.connected()) { Blynk.run(); }
  ArduinoOTA.handle();
}
4 Likes

@wolph42 thx very much for your sample code. I used it in my latest sketch :slight_smile:

At the same time I noticed that there are 3 corner cases where the scheduler fails

  1. A window of -31 to +31 may result in two start or stop activations
  2. In case of heavy load on the CPU, a start or stop may be missed
  3. A start or stop time scheduled at 0.00h will most likely be missed

The reasoning for above three cases is as follows

  1. A time window of 62 seconds fits two events spaced apart by 60 seconds (think of a picket fence)
  2. A function which has a long execution time may delay the next call of checkSchedule(), e.g a time event may be spaced apart by more than 60 seconds occasionally. Likely candidates are any type of internet communication like host lookup (dns), fetching the time from an ntp server or ssl communication. I therefore recommend to increase the upper bound by 15 seconds to account for functions taking over the cpu for several seconds.
  3. The term (nowseconds >= startseconds[nSchedule] - 31) is always false if start seconds is zero (midnight), e.g. the the time window to find start or stop is reduced to range 0 to +31. Setting the upper bound to a value larger than 60 seconds is the solution.

I therefore extended the check window to the range of -30 to +75 and added a flag to track a successful activation (blocking multiple calls of start or stop). As a side effect you may now use this flag to check if the timer is within the activation window.

The updated checkSchedule() looks as follows

bool scheduleActive[MAX_SCHEDULE] = {false};

void checkSchedule()
{
  for (int nSchedule = 0; nSchedule < MAX_SCHEDULE; nSchedule++)
  {
	//Schedule is ACTIVE today
    if (scheduleWeekDay[nSchedule][weekday() - 1])
    { 
      unsigned long nowseconds = hour() * 3600 + minute() * 60 + second();

      if (nowseconds >= startseconds[nSchedule] - 30 && nowseconds <= startseconds[nSchedule] + 75)
      {
        //PUT YOUR START ACTIVITY CODE HERE!!
        if (scheduleActive[nSchedule] == false)
        {
          Serial.println("Schedule started");
        }
        scheduleActive[nSchedule] = true;
      }

      if (nowseconds >= stopseconds[nSchedule] - 30 && nowseconds <= stopseconds[nSchedule] + 75)
      {
        if (scheduleActive[nSchedule] == true)
        {
          Serial.println("Schedule ended");
        }
        scheduleActive[nSchedule] = false;
      } //seconds
    }   //day
  }     //for
} //checkSchedule
3 Likes

Looks good, nice finds! Thanks