Advanced Time Input Blynk

Hi! I’m try to get this to work on a particle argon: https://community.blynk.cc/t/time-input-widget-and-eventor/14868/17

Here’s what I’ve got so far:

// This #include statement was automatically added by the Particle IDE.
#include <SparkTime.h>
UDP UDPClient;
SparkTime rtc;

 #include <blynk.h>
char auth[] = "***"; 
BlynkTimer timer;
WidgetTerminal terminal(V30);

//WidgetRTC rtc;

// Zone valves
#define valve1 3
#define valve2 4
#define valve3 5
#define valve4 7
#define valve5 8
int mode = 999;
int manuel1 = 0;
int lastmanuel1 = 3;
int auto1 = 0;
int lastauto1 = 3;
//int counts = 0;

#define GREEN     "#008000"//#23C48E"
#define BLUE      "#04C0F8"
#define YELLOW    "#ED9D00"
#define RED       "#FF033E" // "#D3435C"
#define DARK_BLUE "#5F7CD8"

WidgetLED led1(V1);

BLYNK_WRITE(V0) {
  switch (param.asInt())
  {
    case 1: // OFF
      //Serial.println("Item 1 selected");
      mode = 1;
      Blynk.virtualWrite(V25, mode);
      shutoffall();
      //led1.setColor(BLYNK_RED);
      break;
    case 2: // Manual Mode
      Serial.println("Item 2 selected");
      mode = 2;
      Blynk.virtualWrite(V25, mode);
      //led1.setColor(BLYNK_GREEN);
      break;
    case 3: // Automatic Mode
      Serial.println("Item 3 selected");
      mode = 3;
      Blynk.virtualWrite(V25, mode);
      break;
    default:
      Serial.println("Unknown item selected");
  }
}

BLYNK_WRITE(V11)
{
  manuel1 = param.asInt();
  if (mode == 2 || mode == 3)
  {
    if (manuel1 == 1)
    {
      digitalWrite(valve1, LOW);
      led1.on();
      led1.setColor(GREEN);
    }
    else
    {
      digitalWrite(valve1, HIGH);
      led1.on();
      led1.setColor(RED);
    }
  }
  if (mode == 1)
  {
      Blynk.virtualWrite(V11, 0);
      Blynk.notify("Select Manual or Auto Mode");
  }
}

BLYNK_WRITE(V21) {   // Scheduler #1 Time Input widget  
  TimeInputParam t(param);
  unsigned int nowseconds = ((hour() * 3600) + (minute() * 60) + second());
  unsigned int startseconds = (t.getStartHour() * 3600) + (t.getStartMinute() * 60);  
  unsigned int stopseconds = (t.getStopHour() * 3600) + (t.getStopMinute() * 60);
  int dayadjustment = -1;  
  if(weekday() == 1){
    dayadjustment = 6; // needed for Sunday Time library is day 1 and Blynk is day 7
  }
  if(t.isWeekdaySelected((weekday() + dayadjustment))){ //Time library starts week on Sunday, Blynk on Monday  
    //Schedule is ACTIVE today 
    if(nowseconds >= startseconds - 31 && nowseconds <= startseconds + 31 ){    // 62s on 60s timer ensures 1 trigger command is sent
      Blynk.virtualWrite(V0, 255);  // turn on virtual LED
      Serial.println("Schedule 1 started");
    }                  
    if(nowseconds >= stopseconds - 31 && nowseconds <= stopseconds + 31 ){   // 62s on 60s timer ensures 1 trigger command is sent
      Blynk.virtualWrite(V0, 0);   // turn OFF virtual LED
      Serial.println("Schedule 1 finished");
    }               
  }
}

/*BLYNK_WRITE(V21) {  // Called whenever setting Time Input Widget
  TimeInputParam t(param);
  int SThour = t.getStartHour();
  int STmin = t.getStartMinute();
  int STsec = t.getStartSecond();
  int SPhour = t.getStopHour();
  int SPmin = t.getStopMinute();
  int SPsec = t.getStopSecond();
  int now = millis();
  Blynk.virtualWrite(V25, now);
  terminal.println(SThour);
}*/


void setup() 
{
    Blynk.begin(auth);
    rtc.begin(&UDPClient, "north-america.pool.ntp.org");
    rtc.setTimeZone(-5);
    timer.setInterval(60000L, activetoday);  // check every 60s if ON / OFF trigger time has been reached
    timer.setInterval(1000L, clockDisplay);  // check every second if time has been obtained from the server
    //timer.setInterval(5000L, sendinfo);
    pinMode(valve1, OUTPUT);

}

void loop()
{
  Blynk.run();
  timer.run();
  
}

void shutoffall()
{
    //shut it all down
}


void activetoday(){         // check if schedule #1 should run today
  if(year() != 1970){
    Blynk.syncVirtual(V1);  // sync scheduler #1  
  }
}

void clockDisplay(){  // only needs to be done once after time sync
  if((year() != 1970) && (clockSync == false)){ 
    sprintf(currentTime, "%02d:%02d:%02d", hour(), minute(), second());
    Serial.println(currentTime);
    clockSync = true;
  } 
}    

My problem: sprinklersystem.ino:88:36: ‘hour’ was not declared in this scope
Where can I go to learn about library’s and how they work? Or is that not my problem here?
I’ve tried the TimeLib.h , but couldn’t get it to work and blynk’s time library and RTCWidget doesn’t seem to be available on particle web IDE.

Thanks for taking the time to read through this!

1 Like

Normally you won’t need SparkTime you can just use Time

Since you are using hour() (with parentheses) this must be a function and with the Time object it would be Time.hour() (same for minute() and second()).

And instead of doing this calculation

  unsigned int nowseconds = ((hour() * 3600) + (minute() * 60) + second());

just use

 unsigned int nowseconds = Time.local() % 86400;

Time.local() will give you the epoch time adjusted for your time zone (when you’ve set Time.zone() appropriately) and the modulo operation (%) will only take the seconds of the current day and discard the date part.

1 Like

Thanks for your help @ScruffR!! I’m getting somewhere now. I’ve got the time input turning on and off a relay now. One thing I don’t quit understand yet is why I’m getting the following:
With:

    Time.zone(-5);   //-2.5

This returns the correct seconds:

unsigned int nowseconds = Time.local() % 86400;

But this returns 5 hours to early:

Blynk.virtualWrite(V100, Time.format(Time.local(), "%r - %a %D"));

The only way to make this read out correct current time is to have -2.5 in time zone.
Any idea how I could get these 2 functions to play along with each other?
Complete Code:

#include <blynk.h>
char auth[] = "***"; 
BlynkTimer timer;
char currentTime[9];
bool clockSync = false;
WidgetTerminal terminal(V30);

// Zone valves
#define valve1 3
#define valve2 4
#define valve3 5
#define valve4 7
#define valve5 8
int mode = 999;
int manuel1 = 0;
int lastmanuel1 = 3;
int auto1 = 0;
int lastauto1 = 3;
//int counts = 0;

#define GREEN     "#008000"//#23C48E"
#define BLUE      "#04C0F8"
#define YELLOW    "#ED9D00"
#define RED       "#FF033E" // "#D3435C"
#define DARK_BLUE "#5F7CD8"

WidgetLED led1(V1);

BLYNK_WRITE(V0) {
  switch (param.asInt())
  {
    case 1: // OFF
      //Serial.println("Item 1 selected");
      mode = 1;
      Blynk.virtualWrite(V25, mode);
      shutoffall();
      //led1.setColor(BLYNK_RED);
      break;
    case 2: // Manual Mode
      Serial.println("Item 2 selected");
      mode = 2;
      Blynk.virtualWrite(V25, mode);
      //led1.setColor(BLYNK_GREEN);
      break;
    case 3: // Automatic Mode
      Serial.println("Item 3 selected");
      mode = 3;
      Blynk.virtualWrite(V25, mode);
      break;
    default:
      Serial.println("Unknown item selected");
  }
}

BLYNK_WRITE(V11)
{
  manuel1 = param.asInt();
  if (mode == 2 || mode == 3)
  {
    if (manuel1 == 1)
    {
      digitalWrite(valve1, LOW);
      led1.on();
      led1.setColor(GREEN);
    }
    else
    {
      digitalWrite(valve1, HIGH);
      led1.on();
      led1.setColor(RED);
    }
  }
  if (mode == 1)
  {
      Blynk.virtualWrite(V11, 0);
      Blynk.notify("Select Manual or Auto Mode");
  }
}

BLYNK_WRITE(V21) {   // Scheduler #1 Time Input widget  
  TimeInputParam t(param);
  //unsigned int nowseconds = ((hour() * 3600) + (minute() * 60) + second());
  unsigned int nowseconds = Time.local() % 86400;
  Blynk.virtualWrite(V25, nowseconds);
  unsigned int startseconds = (t.getStartHour() * 3600) + (t.getStartMinute() * 60);  
  unsigned int stopseconds = (t.getStopHour() * 3600) + (t.getStopMinute() * 60);
  Blynk.virtualWrite(V26, startseconds);
  int dayadjustment = -1;  
  if(Time.weekday() == 1){
    dayadjustment = 6; // needed for Sunday Time library is day 1 and Blynk is day 7
  }
  if(t.isWeekdaySelected((Time.weekday() + dayadjustment))){ //Time library starts week on Sunday, Blynk on Monday  
    //Schedule is ACTIVE today 
    if(nowseconds >= startseconds - 31 && nowseconds <= startseconds + 31 ){    // 62s on 60s timer ensures 1 trigger command is sent
      led1.setColor(BLUE);
      digitalWrite(valve1, LOW);
      Blynk.virtualWrite(V9, 255);  // turn on virtual LED
      Blynk.notify("Schedule 1 started");
    }                  
    if(nowseconds >= stopseconds - 31 && nowseconds <= stopseconds + 31 ){   // 62s on 60s timer ensures 1 trigger command is sent
      led1.setColor(YELLOW);
      digitalWrite(valve1, HIGH);
      Blynk.virtualWrite(V9, 0);   // turn OFF virtual LED
      Blynk.notify("Schedule 1 finished");
    }               
  }
}

/*BLYNK_WRITE(V21) {  // Called whenever setting Time Input Widget
  TimeInputParam t(param);
  int SThour = t.getStartHour();
  int STmin = t.getStartMinute();
  int STsec = t.getStartSecond();
  int SPhour = t.getStopHour();
  int SPmin = t.getStopMinute();
  int SPsec = t.getStopSecond();
  int now = millis();
  Blynk.virtualWrite(V25, now);
  terminal.println(SThour);
}*/


void setup() 
{
    Blynk.begin(auth);
    Time.zone(-5);   //-2.5
    timer.setInterval(60000L, activetoday);  // check every 60s if ON / OFF trigger time has been reached
    timer.setInterval(1000L, clockDisplay);  // check every second if time has been obtained from the server
    timer.setInterval(5000L, sendinfo);
    pinMode(valve1, OUTPUT);

}

void loop()
{
  Blynk.run();
  timer.run();
  
}

void sendinfo()
{
    Blynk.virtualWrite(V100, Time.format(Time.local(), "%r - %a %D"));
}

void shutoffall()
{
    //shut it all down
}


void activetoday(){         // check if schedule #1 should run today
  if(Time.year() != 1970){
    Blynk.syncVirtual(V21);  // sync scheduler #1  
  }
}

void clockDisplay(){  // only needs to be done once after time sync
  if((Time.year() != 1970) && (clockSync == false)){ 
    sprintf(currentTime, "%02d:%02d:%02d", Time.hour(), Time.minute(), Time.second());
    Serial.println(currentTime);
    clockSync = true;
  } 
} 

Blynk Setup:

1 Like

I wonder if this would work? or if it’ll cause other problems

  unsigned int nowseconds = Time.local() % 86400;
  nowseconds = nowseconds - 9000;

and Time.format() will again localize that.
If you only want Time.format() to format the current time then don’t pass in any time parameter.
Try this instead

Blynk.virtualWrite(V100, Time.format("%r - %a %D"));

On the other hand, if you need to formate a specific time (other than current), then you need to take the UTC time (via Time.now()) do your calculations with it and then format it.
Or when taking a timestamp from another source, you need to convert it into UTC before formatting (or as a workaround temporarily reset Time.zone(0.0), format and set the zone again).

2 Likes

Thanks @ScruffR and @lsfarm… . I gave been trying to get the complex timer to work before but have not been able to, so I have been using the simple timer instead.

I will see if I can manage from what I see here.

@lsfarm, quick question, are you using the free BLYNK version or the paid version?

Regards
Friedl.

I’ll give that a try @ScruffR. Thanks!!

@friedl_1977 I’m using the free version of Blynk

@lsfarm

Thanks… me too. I am thinking of signing up for the startup license at $166 as I need to delay some products is simply not possible using the free license.

I also looked into Nymea and IXON, both look feasible and aesthetically pleasing, but will require more a bot work to get going. You will go much further though on their free versions though.

Regards
Friedl.

Interesting links… Thanks!
I’ve seen blynk hint around at a cheaper “individual” license, but it will probably be awhile before anything happens on that note.
It’s kinda the pits for us guys stuck in the middle with ideas on how to solve a problem, but not quite enough resources to build a full blown Blynk, Particle system of our own.

Hi @lsfarm -

UX has been my challenge from the get go, I simply do not understand the pricing models. Blynk is tough, I have been trying to reason with them but to no avail.

Do you require mobile app? I have found Ubidots to work well if web dashboard is required. I need both from time to time, hence my appeal towards Nymea, but seem quite a bit of work to get off the ground. I signed up for some new beta at Blynk but have not heard back yet.

@friedl_1977, yes, all I’m needing for this sprinkler system is an app. But I’m working on a larger project that will require both, so I’ll certainly look into Ubidots.
Anyhow thanks to @ScruffR help on the time problem I’ve got this working now: Automatic Yard Sprinkler System

2 Likes

I looked at your code over at you the Blynk forum and there would be some relatively simple tweaks to make it more professional :wink:

e.g. use arrays and loops instead of dedicated variablenames (e.g. lastauto1) or #define statements.
Extract the common logic from the BLYNK_WRITE() functions in to a “helper” function and call that from the original function.

A rough estimate would suggest that your code would be about 250 lines (less than half of the current length) - with the benefit that you can add a few more valves without any extra code :wink:

BTW, why are you using BlynkTimer and not the inbuilt Software Timers? (Update: As I found out, because for some Blynk calls the stack is too small on SW Timers)
Also why do you have V90 and V103 for alertStatus? Isn’t one virtual pin enough?

1 Like

HI @lsfarm -

Unfortunately at this time, Ubidots does not have a scheduler so you will have to build the widget yourself. Hence my looking into Blynk. Aside from that I found Ubidots to work well.

Scruff and couple of other guys in here very helpful, they have helped me a lot and have shown great patience :rofl:

Best of luck!!

1 Like

Great idea @ScruffR!!
I’ve been working on this and have managed to get the pinMode assignments into an array (I think) but I’m stuck here:

 void manualFunction()
{
  if (mode == 2 || mode == 3)
  {
    if (AppButton == 1)
    {
      digitalWrite(valve[0], LOW);  // how to change this to valve[1] for zone 2??
      led1.setColor(GREEN);         //how to chage this to led2.
      Blynk.virtualWrite(V100, "Zone 1 Turning On"); //and change Zone 1 to whatever the zoneArray names each Zone??
    }
    else
    {
      digitalWrite(valve[0], HIGH);
      led1.setColor(RED);
      Blynk.virtualWrite(V100, "Zone 1 Turning Off");
    }
  }
  if (mode == 1)
  {
    Blynk.virtualWrite(V11, 0);
    Blynk.notify("Select Manual or Auto Mode");
  }
}
}

BLYNK_WRITE(V11)
{
  AppButton = param.asInt();
  zone = 1;
  manualFunction();
}
BLYNK_WRITE(V12)
{
  AppButton = param.asInt();
  zone = 2;
  manualFunction();
}

I currently have 5 zones on V11-V15 and I’d really like to figure out how to do this so, like you said, I could quickly and easily take away or add to the zones.
So… I’m wondering how does manualFunction() figure out which button called it? There an easier way than zone = 1;?
and than once in manualFunction how do I get this bit right?

      digitalWrite(valve[0], LOW);  // how to change this to valve[1] for zone 2??
      led1.setColor(GREEN);         //how to chage this to led2.
      Blynk.virtualWrite(V100, "Zone 1 Turning On"); //and change Zone 1 to whatever the zoneArray names each Zone??

You can have a look at this and test if that works as expected
https://go.particle.io/shared_apps/5ece5460bd1e340007bc9cf1

/////////************* **********/////////
//          Blynk Assignments           //
/////////************* **********/////////
/*
V0   - Mode Switch
VX   - Status LED :: Not working correctly, won't switch colors with app closed
V1X  - Manual Switch
V2X  - Auto Switch :: Time Input Channel
V3X  - Actual Valve State
V4X  - Channel Start Seconds
V5X  - Channel Stop Seconds
V90  - Blynk App Notifications Enable/Disable
V100 - Controller Status
V101 - secNow
V102 - Mode on Controller 1=OFF 2=Manual 3=Auto
V103 - NotifyStatus : for debugging will delete
V105 - WiFi Signal Strength
Thanks to @Costas:
https://community.blynk.cc/t/time-input-widget-and-eventor/14868/16
*/
#include <blynk.h>

const char   *auth       = "****";
const char   *GREEN      = "#008000"; //"#23C48E"
const char   *BLUE       = "#04C0F8";
const char   *YELLOW     = "#ED9D00";
const char   *RED        = "#FF033E"; //"#D3435C"
const char   *DARK_BLUE  = "#5F7CD8";

enum  MODE { off = 1, manual = 2, automatic = 3, unknown = 999 };

MODE         mode        = unknown;
unsigned int secNow      = 86400;
int          alertStatus = off;  //EEPROM location 1

struct valve_t {
  int        pin;
  bool       manual; // false .. automatic
  bool       state;
  WidgetLED  led;
};
const int nValves = 5;                                      // number of valves
valve_t valve[nValves] = 
{ { D2, false, LOW, WidgetLED(V1) }
, { D3, false, LOW, WidgetLED(V2) }
, { D4, false, LOW, WidgetLED(V3) }
, { D5, false, LOW, WidgetLED(V4) }
, { D6, false, LOW, WidgetLED(V5) }
};

// Software Timers have too limited stack for the calls
//Timer timActivity(60000L, activeToday);                   // check every 60s if ON / OFF trigger time has been reached
//Timer timClock(1000L,  clockDisplay);                     // check every second if time has been obtained from the server
//Timer timInfo(1000L, sendInfo);
BlynkTimer timer;

void         setMode(MODE m);

BLYNK_WRITE(V90)
{
  alertStatus = param.asInt();
  EEPROM.put(50, alertStatus);
}

/////////************* **********/////////
//             Mode Selection           //
/////////************* **********/////////

BLYNK_WRITE(V0) {
  switch (mode = (MODE)param.asInt())
  {
    case off:
      Blynk.virtualWrite(V100, "Mode: OFF");
      break;
    case manual: 
      Blynk.virtualWrite(V100, "Mode: Manual");
      break;
    case automatic: 
      Blynk.virtualWrite(V100, "Mode: Automatic");
      break;
    default:
      mode = unknown;
  }

  setMode(mode);  
  if (mode != unknown) 
    EEPROM.put(1, mode);                              //not used--may use if Wifi issues and blynk_connect fails
}

/////////************* **********/////////
//             Manual Buttons           //
/////////************* **********/////////

void blynkWriteManual(int nr, int value) {
  char msg[32];
  valve[nr].manual = value;
  
  switch (mode) {
    case off:
      Blynk.virtualWrite(V11, 0);
      Blynk.notify("Select Manual or Auto Mode");
      break;
      
    case manual:
    case automatic:
      digitalWrite(valve[nr].pin, !valve[nr].manual);
      valve[nr].led.setColor(valve[nr].manual ? GREEN : RED);
      snprintf(msg, sizeof(msg), "Zone %d Turning %s", nr+1, valve[nr].manual ? "On" : "Off");
      Blynk.virtualWrite(V100, msg);
      break;
      
    case unknown:
    default:
      break;
  }
}

BLYNK_WRITE(V11) { blynkWriteManual(0, param.asInt()); }
BLYNK_WRITE(V12) { blynkWriteManual(1, param.asInt()); }
BLYNK_WRITE(V13) { blynkWriteManual(2, param.asInt()); }
BLYNK_WRITE(V14) { blynkWriteManual(3, param.asInt()); }
BLYNK_WRITE(V15) { blynkWriteManual(4, param.asInt()); }


/////////************* **********/////////
//          Automatic Controls          //
/////////************* **********/////////

void blynkWriteAuto(int nr, const BlynkParam& param) {
  TimeInputParam t(param);
  char msg[32] = "";
  int weekDay = Time.weekday() + ((Time.weekday() == 1) ? +6 : -1);     // weekday mapping 1=Sun,2=Mon,...,6=Sat -> 1=Mon,2=Tue,...,7=Sun
  unsigned int secStart = (t.getStartHour() * 3600) + (t.getStartMinute() * 60);  
  unsigned int secStop = (t.getStopHour() * 3600) + (t.getStopMinute() * 60);

  secNow = Time.local() % 86400;
  Blynk.virtualWrite(V41+nr, secStart);
  Blynk.virtualWrite(V51+nr, secStop);

  if(t.isWeekdaySelected(weekDay)) {  
    //Schedule is ACTIVE today 
    if(secStart - 31 <= secNow && secNow <= secStart + 31) {            // 62s on 60s timer ensures 1 trigger command is sent
      snprintf(msg, sizeof(msg), "Zone %d Starting  AUTOMODE", nr+1);
      Blynk.virtualWrite(V100, msg);
      Blynk.setProperty(V1+nr, "color", BLUE);
      Blynk.virtualWrite(V11+nr, HIGH);
      digitalWrite(valve[nr].pin, LOW);
    }                  
    if(secStop -31 <= secNow && secNow <= secStop + 31) {               
      snprintf(msg, sizeof(msg), "Zone %d Stopping  AUTOMODE", nr+1);
      Blynk.virtualWrite(V100, msg);
      Blynk.setProperty(V1+nr, "color", YELLOW);
      Blynk.virtualWrite(V11+nr, LOW);
      digitalWrite(valve[nr].pin, HIGH);
    }               
    
    if(alertStatus) 
      Blynk.notify(msg);
  }
}

BLYNK_WRITE(V21) { blynkWriteAuto(0, param); }          // Scheduler #X Time Input widget  
BLYNK_WRITE(V22) { blynkWriteAuto(1, param); }   
BLYNK_WRITE(V23) { blynkWriteAuto(2, param); }     
BLYNK_WRITE(V24) { blynkWriteAuto(3, param); }     
BLYNK_WRITE(V25) { blynkWriteAuto(4, param); }     

BLYNK_CONNECTED() {                                                     //get data stored in virtual pin V0 from server
  Blynk.syncVirtual(V0);
}

void setup() {
    Blynk.begin(auth);
    Time.zone(-5);   
  
    // limited stack size prevents the use of Software Timers
    //timActivity.start();
    //timClock.start();
    //timInfo.start();

    timer.setInterval(60000L, activeToday);  // check every 60s if ON / OFF trigger time has been reached
    timer.setInterval(1000L, clockDisplay);  // check every second if time has been obtained from the server
    timer.setInterval(5000L, sendInfo);
    
  
    //Blynk.virtualWrite(V0, 2);
    for (int i = 0; i < nValves; i++) {
      pinMode(valve[i].pin, OUTPUT);
      digitalWrite(valve[i].pin, HIGH);
    }

    firstrun();
}

void loop() {
  Blynk.run();
  timer.run();
}

void firstrun() {
  EEPROM.get(50, alertStatus);
  Blynk.virtualWrite(V90, alertStatus);
}

void sendInfo() {
  Blynk.virtualWrite(V100, Time.format("%r - %a %D"));
  Blynk.virtualWrite(V101, secNow);
  Blynk.virtualWrite(V102, mode);
  Blynk.virtualWrite(V103, alertStatus); // may delete
  Blynk.virtualWrite(V105, WiFi.RSSI().getStrength());
    
  for (int i = 0; i < nValves; i++) {
    valve[i].state = !digitalRead(valve[i].pin);
    Blynk.virtualWrite(V31+i, valve[i].state);
  }
}

void setMode(MODE m) {
  switch (m) {
    case off:
      for (int i = 0; i < nValves; i++) {
        valve[i].led.off();
        Blynk.virtualWrite(V11+i, LOW);
        digitalWrite(valve[i].pin, HIGH);
      }
      break;
      
    case manual:
    case automatic:
      for (int i = 0; i < nValves; i++) {
        valve[i].led.on();
        valve[i].led.setColor((m == manual)? RED : YELLOW);
        Blynk.virtualWrite(V11+i, LOW);
        digitalWrite(valve[i].pin, HIGH);
      }
      
    default: 
      break;
  }
}

void activeToday() {  // check if schedule # should run today
  if(mode != 3 || !Time.isValid()) 
    return;

  for (int i = 0; i < nValves; i++) 
    Blynk.syncVirtual(V21+i);  // sync scheduler #
}

void clockDisplay() {  // only needs to be done once after time sync
  static bool hasRun = false;
  if (hasRun || !Time.isValid) return;
  hasRun = true;
  Serial.println(Time.format("%T"));
} 

Instead of using V#x + nr for your virtual pin regions (Vx, V1x, V2x, V3x, V4x, V5x) you could add these assignments to the definition of valve_t and provide the respective values explicitly. This way you’d not be bound to a consecutive list of V# numbers but could choose arbitrary values when populating the valve[] array.

BTW, the Blynk library merely defines V# macros like this

#define V1      (1)
#define V2      (2)
...
#define V128  (128) 

hence you can simply add the offset (e.g. V2 + 4 would give you 6 which is the same as V6).

BTW, once you get used to zero-based indexes things get a bit easier coding arrays and loops (i.e. getting rid of this pescy nr+1 business and misalignment between V#x and the array index).

@ScruffR,
Been studying through your code here. trying to learn what I can. I’ve never taken any computer or programming classes, so what I’ve learned has been picked up along the way in bits and bytes. Know of any good tutorials out there I could go through to get more of a complete knowledge of all this? Or just keep hacking away here the best way to learn?

Ok on this project at hand, I’ve been getting SOS and than 10 flashes a few seconds in. I’ve been running through the code commenting stuff out trying to find the problem and I’ve zeroed in on sendinfo() and thinking about it now I never have seen a timestamp come in on V100. The only way to get this to run without problems is like:

void sendInfo() {
  //Blynk.virtualWrite(V100, Time.format("%r - %a %D"));
  //Blynk.virtualWrite(V101, secNow);
  //Blynk.virtualWrite(V102, mode);
  //Blynk.virtualWrite(V103, alertStatus); // may delete
  //Blynk.virtualWrite(V105, WiFi.RSSI().getStrength());
/*    
  for (int i = 0; i < nValves; i++) {
    valve[i].state = !digitalRead(valve[i].pin);
    Blynk.virtualWrite(V31+i, valve[i].state);
  }*/
}

And as soon as I try even 1 blynkWrite:

void sendInfo() {
  //Blynk.virtualWrite(V100, Time.format("%r - %a %D"));
  //Blynk.virtualWrite(V101, secNow);
  Blynk.virtualWrite(V102, mode);
  //Blynk.virtualWrite(V103, alertStatus); // may delete
  //Blynk.virtualWrite(V105, WiFi.RSSI().getStrength());
/*    
  for (int i = 0; i < nValves; i++) {
    valve[i].state = !digitalRead(valve[i].pin);
    Blynk.virtualWrite(V31+i, valve[i].state);
  }*/
}

I get SOS than 10 red flashes on my argon. Assertion failure?? I wonder what that is?

I’d have to test why there is an SOS+10 happening, but can you try to home in on the offending line of code a bit more?


Update:
I have tested this now and it’s actually an SOS+13 (Stack Overflow)
That’s most likely due to the heavy Blynk calles combined with the limited stack size for Software Timers (that cannot be increased - which is a bummer).

So I guess you need to stick with the Blynk timers :confounded:

This should work
https://go.particle.io/shared_apps/5ed5fd11399f7a0022ec036d

(I have also updated the code above accordingly)

Nice bit of code @ScruffR!!
Been studying through it here trying to figure out how this all works. Bit of a head scratcher at times but I think I’m getting there. 1 question on this bit:

   int weekDay = Time.weekday() + (Time.weekday() == 1) ? +6 : -1;       
  Blynk.virtualWrite(V99, weekDay);

V99 has been returning 6 for the last couple days.
If I do this:

  int weekDay = Time.weekday() + ((Time.weekday() == 1) ? +6 : -1);

It’s now returning 7 for Sunday. I’ll give this a go over the next few days and see if Monday will break it again.
Or will those extra parentheses cause other issues?

The extra parenthesis are a good idea.

Without them it appears that the entired left side Time.weekday() + Time.weekday() == 1 was evalated as bool expression for the ? operator and hence always evaluating to something greater 0 resulting in falling into the true branch.