// ---------------------------------------------
// Too_Much_For_One_Button.ino
// ---------------------------------------------
// License: The Unlicense, Public Domain.
// Author: Koepel
// 2019 june 27
// ---------------------------------------------
//
// Selecting more than 40 different commands with a single button.
// That is just too much for one button, and it is hard to use.
// The first 12 button sequences are reasonably doable with some practice.
// Single click, double click, triple click, long click, they are all part
// of the more than 40 different button sequences.
//
// The sketch might not be very educational.
// It is not just a simple example.
//
// A button is connected to pin 2 and GND.
//
// Explanation of the list of possible sequences:
// S Short press, shorter than 190ms.
// L Long press, between 190ms and 900ms.
// - Short pause, shorter than 190ms
// = Long pause, between 190ms and 900ms.
// Z A long continuous press, longer than 900ms.
// This is considered to be a 'Escape'.
// Any preceding sequence with the button is discarded.
// The 'Escape' becomes active while the button is still
// being pressed to be able to make a beep sound with a piezo.
// '\0' A pause longer than 900ms is the end of a sequence.
//
//
// A normal library that is useful is this one:
// https://github.com/fasteddy516/ButtonEvents
// (it uses the Bounce2 library for debouncing).
//
#define MAX_SEQUENCE 8
#define ID_ESCAPE 100
#define ID_ERROR 101
typedef struct idSequence_STRUCT
{
byte id;
char sequence[MAX_SEQUENCE];
} idSequence;
// For the Arduino Uno this list of 378 bytes could be placed in PROGMEM.
const idSequence list[] =
{
0, "L", // long press (between 190ms and 900ms)
1, "S", // short press (less than 190ms)
2, "S-S", // short press, short pause, short press
3, "S-S-S",
4, "S-S-S-S",
5, "L-L",
6, "S=S",
7, "L=L",
8, "S-L",
9, "L-S",
10, "S=L",
11, "L=S",
// The next sequences are not easy to enter, maybe it's better not to use them.
12, "L-L-L",
13, "S=S=S",
14, "L=L=L",
15, "S-S=S",
16, "S=S-S",
17, "L-L=L",
18, "L=L-L",
19, "S-S-L",
20, "S-L-L",
21, "S-L-S",
22, "L-S-S",
23, "L-L-S",
24, "L-S-L",
25, "S=S=L",
26, "S=L=S",
27, "S=L=L",
28, "L=S=S",
29, "L=L=S",
30, "L=S=L",
31, "S=L-S",
32, "S=S-L",
33, "S-L=S",
34, "S-S=L",
35, "S-L=L",
36, "L=L-S",
37, "L=S-L",
38, "L-L=S",
39, "L-S=L",
40, "L-S=S",
41, "S=L-L",
42, "L=S-S",
// These are special situations:
ID_ESCAPE, "E", // Escape
ID_ERROR, "Z", // Error
};
char input[20]; // Only 6 are needed, but more is allowed, e.g. to detect a open input with noise.
size_t index; // index of 'input[]'
#define indexPlusPlus if( index < (sizeof( input) - 1)) index++
unsigned long previousMillisPressed;
unsigned long previousMillisReleased;
const unsigned long shortDelay = 190;
const unsigned long longDelay = 900;
const byte pinButton = 2;
byte lastButtonState = HIGH; // set default to not active
bool sequenceActive = false; // true when a button sequence is active
void setup()
{
Serial.begin( 9600);
Serial.println( "Press the button in a specific sequence");
pinMode( pinButton, INPUT_PULLUP);
}
void loop()
{
// --------------------------------------------
// Create temporary variables
// --------------------------------------------
bool processInput = false; // set to default
unsigned long currentMillis = millis();
// --------------------------------------------
// Gather all information
// --------------------------------------------
byte buttonState = (byte) digitalRead( pinButton);
// --------------------------------------------
// Check the button events
// --------------------------------------------
if( buttonState != lastButtonState)
{
if( buttonState == LOW) // LOW when the button is pressed
{
// Button event: the button has been pressed just now.
if( sequenceActive)
{
// Calculate for how long the button was released.
unsigned long t = currentMillis - previousMillisReleased;
if( t < shortDelay)
{
input[index] = '-'; // short pause between button presses
indexPlusPlus; // macro to increment index
}
else if( t < longDelay)
{
// A valid pause
input[index] = '='; // long pause between button presses
indexPlusPlus; // macro to increment index
}
}
else
{
// The button was pressed, but a sequence is not active.
// That means the button is pressed for the first time.
sequenceActive = true;
}
// Update previousMillisPressed to remember the moment of the last time
// the button was pressed.
previousMillisPressed = currentMillis;
}
else
{
// Button event: the button has been released just now.
if( sequenceActive) // ignore releasing the button if 'Escape' was given before.
{
// Calculate for how long the button was pressed.
unsigned long t = currentMillis - previousMillisPressed;
if( t < shortDelay)
{
input[index] = 'S'; // button was pressed for a short time
indexPlusPlus; // macro to increment index
}
else if( t < longDelay)
{
// A valid pause
input[index] = 'L'; // button was pressed for a long time
indexPlusPlus; // macro to increment index
}
}
// Update previousMillisReleased to remember the moment of the last time
// the button was released.
previousMillisReleased = currentMillis;
}
lastButtonState = buttonState; // remember button state to detect a change next time
}
// --------------------------------------------
// Check the button state
// --------------------------------------------
if( sequenceActive)
{
if( buttonState == LOW) // the button state is being pressed ?
{
// Button state: the button is pressed.
if( currentMillis - previousMillisPressed >= longDelay)
{
// longer than 900ms pressed, "Escape" code.
// Ignore any input sequence up to now.
strcpy( input, "E");
processInput = true;
}
}
else // the button state is not pressed ?
{
// Button state: the button is not pressed.
if( currentMillis - previousMillisReleased >= longDelay)
{
// No button pressed for some time.
// End of the sequence.
input[index] = '\0'; // finish the input sequence
processInput = true;
}
}
// If reading a input sequence is busy, but not yet completed, then do an extra safety check
// Check if the input buffer is filled completely with noise or random button presses
// If it is, stop reading and return an error identifier of -1.
if( !processInput)
{
if( index >= sizeof( input) - 1)
{
// Throw away everything in the input buffer and set error sequence "Z".
strcpy( input, "Z");
processInput = true;
}
}
}
// --------------------------------------------
// Process the sequence of the button input
// --------------------------------------------
if( processInput)
{
bool found = false; // set default
byte identifier = ID_ERROR; // set default, no valid sequence
// Compare the sequence to the known sequences.
for( size_t i=0; i < (sizeof( list) / sizeof( list[0])) && !found; i++)
{
if( strcmp( input, list[i].sequence) == 0)
{
found = true;
identifier = list[i].id;
break; // found it, no need to search the rest
}
}
Serial.print( "Identifier: ");
Serial.print( identifier);
// Sound a beep for 'Escape' or wrong input.
if( identifier == ID_ESCAPE) // 'Escape'
{
Serial.print( " \"beep\" (escape)");
}
else if( identifier == ID_ERROR)
{
Serial.print( " \"beep\" (wrong input)");
}
Serial.println();
// Reset the variables and clear the sequence input buffer
sequenceActive = false;
index = 0;
input[0] = '\0';
}
delay( 10); // a delay as a simple debounce for the button
}