Device goes offline in less than one hour

Body is limited to 32000 characters; you entered 97748.
I’ll have to break it up.

#include <Ethernet.h>
#include <EthernetUdp.h>
#include <Time.h>
#include <TimeLib.h>
#include <string.h>
#include <EEPROM.h>
#include <Blynk.h>
#include <WiFiNINA.h>
#include <MemoryFree.h>;

/* ******************** UPDATE HERE  ******************/
float firmwareVersion = 2.3;  //Enter new VM 3000 firmware version number for every change floating point real numberx.xx since the type is float
char versionDate [] = "8/28/2022";  //date of latest firmware
int displayTime = 6000;  // used to hold the firmware version number x 100 on the counter display for a while.
char hardwareVersion [] = "Arduino Wifi Rev 2, ATMEGA328";
char displayVersion [] = "YB5135C pin 1: 5V-blk, 2: open-red, 3: GND-grn, 4: active high trigger-yel "; // I think there is a transistor to invert countSig from power box.  
// If there is an inverting transistor in the power box, it should be moved out of the power box and into only those display boxes using active-low display signals (count increment and reset)
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress googleDNS(8, 8, 8, 8); // google's primary DNS
const int timeZone = -5;  // Eastern Standard Time (USA)
//const int timeZone = -4;  // Eastern Daylight Time (USA)
EthernetUDP Udp;
unsigned int localPort = 8888;  // local port to listen for UDP packets
  Download latest Blynk library here:

  Blynk is a platform with iOS and Android apps to control
  Arduino, Raspberry Pi and the likes over the Internet.

  You can easily build graphic interfaces for all your
  projects by simply dragging and dropping widgets.

    Downloads, docs, tutorials:
    Sketch generator: 
    Blynk community:  
    Follow us:        

  Blynk library is licensed under MIT license

  Virtual pin assignments and function
  V0 Arduino to Blynk - Count of captures.  
  V1 Blynk to Arduino - Reset count to zero
  V2 Blynk to Arduino - Set or restore count in case it was accidentally zeroized or is wrong. A 3-digit number from 000 - 679 for each of the VMs.  
     Hundreds position is the address and tens/ones is the count except 80-99 reserved for commands.  see code lines around Line 320.
  V3 current time (not used)
  V4 current date (not used)
  V5 Arduino to Blynk - Up time is the number of seconds the Arduino has been up running
  V6 Timer - not used now
  V7 - V12 are Arduino-to-Blynk reports of three variables concatenated here: count, location, uptime saving Blynk app 'energy'
  V7 loc 0 Westwood
  V8 loc 2 Norwood
  V9 loc 1 Brownfield
  V10 loc 3 Nottingham (not displayed because David covers it)
  V11 loc 4 Dedham (not displayed because no VM there unless testing on Erick's laptop)
  V12 loc 5 and loc 6 loc 7 combined using either Sue or Dana's iphone as a hotspot or Sherborn Diana's shares V12 too.  Need to think about when a VM is at Diana's and on V12,
      what happens if V12 is also used elsewhere for a wifi hotspot?  Zeroize and setCount commands will go to both devices.  Reports from devices (Count and Uptime) will be confused, 
      both being reported on V12 so appear on the App as from the same location.  So ideally wifi hotspots will work best when V12 not being used at Diana's.  Maybe move wifi hotspot to 
      share with V10 - nottingham because it has it's own separate Auth token. Or Use V10 for Diana's.  
  BlynkTimer provides SimpleTimer functionality:
  App project setup:
    Value Display widget attached to Virtual Pin V5
/* Rev1 of this program has these objectives:
    Be able to add a device to the same acount; completed by reporting on device-unique virtual pins.  Devices are actually generic, taking on the ID of their location
    be able to associated the device with a different account not done yet.
  1.  increment count variable when breakbeam (input on D3) goes active low and not when count signal input on D5 from 555 goes active high
  this decouples the display hardware which could be either active hign or active low counters from the breakbeam trigger signal - alway active-low.
  In Rev 1.8 the trigger signal is disconnected from the Arduino input D3 because on David's SN003, trigger sig is pulled up to 17V and it overwhelms the
  Arduino digital input.  We use only the Count signal to report captures now and no error checking of trigger on too long.
  With the 555 timer (not in SN003), a stuck low trigger signal will not keep Count signal High and therefore not keep the vac ON until trigger signal transitions
  back to high, then low again. Displays that increment on active low must use a transister to invert the CountSig from active high to active low.
  Rev 1.83 add hardware version into to the Serial print at startup;  Allows remote Blynk reset (V1) to zeroize LED display if hardware mod allows it.
  Rev 1.84 lengthened myTimer interval to 7.5s from 2.5s.  Take turns sending upTime and count.  Space out notifications. Changed CountSig input to pin D7
  Rev 1.85 add four separate virtual pins for each of the four locations
  Rev 1.86 mostly fixed some comments. Deleted tenant name from Norwood.  Norwood still not reporting count.  maybe locIndex is off?
    Because D5 broken on SN002 display box, need to move CountSig wire in SN001 to D7 as well - done.
    Added virtual terminal to allow device to report its ID so multiple devices could report.  Not working well though.
  Rev 1.87 and 1.88 added Sue's and Dana's phones as personal hotspots both into V12 for test use when no other wifi network available. 
  Rev 1.89 eliminated turn1 so Virtual write go up every 15 sec instead of 30.
  Rev 1.90 do online check less often because pinging may be taking up too much CPU time causing Arduino to miss countSig.  Controlled by myTurn1 in 
  Rev 1.91 Try some things like don't checkonlineStatus; don't write V5; 
  Rev 1.92 Added user changeable timer interval and count of void loop() runs.  made lookforSignals a function so it can run in both the void loop and myTimer
  Rev 1.93 Eliminate some Serial.prints, add VM addressabe Reset and Set count command on V1 and V2, correct (forgot to reset) void loop count, determine how often 
   lookforSignals runs. would like it to run every few milliseconds so it doesn't miss a trigger.  - It runs every 3 ms on average over n=1000 samples.
  Changed V1 and V2 to make reset and setCount commands from Blynk app to Arduino addressable to one of the six VM devices that could be connected, using two sliders.
  Added measurement between signal Reads in lookforSignals function and an average time over n = 100 between measurement (3 ms)  Added D and E char to serial Read
  to adjust serial.prints of consecutive digitalReads of signal (2-4 ms).  What does D and E do?  D is decrement by 500 ms; E is increment by 500 ms.
  Rev 1.94 Increase size of the email subject field from 30 to 50 characters because we were exeeding the allocated buffer causing a crash.  Deleted a few debug Serial.prints
    reduced max_ from 1000 to 100 on theory that memory was low and this might be clobbering something.  email subject field was corrupted and caused restart.  Seems to have worked.
  Rev 1.95 Adds a remote Restart command similar to remote Reset count.  User sents a setCountValue = 99 to selected device.  Added 7th loc - Sherborn Diana's wifi credentials to V12.
  Rev 1.96 Corrected Brownfield credentials to CAMP from Fairpoint 864C.  
  Rev 1.97 Added serial command 'F' to  check internet connection status.  Need to not spend so much time (12s) in myTimerEvent when internet DOWN.  It only takes one ping to know internet UP
       Changed checkonlineStatus to look only at the Google DNS site and return immediately on detection of a ping response. When internet down, void loop would not execute much; only once per
       call to myTimer function.  This was because in the void loop tries to connect to Blynk and being offline, never does resulting in a 28 sec timeout. Fixed by conditioning
       with Blynk.connect.  If not connected we won't attempt which just relinks all the Blynk libraries.  In the process discovered that Blynk.connect is all we really need to check online
       status.  Could delete the checkonlineStatus function but left it in for now, since it's working fine.  It used to check 4 sites, now only one to reduce CPU time waiting for ping responses.
       During void setup () the firmware version number is sent to the counter display for five seconds.  This helps with configuration control because it's hard to know which box has which firmware
       version.  However it requires a HW mod to add a series diode on countSig allowing Arduino D7 to temporarily act as an output without conflicting with the 555 pin3 output that otherwise
       drives D7. So it's equally important to track HW versions.  Use a label on the box.
       After displaying firmware version, restore the count from eeprom.  This is new and will correct the display after a power outage which otherwise would reset the display to zero
  Rev 1.98 Added Q4 to YB5135 display to invert the reset display signal from active-high out of Arduino pin 6 to active-low at the display input for reset
       Flipped all D6 and resetDisplay references to active-high as it leaves Arduino.  Reset will remain an active-high signal and the display box is responsible for 
       inverting the signal if the display requires an active-low signal for reset. (Both display types do.)  This is consistent with how the countSig works too; it's active hight leaving Arduino
       and inverted in the display box.  Firmware should now be able to reset the display to zero, which is necessary to display the firmware version, then restore the count to the display.
  Rev 1.99 Increased the resetDisplay() duration by two more lambdas (3 * lambda) where lambda is the delay representing the duration the signal is held active and inactive.
  Rev 2.00 Made resetDisplay() and displayonLED (number) separate functions Added a delay after reset 9zeroize display before bumping up display counter
  Rev 2.1  Droping two decimals in favor of one decimal version numbers.  deleted visit to void loop count and times.  When knocked offline, aautomatically try to reconnect and count the occurence.  
    concatenated a count of Blynk reconnects to the end of locationCount to be reported to app.  Added recognition of setCount 80-89 from app to be used later for something.
  added serial print of free RAM. Deleted code for serial read D and E.  Reused D to dump via serial print an array of free RAM values at device disconnect time.  So far nothing unusual.  Free RAM stays constant.
  E now will increment how ofter we lookforSignals by 50 ms and G will decrease it by 50 ms.  default is 150 ms instead of every void loop().
  Rev 2.2 comment out lookforSignals calls See if device offline disconnects go away. Line 387, 972 not they didn't so put them back
    When valid setCount received, write it to EEPROM not just the display. Otherwise display was out of sync with Count value in EEPROM. 
  Rev 2.3 increased myTimer run interval to 75000 ms.
  E2.  deal with multiple devices somehow?  How to determine which auth code to use.  For now used SN2 devices auth code otherwise the Blynk App
  gets two sets of virtua pin values.  It toggles between them.  Rev 1.93 allows the app to deal with up to 5 devices by combining all reported data into one message
  and reporting it on one virtual pin.  This saved app 'energy' only costing 200 for a value display each.
  3. Learn how to put the wifi credentials and maybe the auth code in a header file so as not to disclose them if published. not done
  4. Is there a way to let the device (maybe by having its deviceID in EEPROM to choose which auth code it will use when communicating with its
  Blynk App and also maybe even which wifi credentials it tries. v1.93 we use the wifi location SSID as ID.  Advantage: Devices don't need unique IDs. Any S/N device adopts the ID of the wifi network.
  5. For other customers, should they have their own account?  Or should there be only my account.  With one account, how can I send email to another
  user?  Have not been successful sending to email other than the account default.  This needs to be reexamined before Dec 2022 when Blynk deprecates the legacy app.

  Auth Token for VM3000SN2 project and device VM3000SN2  This begs the question, do I want or need a new project or just new device or both?
  here's the new auth code -  Auth Token : xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  For a second device on the same account (same email), use just a new auth token.  but each device may use unique virtual pins V8 - V12 depending on location so one account with one email is sufficient.
  For a second account using a different email, use both a different email and a different auth token.
   Identify account and device somewhere so you can change it
#define BLYNK_PRINT Serial  // not used yet
// Digital pins defined
#define resetDisplay 6 // output sent to LCD (via Q4 if necessary on active-low displays) to zero out the display 
#define countSig 7 //The count signal into display box from Power Control box connects to input pin D7 (active high) configured as an INPUT. Means vac is on.
#define resetSig 4 //The reset signal from the display box Reset momentary switch (active low) configured as an INPUT
//define triggerSig 3 //  the trigger signal from the breakbeam IR sensor (active low) configured as an INPUT. Not used in 1.8 because the Arduino doesn't need to see it.
//  Arduino looks at the CountSig which tells when vac is ON.  Trigger sig is asserted when the IR beam breaks which triggers a 5-7 sec pulse on countSig, a more reliable signal to look at.
#define wifiLED 2  // The wifi LED is on digital pin 2 through a 2.7k ohm resistor to ground  HIGH will illuminate it.
#define UP 1  // UP and DOWN used for internet status meaning connected and disconnected respectively.
#define DOWN 0
#include <SPI.h>
#include <BlynkSimpleWifi.h> //May need to install this library

int eepromAddress = 0; // EEPROM address counter

// You get Auth Token sent to you in the Blynk App.when you set up a project
// Go to the Project Settings (nut icon).
char auth[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";  // my token copy and pasted from the email
//char auth[] = "xxxxxxxxxxx-xxxxxxxxxxxxxxxx";  //David's token copy and pasted from the email
//  WiFi credentials.
// Set password to "" for open networks.
char ssid[30] = "FiOS-SO2CY";  //initialize variables but not used.  alt0-2 are used below
char pass[30] = "xxxxxxxxxxx";
char location[20] = "Westwood";
const int NUMBER_OF_LOCATIONS = 8; // change this to add locations and add location's ssid, pass, location text string
const int MAX_SIZE = 30;

//----------------------------- Enter all known wifi credentials and location descriptions here ----------------------
// For future consideration, put this data into a separate header file for privacy if you ever wanted to share this code without disclosing wifi credentials.
int locIndex = 0;  // locaton index is used to index into the char array for ssid, pass and locDesc of each location
char locDesc [NUMBER_OF_LOCATIONS] [MAX_SIZE] = { // description of locations
  { "Westwood" },   // 0 locIndex V7
  { "Brownfield" }, // 1  V9
  { "Norwood" },     // 2  V8
  { "Nottingham" },     // 3  V10 "Nottingham" includes both David's house and the Lake because the ssids and passwords are the same
  { "Dedham-Erick" },  // 4  V11
  { "Sue DeLuca's iPhone" },    // 5 V12 is shared use only one at a time
  { "Dana's iPhone" },    // 6  V12
  { "Sherborn Diana's" }    // 7  V12
  { "FiOS-SO2CY" },        // 0 Westwood locIndex
  { "CAMP" },    // 1 Brownfield locIndex
  { "Kevin1" },        // 2 Norwood locIndex
  { "Joyce" },     // 3 Nottingham locIndex
  { "Atitlan-V" },     // 4 Udi and Erick house
  { "Sue DeLuca's iPhone" },       // 5 Sue's phone as a wifi hotspot
  { "Dana's iPhone" },       // 6 Dana's phone as a wifi hotspot
  { "FeathersFinest.2.4" }    // 7
  { "xxxxxxx" },        // 0 westwood
  { "xxxxxxx" },       // 1 brownfield
  { "xxxxxxx" },  // 2 norwood corrected in vers 1.82
  { "xxxxxxx" },       // 3 Nottingham
  { "xxxxx-xxxx-xxxxx" },       // 4
  { "xxxxxxxxxxx" },       // 5  
  { "xxxxxxxxxxxx" },       // 6  to add any, don't forget the comma(,) after the next to last one
  { "xxxxxxxxxxx"}        // 7
//--------------------------------End of wifi credentials -----------------------------------------------------------------------
int availableMem[50]; // array to store a log of the size of free RAM at time of device disconnect for debugging possible memory leak
int lookforSignalsrunCount[50]; //  array of run counts since last device offline disconnect]
long int uptimeofDisconnect[50]; // array of uptimes when disconnect occured
//************other settable parameters ******************
const int timerInterval_ms = 7500; // start with 7.5 sec myTimer interval
const int lambda = 15;  // defines length of pulse in ms used to increment the LED display counter.  Pulse duration in ms for countSig HIGH and LOW outputs.
const int pinmodeDelay = 350; // defines length of time we wait after changing a pinMode to allow actuation
const int msg5reportingInterval_ms = 3600000;  // How often (in milliseconds) we annoy the user with a msg5 that VAC is stuck ON
const int messageSpacing_ms = 20000; //  allowed interval between messages to Blynk in milliseconds
const int emailSpacing_ms = 30000; // wait longer for emails
char rxByte = 0 ;  // receive serial port
int count;  // variable keeps track of the number of times digital input pin 7 has triggered
int resetValue = 0;
int numberoftimesBlynkReconnected = 0; // counts how many times since the last boot up or restart we had to reconnect to Blynk after being knocked offline. 
int countSet;  //Used to set or restore count when inadvertently zerozed by hitting the Reset button on Blynk App  Only affects App, not the display box 
// there is no need to remotely set the display box count to zero or any other number.  That is only done on site.  Any change on site will be reflected in the App.
// this all changed in rev 2.1 and HW mode to change pinMode of D7, where we now show the firmware version number on the LED display, so now app can change the display too.  
int gotOne = 0;  // used to pass this fact to the timer function which then resets it to zero after performing its functions
int toggleCount = 0; // counts how many times we toggled the wifi LED on and off.  After a patient 2 hours we will software reset the Arduio Rev 2.3
boolean restartRequest = 0;  // Flag used to pass restart request to the timer function which then restarts the Arduino by calling resetFunc()
boolean resetCount = LOW; // Flag used to pass a reset request to the timer function which then resets the resetCount request flag to zero after resetting the count
boolean notifym = 0; // Flag if a notify message is prepared to be sent. used to tell timer function to send it because sending not allowed in void (loop)
boolean countAsserted = 0; //  records that count has been HIGH so we know to look for LOW before we declare gotOne
//  this flag is reset after the notify message is sent so it's also an indicator of sent.  It's a boolean so it cannot be used to measure the time between messages
// because it may be cleared (no messages sent) for a long time.  It's assertion only indicates another one is ready.  Need a separate flag for lastNotification
boolean emailm = 0; // Flag indicating an email subject and body message is prepared to be sent to the default account email address
boolean textm = 0;  // Flag indicating a text message was sent.  texts don't work.  commented out that code.
boolean tooLongON = 0;  // give up waiting flag and used to know if Vac ON too long reported
boolean myTurn = 1;  // used in myTimer to toggle between sending upTime or count which would otherwise be too close in time.
int myTurn1 = 1; // used in myTimer do something less frequently say every other time as controlled toggling it.
boolean internetConnection = 0; // Flag to indication internet status UP or 1 is connected; DOWN or 0 is disconnected.
unsigned long  previousBlynk_Writetime_s = 0; //tracks when we send up a virtual.write to server in seconds
long int tooLongONthreshold_ms = 30000;  // when to declare a problem
long int thresholdMax_ms = 7200000; //2 * 3600 * 1000  at least every 2 hour; We de-escalate (lengthen reporting interval) error messages but stop de-escalating at 2 hours.
int lookforSignalsEvery_ms = 150;  // check every 150 ms for count or reset
boolean toggle = 1; // used to blink the wifiLED when connected to wifi but not connected to internet.
//boolean notCounted = 0; // flag for we missed one because beam trigger but count not incremented 
int status = WL_IDLE_STATUS;  //sets the wifi status to its initial state
IPAddress webhostName(45, 55, 96, 146); //   IP address of ""  this is the address to which VM count and uptime are sent
IPAddress localServer(192, 168, 1, 1); // this is the IP address of the local router
//================  variables for averaging the the time between signal checks  ================== Deleted in rev 2.1
/* rev 2.1 int k = 0;  // global k to reference the specific array value (not sure it has to be global)
int previousarrivalTime_ms = 0;  // previous uptime (last time visited) in milliseconds
int currentarrivalTime_ms = 0;  //  current uptime in milliseconds
const int max_i = 100;  //  number of time intervals over which you intend to average
int maxsignalintervalstoPrint = 0; // initially don't print any but this is controllable via serial port read 'D' for decrement and 'E' for increment */
// rev 2.1 int itsbeenAwhile_ms [max_i]; // array to hold the time intervals in milliseconds between visits
// rev 2.1 int avgintervalbetweenVisits_ms = 0;  /* holds the calculated average time between signal checks.  Would like this to be less than 40 ms
/*so we don’t miss anything.  Doubt if a mouse could run by the sensor in less time so it could probably go as high as 100 ms and still catch it.
Suspect a high number here is why we sometimes miss a countSig especially with using someone else’s wifi where pings could delay things.  
If we are waiting for ping responses over a slow internet, they could take 200-300 ms each and there’s four of them.  But we do this relatively
infrequently like once every 15s, so they may not hurt the average much.  If this turns out to be a problem, do fewer pings I guess, and minimize
or eliminate as many delay() instructions as possible. Ans.  average is lookforSignals once every 3 ms which is good. 
Individual reads occur between 2 - 4 ms apart. With this empirical data answering the question, this code commented out.

// ********************* Email and notification Message definitions *************** /
String bodyField = "Body4567891123456789212345678931234567894123456789512345678961234567897";  // allocate 70 bytes of memory for email body text
String subjectField = "Subj45678911234567892123456789312345678941234567895"; // allocate 50 bytes of memory for email subject; not enough uped to 50 in rev 1.94 
String notifyMessage = "Notify78911234567892123456789312345678941234567895"; // allocate 50 bytes of memory for notify message
String nMsg1 = "Obstruction in tube has cleared.";
String nMsg2 = "Likely there is an obstruction in tube. ";
String nMsg3 = "Vacuum is ON for too long.  ON duration (hh:mm) = ";
String nMsg4 = ". Vacuum has been ON for ";
String nMsg5 = "";  // place hold for the combined nMsg4 and hoursON
//String statusMessageBBNV = "Break-beam but vac didn’t turn ON "; //   beam trigger but no vac ON; not looking at beam trigger anymore
String statusMessageVTBO = "Vac turned back OFF "; //  After being stuck on the vac turned back off
String locationCount = "";  // for building the "count at location" message for V8 to Blynk app
int memoryAvailable = 9999;  // stores free memory space in bytes for debugging any memory leaks.  Set after device offline detected rev 2.1
int vmAddr = 0; // Obtained from serial port read to designate which VM you are sending a command to.  Blynk can reset or set count values to any VM but you need to pick one
                // this will be taken from the hundreds position of a number send down to Arduino via Blynk Write to virtual pin 
int setcountValue = 0;  // this will be taken from the tens/ones position of the V2 sent down to Arduion indicating what value to setCount to
unsigned long triggerActivated_ms = 0; // uptime in ms
unsigned long countActivated_ms = 0;  // uptime in ms
unsigned long msg5Sent_ms = 0; // records uptime in ms when msg5 went out
//unsigned long triggerDuration_ms = 0; // to measure how long the break-beam trigger has been active in milliseconds v1.8 change
unsigned long countDuration_ms = 0; // to measure how long the countSig which is the relay drive has been active in milliseconds
int hoursON = 0;  // tracks how long Vac is ON
int minutesON = 0; // tracks how long Vac is ON
// char countA;  // this is to put the count into the email and or notification sent up
// char countB;  // this for 2-digit count for the ones column  Note:  this assumes count will not exceed 99.  However there was one case of a stuck chipmunk causing over 9999 triggers
// counts over 99 will not be properly reported in a notification message or email  Blynk limits the messages frequency anyway to once per second I think, so it would not accept it.
int voidLoops = 0; // used to count passes through void loop()

int webhostResult;  //stores the ping response time (ms) from the webhost IP address
int localserverResult; //stores the ping response time (ms) from the pinged router
int numberoftimeswerancheckforonlinStatus = 0; // counts how many time checkonlineStatus has run
int numberoftimeswelookedforSignals = 0; // counts times lookforSignals has run since the last device offline disconnect
unsigned long upTime;  // used to report the number of seconds the VM3000 has been online.  Note the VM3000 will still work as a trap and still count when it's off line; it just doesn't report.
unsigned long previousUptime = 0; // used to mark a timepoint from which to measure an interval of time
unsigned long previousMessagetime = 0; // used to mark a timepoint from which to measure a message-to-message interval
float voidStart, voidEnd, averagetimespentinvoidLoop; // used to calculate percent time spent in void loop()
BlynkTimer timer;  // Blynk doesn't allow reporting to be in the void (loop) because it would flood their site so timer throttles the traffic to periodic intervals (set in seconds)

BLYNK_WRITE(V1) // V1 assigned to soft Reset button in Blynk App.  Allows user of Blynk App remotely to reset count to zero in the App (should be done only after cleaning out VM). 
// (Hit Reset button on Blynk App).  Note however that the VM3000 is now wired to detect the Reset Button on the physical counter display and report it, so it will automatically zero
//  out the count making the Blynk App Reset button redundant to the physical reset switch but necessary for remote reset.  
// The App Reset buttom is eliminated in v1.93.  Instead use V1 slider in the App, land on whichever device you want to reset (0-6) per locIndex.
// It will not zero out the display but in 1.97, pin 6 is asserted on remote reset then dropped which will zero out the display.  
// counter.  [This is intentional because if the remote user doesn't clean out the trap, you don't want the count being zeroed remotely.  Likewise the App cannot
// increment the physical display counter at the trap.]  Changed in 1.98.  Remote zeroize implemented.  The Arduino attempts to stay in sync with the display counter by counting the
//same events as the display counter and but there is no guarantee it will remain in sync.  The counter in the App can be incremented or zeroized to correct any known discrepancies.
// Scenario:  mouse caught immediately after restoration of power outage such that the physical display counter detects the event while the Arduino is still starting
//  up looking for a wifi signal. In ver 1.97 we can remotely zero out display and internal count - with HW mod Q4.  You could remote set or clear the internal count so  
// remote display clear is needed too, to keep it in sync.
  resetValue = param.asInt();  // gets V1 from App as integer  This will indicate which device to zeroize.
  if(resetValue >= 0 && resetValue <= NUMBER_OF_LOCATIONS) { // validate input; it must be for a device at one of the locations.
   Serial.print("Reset value received on V1 from app = "); Serial.println(resetValue);
   if (resetValue == locIndex) { // yes, it's for me else it's not for me
    count = 0;  // and resets the count to zero
    EEPROM.write(eepromAddress, 0);         //write 0 to address eepromAddress to reset stored count and retain through power loss
    Serial.println("Count has been reset to zero"); // notifies serial monitor if connected
    /*resetDisplay outputs an active-high signal from Arduino that's inverted through Q4 transistor to active-low display reset signal with the hardware reset signal to reset display count to zero
      The hardware 'OR' is implemented with rev 1.98.  ver 1.98 does it for YB5135C active-low reset and Pin 6 wired to Reset.  Both display types use active-low resets. */
   } // endif resetValue is for me (my locIndex)
   else Serial.println("Reset (zeroize) command received but it's not for me."); 
  } // endif resetValue is valid
  else {
    Serial.print("The Reset parameter received: "); Serial.print(resetValue); Serial.print(" is invalid because it's not between 0 and "); 
    Serial.print (NUMBER_OF_LOCATIONS);  Serial.println(".");
} //end V1
BLYNK_WRITE(V2) //  Count value is assigned to virtual pin V2. In case Count variable in App is accidentally zeroized, this gives user of Blynk App ability to restore it to a value 1-98
//  To use, the V2 slider must be used in the App to set Count. Typically not needed in App but can be added in Blynk App any time.  Use a slider from 0-799. 0-7 devices; 1-98 count; 99 restart
//  Hundreds digit selects the locIndex of device, tens/ones digits will be used to set the count on the VM at that location per the locIndex. Note it will only increase count.  Can't zeroize with
//  this commmand on V2 slider.  To set the count to a lesser number, use V1 to zeroize then V2 to set count to desired value.
//  v1.95. Let's use X99 as the Restart Code, where X is the locIndex of device to be restarted and 99 is the special case code that
//  forces a Restart. Slider is touchy and tough to be accurate so provide a range 90-99, any of which call the Restart. Done in Rev 2.1 90-99 
  vmAddr = param.asInt()/100;  //  Takes the hundreds digit which indicates which VM to act on.  As int, it will truncate the decimal.
  // validate the input first
  if(vmAddr <= NUMBER_OF_LOCATIONS - 1 && vmAddr >= 0) { //check for valid parameter between 0 and NUNBER_OF_LOCATIONS else serial.print error.  Only the connected device prints, obviously.
   setcountValue = param.asInt() - vmAddr * 100;  // get the tens and ones column which contains the value 0-99,  which is used to setCount or Restart
   if( vmAddr == locIndex) { // yes, the command is for me else it's not for me
    Serial.print("Received command for location "); Serial.println(locDesc[vmAddr]);
    if ( setcountValue >= 90 ) { // 90-99 is a Restart command. It's only two digits so max is 99.
      Serial.print(" Restarting "); Serial.println(locDesc[vmAddr]); 
      //resetFunc(); //restart the Arduino which forces it back into Hunt mode looking for WiFi. not defined here so just set a Restart request flag ...
      restartRequest = 1; // and we will restart later once we get into the myTimerEvent function.
    } //endif setcountValue 90-99
       if (setcountValue >= 80 && setcountValue < 90) { //added rev 2.1; intended to request number of times we had to reconnect to Blynk but added that to locationCount.  
       } //endif setcountValue 80-89. Reserved for some other future user command
    if (setcountValue > count && setcountValue < 80) { // don't waste EEPROM writes if count hasn't changed or if it's a Restart command.
     countSet = setcountValue;  Serial.print("Setting count to "); Serial.println(setcountValue); 
     count = countSet - 1; //count gets bumped in the mytimer function when it gets written to EEPROM. This accounts for it. // v1.95 but no need to write the temporarily diminished number.
     gotOne = 1;  // this will cause an EEPROM update in the mytimerEvent function; v1.95 deleted redundant write to EEPROM one line above.
     displayonLED (countSet);  // show the new count on the LED display
     if(count > writeCount();//  put it in EEPROM too. 
    } // end of if > count
   } // end if it's for me
   else Serial.println("Command received but it's not for me.");
  } // end of if valid vmAddr < devices
  else {
    Serial.print("The setCount parameter received: "); Serial.print(vmAddr); 
    Serial.print(" is invalid because it's not between 0 and "); Serial.print (NUMBER_OF_LOCATIONS - 1);  Serial.println(" inclusive.");
  }  // end else
}// end of BlynkWrite(V2)
//WidgetTerminal terminal(V7); // not used now
/*BLYNK_WRITE(V7) // send an ID as a label to count
  //  terminal.flush();
// -------------------------------------- Functions ---------------------------------

// -------------------------------------- Reset the Arduino Function --------------
void(* resetFunc) (void) = 0;  //declare reset function at address 0 this will restart the Arduino
// end of reset Arduino function

//-------------------------------------- myTimerEvent --------------------------------------------
// This function sends Arduino's up time periodically to Virtual pins, among other things It takes turns doing stuff
// myTurn (boolean) allows execution every other time and myTurn1 (int) allow execution every nth time.  Used instead of multiple timers, which can 
// overlap at the multiples.
// In the app, Widget's reading frequency should be set to PUSH. This means
// that you define how often to send data to Blynk App as opposed to an automatic time interval to sending data
// don't forget that notifications and email should be no more frequent than once every 15 seconds.
void myTimerEvent()  { //
if (restartRequest == 1 ) resetFunc(); // Restart the Arduino if there's a pending restartRequest
  //------------- average how often we check for signals local to myTimerEvent ---------
/* Rev 2.1 eliminate this which was testing code to measure how often we checked for signals
unsigned long intervalSum_ms = 0;
for (int i = 1; i <= max_i; i ++) { // sum up all the intervals between visits
intervalSum_ms = intervalSum_ms + itsbeenAwhile_ms[i]; // get the sum of the intervals between visits
if(i <= maxsignalintervalstoPrint) { // prints a bunch of visit intervals to see how short or long it's between individual visits where 'bunch' is adjustable with D and E serial reads
Serial.print("Last looked for signals "); Serial.print( itsbeenAwhile_ms[i]); Serial.println(" ms ago");
lookforSignals(); // otherwise this for-loop creates a gap where we don't lookforSignals
} // end if i <= interval
} // end for (int
avgintervalbetweenVisits_ms = intervalSum_ms/max_i ; // calculate the average interval between visits
Serial.print ("Averaging "); Serial.print(max_i); Serial.print(" samples, we look for count or reset signals once every "); Serial.print (avgintervalbetweenVisits_ms); Serial.println(" milliseconds.");

lookforSignals();  // check for countSig and reset signals eliminated v2.1 (to no avail) restored in v2.2

 // Serial.print("Number of passes through void loop() before myTimer is called: "); Serial.println(voidLoops); 
 // voidLoops = 0;
  //Serial.print(" Percent of time spend in void loop() = "); Serial.print(averagetimespentinvoidLoop); Serial.println("%"); //we want to be in the void (main) loop
  //often enough to so that we don't miss a signal where this function is not executed often enough. It is.
  /*webhostResult =;
  localserverResult =;
  Serial.print("Web Host ping (ms): "); Serial.println(webhostResult);
  Serial.print("Local Server ping (ms): ");  Serial.println(localserverResult);
  // Please don't send more that 10 values per second. (Blynk requirement)
  //Setup count first  write V0 not used anymore
  count =; // get the stored count out of EEPROM in case power was lost

  if(digitalRead(countSig == LOW) && myTurn1 == 1) {  // check that countSig not active before checking online status because the pings distract the arduino from seeing countSig
 //  make sure we're online before trying to write up to Blynk app.  Good idea but where is it enforced? ver 1.97 insures internetConnection == UP
  //prior to any  Also pingSum is no longer a global variable;  It's only local to checkonlineStatus which returns the value of pingSum. so delete use of pingSum in myTimer
  myTurn1 = !myTurn1;  //(myTurn1 + 1) % 2; rev 2.1 Toggle, don't mod. Execute above check every second time (15 sec) through so as not to use up some much CPU time pinging. 
  if (checkonlineStatus() <= -1) { // a minus one is returned if host doesn't respond to ping.  this accommodates multiple pings all failing
    Serial.println("Loss of internet detected because we can't ping webhosts. ");
    internetConnection = DOWN; // Flag the internet as Down (disconnected)  v1.97 should already be marked as DOWN because we did so in checkonlineStatus
  } //end if checkonlineStatus() <= -1  commented out in 2.2 
  else { // internet is up so set flag
    internetConnection = UP; // Flag the internet as Up (connected) meaning we can ping webhosts
   // end else  now check if Blynk webhost is connected
    if (!Blynk.connected()) { // if it's not connected then take some statistics to try to figure out why it's disconnecting
      Serial.print("Blynk not connected at " ); //But if Blynk doesn't think we're connected, I guess we're not!
      Serial.print(upTime); Serial.println(" upTime."); 
      //resetFunc(); // reboot which will re-hunt for wifi; not yet, just try to start blynk up again
      // print how much RAM is available.
      Serial.print(" There are "); Serial.print(freeMemory(), DEC); Serial.println(" bytes of free memory available."); 
      memoryAvailable = freeMemory();
      //Every time we disconnect, write how much free memory there was abailable at the time into a log (keep up to 50 entries) 
      // Containing the uptime disconnect occurred, the incident number, the free memory available, and how many times we looked for signals (function)
      if( numberoftimesBlynkReconnected >= 49) { numberoftimesBlynkReconnected = 0; } // array is 50 inteters long
      availableMem[numberoftimesBlynkReconnected] = memoryAvailable; // Keep a log of free RAM size the last 50 times device went offline
      lookforSignalsrunCount[numberoftimesBlynkReconnected] = numberoftimeswelookedforSignals; // Keep a log of lookforSignals runs
      numberoftimeswelookedforSignals = 0; // Count only runs since last device offline disconnect
      uptimeofDisconnect[numberoftimesBlynkReconnected] = upTime; // 
      Blynk.begin(auth, ssid, pass, "", 80); //now try to reconnect to Blynk
      Serial.println("Sent Blynk.begin() "); //Trying to reconnect
      if(Blynk.connected()) { // successful reconnect 
        Serial.print("At uptime "); Serial.print(upTime); Serial.println(" Blynk connected again");
        numberoftimesBlynkReconnected++; // Increment the reconnected count.  in other words how many device offline disconnects occurred 
        Serial.print("Blynk reconnected "); Serial.print( numberoftimesBlynkReconnected); Serial.println(" times.");
        numberoftimeswerancheckforonlinStatus = 0; // reset the count of onlin[e] Status checks we ran.  (variable name has typo but left it)
      } //endif Blynk.connected  i.e., reconnected
  } //endif !Blynk.connected
  } // end else
      // we are online now (we know this because webhosts are responding to pings) so if the count has not been reported in over one minute, send count.

  // send any notifications immediately  should collapse into calling sendNotify function
  // call sendNotify? just in case there's a notify waiting.   no, deleted

  /*  // text Dana too  special case.  We don't have a function for text messages,  don't work anyway: tried email-to-text
    if (textm = 1 && millis() / 1000 - previousMessagetime > messageSpacing_ms/1000) { // make sure we don't flood Blynk site"", "VM capture! ", notifyMessage); // text a copy of notify message to Dana's cell never seen this either
      textm = 0;
      previousMessagetime = millis() / 1000; // store when email sent
      Serial.println(" Line 456 hit for textm = 1");
    } // end if textm  This has never worked.  No text messages ever come.

  if (gotOne == 1) { // check if we got a new one
    count =;    //get stored count.  Only executes when input pin countSig is HIGH so not a frequent read
    count = count + 1;  // when countSig pin is released count++ only if sig was HIGH (hence the above If statement)
    writeCount();        //write new count to eepromAddress
// Build the notification message
    notifyMessage = String("Count changed to ");
    notifyMessage += String(count);  // this one statement replaces all the stuff that's commented out
    notifyMessage += " at ";
    notifyMessage += String(upTime);
    notifyMessage += " seconds up time. ";
  } // end if gotOne
  //Build the email message.  Why isn't this part of if(gotOne)?  Want to build this every time?  Not necessary it's only sent after we got one.  
  subjectField = String("VM at ");
  subjectField += location;
  subjectField += " now at count = ";
  subjectField += String(count);
  bodyField = String(" Caught by VM device at ");
  bodyField += location;
  bodyField += ".  Capture occurred at Uptime = "; bodyField += upTime;  // this email does arrive on each capture

  //   Serial.print("notifyMessage = "); Serial.println(notifyMessage); // redundant with sendNotify?

  if (gotOne == 1 && countDuration_ms == 0 ) {  //wait for countSig to fall back LOW; should take about 7 seconds

    notifym = 1; // flag notify ready only after count turns OFF but not too soon otherwise another notification will be sent

    sendNotification (notifyMessage);  // this will execute periodically but because we are not setting notifym True here, this will result in a notification
    // will both these go out?  If one goes out and the flag gets set to send it again, the other one may never get sent because it keeps being
    // superceded by prior statement.  Do we need a msg queue to make sure messages go out in order they were created?  Would need a time tag.
    // there is a serial print for every notify and email so we may be able to test it.  These flags only get sent when gotOne is set but the error
    // conditions flags might persist and error messages could override any other messages.  Maybe that's legit since errors should take priority.
    emailm = 1;  // flag email ready

    gotOne = 0;  // clears this flag after we asserted the notify and email ready flags.
  } // end if got one

  if (millis() / 1000 - previousMessagetime > emailSpacing_ms / 1000) {  // If enough time elapsed since last email, emails must be spaced out in time,
    sendMail (subjectField, bodyField);  //  send email if one's ready  The sendMail function checks whether one's ready. 
  // check for Fault conditions
  if (countDuration_ms > tooLongONthreshold_ms) {  // Vac ON for too long, start tracking how long it's been ON
    tooLongON = 1;  // Flag that msg is being sent and use the flag to know when to stop sending 
    hoursON = countDuration_ms / 1000 / 3600; //  convert countDurtion_ms to seconds then hours the vac has been ON
    // keep track on ON time hours and minutes
    minutesON = (countDuration_ms / 1000 - hoursON * 3600) / 60; // get minutes too
    // just in case build notify message and add hoursON but first convert hoursON to string with myStr = String(hoursON)
    nMsg5 = nMsg3;  // start building message
    nMsg5 += String(hoursON);  nMsg5 += String(":");  nMsg5 += String(minutesON); nMsg5 += " ";
    notifym = 1; // send out nMsg5 for on too long
    if (tooLongONthreshold_ms < thresholdMax_ms) {
      tooLongONthreshold_ms = tooLongONthreshold_ms * 3; // rapidly decrease reporting frequency to once every 8 hours max
    sendNotification (nMsg5); // ON too long message including duration
  }  // end if countDuration...

  // Clear fault flag; send fault cleared msg
  if (tooLongON == 1 && countDuration_ms == 0 && millis() / 1000 - previousMessagetime > messageSpacing_ms / 1000) {
    // check if we reported a too longON msg that's now obsolete and it's clear to send another message then send update msg
    tooLongONthreshold_ms = 30000;  // set detection threshold back to 30 sec.  We increase it exponentially throughout.
    countActivated_ms = 0;  //  count not active anymore so free up the activation time
    Serial.println( " We got here Line 522 so we are sending the VTBO notify msg");
    //need to wait a while I think before sending because there are lots of messages going out.
    notifym = 1; // send notification
    sendNotification ( statusMessageVTBO ); // report it's back ON
    tooLongON = 0; //clear tooLongON flag  means the vac finally shut off after we reported it was ON too long.
  } // end if tooLongON

  // check for another fault condition
  /*if (triggerDuration_ms > 1000) { // something blocking tube?  // not looking at trigger (v1.8) anymore but it's still a good idea
    a very fat mouse or a chipmunk can get stuck in the tube.  To re-enable, connect the triggerSig (red wire) back to digital pin 3.
    tooLongtrigger = 1;  // flag that says this is being reported so we know if we should send update msg when it's cleared
    notifym = 1;
    sendNotification (nMsg2); // probable obstruction
  /*//Clear flag that this was sent.
    if (tooLongtrigger == 1 && triggerDuration_ms == 0) { // check if we reported a too long trigger msg that's now obsolete so send update msg
      tooLongtrigger = 0; //clear tooLongtrigger flag means the tube finally cleared after we reported it was obstructed.
      triggerActivated_ms = 0;
      notifym = 1;
      sendNotification ( nMsg1 ); // report obstruction cleared
    }  */

  // check for another fault condition
  /* if (notCounted == 1 ) { // missed one? //not checking this in 1.8
     notifym = 1;
     sendNotification (statusMessageBBNV); //BBNV = IR beam-broken but no vacuum signal
     notCounted = 0;  // reset it not that we reported it
    } */
  /*  redundant.  included in nMsg3 and sendNotification adds location
    nMsg5 += "VM at ";
    nMsg5 += location;
    nMsg5 += nMsg4;
    nMsg5 += String(hoursON);
    nMsg5 += " hours and ";
    nMsg5 += String(minutesON);  // add the minutes - mostly for testing
    nMsg5 += " minutes.";
  // Check if vac still on for a while  countDuration_ms variable contains the length of time in ms that the vacuum is being held ON with the countSig
  if (countDuration_ms > msg5reportingInterval_ms && millis() - msg5Sent_ms > msg5reportingInterval_ms ) { // next reporting threshold
    notifym = 1;  // if msg5 reporting interval exceeded and count still asserted, flag msg ready
    sendNotification (nMsg5); // report via notification how many hours and minutes Vac has been ON
    msg5Sent_ms = millis();  // record the uptime when msg5 went out. (sent every reporting interval)
  } // end if countDuration_ms

  // if Vac ON continuously, send emails too, every couple of hours
  if (hoursON % 2 == 1) { // every other hour
    // build an email message too  this code for timer function when email needed
    subjectField = String("VM at ");
    subjectField += location;
    subjectField += " status is: FAULT ";
    bodyField = String(" VM device at ");
    bodyField += location;
    bodyField += nMsg4;
    bodyField += String(hoursON);
    bodyField += " hours and ";
    bodyField += String(minutesON);
    bodyField += " minutes.";
    bodyField += "  Fault being reported at Uptime = "; bodyField += upTime;
    if (millis() - msg5Sent_ms > msg5reportingInterval_ms + emailSpacing_ms ) {
      emailm = 1;  // if msg5 reporting interval exceeded, flag email msg ready
      sendMail (subjectField, bodyField);
      msg5Sent_ms = millis();  // record the uptime in ms when msg5 went out.
  } // end if hoursON

  if (resetCount == HIGH) { // check if we need to reset the count to zero

    count =;  //get stored count.  Only executes when input pin resetCount is HIGH so not a frequent read
    if (count > 0) { //don't zeroize it and waste an EEPROM write cycle unless it's > zero to begin with
      count = 0;  // when resetCount flag is HIGH and stored count is > zero, zero out the count
      resetCount = LOW;  // clear the flag telling us to reset the Count because we just reset it 
      writeCount();  //now write it to eeprom
      Serial.println("Count has been reset to zero");
    } // end if (count
  }  // end if countReset
  // maybe these two virtual writes are too close together.  Let's alternate. Do one write on this pass, then the other next pass.
  if (myTurn) { //take turns doing a Blynk_virtualWrite because one might be preventing the other due to proximity in time.
      // build a string to be reported to Blynk app on one of the virtual pins V8 - V12 depending on location index
      locationCount = ""; // initiates the string
      locationCount += count; // start with the number count of how many captures so far
      locationCount += " @ ";
      locationCount += location; // add where we are
      locationCount += " Uptime: "; 
      locationCount += upTime; // add on the current uptime
      locationCount += " OL: ";  
      locationCount += numberoftimesBlynkReconnected; // add # of times we reconnected after being knocked offline
      locationCount += " Mem= ";
      locationCount += memoryAvailable; // add number of bytes free RAM available (for debug of possible memory leak - none found)
      if (internetConnection == UP) { // allow Blynk.virtualWrite only when online.  Don't attempt Blynk virtual Write if internet down
      switch (locIndex) { // for each location, write the locationCount ("Count at location at uptime") to a different virtual pin
        case 0:
          Blynk.virtualWrite(V7, locationCount ); // report to the app on virtual pin V7, from which VM location the Count is coming - westwood
          Serial.print( "On V7 we");
        case 1:
          Blynk.virtualWrite(V9, locationCount ); // report to the app on virtual pin V9, from which VM location the Count is coming - brownfield
          Serial.print( "On V9 we");
        case 2:
          Blynk.virtualWrite(V8, locationCount ); // report to the app on virtual pin V8, from which VM location the Count is coming - norwood
          Serial.print( "On V8 we");
        case 3:
          Blynk.virtualWrite(V10, locationCount ); // report to the app on virtual pin V10, from which VM location the Count is coming - nottingham
          Serial.print( "On V10 we");
        case 4:
          Blynk.virtualWrite(V11, locationCount ); // report to the app on virtual pin V11, from which VM location the Count is coming - dedham
          Serial.print( "On V11 we");
        case 5:
          Blynk.virtualWrite(V12, locationCount ); // V12 is the last catchall virtual pin because we ran out of 'energy' in the app - sherborn or WiFi hotspot
          Serial.print( "On V12 we");
        case 6:
          Blynk.virtualWrite(V12, locationCount ); // use V12 again can only show 5 at cost of 200 ea.
          Serial.print( "On V12 we");
        case 7:
          Blynk.virtualWrite(V12, locationCount ); // use V12 again can only show 5 at cost of 200 ea.
          Serial.print( "On V12 we");
          // if nothing else matches, do the default - nothing right now
      } // end switch locIndex
      Serial.print(" sent this virtual write up to the Blynk app: ");Serial.println( locationCount);
      } else { //don't report info
        Serial.println( "Did not send info up to Blynk app because internet connection is down.");  //added if (internet UP) in ver 1.97 because suspect this is causing 
        //  a delay possibly due to waiting for timeout.
    upTime = (millis() / 1000);
    previousBlynk_Writetime_s = upTime; //save the time we sent a Blynk_Write
    myTurn = !myTurn; // now it's your turn/my turn
  } // end myturn
  else {  // not myTurn so just print upTime
  // Deleted V5 from app because uptime is alreaded embedded in the locationCount composite
    Serial.print("Count = "); Serial.println(count);
    Serial.print(" There is "); Serial.print(freeMemory(), DEC); Serial.println(" bytes available.");
    Serial.print( "Time on = "); Serial.print(millis() / 1000); Serial.println("s");
    Serial.println(); // make a space at the end of each pass through myTimer event
    //Serial.print("status = "); Serial.println(WiFi.status);
    upTime = (millis() / 1000);
    previousBlynk_Writetime_s = upTime; // is this ever checked?  Don't think it's needed if this taking turns structure is left in.
    myTurn = !myTurn;
  } // end else not my turn, write uptime
// End of taking turns.  Do the following every time through...
  if (WiFi.status() != WL_CONNECTED) {   // if we lose the WiFi connection request a restart
    digitalWrite(wifiLED, LOW); // turn off the wifi status indicator LED
    Serial.println(" Restarting because of lost WiFi signal.");
    delay(5);  //not critical since we are rebooting.
    restartRequest = 1;  // request reboot which will re-hunt for wifi
  } // endif (WiFi not connected)
  else { // wifi is connected but if internet DOWN blink green LED
  /* Need a truth table for what to do for internet lost and/or Blynk connection lost or wifi lost
                              wifi not connected    wifi connected
      internet connected              N/A             wifiLED ON
      inernet not connected         Restart           wifiLED toggle ON and OFF on each pass through
      internet not connected
      & Blynk not connected for n
      times through                                   Restart
    digitalWrite(wifiLED, HIGH); // Wifi is UP in this else clause, independent of online status, so start with the wifi LED illuminated
    if( internetConnection == DOWN) {
      digitalWrite(wifiLED, toggle); // if Internet is down toggle (blink) the wifiLED on each pass 
      toggle = !toggle; 
      toggleCount++;  // count times we toggled LED.
      Serial.print("Internet is down for the count of 10: "); Serial.println(toggleCount); // print the count
      // try this for ten passes then try restarting the Arduino
      if(toggleCount > 10) { // after toggling for n times request a restart of arduino.  ten for test but make this 2 hours, normally. 
      // later restore toggleCount > 7200/(timerInterval_ms/1000) will give 2 hours for whatever timerInterval_ms we use
        restartRequest = 1; // only restart if we've tried many times
        Serial.println(" We are requesting a restart.");
      } // end if toggleCount
    } // end if internet DOWN
    if ( internetConnection == UP) {
      digitalWrite(wifiLED, HIGH); //It's likely already HIGH because we are still on wifi but the internet could have been down and just restored, so light it.
    } // end if internet UP
  } //end else wifi connected
}  //******************** end of myTimerEvent() *********************************

//*************** sendNotification function *************************
void sendNotification (String msgInput) {
  // check when the last email or notification was sent and send only if greater than 15 sec.
  // but what if a Blynk_Write was just sent?  Would that block a notify?  Could delay it with a previousBlynk_Writetime.
  if (notifym == 1 && millis() / 1000 - previousMessagetime > messageSpacing_ms / 1000 && internetConnection == UP) { // make sure we don't flood Blynk site
    if ( millis() / 1000 - previousBlynk_Writetime_s < messageSpacing_ms / 1000 ) { //makes sure there were no recent Blynk_Writes either
      msgInput += "Location: ";
      msgInput += location;  // add where the device is located.
      Blynk.notify(msgInput);  // this works
      notifym = 0;
      Serial.print("notifyMessage = "); Serial.println(msgInput); // print what we just sent.
      //textm = 1; // send out text too when time elapsed
      previousMessagetime = millis() / 1000; // store when notify sent
      Serial.println(" Line 703 hit for notification sent");
      delay(1); //why? changed from 100 to 1 ms 2/28/22; normally there is no notify pending so not critical
    }// end if millis()
  } // end if notifym
} // end sendNotify function

//*************** sendMail function *************************
void sendMail (String subjectInput, String bodyInput) {
  // check when the last email or notification was sent and send only if greater than 15 sec.
  // send any notifications immediately
  if (emailm == 1 && millis() / 1000 - previousMessagetime > messageSpacing_ms / 1000 && internetConnection == UP ) { // make sure we don't flood Blynk site
    if ( millis() / 1000 - previousBlynk_Writetime_s < messageSpacing_ms / 1000 ) { //makes sure there were no recent Blynk_Writes either, bodyInput);  // this works
      emailm = 0;
      Serial.print("email Subject = "); Serial.println(subjectInput); // print what we just sent.
      Serial.print("email Body = "); Serial.println(bodyInput); // print what we just sent.
      // textm = 1; // send out text too when time elapsed
      previousMessagetime = millis() / 1000; // store when email sent
      Serial.println( " Line 721 hit for email.");
      delay(1); //why? changed from 100 to 1 ms 2/28/22 again not critical because email is a rare event
    } // end if millis()
  }  // end if email
} // end sendMail
/*------------------------------------- End of Functions ----------------------*/
void setup()
  pinMode(countSig, INPUT); //pin 7  this was on pin 5 but destroyed pin 5 on one Arduino so moved countSig to pin 7 on all.  Update Fritzing!
  pinMode(resetSig, INPUT_PULLUP); // pin 4 and tell Arduino to use its internal pullup resistor to keep this normally high lookiing at panel switch
  pinMode(resetDisplay, OUTPUT); //Pin 6 active HIGH to reset - displays are active-low so hardware inverts allowing LED reset to be OR'd w. grounding switch
  digitalWrite(resetDisplay, LOW); // Initialize reset pin LOW which is idle state.
  /*  When reset button pushed, display zeroizes, Arduino pin 4 input senses the clear and clears internal count too to match what display now says (zero).  
    Take care to not clear internal count when pin 6 asserted only to clear display for displaying firware version.  Since firmware version display occurs only during setup () pin 4 is not being checked. 
  So that's ok.  During  operation when pin 4 is being checked (by lookforSignals function) and pin 6 is asserted due to remote zeroize command, then pin 4 will sense and reset internal count too.  
  This is also ok; that's intended.  But we don't want a circular problem. Pin 4 asserted causes firmware to assert pin 6 to (HIGH). 
   Someone hits reset button, pin 4/6 asserted and dropped on release.  No need to have software read pin 4 and assert pin 6 because assertion of pin 6 only purpose is to allow software to zeroize diaplay. 
   When panel reset button is pushed, display gets cleard in HW; so no need for software assertion of pin 6 when pin 4 is asserted.  
  // pinMode(triggerSig, INPUT);  // INPUT_PULLUP); // pin 3 use Arduino internal pullup to keep this normally high during stanalone test.  v1.8 - It's pulled up
  //by 10k ohm || 4.7k on PCB. Not connected.  triggerSig replaced with active-high countSig.
  /* on VM2000 SN003 this signal is pulled up to 17 VDC through a 1k resistor. It's pulled low by breakbeam to activate the base of a PNP transistor whose emmitter
    is at 17 VDC.  Pulling it to 5V through a 10K in the Arduino with INPUT_PULLUP doesn't allow the PNP to turn off because the 5v is keeping its base low enough to
    produce a Vbe of 1.09 volts which is not enought to turn off the PNP, which needs a Vbe of 0.4 volts.   Eliminating the INPUT_PULLUP will allow it to turn off.  in v1.8
    tried to used 10k but that was not strong enough pullup so put 4.7k in parallel.

  pinMode(wifiLED, OUTPUT); // pin 2 driving the LED is an output and is normally low.  Used to control the green wifi LED.
  digitalWrite(wifiLED, LOW); // initialize the LED off
  // Debug console

  Serial.println(); Serial.println();
  Serial.print("Vacuumouse 3000 firmware version "); Serial.print(firmwareVersion); Serial.print("; Firmware version date:  "); Serial.println(versionDate);
  Serial.print("Vacuumouse 3000 hardware version "); Serial.println(hardwareVersion);
  Serial.print("LED display version "); Serial.println(displayVersion);
  Serial.print(" Started up. "); Serial.print(millis()); Serial.println(" ms elapsed time");
  // Show firmware version number times ten on the LED counter display.  E.g., ver 2.1 will show as 21 and hold for a few seconds.
  // this feature needs a diode protecting Arduino digital pin 7 in series with the incoming countSig (active high) signal.  Diode actually protects the 555-3 pin, which can sink 200ma
  // Arduino sinks or sources only 40ma so if 555-3 is high and D7 is low Arduino needs protection. diode points toward D7, add a series 120 ohms.  Arduino suggests a 470 or 1k resistor in series with Arduino I/O pins. 
  // This will allow D7 to be initialized as an output and use to drive the count display.  Code moved to displayonLED() and zeroizeLEDdisplay() functions.

  // It gets used too show firmware version, show restored count, maybe whenever set_a_count is sent down,  and when reset count is sent down.
  //Start firmware version display by zeroizing LED display.
  // Spin up the counter to display the firmware version number
   displayonLED(firmwareVersion * 10);
  Serial.print (" Now showing the firmware version number on the LED counter display: "); Serial.println (firmwareVersion * 10);
   delay(displayTime); // show the display for displayTime seconds
  // Now restore the display to the correct count. 
  int countstoredinEEPROM =; // read EEPROM once and get the count so as to not waste EEPROM cycles (although Reads shouldn't hurt)
   displayonLED(countstoredinEEPROM);  // put the stored count back on the display
  Serial.print(" Just restored the displayed count back to: "); Serial.println(countstoredinEEPROM);

  Serial.println("Start WiFi scan of neighborhood ");

  // WiFi.scanNetworks will return the number of networks found
  int n = WiFi.scanNetworks();
  Serial.println("scan done");
  if (n == 0) {
    Serial.println("no networks found");
  } else {
    Serial.print(n); Serial.println(" WiFi networks found in the neighborhood.");
    for (int i = 0; i < n; i++) {
      // Print SSID and RSSI for each network found
      Serial.print(i + 1);
      Serial.print(": ");
      Serial.print(" \tSignal ");
      Serial.print(" dBm \tEncryption type: ");
      Serial.print("\tChannel: ");
      /*Type of Encryption
          TKIP (WPA) = 2
          WEP = 5
          CCMP (WPA) = 4
          NONE = 7
          AUTO = 8 */

  // Wait a bit before scanning again

  unsigned int i = 0; // used to count while loop executions/  int could wrap and overflow which is undefined.
  int pingResult[6];
  //--------------------------------------- scan all known wifi networks  -------------------
  while (status != WL_CONNECTED) {
    locIndex = i % NUMBER_OF_LOCATIONS;  // cycles thorugh all known locations
    // try all known networks sequentially one at a time
    // i increments once every time through the while loop
    strcpy(ssid, altssid[locIndex]);   // try each alternative network
    strcpy(pass, altpass[locIndex]);   //
    strcpy(location, locDesc[locIndex]);  // record of our location retained when connected
    Serial.print("Attempting to connect to Network named: ");
    Serial.print(ssid);  Serial.print(" "); Serial.println (locDesc [locIndex]);
    WiFi.end();  // if connected, then disconnect.
    // WiFi.mode(WIFI_STA); // SETS TO STATION MODE!
    status = WiFi.begin(ssid, pass);
    Serial.print("Reason code: ");
    if (WiFi.reasonCode() == 0) {
      digitalWrite(wifiLED, HIGH); // illuninate wifi LED to indicate we are connected to a wifi network
    } else {
      Serial.print("Failed to connect because of reason code: ");
    } //end of else
    delay(000); //why? reduced to 1s in 1.97.  As we increase known wifi networks to hunt for, this delays the find.
  } // end of while loop
  Serial.print("After "); Serial.print(i); if( i == 1) Serial.print(" try, ");  else Serial.print(" tries, "); Serial.print(" Wifi is ");
  Serial.print("connected to ssid = "); Serial.print(ssid); Serial.print(" at "); Serial.println(location); // at this point is ssid current? Yes.
  //Serial.println(WiFi.SSID()); // redundant because ssid above is current
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address assigned by router to Vacuumouse: "); Serial.println(ip); // Print the IP address that the LAN router has assigned the Arduino

  /* checking onlineStatus here.
  We may be on a wifi LAN (just an extender) but the router has no internet connection, so try pinging 4 websites. Not pinging local router because
  it's address in not standard so why wait 3s for timeout?
  and if all fail to ping there's no internet so reset */

  // see if we are still on internet by pinging some well-known websites. ver 1.97 reduced this to one website
  if (checkonlineStatus() <= -1) { // we're not on Internet.  Maybe the wifi is down. Reset Arduino, hunt for wifi again. No, we check for wifi separately. Don't assume wifi down.  Just reset LED
                        // if wifi is down, we wouldn't be here because of the while on wifi loop wouldn't exit.
    Serial.println("Wifi LED extinguished because sum of pings to webhosts <= -1 ");
    digitalWrite(wifiLED, LOW); // Just extinguish the wifi LED; don't restart.  Means we lost Internet but if wifi is still connected it will blink back ON in myTimerEvent.
    internetConnection = DOWN;  // Flag the internet offline - disconnected
    //resetFunc();  //  we can no longer ping any of the four common websites so we no longer have internet access.  1.96 commented out Resetting the Arduino. Stay just on wifi.
  } else { // internet is up so ...
    Blynk.begin(auth, ssid, pass);  // We're online.
    internetConnection = UP; // flag the internet as online - connected
    toggleCount = 0;  // reset toggleCount.  We are not blinking LED anymore
    numberoftimesBlynkReconnected = 0;  // reset the number of times we had to reconnect to Blynk server
    // You can also specify server:
    Blynk.begin(auth, ssid, pass, "", 80);
    //Blynk.begin(auth, ssid, pass, IPAddress(192,168,1,223), 8080);  // fix this when you scan the LAN to see what IP it's assigned

  //terminal.clear();  not using Terminal on blynk app right now.  Termainal is a window through which text can be sent or received between blynk app and Arduino
  // Setup a function to run every 7.5 seconds.  (set using timerInterval_ms)
  } //end else // even if not online, do the following but it doesn't seems like it's executing
  timer.setInterval(timerInterval_ms, myTimerEvent); // I guess setting the timeer only needs to run once.  
  digitalWrite(resetDisplay, LOW); // de-assert active-high resetDisplay signal.  This is just to reinitialize the signal to idle not to reset display.  

  } // end of setup

void loop()  { // main part of program
  // This for debug but leave it in.  Allows serial printout of selected variables by sending input of 1 - 9, A, B, C, D - G. increase as needed.
  // voidStart = millis();  // testing code.  Not tracking void loop executions in normal mode.
  if (Serial.available() > 0) {   // is a character available?  I.e., did the user type and send anything into the serial monitor SEND window?
    rxByte =;       // get the character

    // check what character was received and print it to serial monitor, then if valid, execute the command.
    if ((rxByte >= '0') && (rxByte <= 'G')) { // validate command is within the defined range
      Serial.print("Command code received: ");
      Serial.println(rxByte);  // print out what command code was received
      switch (rxByte) { // execute whatever command the user sent down
        case '1':
          Serial.print("hoursON = "); Serial.println( hoursON); // zero is normal.  hours & minutesON are how long vac is stuck ON - Error cond.
        case '2':
          Serial.print("minutesON = "); Serial.println( minutesON); // zero is normal.  ditto
        case '3': //If vac stuck on, checks how long the count signal has been asserted. Not asserted is normal but when activated 5-7 sec is normal
          Serial.print("countDuration_ms = "); Serial.println( countDuration_ms); // 
        case '4':  // now spare
          // Serial.print("triggerDuration_ms = "); Serial.println( triggerDuration_ms); //removed in v1.8
        case '5': // this threshold escalates over time.  It spaces out longer and longer in time the error message reminders to the user.  But max is 2 hrs.
          Serial.print("tooLongONthreshold_ms = "); Serial.println( tooLongONthreshold_ms );
        case '6': // show me the last notify message sent to user. If none, initialize value is shown: 78911234567892123456789312345678941234567895
          Serial.print("Notify message = "); Serial.println( notifyMessage );
        case '7':
          Serial.print("countActivted_ms = "); Serial.println( countActivated_ms );
        case '8':
          Serial.print("previousMessagetime = "); Serial.println( previousMessagetime );
        case '9':  // print the text message ready flag 
          Serial.print("textm = "); Serial.println( textm );
        case 'A':  // quick real-time look at countSig
          Serial.print("countSig = "); Serial.println( digitalRead(countSig) );
        case 'B':  // where are we at?
          Serial.print("Location Index = "); Serial.println( locIndex );
        case 'C':  // print the composite message built for this location
          Serial.print("locationCount = "); Serial.println( locationCount );
        case 'D': // Dump the contents of the memAvailable array to serial port rev 2.1 
          Serial.println(" ");  
          Serial.println("Dump of memAvailable array of free RAM when device went offline ");
          Serial.println("Uptime   Disconnect#  free RAM  # times we lookedforSignals");
          for (int i = 0; i < numberoftimesBlynkReconnected; i++ ) {
            // print upimeofDisconnect, print disconnect # i, print free RAM Array[] print lookforSignals runs count
            Serial.print(uptimeofDisconnect[i]); Serial.print("      "); Serial.print(i+1); Serial.print("         "); Serial.print(availableMem[i]);
            Serial.print("     "); Serial.println(lookforSignalsrunCount[i]);
            // Note the first entry in array starts at index 0, hence i+1
          } // end of case D
        case 'E': // for Increase to lookforSignalsEvery_ms by 50 ms
          lookforSignalsEvery_ms = lookforSignalsEvery_ms + 50; // rev 2.1 
          Serial.print("increase lookforSignals interval to = "); Serial.println( lookforSignalsEvery_ms );
        case 'F': //  serial print internet connection status up or down
          Serial.print("Internet connection is "); Serial.println( internetConnection );
        case 'G': //  decrement lookforSignalsEvery_ms by 50 ms 
          if(lookforSignalsEvery_ms > 50) lookforSignalsEvery_ms = lookforSignalsEvery_ms - 50; // rev 2.1 
          else lookforSignalsEvery_ms = 50; // else set it to 50, the minimun allowed
          Serial.print("decrease lookforSignals interval to = "); Serial.println( lookforSignalsEvery_ms );
          // if nothing else matches, do the default - nothing right now
          Serial.println("Not an option.");  // you enterd a choice that has no case statement
      } // end switch case
    } // end if rxByte

  } // end: if (Serial.available() > 0)
// ------------------------------------------ end of debug feature for printing out variables of interest -------------------
  /*  Not using this trigger signal code.  Trigger is disconnected from Arduino.  Instead Arduino looks for countSignal assertion from the power box
    // looking at anything in the void loop means it gets checked all the time
    // mark the time when triggered (start stopwatch)
    if (digitalRead(triggerSig) == LOW && triggerDuration_ms == 0) { // new trigger event
      triggerActivated_ms = millis();  // mark the time it triggered in ms of uptime. initially triggerDuration_ms is 0.
      triggerDuration_ms = 1; // ensures execution won't return here until triggerDuration_ms == 0 again after it's reset
    }  // end if triggerSig

    // update the stopwatch until trigger released which ends stopwatch and calculates duration that mouse was there before being wisked away
    if (digitalRead(triggerSig) == LOW && triggerDuration_ms != 0) {
      triggerDuration_ms = millis() - triggerActivated_ms ; // record duration stopwatch of trigger signal
    }  // end if triggerSig
    else {
      triggerDuration_ms = 0;  // reset stopwatch when trigger negated.  Note triggerDuration_ms variable only non-zero during its assertion.
      triggerActivated_ms = 0; // reset starting point too
    } // end else duration
 if (millis() % lookforSignalsEvery_ms == 0) lookforSignals(); // commented out v2.1 restored v2.2 look for countSig and reset every 150 ms (adjustable via serial read commands E & G)
 // settable with E instead of executing every time through void loop but never executing lookforSignals() did not prevent device disconnects
  // voidLoops = voidLoops + 1; // count how many times we go through here every timer.interval.  Rev 2.1 Forgot comment this out and the reset to zero was commented out so the varialble overflows.
  if (Blynk.connected());  // i don't see this defined anywhere.  what happens here? all the Blynk magic it says.  I think it tries to connect and if offline, it tries for 30s
  else  { // should never happen that Blynk not connected here.
   Blynk.begin(auth, ssid, pass, "", 80); //now try to reconnect to Blynk
      if(Blynk.connected())  numberoftimesBlynkReconnected++; // successful reconnect Increment the reconnected count.
  }; // Initiates BlynkTimer.  Why 'initiate'?  Your going to initiate it a gazillion times don't see a timer function definition. this code part of template
  // voidEnd = millis(); // testing only.  not tracking void loops times in normal code
  //averagetimespentinvoidLoop = timeServed(voidStart, voidEnd);  // sends the entry and exit times off for tabulation of time spend in the void loop(). It returns a percentage though. 
  // Functon is broken and no longer needed.
} //end void loop

/*------------------------------- More Functions ----------------------*/
void writeCount()  //Writes to the EEPROM so values stored here can survive a loss of power

  EEPROM.write(eepromAddress, count);         //write value to current address counter address

  Serial.print("Count stored at address ");

  if (eepromAddress == EEPROM.length()) //check if address counter has reached the end of EEPROM
    eepromAddress = 0;              //if yes: reset address counter to wrap around.
} // end of void writeCount ----------------------

/*-------------------------------- Software Keep alive checks if we're still online i.e., on the internet --------------------------*/
//Being on wifi doesn't mean internet.  We may be on a wifi LAN (just the extender) with no internet connection so check 4 sites and if all fail to ping it will return -4.
// then the caller of this function can decide to reset the Arduino and start hunting for wifi again and testing for internet access again.  Better is to live on w/o Internet. Just blink OFF the green LED
int checkonlineStatus (void) { //too frequent and we waste CPU time; too infrequent and you delay finding out you're offline.  Note it takes 12 sec to fail 4 pings waiting 3s per ping. 1.97 cuts to 1 ping
  int pingResult[6]; // size this at least at however many sites you want to ping
  int pingSum = 0; /* a failed ping will return a value of -1 meaning it timed out.  So fail criteria is -4 meaning pings to all four sites timed out.
  //pingResult[1] ="");  // first ping the local router. It should be close to zero ms. commented out because when not on local router it's a 3 sec delay
  and we might miss a mouse.  It seams the Arduino sits and waits for a ping response.  So it would miss anthing in those 3 sec
  //delay(1); // wait 5 ms before pinging the next one.  Seems like a good idea but don't know if necessary.  Speeded up in Rev 1.90 to 1 ms delay each
  Serial.print(" Pinging websites to see if we are online:  "); //
  //pingResult[2] =; // blynk
  //pingResult[3] =; // blynk again ("207,171,166,22") //  why ping same site twice? why not.  Need four and Blynk is a necessary site so if it fails we're done anyway.
  pingResult[4] ="");  // Google's primary DNS. Good a/o 8/30/2021 v1.8  these addresses could change resulting in fail but ok because it takes four (ver 1.97 change this to one) failures to fail the test.
 // delay(1);  // because we were waiting too long for 4 webhosts to time out.
  //pingResult[5] ="");  // good a/o 8/30/2021 v1.
  for (int j = 4; j <= 4; j++) { //just do one
    Serial.print(pingResult[j]); Serial.print(", ");
    pingSum = pingSum + pingResult[j];
  numberoftimeswerancheckforonlinStatus = numberoftimeswerancheckforonlinStatus + 1;
  Serial.print(" Sum of pings to different webhosts = "); Serial.print(pingSum);  Serial.println(" milliseconds total");  //ping webhosts
  Serial.print(" Checked online status "); Serial.print(numberoftimeswerancheckforonlinStatus); Serial.println(" times.");
  if( pingSum <= -1) internetConnection = DOWN;
  else internetConnection = UP; // declare internet UP or DOWN immediately.  Probably don't even need pingSum but leave it for Serial printing.
  return (pingSum); // if any website responds to the ping, we are still online.  Only fail if result is -4 which results if all 4 fail.  Change failure criterion to < 0. Just one ping response should
  // make pingSum positive since it's in ms and I don't think any server will respond in less than 4 ms but not that a local host if used could respond in 0 ms. Don't ping local host.  No need.  We already
  // have a wifi connected indicator and check. See Line ~ 661 and wifi.status command.  
//---------------- end of checkonlineStatus ------------------------*/

/*=============function definition timeServed() ===========  Debug only and it was causing restarts so it probably has memory leak.  Served it purpose.  About 85% of time is spend
in the void loop.  Average pass through takes about 2 ms. In 100 times through it takes a little over 200 ms.
// Gets the % of time spent inside the void loop - It's 84-85%
// Returns percentage. keeps the moving sum of the first n time durations (n is timePeriods)  
// function global variables

int timeSum = 0; //  want this to persist after exiting the function so it's global  It sums up all (100) the durations so the number should be less than 1000.
const int timePeriods = 100; // range over which we track the moving average.  Is it really a moving average?  always summing 1 - 100. no let the timeIndex wrap to 0. unsigned long
int times[100]; // array to track times (ms) spend in void loop()
unsigned long entryTimes[100];  // array of entry times that must persist outside of function
unsigned long timeIndex = 0;  // timeIndex icrements each time through the function.  It must persist.  Let it wrap.  At 2^32-1 it goes back to zero
int n = 0;  // saves the timeIndex reduced modular timePeriods
int prints = 0; // limit how many prints we do
 // ===================== Function definition  ===================
float timeServed (float entranceTime, float exitTime)  {//commented out in 2.1  
  //local variables
  int timespentIn; // the duration of time spent in the void loop most recently executed
timespentIn = exitTime - entranceTime; // milliseconds spent in the void loop() for the last instance only
times[timeIndex] = timespentIn;  //Serial.print ("Time spent in this iteration of void loop = "); Serial.println (timespentIn); Serial.println(millis());
n = timeIndex % timePeriods; // n will be our new index  0  to (timePeriods - 1);  reducings to a manageable integer < timeIndex which is an unsigned long so grows to 2^32 -1 
entryTimes[n] = entranceTime; // place the current iteration's entrance time into the entranceTime array up to timePeriods -1
timeIndex = timeIndex + 1;  // increment the timeIndex each time through
if (timeIndex == 1,000) timeIndex = 0;  //don't let it wrap because it might be blowing up memory
timeSum = 0; // initialize the sum to zero before adding buffer entries
for (int i = 0; i <= timePeriods -1 ; i++)  //add up all entries in times[] array so far.  Assume there are always timePeriods entries in the array even though initially there are fewer while it fills.
    timeSum = timeSum + times[i]; //  in the beginning the array is zero or undefined but it will quickly fill with valid times.  Every time through you sum the last 100 durations of timespentIn
   // this function will calculate a simple moving average over the range defined by timePeriods 
    // entryTimes[timeIndex + 1 ] will have the entry time from the oldest timePeriod because the array wraps so it overwrites the oldest entry ??see next
    // ver 1.97 measures from the first entryTime  entryTimes[1] because entryTimes[i+1] is 100 entries ago and you're not always summing 100 timePeriods; you sum up
    // to the current timePeriod.  But this will diminish the denominator which will increase the percentage calculation???  It's already over 100%. Are the entry times in ms? YES
  float percentvoidTime = timeSum / (millis() - entryTimes[ (n + 1) % timePeriods ]) * 100;   // % time spent in void loop/ (now - oldest entry time) ver 1.97 changed to (now - first entry)
  Try moving these serial prints to mytimerEvent.  
  if( n == (timePeriods - 1) && prints < 4) {
     prints = prints ++; 
   Serial.print( "  timeSum = ");  Serial.println ( timeSum);
   Serial.print ("Now = "); Serial.println( millis());
   Serial.print (" timeIndex = "); Serial.println (timeIndex);
   Serial.print (" entryTimes [ (n+1) % timePeriods ] = "); Serial.println ( entryTimes[ (n + 1) % timePeriods ]); 
 return percentvoidTime; // returns percent of time spent in void loop()
 } // end of timeServed function
------------------------------------------------ */

//  -------------------------------- Look for signals ----------------------- 
 This function checks for the countSig which means we got one and the reset signal which zeroizes count from the local reset button on the display box.
 it debounces it as well. 
    Looking at the count signal only in void loop might miss signals that occur using the myTimer event, so made this a function that can be called from myTimerEvent too
 //   the countSig is asserted by the power box when the triggerSig from the capture tube goes low.  While the triggerSig could be sub-millisecond (like a flick of a tail)
    the countSig is the pulse output from the 555 timer or an RC network depending on VM vintage, but it's relatively long.  Still we make sure it's on for at least 5 ms.
  // mark the time when count signal asserted and vac relay actuated (start another stopwatch)  Idea is to make sure it's activated for at least 5 ms before counting it
  // made into separate function so it can be checked in the void loop() and in the myTimer event.  Need to find out how often it runs. How? Did this and the ans is:  
    It runs every 3 ms on average over 1000 samples.  However if there are a lot of Serial.prints in the myTimer event, especially when using the serial command 'E' to
    print out each individual calls to lookforSignals, this results in an increased in time spent in myTimerEvent, decreasing the run frequency of lookforSignals.  So we
    added a call to lookforSignals in between the Serial.prints in the for loop when printing "Last looked for signals x ms ago" in myTimerEvent 
 void lookforSignals (void) {
 // --------------------------------------  add this debugging code -----------------------------  
   /* In lookforSignals() function do this checking up front.  This code is for debugging and only needs to be temporary.  It’s wasting the time we are trying to save. It tracks
   how frequently this function is called so we can see if it's frequent enough not to miss any signals.  Logic works as follows: for every entry, record the entry time.
   Next entry time subtract previous entry time from it and record the away time in an array, index array pointer. if pointer at max, wrap it around. this fill up an array with
   the times between each call to the function.  myTimerEvent will then take an average and serial.print it as well as any number of individual times to see short and long times.
/* Removed in 2.1 previousarrivalTime_ms = currentarrivalTime_ms; // the old current arrival is now the previous arrival time 
currentarrivalTime_ms = millis();  // record the present arrival time	of when this function has been called
itsbeenAwhile_ms [k] = currentarrivalTime_ms - previousarrivalTime_ms; // how long has it been since you were last here so this is just the interval between visits
k = k + 1;  //  Only do one per visit but do it every time until the itsbeenAwhile array is filled
if(k >= max_i) k = 1;  // save the last max_i visit intervals initially 1000 visits then wrap around

// ------------------------------------ end of special debugging code -----------------------------*/
  if (digitalRead(countSig) == HIGH && countDuration_ms == 0) { // countSig first seen. countDuration_ms is 0 here meaning it's a new event
    countActivated_ms = millis() ;  // mark the time count first activated in ms of uptime. initially countAtivated_ms is 0.
    // Serial.println(" got to Line 1100 countSig HIGH"); //debug
    countDuration_ms = 1; // acknowledges a count signal by kicking it to 1 ms so we should not return here because countDuration_ms not equal 0
  }   // end if countSig

  // update stopwatch as long as count signal is HIGH.  Note first time through milllis() may not have changed so countDuration could be zero
  delay(1); // v1.83so lets delay one count that way countDuration_ms won't get put back to zero.  that may be why there's no message sent ao 2/23/22
  if (digitalRead(countSig) == HIGH && countDuration_ms != 0) { // countSig still high so we're counting
    countDuration_ms = millis() - countActivated_ms ; // record duration since countActivated_ms; this might be knocking countDuration_ms back to zero
  }  // end if count
  else { // only get here on deactivation of countSig (LOW) because the second condition countDuration_ms > 0 here is satisfied.
    countDuration_ms = 0; // reset stopwatch when countSig drops.  Note: countDuration_ms variable only valid when countSig asserted
    hoursON = 0;  // clears time in hours that the countSig has been active.  (Used to detect if vac is stuck on, in which case we send notification and email to user.)
    minutesON = 0;  //ditto in minutes
    countActivated_ms = 0; // reset the starting point
    // this a good time to assert clear tooLongON?  no, it involves a msg send so do it in the myTimerEvent function.
  } //end else duration

  // look for error condition where mouse triggered the break-beam but count signal failed to assert so no vac ON - removed in rev 1.8
  /* if (triggerDuration_ms > 1 && triggerDuration_ms < 1000 && digitalRead(countSig) != HIGH) {  // if not HIGH, but just triggered at least 1 mS ago, there's a problem because vac is not ON, otherwise move on
    // Techncally the vac won't turn on until the relay closes but the count signal driving the relay should be immediate.  note that during test hitting TEST button on display
    // simulates a trigger.  If here, we triggered without turning on Vac.  Build a notify message to report it
    notCounted = 1; // flag for timer to send a message
    } // end i  
   v1.8 removed this because triggerSig no longer looked at by Arduino.  Only the more reliable countSig is used for gotOne.  So it means we can't detect an obstruction
     in the capture tube directly.  But in VM1000 and VM2000, an asserted triggerSig will keep the Vac ON too long and that is a detectable error.  In VM2555 and VM3000 power control box
     the 555 timer circuit will assert the countSig once for its duration, then Vac goes OFF but then not reassert it until the triggerSig clears and reasserts (I think),
     so obstruction is not detected.  This has actually happened twice.  Once was a chipmunk and once a very fat mouse got stuck in the smaller shop vac tube. It's not likely
     to happen with the 2" shop vacs.

  // look for correct condition where Vac signal (CountSig) is ON for at least 5 milliseconds (test button minimum is about 40 ms), then declare we gotOne
  if (countDuration_ms  > 5) {  // on for at least .5 msec.  Not  5s on VM3000SN2 because there's no 555 timer.  Duration of Count signal determined by RC time
    //  constant on base of Q2 and it may only last one or two seconds  *Threhold changed to 500 ms in v1.8 then to 5 ms later
    countAsserted = 1;  // record that it was asserted because only when count goes down do we claim gotOne
  } // end if > 5
  if (countDuration_ms == 0 && countAsserted == 1) { // it was up long enough, now it's down so count it.  Note the display counter in incremented in hardware
    // only count it when it returns to zero
    // Serial.println("We got here on Line 1146.  countduration = 0 countAsserted = 1");
    gotOne = 1;  // take care to only increment variable count once per capture.  It's going to stay ON for over 5 sec or longer (maybe 10 more seconds)
    countAsserted = 0;  // reset the flag because we are counting it now.
    Serial.println(" gotOne = 1 at Line 1153"); // leave this one Serial.print
  } // end if countDuration_ms == 0 && cA == 0

  // so we want to send only one set of messages too.  When does gotOne reset? After we send the notification but this code will get hit again after that possibly.
  // ok I think this will work. Only send message if gotOne is TRUE and after count signal asserted then negated. countAsserted boolean
  // only gets reset when the count signal clears

  // look for user hitting reset button on display box
  if (digitalRead(resetSig) == LOW) {  //look for the reset signal (push button on display box)
    while (digitalRead(resetSig) == LOW) {
      /* Wait here for it to release. When released, it has set resetCount boolean to HIGH (many times).
        The reset signal from VM display box push button is active low.  Just tie it to D4. It's in the loop() so we check it all time here.
        Digital Input pin 4 normally is set as INPUT_PULLUP so it normally sees HIGH but on closure it sees LOW or GND.
      resetCount = HIGH;  //flag to timer loop to reset the internal count.  The display counter gets reset to zero in hardware
    }  //end while resetSig
  } // end if resetSig
    numberoftimeswelookedforSignals = numberoftimeswelookedforSignals + 1;
  } // end of lookforSignals
  /* ---------------------------- displayonLED --------------------------
  //  Maybe next version  2.01 make digitalWrite(countSig) and digitalWrite(resetDisplay) callable functions.
  // Names?  void displayonLED (numbertoDisplay) and void zeroizeLEDdisplay ().  Where displayonLED would temporarily set
  pinMode D7 to output
  // Pass a number to display as an input.  resetDisplay need no input parameter, it always only zeroizes.  displayonLED would even use zeroizeLEDdisplay
  // It would get used too show firmware version, show restored count, maybe whenever set_a_count is sent down,  and when reset count is sent down.
  This is the displayonLED function */
  void displayonLED (int numbertoDisplay) {
  //First zeroize whatever was on the LED display.
  zeroizeLEDdisplay(); // resets display to zero
  // Spin up the counter to display the numbertoDisplay, which could be the firmware version number, or restoring the stored count.
  int countBumper; // used to bump up count on the display
  pinMode(countSig, OUTPUT); // pin 7 temporarily configured as output. Maybe this takes time? - No.
  delay(pinmodeDelay); // this is extra but the five lambdas below made the first one or two count bumps work.  
  // if true and this is the fix, we could speed up the countBumper and get to the numbertoDisplay faster. Create a pinMode delay constant
  // so it can be independently adjusted while we increase the countBump spin up frequency by shortening lambda.  Not the problem.  Adding a diode to allow
  // this pinmode change (by isolating it from other source) caused Q3's base to be unbiased.  Added voltage divider and fixed the problem.
  digitalWrite(countSig, LOW); // initialize countSig to inactive
  delay(5 * lambda);  // all other countBumps don't need this delay.  but it's only one time here. Not needed with the HW fix but leave it.
  for (countBumper = 1; countBumper <=  numbertoDisplay; countBumper ++) {
    digitalWrite(countSig, HIGH); // should increment display counter  
    delay(lambda);  // the YB5135C counter will work with frequencies as high as 2000Hz which is 0.5 ms per count.  Reduce duty cycle to 33%.
    digitalWrite(countSig, LOW); // but you have to bring the signal down too.  A cycle of HIGH then LOW is one count.
  }  // endfor
 // Put D7 back as an input.
   pinMode(countSig, INPUT); // restore D7 as an input
  } // end of displayonLED
  /* ---------------------------------------  zerize LED display ----------------- 
  This sends an active-high pulse out on the Arduino pin D6 always configured as output
  In hardware D6 is inverted by Q4 because both display types use active-low reset signals
  right now we share the use of lambda.  It takes three lambda's to ensure a good pulse for reset
  lambda is also used to set pulse duration of count+ increments to LED display and probably should be
  defined separately but for now, we share lambda. Q4 has a 2.2k pullup resistor to +5v that's probably unnecessary since the displays 
  pull it up internally.  This may be slowing Q4 down when going low because of Ic would be around 2.5 ma. Not likely.
  void zeroizeLEDdisplay() {
  digitalWrite(resetDisplay, HIGH);  
  delay(3 * lambda);
  digitalWrite(resetDisplay, LOW);
  delay(3 * lambda);
  } // end of zeroizeLEDdisplay

@Dana Please edit your post, using the pencil icon at the bottom, and add triple backticks at the beginning and end of your code so that it displays correctly.
Triple backticks look like this:

Copy and paste these if you can’t find the correct symbol on your keyboard.

Edited to add…
In fact, you’d probably be better to delete these posts and use a free service such as Pastebin to share your code.


Your sketch is never going to work consistently with Blynk in its current format.

Your void loop is one of the worst I’ve ever seen, and doing all this elapsed millis comparison stuff instead of using BlynkTimer or SimpleTimer complicates things enormously. You should read this…

You’d also be far better using a decent board for your project instead of an Uno WiFi, and if you need all of this functionality then maybe using both cores of an ESP32 might be a better option.

However, there are much simpler ways of doing some of this stuff. Instead of using serial monitor user input you’d be better using Blynk to input these choices/parameters.
You’d also be better-off using the Blynk server to store the data currently being written to EEPROM and retrieving it via BLYNK_ CONNECTED and Blynk…syncVirtual.


Thanks. I’ll put in some more timers and eliminate code in void loop for starters.

void loop()  { // main part of program
  if (Blynk.connected());  // i don't see this defined anywhere.  what happens here? all the Blynk magic it says.  
  //I think it tries to connect and if offline, it tries for 30s
  else  { // should never happen that Blynk not connected here.
   Blynk.begin(auth, ssid, pass, "", 80); //now try to reconnect to Blynk
      if(Blynk.connected())  numberoftimesBlynkReconnected++; // successful reconnect Increment the reconnected count.
  }; // Initiates BlynkTimer.  Why 'initiate'?  Your going to initiate it a gazillion times don't see a timer function definition. this code part of template
} //end void loop

I added timers and here’s the void loop now. I left conditional on Blynk.connected because when device goes offline and attempts to execute, it hangs if it can’t communicate with Blynk server. Is there a better way to do this?

BTW - still getting disconnects. Does executing Blynk.connected in the void loop () constitute flooding the site?

Yes, manage your internet connection yourself (which you’re already doing) then use Blynk.config and Blynk.connect.
You still need a conditional test in your void loop, as executing will instigate a connection attempt and cause the Blynk.connect default timeout (or the optional timeout that you specify) to occur each time.

BTW, your use of a manual internet connection along with Blynk.begin is very odd, as the first thing that Blynk.begin dies is to create its own internet connection, then attempt to connect to the Blynk server. As Blynk.begin is a blocking function, if that connection to the server cannot be made then all code execution will halt at that point, requiring a reboot to overcome the issue.

The success of your code also depends on what is happening in your timed functions. If they take longer to execute than the Blynk heartbeat period then the server will drop the connection to the device and it will go offline.