So I finally finished re write of the code.
The new code has the same features but number of schedulers and timers can be set using define (no limit on number of schedulers and timers). Keeping SCHEDULER_CNT == 4 and TIMER_CNT == 4 you are able to use the same app as before. If you change one of these defines you will need to reassign the Virtual Pins at the app.
Added system sleep mode that deactivate tasks (gpios) without deactivating schedulers (disable mode). During sleep all schedulers are active (only task is deactivated). When out of sleep if a scheduler is active the task will be turned ON.
Also added a hierarchy on the gpio activation to enable you to schedule tasks other than gpio activation.
Please let me know if you find any bugs.
/* Blynk SCHEDULER
* This code implements a parametric scheduler to activate a task
* at specific times and specific days .
* The system operates at resolution of minuets.
* Each scheduler can be assigned one task (i.e. turn on/off a gpio ...).
* The user can parametrize the number of schedulers.
* The user can parametrize the number of timers assigned to each scheduler.
* Each timer can be configured (from the app) to activate/deactivate
* the task at specific start/end time and days of week
* Each task can also be activated immediately and turned off after a
* configurable default time per scheduler (from the app).
* If end time is not given the task will be deactivated
* after the scheduler default time.
* Each scheduler can be configured (from app) with a max duration.
* If when timer activates a task the max duration is smaller than
* the configured task duration then the task is activated for max duration.
* If max duration is smaller than 2min it is ignored.
*
* The system can be disabled for a configurable (from app) number of days.
* When in sleep mode all active tasks and all schedulers are turned off.
* If number of days is 0 all current active tasks will be turned off
* If number of days is 1 all tasks and all schedulers will be
* turned off until midnight of today.
* If number of days is 2 all tasks and all schedulers will be
* turned off until midnight of next day. ...
* The system can also be put in sleep (from app) .
* When in sleep mode all active tasks are turned off. Schedulers continue working
* but will not activate tasks. Once out of sleep mode tasks are turned on if they
* ware expected to be active without the sleep mode.
* if number of days is 0 the sleep button has no effect.
* if number of days is 1 the sleep mode continue until midnight.
* if number of days is 2 the sleep mode continue until midnight next day....
*
*/
/* Comment this out to disable prints and save space */
#define BLYNK_PRINT Serial
#include <ESP8266WiFi.h>
//#include <ESP8266mDNS.h>
//#include <WiFiUdp.h>
//#include <ArduinoOTA.h>
#include <BlynkSimpleEsp8266.h>
#include <TimeLib.h>
#include <WidgetRTC.h>
/////////////////////////////////////////////////////////////
// system configuration section
// The following section contains code that should be changed
// by user to match the exact system needs.
////////////////////////////////////////////////////////////
#define WIFI_LED 16
// define parameters for number of schedulers and number of timers
#define SCHEDULER_CNT 4
#define TIME_CNT 4
// This system uses gpio on of as tasks.
// The number of used gpio per task is given by task_gpio_pins array
//byte task_gpio_pins[] = {12 , 5 , 4 , 15}; // number of gpio to be used as task control
byte task_gpio_pins[] = {2 , 5 , 14 , 15}; // number of gpio to be used as task control
// The default value of the gpio for task off is given by task_gpio_default.
// (used for gpio negative polarity)
bool task_gpio_default[] = {LOW,HIGH,LOW,HIGH};
// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
char auth[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "xxxxxx";
char pass[] = "xxxxxxxxxxxxxx";
//////////////////////////////////////////////////////////////////////////////////
// task activation function
// this is an abstraction layer for activating task
// currently only simple activation of gpio but user can change this
//////////////////////////////////////////////////////////////////////////////////
void task_on_off(bool task_state, char scheduler_num){
bool gpio_val = task_state^task_gpio_default[scheduler_num];
digitalWrite(task_gpio_pins[scheduler_num],gpio_val);
if (task_state)
Serial.println(String("Turn On Task: ") + (int)scheduler_num);
else
Serial.println(String("Turn Off Task: ") + (int)scheduler_num);
}
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
// end system configuration section //
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
// The following defines will automatically assign virtual pins //
// The first SCHEDULER_CNT*TIME_CNT virtual pins are assigned for time inputs //
// where virtual pins 0..TIME_CNT-1 are for scheduler 0 //
// pins TIME_CNT..2*TIME_CNT-1 for scheduler 1 ... //
// The following SCHEDULER_CNT pins are used for default duration sliders //
// The following SCHEDULER_CNT pins are used for immediate activation button //
// The following SCHEDULER_CNT pinst are used for maximum duration slider //
// Pin ( SCHEDULER_CNT*(TIME_CNT+3) ) +3 used for system sleep button //
// Pin ( SCHEDULER_CNT*(TIME_CNT+3) ) +4 used for system disable button //
// Pin ( SCHEDULER_CNT*(TIME_CNT+3) ) +5 used for slider of number of days //
// to disable system //
// User must ensure that number of used Virtual pins will not exceed 127 //
// So the following rule must be kept when selecting SCHEDULER_CNT and TIME_CNT//
// //
// SCHEDULER_CNT*(TIME_CNT+3) < 123 //
// //
//////////////////////////////////////////////////////////////////////////////////
// define time inputs Virtual Pin (Time Input Widget) for timer 0 of scheduler 0
#define VP_SWCH0_TIME0 V0
// define activation duration Virtual Pin (Slider Widget) for Scheduler 0 (V16)
#define VP_ACTV_DUR_SCHD0 (VP_SWCH0_TIME0+(TIME_CNT*SCHEDULER_CNT))
// define immidiate activation Virtual Pin (Button Widget) for Scheduler 0 (V20)
#define VP_IMMD_ACTV_SCHD0 (VP_ACTV_DUR_SCHD0+SCHEDULER_CNT)
// define max duration Virtual Pin (Slider Widget) Scheduler 0 (V24)
#define VP_MAX_DUR_SCHD0 (VP_IMMD_ACTV_SCHD0+SCHEDULER_CNT)
// define disable system input and number of days to disable input
#define VP_SLEEP_SYS (VP_MAX_DUR_SCHD0+SCHEDULER_CNT+3)
#define VP_DISBL_SYS (VP_MAX_DUR_SCHD0+SCHEDULER_CNT+4)
#define VP_DISBL_DAYS (VP_DISBL_SYS+1)
// maximum possible time in seconds using uint32_t (this is at year 2106)
#define TIME_MAX 0xFFFFFFFF
////////////////////////////////////////////////////////////////////////////////////////
// time input arrays
////////////////////////////////////////////////////////////////////////////////////////
int start_time_sec[SCHEDULER_CNT][TIME_CNT]; // Array of TIME_CNT timers per scheduler containing
// minuet in day for scheduler activation
bool weekdays[SCHEDULER_CNT][TIME_CNT][7]; // Array of 7 days (Sunday is day 0 ) for each time
int32_t active_duration_sec[SCHEDULER_CNT][TIME_CNT+1];// duration per time input (in sec) additional "timer"
// is used to save the default active duration
int32_t max_duration_sec[SCHEDULER_CNT]; // max duration per scheduler (in sec)
bool system_sleep_mode = false; // current sleep mode
uint32_t active_end_time_sec[SCHEDULER_CNT]; // current end time (in sec from 1970) per active scheduler
// if scheduler is not active (no active task) this == TIME_MAX
uint32_t max_end_time_sec[SCHEDULER_CNT]; // current end time due to max active time time (in sec from 1970)
// per active scheduler
// If scheduler is not active or max time =< 2 min this == TIME_MAX
// saves id of main timer
uint8_t main_timer_id;
// system disable timer
uint8_t disable_timer_id = 32; // When system is disabled or at sleep a timer is set to wake
// the system disable_timer_id saves this timer ID
// system disable days
int32_t system_disable_days_sec; // VP_DISBL_DAYS defines days to disable/sleep the system (including today) given in sec
// timer object declaration
BlynkTimer SystemTimer; // for calling activetoday and for turning off sleep/disable modes
// this code use Real Time Clock widget in the blynk app to keep the clock updated from net
WidgetRTC rtc;
BLYNK_CONNECTED() {
// Synchronize time on connection
rtc.begin();
Blynk.syncAll();
}
//////////////////////////////////////////////////////////////////////////////////////
// End of Sleep and Disable Modes //
// (They will probably not be used together so if one ends the other must end too) //
//////////////////////////////////////////////////////////////////////////////////////
void sleep_disable_mode_off()
{
system_sleep_mode = false;
// iterate over all schedulers and re reactivate tasks if needed (sleep mode).
for (int scheduler_cnt = 0; scheduler_cnt< SCHEDULER_CNT; scheduler_cnt++)
if(active_end_time_sec[scheduler_cnt]!=TIME_MAX) task_on_off(HIGH, scheduler_cnt);
// enable main timer (only if in disable mode)
SystemTimer.enable(main_timer_id);
// set disable and sleep buttons to off
Blynk.virtualWrite(VP_DISBL_SYS,0);
Blynk.virtualWrite(VP_SLEEP_SYS,0);
// set disable timer id to 32 to prevent disabling system timer by mistake
SystemTimer.deleteTimer(disable_timer_id);
disable_timer_id = 32;
Serial.println(String("Sleep/Disable mode off"));
}
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
////// BLYNK_WRITE_DEFAULT ///////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
// BLYNK_WRITE_DEFAULT enables defining configurable number of schedulers and
// time inputs without changing the code (only change defines)
// BLYNK_WRITE_DEFAULT takes any write to virtual pin and parse it using request.pin
BLYNK_WRITE_DEFAULT() {
uint8_t pin = request.pin; // Which exactly pin is handled?
///////////////////////////////////////////////////////////////////////
// DISABLE DAYS SLIDER INPUT //
///////////////////////////////////////////////////////////////////////
if (pin == VP_DISBL_DAYS) system_disable_days_sec = param.asInt()*86400; // V33
///////////////////////////////////////////////////////////////////////
// SYSTEM DISABLE/SLEEP BUTTONS INPUT //
///////////////////////////////////////////////////////////////////////
else if((pin == VP_DISBL_SYS )|(pin == VP_SLEEP_SYS)){ // V31 & V32
if (param.asInt()!=0){
String currentDate = String(day()) + "\\" + month() + "\\" + year();
if (pin == VP_SLEEP_SYS){
system_sleep_mode = true;
// turn off all tasks (do not disable the schedulers )
for (int task_cnt = 0; task_cnt< SCHEDULER_CNT; task_cnt++)
task_on_off(LOW, task_cnt);
// print current data on VP_SLEEP_SYS button
Blynk.setProperty(VP_SLEEP_SYS, "onLabel", currentDate);
}
else{
// disable main time tick
SystemTimer.disable(main_timer_id);
// turn off all active schedulers
Serial.println(String("Disable all"));
for (int scheduler_cnt = 0; scheduler_cnt< SCHEDULER_CNT; scheduler_cnt++)
scheduler_turn_off(scheduler_cnt);
// print current data on VP_SLEEP_SYS button
Blynk.setProperty(VP_DISBL_SYS, "onLabel", currentDate);
}
// if disable days is 0 then set nowseconds to -1 to prevent timer with negative duration
int32_t nowseconds = -1;
if (system_disable_days_sec!=0)
nowseconds = elapsedSecsToday(now());
// create a timer to wake the system up after system_disable_days_sec are over
SystemTimer.deleteTimer(disable_timer_id); // make sure no disable timer is active
disable_timer_id = SystemTimer.setTimeout(1000*(system_disable_days_sec-nowseconds),sleep_disable_mode_off);
}
else {// if system is manually out of seep mode delete disable timer and enable schedulers
sleep_disable_mode_off();
}
}
////////////////////////////////////////////////////////////////////////////////////
// MAX DURATION SLIDER INPUT //
////////////////////////////////////////////////////////////////////////////////////
else if (pin >= VP_MAX_DUR_SCHD0) { //V24..V27
if (pin < VP_MAX_DUR_SCHD0+SCHEDULER_CNT){
if(param.asInt()>2)
max_duration_sec[pin-VP_MAX_DUR_SCHD0] = param.asInt()*60;
else
max_duration_sec[pin-VP_MAX_DUR_SCHD0] = 864000; // set to 10 days is equivalent to not setting max duration
}
}
///////////////////////////////////////////////////////////////////////////////////
// SCHEDULER IMMEDIATE TASK ACTIVATION BUTTON INPUT //
///////////////////////////////////////////////////////////////////////////////////
else if (pin >= VP_IMMD_ACTV_SCHD0) { //V20..V23
if ( pin <(VP_IMMD_ACTV_SCHD0+SCHEDULER_CNT) ) {
if (param.asInt()==0)
scheduler_turn_off(pin-VP_IMMD_ACTV_SCHD0);
else
scheduler_turn_on(pin-VP_IMMD_ACTV_SCHD0,TIME_CNT); // immediate turn on using duration from slider
}
}
///////////////////////////////////////////////////////////////////////////////////
// DEFAULT ACTIVE DURATION SLIDER INPUT //
///////////////////////////////////////////////////////////////////////////////////
else if (pin >= VP_ACTV_DUR_SCHD0) { //V16..V19
// default duration is written in extra "timer"
if (pin <VP_ACTV_DUR_SCHD0+SCHEDULER_CNT) active_duration_sec[pin-VP_ACTV_DUR_SCHD0][TIME_CNT] = param.asInt()*60;
}
///////////////////////////////////////////////////////////////////////////////////
// TIME INPUT //
///////////////////////////////////////////////////////////////////////////////////
else if (pin >= VP_SWCH0_TIME0) { // V0..V15
int scheduler_num = (pin-VP_SWCH0_TIME0)/TIME_CNT;
int time_num = (pin-VP_SWCH0_TIME0)%TIME_CNT;
if ((scheduler_num<SCHEDULER_CNT) &( time_num<TIME_CNT)) {
TimeInputParam t(param); // convert to time input parameter
// Process start time
start_time_sec[scheduler_num][time_num]= -1; // start_time_sec of -1 indicate no start time is set (will never match current time)
if (t.hasStartTime())
{
Serial.println(String("Start: ") + t.getStartHour() + ":" + t.getStartMinute() + ":" + t.getStartSecond());
Serial.println(String("Start in sec: ") + param[0].asLong() + String(" for scheduler") + scheduler_num + String(" time_no: ") + time_num);
start_time_sec[scheduler_num][time_num]=param[0].asLong();
}
//////////////////////////////////////////////////////////////////////////////
// check if end time is received convert and save it as day time in seconds //
//////////////////////////////////////////////////////////////////////////////
active_duration_sec[scheduler_num][time_num] = -1;
if (t.hasStopTime())
{
Serial.println(String("Stop: ") + t.getStopHour() + ":" + t.getStopMinute() + ":" + t.getStopSecond());
Serial.println(String("Stop duration: ") + (param[1].asLong() -param[0].asLong()));
active_duration_sec[scheduler_num][time_num] = (param[1].asLong()-param[0].asLong());
// if end time is smaller than start time this means end time is at next day
if (active_duration_sec[scheduler_num][time_num]<=0)
active_duration_sec[scheduler_num][time_num] = 86400+active_duration_sec[scheduler_num][time_num];
}
/////////////////////////////////////////////////////////////////////////////
// Process weekdays (1. Mon, 2. Tue, 3. Wed, ...) //
/////////////////////////////////////////////////////////////////////////////
for (int i = 1; i <= 7; i++) {
weekdays[scheduler_num][time_num][(i%7)] = t.isWeekdaySelected(i);
if (t.isWeekdaySelected(i))
Serial.println(String("Day ") + i + " is selected");
}
Serial.println();
}
}
}
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
////// END OF BLYNK_WRITE_DEFAULT ///////
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
// Handle Scheduler events (from app or from timers ) //
/////////////////////////////////////////////////////////////////
// handle scheduler ON
void scheduler_turn_on(byte scheduler_num , byte time_no){
uint32_t now_abs_time = now();
// turn on the task
if (!system_sleep_mode) task_on_off(HIGH, scheduler_num);
// if stop time is not defined use the duration from slider
if(active_duration_sec[scheduler_num][time_no]>=0)
active_end_time_sec[scheduler_num] = now_abs_time+active_duration_sec[scheduler_num][time_no];
else
active_end_time_sec[scheduler_num] = now_abs_time+active_duration_sec[scheduler_num][TIME_CNT];
// max_end_time_sec is changed only if it was not previously set
if (max_end_time_sec[scheduler_num]==TIME_MAX)
max_end_time_sec[scheduler_num]=now_abs_time+max_duration_sec[scheduler_num];
// create time as string to print on activation button
char Time_print[10];
sprintf(Time_print, "%02d:%02d", hour(), minute());
// set button on and write the activation time on the button
Blynk.setProperty(VP_IMMD_ACTV_SCHD0+scheduler_num, "onLabel", String(Time_print));
Blynk.virtualWrite(VP_IMMD_ACTV_SCHD0+scheduler_num,1);
Serial.println(String("turn ON scheduler: ") + scheduler_num + String(" Until : ") + max_end_time_sec[scheduler_num]);
}
// handle scheduler OFF
void scheduler_turn_off(char scheduler_num ){
// set task off
task_on_off(LOW, scheduler_num);
// delete associated timer
active_end_time_sec[scheduler_num] = TIME_MAX;
max_end_time_sec[scheduler_num] = TIME_MAX;
Serial.println(String("turn OFF scheduler: ") + scheduler_num);
// reset button
Blynk.virtualWrite(scheduler_num + VP_IMMD_ACTV_SCHD0,0);
}
/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////
//// the following function is called every 10 seconds by a timer ////
//// the function checks if a start time is reached and if yes it will call ////
//// the scheduler_turn_on function (to turn on the scheduler and ////
//// set timer for turning off scheduler) ////
/////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////
uint32_t last_abs_min = 0; // saves the last minute that activetoday was started at
// this is later used to ensure activetoday will not be started twice at the same minute
void activetoday(){ // check if schedule #1 should run today
uint32_t now_abs_sec = now(); // time in seconds from 1970
// set wifi led if no connection
if(Blynk.connected())
digitalWrite(WIFI_LED,HIGH);
else
digitalWrite(WIFI_LED,LOW);
if(year() != 1970){ // if RTC was not started do nothing
uint32_t now_sec = elapsedSecsToday(now_abs_sec);
uint32_t now_abs_min = now_abs_sec/60;
for (uint8_t scheduler_cnt = 0; scheduler_cnt< SCHEDULER_CNT; scheduler_cnt++) {
////////////////////////////////////////////
// scheduler itteration
////////////////////////////////////////////
for (uint8_t timer_cnt = 0; timer_cnt< TIME_CNT; timer_cnt++) {
uint8_t tmp_day = 0x01<<weekday();
///////////////////////////////////////
// Timer itteration
//////////////////////////////////////
if (now_abs_min != last_abs_min) {
if ((weekdays[scheduler_cnt][timer_cnt][weekday()-1]==true) &
((now_sec >= start_time_sec[scheduler_cnt][timer_cnt]) & (now_sec < (start_time_sec[scheduler_cnt][timer_cnt])+60)) ){
/////////////////////////////////////
// activation
////////////////////////////////////
scheduler_turn_on(scheduler_cnt,timer_cnt);
}
}
}
///////////////////////////////////
// De activation (per scheduler)
//////////////////////////////////
if ((now_abs_sec >= active_end_time_sec[scheduler_cnt])|
(now_abs_sec >= max_end_time_sec[scheduler_cnt]) ){
scheduler_turn_off(scheduler_cnt);
}
}
last_abs_min = now_abs_min; // save last used min
}
}
/////////////////////////////////////
// BLYNK
void setup() {
// Debug console
Serial.begin(115200);
// reset tasks and system variables
for (int i = 0; i<SCHEDULER_CNT ; i++){
pinMode(task_gpio_pins[i], OUTPUT); // set output pins
task_on_off(LOW,i ); // turn tasks off
for (int j = 0; j<TIME_CNT ; j++) { // reset timer parameters
active_duration_sec[i][j] = -1; // duration of -1 indicate no stop time is set
start_time_sec[i][j]= -1; // start_time_sec of -1 indicate no start time is set
}
active_duration_sec[i][TIME_CNT] = 0; // this is the duration from the slider
active_end_time_sec[i] = TIME_MAX; // TIME_MAX indicate scheduler is not active
max_end_time_sec[i] = TIME_MAX; // TIME_MAX indicate scheduler is not active
}
pinMode(WIFI_LED, OUTPUT);
Blynk.begin(auth, ssid, pass);
// active today is called every 10 secondes
// if scheduler is manualy activated the scheduler may be active for up to extra 10 seconds
main_timer_id = SystemTimer.setInterval(10000L, activetoday);
setSyncInterval(10 * 60); // Sync interval in seconds (10 minutes)
// Port defaults to 8266
// ArduinoOTA.setPort(8266);
// Hostname defaults to esp8266-[ChipID]
// ArduinoOTA.setHostname("myesp8266");
// No authentication by default
// ArduinoOTA.setPassword((const char *)"Hay6t7r)");
// ArduinoOTA.onStart([]() {
// Serial.println("Start");
// });
// ArduinoOTA.onEnd([]() {
// Serial.println("\nEnd");
// });
// ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
// Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
// });
// ArduinoOTA.onError([](ota_error_t error) {
// Serial.printf("Error[%u]: ", error);
// if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
// else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
// else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
// else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
// else if (error == OTA_END_ERROR) Serial.println("End Failed");
// });
// ArduinoOTA.begin();
}
void loop() {
// ArduinoOTA.handle();
Blynk.run();
SystemTimer.run();
}