/*
* This arduino code maps physical buttons from a keypad to bluetooth keyboard commands
* Intended to make using your phone for navigation on a motorcycle easier
* See https://www.tinkercad.com/things/f8CpJLvZISb-navi-control-2 for a design of a case
*
* - The basic idea is that the keys are in a 'matrix keypad' numbered 1..9.' These
* are mapped to actual keys
* - You can have multiple keymaps and switch with the top rightbutton
* - keymaps need to be a specific order.
* - keymaps can have an option for long press
*
*/
#include <Keypad.h>
// For ESP32 Bluetooth keyboard HID https://github.com/T-vK/ESP32-BLE-Keyboard
// for using NUM_MINUS and NUM_PLUS rework changes from https://github.com/T-vK/ESP32-BLE-Keyboard/pull/54 until merged
#include <BleKeyboard.h>
// Set up the bleKeyboard instance
BleKeyboard bleKeyboard("Navi Control", "Joost Bijl", 100);
// Keypad library set up
////////////////////////
// This needs to be aligned with how you wired the buttons exactly.
// see https://www.circuitbasics.com/how-to-set-up-a-keypad-on-an-arduino/ for an intro into the keypad library
const byte ROWS = 3;
const byte COLS = 3;
// for this project we'll number the keys, and use their value to 'press' the correct button
char keys[ROWS][COLS] = {
{'1','4','7'},
{'2','5','8'},
{'3','6','9'}
};
// pin assignments (https://randomnerdtutorials.com/esp32-pinout-reference-gpios/)
//////////////////
// Signal led for status on handlebar, and flash on keymap change
const int LED_PIN = 15;
// pins for keypad library, these need to be aligned with how you wired the buttons exactly.
//byte rowPins[ROWS] = {18, 19, 23}; // left to right
//byte colPins[COLS] = {27, 26, 25}; // top to bottom
byte colPins[COLS] = {18, 19, 23}; // left to right
byte rowPins[ROWS] = {27, 26, 25}; // top to bottom
// Initialization
/////////////////
// Initialize keypad library
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );
// Initialize variable that determines how long a key is pressed to the current time in milliseconds
unsigned long hold_time = millis();
// define time for a long press (ms)
const int long_press_time = 440;
const int long_press_repeat_interval = 160;
// Variable that holds the current active keymap
int current_keymap = 0;
// How many keymaps do we have?
const int KEYMAP_COUNT = 3;
/*
* The 2 functions below determine what key gets sent.
*
* Key mapping for Kurviger
* https://forum.kurviger.de/t/feature-request-be-able-to-use-any-bluetooth-button-keyboard/5356
*
* Key mapping for Osmand
* https://github.com/osmandapp/OsmAnd/blob/master/OsmAnd/src/net/osmand/plus/activities/MapActivityKeyListener.java
* https://www.rigacci.org/wiki/doku.php/doc/appunti/android/osmand_remote_controller
*
* See here for special keys: https://github.com/T-vK/ESP32-BLE-Keyboard / https://www.arduino.cc/en/Reference/KeyboardModifiers
* NUM_PLUS / NUM_MINUS
* KEY_MEDIA_VOLUME_UP / KEY_MEDIA_VOLUME_DOWN
* KEY_UP_ARROW / KEY_DOWN_ARROW / KEY_LEFT_ARROW / KEY_RIGHT_ARROW
* KEY_MEDIA_PLAY_PAUSE
*/
// Routine to send the keystrokes on a short press of the keypad
void send_short_press(KeypadEvent key) {
Serial.println("Sending short press key");
Serial.println(key);
switch(key) {
case '1':
switch(current_keymap) {
case 0: bleKeyboard.write('c'); break;
case 1: bleKeyboard.write('c'); break;
case 2: bleKeyboard.write('c'); break;
}
break;
case '2':
switch(current_keymap) {
case 0: bleKeyboard.write('d'); break;
case 1: bleKeyboard.write('d'); break;
case 2: bleKeyboard.write('d'); break;
}
break;
case '4':
switch(current_keymap) {
case 0: bleKeyboard.write(KEY_MEDIA_VOLUME_UP); break;
case 1: bleKeyboard.write('='); break;
case 2: bleKeyboard.write(KEY_MEDIA_NEXT_TRACK); break;
}
break;
case '5':
switch(current_keymap) {
case 0: bleKeyboard.write(KEY_UP_ARROW); break;
case 1: bleKeyboard.write(KEY_UP_ARROW); break;
case 2: bleKeyboard.write(KEY_MEDIA_VOLUME_UP); break;
}
break;
case '6':
switch(current_keymap) {
case 0: bleKeyboard.write(KEY_RIGHT_ARROW); break;
case 1: bleKeyboard.write(KEY_RIGHT_ARROW); break;
case 2: bleKeyboard.write(KEY_RIGHT_ARROW); break;
}
break;
case '7':
switch(current_keymap) {
case 0: bleKeyboard.write(KEY_MEDIA_VOLUME_DOWN); break;
case 1: bleKeyboard.write('-'); break;
case 2: bleKeyboard.write(KEY_MEDIA_PREVIOUS_TRACK); break;
}
break;
case '8':
switch(current_keymap) {
case 0: bleKeyboard.write(KEY_LEFT_ARROW); break;
case 1: bleKeyboard.write(KEY_LEFT_ARROW); break;
case 2: bleKeyboard.write(KEY_LEFT_ARROW); break;
}
break;
case '9':
switch(current_keymap) {
case 0: bleKeyboard.write(KEY_DOWN_ARROW); break;
case 1: bleKeyboard.write(KEY_DOWN_ARROW); break;
case 2: bleKeyboard.write(KEY_MEDIA_VOLUME_DOWN); break;
}
break;
}
}
// Routine to send the keystrokes on a long press of the keypad
void send_long_press(KeypadEvent key) {
Serial.println("Sending long press key");
Serial.println(key);
switch(key) {
case '1':
switch(current_keymap) {
case 0: bleKeyboard.write('c'); break;
case 1: bleKeyboard.write('c'); break;
case 2: bleKeyboard.write('c'); break;
}
break;
case '2': switch_keymap(); break; // Keymap switcher button long press
case '4':
switch(current_keymap) {
case 0: send_repeating_key(KEY_MEDIA_VOLUME_UP); break;
case 1: send_repeating_key(NUM_PLUS); break;
case 2: send_repeating_key(KEY_MEDIA_VOLUME_UP); break;
}
break;
case '5':
switch(current_keymap) {
case 0: send_repeating_key(KEY_UP_ARROW); break;
case 1: send_repeating_key(KEY_UP_ARROW); break;
case 2: send_repeating_key(KEY_UP_ARROW); break;
}
break;
case '6':
switch(current_keymap) {
case 0: send_repeating_key(KEY_RIGHT_ARROW); break;
case 1: send_repeating_key(KEY_RIGHT_ARROW); break;
case 2: send_repeating_key(KEY_RIGHT_ARROW); break;
}
break;
case '7':
switch(current_keymap) {
case 0: send_repeating_key(KEY_MEDIA_VOLUME_DOWN); break;
case 1: send_repeating_key(NUM_MINUS); break;
case 2: send_repeating_key(KEY_MEDIA_VOLUME_DOWN); break;
}
break;
case '8':
switch(current_keymap) {
case 0: send_repeating_key(KEY_LEFT_ARROW); break;
case 1: send_repeating_key(KEY_LEFT_ARROW); break;
case 2: send_repeating_key(KEY_LEFT_ARROW); break;
}
break;
case '9':
switch(current_keymap) {
case 0: send_repeating_key(KEY_DOWN_ARROW); break;
case 1: send_repeating_key(KEY_DOWN_ARROW); break;
case 2: send_repeating_key(KEY_DOWN_ARROW); break;
}
break;
}
}
// Routine that sends a key repeatedly (for single char)
void send_repeating_key(uint8_t key) {
while(keypad.getState() == HOLD) {
bleKeyboard.write(key);
delay(long_press_repeat_interval); // pause between presses
keypad.getKey(); // update keypad event handler
}
}
// Routine that sends a key repeatedly (for double char 'MediaKeyReport')
void send_repeating_key(const MediaKeyReport key) {
while(keypad.getState() == HOLD) {
bleKeyboard.write(key);
delay(long_press_repeat_interval); // pause between presses
keypad.getKey(); // update keypad event handler
}
}
// This function handles events from the keypad.h library
void keypad_handler(KeypadEvent key){
//Serial.println(key);
//return;
// State changes for the buttons
switch(keypad.getState()) {
case PRESSED:
Serial.println("keypad.getState = PRESSED");
send_short_press(key);
break;
case HOLD:
Serial.println("keypad.getState = HOLD");
send_long_press(key);
break;
}
}
// Arduino built-in setup loop
void setup(){
Serial.begin(9600);
// Handle all keypad events through this listener
keypad.addEventListener(keypad_handler); // Add an event listener for this keypad
// set HoldTime
keypad.setHoldTime(long_press_time);
// Start bluetooth keyboard module
bleKeyboard.begin();
// Enable the led to indicate we're switched on
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
// End of setup()
Serial.println("Good to go!");
}
void loop(){
// Need to call this function constantly to make the keypad library work correctly
keypad.getKey();
delay(10);
}
// This function cycles the keymap and signals the new keymap via the LED
void switch_keymap() {
Serial.println("Switching keymap");
// cycle to next keymap
current_keymap++;
if(current_keymap > KEYMAP_COUNT-1) {
current_keymap = 0;
}
// flash led to indicate current map
// Take the first map out of the for loop (so we don't have a delay at the end)
digitalWrite(LED_PIN, LOW);
delay(200);
digitalWrite(LED_PIN, HIGH);
for (int i = 1; i <= current_keymap; i++) {
delay(200);
digitalWrite(LED_PIN, LOW);
delay(200);
digitalWrite(LED_PIN, HIGH);
}
}
uno:A5.2
uno:A4.2
uno:AREF
uno:GND.1
uno:13
uno:12
uno:11
uno:10
uno:9
uno:8
uno:7
uno:6
uno:5
uno:4
uno:3
uno:2
uno:1
uno:0
uno:IOREF
uno:RESET
uno:3.3V
uno:5V
uno:GND.2
uno:GND.3
uno:VIN
uno:A0
uno:A1
uno:A2
uno:A3
uno:A4
uno:A5
btn1:1.l
btn1:2.l
btn1:1.r
btn1:2.r
btn2:1.l
btn2:2.l
btn2:1.r
btn2:2.r
btn3:1.l
btn3:2.l
btn3:1.r
btn3:2.r
btn4:1.l
btn4:2.l
btn4:1.r
btn4:2.r
btn5:1.l
btn5:2.l
btn5:1.r
btn5:2.r
btn6:1.l
btn6:2.l
btn6:1.r
btn6:2.r
btn7:1.l
btn7:2.l
btn7:1.r
btn7:2.r
btn8:1.l
btn8:2.l
btn8:1.r
btn8:2.r
led1:A
led1:C