Using Multiple Push Buttons

Hello Everyone!

So I am working on a project that uses multiple push buttons. I have been able to use one for a while now but this is the first time attempting to use more. Anyway... It's not working. I have been working on fixing the issue for almost 6hrs now and think it must be some simple error that will be SUPER OBVIOUS. Maybe not though... Either way it is driving me bonkers. So I am looking for some assistance.

In this code we have 3 push buttons. They can be monitored in the serial port. When I click one I see that it goes into the section of code assigned for it but then returns the value as though it was the first button (event = 11). I need a different value submitted for different buttons pushed.

Your help on fixing this would be much appreciated. I really want to get over this unplanned obstacle and start coding on all the fun goodies the project has to offer.

Here is a link to the art piece the code is for if your interested. https://www.instagram.com/p/BUgPR8YFmY2/?taken-by=_rexhex_

The forum would not let me post the code here due to too many characters... So here is a sharable link to it in my google drive.

Button_4x_pushes.ino (8.77 KB)

The forum would not let me post the code here due to too many characters.

That's why there was an Additional Options link at the bottom of the text entry field.

So here is a sharable link to it in my google drive.

The proxy server is refusing connections

The forum would not let me post the code here due to too many characters

Did you read How to use this forum it tells you about attaching a file?

However that code is appalling. You need to learn how to use arrays, not just repeat lumps of code. While you treat the input of the three buttons separately you do not have separate variables for each of the other data associated with each button like the down time counter. Also you are only ever returning the event type for one button so how do you expect to be able to use three?

Are you wired as in one of these three examples S1 S2 S3:

.

PaulS: Thanks for letting me know. It has been a while since I have used this forum and I am not sure I have used that feature before. I have attached the source code.

Im sorry the proxy server (google?) is refusing connection. I thought that would work fine.

Grumpy_Mike: Always a pleasure... I do appreciate your dedication and quick responses over the years. As always I am happy to hear how you think the code could be better implicated. It works great with one button and has been used for many projects. I am a self taught programmer and am pretty happy with what I am doing these days but also am aware many improvements on code could be made. I use arrays often for my LED work but did not think of how that would help me with the buttons. If you would like to assist me, in a not too grumpy manner, I will be happy to listen.

My line of thinking for returning the event type is this.

button gets clicked --> program identifies which button --> program identifies which type of click and a specific value for that button + click is then returned.

I thought that I would not need all of the variables to be different because it would be going into a unique 'if' statement for the button while ignoring the rest. Obviously something is wrong so this could be it.

larryd: I am using the S1 External pull Down version.

Thank you guys for your interest. Here is an extra link of the project from last night with sound reaction. I have been dialing it in more since the video was taken and it makes me really happy. I can't wait to pass this button hurdle and start adding many settings.
https://www.instagram.com/p/BUi4Ua_lPNg/?taken-by=_rexhex_

Button_4x_pushes.ino (9.93 KB)

Time variables like "downTime" should be unsigned long, not just long.

aarg, I changed downTime and upTime from long to unsigned long but am still getting the same results.

Note to all: When I originally tried to cut down the code to fit arduino's max characters I cut too much. Im sure anyone trying to compile figured out the compiling error but here is whats missing any way.

#define BUTTON_PIN1 2
#define BUTTON_PIN2 3
#define BUTTON_PIN3 4

I have updated Button_4x_pushes.ino to have these definitions. It can be found on the first posting. The new link also includes aargs unsigned long suggestion.

That's a huge program. The inline logic and large complement of state variables make it extremely hard to analyze. I've been wrong before, but I doubt that anyone here will be willing to plow through it, as it currently stands. It seems as though you haven't compartmentalized the design so that the implementation is cleaner and more obvious in how it operates. In other words, the intricacy has made it fragile. That is a large part of the reason why a bug has crept in that you can't find - the program has actually grown to exceed your capacity of understanding. Situations like this are why modular top down programming was developed. Sorry I can't be of help in directly solving your problem. I would like to, but it would involve many hours of work.

Alright aarg, thank you for your input. It has taken me hours to try to fix the issue. I will start from scratch and see if I can 1. make the code much more concise and 2. fix the issue.

I don't remember hearing about this top down programming. I looked it up and will see if it will help me solve the problem. This issue has been a unfortunate time waster and was thinking someone would find the solution quickly. It kinda makes me happy that you are saying the code is too large to really find the issue because I am not the only one confused with it. Time to bust out the pen and paper.

Cheers

Look to see if there is a button library that could help you.

For multiple switches, I do like Nick Gammon's switch manager library.

Example:

/*SwitchManager skeleton 
 
 
 This sketch is to introduce new people to the SwitchManager library written by Nick Gammon
 
 The library handles switch de-bouncing and provides timing and state change information in your sketch.
 The SwitchManager.h file should be placed in your libraries folder, i.e.
 C:\Users\YourName\Documents\Arduino\libraries\SwitchManager\SwitchManager.h
 You can download the library at:
 http://gammon.com.au/Arduino/SwitchManager.zip    Thank you Nick!
 
 In this example we have 2 normally open (N.O.) switches connected to the Arduino - increment and decrement.
 The increment switch will also be used as a "Reset" switch if pressed for more than two seconds.
 The two switches are connected between GND (0 volts) and an Arduino input pin.
 The library enables pull-up resistors for your switch inputs.
 Pushing a switch makes its pin LOW. Releasing a switch makes its pin HIGH.
 
 The SwitchManager library provides 10ms de-bounce for switches. 
 i.e. enum { debounceTime = 10, noSwitch = -1 };
 If you need more time, edit the SwitchManager.h file
 i.e. enum { debounceTime = 50, noSwitch = -1 }; //here it is changed to 50ms
 */

#include <SwitchManager.h>             
//object instantiations
SwitchManager myIncSwitch;
SwitchManager myDecSwitch;

unsigned long currentMillis;
unsigned long heartBeatMillis;
unsigned long heartFlashRate  = 500UL; // time the led will change state       
unsigned long incShortPress   = 500UL; // 1/2 second
unsigned long incLongPress    = 2000UL;// 2 seconds 
unsigned long decShortPress   = 500UL; // 1/2 second

const byte heartBeatLED       = 13;
const byte incSwitch          = 4; //increment switch is on Arduino pin 4
const byte decSwitch          = 5; //decrement switch is on Arduino pin 5

int myCounter;

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

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

  //gives a visual indication if the sketch is blocking
  pinMode(heartBeatLED, OUTPUT);  

  myIncSwitch.begin (incSwitch, handleSwitchPresses); 
  myDecSwitch.begin (decSwitch, handleSwitchPresses);
  //the handleSwitchPresses() function is called when a switch changes state

} //                   E N D  O F  s e t u p ( )

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

void loop()
{
  //leave this line of code at the top of loop()
  currentMillis = millis();

  //***************************
  //some code to see if the sketch is blocking
  if (CheckTime(heartBeatMillis, heartFlashRate, true))
  {
    //toggle the heartBeatLED
    digitalWrite(heartBeatLED,!digitalRead(heartBeatLED));
  }

  //***************************
  //check to see what's happening with the switches
  //"Do not use delay()s" in your sketch as it will make switch changes unresponsive 
  //Use BlinkWithoutDelay (BWD) techniques instead.
  myIncSwitch.check ();  
  myDecSwitch.check (); 

  //***************************
  //put other non-blocking stuff here


} //                      E N D  O F  l o o p ( )


//======================================================================
//                          F U N C T I O N S
//======================================================================


//                        C h e c k T i m e ( ) 
//**********************************************************************
//Delay time expired function
//parameters:
//lastMillis = time we started
//wait = delay in ms
//restart = do we start again  

boolean CheckTime(unsigned long  & lastMillis, unsigned long wait, boolean restart) 
{
  //has time expired for this task?
  if (currentMillis - lastMillis >= wait) 
  {
    //should this start again? 
    if(restart)
    {
      //yes, get ready for the next iteration
      lastMillis = millis();  
    }
    return true;
  }
  return false;

} //                 E N D   o f   C h e c k T i m e ( )


//                h a n d l e S w i t c h P r e s s e s ( )
//**********************************************************************

void handleSwitchPresses(const byte newState, const unsigned long interval, const byte whichPin)
{
  //  You get here "ONLY" if there has been a change in a switches state.

  //When a switch has changed state, SwitchManager passes this function 3 arguments:
  //"newState" this will be HIGH or LOW. This is the state the switch is in now.
  //"interval" the number of milliseconds the switch stayed in the previous state
  //"whichPin" is the switch pin that we are examining  

  switch (whichPin)
  {
    //***************************
    //are we dealing with this switch?
  case incSwitch: 

    //has this switch gone from LOW to HIGH (gone from pressed to not pressed)
    //this happens with normally open switches wired as mentioned at the top of this sketch
    if (newState == HIGH)
    {
      //The incSwitch was just released
      //was this a short press followed by a switch release
      if(interval <= incShortPress) 
      {
        Serial.print("My counter value is = ");
        myCounter++;
        if(myCounter > 1000)
        {
          //limit the counter to a maximum of 1000
          myCounter = 1000; 
        }
        Serial.println(myCounter);
      }

      //was this a long press followed by a switch release
      else if(interval >= incLongPress) 
        //we could also have an upper limit
        //if incLongMillis was 2000UL; we could then have a window between 2-3 seconds
        //else if(interval >= incLongMillis && interval <= incLongMillis + 1000UL) 
      {
        //this could be used to change states in a StateMachine
        //in this example however, we will just reset myCounter
        myCounter = 0;
        Serial.print("My counter value is = ");
        Serial.println(myCounter);
      }

    }

    //if the switch is a normally closed (N.C.) and opens on a press this section would be used
    //the switch must have gone from HIGH to LOW 
    else 
    {
      Serial.println("The incSwitch was just pushed");
    } 

    break; //End of case incSwitch

    //*************************** 
    //are we dealing with this switch?
  case decSwitch: 

    //has this switch gone from LOW to HIGH (gone from pressed to not pressed)
    //this happens with normally open switches wired as mentioned at the top of this sketch
    if (newState == HIGH)
    {
      //The decSwitch was just released
      //was this a short press followed by a switch release
      if(interval <= decShortPress) 
      {
        Serial.print("My counter value is = ");
        myCounter--;
        if(myCounter < 0) 
        {
          //don't go below zero
          myCounter = 0;
        }
        Serial.println(myCounter);
      }

    }

    //if the switch is a normally closed (N.C.) and opens on a press this section would be used
    //the switch must have gone from HIGH to LOW
    else 
    {
      Serial.println("The decSwitch switch was just pushed");
    } 

    break; //End of case decSwitch

    //*************************** 
    //Put default stuff here
    //default:
    //break; //END of default

  } //End switch (whichPin)

} //      E n d   o f   h a n d l e S w i t c h P r e s s e s ( )


//======================================================================
//                      E N D  O F  C O D E
//======================================================================

Alright I am done using all of the power tools for the control box. My neighbors will be happy as it is now 10:44 my time. I had to get that done before it got too late. I am going to get to work on this button situation and consider all that has been brought up here over a cup of joe. Aarg I will take a look at some more button libraries if I can't get the linked code by larryd to work with what I have going. Thank you Larryd for suppling code you like that works with multiple switches. I briefly went through it and think it will help.

Dude, I always use classes for this type of problem. Classes allow you to "bundle" a set of variables together along with functions that work with those variables in isolation fro anything else.

So you have a class "Button" that knows what pin it is attached to and keeps track of its own state.

For the event-driven programming that you are trying to do, a very handy thing is to use pointers to functions. And the new and spiffy way to do this is with closures. Each button object would have a "on click", "on hold", and "on long hold' function pointer. If these are not null, then when the event is detected, that function is called.

It's complicated, but what you are trying to do is complicated.

First, lets define a Button class. The button class is initialiezed with a pin number and with a simple string so that we can print some stuff to serial. I will add a setup() and a loop() method to the class, and I will call those methods from inside the main setup and loop. I'll create three buttons on pins 2,3, and 4.

class Button {
  private:
    const byte pin;
    const char* name;
  public:
    Button(const char *name, const byte pin) : pin(pin), name(name) {}

    void setup() {
      pinMode(pin, INPUT_PULLUP);
      Serial.print("Button ");
      Serial.print(name);
      Serial.println(" set up.");
    }

    void loop() {

    }

};


Button button[] = {
  Button("House", 2),
  Button("Car", 3),
  Button("Widget", 4)
};

const int BUTTONS = sizeof(button) / sizeof(*button);

void setup() {
  Serial.begin(9600);
  while (!Serial) ;

  Serial.print("beginning sketch in 5");
  for (int i = 4; i >= 0; i--) {
    delay(500);
    Serial.print(' ');
    Serial.print(i);
  }
  Serial.println(" GO!");

  for (int i = 0; i < BUTTONS; i++) button[i].setup();
}

void loop() {
  for (int i = 0; i < BUTTONS; i++) button[i].loop();
}

I think I'll do this as a series of posts - it's going to be long :slight_smile:

Next, let's add some logic. The button first needs to sense 'up' and 'down' events, with some debounce logic. I'll use a 50ms debounce.

class Button {
  private:
    const byte pin;
    const char* name;

    byte state;
    uint32_t debounceMs;
    static const uint32_t DEBOUNCE_ms = 50;

  public:
    Button(const char *name, const byte pin) : pin(pin), name(name) {}

    void setup() {
      pinMode(pin, INPUT_PULLUP);
      Serial.print("Button ");
      Serial.print(name);
      Serial.println(" set up.");

      state = digitalRead(pin);
      debounceMs = millis();
    }

    void loop() {
      if (millis() - debounceMs < DEBOUNCE_ms) return;
      byte prevState = state;
      state = digitalRead(pin);
      if (state != prevState) {
        debounceMs = millis();
      }

      if (state == HIGH && prevState == LOW) {
        Serial.print("Button ");
        Serial.print(name);
        Serial.println(" released.");
      }
      else if (state == LOW && prevState == HIGH) {
        Serial.print("Button ");
        Serial.print(name);
        Serial.println(" pressed.");
      }
      else if (state == LOW && prevState == LOW) {
        Serial.print("Button ");
        Serial.print(name);
        Serial.println(" held.");
      }


    }

};


Button button[] = {
  Button("House", 2),
  Button("Car", 3),
  Button("Widget", 4)
};

const int BUTTONS = sizeof(button) / sizeof(*button);

void setup() {
  Serial.begin(9600);
  while (!Serial) ;

  Serial.print("beginning sketch in 5");
  for (int i = 4; i >= 0; i--) {
    delay(500);
    Serial.print(' ');
    Serial.print(i);
  }
  Serial.println(" GO!");

  for (int i = 0; i < BUTTONS; i++) button[i].setup();
}

void loop() {
  for (int i = 0; i < BUTTONS; i++) button[i].loop();
}

I get output that looks like this

Button Widget pressed.
Button Widget held.
Button Widget held.
Button Widget held.
Button Widget held.
Button Widget held.
Button Widget held.
Button Widget held.
Button House pressed.
Button Widget held.
Button Widget held.
Button House held.
Button Widget held.
Button House held.
Button Widget held.
Button House held.
Button Widget released.
Button House released.

See how we got two "Button Widget held." messages? That's because of the debounce delay on the House button. So it's all working fine.

Next - the click functionality.

Ok! Next, clicks and holds. Whenever the button is relesed, that's a click. If a click occurs within the double-click threshold of a previous click, then a click counter should be incremented.

When a button is held for the long and short periods, we want to fire a "hold" message, but we only want to do this once.

And I'll get rid of that "Button held" message .

class Button {
  private:
    const byte pin;
    const char* name;

    byte state;
    uint32_t debounceMs;
    static const uint32_t DEBOUNCE_ms = 50;


    static const uint32_t DOUBLECLICK_ms = 500;
    static const uint32_t SHORTHOLD_ms = 1000;
    static const uint32_t LONGHOLD_ms = 2000;
    uint32_t prevClickMs;
    uint32_t holdStartMs;
    int clickCount = 0;
    boolean shortHoldFired;
    boolean longHoldFired;

  public:
    Button(const char *name, const byte pin) : pin(pin), name(name) {}

    void setup() {
      pinMode(pin, INPUT_PULLUP);
      Serial.print("Button ");
      Serial.print(name);
      Serial.println(" set up.");

      state = digitalRead(pin);
      debounceMs = millis();
    }

    void loop() {
      if (millis() - debounceMs < DEBOUNCE_ms) return;
      byte prevState = state;
      state = digitalRead(pin);
      if (state != prevState) {
        debounceMs = millis();
      }

      if (state == HIGH && prevState == LOW) {
        Serial.print("Button ");
        Serial.print(name);
        Serial.println(" released.");
        // button has just been released. We have a click.

        if (millis() - prevClickMs <= DOUBLECLICK_ms) {
          clickCount ++;
        }
        else {
          clickCount = 1;
        }

        prevClickMs = millis();

        Serial.print("Button ");
        Serial.print(name);
        Serial.print(" ");
        Serial.print(clickCount);
        Serial.println(" click(s)");

      }
      else if (state == LOW && prevState == HIGH) {
        Serial.print("Button ");
        Serial.print(name);
        Serial.println(" pressed.");

        // button pressed. time to start the hold counter

        holdStartMs = millis();
        shortHoldFired = false;
        longHoldFired = false;

      }
      else if (state == LOW && prevState == LOW) {
        // button is being held. Do we have a hold event?

        if (!longHoldFired && millis() - holdStartMs >= LONGHOLD_ms) {
          Serial.print("Button ");
          Serial.print(name);
          Serial.print(" long hold on ");
          Serial.print(clickCount);
          Serial.println(" click(s)");

          longHoldFired = true;
        }
        else if (!shortHoldFired && millis() - holdStartMs >= SHORTHOLD_ms) {
          Serial.print("Button ");
          Serial.print(name);
          Serial.print(" short hold on ");
          Serial.print(clickCount);
          Serial.println(" click(s)");

          shortHoldFired = true;
        }
      }


    }

};


Button button[] = {
  Button("House", 2),
  Button("Car", 3),
  Button("Widget", 4)
};

const int BUTTONS = sizeof(button) / sizeof(*button);

void setup() {
  Serial.begin(9600);
  while (!Serial) ;

  Serial.print("beginning sketch in 5");
  for (int i = 4; i >= 0; i--) {
    delay(500);
    Serial.print(' ');
    Serial.print(i);
  }
  Serial.println(" GO!");

  for (int i = 0; i < BUTTONS; i++) button[i].setup();
}

void loop() {
  for (int i = 0; i < BUTTONS; i++) button[i].loop();
}

Button Widget pressed.
Button Widget released.
Button Widget 1 click(s)
Button Widget pressed.
Button Widget released.
Button Widget 2 click(s)
Button Widget pressed.
Button Car pressed.
Button Car released.
Button Car 1 click(s)
Button Widget short hold on 2 click(s)
Button Car pressed.
Button Car released.
Button Car 2 click(s)
Button Car pressed.
Button Car released.
Button Car 3 click(s)
Button Car pressed.
Button Widget long hold on 2 click(s)
Button Car short hold on 3 click(s)
Button Widget released.
Button Widget 1 click(s)
Button Widget pressed.
Button Widget released.
Button Widget 2 click(s)
Button Car released.
Button Car 1 click(s)

Now, here we can see a bit of a … well, not an error, but a failure to specify what we want. The release at the end of a hold is treated like a click. Furthermore, it's always treated as a single-click because the hold is longer than the double-click timeout. Let's make a modification to keep it consistent.

class Button {
  private:
    const byte pin;
    const char* name;

    byte state;
    uint32_t debounceMs;
    static const uint32_t DEBOUNCE_ms = 50;


    static const uint32_t DOUBLECLICK_ms = 500;
    static const uint32_t SHORTHOLD_ms = 1000;
    static const uint32_t LONGHOLD_ms = 2000;
    uint32_t prevClickMs;
    uint32_t holdStartMs;
    int clickCount = 0;
    boolean shortHoldFired;
    boolean longHoldFired;

  public:
    Button(const char *name, const byte pin) : pin(pin), name(name) {}

    void setup() {
      pinMode(pin, INPUT_PULLUP);
      Serial.print("Button ");
      Serial.print(name);
      Serial.println(" set up.");

      state = digitalRead(pin);
      debounceMs = millis();
    }

    void loop() {
      if (millis() - debounceMs < DEBOUNCE_ms) return;
      byte prevState = state;
      state = digitalRead(pin);
      if (state != prevState) {
        debounceMs = millis();
      }

      if (state == HIGH && prevState == LOW) {
        Serial.print("Button ");
        Serial.print(name);
        Serial.println(" released.");
        // button has just been released. We have a click.

        // unless we have done a hold, in which case we don't trteat this as a click.

        if (shortHoldFired || longHoldFired) {
          clickCount = 0;
        }
        else {
          if (millis() - prevClickMs <= DOUBLECLICK_ms) {
            clickCount ++;
          }
          else {
            clickCount = 1;
          }
          Serial.print("Button ");
          Serial.print(name);
          Serial.print(" ");
          Serial.print(clickCount);
          Serial.println(" click(s)");
        }

        prevClickMs = millis();


      }
      else if (state == LOW && prevState == HIGH) {
        Serial.print("Button ");
        Serial.print(name);
        Serial.println(" pressed.");

        // button pressed. time to start the hold counter

        holdStartMs = millis();
        shortHoldFired = false;
        longHoldFired = false;

      }
      else if (state == LOW && prevState == LOW) {
        // button is being held. Do we have a hold event?

        if (!longHoldFired && millis() - holdStartMs >= LONGHOLD_ms) {
          Serial.print("Button ");
          Serial.print(name);
          Serial.print(" long hold on ");
          Serial.print(clickCount);
          Serial.println(" click(s)");

          longHoldFired = true;
        }
        else if (!shortHoldFired && millis() - holdStartMs >= SHORTHOLD_ms) {
          Serial.print("Button ");
          Serial.print(name);
          Serial.print(" short hold on ");
          Serial.print(clickCount);
          Serial.println(" click(s)");

          shortHoldFired = true;
        }
      }


    }

};


Button button[] = {
  Button("House", 2),
  Button("Car", 3),
  Button("Widget", 4)
};

const int BUTTONS = sizeof(button) / sizeof(*button);

void setup() {
  Serial.begin(9600);
  while (!Serial) ;

  Serial.print("beginning sketch in 5");
  for (int i = 4; i >= 0; i--) {
    delay(500);
    Serial.print(' ');
    Serial.print(i);
  }
  Serial.println(" GO!");

  for (int i = 0; i < BUTTONS; i++) button[i].setup();
}

void loop() {
  for (int i = 0; i < BUTTONS; i++) button[i].loop();
}

utton Widget pressed.
Button Widget released.
Button Widget 1 click(s)
Button Widget pressed.
Button Widget released.
Button Widget 2 click(s)
Button House pressed.
Button Widget pressed.
Button Widget released.
Button Widget 3 click(s)
Button Widget pressed.
Button Widget released.
Button Widget 4 click(s)
Button Widget pressed.
Button House released.
Button House 1 click(s)
Button House pressed.
Button House released.
Button House 2 click(s)
Button House pressed.
Button Widget short hold on 4 click(s)
Button House short hold on 2 click(s)
Button Widget long hold on 4 click(s)
Button House long hold on 2 click(s)
Button House released.
Button Widget released.

Much better

Ok. Now let's add some event handlers. I'll fix it so that house and car deal with clicks, whereas the widget is only interested in presses and long-holds

The event handlers will be passed a reference to the button, so that a handler can talk to the button to ask about who it is. This means that a single handler function can be hooked up to multiple buttons. To make this go, the buttton needs to expose some information about itself. I'll ad an accessor method so that things can ask a button what it's name is.

Because things might want some weird combination of events, rather than make up a kajillion overloaded constructors, I'll create 'attach' methods.

Some minor changes to how the array is done, but it all works the same. And here you go:

class Button {
  private:
    const byte pin;
    const char* name;

    byte state;
    uint32_t debounceMs;
    static const uint32_t DEBOUNCE_ms = 50;


    static const uint32_t DOUBLECLICK_ms = 500;
    static const uint32_t SHORTHOLD_ms = 1000;
    static const uint32_t LONGHOLD_ms = 2000;
    uint32_t prevClickMs;
    uint32_t holdStartMs;
    int clickCount = 0;
    boolean shortHoldFired;
    boolean longHoldFired;

    void (*onPress)( Button &button) = NULL;
    void (*onRelease)( Button &button) = NULL;
    void (*onClick)( Button &button, int clickCount) = NULL;
    void (*onShortHold)( Button &button, int clickCount) = NULL;
    void (*onLongHold)( Button &button, int clickCount) = NULL;


  public:
    Button(const char *name, const byte pin) : pin(pin), name(name) {}

    const char* getName() {
      return name;
    }

    void attachPress(void (*handler)( Button &button)) {
      this->onPress = handler;
    }
    void attachRelease(void (*handler)( Button &button)) {
      this->onRelease = handler;
    }
    void attachClick(void (*handler)( Button &button, int clickCount)) {
      this->onClick = handler;
    }
    void attachShortHold(void (*handler)( Button &button, int clickCount)) {
      this->onShortHold = handler;
    }
    void attachLongHold(void (*handler)( Button &button, int clickCount)) {
      this->onLongHold = handler;
    }

    void setup() {
      pinMode(pin, INPUT_PULLUP);
      state = digitalRead(pin);
      debounceMs = millis();
    }

    void loop() {
      if (millis() - debounceMs < DEBOUNCE_ms) return;
      byte prevState = state;
      state = digitalRead(pin);
      if (state != prevState) {
        debounceMs = millis();
      }


      if (state == HIGH && prevState == LOW) {
        if (onRelease) onRelease(*this);

        // button has just been released. We have a click.
        // unless we have done a hold, in which case we don't trteat this as a click.

        if (shortHoldFired || longHoldFired) {
          clickCount = 0;
        }
        else {
          if (millis() - prevClickMs <= DOUBLECLICK_ms) {
            clickCount ++;
          }
          else {
            clickCount = 1;
          }

          if (onClick) onClick(*this, clickCount);
        }

        prevClickMs = millis();
      }
      else if (state == LOW && prevState == HIGH) {
        
        if (onPress) onPress(*this);
        holdStartMs = millis();
        shortHoldFired = false;
        longHoldFired = false;
      }
      else if (state == LOW && prevState == LOW) {
        if (!longHoldFired && millis() - holdStartMs >= LONGHOLD_ms) {
          if (onLongHold) onLongHold(*this, clickCount);
          longHoldFired = true;
        }
        else if (!shortHoldFired && millis() - holdStartMs >= SHORTHOLD_ms) {
          if (onShortHold) onShortHold(*this, clickCount);
          shortHoldFired = true;
        }
      }
    }
};


Button house = Button("House", 2);
Button car = Button("Car", 3);
Button widget = Button("Widget", 4);

Button *button[] = {
  &house, &car, &widget
};

const int BUTTONS = sizeof(button) / sizeof(*button);

void pressListener(Button &button) {
  Serial.print(button.getName());
  Serial.println(" pressed");
}

void clickListener(Button &button, int clickCount) {
  Serial.print(button.getName());
  Serial.print(" clicked ");
  Serial.print(clickCount);
  Serial.println(" times.");
}

void holdListener(Button &button, int clickCount) {
  Serial.print(button.getName());
  Serial.print(" long-held, clickcount is ");
  Serial.print(clickCount);
  Serial.println(".");
}


void setup() {
  Serial.begin(9600);
  while (!Serial) ;

  Serial.print("beginning sketch in 5");
  for (int i = 4; i >= 0; i--) {
    delay(500);
    Serial.print(' ');
    Serial.print(i);
  }
  Serial.println(" GO!");

  for (int i = 0; i < BUTTONS; i++) button[i]->setup();

  house.attachClick(clickListener);
  car.attachClick(clickListener);
  widget.attachPress(pressListener);
  widget.attachLongHold(holdListener);
}



void loop() {
  for (int i = 0; i < BUTTONS; i++) button[i]->loop();
}

Output is

Car clicked 1 times.
Car clicked 1 times.
Car clicked 2 times.
House clicked 1 times.
House clicked 1 times.
House clicked 2 times.
Widget pressed
Widget pressed
Widget pressed
Widget pressed
Widget pressed
Widget long-held, clickcount is 2.
Widget pressed

Several other ways to do it, of course. You could but press/release functionality in a class, and then put the clicky-holdy gear in a subclass. You could use an event listener interface rather than bare functions. Rather than having separate long and short hold functions, you could pass an additional paramter notifying the function as to whether the hold was long or short. You could put the timings into variables that could be set.

But, although there are several ways to do it, this is definitely one of them.

Ahhhhh! PaulMurray! I was just coming here to say that I got it working with Nick Gammon's switch manager library that Larry provided! Then I see you came to really lay it down with class creation and pointers.

Thank you for spending the time to lay this out. Im in the process of learning Java so I can control my lights via bluetooth. I think your example will really help me have more flexibility there. I have been writing this code with the future app in mind so I can more easily write a program to match up. I get so caught up in trying to get the program to work I neglect working on making classes but you are very right, much better. So much to learn I don't know how anyone could get bored in life.

and thank you Larry, that code you provided was much better than the code I was using.

Arduino is what got me into coding. I wanted to make my own RGB LED light bulb because the one I got online for a lamp shade I made was not bright enough and the settings sucked. Here I am about 5 years later making all kinds of LED stuff and just made my light bulb two weeks ago :slight_smile:

I am stoked about all of the different languages and applications. Possibilities are literally endless. I just wish there was more time in the freaking day.

rexhex:
Im in the process of learning Java so I can control my lights via bluetooth.

You mean, like this: Android and Arduino over Bluetooth?

Code is at Github.