Pete:
Hopefully I modified embedded code as you kindly requested. (very new here, sorry)
LA
Pete:
I am challenged with modifying a 4-channel relay project in hopes of expanding to 12- channels using ESP8266-01 and MCP23017 port expander. Using your guidance, I have attempted to declare the MCP23017 and “pin” numbers/names to a well functioning scheduler/controller (using only the original 4-channels for now), setting up for a total of 12-channels later).
Using the ESP-01, it’s a bit difficult to do much serial monitor tracking, but I have tested other sketches and I am certain all the hardware/wiring is fully functional, using only LED’s to observe the output states of selected pin on the MCP23017.
My feeble attempts compile fine, with few warnings of library issues, so I hope the problem is simple (so I can handle it )
Your kind guidance is appreciated:
CODE FOLLOWS:
/* MODIFIED C:\Users\REDACTED\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 4
#define TIME_CNT 4
#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 auth[] = "REDACTED"; //ESP8266-01 with MCP23017 port expander
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 15 // GPIO15
Adafruit_SSD1306 display(OLED_RESET);
byte switch_pins[] = {mcp_pin_PA0 , mcp_pin_PA1, mcp_pin_PA2 , mcp_pin_PA3}; // number of MCP23017 "gpio" to be used as switch/relay control
bool switch_default[] = {HIGH, HIGH, HIGH, HIGH}; // switches that use reverse polarity should be set to HIGH here
////////////////////////////////////////////////////////////////////////////////////////
// This code can control up to 4 switches //
// For each switch up to 4 schedule start and end times can be configured - (V0..V15) //
// A default duration can be defined per switch - (V16..V19)//
// 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 - (V20..V23)//
// 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..V27)//
////////////////////////////////////////////////////////////////////////////////////////
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, 0, mcp_pin_PA1);
}
BLYNK_WRITE(V2) {
set_time(param, 0, mcp_pin_PA2);
}
BLYNK_WRITE(V3) {
set_time(param, 0, mcp_pin_PA3);
}
BLYNK_WRITE(V4) {
set_time(param, 1, mcp_pin_PA0);
}
BLYNK_WRITE(V5) {
set_time(param, 1, mcp_pin_PA1);
}
BLYNK_WRITE(V6) {
set_time(param, 1, mcp_pin_PA2);
}
BLYNK_WRITE(V7) {
set_time(param, 1, mcp_pin_PA3);
}
BLYNK_WRITE(V8) {
set_time(param, 2, mcp_pin_PA0);
}
BLYNK_WRITE(V9) {
set_time(param, 2, mcp_pin_PA1);
}
BLYNK_WRITE(V10) {
set_time(param, 2, mcp_pin_PA2);
}
BLYNK_WRITE(V11) {
set_time(param, 2, mcp_pin_PA3);
}
BLYNK_WRITE(V12) {
set_time(param, 3, mcp_pin_PA0);
}
BLYNK_WRITE(V13) {
set_time(param, 3, mcp_pin_PA1);
}
BLYNK_WRITE(V14) {
set_time(param, 3, mcp_pin_PA2);
}
BLYNK_WRITE(V15) {
set_time(param, 3, mcp_pin_PA3);
}
// use a slider to define default activation duration (slider count in minute)
BLYNK_WRITE(V16) {
active_duration[mcp_pin_PA0][TIME_CNT] = param.asInt() * 60;
}
BLYNK_WRITE(V17) {
active_duration[mcp_pin_PA1][TIME_CNT] = param.asInt() * 60;
}
BLYNK_WRITE(V18) {
active_duration[mcp_pin_PA2][TIME_CNT] = param.asInt() * 60;
}
BLYNK_WRITE(V19) {
active_duration[mcp_pin_PA3][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;
}
/////////////////////////////////////////////////////////////////
// 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(V20, 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(V21, 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(V22, 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(V23, 0);
Serial.println(String("timer turn off switch 3 ") );
}
// 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(V20, "onLabel", String(Time_print));
Blynk.virtualWrite(V20, 1);
end_timer_id[mcp_pin_PA0] = timer.setTimeout(active_duration_ms, turn_off_switch_no_0);
zone = 1;
break;
case 1:
Blynk.setProperty(V21, "onLabel", String(Time_print));
Blynk.virtualWrite(V21, 1);
end_timer_id[mcp_pin_PA1] = timer.setTimeout(active_duration_ms, turn_off_switch_no_1);
zone = 2;
break;
case 2:
Blynk.setProperty(V22, "onLabel", String(Time_print));
Blynk.virtualWrite(V22, 1);
end_timer_id[mcp_pin_PA2] = timer.setTimeout(active_duration_ms, turn_off_switch_no_2);
zone = 3;
break;
case 3:
Blynk.setProperty(V23, "onLabel", String(Time_print));
Blynk.virtualWrite(V23, 1);
end_timer_id[mcp_pin_PA3] = timer.setTimeout(active_duration_ms, turn_off_switch_no_3);
zone = 4;
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(V20) {
turn_on_off(param.asInt(), mcp_pin_PA0, TIME_CNT);
}
BLYNK_WRITE(V21) {
turn_on_off(param.asInt(), mcp_pin_PA1, TIME_CNT);
}
BLYNK_WRITE(V22) {
turn_on_off(param.asInt(), mcp_pin_PA2, TIME_CNT);
}
BLYNK_WRITE(V23) {
turn_on_off(param.asInt(), mcp_pin_PA3, 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 setup() {
Serial.begin(115200);
// Initialize the MCP23017
mcp.begin(7); // use hardware default address 7
mcp.pinMode(mcp_pin_PA0, OUTPUT);
mcp.pinMode(mcp_pin_PA1, OUTPUT);
mcp.pinMode(mcp_pin_PA2, OUTPUT);
mcp.pinMode(mcp_pin_PA3, OUTPUT);
mcp.digitalWrite(mcp_pin_PA0, HIGH);
mcp.digitalWrite(mcp_pin_PA1, HIGH);
mcp.digitalWrite(mcp_pin_PA2, HIGH);
mcp.digitalWrite(mcp_pin_PA3, 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);
timer.setInterval(60000L, activetoday); // check every 60s if ON / OFF trigger time has been reached
setSyncInterval(10 * 60); // Sync interval in seconds (10 minutes)
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3C (for the 64x48)
delay(2000);
display.clearDisplay();
display.setTextColor(WHITE);
}
void loop()
{
Blynk.run();
timer.run();
LedWidget();
// 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(3);
display.setCursor(40, 25);
display.printf("%d", zone);
display.display();
}