Run code if button held for >20secs

Hi all, I have a bit of an issue with my first arduino project running on a NodeMCU which I am hoping someone will have a solution for: the project uses a home made 'switch' which is actually three pieces of copper which come together to complete a circuit when pushed up by a float. The nature of the project means that the float causes the switch to close a number of times initially, as the measured material rises and falls a little initially. The problem is that this is triggering the code several times in quick succession. I really need the code to only run if the circuit has been closed for say more than 20 seconds to indicate that the material has fully and permanently risen. The code is designed to send a notification via IFTTT via a web hook. Is there a way that I could adapt this to only trigger if the circuit is closed for a certain amount of time? I don't know if it is an issue that the code works by sleeping/waking the Nodemcu:

#include <ESP8266Webhook.h>
#include <ESP8266WiFi.h>

#define wakePin 16
#define _SSID "xxxx"      // Your WiFi SSID
#define _PASSWORD "xxxx"  // Your WiFi Password
#define KEY "xxxx"        // Webhooks Key
#define EVENT "xxxx"      // Webhooks Event Name

Webhook webhook(KEY, EVENT);    // Create an object.

void setup() {
  Serial.begin(115200);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  delay(1000);

  // Connect to WiFi
  Serial.println();
  Serial.println();
  Serial.print("Connecting to: ");
  Serial.println(_SSID);
  WiFi.begin(_SSID, _PASSWORD);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print("-");
  }

  Serial.println("");
  Serial.println("WiFi Connected");

  // Print the IP address
  Serial.print("Use this URL to connect: ");
  Serial.print("http://");
  Serial.print(WiFi.localIP());
  Serial.println("/");
  digitalWrite(LED_BUILTIN, HIGH);

//================================================================//
//================================================================//


  // Trigger without any value and get response.
  int response = webhook.trigger();
  if(response == 200)
    Serial.println("OK");
  else
    Serial.println("Failed");



    ESP.deepSleep(wakePin); 
}

void loop() {
  // Nothing
}

Without thinking-through all of the details for you... You can make a while() loop that does nothing but check to see if the "button" has been pushed for more than 20 seconds and if not, you are simply stuck in that loop.

The code in the loop simply has to keep track of how long the button is pushed and reset the elapsed time whenever (or "while") the button isn't pushed.

thanks DVDdoug that sounds promising! I am very very new to any sort of coding so quite reliant on example projects/tutorials at the moment (which is where my code has come from). I have found some videos on using the while loop and implementing into the code so will review these and see if I can get it working!

Something like this could work.

// global variables
unsigned long timerMillis = 0;
bool flag = false;


void checkButton()
{
  if(button.pressed() == true) // Check if the button was pressed using some library
  {
    if(((millis() - timerMillis) >= 20000UL) && (flag == true))
    {
      // Do something
      flag = false;
    }
    else if(flag == false)
    {
      flag = true;
      timerMillis = millis();
    }
  }
  else
  {
    flag = false;
  }
}

All the code in your original post is in the setup function. That means it will run only once and then stop. Especially as the last thing the code does is to go to sleep.

You don't want to do this, you want to keep on looking at the switch and send the message when you see it has made contact for 20 seconds.

Sounds extremely dodgy.

Proper float switches use a magnet and reed switch, very cheap on Aliexpress.

Code is very easy, but you should study Using millis() for timing. A beginners guide and Blink without delay() explained line-by-line for a start and do some exercises so you fully understand how to perform timing.

That said, I can give you the ready-made "debounce" code. Normally, you debounce a switch for some 10 ms, but my code will work perfectly for your 20 seconds which crude debounce code will not. :roll_eyes:

The below is the simple way, non-blocking

/*
 * Created by ArduinoGetStarted.com
 *
 * This example code is in the public domain
 *
 * Tutorial page: https://arduinogetstarted.com/tutorials/arduino-button-library
 *
 * This example:
 *   + uses debounce for a button.
 *   + reads state of a button
 *   + detects the pressed and released events of a button
 */

#include <ezButton.h>

ezButton button(7);  // create ezButton object that attach to pin 7;

void setup() {
  Serial.begin(9600);
  button.setDebounceTime(20000); // set debounce time to 20 seconds
}

void loop() {
  button.loop(); // MUST call the loop() function first

  if(button.isPressed()) {
    Serial.println("The button has been pressed for 20 seconds");
	// TO DO YOUR WORK HERE
  }
}

The problem I have is that the material is very low resistance. So any additional weight on the float and it will start to penetrate through the material rather than rise up. The copper is ideal as it is 0.1mm thick and almost weightless. I don't think a magnet that was capable of triggering the switch could be pushed up by the float but I may be wrong. Is there such a thing as ultra lightweight but strong (enough) magnets?

I will definitely look into how these functions work. It would be good to understand the basics at least. I think what I struggle with at the moment is understanding how to string the relevant parts of different codes together. As @Grumpy_Mike has pointed out, my code currently works by running once when the device is awaken. Which is great, except it is being awaken 20 or 30 times... So does the debounce code go before this? Or do I need to restructure the code to behave differently entirely, more like the EZbutton examples?

A bit xy problem. Why not tell us the application as the solution may be different than the one you are trying to get working

Hi All,

Thanks for all the helpful responses.

I managed to get the ezButton library to work well with the rest of my existing code and the debounce function is working :slight_smile:

I have now developed this code/design. In response to @Paul_B I have replaced the trigger with a HW-201 IR sensor which is more stable that the three piece copper construction. Does anyone know how I should merge this example code which triggers an LED using the IR, with my existing code, to ask it to trigger the webhook when triggered by the IR? The debouncer should remain so that the action is only triggered after the IR reads <900 for more than 1000 ms.

Example code for the IR:

void setup()
{  
   Serial.begin(9600); // sensor buart rate  
   pinMode(14,HIGH);  // Led Pin Connected To D5 Pin 
}
void loop() 
{
int s1=analogRead(A0); // IR Sensor output pin connected to A0  
  Serial.println(s1);  // See the Value In Serial Monitor     
  delay(100);  
  if(s1< 900 )  
  {  
   digitalWrite(14,HIGH); // LED ON  
  }  
   else  
  {  
   digitalWrite(14,LOW); // LED OFF  
  }  
}

Current code sending webhook with debouncer included:


#include <Arduino.h>
#include <ButtonDebouncer.h>
#include <ESP8266Webhook.h>
#include <ESP8266WiFi.h>

#define BUTTON_PIN              16

#define CUSTOM_DEBOUNCE_DELAY   1000

// Time the library waits for a second (or more) clicks
// Set to 0 to disable double clicks but get a faster response
#define CUSTOM_REPEAT_DELAY     0
#define _SSID "xxxx"      // Your WiFi SSID
#define _PASSWORD "xxxx"  // Your WiFi Password
#define KEY "xxxx"        // Webhooks Key
#define EVENT "xxxx"      // Webhooks Event Name

Webhook webhook(KEY, EVENT);    // Create an object.

ButtonDebouncer * button;

void setup() {
    Serial.begin(9600);
    Serial.println();
    Serial.println();
    button = new ButtonDebouncer(BUTTON_PIN, BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH | BUTTON_SET_PULLUP, CUSTOM_DEBOUNCE_DELAY, CUSTOM_REPEAT_DELAY);

    pinMode(LED_BUILTIN, OUTPUT);
    digitalWrite(LED_BUILTIN, LOW);
    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
    delay(1000);
}

void loop() {
    if (unsigned int event = button->loop()) {
        if (event == EVENT_RELEASED) {
            Serial.print("Count : "); Serial.print(button->getEventCount());
            Serial.print(" Length: "); Serial.print(button->getEventLength());
            Serial.println();
}

 // Connect to WiFi
    Serial.println();
    Serial.println();
    Serial.print("Connecting to: ");
    Serial.println(_SSID);
    WiFi.begin(_SSID, _PASSWORD);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print("-");
  }

    Serial.println("");
    Serial.println("WiFi Connected");

  // Print the IP address
    Serial.print("Use this URL to connect: ");
    Serial.print("http://");
    Serial.print(WiFi.localIP());
    Serial.println("/");
    digitalWrite(LED_BUILTIN, HIGH);

//================================================================//
//================================================================//


  // Trigger without any value and get response.
  int response = webhook.trigger();
  if(response == 200)
    Serial.println("OK");
  else
    Serial.println("Failed");
        }
    }

I have a code that is designed to send an sms via IFTTT using a webhook when bread dough has risen a certain amount. It does this via an IR sensor which, when it detects input of <900 i.e the float on the device has risen into view, creates the webhook event.

At the moment I am getting two issues -

  1. the trigger is running continuously, so when the IR senses dough I receive an endless stream of texts!
  2. because the dough rises so slowly the IR runs the risk of being triggered multiple times as the reflective surface slowly comes into 'view' and the IR just catches the edge of it for a while before it is fully in front of the sensor.

I am looking for a way to force the sketch to only accept a 'new' input from the IR if the obstruction is removed and then replaced. So 'is value <900, if yes - run action, if still yes - do nothing, if no - wait until next <900'.

Then to also add a sort of debounce so the sketch only considers <900 for >10secs as a valid trigger.

I have had a look around but struggle to find other examples of the IR sensor being used in this way. Anyone have any clever solutions?

#include <ESP8266Webhook.h>
#include <ESP8266WiFi.h>

#define _SSID "xxxx"      // Your WiFi SSID
#define _PASSWORD "xxxx"  // Your WiFi Password
#define KEY "xxxx"        // Webhooks Key
#define EVENT "sourdough"      // Webhooks Event Name

Webhook webhook(KEY, EVENT);    // Create an object.

void setup() {  
  Serial.begin(9600); // sensor buart rate  
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, LOW);
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  delay(1000);

   // Connect to WiFi
  Serial.println();
  Serial.println();
  Serial.print("Connecting to: ");
  Serial.println(_SSID);
  WiFi.begin(_SSID, _PASSWORD);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print("-");
  }

  Serial.println("");
  Serial.println("WiFi Connected");

  // Print the IP address
  Serial.print("Use this URL to connect: ");
  Serial.print("http://");
  Serial.print(WiFi.localIP());
  Serial.println("/");
  digitalWrite(LED_BUILTIN, HIGH);
}

void loop() 
{
int s1=analogRead(A0); // IR Sensor output pin connected to A0  
  Serial.println(s1);  // See the Value In Serial Monitor     
  delay(100);  
  if(s1< 900 )  
  {  
   int response = webhook.trigger();
  if(response == 200)
    Serial.println("OK");
   
  }  
   else  
  {  
    Serial.println("Failed"); 
  }  
}

You are checking when something is active rather than when it becomes active. That is a state change. Look at the "StateChangeExample" in the IDE.

Would that be... something like this...? (have removed the wifi connect for now just to focus on getting this part of code right)

const int  sensorPin = A0;    // the pin that the pushbutton is attached to

// Variables will change:
int SensorCounter = 1;   // counter for the number of button presses
int SensorState = 1024;         // current state of the button
int lastSensorState = 1024;     // previous state of the button

void setup() {
  // initialize the button pin as a input:
  pinMode(sensorPin, INPUT);
  // initialize the LED as an output:
  pinMode(LED_BUILTIN, OUTPUT);
  // initialize serial communication:
  Serial.begin(9600);
}


void loop() {
  // read the pushbutton input pin:
  SensorState = analogRead(A0);

  // compare the SensorState to its previous state
  if (SensorState != lastSensorState) {
    // if the state has changed, increment the counter
    if (sensorState< 900) {
      // if the current state is <900 then the button went from off to on:
      SensorCounter++;
      Serial.println("on");
      Serial.print("number of sensor triggers: ");
      Serial.println(SensorCounter);
    } else {
      // if the current state is LOW then the button went from on to off:
      Serial.println("off");
    }
    // Delay a little bit to avoid bouncing
    delay(50);
  }
  // save the current state as the last state, for next time through the loop
  lastSensorState = SensorState;


  if (SensorCounter == 1) {
     int response = webhook.trigger();
  if(response == 200)
    Serial.println("OK");
   
  }  
   else  
  {  
    Serial.println("Failed"); 
  }  
}

Struggling to understand how the buttoncounter (or sensorcounter in my case) works. Does this flick between 1/0 as an on/off? And the statechange is looking for whether there is a 1/0? Slightly confused... Or does it go up by 1 each trigger? In which case how do I tell the statechange to look for the increase?

You want a digital change in state. If you are using analog you need to determine what value equals a change in state. Then you poll your sensor every time through loop. If value is >= to actionValue then state has changed.
This is probably all you need as you don’t really need to debounce unless it reads too early as the thing bobs up and down in which case you are better to adjust hardware.
If state has changed the. Run your function and then, once run, increment into next state which might be, texts etc sent awaiting input from boss!
Look up and understand state machines and why they are necessary in looping code

You have named your pin sensorState which is confusing

The count is not really relevant in this case. You need to resolve the reading of the sensor to a bool (a state is on or off, boolean). Use something like:


if(analogRead(A0) > threshold)
{
    SensorState = true;
}
else
{
    SensorState = false;
}

if(SensorState != lastSensorState)
{
     if(SensorState == true)
     {
         // do whatever
      }
}

bool lastSensorState = SensorState;

Adjust threshold and the state of SensorState to fit your needs.

Something like this?


// this constant won't change:
const int  sensorPin = A0;    // the pin that the pushbutton is attached to

// Variables will change:
int SensorState = 1024;         // current state of the button
int lastSensorState = 1024;     // previous state of the button

void setup() {
  // initialize the button pin as a input:
  pinMode(sensorPin, INPUT);
  // initialize the LED as an output:
  pinMode(LED_BUILTIN, OUTPUT);
  // initialize serial communication:
  Serial.begin(9600);
}


void loop() {
  // read the pushbutton input pin:
  SensorState = analogRead(A0);

  // compare the SensorState to its previous state
  if (SensorState != lastSensorState) {
    // if the state has changed, increment the counter
    if (sensorState< 900) {
      // if the current state is <900 then the button went from off to on:
      SensorCounter++;
      Serial.println("on");
      Serial.print("number of sensor triggers: ");
      Serial.println(SensorCounter);
    } else {
      // if the current state is LOW then the button went from on to off:
      Serial.println("off");
    }
    // Delay a little bit to avoid bouncing
    delay(50);
  }
  // save the current state as the last state, for next time through the loop
  lastSensorState = SensorState;


if(analogRead(A0) > 900)
{
    SensorState = true;
}
else
{
    SensorState = false;
}

if(SensorState != lastSensorState)
{
     if(SensorState == true)
     {
     int response = webhook.trigger();
  if(response == 200)
    Serial.println("OK");
  }

   else  
  {  
    Serial.println("Failed"); 
  }  
  
bool lastSensorState = SensorState;
}

Am I right in thinking that parts of this code are now redundant as the true/false section is doing what the on/off section was doing? But i still need the code to record the sensorState and save as lastSensorState?

You name your sensorPin then use A0
States should be true or false if binary or 1/2/3etc if you have more than 2 states.
Change sensorState to sensorValue or similar in first instance
From then on in use the value to increment your machines state ie state 1; waiting for rise, state 2 risen, state 3, whatever is next
In the context given you only need a bool as you have 2 states: pre-rise and risen. You need to understand the concept of a state in the context of a state machine

Sorry, that should be just lastSensorState = SensorState;

The code that I posted was meant to replace the on-off part.

The SensorState and lastSensorState variables should be declared bool.

Do you need the count of triggers?

Here is a code that you can run to see what I mean.

const int sensorPin = A0;

const int threshold = 300;

bool SensorState = true;
bool lastSensorState = true;

void setup()
{
   Serial.begin(115200);
}

void loop()
{
   if (analogRead(sensorPin) < threshold)
   {
      SensorState = true;
   }
   else
   {
      SensorState = false;
   }

   if (SensorState != lastSensorState)
   {
      if (SensorState == true)
      {
         Serial.println("light on");
          // add the count here if you need it.
      }
      else
      {
         Serial.println("light off");
      }
      lastSensorState = SensorState;
   }
}

I added hysteresis so that there won't be oscillation if the condition is near the threshold.

const int sensorPin = A0;

const int threshold = 350;
const int hysteresis = 50;

bool SensorState = true;
bool lastSensorState = true;

void setup()
{
   Serial.begin(115200);
}

void loop()
{
   int sensorReading = analogRead(sensorPin);
   if (sensorReading < threshold - hysteresis)
   {
      SensorState = true;
   }
   else if (sensorReading > threshold + hysteresis)
   {
      SensorState = false;
   }

   if (SensorState != lastSensorState)
   {
      if (SensorState == true)
      {
         Serial.println("light on");
         // add the count here if you need it.
      }
      else
      {
         Serial.println("light off");
      }
      lastSensorState = SensorState;
   }
}