Logic to trigger relay based on sensor counts over a unit of time

I've been struggling for days to come up with the logic I need to program the following:

I'm making a wind sensor that will shut off my outdoor water fountain when it gets windy. I have a reed switch that's triggered by a magnet which is embeded in a rotating wind sensor. I plan to program a relay to shut-off the power to the fountain when the reed switch triggers "x" number of times over a time period "p".

It would be a simple program if it was just a matter of counting "x" and when that count gets to a certain number, the relay goes low (pump shuts off) for a set amount of time. The problem is that I need to count "x" over a period of time "p" otherwise I could get a few wind gusts here and there and eventually, perhaps days later, the pump will finally go off. I want it to shut off only during sustained, significant winds, not little gusts over a long period of time.

The pump also needs to remain off for a set amount of time "t" before turning back on. If I just reset the counts "x" to zero (0) at the same time the pump turns back on, and it's still windy, the pump will turn on when "t" is reached but then go right back off a few moments later. What I need to do is continue to measure the wind in the background, even after the pump turns off. If it's still windy, the pump never turns back on until the wind subsides.

As I'm typing this, I suddenly realized one thing I might be able to do is to create another variable that includes the count "x" divided by the time period for counting "p". If this ratio (x/p) exceeds a certain threshold, the pump turns off for time period "t". If the ratio continues to exceed the threshold, the pump never turns on. If the ratio goes below the threshold, I delay (yes, I'll use a millis delay) the pump turning on for awhile longer to make sure the wind is really dying (x/p stays low.)

I thought about deleting this post now that I have a possible solution but I'll leave it up in case someone has a better solution or feels like my ramblings might be of use for some other project.

You have independently discovered the Frequency formula:

Frequency = (number of events) / time period

But you don't need it if you are just looking for a threshold value, because you can just calculate the threshold.

Suppose you measure pulses for one minute. You want to trigger on 0.5 pps. Your suggestion is like

let x = pulses/60
if x > 0.5 do something

but !!!!

you can also just say

if pulses > 30 do something

The calculation for the threshold is 30/60, but you only need to ever do it once. The other one has to be done for every reading you take.

Darn, if I just would have made my discovery several thousand years ago maybe they would have named it after me!

I might be missing something in your reply, but I don’t think simply counting pulses will work. There has to be a time component (frequency) otherwise I could end up counting a little bit of wind over a long period of time and the pump would shut off (it would get to 30 but it might take days.) I am only concerned with lots of pulses in a short amount of time.

Am I misunderstanding what you're trying to tell me?

I haven't had breakfast yet but what if

when the next pulse is soon enough after the preceding pulse, start counting.

If long enough between pulses goes by, reset the system.

If N pulses come each within soon enough, consider it proof that the trigger shoukd happen.

So it is I think a frequency sustained for a period.

A few pulses in a row here and there will not accumulate.

a7

TL;DR
Do you have a short version?

seems like you need to make decisions for "buckets" of time


byte PinOutput     = LED_BUILTIN;
byte PinInput      = A1;
byte inpLst;

const unsigned Thresh = 3;
unsigned cnt;

const unsigned long Period = 1000;
unsigned long msecLst;

// -----------------------------------------------------------------------------
void
loop (void)
{
    // capture frequecy of events
    byte inp = digitalRead (PinInput);
    if (inpLst != inp)  {
        inpLst = inp;
        if (LOW == inp)
            cnt++;
    }

    // set output based on cnts
    unsigned long msec = millis ();
    if (msec - msecLst > Period)  {
        msecLst = msec;

        digitalWrite (PinOutput, ! (Thresh <= cnt));
        cnt = 0;
    }
}

// -------------------------------------
void
setup (void)
{
    Serial.begin (9600);

    pinMode (PinOutput, OUTPUT);
    pinMode (PinInput,  INPUT_PULLUP);
    inpLst = digitalRead (PinInput);
}

The algorithm suggested by @alto777 is essentially using buckets, if I read your code correctly.

It has the added advantage that it would actually work, that is to say it would always catch 3 threshhold number of pulses that arrived in 1000 ms if those were the criterea.

If you reset the bucket timer when the "first" pulse comes in, and then fix the implications of having done, you will arrive at code for that algorithm.

I gave it a stab, but would rather write it myself from scratch, which I'm not going to do either. :expressionless:

a7

the bucket would be both

  • a large enough period to recognize a sustained increase in winds, as well as
  • some minimum time the pump would be disabled

they could be 2 different periods

i used 1 sec in the code i posted to test, but think a larger period ~10sec might be more appropriate

That could and I would say should be handled as an entirely separate matter.

Detect the arrival of N pulses within M seconds.

Disable the pump for K hours.

a7

ok, what would that algorithm be?

Haha, the same algorithm, upside down backwards.

a7

Here's a simulation.

If there are enough wind events within a period of time, the pump is turned off for awhile.

If there are enough wind events within a period of time during the pump lockout period, the lockout period begins anew.

A wind event is simulate by a button press, presumably that function would deal, however it might, with the real sensor.

The code is plain and verbose, perhaps to a fault. It does not block.

// https://wokwi.com/projects/353115104055753729
// https://forum.arduino.cc/t/logic-to-trigger-relay-based-on-sensor-counts-over-a-unit-of-time/1073844

# define windPin    6
# define pumpLED    7   // pump running lamp

# define THRESHOLD  5   // five wind events
# define WINDOW 2000    // within 2 seconds

# define LOCKOUT  15000 // pump disabled for 15 seconds

unsigned long pumpLockoutTimer;

bool pumpRunning;
bool windFlag;

void setup() {
  Serial.begin(115200);
  Serial.println("wind jammer demo\n");

  pinMode(windPin, INPUT_PULLUP);
  pinMode(pumpLED, OUTPUT);

  turnOnPump();
}

unsigned long now;      // time for all non-blocked functioning

void loop()
{
  windJammer();
}

void windJammer()
{
  now = millis();

  if (tooMuchWindQ()) {
    Serial.println(" enough!");

    turnOffPump();
    pumpLockoutTimer = now;
  }

  if (now - pumpLockoutTimer > LOCKOUT) {
    turnOnPump();
  }
}

bool pumpIsOn;

void turnOnPump()
{
  if (!pumpIsOn) {
    pumpIsOn = true;    
    Serial.println("turn pump ON");
    digitalWrite(pumpLED, HIGH);
// and do whatever else needs be done 
  }
}

void turnOffPump()
{
  if (pumpIsOn) {
    pumpIsOn = false;
    Serial.println("turn pump OFF");
    digitalWrite(pumpLED, LOW);
// and do whatever else needs be done 
  }
}

bool tooMuchWindQ()
{
  static unsigned long timer;
  static unsigned int counter;

  if (now - timer > WINDOW) { // all quiet, reset counter
    if (counter)
      Serial.println("       wind reset");      

    counter = 0;
    timer = now;
  }

  windSensorCheck();

  if (windFlag) {   // handle the windFlag
    counter++;

    Serial.println("       wind up!");
    timer = now;
    windFlag = false;
  }

  if (counter >= THRESHOLD) {
    counter = 0;
    return true;
  }
  else return false;
}

// here just a debounced button - replace with something that catches the wind
void windSensorCheck()
{
  static byte lastReading;
  static unsigned long lastTime;

  if (now - lastTime < 20)
    return;

  byte windReading = !digitalRead(windPin);  // LOW = wind

  if (windReading != lastReading) {
    if (windReading) {
      windFlag = 1;

      Serial.println("see the wind");
    }
    lastTime = now;
  }

  lastReading = windReading;
}

play with it in the wokwi simulator


More than half credit and a shoutout to the Umbrella Academy! Think of me doing the vacuuming and have one for me.

HTH

a7

Oh, my! I wasn't expecting someone to take the time to write the program, I was just asking for some help figuring out the logic! Early this morning I tried to use moving averages, nested timers, etc. and all I did was burn several hours solving nothing. I told my wife, if someone hired me as a programmer, I'd be fired within a day.

Thanks for all your time. I'll see if I can incorporate that code into mine.

It works! I will say, I don't understand much of the code (I'm lucky if I program once or twice a year) but I modified alto777's code for my pins and wind-vane/magnetic-reed setup and it works...PERFECTLY! I'll go through the code line-by-line so I can better understand what magic you've bestowed upon me and, after I have everything working, I'll post my final code

Thank you, thank you, thank you. I might actually be able to sleep tonight!

While I am delighted to find the code works for you, and I often advise against arguing with success, in this case I am compelled to say that I have found an error which may have gone unnoticed but certainly exists.

Since the code is not blocking, it was easy to write a small function, called from the free running loop(), that could periodically "inject" wind events and thereby verify that...

wind events arriving just before the sampling period or bucket expires, will accumulate over time and shut down the pump, even though such a signal does not mean it is too windy.

In real life I can imagine this as the wind rotary device turning relatively slowly over a long time, ultimately delivering enough "just in time" pulses.

Since you seem to imply you have it running with real hardware and real wind (!), I would like you to make a modification in the function tooMuchWindQ(),

  if (windFlag) {   // handle the windFlag
    counter++;

    Serial.println("       wind up!");
//    timer = now;     <--- 
    windFlag = false;
  }

That is, comment out (or remove) the update to the timer in the if statement I quote just now.

My hope is that from your point of view, the sketch will still function perfectly. From my point of view, one very demonstrable flaw will be repaired.

Unfortunately, as is often the case, the correction introduces a somewhat more subtle and probably even less likely to occur error. But if it is really windy, it should work OK.

So I wouldn't send it to the Moon. I don't like relying on practical realities - I would rather have code that works, period.

I have an idea that should fix both errors. It will fit into the structure of the current sketch, and should be closer to actually perfect.

In the meantime, if you do make the change and can test it, I'd like to hear that it works.

a7

Thanks for you concern and your taking the time to assist me. I commented out the one line, as requested, and it still seems to be working perfectly.

I spent some time last night modifying your code to my application and commenting on each line so I can try to actually understand how it works. I'll admit, I don't fully understand what some of the code does but, what I do know, is it seems to be working. So, once again, thanks for the assist!

THX.

Meanwhile I have a different approach for the low level sensor management working very nicely. What kind of pulse frequency does the reed switch deliver? That will help me investigate and tune the code.

The code is a horrible mess. Cowboy hacker, sry. When I am back in the lab I will try to post something. Ddon't worry about using it - I'm on my own mission now; if you benefit or anyone else does is just extra.

You spoke of strukkling with all manner of getting from the wind to the pump. When you mentioned "moving averages" and that was also mentioned recently in another thread, it reminded me of a neat trick I had seen but never used called a low pass filter.

It's a way of smoothing out a data stream without needing anything but the current value and the previous value, and it works a treat.

I filed it under "I learn something every day, or try to", but forgot to remmber it. :expressionless:

The essence

   runningValue = 0.9 * runningValue + 0.1 * newReading;

takes most of the old value but tends it towards the new value. You'll like to see it in action.

The 0.9 / 0.1 split can be changed to affect the rate at which a new values influence the running value.

And to any who are saying they told us so upthread, my apologies. They were absolutely correct to do.

a7

You specified the time component, and then I repeated it. You called it 't' in your original post. In my example, I specified one minute.

1 Like

Sorry for the delay in replying. Life is such. Regarding your question about pulse frequency, it's a bit hard to tell what it will be at this point. In my comments for my code (now, mostly YOUR code) I've been using the terminology "windy enough."

My wind sensor is a simple 3-cup arrangement that, when it catches wind and rotates, the (normally closed) magnetic reed is triggered TWICE (opens) each rotation because of how the magnetic field on the rotor magnet affects the magnetic reed on the base unit. None of this necessarily matters and does not answer your question.

The reality is, I can make bigger cups that could more easily catch the wind so, to some degree, I can make it rotate at any speed I like, within certain limits. While I'm still prototyping, I would say that if it's turning at all, it might be "too windy" and the water fountain will be affected, assuming the wind continues "long enough." So...finally...the answer to your question might be 1 pulse per second. But it could be much higher in gusty conditions.

I'm not sure what you're hoping to accomplish with a low pass filter. I understand the concept but I must say, the code you gave me before seems to accomplish exactly what I was hoping to accomplish, which is:

  • Monitor the wind

  • If it gets "windy enough" for a "long enough" period, shut off the pump

  • Continue monitoring the wind

  • If the wind is "calm enough" wait, say, 30 minutes, checking to make sure the wind remains calm the entire 30 minutes and, if it is, then turn the pump back on

One thing I started looking at after my original post was arrays. I don't know how to program arrays (or much else, quite frankly) but it seems a moving average array could be a good option for this application. Monitor the wind by counting the number of reed switch triggers "x" over some period of time "p" (the frequency as aarg has pointed out) and put those values into an array. If we count x over a time period p of 1 minute, and put those x values into an array with, say, 30 slots (= 30 minutes), we could look at the moving average within the array and if the average is over something like 50 we shut the pump off. Once the average goes below, say 10, we turn the pump back on and we keep it on until the average exceeds 50 again. It seems to me this is an easy way (for someone that knows what they're doing, not me) to smooth out the wind speed sensor rotations which will, most assuredly, be erratic.

Regardless of how I finally end up doing it, I'll need to calibrate the set points within the program based on my visual observations of the fountain. Since I'm using a Particle Photon, and can easily modify the program from my desk, it should be easy enough to calibrate. All I'll need to do is figure out what's "windy enough" and "calm enough."

Indeed. At some point you'll want to know how to use arrays anyway, so this could be an excuse to give implementing that idea a turn.

Here's my latest experiment where I apply the low pass filter concept. No array, no moving average.

If you press the button at a steady rate you will see the "wind speed". Faster and the displayed speed will soon catch up and reflect the rate at which you are pressing. Fast enough is windy enough, and the pump will be locked out, and if the speed is high enough within the lockout period, that time will be extended.


Play with the wokwi simulation here.


// https://wokwi.com/projects/353163986319942657
// https://forum.arduino.cc/t/logic-to-trigger-relay-based-on-sensor-counts-over-a-unit-of-time/1073844

# define windPin    6
# define pumpLED    7   // pump running lamp

# define LOCKOUT  15000 // pump disabled for 15 seconds

unsigned long pumpLockoutTimer;

void setup() {
  Serial.begin(115200);
  Serial.println("wind jammer demo\n");

  pinMode(windPin, INPUT_PULLUP);
  pinMode(pumpLED, OUTPUT);

  turnOffPump();
  turnOnPump();
}

unsigned long now;      // time for all non-blocked functions

void loop()
{
  now = millis();

  windSpeedPumpControl();   // calculate wind speed and turn on/off/lockout fan
  report();
}

void windSpeedPumpControl()
{
  static unsigned long lastPrint;
  static unsigned long counter;

  if (windSensorCheck()) {
    turnOffPump();
    pumpLockoutTimer = now;
  }

  if (now - pumpLockoutTimer > LOCKOUT) {
    turnOnPump();
  }
}

float theWindSpeed;

# define RESET  2000  // may not need to, but if no activity, throw out old data.
# define REPORT 100   // throttle flagging the high wind condition
# define WINDY  7.0   // worked for button pressing wildly

// maintains windSpeed with a low pass filter on debounced sensor clicks
bool windSensorCheck()
{
  static byte lastReading;
  static unsigned long lastTime;
  static unsigned long lastEventTime;

  static float windPeriod;

// debounce
  if (now - lastTime < 10) {     // limits usefulness to 100 Hz
    return theWindSpeed > WINDY;
  }

  if (now - lastTime > RESET) {
    windPeriod = 999.9;
    theWindSpeed = 0.0;

    lastTime = now;
  }

  byte windReading = !digitalRead(windPin);  // LOW = wind

  if (lastReading != windReading) {
    if (windReading) {
      windPeriod = 0.9 * windPeriod + 0.1 * (now - lastTime);

      theWindSpeed = 1000.0 / windPeriod;
    }

    lastReading = windReading;
    lastTime = now;
  }

  return theWindSpeed > WINDY;
}

bool pumpIsOn;  // just so the pump is only switched if it is in the wrong state

void turnOnPump()
{
  if (!pumpIsOn) {
    pumpIsOn = true;    
    Serial.println("turn pump ON");
    digitalWrite(pumpLED, HIGH);    // and do whatever else needs be done 

  }
}

void turnOffPump()
{
  if (pumpIsOn) {
    pumpIsOn = false;
    Serial.println("turn pump OFF");
    digitalWrite(pumpLED, LOW);   // and do whatever else needs be done 

  }
}

// reporting only - adjust for taste
void report()
{
  static unsigned long lastPrint;
  static unsigned long counter;

  if (now - lastPrint > 777) {
    Serial.print(counter); counter++;
    Serial.print(" ");
    Serial.println(theWindSpeed);

    lastPrint = now;
  }
}

I think this is easier to understand and probably easier to tune than the previous solution.

HTH

a7