Arduino Keypad Hold function strange behavior

Hello guys...

I just hit a bottom pit with something and i can't see the solution here, maybe you can help.

I'm using a keypad for various functions and i would like a key to have double function like:

  • if pressed then do action1
  • if hold then do action2

The problem is this: when hold the key perform action1 and then action2

Is there any way to have it perform only action2?

For the moment the behavior seems logical, as the key is first press and the hold ... there is a catch somewhere?

Thank you in advance for you help.

Best regards,
Gabriel Tudoran

So just to be clear it sounds like what you really want to do is have the button only do one thing at a time. If you hold the button do action2 but ignore action1. Else, just do action1? There are two ways you can do this:

  1. Before calling either action wait for a few milliseconds to see if the button reaches the hold state. If it doesn't then call action1. Else, call action2.

  2. You can monitor the button states and see if you get the PRESSED state. Once a key has been pressed then you monitor for key.keyStateChanged() && RELEASED which would then be action1 or key.keyStateChanged() && HOLD which would be action2.

Let me know if this makes any sense.

Hi mstanley and any other helpful genii,

I'm trying to implement the above proposed solution without luck, and am at a point where any change I make is not in the direction of rectifying this issue, and have for quite time almost literally been beating my head against a wall...

May I please request some help in getting my sketch to work - it is an MQTT client post sketch for my home lighting automation.

For the purpose of keeping it lean and reducing the bloating, I have only pasted the troublesome code, but of course, can post the whole lot if required.


To briefly describe my requirements:

If a light is off:
turn it on... that is all

If a light is on:
If the switch is pressed for a short period of time (<= 300ms):
turn it off... that is all

If the switch is pressed for a long period of time:
count the press length (this will be used to provide a dimming resistor a value proportional to the length
of time held).

Please let me know if you need any more info, I'm not sure if I've missed any pertinent details, and sincerest thanks in advance!


The issue arises within the loop() function:

void loop()
{
char key = keypad.getKey();
/* Get the length of a key pressed*/
countDelay(key);

/* Find out which key was pressed*/
switch(key)
{
case '1':
{
/* Check to see if the switch has been pressed or not by checking
the numerical state value./
if (switchOneState == 1) // If the light is on...
{
/
If the key is pressed for less than 300 milliseconds turn the light off.
The 3 below represents each 100 milliseconds elapsed in the countDelay() function's
'HOLD' state called above.*/
Serial.print("Press delay before evaluation is ");
Serial.println(pressDelay);
Serial.print("Last switch state is ");
Serial.println(lastState);

if (lastState == 2 && pressDelay > 3)
{
Serial.println("Exceeded 300 milliseconds, getting dimming resistance value...");
Serial.print("Value sent to the pot is: ");
Serial.println(pressDelay);
/* send pressDelay to digital variable resistor*/
}
else
{
switchOneState++;
chState = "swOne";
client.publish("switchStateId", chState);
/Serial.print("switch1StateId");
Serial.println(chState);
/
Serial.println("Light is now off...");
lastState = 1;
}
}
else // The light is off...
{
switchOneState--;
chState = "swOne";
client.publish("switchStateId", chState);
/Serial.print("switch1StateId");
Serial.println(chState);
/
Serial.println("Light is now on...");
pressDelay = 0;
lastState = 2;
}
//pressDelay = 0;
break;
}
case '2':
{
//Not yet implemented
Serial.println(key);
break;
}
case '3':
{
//Not yet implemented
Serial.println(key);
break;
}
case '4':
{
//Not yet implemented
Serial.println(key);
break;
}
}

client.loop();
}

void countDelay(char key)
{
if (keypad.keyStateChanged() && keypad.getState() == HOLD)
{
if ((millis() - t_hold) > 100 )
{
pressDelay++;
Serial.println(pressDelay);
t_hold = millis();
}
lastState = 2;
}
}

Humbly grateful,

Alex

Hello dukman83, Give me a little time to look this over and I will see what I can do.

Thank you so much mstanley, I really appreciate it!!

Alex,

I had to make some assumptions about your circuit.

From your description you want to control a light by turning it on, off, and dimming it. So my first assumption is that you would use a digital pin to turn the light on and off and had some other way for your circuit to assign a value to the dimmer resistor.

My second assumption is the values for the dimmer resistor. I just randomly chose 0 to 1023. Make sure you adjust those so you don't damage your circuit. You'll find them in the

if ( (brightness <= 0) || (brightness >=1023) )      // If light reaches end of brightness range...

statement in loop().

Finally, I assumed that you only wanted the dimmer to work while the light was on. Kind of a pain to adjust it otherwise. :wink:

#include <Keypad.h>

const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns
char keys[ROWS][COLS] = {
    {'1','2','3'},
    {'4','5','6'},
    {'7','8','9'},
    {'*','0','#'}
};

byte rowPins[ROWS] = {42, 40, 38, 36}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {48, 46, 44}; //connect to the column pinouts of the keypad

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

#define ON  1    // These defines can be swapped to match
#define OFF 0    // your circuits' logic.




byte lightPin = 13;     // Light control pin.
boolean lightState;     // Is the light ON or OFF?
int brightness;         // Guessing at resistor values here: will be 0 to 1023 for now.
int upDown;             // +num or -num: '+' brightens, '-' dims and 'num' says how fast.

unsigned long startTime;    // Control timer works without delay().
unsigned long delayTime;    // Use this to change the lights' toggle speed.

boolean adjust = false;       // Are we actually pressing any keys?
boolean toggleLight = true;   // Selects between toggling or dimming the light.




void setup()
{
    Serial.begin(9600);
    keypad.addEventListener(keypadEvent); // Add an event listener for this keypad
    
    pinMode(lightPin, OUTPUT);              // Output means we can control the light.
    lightState = OFF;                        // Let's just start with the light on ...
    brightness = 512;                       // but only half bright when it gets turned on ...
    upDown = 16;                            // and increasing brightness.
    digitalWrite(lightPin,lightState);      // Make it so.
}




void loop()
{
    char key = keypad.getKey();

    if ( key )
    {
        if (key == '#')
        {
            Serial.println(!lightState);
        }
        else
        {
            Serial.println(lightState);
        }
    }


    if ( (adjust == true) && (lightState == ON) )
    {
        brightness = brightness + upDown;                    // Changing brightness while a key is pressed.

        if ( (brightness <= 0) || (brightness >=1023) )      // If light reaches end of brightness range...
        {
            upDown = upDown * -1;                            // change direction of dimmer.
        }

        Serial.print("Adjusting brightness = ");
        Serial.println(brightness);
        delay(200);                                // You won't need this delay(200) in your sketch.
    }
}




// Taking care of some special events.
void keypadEvent(KeypadEvent key)
{
    switch (keypad.getState())
    {
        case PRESSED:
            if ( (key == '#') && (millis() - startTime > delayTime) ) 
            {
                toggleLight = true;
            }
            break;
    
        case HOLD:
            if (key == '#') 
            {
                adjust = true;
                toggleLight = false;
            }
            break;
    
        case RELEASED:
            if (key == '#') 
            {
                if (toggleLight == true)
                {
                    digitalWrite(lightPin,!lightState);     // Toggle the light.
                    lightState = digitalRead(lightPin);
                }
                startTime = millis();           // Reset our toggle delay timer.
                adjust = false;
            }
            break;
    }
}

Hi mstanley,

Thank you so much for getting back to me so quickly!

The code is so nice and elegant! And I love the dimming loop, it’s genious! :slight_smile:

I just realised that I’ve omitted some important information… I probably should have mentioned this before; this code will be for one Arduino which will make a post to the Raspberry Pi 2 MQTT broker, while another Arduino will listen to the post messages from all of the posting arduinos. This is currently working, and because all the posting Arduino will do is just that – post, it will not be able to ascertain what state each of the 50 LED downlights is in (well, not without making the code much larger), therefore I may need to internally store the toggled state of each light within the posting unit.

For all intents and purposes, this operation works, the only thing which will vary is the posted message, which is based upon the switch and state of the light, if that makes sense…

I apologise for the layout of this message, I've formulated it in a word doc, which didn't preserve the nice layout and indentation I had.

Just to confirm that I’ve got the operation and flow of this code correct…

Is it correct to assume that the ‘delayTime’ is assigned at the time of declaration – I don’t see a value assigned anywhere else?

Say, for example the user presses 1:

Iteration 1:

Keypad.getKey() returns ‘1’,

The ‘keypadEvent(KeypadEvent key)’ event handler sees the event and starts to execute,
The first thing that is registered is the ‘PRESSED’ state,
Then because the key is NOT a ‘#’, exit the switch statement and return to the loop() function,
Next, the key evaluation will print the ‘lightState’ condition of ‘ON’,
Finally, the adjust and lightState conditions are both evaluated to false and the code block is not entered - End of iteration 1.

Iteration 2:

Keypad.getKey() is still ‘1’,

The keypadEvent(KeypadEvent key) handler is again invoked,
The next state registered is the ‘RELEASED’ state,
Again, because the key is not a ‘#’, exit the switch statement and return to the loop() function,
Again, the key evaluation will print the ‘lightState’ condition of ‘ON’ – End of iteration 2.
Judging by this, the light will never be turned on? And only toggled if the ‘#’ is pressed?

This is the bit I’m confused with.

Please let me know if I’ve gone waaaay of the mark here…

If I make the event handler evaluate each switch state against the locally stored switch variable and adjust accordingly as per below, do you think this could work?

void keypadEvent(KeypadEvent key)
{
/* Convert 'key' char array in declaration to a byte array to do math evaluation or cast it as required*/
/* Determine if the switch state is odd(on) or even(off) - if even decrease key value
global variable (or pass it as local) to be posted and vice versa*/
byte oddEven = (key % 2) ? key-- : key++;

switch (keypad.getState())
{
case PRESSED:
if (millis() - startTime > delayTime) //Assign delay value upon declaration?
{
toggleLight = true;
}
break;

case HOLD:
adjust = true;
toggleLight = false;
break;

case RELEASED:
if (toggleLight == true)
{
client.publish("switchStateId", key); // Do this post
digitalWrite(lightPin,!lightState); // Instead of this.
lightState = digitalRead(lightPin);
}
startTime = millis(); // Reset our toggle delay timer. // what happens after 50 days?
adjust = false;
break;
}
}

Lastly, I've briefly read about the millis() function, it seems like a really clever way to determine the time passed, but what happens after it resets itself in roughly 50 days time? What will happen to the dependent variables if the time passed is now less than what it previously was?

Again, thank you so much for your time, I really appreciate it!!

I'm at work so I'm just going to sling a few quick remarks to keep you going. If it prompts more questions then that is OK.

Is it correct to assume that the 'delayTime' is assigned at the time of declaration - I don't see a value assigned anywhere else?

Yes. I didn't assign it because I had that delay(200) in the code.

The iterations are a bit much to explain right now. But consider that in the amount of time that it takes a human to press-and-release a key the Arduino could have passed through the loop() hundreds, if not thousands, of times. So I created a state-machine that is based on time and user input. As soon as a key is pressed the PRESSED state is reached, then if the user continues to press the key for more than holdTime the state reaches HOLD. The RELEASED state is only ever reached after a PRESSED or HOLD state. Given that holdTime can be hundreds of milliseconds the Arduino can run tens of thousands of loop()'s. Way too many to worry about counting iterations.

Lastly, I've briefly read about the millis() function, it seems like a really clever way to determine the time passed, but what happens after it resets itself in roughly 50 days time? What will happen to the dependent variables if the time passed is now less than what it previously was?

Then you will never get a read on the keypress ever again!!! :astonished:

So maybe the sketch author could test for that situation and remediate it with something like:

        case PRESSED:
            if ( (millis() - startTime) < 0 )
            {
                    startTime = 0;
            }
            if ( (key == '#') && (millis() - startTime > delayTime) )
            {
                toggleLight = true;
            }
            break;

OK, sorry for the snarky remark. It's because I've been reading Rachel Kroll's books, rachelbythebay.com. Anyway, that is just a quick example to demonstrate how that situation might be dealt with. However, a slightly more sophisticated approach may be required based on your system. If your circuit can handle a small change in the delayTime once every 50 days then the above solution might be sufficient.

Thanks very much for that again mstanley, I really appreciate your help!
This should keep me going for a while :slight_smile:

Cheers,

Alex

G'day mstanley,

I've implemented that awesome code with modifications to closely suit my needs, and wow, how good it is to see it work so well!

Thanks again for your help!!

Alex