This has me stumped!

A friend has a cow watering device in his field. The cows hit a lever with their nose, and get water. I have a flow valve that will a switch when it senses that the valve opens, and I have a 12 volt alarm transducer. I would like an alarm that would tell me if the valve got stuck and the water flows in excess of 20 minutes. The issue is that when the cows keep hitting the lever, the flow valve pulses on and off on and off, so I need ignore the pulses and only time when it stays on for an extended period.

Can this be done with the Arduino?

I really don't want to learn C++, I just want to help a friend out! can anyone recommend someone to write this sketch for me? Thanks, Chuck

Yeah, easy (well, depends a bit on how you get the signal back, and how far - you could make this hard if you're monitoring distant cows wirelessly or something)

If you described the parameters I mentioned we could give you a better idea of how big a deal this is. The simple cases are truly trivial.

Arduino has a built-in time counter that tells milliseconds since board power-up and start.

It returns a 32-bit unsigned long number and can count up to 49.7-some days in milliseconds. The sketch won't break after 49.7-some days, that is just the longest interval you can time using unsigned long milliseconds. Unsigned subtraction always delivers the difference between end and start. It's like how you know that 2PM - 10AM is 4 hours as is 7PM - 3PM.. you count from one to the other, on the clock 11 hours is the longest you can do that.

This only checks time if water flows. It sets the flow start time whenever flow starts.

Not compiled or tested:

unsigned long flowStartMillis;
const unsigned long flowDurationMillis = 20UL * 60000UL; // UL, make it unsigned long
byte waterIsFlowing;
cost byte waterFlowDetectPin = 2; 

................

void loop()
{
  if ( digitalRead( waterFlowDetectPin )) // or however you detect water flow, this is an example
  {
    if ( waterIsFlowing == 0 )
    {
      waterIsFlowing = 1;
      flowStartMillis = millis();
    }
  }
  else waterIsFlowing = 0;

  if ( waterIsFlowing == 1 )
  {
    if ( mills() - flowStartMillis >= flowDurationMillis )  // now - start always == time difference
    {
      // code to send alarm and maybe turn the flow off goes here

      waterIsFlowing = 0; // if flow stays ON, this will make the alarm not repeat for 20 minutes
    }
  }
}

DrAzzy: Yeah, easy (well, depends a bit on how you get the signal back, and how far - you could make this hard if you're monitoring distant cows wirelessly or something)

If you described the parameters I mentioned we could give you a better idea of how big a deal this is. The simple cases are truly trivial.

The flow switch is not in the field, it is up in the house where this circuit will be.

Hello Gofosmoke,

Thank you for writing this sketch for me.

This looks neat, I see where the input from the switch goes (pin 2)I guess that would be a 5volt signal?, But I don't see where the output to the alarm is! And would that be a 5 volt output?

Also I assume that to reset it after the alarm goes off, all you have to do is cycle the power??

Thanks, You , Chuck

helichuck: Hello Gofosmoke,

Thank you for writing this sketch for me.

This looks neat, I see where the input from the switch goes (pin 2)I guess that would be a 5volt signal?, But I don't see where the output to the alarm is! And would that be a 5 volt output?

Also I assume that to reset it after the alarm goes off, all you have to do is cycle the power??

Thanks, You , Chuck

It resets itself after the alarm signal is sent (however that happens) by setting waterIsFlowing to 0. Next time through loop, if water is detected flowing it will set waterIsFlowing to 1 and set start time to then. If water stops flowing before 20 more minutes it will reset itself again.

Also I assume that to reset it after the alarm goes off, all you have to do is cycle the power??

What do you do if the cow lever is stuck or held down?

I don't have all the details of the system so I can't write it. How will you detect water flow? Can you clear the valve lever automatically? Can a shutoff be added at the site? I don't know, can't code for don't know.

Remember, loop() runs over and over. The code inside this one is like a wheel, goes around comes around.

I am Sorry, I thought it was clear in my first post.

The alarm is to let us know that the valve is stuck or there is a break in the line. When we hear the alarm, we know to come and shut off the water and look for the problem.

I don't know anything about code, so what you are telling me is all greek to me, I was just asking for some help. I will supply any information that you need.

Thanks for all of your help!

Chuck

So, you want an alarm cancel button?

Ok. This sketch keeps a circular buffer of 40 30-second periods (20 minutes). If the total count exceeds the alarm trigger, then the alarm is triggered (duh).

I am assuming that when the water switch is ON, then the digital read is LOW.

Over any 20-minute period, 1200 reading are taken. if 90% (1080) are LOW, then the alarm pin is set to HIGH. You can change this if you like.

Rather than keeping “is the alarm on?” in a variable, I just read the alarm pin.

This sketch contains provision for a ‘turn off the alarm’ switch. this should be a normally-open switch wired between the pin and ground (the usual arrangement).

byte waterSensorPin = 3;
byte alarmOffPin = 4;
byte alarmOutPin = 5;


const unsigned int sampleRateMs = 1000; // sample every 1000 ms
const int samplesPerPeriod = 30; // make 30 samples per perod, so 30 seconds
const int nPeriods = 40; // 40 thirty second samples is 20 minutes total

// if 90% of the samples over the total time are high, then sound the alarm
// make sure that samplesPerPeriod * nPeriods is not greater than 32767 !
const int alarmTriggerPoint = (int)(samplesPerPeriod * nPeriods* .9);

unsigned long mostRecentSampleMs = 0;
int samplesThisPeriod;
int period;

int countOfHigh[nPeriods];
int totalCountOfHigh;

void setup() {
  // put your setup code here, to run once:

  pinMode(waterSensorPin, INPUT_PULLUP);
  pinMode(alarmOffPin, INPUT_PULLUP);
  pinMode(alarmOutPin, OUTPUT);

  resetSamples();

}

void loop() {
  // put your main code here, to run repeatedly:

  if(digitalRead(alarmOutPin)) {
    // alarm is on
    if(digitalRead(alarmOffPin) == LOW) {
      digitalWrite(alarmOutPin, LOW);
      resetSamples();
    }
  }
  else {
    if(millis() - mostRecentSampleMs >= sampleRateMs) {
      takeASample();
      mostRecentSampleMs = millis();
      if(totalCountOfHigh >= alarmTriggerPoint) {
        digitalWrite(alarmOutPin, HIGH);
      }
    }
  }
}

void resetSamples() {
  memset(countOfHigh, 0, sizeof(countOfHigh));
  totalCountOfHigh = 0;
  period = 0;
  samplesThisPeriod = 0;
}

void takeASample() {
  if(digitalRead(waterSensorPin) == LOW) {
    totalCountOfHigh ++;
    countOfHigh[period] ++;
  }
  samplesThisPeriod ++;

  if(samplesThisPeriod >= samplesPerPeriod) {
    // advance circular buffer one step
    if(++period >= nPeriods) {
      period = 0;
    }
    // clear out the results that were recorded way back when
    totalCountOfHigh -= countOfHigh[period];
    countOfHigh[period] = 0;
  }
  
}

helichuck:
I am Sorry, I thought it was clear in my first post.

Sure. Minus a few details.

The alarm is to let us know that the valve is stuck or there is a break in the line.

So monitor the pump for being switched on for over 20 minutes, no need to mess with the valve?

When we hear the alarm, we know to come and shut off the water and look for the problem.

As long as you fix it in under 20 minutes the alarm won’t sound again. That could be real handy if for some reason you miss it the first time.

The actual alarm code doesn’t have to stop ringing right away, that’s another detail – how you want the alarm to behave and where it is and where the pump is

As soon as you turn the pump off, the code will stop timing pump on-time.

I don’t know anything about code, so what you are telling me is all greek to me, I was just asking for some help. I will supply any information that you need.

Thanks for all of your help!

Chuck

Tell it in descriptions of what someone would do if they did it all by hand. Try and get it down to the least number of steps. This is about how the system should act, not the code to do it.

I have so far:

  1. check the pump to see if it’s on or off

  2. if it’s on and last time it was off, start timing.

  3. if it is on and time is up then give a yell and tell yourself it’s off. You’ll be checking again soon.

  4. go to step 1

Wow, this is getting complicated! There is no pump. There is just a water line that feeds the house and feeds the cattle watering device. Same line, same system!

I installed a flow switch in the line that senses when water is being used.

The water may turn on and off and on and off several times in any given period. I just want to monitor the flow to see if it happens to stay on for a long time, indicating a stuck valve of a leak.

Then sound an alarm when that happens to let us know there is a problem.

Then we can turn off the alarm,fix the problem and start over again.

@helichuck: I live on a horse farm and I have a similar problem except it's human-caused. Someone turns on the water to fill the trough and forgets to turn it off until there's a major flood and a huge ice-patch in winter.

I eventually designed a timer to shut the water off, but when I was looking for a solution I came across water flow alarms that do what you need. You can buy these things online fairly inexpensively.

If you're getting confused by the messages here, send me a PM. I'm pretty sure I have some code that does what you need and I can send it to you.

helichuck: Wow, this is getting complicated! There is no pump. There is just a water line that feeds the house and feeds the cattle watering device. Same line, same system!

I installed a flow switch in the line that senses when water is being used.

The water may turn on and off and on and off several times in any given period. I just want to monitor the flow to see if it happens to stay on for a long time, indicating a stuck valve of a leak.

Then sound an alarm when that happens to let us know there is a problem.

Then we can turn off the alarm,fix the problem and start over again.

Not a pump but a flow meter. Complicated? Changes the control scheme not at all. There will be code to read the flow meter instead of pump power switch, a detail. Small flow is probably a leak so all flow counts.

No crisis, only something that has to be fit in to work.

Take the horse system and do what you're told to set that up,it has to be easier than invention.

Hello Paul, I have the sketch loaded, and here is what I have hooked up.

  • I ground pin 3 to simulate flow
  • I have an LED on pin 5 and ground to simulate the alarm
  • I changed the periods to 2 so I can test it,

Nothing is happening. I have a red light and a green light on the board that are on. Any suggestions? Do I have to do something to start it?

Hmm.

Well, you'll want:

  • A button between pin 3 and ground to simulate flow (which you already have)
  • An LED between pin 5 and ground to simulate the alarm (this should have a 220 ohm current limiting resistor
  • A button between pin 4 and ground to reset the alarm

Changing the 'periods' to 2 means you get 2 30-second periods. Ill test it by changing the parameters like so:

const unsigned int sampleRateMs = 100; // sample every 1000 ms
const int samplesPerPeriod = 10; // make 30 samples per perod, so 30 seconds
const int nPeriods = 10; // 40 thirty second samples is 20 minutes total

which will give me a 10-second window.

I added some debugging - wait, wat? This is giving me some very unexpected stuff! Back soon …

Gahh! I had two bugs.

The more subtle one was advancing the period after taking the sample. This mean that after taking the final sample of a period, the next block would get cleared, and so that final sample would almost never “count”.

The one actually cauing the problem, however, was that I was not clearing “samples taken this period”, so each period only got one sample and this (of course) meant that the limit never tripped.

Shows what happens when you don’t test your code.

byte waterSensorPin = 3;
byte alarmOffPin = 4;
byte alarmOutPin = 5;

// To see debugging output, remove the comment on the next line
// #define DEBUG

// these constants are for testing. they give a 10-second cycle, taking 10 samples each second.
const unsigned int sampleRateMs = 100; 
const int samplesPerPeriod = 10;
const int nPeriods = 10; 

//const unsigned int sampleRateMs = 1000; // sample every 1000 ms
//const int samplesPerPeriod = 30; // make 30 samples per perod, so 30 seconds
//const int nPeriods = 40; // 40 thirty second samples is 20 minutes total

// if 90% of the samples over the total time are high, then sound the alarm
// make sure that samplesPerPeriod * nPeriods is not greater than 32767 !
const int alarmTriggerPoint = (int)(samplesPerPeriod * nPeriods* .9);

unsigned long mostRecentSampleMs = 0;
int samplesThisPeriod;
int period;

int countOfHigh[nPeriods];
int totalCountOfHigh;

void setup() {
  // put your setup code here, to run once:

  pinMode(waterSensorPin, INPUT_PULLUP);
  pinMode(alarmOffPin, INPUT_PULLUP);
  pinMode(alarmOutPin, OUTPUT);

  resetSamples();

#ifdef DEBUG
  Serial.begin(57600);
  while (!Serial);
  Serial.print("Sketch begins in ");
  for (int i = 3; i > 0; i--) {
    Serial.print(i);
    Serial.print("...");
    delay(1000);
  }
  Serial.println('0');
#endif
}

void loop() {
  // put your main code here, to run repeatedly:

  if (digitalRead(alarmOutPin)) {
    // alarm is on
    if (digitalRead(alarmOffPin) == LOW) {
      digitalWrite(alarmOutPin, LOW);
      resetSamples();
    }
  }
  else {
    if (millis() - mostRecentSampleMs >= sampleRateMs) {
      takeASample();
      mostRecentSampleMs = millis();
      if (totalCountOfHigh >= alarmTriggerPoint) {
        digitalWrite(alarmOutPin, HIGH);
      }
    }
  }
}

void resetSamples() {
  memset(countOfHigh, 0, sizeof(countOfHigh));
  totalCountOfHigh = 0;
  period = 0;
  samplesThisPeriod = 0;
}

void takeASample() {
  if (samplesThisPeriod >= samplesPerPeriod) {
    // advance circular buffer one step
    if (++period >= nPeriods) {
      period = 0;
    }
    // clear out the results that were recorded way back when
    totalCountOfHigh -= countOfHigh[period];
    countOfHigh[period] = 0;
    samplesThisPeriod = 0;
  }
  
  if (digitalRead(waterSensorPin) == LOW) {
    totalCountOfHigh ++;
    countOfHigh[period] ++;
  }
  samplesThisPeriod ++;

#ifdef DEBUG
  for(int i = 0; i<nPeriods; i++) {
    if(i==period) Serial.print('[');
    Serial.print(countOfHigh[i]);
    if(i==period) Serial.print(']');
    Serial.print(' ');
  }
  Serial.print(": ");
  Serial.println(totalCountOfHigh);
#endif

}

WOW Paul, that works great!!

For any newbies/lurkers: remove the comment on #define DEBUG, and the serial output will give you a look at what a circular buffer looks like in operation.

The buffer is the key to the algorithm. Without it, you'd only be turning off the water at the 20-minute mark. With it, the water gets turned off within 30 seconds of the trip point being reached. Now, because our sensitivity is set to 90%, this is 18 minutes of water running. The sketch could be made more sensitive by reducing this, at the cost of possible false positives. It really depends on the cows.

You know - this sort of problem is very amenable to baysean analysis. By getting records of the switch operation, we get prior probabilities for the sensor behaviour when the water thing is working correctly and for when it is stuck. We can then ask, "If the sensor is more than A% on over B time period, what's the probability that the switch is stuck?" Then one asks the question, "for a given time period B, what setting for A will give us no more than a 5% chance per day of getting a false alarm?" Finally, one can ask "what's the optimal A and B that wastes the least water while giving us a false alarm no more than about once every couple of weeks?".

That's the trade-off, of course. Water waste vs. walking out to the cow trough to reset a false alarm. There's no avoiding that.

Now, if we could hook this baby up to a solenoid whose job it was to give the lever a thump with a rubber mallet when it was stuck …

The good news is you don't have to walk out to the trough, the alarm circuit will be in the house.

The bad news is I got to thinking, and all the water that the house uses goes through the flow valve, so, say, you do dishes and a couple of people take a shower you would have a 90% senario and could trigger the alarm.

Does hitting the reset also start the sequence all over??

Or should it auto reset any time the valve closes, and only time while the valve is open, and have it alarm only when you have a flow for a constant 20 minute period?

Boy this could be interesting.

Also on another note, I wonder if you could take the original Blinky sketch and add 2 pots, one would adjust how long the light was on and the other how long the light was off???

helichuck: The bad news is I got to thinking, and all the water that the house uses goes through the flow valve, so, say, you do dishes and a couple of people take a shower you would have a 90% senario and could trigger the alarm.

Well! Looks like you need to log the data for a while (as in, a few days at least) and examine it. Store it on an SD card, or upload it to a web server. Once you log the data and keep a diary of when you needed to un-jam the cow thing (v important), then maybe you'll be in a position to decide how to tell when the gate is jammed open.

Or you could just put a stronger return spring on the gate and not have this problem at all.