/*
 * 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);
  }
}