BT-NaviButtons

Vorwort: Ich beziehe mich im Folgenden auf meine Erfahrungen mit der Umsetzung und dem Bau von Entwicklungen Anderer, die ich für mich als gut befunden habe und die ich mit deren Einverständnis genutzt und für meine Bedürfnisse zum Teil noch angepasst und erweitert habe.

Der Dank für die eigentliche Entwicklungsarbeit steht in vollem Umfang den Entwicklern zu.


Ich navigiere auf dem Motorrad schon länger mit meinem Outdoor-Smartphone und nutze dafür meist die App OsmAnd.

Das Smartphone (derzeit ein Ulefone Armor 6) ist dabei dank kabelloser Ladefunktion jederzeit komplett wasserdicht.

Die Halterung ist nach dem Entwurf von Sindre Kopland-Hansen (Link) im 3D-Druck entstanden und hält das Gerät einfach und sicher, auch bei Offroad-Einsätzen.

Es gibt aber auch für andere Geräte passende und auch universell verwendbare Halterungen, die ähnlich gut nutzbar sind.

Hier möchte ich aber in erster Linie auf mein neuestes Projekt eingehen:

Ich wünschte mir schon lange eine Möglichkeit, die Kartenansicht in OsmAnd während der Fahrt manipulieren zu können, also die Karte rein oder raus zu zoomen oder zu verschieben.

Das geht zwar auf dem Bildschirm selber, ggf. auch mit geeigneten Handschuhen, ist aber nur unsicher durchzuführen, wenn das Smartphone weit vorne, oberhalb der Armaturen angebracht ist.

Es gibt entsprechende Lösungen für Bedienknöpfe am Lenker, die meist für Rallyzwecke zur Bedienung des Roadbooks vorgesehen sind, allerdings auch recht teuer und oft speziell ausgelegt.

OsmAnd lässt von Hause aus zu, die Kartenansicht über die Lautstärketasten des Smartphones zu zoomen, was man dann auch über einfache und preiswerte Bluetooth Mediabuttons, die man am Lenker anbringt, machen kann, aber mehr eben auch nicht.

Nun bin ich vor kurzem über das Projekt von Joost Bijl aus den Niederlanden gestolpert (Link).

Er hat ein DIY-Projekt auf Basis einer Arduino-Platine, und auch wieder mit 3D-Druck Einsatz, entwickelt, das genau meinen Vorstellungen entsprach.

Über eine Bluetoothverbindung zum Smartphone kann damit die Kartenansicht in OsmAnd genau nach einen Wünschen bedient werden, ohne die Hand vom Lenker zu lösen.

Schnell waren die nötigen Bauteile bestellt und, wegen seiner Erfahrung mit Arduinos, mein Freund Tobi informiert, der auch gleich von dem Projekt begeistert war.

Kurze Zeit später wurden die Komponenten geliefert und das Gehäuse (Link zu Thingiverse) ging in Druck.

Tobi gab mir noch einen Crashkurs in Arduino Grundlagen und das große Löten ging, der Anleitung folgend, los.

Im Testaufbau auf dem Arduino Breadboard hatte nach einigen kleinen Anpassungen alles gut funktioniert, so in echt ging aber gar nichts mehr richtig.

Joost hat in dem Lenkergehäuse nur die Schalter untergebracht und diese dann über ein acht-poliges Netzwerkkabel mit dem Prozessor in seinem eigenen Gehäuse verbunden, dass an geeigneter Stelle am Motorrad untergebracht wird.

Genau so habe ich es natürlich auch gemacht, die Kabellänge mal mit ca. 70 cm bis hinter die Cockpit-Seitenverkleidung gewählt, uuund . . . . nichts funktionierte mehr, wie es soll.

Der Prozessor gab laufend und völlig zufällige, unkoordinierte Befehle zum Smartphone, ohne dass ein Knopf betätigt wurde.

Mehre Versuche mit anderen Kabeln und -längen ergab, dass es bis zu einer Länge von ca. 25 cm sicher funktioniert und bei längeren Kabeln zu den beschriebenen Störungen führt

Das war mir deutlich zu kurz, da das Prozessorgehäuse ja dann auch noch am Lenker untergebracht werden müsste.

Meine Idee war dann, die Arduino-Platine doch gleich im Schaltergehäuse zu integrieren, dann würde ein dünnes, zweipoliges Kabel als Spannungsversorgung zum Schaltergehäuse reichen.

Joost hatte ein ESP32 Modul gewählt, bei dem Bluetooth bereits integriert ist, und das mittels einer zweiten kleinen Platine, die 12 Volt Bordspannung auf die vom Arduino benötigten 3,3 Volt reduziert, mit Strom versorgt wird.

Für dieses Modul war das Schaltergehäuse aber auch ohne die optionalen Steckleisten etwas zu eng, eine Suche führte mich aber schnell zu dem ESP32 D1 Mini NodeMCU Modul.

Es lässt sich noch gut in dem Schaltergehäuse unterbringen, bei dem ich aber trotzdem zur Sicherheit die Tiefe noch um ca. 2 mm erhöht habe (Joost hatte zwischenzeitlich eine verschraubte Version des Gehäuses entworfen).

Die Arduino-Platine habe ich mit etwas dickerem, doppelseitigem 3M-Klebepad an der Gehäuserückseite befestigt.

Da die, nun im Gehäuse enthaltene, Elektronik natürlich etwas empfindlicher ist, als die reinen Schalter, habe die die Kontaktflächen der Gehäusehälften sauber glatt geschliffen und mit ein wenig Silikon zur zusätzlichen Abdichtung versehen.

Der ESP32 D1 Mini kann auch direkt mit 5 Volt Spannung versorgt werden, so dass jetzt ein dünnes Kabel zu meinem bereits vorhandenen, wasserdichten 12V/5V-Konverter reicht, der das QI-Ladepad für das Smartphone versorgt.

Nach dem ersten Schalterbau war mir aufgefallen, dass eine Änderung oder Aktualisierung der Programmierung, ein aufwändiges Zerlegen der Schaltereinheit bedingen würde.

Da ich sowieso noch eine zweite Einheit (für das Zweitmotorrad) benötigte, brachte ich noch eine Öffnung in der Gehäuserückseite an, durch die der Micro USB Port des ESP32 auch bei geschlossenem Gehäuse zugänglich ist, und das durch einen passenden Stopfen wohl ausreichend verschlossen wird.

Mit den 8 Buttons in der Schaltereinheit kann man nun in OsmAnd die Karte nach links/rechts/oben/unten verschieben, hinein- und hinauszoomen, die Karte wieder auf den Standort zentrieren und die Kartenausrichtung (Fahrtrichtung/Nord/Kompass) wechseln.

Die Kartenfunktionen erlauben ebenfalls die Bedienung der beliebten Kurviger App.

Weiterhin kann zwischen drei unterschiedlichen Buttonmappings gewählt werden, die beim Umschalten über die Power-LED per Blinksignal angezeigt werden.

Die Mappings können in dem Arduino-Programmscript frei angepasst werden.

Ich steuere derzeit neben OsmAnd in einem weiteren Mapping meinen Android Musicplayer mit dem Befehlen Musik Start/Pause, nächster/vorheriger Song und lauter/leiser.


Nachstehend das von Tobi und mir für den ESP32 D1 Mini angepasste Script, das aber grundsätzlich immer noch auf der Idee von Joost basiert und die Button-Zuordnung mit den Ports auf dem ESP32.

Nähere Beschreibungen zu den genutzten Arduino-Bibliotheken und den nutzbaren Befehlen finden sich bei den im Script genannten Quellen.

/*
 * 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
 *
 * - 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
#include <BleKeyboard.h> 

// Set up the bleKeyboard instance
BleKeyboard bleKeyboard;

// 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 = 26;  

// pins for keypad library, these need to be aligned with how you wired the buttons exactly. 
byte colPins[COLS] = {18, 19, 23};  // left to right
byte rowPins[ROWS] = {32, 25, 27};  // 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. 
 * 
 * See here for special keys:
 * - https://github.com/T-vK/ESP32-BLE-Keyboard/blob/master/BleKeyboard.h
 * - https://www.arduino.cc/en/Reference/KeyboardModifiers
 *
 * Candidates are:
 * 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(KEY_MEDIA_PLAY_PAUSE); 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_VOLUME_UP); 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_UP_ARROW); break;
        }
        break;
    case '6': 
        switch(current_keymap) { 
          case 0: bleKeyboard.write(KEY_LEFT_ARROW); break;
          case 1: bleKeyboard.write(KEY_LEFT_ARROW); break;
          case 2: bleKeyboard.write(KEY_MEDIA_PREVIOUS_TRACK); 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_VOLUME_DOWN); break;
        }
        break;
    case '8': 
        switch(current_keymap) { 
          case 0: bleKeyboard.write(KEY_RIGHT_ARROW); break;
          case 1: bleKeyboard.write(KEY_RIGHT_ARROW); break;
          case 2: bleKeyboard.write(KEY_MEDIA_NEXT_TRACK); 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_DOWN_ARROW); 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(KEY_MEDIA_PLAY_PAUSE); 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(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_LEFT_ARROW); break;
          case 1: send_repeating_key(KEY_LEFT_ARROW); break;
          case 2: send_repeating_key(KEY_MEDIA_PREVIOUS_TRACK); break;
        }
        break;
    case '7': 
        switch(current_keymap) { 
          case 0: send_repeating_key(KEY_MEDIA_VOLUME_DOWN); break;
          case 1: send_repeating_key(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_RIGHT_ARROW); break;
          case 1: send_repeating_key(KEY_RIGHT_ARROW); break;
          case 2: send_repeating_key(KEY_MEDIA_NEXT_TRACK); 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);
  }
}

One thought on “BT-NaviButtons

Schreibe einen Kommentar