Exception error with MCP23017 and BlynkTimer

Code Wizards:

I am hoping to extend a long working project to control 12 relays using a scheduler and Blynk. I’m sure my coding is primitive, however working well with original 4 relay controller mostly from Hagary et.al.

Hardware is:
LOLIN D1 Mini Pro
LOLIN OLED Shield 64 X 48 (plugs directly to Lolin D1)
Adafruit_MCP23017 port extender using I2C on Lolin D1 SCL/SDA (1,2)
Power from proto board supply 3.3VDC-700 ma

Albeit, there a probably several bugs remaining in the “time, day, schedule routines”, I can’t seem to get past an exception causing MCU resets about every 10 seconds,

From Exception Decoder:

**Exception 0: Illegal instruction** PC: 0x4000e25d EXCVADDR: 0x00000000 *Decoding stack results* 0x402042cd: **BlynkTimer::run()** at C:\Users\xxxxxxxx\Documents\Arduino\libraries\blynk-library-master\src\utility\**BlynkTimer.cpp** line **60** 0x40203ff0: **setup()** at C:\Users\lawalter\Documents\Arduino\libraries\Adafruit_GFX_Library/**Adafruit_GFX.h** line **141** 0x40203c4f: **loop()** at C:\Users\lawalter\Documents\Arduino\sketch_jun15a/**sketch_jun15a.ino** line **551** 0x4020865c: **loop_wrapper()** at C:\Users\xxxxxxxx\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.0\cores\esp8266\**core_esp8266_main.cpp** line **201**

From hardware behavior it seems the OLED display never has time to get booted/reset, and a blinky red LED on MCU. From Serial monitor I observe the resets at time-stamp of very close to 10 seconds.

After 10’s of hours of searching, testing, focused small changes, this reset behavior and exceptions remain identical, and I am lost in what is probably something simple.

The crude code compiles well and occupies; “…Sketch uses 308977 bytes (29%) of program storage space. Maximum is 1044464 bytes…”

Code follows:

/* MODIFIED C:\Users\xxxxxxx\Documents\Arduino\libraries\Blynk\src WidgetTimeInput.h
  replace “-1” of the code “mWeekdays = -1;” by “0”. You can find the code in line “33” of file “WidgetTimeInput.h” (Version: Aug 2016, respectively 14 Sep 2016).*/
#define BLYNK_PRINT Serial
#define WIFI_LED 10

// define configuration (number of switches and number of timers)
#define SWITCH_CNT 12
#define TIME_CNT 4

//#include <ESP8266_Lib.h>
#include <BlynkSimpleEsp8266.h>
#include <TimeLib.h>
#include <WidgetRTC.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_GFX.h>
#include <Adafruit_MCP23017.h>

Adafruit_MCP23017 mcp;

byte mcp_pin_PA0=0;
byte mcp_pin_PA1=1;
byte mcp_pin_PA2=2;
byte mcp_pin_PA3=3;
byte mcp_pin_PA4=4;
byte mcp_pin_PA5=5;
byte mcp_pin_PA6=6;
byte mcp_pin_PA7=7;
byte mcp_pin_PB0=8;
byte mcp_pin_PB1=9;
byte mcp_pin_PB2=10;
byte mcp_pin_PB3=11;
byte mcp_pin_PB4=12;
byte mcp_pin_PB5=13;
byte mcp_pin_PB6=14;
byte mcp_pin_PB7=15;


//char auth[] = "hX7p4aL29tlmIrxpFPQxN8i8uriF9rWj"; //LOLIN D1 Mini
char auth[] = "aGx-6GnTSyissGnwCZJGWHQsa_JujVaJ";   //LOLIN D1 Mini
char ssid[] = "2WIRE505_AP";
char pass[] = "1248345632";

    #define SCREEN_WIDTH 64 // OLED display width, in pixels
    #define SCREEN_HEIGHT 48 // OLED display height, in pixels

    // Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
    #define OLED_RESET 0  // Default OLED Shield
    Adafruit_SSD1306 display(OLED_RESET);

byte switch_pins[] = {mcp_pin_PA0 , mcp_pin_PA1, mcp_pin_PA2 , mcp_pin_PA3 ,mcp_pin_PA4 , mcp_pin_PA5, mcp_pin_PA6 , mcp_pin_PA7 , mcp_pin_PB0 , mcp_pin_PB1, mcp_pin_PB2 , mcp_pin_PB3 }; // number of MCP23017 "gpio" to be used as switch/relay control

bool switch_default[] = {HIGH, HIGH, HIGH, HIGH ,HIGH, HIGH, HIGH, HIGH , HIGH, HIGH, HIGH, HIGH}; // switches that use reverse polarity should be set to HIGH here

////////////////////////////////////////////////////////////////////////////////////////
// This code can control up to 12 switches                                            //
// For each switch up to 1 schedule start and end times can be configured - (V0..V11) //
// A default duration can be defined per switch                           - (V12..V23)//
// If an end time is not defined at the app the default duration is used              //
// default duration is also used when manually activating the switch       - (V36..V47)//
// for each switch when start time is reached the switch turns on for the duration    //
//                                                                                    //
// maximum duration is used for safety (to limit activation time)          - (V24..V35)//
////////////////////////////////////////////////////////////////////////////////////////
int  start_time_sec[SWITCH_CNT][TIME_CNT];     // array of 4 start times (in seconds) for 12 switches [switch number][schedual timer number]
bool start_valid[SWITCH_CNT][TIME_CNT];        // is the start time valid ?
bool weekdays[SWITCH_CNT][TIME_CNT][8];        // array of 8 days (day 0 not used) for each schedual time
int  active_duration[SWITCH_CNT][TIME_CNT + 1]; // duration per switch per time(in sec)
bool end_valid[SWITCH_CNT][TIME_CNT];          // is the end time valid ?
int  max_duration[SWITCH_CNT];                 // max duration per switch
int auto_off = 1;           // 1 is auto or on

char zone  ;      // HOPEFUL GLOBAL VARIABLE TO DISPLAY ON OLED, I ATTEMPT TO CHANGE VALUE AND ASSIGN TO "ZONE" IN THE "handle switch state" loop

// when activating a switch a timer is set for the configured duration
// when the duration ends the switch is turned off by the timer
// the id of the timer is saved using end_timer_id
// if switch is manually turned off the end_timer_id is used to stop the timer.

// end_timer_id is initialized to 32 (per entry) at the setup section

int end_timer_id[SWITCH_CNT];

// timer object declaration
BlynkTimer timer;

// 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();
}
WidgetLED led1(V55);

bool ledStatus = auto_off;

// V55 LED Widget is ON or OFF
void LedWidget()
{ ledStatus = auto_off;
  if (ledStatus) {
    led1.on();
  } else {
    led1.off();

  }
}

//////////////////////////////////////////////////////////
// get schedual parameters from App                     //
//////////////////////////////////////////////////////////
void set_time(BlynkParam param, byte switch_no, byte time_no) {

  TimeInputParam t(param);
  // Process start 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 switch") + switch_no + String(" time_no: ") + time_no);
    start_time_sec[switch_no][time_no] = param[0].asLong();
    start_valid[switch_no][time_no] = true;
  }
  else
  {
    // Do nothing
    Serial.println(String("No Start Time Given for switch: ") + switch_no + String(" time_no: ") + time_no);
    start_valid[switch_no][time_no] = false;
  }

  //////////////////////////////////////////////////////////////////////////////
  // check if end time is received convert and save it as day time in seconds //
  //////////////////////////////////////////////////////////////////////////////
  if (t.hasStopTime())
  {
    Serial.println(String("Stop: ") +
                   t.getStopHour() + ":" +
                   t.getStopMinute() + ":" +
                   t.getStopSecond());
    Serial.println(String("Stop in sec: ") + param[1].asLong() + String(" for switch") + switch_no + String(" time_no: ") + time_no);

    active_duration[switch_no][time_no] = (param[1].asLong() - start_time_sec[switch_no][time_no]);

    // if end time is smaller than start time this means end time is at next day
    if (active_duration[switch_no][time_no] < 0) active_duration[switch_no][time_no] = 86400 + active_duration[switch_no][time_no];

    Serial.println(String("Stop duration: ") + active_duration[switch_no][time_no]);

    end_valid[switch_no][time_no] = true;
  }
  else // if end time is not defined //
  {
    // Do nothing
    Serial.println(String("No Stop Time Given for switch: ") + switch_no + String(" time_no: ") + time_no);
    end_valid[switch_no][time_no] = false;
  }

  // Process weekdays (1. Mon, 2. Tue, 3. Wed, ...)

  for (int i = 1; i <= 7; i++) {
    if (t.isWeekdaySelected(i)) {
      Serial.println(String("Day ") + i + " is selected");
      weekdays[switch_no][time_no][i] = true;
    }
    else {
      weekdays[switch_no][time_no][i] = false;
    }
  }

  Serial.println();
}

//this reads status of on/off mode and displays on screen
BLYNK_WRITE(V50) {
  int Vpin50 = param.asInt();
  auto_off = Vpin50;
}

// V0..V3 for switch 0, V4..V7 for switch 1 ...
BLYNK_WRITE(V0)  { set_time(param, 0, mcp_pin_PA0);  }
BLYNK_WRITE(V1)  { set_time(param, 1, mcp_pin_PA1);  }
BLYNK_WRITE(V2)  { set_time(param, 2, mcp_pin_PA2);  }
BLYNK_WRITE(V3)  { set_time(param, 3, mcp_pin_PA3);  }
BLYNK_WRITE(V4)  { set_time(param, 4, mcp_pin_PA4);  }
BLYNK_WRITE(V5)  { set_time(param, 5, mcp_pin_PA5);  }
BLYNK_WRITE(V6)  { set_time(param, 6, mcp_pin_PA6);  }
BLYNK_WRITE(V7)  { set_time(param, 7, mcp_pin_PA7);  }
BLYNK_WRITE(V8)  { set_time(param, 8, mcp_pin_PB0);  }
BLYNK_WRITE(V9)  { set_time(param, 9, mcp_pin_PB1);  }
BLYNK_WRITE(V10) { set_time(param, 10, mcp_pin_PB2);  }
BLYNK_WRITE(V11) { set_time(param, 11, mcp_pin_PB3);  }

// use a slider to define default activation duration (slider count in minute)
BLYNK_WRITE(V12) { active_duration[mcp_pin_PA0][TIME_CNT] = param.asInt() * 60;  }
BLYNK_WRITE(V13) { active_duration[mcp_pin_PA1][TIME_CNT] = param.asInt() * 60;  }
BLYNK_WRITE(V14) { active_duration[mcp_pin_PA2][TIME_CNT] = param.asInt() * 60;  }
BLYNK_WRITE(V15) { active_duration[mcp_pin_PA3][TIME_CNT] = param.asInt() * 60;  }
BLYNK_WRITE(V16) { active_duration[mcp_pin_PA4][TIME_CNT] = param.asInt() * 60;  }
BLYNK_WRITE(V17) { active_duration[mcp_pin_PA5][TIME_CNT] = param.asInt() * 60;  }
BLYNK_WRITE(V18) { active_duration[mcp_pin_PA6][TIME_CNT] = param.asInt() * 60;  }
BLYNK_WRITE(V19) { active_duration[mcp_pin_PA7][TIME_CNT] = param.asInt() * 60;  }
BLYNK_WRITE(V20) { active_duration[mcp_pin_PB0][TIME_CNT] = param.asInt() * 60;  }
BLYNK_WRITE(V21) { active_duration[mcp_pin_PB1][TIME_CNT] = param.asInt() * 60;  }
BLYNK_WRITE(V22) { active_duration[mcp_pin_PB2][TIME_CNT] = param.asInt() * 60;  }
BLYNK_WRITE(V23) { active_duration[mcp_pin_PB3][TIME_CNT] = param.asInt() * 60;  }

// use a slider to define default max duration (slider count in minute)   ***HARD CODE THIS*** NL
BLYNK_WRITE(V24) { max_duration[mcp_pin_PA0] = param.asInt() * 60;  }
BLYNK_WRITE(V25) { max_duration[mcp_pin_PA1] = param.asInt() * 60;  }
BLYNK_WRITE(V26) { max_duration[mcp_pin_PA2] = param.asInt() * 60;  }
BLYNK_WRITE(V27) { max_duration[mcp_pin_PA3] = param.asInt() * 60;  }
BLYNK_WRITE(V28) { max_duration[mcp_pin_PA4] = param.asInt() * 60;  }
BLYNK_WRITE(V29) { max_duration[mcp_pin_PA5] = param.asInt() * 60;  }
BLYNK_WRITE(V30) { max_duration[mcp_pin_PA6] = param.asInt() * 60;  }
BLYNK_WRITE(V31) { max_duration[mcp_pin_PA7] = param.asInt() * 60;  }
BLYNK_WRITE(V32) { max_duration[mcp_pin_PB0] = param.asInt() * 60;  }
BLYNK_WRITE(V33) { max_duration[mcp_pin_PB1] = param.asInt() * 60;  }
BLYNK_WRITE(V34) { max_duration[mcp_pin_PB2] = param.asInt() * 60;  }
BLYNK_WRITE(V35) { max_duration[mcp_pin_PB3] = param.asInt() * 60;  }

/////////////////////////////////////////////////////////////////
// Handle switch events (from app or from scheduler )          //
/////////////////////////////////////////////////////////////////

// turn off switch after active duration ends
// duration number is not important here
void turn_off_switch_no_0() { turn_on_off(0, mcp_pin_PA0, 0); Blynk.virtualWrite(V36, 0); Serial.println(String("timer turn off switch 0 ") );  }
void turn_off_switch_no_1() { turn_on_off(0, mcp_pin_PA1, 0); Blynk.virtualWrite(V37, 0); Serial.println(String("timer turn off switch 1 ") );  }
void turn_off_switch_no_2() { turn_on_off(0, mcp_pin_PA2, 0); Blynk.virtualWrite(V38, 0); Serial.println(String("timer turn off switch 2 ") );  }
void turn_off_switch_no_3() { turn_on_off(0, mcp_pin_PA3, 0); Blynk.virtualWrite(V39, 0); Serial.println(String("timer turn off switch 3 ") );  }
void turn_off_switch_no_4() { turn_on_off(0, mcp_pin_PA4, 0); Blynk.virtualWrite(V40, 0); Serial.println(String("timer turn off switch 4 ") );  }
void turn_off_switch_no_5() { turn_on_off(0, mcp_pin_PA5, 0); Blynk.virtualWrite(V41, 0); Serial.println(String("timer turn off switch 5 ") );  }
void turn_off_switch_no_6() { turn_on_off(0, mcp_pin_PA6, 0); Blynk.virtualWrite(V42, 0); Serial.println(String("timer turn off switch 6 ") );  }
void turn_off_switch_no_7() { turn_on_off(0, mcp_pin_PA7, 0); Blynk.virtualWrite(V43, 0); Serial.println(String("timer turn off switch 7 ") );  }
void turn_off_switch_no_8() { turn_on_off(0, mcp_pin_PB0, 0); Blynk.virtualWrite(V44, 0); Serial.println(String("timer turn off switch 8 ") );  }
void turn_off_switch_no_9() { turn_on_off(0, mcp_pin_PB1, 0); Blynk.virtualWrite(V45, 0); Serial.println(String("timer turn off switch 9 ") );  }
void turn_off_switch_no_10() { turn_on_off(0, mcp_pin_PB2, 0); Blynk.virtualWrite(V46, 0); Serial.println(String("timer turn off switch 10 ") );  }
void turn_off_switch_no_11() { turn_on_off(0, mcp_pin_PB3, 0); Blynk.virtualWrite(V47, 0); Serial.println(String("timer turn off switch 11 ") );  }

// handle switch state
void turn_on_off(int on_off, byte switch_no , byte time_no) {
  long active_duration_ms ;
  char Time_print[16];

  if ((on_off == 1) && (auto_off == 1)) //auto_off is a slider in app to shut off the program
  {
    // create time as string to print on activation button
    sprintf(Time_print, "%02d:%02d", hour(), minute());

    // turn on the switch (or off if default is on)
    mcp.digitalWrite(switch_pins[switch_no], !switch_default[switch_no]);

    // if end time is valid use the active duration assigned to this time
    // (change to msec will be done later)
    if (end_valid[switch_no][time_no])
      active_duration_ms = ((long)active_duration[switch_no][time_no]);
    else // otherwise use the general time duration
      active_duration_ms = ((long)active_duration[switch_no][4]);

    // max duration smaller than two min is not valid
    if ( (max_duration[switch_no] < 120) | (max_duration[switch_no] > active_duration_ms) )
      active_duration_ms = active_duration_ms * 1000;
    else
      active_duration_ms = ((long)max_duration[switch_no]) * 1000;

    // if new timer is set before another one ended then disable previous timer
    if (end_timer_id[switch_no] != 32) timer.deleteTimer(end_timer_id[switch_no]);

    // turn on switch and set timer
    switch (switch_no) {
      case 0:
        Blynk.setProperty(V36, "onLabel", String(Time_print));
        Blynk.virtualWrite(V36, 1);
        end_timer_id[0] = timer.setTimeout(active_duration_ms, turn_off_switch_no_0);
  zone = 1;
        break;
      case 1:
        Blynk.setProperty(V37, "onLabel", String(Time_print));
        Blynk.virtualWrite(V37, 1);
        end_timer_id[1] = timer.setTimeout(active_duration_ms, turn_off_switch_no_1);
  zone = 2;
        break;
      case 2:
        Blynk.setProperty(V38, "onLabel", String(Time_print));
        Blynk.virtualWrite(V38, 1);
        end_timer_id[2] = timer.setTimeout(active_duration_ms, turn_off_switch_no_2);
  zone = 3;
        break;
      case 3:
        Blynk.setProperty(V39, "onLabel", String(Time_print));
        Blynk.virtualWrite(V39, 1);
        end_timer_id[3] = timer.setTimeout(active_duration_ms, turn_off_switch_no_3);
  zone = 4;
        break;
      case 4:
        Blynk.setProperty(V40, "onLabel", String(Time_print));
        Blynk.virtualWrite(V40, 1);
        end_timer_id[4] = timer.setTimeout(active_duration_ms, turn_off_switch_no_4);
  zone = 5;
        break;
      case 5:
        Blynk.setProperty(V41, "onLabel", String(Time_print));
        Blynk.virtualWrite(V41, 1);
        end_timer_id[5] = timer.setTimeout(active_duration_ms, turn_off_switch_no_5);
  zone = 6;
        break;
      case 6:
        Blynk.setProperty(V42, "onLabel", String(Time_print));
        Blynk.virtualWrite(V42, 1);
        end_timer_id[6] = timer.setTimeout(active_duration_ms, turn_off_switch_no_6);
  zone = 7;
        break;
      case 7:
        Blynk.setProperty(V43, "onLabel", String(Time_print));
        Blynk.virtualWrite(V43, 1);
        end_timer_id[7] = timer.setTimeout(active_duration_ms, turn_off_switch_no_7);
  zone = 8;
        break;
      case 8:
        Blynk.setProperty(V44, "onLabel", String(Time_print));
        Blynk.virtualWrite(V44, 1);
        end_timer_id[8] = timer.setTimeout(active_duration_ms, turn_off_switch_no_8);
  zone = 9;
        break;
      case 9:
        Blynk.setProperty(V45, "onLabel", String(Time_print));
        Blynk.virtualWrite(V45, 1);
        end_timer_id[9] = timer.setTimeout(active_duration_ms, turn_off_switch_no_9);
  zone = 10;
        break;
      case 10:
        Blynk.setProperty(V46, "onLabel", String(Time_print));
        Blynk.virtualWrite(V46, 1);
        end_timer_id[10] = timer.setTimeout(active_duration_ms, turn_off_switch_no_10);
  zone = 11;
        break;
      case 11:
        Blynk.setProperty(V47, "onLabel", String(Time_print));
        Blynk.virtualWrite(V47, 1);
        end_timer_id[11] = timer.setTimeout(active_duration_ms, turn_off_switch_no_11);
  zone =12;
        break;
    }    

    Serial.println(String("turn ON switch: ") + switch_no + String(" for duration: ") + active_duration_ms / 60000 + String("min "));
  }
  else
  {
    mcp.digitalWrite(switch_pins[switch_no], switch_default[switch_no]);
    timer.deleteTimer(end_timer_id[switch_no]);
    end_timer_id[switch_no] = 32;
    Serial.println(String("turn OFF switch: ") + switch_no);

    zone = '\0';
  }
}

// set switch state from APP
BLYNK_WRITE(V36) { turn_on_off(param.asInt(), mcp_pin_PA0, TIME_CNT);  }
BLYNK_WRITE(V37) { turn_on_off(param.asInt(), mcp_pin_PA1, TIME_CNT);  }
BLYNK_WRITE(V38) { turn_on_off(param.asInt(), mcp_pin_PA2, TIME_CNT);  }
BLYNK_WRITE(V39) { turn_on_off(param.asInt(), mcp_pin_PA3, TIME_CNT);  }
BLYNK_WRITE(V40) { turn_on_off(param.asInt(), mcp_pin_PA4, TIME_CNT);  }
BLYNK_WRITE(V41) { turn_on_off(param.asInt(), mcp_pin_PA5, TIME_CNT);  }
BLYNK_WRITE(V42) { turn_on_off(param.asInt(), mcp_pin_PA6, TIME_CNT);  }
BLYNK_WRITE(V43) { turn_on_off(param.asInt(), mcp_pin_PA7, TIME_CNT);  }
BLYNK_WRITE(V44) { turn_on_off(param.asInt(), mcp_pin_PB0, TIME_CNT);  }
BLYNK_WRITE(V45) { turn_on_off(param.asInt(), mcp_pin_PB1, TIME_CNT);  }
BLYNK_WRITE(V46) { turn_on_off(param.asInt(), mcp_pin_PB2, TIME_CNT);  }
BLYNK_WRITE(V47) { turn_on_off(param.asInt(), mcp_pin_PB3, TIME_CNT);  }

/////////////////////////////////////////////////////////////////////////////////////////////
// the following function is called every 60 seconds by a timer                            //
//  the function checks if a start time is reached and if yes it will call                 //
//   the turn_on_off function (to turn on the switch and set timer for turning off switch) //
/////////////////////////////////////////////////////////////////////////////////////////////
void activetoday() {        // check if schedule #1 should run today

  if (Blynk.connected()) // set wifi led if no connection
  {
    mcp.digitalWrite(WIFI_LED, LOW);
  }
  else
  {
    mcp.digitalWrite(WIFI_LED, HIGH);
  }

  if (year() != 1970) {
    unsigned int nowseconds = ((hour() * 3600) + (minute() * 60) + second());
    int dayadjustment = -1;
    if (weekday() == 1) {
      dayadjustment = 6; // needed for Sunday Time library is day 1 and Blynk is day 7
    }

    for (int switch_cnt = 0;  switch_cnt < SWITCH_CNT; switch_cnt++) {
      for (int timer_cnt = 0;  timer_cnt < TIME_CNT; timer_cnt++) {
        if (start_valid[switch_cnt][timer_cnt] == true) {
          if (weekdays[switch_cnt][timer_cnt][weekday() + dayadjustment] == true) {
            if (nowseconds >= start_time_sec[switch_cnt][timer_cnt]) {
               if (nowseconds < start_time_sec[switch_cnt][timer_cnt] + 90) {
                
                turn_on_off(1, switch_cnt, timer_cnt);
                Serial.println(String("turn ON switch: ") + switch_cnt);

              }
            }
          }
        }
      }
    }
  }
}

/////////////////////////////////////

// Digital clock display of the time
void clockDisplay()
{
  // You can call hour(), minute(), ... at any time
  // Please see Time library examples for details

  String currentTime = String(hour()) + ":" + minute() + ":" + second();
  String currentDate = String(month()) + "." + day() + "." + year();
  Serial.print("Current time: ");
  Serial.print(currentTime);
  Serial.print(" ");
  Serial.print(currentDate);
  Serial.println();

  // Send DATE and DATE to Blynk APP
  Blynk.virtualWrite(V101, currentTime);
  // Send date to the App--Not used but is available--this will show processor date if it is important to you
  Blynk.virtualWrite(V102, currentDate);
}

void OLED_SSD1306() {
      // clear display
      display.clearDisplay();

      //display
      display.setTextSize(1);
      display.setCursor(0, 0);
      display.print("TIME");
      display.setTextSize(1);
      display.setCursor(0, 10);
      display.print(String(hour()) + ":" + minute() + ":" + second());
      display.print(" ");
      display.setTextSize(1);
      display.setCursor(0, 30);
      display.print("ZONE");
      display.setTextSize(2);
      display.setCursor(34, 25);
      display.printf("%2d", zone);
      display.display();
}

void setup() {

//  Wire.begin(0,2); //SDA, SCL

  Serial.begin(115200);

  // Initialize the MCP23017
  mcp.begin(7);      // use hardware default address 7

  mcp.pinMode(mcp_pin_PA0, OUTPUT);
  mcp.pullUp(mcp_pin_PA0, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA0, HIGH);

  mcp.pinMode(mcp_pin_PA1, OUTPUT);
  mcp.pullUp(mcp_pin_PA1, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA1, HIGH);
  
  mcp.pinMode(mcp_pin_PA2, OUTPUT);
  mcp.pullUp(mcp_pin_PA2, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA2, HIGH);
    
  mcp.pinMode(mcp_pin_PA3, OUTPUT);
  mcp.pullUp(mcp_pin_PA3, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA3, HIGH);  

  mcp.pinMode(mcp_pin_PA4, OUTPUT);
  mcp.pullUp(mcp_pin_PA4, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA4, HIGH);
    
  mcp.pinMode(mcp_pin_PA5, OUTPUT);
  mcp.pullUp(mcp_pin_PA5, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA5, HIGH);
      
  mcp.pinMode(mcp_pin_PA6, OUTPUT);
  mcp.pullUp(mcp_pin_PA6, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA6, HIGH);
      
  mcp.pinMode(mcp_pin_PA7, OUTPUT);
  mcp.pullUp(mcp_pin_PA7, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA7, HIGH);
    
  mcp.pinMode(mcp_pin_PB0, OUTPUT);
  mcp.pullUp(mcp_pin_PB0, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PB0, HIGH);
      
  mcp.pinMode(mcp_pin_PB1, OUTPUT);
  mcp.pullUp(mcp_pin_PB1, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PB1, HIGH);
        
  mcp.pinMode(mcp_pin_PB2, OUTPUT);
  mcp.pullUp(mcp_pin_PB2, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PB2, HIGH);
        
  mcp.pinMode(mcp_pin_PB3, OUTPUT);
  mcp.pullUp(mcp_pin_PB3, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PB3, HIGH);
        
  // reset output pins used and system variables
  for (int i = 0; i < SWITCH_CNT ; i++) {

    mcp.digitalWrite(switch_pins[i], switch_default[i]); // set switch to default
    end_timer_id[i] = 32;            // reset end timer id's

    for (int j = 0; j < TIME_CNT ; j++)
    {
      end_valid[i][j] = false;       // reset end valid array
    }
  }
ayr
  Blynk.begin(auth, ssid, pass);
  rtc.begin();  // Begin synchronizing time
  timer.setInterval(10000L, clockDisplay); // check every 10s 
  timer.setInterval(60000L, activetoday);  // check every 60s if ON / OFF trigger time has been reached
  setSyncInterval(5 * 60); // Sync interval in seconds (5 minutes)  

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 64x48)

      delay(5000);
      display.clearDisplay();
      display.setTextColor(WHITE);
}

void loop()
{
  Blynk.run();
  timer.run();
  LedWidget();
  OLED_SSD1306();

}

Coaching is sincerely appreciated as possible,

I’ve moved this post to its own topic.

This appears to be intended to be used with the old version of Blynk. If that is the case then you need to be using Blynk library version 0.6.1

also, have you seen this topic…

Pete.

Pete:

I believe I just reloaded newer library in pursuit of this challenge:

name=Blynk
version=1.0.0
author=Volodymyr Shymanskyy
license=MIT
maintainer=Volodymyr Shymanskyy vshymanskyi@gmail.com

I’ll to figure out how to collect that version 0.6.1. (albeit, I’m new to dealing with those as the Arduino IDE seems to “include” same/similar names to calls from libraries with other names.???)

(I finally received my more capable MCU’s from your prior coaching on the MCP23017)

Many thanks

You do it in the IDE library manager.

Pete.

Pete:

I think I got the library 0.6.1, and tried it in two ways:

  1. Using IDE "include library- Blynk, which included Blynk.h. (with or without existing library- BlynkSimpleEsp8266.h)
  2. Using “add file” and got into C:\Users\xxxxxxx\Documents\Arduino\libraries\Blynk\src, and found board-specific attempt “BlynkSimpleShieldEsp8266.h” (couldn’t find it using “Include Library”)

Identical behavior as follows:

Exception 0: Illegal instruction
PC: 0x4000e25d
EXCVADDR: 0x00000000

Decoding stack results
0x402042cd: BlynkTimer::run() at C:\Users\xxxxxxx\Documents\Arduino\libraries\Blynk\src\utility\BlynkTimer.cpp line 60
0x40203ff0: setup() at C:\Users\xxxxxxx\Documents\Arduino\libraries\Adafruit_GFX_Library/Adafruit_GFX.h line 141
0x40203c4f: loop() at C:\Users\xxxxxxx\Documents\Arduino\sketch_jun15a/sketch_jun15a.ino line 554
0x4020865c: loop_wrapper() at C:\Users\xxxxxxx\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.0\cores\esp8266\core_esp8266_main.cpp line 201

Many thanks for your thoughts

Pete:

As follow-up to your kind coaching, trying to use the "BlynkSimpleShieldEsp8266.h” library fails, as the LOLIN D1 Mini Pro is not supported.

Studies continue,

I’ve experienced some issues with the D1 Mini/Pro in Beta testing when using dynamic provisioning, and that issue is still open…
ESP8266: Provisioning Failed, had to use workaround · Issue #3 · blynkkk/blynk_Issues · GitHub

Static provisioning does work though.

Pete.

Pete:

Apologies for likely dumb question, but how do you display the “details” in the serial monitor as Blynk
begins loading (Line 406 in your example)

Many thanks

You see that when you have #define BLYNK_PRINT Serial at the top of your sketch.
You also need a Serial.begin(baudrate) command in your void setup of course.

Pete.

Pete:

Major discovery on my side, wherein the reboots seem to have a DIRECT relationship to a properly configured BLYNK Project on my mobile device. Probably also requiring a “unique” authorization code, along with all the widgets called by the program properly assigned to the V-pins.

At this time, and without fiddling with the BLYNK app “buttons”, time reporting and time sync is ticking along without rebooting the MCU. I intend to run that over the evening to assure stability in hardware, current code, et.al.

Following, I will peck along at BLYNK app buttons and code associations and try to eliminate these “exception” crashes.

At least I found the mysterious relationship between the app and the program. Doing exactly what it’s supposed to do !! My bad!!

LA

I don’t really understand what you’re saying in your last post.

Looking at your sketch a little more, it’s clear that there are several things you’re doing that can lead to issues.
I don’t understand what the LedWidget function is meant to achieve, but because it’s being called from your void loop it seems that it may be sending a command to the Blynk server each time it executes, which is every time the void loop is processed.

Use of Blynk.syncAll is lazy, and I’d reccomend you don’t do it.

Your use of individual BLYNK_WRITE(vPin) commands, rather than BLYNK_WRITE_DEFAULT() isn’t an issue in itself, but it makes the code very cluttered and extremely difficult to follow.

Pete.

Pete:

Many thanks for the coaching, I’m studying/implementing the good advice.

Best,
LA

Pete:

With most of your kind suggestions implemented (haven’t got a grip on the BLYNK_WRITE_DEFAULT() yet) and an almost fully functional code & app, I’m back to the original “exception error” when trying to use a non-timer pushbutton (V36 thru V47) to activate relays for a delay time (slider value- V12 thru V23).

Feeling like a “crash dummy” albeit wearing a seat-belt (studying) the “airbag” is bruising my face :slight_smile:

Exception Decoder:

Exception 0: Illegal instruction
PC: 0x4000e25d
EXCVADDR: 0x00000000

Decoding stack results
0x402042c1: BlynkTimer::run() at C:\Users\"REDACTED"\Documents\Arduino\libraries\Blynk\src\utility\BlynkTimer.cpp line 60
0x40201f04: loop() at C:\Users\"REDACTED"\Documents\Arduino\sketch_jun15a/sketch_jun15a.ino line 627
0x4020858c: loop_wrapper() at C:\Users\"REDACTED"\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\3.0.0\cores\esp8266\core_esp8266_main.cpp line 201

Code follows:

[Unformatted code removed by moderator]

@bear1 please format your code correctly, with triple backticks at the beginning and end, otherwise your unformatted code will will be deleted.

I provided a link to my topic about using the MCP23017, which uses BLYNK_WRITE_DEFAULT. Have you studied that example?

Pete.

Pete:

I’m working on that, but in a much less fluffy example until I get it working for so many different “pins”

BTW, I used the code as you suggested, however I noticed there was some scrolling segment between the “select all” copy/paste. Then edited “REDACTED” elements. ??? Sorry!

Many thanks

It’s still not formatted correctly, please try again.

The triple backticks need to be on a line of their own, immediately above and below the sketch.

Pete.

Pete:

With your kind instruction:


/* MODIFIED C:\Users\Redacted\Documents\Arduino\libraries\Blynk\src WidgetTimeInput.h
  replace “-1” of the code “mWeekdays = -1;” by “0”. You can find the code in line “33” of file “WidgetTimeInput.h” (Version: Aug 2016, respectively 14 Sep 2016).*/
#define BLYNK_PRINT Serial
//#define BLYNK_DEBUG

#define WIFI_LED 10

// define configuration (number of switches and number of timers)
#define SWITCH_CNT 12
#define TIME_CNT 12


#include <Blynk.h>
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include <TimeLib.h>
#include <WidgetRTC.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_MCP23017.h>

Adafruit_MCP23017 mcp;

byte mcp_pin_PA0 = 0;
byte mcp_pin_PA1 = 1;
byte mcp_pin_PA2 = 2;
byte mcp_pin_PA3 = 3;
byte mcp_pin_PA4 = 4;
byte mcp_pin_PA5 = 5;
byte mcp_pin_PA6 = 6;
byte mcp_pin_PA7 = 7;
byte mcp_pin_PB0 = 8;
byte mcp_pin_PB1 = 9;
byte mcp_pin_PB2 = 10;
byte mcp_pin_PB3 = 11;
byte mcp_pin_PB4 = 12;
byte mcp_pin_PB5 = 13;
byte mcp_pin_PB6 = 14;
byte mcp_pin_PB7 = 15;

char auth[] = "Redacted";   //LOLIN D1 Mini
char ssid[] = "Redacted";
char pass[] = "Redacted";

#define SCREEN_WIDTH 64 // OLED display width, in pixels
#define SCREEN_HEIGHT 48 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET 0  // Default OLED Shield
Adafruit_SSD1306 display(OLED_RESET);

byte switch_pins[] = {mcp_pin_PA0 , mcp_pin_PA1, mcp_pin_PA2 , mcp_pin_PA3 , mcp_pin_PA4 , mcp_pin_PA5, mcp_pin_PA6 , mcp_pin_PA7 , mcp_pin_PB0 , mcp_pin_PB1, mcp_pin_PB2 , mcp_pin_PB3 }; // number of MCP23017 "gpio" to be used as switch/relay control

bool switch_default[] = {HIGH, HIGH, HIGH, HIGH , HIGH, HIGH, HIGH, HIGH , HIGH, HIGH, HIGH, HIGH}; // switches that use reverse polarity should be set to HIGH here

////////////////////////////////////////////////////////////////////////////////////////
// This code can control up to 12 switches                                            //
// For each switch up to 1 schedule start and end times can be configured - (V0..V11) //
// A default duration can be defined per switch                           - (V12..V23)//
// If an end time is not defined at the app the default duration is used              //
// default duration is also used when manually activating the switch       - (V36..V47)//
// for each switch when start time is reached the switch turns on for the duration    //
//                                                                                    //c
// maximum duration is used for safety (to limit activation time)          - (V24..V35)//
////////////////////////////////////////////////////////////////////////////////////////
int  start_time_sec[SWITCH_CNT][TIME_CNT];     // array of 1 start times (in seconds) for 12 switches [switch number][schedual timer number]
bool start_valid[SWITCH_CNT][TIME_CNT];        // is the start time valid ?
bool weekdays[SWITCH_CNT][TIME_CNT][8];        // array of 8 days (day 0 not used) for each schedual time
int  active_duration[SWITCH_CNT][TIME_CNT + 1]; // duration per switch per time(in sec)
bool end_valid[SWITCH_CNT][TIME_CNT];          // is the end time valid ?
int  max_duration[SWITCH_CNT];                 // max duration per switch
int  auto_off = 1;           // 1 is auto or on

char zone  ;      // HOPEFUL GLOBAL VARIABLE TO DISPLAY ON OLED, I ATTEMPT TO CHANGE VALUE AND ASSIGN TO "ZONE" IN THE "handle switch state" loop

// when activating a switch a timer is set for the configured duration
// when the duration ends the switch is turned off by the timer
// the id of the timer is saved using end_timer_id
// if switch is manually turned off the end_timer_id is used to stop the timer.

// end_timer_id is initialized to 32 (per entry) at the setup section

int end_timer_id[SWITCH_CNT];

// timer object declaration
BlynkTimer timer;

// 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();
}
WidgetLED led1(V55);

bool ledStatus = auto_off;

// V55 LED Widget is ON or OFF
void LedWidget()
{ ledStatus = auto_off;
  if (ledStatus) {
    led1.on();
  } else {
    led1.off();

  }
}

//////////////////////////////////////////////////////////
// get schedual parameters from App                     //
//////////////////////////////////////////////////////////
void set_time(BlynkParam param, byte switch_no, byte time_no) {

  TimeInputParam t(param);
  // Process start 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 switch") + switch_no + String(" time_no: ") + time_no);
    start_time_sec[switch_no][time_no] = param[0].asLong();
    start_valid[switch_no][time_no] = true;
  }
  else
  {
    // Do nothing
    Serial.println(String("No Start Time Given for switch: ") + switch_no + String(" time_no: ") + time_no);
    start_valid[switch_no][time_no] = false;
  }

  //////////////////////////////////////////////////////////////////////////////
  // check if end time is received convert and save it as day time in seconds //
  //////////////////////////////////////////////////////////////////////////////
  if (t.hasStopTime())
  {
    Serial.println(String("Stop: ") +
                   t.getStopHour() + ":" +
                   t.getStopMinute() + ":" +
                   t.getStopSecond());
    Serial.println(String("Stop in sec: ") + param[1].asLong() + String(" for switch") + switch_no + String(" time_no: ") + time_no);

    active_duration[switch_no][time_no] = (param[1].asLong() - start_time_sec[switch_no][time_no]);

    // if end time is smaller than start time this means end time is at next day
    if (active_duration[switch_no][time_no] < 0) active_duration[switch_no][time_no] = 86400 + active_duration[switch_no][time_no];

    Serial.println(String("Stop duration: ") + active_duration[switch_no][time_no]);

    end_valid[switch_no][time_no] = true;
  }
  else // if end time is not defined //
  {
    // Do nothing
    Serial.println(String("No Stop Time Given for switch: ") + switch_no + String(" time_no: ") + time_no);
    end_valid[switch_no][time_no] = false;
  }

  // Process weekdays (1. Mon, 2. Tue, 3. Wed, ...)

  for (int i = 1; i <= 7; i++) {
    if (t.isWeekdaySelected(i)) {
      Serial.println(String("Day ") + i + " is selected");
      weekdays[switch_no][time_no][i] = true;
    }
    else {
      weekdays[switch_no][time_no][i] = false;
    }
  }

  Serial.println();
}

//this reads status of on/off mode and displays on screen
BLYNK_WRITE(V50) {
  int Vpin50 = param.asInt();
  auto_off = Vpin50;
}

// V0..V3 for switch 0, V4..V7 for switch 1 ...
BLYNK_WRITE(V0)  { set_time(param, 0, mcp_pin_PA0);}
BLYNK_WRITE(V1)  { set_time(param, 1, mcp_pin_PA1);}
BLYNK_WRITE(V2)  { set_time(param, 2, mcp_pin_PA2);}
BLYNK_WRITE(V3)  { set_time(param, 3, mcp_pin_PA3);}
BLYNK_WRITE(V4)  { set_time(param, 4, mcp_pin_PA4);}
BLYNK_WRITE(V5)  { set_time(param, 5, mcp_pin_PA5);}
BLYNK_WRITE(V6)  { set_time(param, 6, mcp_pin_PA6);}
BLYNK_WRITE(V7)  { set_time(param, 7, mcp_pin_PA7);}
BLYNK_WRITE(V8)  { set_time(param, 8, mcp_pin_PB0);}
BLYNK_WRITE(V9)  { set_time(param, 9, mcp_pin_PB1);}
BLYNK_WRITE(V10) { set_time(param, 10, mcp_pin_PB2);}
BLYNK_WRITE(V11) { set_time(param, 11, mcp_pin_PB3);}

// use a slider to define default activation duration (slider count in minute)
BLYNK_WRITE(V12) {
  active_duration[mcp_pin_PA0][TIME_CNT] = param.asInt() * 60;
}
BLYNK_WRITE(V13) {
  active_duration[mcp_pin_PA1][TIME_CNT] = param.asInt() * 60;
}
BLYNK_WRITE(V14) {
  active_duration[mcp_pin_PA2][TIME_CNT] = param.asInt() * 60;
}
BLYNK_WRITE(V15) {
  active_duration[mcp_pin_PA3][TIME_CNT] = param.asInt() * 60;
}
BLYNK_WRITE(V16) {
  active_duration[mcp_pin_PA4][TIME_CNT] = param.asInt() * 60;
}
BLYNK_WRITE(V17) {
  active_duration[mcp_pin_PA5][TIME_CNT] = param.asInt() * 60;
}
BLYNK_WRITE(V18) {
  active_duration[mcp_pin_PA6][TIME_CNT] = param.asInt() * 60;
}
BLYNK_WRITE(V19) {
  active_duration[mcp_pin_PA7][TIME_CNT] = param.asInt() * 60;
}
BLYNK_WRITE(V20) {
  active_duration[mcp_pin_PB0][TIME_CNT] = param.asInt() * 60;
}
BLYNK_WRITE(V21) {
  active_duration[mcp_pin_PB1][TIME_CNT] = param.asInt() * 60;
}
BLYNK_WRITE(V22) {
  active_duration[mcp_pin_PB2][TIME_CNT] = param.asInt() * 60;
}
BLYNK_WRITE(V23) {
  active_duration[mcp_pin_PB3][TIME_CNT] = param.asInt() * 60;
}

// use a slider to define default max duration (slider count in minute)   ***HARD CODE THIS*** NL
BLYNK_WRITE(V24) {
  max_duration[mcp_pin_PA0] = param.asInt() * 60;
}
BLYNK_WRITE(V25) {
  max_duration[mcp_pin_PA1] = param.asInt() * 60;
}
BLYNK_WRITE(V26) {
  max_duration[mcp_pin_PA2] = param.asInt() * 60;
}
BLYNK_WRITE(V27) {
  max_duration[mcp_pin_PA3] = param.asInt() * 60;
}
BLYNK_WRITE(V28) {
  max_duration[mcp_pin_PA4] = param.asInt() * 60;
}
BLYNK_WRITE(V29) {
  max_duration[mcp_pin_PA5] = param.asInt() * 60;
}
BLYNK_WRITE(V30) {
  max_duration[mcp_pin_PA6] = param.asInt() * 60;
}
BLYNK_WRITE(V31) {
  max_duration[mcp_pin_PA7] = param.asInt() * 60;
}
BLYNK_WRITE(V32) {
  max_duration[mcp_pin_PB0] = param.asInt() * 60;
}
BLYNK_WRITE(V33) {
  max_duration[mcp_pin_PB1] = param.asInt() * 60;
}
BLYNK_WRITE(V34) {
  max_duration[mcp_pin_PB2] = param.asInt() * 60;
}
BLYNK_WRITE(V35) {
  max_duration[mcp_pin_PB3] = param.asInt() * 60;
}

/////////////////////////////////////////////////////////////////
// Handle switch events (from app or from scheduler )          //
/////////////////////////////////////////////////////////////////

// turn off switch after active duration ends
// duration number is not important here
void turn_off_switch_no_0() { turn_on_off(0, mcp_pin_PA0, 0); Blynk.virtualWrite(V36, 0); Serial.println(String("timer turn off switch 0 ") );}
void turn_off_switch_no_1() { turn_on_off(0, mcp_pin_PA1, 0); Blynk.virtualWrite(V37, 0); Serial.println(String("timer turn off switch 1 ") );}
void turn_off_switch_no_2() { turn_on_off(0, mcp_pin_PA2, 0); Blynk.virtualWrite(V38, 0); Serial.println(String("timer turn off switch 2 ") );}
void turn_off_switch_no_3() { turn_on_off(0, mcp_pin_PA3, 0); Blynk.virtualWrite(V39, 0); Serial.println(String("timer turn off switch 3 ") );}
void turn_off_switch_no_4() { turn_on_off(0, mcp_pin_PA4, 0); Blynk.virtualWrite(V40, 0); Serial.println(String("timer turn off switch 4 ") );}
void turn_off_switch_no_5() { turn_on_off(0, mcp_pin_PA5, 0); Blynk.virtualWrite(V41, 0); Serial.println(String("timer turn off switch 5 ") );}
void turn_off_switch_no_6() { turn_on_off(0, mcp_pin_PA6, 0); Blynk.virtualWrite(V42, 0); Serial.println(String("timer turn off switch 6 ") );}
void turn_off_switch_no_7() { turn_on_off(0, mcp_pin_PA7, 0); Blynk.virtualWrite(V43, 0); Serial.println(String("timer turn off switch 7 ") );}
void turn_off_switch_no_8() { turn_on_off(0, mcp_pin_PB0, 0); Blynk.virtualWrite(V44, 0); Serial.println(String("timer turn off switch 8 ") );}
void turn_off_switch_no_9() { turn_on_off(0, mcp_pin_PB1, 0); Blynk.virtualWrite(V45, 0); Serial.println(String("timer turn off switch 9 ") );}
void turn_off_switch_no_10() { turn_on_off(0, mcp_pin_PB2, 0); Blynk.virtualWrite(V46, 0); Serial.println(String("timer turn off switch 10 ") );}
void turn_off_switch_no_11() { turn_on_off(0, mcp_pin_PB3, 0); Blynk.virtualWrite(V47, 0); Serial.println(String("timer turn off switch 11 ") );}

// handle switch state
void turn_on_off(int on_off, byte switch_no , byte time_no) {
  long active_duration_ms ;
  char Time_print[16];

  if ((on_off == 1) && (auto_off == 1)) //auto_off is a slider in app to shut off the program
  {
    // create time as string to print on activation button
    sprintf(Time_print, "%02d:%02d", hour(), minute());

    // turn on the switch (or off if default is on)
    mcp.digitalWrite(switch_pins[switch_no], !switch_default[switch_no]);

    // if end time is valid use the active duration assigned to this time
    // (change to msec will be done later)
    if (end_valid[switch_no][time_no])
      active_duration_ms = ((long)active_duration[switch_no][time_no]);
    else // otherwise use the general time duration
      active_duration_ms = ((long)active_duration[switch_no][4]);

    // max duration smaller than two min is not valid
    if ( (max_duration[switch_no] < 120) | (max_duration[switch_no] > active_duration_ms) )
      active_duration_ms = active_duration_ms * 1000;
    else
      active_duration_ms = ((long)max_duration[switch_no]) * 1000;

    // if new timer is set before another one ended then disable previous timer
    if (end_timer_id[switch_no] != 32) timer.deleteTimer(end_timer_id[switch_no]);

    // turn on switch and set timer
    switch (switch_no) {
      case 0:
        Blynk.setProperty(V36, "onLabel", String(Time_print));
        Blynk.virtualWrite(V36, 1);
        end_timer_id[mcp_pin_PA0] = timer.setTimeout(active_duration_ms, turn_off_switch_no_0);
        zone = 1;
        break;
      case 1:
        Blynk.setProperty(V37, "onLabel", String(Time_print));
        Blynk.virtualWrite(V37, 1);
        end_timer_id[mcp_pin_PA1] = timer.setTimeout(active_duration_ms, turn_off_switch_no_1);
        zone = 2;
        break;
      case 2:
        Blynk.setProperty(V38, "onLabel", String(Time_print));
        Blynk.virtualWrite(V38, 1);
        end_timer_id[mcp_pin_PA2] = timer.setTimeout(active_duration_ms, turn_off_switch_no_2);
        zone = 3;
        break;
      case 3:
        Blynk.setProperty(V39, "onLabel", String(Time_print));
        Blynk.virtualWrite(V39, 1);
        end_timer_id[mcp_pin_PA3] = timer.setTimeout(active_duration_ms, turn_off_switch_no_3);
        zone = 4;
        break;
      case 4:
        Blynk.setProperty(V40, "onLabel", String(Time_print));
        Blynk.virtualWrite(V40, 1);
        end_timer_id[mcp_pin_PA4] = timer.setTimeout(active_duration_ms, turn_off_switch_no_4);
        zone = 5;
        break;
      case 5:
        Blynk.setProperty(V41, "onLabel", String(Time_print));
        Blynk.virtualWrite(V41, 1);
        end_timer_id[mcp_pin_PA5] = timer.setTimeout(active_duration_ms, turn_off_switch_no_5);
        zone = 6;
        break;
      case 6:
        Blynk.setProperty(V42, "onLabel", String(Time_print));
        Blynk.virtualWrite(V42, 1);
        end_timer_id[mcp_pin_PA6] = timer.setTimeout(active_duration_ms, turn_off_switch_no_6);
        zone = 7;
        break;
      case 7:
        Blynk.setProperty(V43, "onLabel", String(Time_print));
        Blynk.virtualWrite(V43, 1);
        end_timer_id[mcp_pin_PA7] = timer.setTimeout(active_duration_ms, turn_off_switch_no_7);
        zone = 8;
        break;
      case 8:
        Blynk.setProperty(V44, "onLabel", String(Time_print));
        Blynk.virtualWrite(V44, 1);
        end_timer_id[mcp_pin_PB0] = timer.setTimeout(active_duration_ms, turn_off_switch_no_8);
        zone = 9;
        break;
      case 9:
        Blynk.setProperty(V45, "onLabel", String(Time_print));
        Blynk.virtualWrite(V45, 1);
        end_timer_id[mcp_pin_PB1] = timer.setTimeout(active_duration_ms, turn_off_switch_no_9);
        zone = 10;
        break;
      case 10:
        Blynk.setProperty(V46, "onLabel", String(Time_print));
        Blynk.virtualWrite(V46, 1);
        end_timer_id[mcp_pin_PB2] = timer.setTimeout(active_duration_ms, turn_off_switch_no_10);
        zone = 11;
        break;
      case 11:
        Blynk.setProperty(V47, "onLabel", String(Time_print));
        Blynk.virtualWrite(V47, 1);
        end_timer_id[mcp_pin_PB3] = timer.setTimeout(active_duration_ms, turn_off_switch_no_11);
        zone = 12;
        break;
    }

    Serial.println(String("turn ON switch: ") + switch_no + String(" for duration: ") + active_duration_ms / 60000 + String("min "));
  }
  else
  {
    mcp.digitalWrite(switch_pins[switch_no], switch_default[switch_no]);
    timer.deleteTimer(end_timer_id[switch_no]);
    end_timer_id[switch_no] = 32;
    Serial.println(String("turn OFF switch: ") + switch_no);

    zone = '\0';
  }
}

// set switch state from APP

BLYNK_WRITE(V36) {
  turn_on_off(param.asInt(), mcp_pin_PA0, TIME_CNT);
}
BLYNK_WRITE(V37) {
  turn_on_off(param.asInt(), mcp_pin_PA1, TIME_CNT);
}
BLYNK_WRITE(V38) {
  turn_on_off(param.asInt(), mcp_pin_PA2, TIME_CNT);
}
BLYNK_WRITE(V39) {
  turn_on_off(param.asInt(), mcp_pin_PA3, TIME_CNT);
}
BLYNK_WRITE(V40) {
  turn_on_off(param.asInt(), mcp_pin_PA4, TIME_CNT);
}
BLYNK_WRITE(V41) {
  turn_on_off(param.asInt(), mcp_pin_PA5, TIME_CNT);
}
BLYNK_WRITE(V42) {
  turn_on_off(param.asInt(), mcp_pin_PA6, TIME_CNT);
}
BLYNK_WRITE(V43) {
  turn_on_off(param.asInt(), mcp_pin_PA7, TIME_CNT);
}
BLYNK_WRITE(V44) {
  turn_on_off(param.asInt(), mcp_pin_PB0, TIME_CNT);
}
BLYNK_WRITE(V45) {
  turn_on_off(param.asInt(), mcp_pin_PB1, TIME_CNT);
}
BLYNK_WRITE(V46) {
  turn_on_off(param.asInt(), mcp_pin_PB2, TIME_CNT);
}
BLYNK_WRITE(V47) {
  turn_on_off(param.asInt(), mcp_pin_PB3, TIME_CNT);
}

/////////////////////////////////////////////////////////////////////////////////////////////
// the following function is called every 60 seconds by a timer                            //
//  the function checks if a start time is reached and if yes it will call                 //
//   the turn_on_off function (to turn on the switch and set timer for turning off switch) //
/////////////////////////////////////////////////////////////////////////////////////////////
void activetoday() {        // check if schedule #1 should run today

  if (Blynk.connected()) // set wifi led if no connection
  {
    mcp.digitalWrite(WIFI_LED, LOW);
  }
  else
  {
    mcp.digitalWrite(WIFI_LED, HIGH);
  }

  if (year() != 1970) {
    unsigned int nowseconds = ((hour() * 3600) + (minute() * 60) + second());
    int dayadjustment = -1;
    if (weekday() == 1) {
      dayadjustment = 6; // needed for Sunday Time library is day 1 and Blynk is day 7
    }

    for (int switch_cnt = 0;  switch_cnt < SWITCH_CNT; switch_cnt++) {
      for (int timer_cnt = 0;  timer_cnt < TIME_CNT; timer_cnt++) {
        if (start_valid[switch_cnt][timer_cnt] == true) {
          if (weekdays[switch_cnt][timer_cnt][weekday() + dayadjustment] == true) {
            if (nowseconds >= start_time_sec[switch_cnt][timer_cnt]) {
              if (nowseconds < start_time_sec[switch_cnt][timer_cnt] + 10) {

                turn_on_off(1, switch_cnt, timer_cnt);
                Serial.println(String("turn ON switch: ") + switch_cnt);

              }
            }
          }
        }
      }
    }
  }
}

/////////////////////////////////////

// Digital clock display of the time
void clockDisplay()
{
  // You can call hour(), minute(), ... at any time
  // Please see Time library examples for details

  String currentTime = String(hour()) + ":" + minute() + ":" + second();
  String currentDate = String(month()) + "." + day() + "." + year();
  Serial.print("Current time: ");
  Serial.print(currentTime);
  Serial.print(" ");
  Serial.print(currentDate);
  Serial.println();

  // Send DATE and DATE to Blynk APP
  Blynk.virtualWrite(V101, currentTime);
  // Send date to the App--Not used but is available--this will show processor date if it is important to you
  Blynk.virtualWrite(V102, currentDate);
}

void OLED_SSD1306() 
{
      // clear display
      display.clearDisplay();

      //display
      display.setTextSize(1);
      display.setCursor(0, 0);
      display.print("TIME");
      display.setTextSize(1);
      display.setCursor(0, 10);
      display.print(String(hour()) + ":" + minute() + ":" + second());
      display.print(" ");
      display.setTextSize(1);
      display.setCursor(0, 30);
      display.print("ZONE");
      display.setTextSize(2);
      display.setCursor(34, 25);
      display.printf("%2d", zone);
      display.display();
}

void setup() {

  //  Wire.begin(0,2); //SDA, SCL

  Serial.begin(115200);

  // Initialize the MCP23017
  mcp.begin(7);      // use hardware default address 7

  mcp.pinMode(mcp_pin_PA0, OUTPUT);
  mcp.pullUp(mcp_pin_PA0, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA0, HIGH);

  mcp.pinMode(mcp_pin_PA1, OUTPUT);
  mcp.pullUp(mcp_pin_PA1, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA1, HIGH);

  mcp.pinMode(mcp_pin_PA2, OUTPUT);
  mcp.pullUp(mcp_pin_PA2, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA2, HIGH);

  mcp.pinMode(mcp_pin_PA3, OUTPUT);
  mcp.pullUp(mcp_pin_PA3, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA3, HIGH);

  mcp.pinMode(mcp_pin_PA4, OUTPUT);
  mcp.pullUp(mcp_pin_PA4, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA4, HIGH);

  mcp.pinMode(mcp_pin_PA5, OUTPUT);
  mcp.pullUp(mcp_pin_PA5, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA5, HIGH);

  mcp.pinMode(mcp_pin_PA6, OUTPUT);
  mcp.pullUp(mcp_pin_PA6, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA6, HIGH);

  mcp.pinMode(mcp_pin_PA7, OUTPUT);
  mcp.pullUp(mcp_pin_PA7, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PA7, HIGH);

  mcp.pinMode(mcp_pin_PB0, OUTPUT);
  mcp.pullUp(mcp_pin_PB0, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PB0, HIGH);

  mcp.pinMode(mcp_pin_PB1, OUTPUT);
  mcp.pullUp(mcp_pin_PB1, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PB1, HIGH);

  mcp.pinMode(mcp_pin_PB2, OUTPUT);
  mcp.pullUp(mcp_pin_PB2, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PB2, HIGH);

  mcp.pinMode(mcp_pin_PB3, OUTPUT);
  mcp.pullUp(mcp_pin_PB3, HIGH);  // turn on a 100K pullup internally
  mcp.digitalWrite(mcp_pin_PB3, HIGH);

  // reset output pins used and system variables
  for (int i = 0; i < SWITCH_CNT ; i++) {

    mcp.digitalWrite(switch_pins[i], switch_default[i]); // set switch to default
    end_timer_id[i] = 32;            // reset end timer id's

    for (int j = 0; j < TIME_CNT ; j++)
    {
      end_valid[i][j] = false;       // reset end valid array
    }
  }

  mcp.pinMode(WIFI_LED, OUTPUT);

  Blynk.begin(auth, ssid, pass);
  rtc.begin();  // Begin synchronizing time
  timer.setInterval(10000L, clockDisplay); // check every 10s
  timer.setInterval(10000L, activetoday);  // check every 10s if ON / OFF trigger time has been reached
  setSyncInterval(10 * 60); // Sync interval in seconds (10 minutes)

  Blynk.syncVirtual(V50, V36, V37, V38, V39, V40, V41, V42, V43, V44, V45, V46, V47);

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 64x48)
  delay(2000);
  display.clearDisplay();
  display.setTextColor(WHITE);

  LedWidget();
}

void loop()
{
  timer.run(); 
  OLED_SSD1306();

}