Measuring time between pump runs

I’m using an e-tape liquid measuring sensor to measure the height of the bilge in my boat. I have a simple alarm set up to alert me in case the bilge high water switch does not activate and empty the bilge. I would like to measure the rate of filling and number of times the bilge pump runs in a 24 hr period (as indicated by the fill/empty cycle). Below is a screen capture of the sensor readings from Ubidots. You can see the fill/empty cycle takes about 1hr 20 min. Can anyone suggest a programmatic way to time and count the interval between pumping cycles? (I can measure the volume of water pumped out during the cycle and do some math to get the filling rate)
*note; the bilge is filling as the readings decrease to the bottom right. When the values get to around ~2000 the pump fires and empties the bilge where it reads around ~2200 at empty

Not quite sure what you’re asking :confused:

You want to take the current time of the RTC for each instance of the level to rapidly increase from near zero to near 2000 and calculate the difference, right?
The time between these two instances is Time.now() - lastTime in seconds and subsequently you’ll set lastTime = Time.now(). If you are sending the device to sleep in between, you should have lastTime marked as retained uint32_t.

I think you have it right. If the bottom of the peak, just before the pump runs is T1, and the shutdown of the pump and subsequent resumption of filling is T2 then whenever T2-T1>100 this indicates a pump cycle. How would you constantly evaluate a new value to the last value and initiate the time calculation (and increment a counter to count pump cycles)?

That depends on how you do the individual measurements and what you do in between, but for safe measure I'd also use a global retained int lastReading = 0; to keep track of the last reading along side the last cycle starting time.
You could also use an array of lastReadings[x] (as ring buffer) to calculate a moving average over x measurements.

I do something similar with Sewage Pump Stations, but I have CT’s that measure the amps from the pumps.
I set 2 variables (PumpStart & PumpStop) with millis() to calculate the Elapsed Time for each Fill Cycle and Pump Cycle.
Once you know the Cycle Volume (or “Drawdown” volume), you can calculate the Influent and Effluent flow rates in GPM. If the pumping time is relatively long, you simply add [Influent GPM * Pumping time] to the calculated Pumping volume for accurate Effluent GPM results.

In other words : The Drawdown volume is constant, but you add the amount of influent water that occurred during the Pumping Time, unless the pumping time is short.

GPM * Elapsed Time = GALLONS per cycle.

Reporting the Total Gallons that your bilge removed in 24 hours is much better information than how many times the Pump Started. If a major leak occurred, your total Gallons Pumped would increase, but your # of Pump Cycles would actually decrease. You may not know your hull was compromised with # of Starts, but you would if you report Total Gallons Pumped in the last 24 hours.

If you have trouble accurately detecting a PumpOn/PumpOFF event, you could look at measuring the AMPS with something like : Current Sensor for $20 for your 12v Bilge Pump.

Measuring the Pump Directly allows you to set a RunTime Alarm also. For Instance, If your bilge pump runs for a few minutes straight, then you may have a major problem…compromised hull, pump clogged, etc.

1 Like

Thanks for the feedback. I have a couple of current sensors waiting for me to figure them out. In the mean time I’m struggling with measuring time in between pump runs. Appreciating Rftop’s comments, this is mostly a learning exercise.
As the code is working today, the pump counter is incrementing properly indicating that the trigger is functioning as expected. I put a couple lines of code in to mark the times when the pump runs and calculate the time from the last pump run. What I see happening is that the time counter (interval) is accumulating and not zeroing out and resetting at each pump run. What I expect to happen is to get a value for the time interval between times the trigger executes.
I’d appreciate any help (and suggestions on basic coding practices) you might have.

> // This #include statement was automatically added by the Particle IDE.
> #include <Ubidots.h>

> // the value of the 'other' resistor
> #define SERIESRESISTOR 547.0  //value of supplied resisitor   
>  
> // What pin to connect the sensor to
> #define SENSORPIN A0

> //token for ubidots
> #define TOKEN "123456789" 

> Ubidots ubidots(TOKEN);

>     // set global variables
> double reading = 0.0;
> double average = 0.0;
> double totalPumpRuns = 0.0;                         // total number of pump runs
> int oldVal = 0;                                     // make var available for calc pump runs
> unsigned long interval = 0;                         // time between pump runs
> unsigned long oldTime = 0;                          // initializes variable
> unsigned long TimeNow = 0;
> unsigned long lastInt = 0;                         // time between pump runs


> void setup(void) {
>     Particle.variable("bilgeLevel", average);		//make bilge level avail via var
>     Particle.variable("PumpRuns", totalPumpRuns);
>     Particle.variable("CycleTime", lastInt);       //make number of pump runs avail via var
>     Serial.begin(9600); 
> }

> void loop(void) {

>     //averages 30 readings 1 seconds apart
>   for (int i=0; i<30; i++) {
>         reading += analogRead(SENSORPIN);
>         delay(1000);
>     }
>   average = reading /30.0;
>   Serial.print("rawAverage ");
>   Serial.println(average);
>   reading = 0.0;                                    //resets counter


>   // convert the value to resistance
>   average = (4096.0 / average)  - 1;
>   average = SERIESRESISTOR / average;
>   Serial.print("Sensor resistance ");
>   Serial.println(average);

>   // count number of times pump runs and time interval between pump runs

> int newVal = average;                               // read the bilge level into newVal:

>   if(oldTime = 0)                                   // check to see if oldTime has been initialized
>   {
>     oldTime = millis();                             // set oldTime to current time
> }

>   if((newVal - oldVal) > 100)                       // Trigger: compare reading to old value, over 100 indicates pump ran
>   {
> 	totalPumpRuns =  totalPumpRuns + 1;             // increment total number of pump runs
> 	TimeNow = millis();				                // get current time
>     interval = 0;					                // set interval to 0
> 	interval = TimeNow - oldTime;                   // calculate interval between last pump run and current run
> 	oldTime = TimeNow;				                // set oldTime to current time
> }
>     
>     lastInt = interval;
>     oldVal = newVal;                                // get current value into oldvalue for next comparison

>   ubidots.add("bilgeLevel", average);               //define bilge level as ubidot var
>   ubidots.add("pumpRuns", totalPumpRuns);           //define # of pump runs as ubidot var
>   ubidots.add("cycleInterval", lastInt);           //define time between pump runs as ubidot var
>   ubidots.sendAll();                                //send vars to ubidot
>   

>   //Section for High Bilge Alarm
> if (average <= 2000) {
>         Particle.publish("BilgeAlert", "High Bilge level Detected", 60, PRIVATE);
>     }

>     // Publish temp var for reading remotely
>   //Particle.publish("bilgeLevel", String(average));
>   
> }

I'm not too surprised looking at that code

  if(oldTime = 0)                                   // check to see if oldTime has been initialized
  {
    oldTime = millis();                             // set oldTime to current time
  }

  if((newVal - oldVal) > 100)                       // Trigger: compare reading to old value, over 100 indicates pump ran
  {
    totalPumpRuns =  totalPumpRuns + 1;             // increment total number of pump runs
    TimeNow = millis();				                // get current time
    interval = 0;					                // set interval to 0
    interval = TimeNow - oldTime;                   // calculate interval between last pump run and current run
    oldTime = TimeNow;				                // set oldTime to current time
  }

You do reset interval but immediately set it again to a non-zero valur since oldTime will be long enough in the past that the difference will be > 0.

The other thing is that you always reset oldTime with this line

  if(oldTime = 0)  

This should acutally be

  if(oldTime == 0)  

Thanks for the feedback. My thinking in resetting interval to 0 prior to calculating the interval was to clear out any accumulating time so it wouldn’t add to the previous interval time. Is that not correct or is there a better way to do it?
Similarly I found that the pump had to cycle once to get the oldtime variable to represent the true last time it ran hence oldTime = TimeNow.

It is unnecessary, since the subsequent assignment operation will completely overwrite the value.

With this line as you had it (if (oldTime = 0)), that line oldTime = TimeNow will be renedered useless, since you always overwrite oldTime with 0.
This is C/C++ single equla signs (=) are assignments, not comparisons. The equality-check operator is a doubel equal sign (==).

OK, got distracted by life for awhile but I like where Rftop is headed. I purchased a few ACS712 current meters and am able to detect current flow to the pump. Can you help me understand how you are detecting a ‘state change’ from pump off to pump on and vice-versa? I’d assume that I can store those events in variables and do a bunch of math on them to calculate total and single event pumping time, time between pumping events, number of pumping events, volume pumped etc. All these calcs seem dependent on detecting an analog change from some small current draw (pump off state) to some larger current draw (pump on state). Most if not all info I find when I google for this seems to use the state change from HIGH to LOW i.e. digital change. Anyway, just babbling now… Any thoughts and direction would be appreciated.

I can send you my Code if you need it, but the short answer is I determine Cycles by the Amp readings compared to the previous Amp Reading:

Each Amp Reading = nowValue
Compare it to a minAmps threshold (#define this lower than the AMPS you expect during pump operation). This is to remove “noise” on the low end.
If nowValue < minAmps, then set your nowValue to 0.
(Nested) Now check if your oldValue > minAmps, if that’s True, then the PUMP Just STOPPED.
So go ahead and increment your Pump Counter, and stop a RunTime meter, and perform GPM calc, etc.
End those 'If’s"

Now check if nowValue > minAmps & oldValue < minAmps , if both are True = the Pump Just STARTED.
So start your Runtime meter.

At the end of your loop(), set oldValue = nowValue

Thanks for the reply. I’d love to see your code. I hacked something together (really hurt my brain) so I’d like to compare things.

So here’s what I have so far. It seems to be working as expected except for one thing - as written things only happen after the pump stops (counters incremented, run times calculated etc) which programatically is expected.

I’d like to have some events happen real-time such as run time alarms. I’d like to alarm if the pump runs for more than X seconds. The sketch as written alarms after the pump stops and it calculates that the run time exceeded X seconds.
Any direction on how to modify to support real-time alerts? (other coding or formatting suggestions would be appreciated)

double threshold = 2;   // set threshold to indicate pump is running
double totGal = 0;      // initialize total Gallons pumped
double galPumped = 0;
double galPerSec = .1;  // const for flow rate
double runCount = 0;    // initialize number of times pump has run
double startTime = 0;  
double stopTime = 0;  
double runTime = 0;  
double totTime = 0;     // initialize total time pump has run
double Voltage = 0;
double Current = 0;

boolean pumpLastState = false;    // set pump last state to "off"
boolean pumpRunning = false;      // set pump running state to "off"

const int buzPin = 8;     // choose the pin for the Buzzer

void setup()  
{                 
  Serial.begin(9600);   // initialize serial port to 9600 baud  
  totTime = 0 ;
  pinMode(buzPin, OUTPUT);    // declare buzzer as output
}

void loop(){  
{  
// Voltage is Sensed 1000 Times for precision
  for(int i = 0; i < 1000; i++) {
    Voltage = (Voltage + (.0247 * analogRead(A0)));   // (5 V / 1024 = .0247) which converter Measured analog input voltage to 5 V Range
    delay(1);
  }
Voltage = Voltage /1000;
Current = (Voltage -2.5)/ 0.185; // Sensed voltage is converted to current
 
// Serial.print("\n Voltage Sensed (V) = "); // shows the measured voltage
// Serial.print(Voltage,2);  // the '2' after voltage allows you to display 2  digits after decimal point
// Serial.print("\t Current (A) = ");   // shows the voltage measured
// Serial.print(Current,2);   // the '2' after voltage allows you to display 2  digits after decimal point

}
  if (Current > threshold)  // if current exceeds threshold then
   {
  pumpRunning = true;   // set running state to "on"
   }
   else
   {  
  pumpRunning = false;
   }

  if (pumpLastState == false && pumpRunning == true )  //detect if pump has started
  {  
     pumpRunning = true;    // set running state to "on"
     pumpLastState = true;    // set last running state to "true"
     runTime = 0; // reset as were just turning on again and its already added to accumilator  
     galPumped = 0; //reset as above
     startTime = millis();    // note start time  
     Serial.println("Pump on, Starting Timer. ");  
   }  
   else if (pumpLastState == true && pumpRunning == false) //detect if pump has stopped
   {  
     pumpRunning = false;   // reset running state to "off"
     pumpLastState = false;   // set last running state to "false"
     stopTime = millis();   // note stop time
     runTime = (stopTime - startTime)/1000;   // calculate last run time in seconds  

             if (runTime > 3) // if pump runs more than 3 sec then alert
   {
  tone(buzPin, 1000, 3000);                     //activate buzzer for 3 sec
   }
    
     Serial.print("Run time ");Serial.print(runTime);Serial.print(" sec. ");
     runCount = runCount + 1;   // increment run counter
     totTime += runTime;    // add to accumulator         
     galPumped = (runTime * galPerSec);   // calculate how many gallons were pumped during run cycle
     Serial.print(" Gal pumped = ");Serial.println(galPumped);
     totGal += (galPumped);  // add to accumulator

     Serial.print(" Run count = ");Serial.print(runCount);
     Serial.print(" Total run time = ");Serial.print(totTime); 
     Serial.print(" Tot Gal pumped = ");Serial.println(totGal); 
     delay(500); 
 

   } 

 
   }

Incorporate something like this:

unsigned long now = millis();  
int alarmRuntime = 3000;  // 3 seconds


loop()
  now = millis();  
  if (abs(now - startTime) >= alarmRuntime) {    
      // perform Alarm Action/Publish here
  }

Wrap that in a Counter or your favorite method to prevent the Alarm from firing every loop after the alarmRuntime has been met.