Beginner (solved) - Flow meter and store variable

Hello All.

I have a Particle Photon and my main idea is to be able to read my water consumption.
There have been some good threads here that i have read so i got it working but i need an additional variable
that stores the consumption.

var consumption = how much water have passed thru the last hour;

The idea is that every hour i want this variable to be sent out to the cloud so i can see how much water have been used the last hour or day etc.

Why want this is because i have a filter that cleans water but there is no way to tell when the filter is going bad.
I know for a fact that the filter can take 300k liter of water before its bad.

So i want to know approximately how much water i use a day and also when i am close to the tresshold of 300k i want an email,sms,warning.

So i started out with connecting a water flow meter (https://www.adafruit.com/products/828)
and by other threads here i use this code.

/*
    Water flow sensor test sketch

*/


unsigned long oldTime;
volatile unsigned int WaterPulseCount = 0;

// conversion from pps to litres, plastic sensor (485 for metal)
const float pulsesPerLiter = 450;

// Spark Digial Pin D3 (D2 did not work)
#define WATER_SENSOR_PIN D3    // Water sensor digital pin

// Define Spark variable - not sure "float" type works so define as INT
// so decimal is shifted left with * 100 (so xx.yy becomes xxyy)
float liters = 0;
char liters_S[20];

//-----------------------------------------------------------------------------
// Water Sensor interrupts
//-----------------------------------------------------------------------------
void WaterPulseCounter(void)
{
    // Increment the water pulse counter
    //detachInterrupt (WATER_SENSOR_PIN) ;
    WaterPulseCount++;
    //attachInterrupt(WATER_SENSOR_PIN, WaterPulseCounter, FALLING) ;
}


void setup()
{
  Serial.begin(9600);
  
  Particle.variable("litersS", liters_S, STRING);

  // Set Digital pin WATER_SENSOR_PINT to INPUT mode and set
  // interrupt vector (water flow sensor) for FALLING edge interrupt
  pinMode(WATER_SENSOR_PIN, INPUT);
  attachInterrupt(WATER_SENSOR_PIN, WaterPulseCounter, FALLING) ;
  oldTime = millis();
}


void loop()
{
  unsigned long t;
  static unsigned int pc;

  t = (millis() - oldTime);
  if(t >= 1000)                // Only process counters once per second
  {
    //Read water sensor pulse count and process
    if (WaterPulseCount != 0)        // Do nothing if water is not flowing!
    {
    detachInterrupt (WATER_SENSOR_PIN);    // Disable water flow interrupt to read value
    //Calculate litres and adjust for 1 sec offset, if any
    liters = (WaterPulseCount / pulsesPerLiter) * (t / 1000);
    oldTime = millis();                // Reset base delay time
    pc = WaterPulseCount;
    WaterPulseCount = 0;            // Reset the water pulse counter
    attachInterrupt(WATER_SENSOR_PIN, WaterPulseCounter, FALLING);

    sprintf(liters_S, "%4.3f", liters);
    
    Serial.print("WaterPulseCount= ");
    Serial.print(pc);
    Serial.print(", liters= ");
    Serial.print(liters,3);
    Serial.print(", liters_S= ");
    Serial.println(liters_S);
    }
  }
}

There are some part here that i dont understand but we can take that later on on a different thread.

I thought this function would do so sprintf(liters_S, "%4.3f", liters); but it just print the value of the liters again.
So how do i do to not reset the value all the time so i can see how much water has been used for a whole day.

As it is now it prints the value every second but it goes back to “0” all the time.

Sorry for my beginner questions.

Many thanx
Emilkl

Your resetting of the count happens in this line

  WaterPulseCount = 0;

and liters is always recalculated (not incremented) here

  liters = (WaterPulseCount / pulsesPerLiter) * (t / 1000);

But liters does not actually tell the amount of water counted ([l]) but the rate of water throughput ([l/h]).
Only if this rate were constant for a whole hour you could use this as a amount reading ([l/1]).
To get the amount, you’d need to accumulate all WaterPulseCount readings and at the end of each hour you’d calculate the over all water usage of that hour.

To accomplish this you could do the following

// add a global variable for the accumulated pulse count
unsigned int accumPulseCount = 0;
// and a helper variable for the hour timing
unsigned long msStartOfHour = 0;
...
void loop()
{
  ...
  if(t >= 1000)                // Only process counters once per second
  {
    //Read water sensor pulse count and process
    if (WaterPulseCount != 0)        // Do nothing if water is not flowing!
    {
      // replace this
      //detachInterrupt (WATER_SENSOR_PIN);    // Disable water flow interrupt to read value
      ////Calculate litres and adjust for 1 sec offset, if any
      //liters = (WaterPulseCount / pulsesPerLiter) * (t / 1000);
      //oldTime = millis();                // Reset base delay time
      //pc = WaterPulseCount;
      //WaterPulseCount = 0;            // Reset the water pulse counter
      //attachInterrupt(WATER_SENSOR_PIN, WaterPulseCounter, FALLING);
      // with this (to shorten periode not watching interrupts)

      oldTime = millis();                // Reset base delay time
      detachInterrupt (WATER_SENSOR_PIN);    // Disable water flow interrupt to read value
      pc = WaterPulseCount;
      WaterPulseCount = 0;            // Reset the water pulse counter
      attachInterrupt(WATER_SENSOR_PIN, WaterPulseCounter, FALLING);
      //Calculate litres and adjust for 1 sec offset, if any
      liters = (pc / pulsesPerLiter) * (t / 1000);
      accumPulseCount += pc;  // accumulate the readings
      ...
    }
  }

  if (millis() - msStartOfHour > 60*60*1000)
  {
    msStartOfHour = millis(); // start a new hour

    float hourLiters = accumPulseCount / pulsesPerLiter;
    accumPulseCount = 0;  // reset counter
    Particle.publish("HourlyWaterReport", String::format("%4.3f l", hourLiters));
  }
  // for a day act accordingly
}

The above does not take into account that your device might reboot and loose all readings, so doing a accumulation over a day might only be useful in conjunction with some way of persisting your readings (e.g. EEPROM, SD, FRAM, online).

@emilkl, great advice from @ScruffR! In my water meter I accumulate pulses for 1 second and calculate the equivalent flowrate and volume for that second. Every second I also calculate average and peak hourly flowrates, I accumulate total volume in a set of globals for hourly (array of 24) and daily volumes. Everything is calculated as floats/doubles where necessary but stored as integers with two decimals shifted over by multiplying by 100. This keeps data small and easy to send to the cloud or server. It is also easy to construct a publish string using sprintf().

1 Like

Hello and thanx.

I think you made a mistake

  if (millis() - msStartOfHour > 60*60*1000)
  {
    msStartOfLastHour = millis(); // start a new hour 

I do believe you mean that msStartOfLastHour should be msStartHour, or am i missing something?

Else it appears to be working fine :slight_smile: many thanx.

I was wondering one thing, since the code is only sending the value if water is flowing i asume that that if the water is flowing for 20 seconds then stop and dont start untill the next day i will not be able to see that 20 seconds of water untill next day? Or does the code stops in the IF sentence and will send that 20 seconds after 1 hour anyway?

BR
emilkl

Good catch, that was a mistake.

But no for the other bit :wink:
If you look of the indentation level of my final if block, it’s not part of the if (t >= 1000) nor of the if (WaterPulseCount != 0) blocks.
So you’ll get a publish each hour reporting once your 20sec amount and thereafter zero liters.
But to make this a bit clearer I added the curly braces.

aa that is correct, i suppose my copy paste made it faulty :slight_smile:

Again many thanx for all the help. my problem is solved.

1 Like