Flow Meters sporadically log flow when there is none - Otherwise works perfectly

It’s still losing several liters every time this glitch reconnect happens.

In the below log you can see the tail end of valid water use and WASHpulseCount at 15:00, then at 15:56 it reconnects and retrieves the last known water levels. I’ve noticed that it seems the first reconnection event, it retrieves the correct values. (15.20 and 9.85) - But on subsequent reconnects it loses 2.69 liters of drinking water and 1.3 liters of wash water. Note it loses the same amount on each reconnect, and both counts are affected… at 8:13 this morning, valid pulses counting again.

I’m at a total loss…

[15:00:56:796] Sending Values to Server␍␊
[15:00:56:830] Drinking Water = 15.20␍␊
[15:00:56:830] Wash Water = 9.85␍␊
[15:00:56:864] WASHpulseCount = 1␍␊
[15:00:57:730] Flowing: 0.0L/min␍␊
[15:00:57:730] Flowing: 0mL/Sec␍␊
[15:00:57:764] Washing Water Remaining: 9.85L␍␊
[15:00:57:797] Sending Values to Server␍␊
[15:00:57:797] Drinking Water = 15.20␍␊
[15:00:57:832] Wash Water = 9.85␍␊
[15:56:09:513] [178186516] Connecting to 192.168.10.100:8080␍␊
[15:56:09:545] [178186528] Ready (ping: 8ms).␍␊
[15:56:09:663] Retrieving levels from server␍␊
[15:56:09:663] 15.20␍␊
[15:56:09:684] 9.85␍␊
[16:37:30:275] [180667215] Connecting to 192.168.10.100:8080␍␊
[16:37:30:308] [180667222] Ready (ping: 4ms).␍␊
[16:37:30:419] Retrieving levels from server␍␊
[16:37:30:419] 12.51␍␊
[16:37:30:440] 8.55␍␊
[21:24:06:254] [197862755] Connecting to 192.168.10.100:8080␍␊
[21:24:06:286] [197862763] Ready (ping: 5ms).␍␊
[21:24:06:399] Retrieving levels from server␍␊
[21:24:06:399] 9.81␍␊
[21:24:06:420] 7.25␍␊
[08:13:26:477] DRINKpulseCount = 1␍␊
[08:13:26:511] DRINKpulseCount = 2␍␊
[08:13:26:511] DRINKpulseCount = 3␍␊

Interestingly, I just did the math on the losses logged on my post from Sept 11, and the losses are the same on every reconnect. 0.97L of drinking water and 1.03L of wash water.

To clarify,

  1. The first re-connection event it retrieves the correct values.
  2. Then, the losses are the same on every reconnection event in that set.
  3. The losses vary from set to set… It seems like about 2 days between sets.

Both these pins will pulse HIGH then LOW at device boot… perhaps that is being interpreted as flow count? I do know (while testing another button sync sketch) that even when the code is timed out with something else (a Blynk.connect() timeout in my case),any triggered interrupts during the timeout did process later after the code started running again.

If so, perhaps some form of timeout timer that delays attaching the interrupts and/or any flow reading until a second or two after boot.

Here is what I’ve been using and I haven’t had that issue with it.

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include <ArduinoOTA.h>
#include <WidgetRTC.h>

//LittleFS includes
#include <ArduinoJson.h>
#include "FS.h"
#include <LittleFS.h>

char auth[] = "";
char ssid[] = "";
char pass[] = "";

byte sensorInterrupt = 4;  // 0 = digital pin 2
byte sensorPin       = 4;

// The hall-effect flow sensor outputs approximately 4.5 pulses per second per
// litre/minute of flow.
float calibrationFactor = 2.25;

volatile byte pulseCount;                    //must be a volatile variable because triggered in a interrupt routine

float flowRate;
unsigned int flowMilliLitres;
unsigned long totalMilliLitres;
unsigned long totalLitres;

String currentTime;
String currentDate;
String yesterDate;

int ReCnctFlag;
int ReCnctCount;

int SThour;
int STmin;
int STsec;

bool litersResetFlag = 1;
bool flowNotifyFlage = 0;

unsigned long oldTime;

BlynkTimer timer;
BlynkTimer timer1;
WidgetRTC rtc;
WidgetTerminal terminal1(V8);
WidgetTable table;

BLYNK_ATTACH_WIDGET(table, V1);

int rowIndex;

// Set your Static IP address
IPAddress local_IP(192, 168, 0, 42);
// Set your Gateway IP address
IPAddress gateway(192, 168, 0, 1);

IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8);   //optional
IPAddress secondaryDNS(8, 8, 4, 4); //optional

bool loadData() {
  File dataFile = LittleFS.open("/data.json", "r");
  if (!dataFile) {
    Serial.println("Failed to open data file");
    return false;
  }

  size_t size = dataFile.size();
  if (size > 1024) {
    Serial.println("Data file size is too large");
    return false;
  }

  // Allocate a buffer to store contents of the file.
  std::unique_ptr<char[]> buf(new char[size]);

  // We don't use String here because ArduinoJson library requires the input
  // buffer to be mutable. If you don't use ArduinoJson, you may as well
  // use configFile.readString instead.
  dataFile.readBytes(buf.get(), size);

  StaticJsonDocument<100> doc;
  auto error = deserializeJson(doc, buf.get());
  if (error) {
    Serial.println("Failed to parse config file");
    return false;
  }

  totalMilliLitres = doc["totalMilliLitres"];
  yesterDate = doc["yesterDate"].as<String>();   ////Note: loading as string from LittleFS
  return true;
}

bool saveData() {
  StaticJsonDocument<100> doc;

  doc["totalMilliLitres"] = totalMilliLitres;
  doc["yesterDate"] = yesterDate;                ////Note: but to save does not work to load as<String>..
  
  File dataFile = LittleFS.open("/data.json", "w");
  if (!dataFile) {
    Serial.println("Failed to open config file for writing");
    return false;
  }

  serializeJson(doc, dataFile);
  return true;
}

void ICACHE_RAM_ATTR pulseCounter()
{
  // Increment the pulse counter
  pulseCount++;
}

void setup()
{
  pinMode(sensorPin, INPUT);
  digitalWrite(sensorPin, LOW);
  WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS);
  WiFi.begin(ssid,pass);
  Blynk.config(auth);
  Blynk.connect();
  ArduinoOTA.setHostname("flowmeter_v5pnt4");
  ArduinoOTA.begin();

  if (!LittleFS.begin()) 
  {
    Serial.println("Failed to mount file system");
    return;
  }

  if (!loadData()) 
  {
    Serial.println("Failed to load Data");
  } 
  else 
  {
    Serial.println("Data loaded");
  }

  timer.setInterval(1000L, sendValues);
  delay(100);
  timer.setInterval(1000L, printTIME);
  delay(100);
  timer.setInterval(1000L, WiFistrength);
  delay(100);
  timer.setInterval(10000L, checkcounterReset);
  
  
  digitalWrite(sensorPin, HIGH);

  setSyncInterval(720*60);

  pulseCount        = 0;
  flowRate          = 0.0;
  flowMilliLitres   = 0;
  //totalMilliLitres  = 0;
  oldTime           = 0;

  // The Hall-effect sensor is connected to pin 2 which uses interrupt 0.
  // Configured to trigger on a FALLING state change (transition from HIGH
  // state to LOW state)
  attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
  
}

BLYNK_CONNECTED()
{
  rtc.begin();
  ReCnctCount = 0;
  printTIME();
  Blynk.syncVirtual(V9);
  //Blynk.syncVirtual(V0);
  timer.setTimeout(5000L,[](){                                   //Lambda "Reconnection" timer function
      terminal1.println(" ");
      terminal1.println(String ("Connected ") + currentTime + " " + currentDate);
      terminal1.println(String ("Yesterdate ") + yesterDate);
      terminal1.flush();
    });
    
  Blynk.syncVirtual(V12);
  Blynk.syncVirtual(V7);
}

void loop()
{
  ArduinoOTA.handle();
  Blynk.run();
  timer.run();
  
  if (Blynk.connected())
  {                                             //If Blynk connected run as normal
    Blynk.run();
  }
  else if (ReCnctFlag == 0){                                          //test connection flag
    ReCnctFlag = 1;                                                 //set connection flag
    timer.setTimeout(60000L,[](){                                   //Lambda "Reconnection" timer function
      ReCnctFlag = 0;
      ReCnctCount++;                                                //count up reconnection attempts
      Blynk.connect();                                              //try to connect again
    });
  }
}

void sendValues()
{
    detachInterrupt(sensorInterrupt);
       
    // Because this loop may not complete in exactly 1 second intervals we calculate
    // the number of milliseconds that have passed since the last execution and use
    // that to scale the output. We also apply the calibrationFactor to scale the output
    // based on the number of pulses per second per units of measure (litres/minute in
    // this case) coming from the sensor.
    flowRate = ((1000.0 / (millis() - oldTime)) * pulseCount) / calibrationFactor;
   
    // Note the time this processing pass was executed. Note that because we've
    // disabled interrupts the millis() function won't actually be incrementing right
    // at this point, but it will still return the value it was set to just before
    // interrupts went away.
    oldTime = millis();
   
    // Divide the flow rate in litres/minute by 60 to determine how many litres have
    // passed through the sensor in this 1 second interval, then multiply by 1000 to
    // convert to millilitres.
    flowMilliLitres = (flowRate / 60) * 1000;
   
    // Add the millilitres passed in this second to the cumulative total
    totalMilliLitres += flowMilliLitres;
     
    unsigned int frac;
   
    // Reset the pulse counter so we can start incrementing again
    pulseCount = 0;
   
    // Enable the interrupt again now that we've finished sending output
    attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
    totalLitres = (totalMilliLitres/1000);
    Blynk.virtualWrite(V0, totalMilliLitres);
    Blynk.virtualWrite(V1, totalLitres);
    Blynk.virtualWrite(V6, flowRate);
    
    if(flowRate >= 3.1 && flowNotifyFlage == true)
    {
      Blynk.notify(String ("Waters flowing @ ") + flowRate + (" L/Min"));
      flowNotifyFlage = false;
      timer.setTimeout(3600000L,[]()
      {                                   //after one hour reset notify
        flowNotifyFlage = true;
      });
    }
    
  if (!saveData())                         //save mL to little FS
  {
    Serial.println("Failed to save Data");
  } 
  else 
  {
    Serial.println("Data saved");
  }
 
}

void checkcounterReset()                         //time input
{
 if(hour() == SThour)
 {                           //daily clear total liters
  if((minute() >= STmin) && (minute() <= (STmin + 50)))
  {
    if(litersResetFlag == 0)
    {
      litersResetFlag = 1;                           //change flag so only exicute once
      Blynk.notify(String ("Yesterdays water ") + (totalMilliLitres / 1000));

    
      if(totalMilliLitres >= 1000)
      {
        Blynk.virtualWrite(V10, "add", rowIndex , yesterDate + String("  Water received"),(totalMilliLitres/1000) + String(" L")) ;
        Blynk.virtualWrite(V10, "pick", rowIndex);
        yesterDate = currentDate;
        terminal1.println(" ");
        terminal1.print(yesterDate);
        terminal1.flush();
        
        if (!saveData()) 
        {
          Serial.println("Failed to save Data");
        } else {
        terminal1.println(" ");
        terminal1.print("Saved LittleFS");
        terminal1.flush();
        }

      }
      else
      {
        Blynk.virtualWrite(V10, "add", rowIndex , yesterDate + String("    No Water"),(totalMilliLitres/1000) + String(" L")) ;
        Blynk.virtualWrite(V10, "pick", rowIndex);
        terminal1.println(" ");
        terminal1.print(yesterDate);
        terminal1.flush();
        yesterDate = currentDate;
        terminal1.println(" ");
        terminal1.print(yesterDate);
        terminal1.println("");
        terminal1.flush();

        if (!saveData()) 
        {
          terminal1.print("Failed to save Data");
          terminal1.flush();
        } else {
          terminal1.print("Data saved");
          terminal1.flush();
        }
      }
      
      rowIndex++; 
      totalMilliLitres = 0;
      Blynk.virtualWrite(V6,totalMilliLitres);
      Blynk.virtualWrite(V12, rowIndex);
    }
  }
 }

 
 
 if(hour()== 23 && litersResetFlag == 1){                            //change flag so that counter can be reset next day
    if(minute()>= 01){
      litersResetFlag = 0;
      yesterDate = currentDate;
      terminal1.println(" ");
      terminal1.print(yesterDate);
      terminal1.println(" ");
      terminal1.println(String ("Changeflag ") + currentTime + " " + currentDate);
      terminal1.flush();
      Blynk.notify("ChangeFlag");

      if (!saveData())                         //save mL to little FS
      {
        Serial.println("Failed to save Data");
      } 
      else 
      {
        Serial.println("Data saved");
      }
    }
  }
}

void printTIME()                                //what it says!
{
  currentDate = String(day())+ "/" + month();
  currentTime = String(hour())+ ":" + minute() + ":" + second();
  Blynk.virtualWrite(V4, currentTime);
}

void WiFistrength()                              //prints wifi strength
{
  long rssi = WiFi.RSSI();           
  Blynk.virtualWrite(V5,rssi);
}

BLYNK_WRITE(V0)
{
  totalMilliLitres = param.asInt();
}

BLYNK_WRITE(V2)
{
  if (param.asInt())
  {                             //manual reset Liters
    totalMilliLitres = 0;
    Blynk.virtualWrite(V1, totalLitres);
  }
}

BLYNK_WRITE(V3)
{
  if(param.asInt())
  {                             //manual uptick for testing
    totalMilliLitres += 1000; 
    Blynk.virtualWrite(V0, totalMilliLitres);
    Blynk.virtualWrite(V1, totalLitres);
  }
}

BLYNK_WRITE(V7)
{
  if(param.asInt())
  {
    flowNotifyFlage = false;
  }else{
    flowNotifyFlage = true;
  }
}

BLYNK_WRITE(V9)
{
  TimeInputParam t(param);                        //time input
  SThour = t.getStartHour();
  STmin = t.getStartMinute();
  
  litersResetFlag = 0;

  terminal1.println(" ");
  terminal1.println(String("Counter reset time ") + t.getStartHour() + ":" +
               t.getStartMinute());
  terminal1.flush();   
  
}

BLYNK_WRITE(V11)
{
  if (param.asInt()){                             //manual reset esp if needed
    Blynk.notify("Reseting ESP in 5s");
    timer.setTimeout(5000L,[]()
      {                                   //after one hour reset notify
         ESP.reset();
      });
  }
}

BLYNK_WRITE(V12)
{
  rowIndex = param.asInt();
}

BLYNK_WRITE(V14)
{
  if (param.asInt())
  {
    //Blynk.notify("ResetFlag");
    litersResetFlag = 0;
    Blynk.virtualWrite(V14, 0);
  }
}

I don’t claim to understand all of it, I got it off of another’s project here on the forum. This also uses LittleFS to store the mL every time it figures it and it worked pretty good with reboots and disconnects.

EDIT: I’m guessing you would want to remove the daily reset.