19. Event Driven Keypad

19. Event Driven Keypad
Photo by ifer endahl / Unsplash

In the original A Simple Keypad post, we wrote some code to send keyboard events using the Teensy's built in Keyboard emulator libraries. While this works, the code as written pretty much acts as a momentary button works, by checking for a key press in the loop() function and send a key press event, wait a bit, and send a key release event. But what if we want to use the keypad as an actual keyboard, where you can press and hold a key and it will initially type it once, and while hold it down, it will repeatedly keep sending the same key until you release it. Luckily, the Keypad.h library provides a mechanism for that via an event listener.

Here are the code changes:

#include <Keypad.h>

const byte ROWS = 4;
const byte COLS = 4;

char keys[ROWS][COLS] = {
  { '1', '2', '3', 'A' },
  { '4', '5', '6', 'B' },
  { '7', '8', '9', 'C' },
  { '*', '0', '#', 'D' }
};

// pads keypad
// byte rowPins[ROWS] = { 12, 11, 10, 9 };  //connect to the row pinouts of the keypad
// byte colPins[COLS] = { 8, 7, 6, 5 };     //connect to the column pinouts of the keypad
// mechanical keypads
byte rowPins[ROWS] = { 8, 7, 6, 5 };     //connect to the row pinouts of the keypad
byte colPins[COLS] = { 12, 11, 10, 9 };  //connect to the column pinouts of the keypad

//Create an object of keypad
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

void setup() {
  keypad.addEventListener(keypadEvent);
}


void loop() {
  keypad.getKey();
}

void keypadEvent(KeypadEvent key) {
  switch (keypad.getState()) {
    case PRESSED:
    case HOLD:
      Keyboard.press(key);
      break;
    case RELEASED:
      Keyboard.release(key);
      break;
    case IDLE:
      break;
  }
}

Breakdown

The first noticeable change is that during the setup() function, we are adding an event listener called keypadEvent, which we later define in the code. Next, our loop() function now just calls getKey() but doesn't do anything with it. This allows the library to scan for key presses.

If we take a look at the void keypadEvent(KeypadEvent key) function, we see that now we can get the state of the keypad using the keypad.getState(). There are 4 predefined states, IDLE, PRESSED, HOLD and RELEASED. With these now we can send the corresponding keyboard events using the built-in Keyboard object, which contains a press(key) and release(key) functions that we can use.

Sending Multiple Keys

So now that we have this basic functionality in place, we can make some more fun stuff. Lets map the * and # keys to be CTRL+C and CTRL+V to do something different. We can change the event listener function to do something like this instead:

void keypadEvent(KeypadEvent key) {
  switch (keypad.getState()) {
    case PRESSED:
    case HOLD:
      pressKey(key);
      break;
    case RELEASED:
      releaseKey(key);
      break;
    case IDLE:
      break;
  }
}

void pressKey(KeypadEvent key) {
  switch (key) {
    case '*':
      Keyboard.press(KEY_LEFT_CTRL);
      delay(25);
      Keyboard.press(KEY_C);
      delay(25);
      break;
    case '#':
      Keyboard.press(KEY_LEFT_CTRL);
      delay(25);
      Keyboard.press(KEY_V);
      delay(25);
      break;
    default:
      Keyboard.press(key);
      break;
  }
}

void releaseKey(KeypadEvent key) {
  switch (key) {
    case '*':
      Keyboard.release(KEY_LEFT_CTRL);
      delay(25);
      Keyboard.release(KEY_C);
      delay(25);
      break;
    case '#':
      Keyboard.release(KEY_LEFT_CTRL);
      delay(25);
      Keyboard.release(KEY_V);
      delay(25);
      break;
    default:
      Keyboard.release(key);
      break;
  }
}

Now depending on what key was pressed, we can either send the default Keyboard press and release events, or send a combination of them. And you are not just limited to send Keyboard events. The Teensy's also have built-in Mouse and Joystick emulation so you can technically send Mouse clicks, drags, etc. the same way you send the Keyboard events.

Here is an example on how I bound the # key to click on the screen, press the right ALT key down and send a mouse click. If you play Microsoft Flight Simulator you know this is what you need to click to pop-out the digital panels, and then use the keypad's D to maximize it.

 case '#':
      Mouse.click();
      delay(25);
      Keyboard.press(KEY_RIGHT_ALT);
      delay(25);
      Mouse.set_buttons(1, 0, 0);
      break;

case 'D':
      Mouse.click();
      delay(25);
      Keyboard.press(KEY_RIGHT_ALT);
      delay(25);
      Keyboard.press(KEY_ENTER);
      break;

Conclusion

While the initial way of sending keyboard events discussed earlier works, it's limited if you for example wanted to do key combinations. I ended up changing my code to this way because I wanted to bind a key to ALT and hold it down while I clicked on my mouse, but eventually figured out that I could just bind a key and send all those events and don't need to even use my keyboard or mouse to do so.

Hope you found this updated code useful and see you on the next one!