ppk_bt/ppk_ble.ino
2022-10-23 16:35:57 +01:00

535 lines
13 KiB
C++

/*
* ppk_usb
*
* Copyright (C) 2014 cy384
* All rights reserved.
*
* This software may be modified and distributed under the terms
* of the BSD license. See the LICENSE file for details.
*
* MODIFIED by Christian Lysholm 2021
* Modified for BLE support using a tinyPICO ESP32 module.
* Added battery monitoring and reporting via RGB LED
* Moved keyboard booting until peripheral is detected
*
*/
// Arduino BLE HID adapter for the Palm Portable Keyboard
// If this include causes an error Arduino, just comment it out
//#include <Keyboard.h>
#include <SoftwareSerial.h>
#include <TinyPICO.h>
//#include <BleConnectionStatus.h>
#include <BleKeyboard.h>
//#include <KeyboardOutputCallbacks.h>
// set to 3 for III hardware, or 5 for V hardware
#define PPK_VERSION 500
// set to 1 to enable debug mode, which notes to the arduino console at 9600
#define PPK_DEBUG 0
#if PPK_VERSION == 3
#define VCC_PIN 2
#define RX_PIN 8
#define RTS_PIN 4
#define DCD_PIN 5
#define GND_PIN 6
#endif
#if PPK_VERSION == 5
#define VCC_PIN 7
#define RX_PIN 8
#define RTS_PIN 5
#define DCD_PIN 4
#define GND_PIN 2
#endif
#if PPK_VERSION == 500
#define VCC_PIN 4
#define RX_PIN 27 //External 10k pulldown resistor added
#define RTS_PIN 26
#define DCD_PIN 25
#define GND_PIN 18 //UNUSED
#define DET_PIN 22 //Peripheral detect, held low by KB
#endif
#define PULLDOWN_PIN 23
// set this to any unused pin
#define TX_PIN 19
#if (PPK_VERSION != 3) && (PPK_VERSION != 5) && (PPK_VERSION != 500)
#error
#error
#error you did not set your ppk version!
#error read the instructions or read the code!
#error
#error
#endif
// convenience masks
#define UPDOWN_MASK 0b10000000
#define X_MASK 0b00000111
#define Y_MASK 0b01111000
#define MAP_MASK 0b01111111
// wait this many milliseconds before making sure keyboard is still awake
#define TIMEOUT 500000
#define BAT_CHECK_TIME 300000 //5 minutes
#define HEARTBEAT_TIME 5000 //5 seconds
// macro for testing if a char is printable ASCII
#define PRINTABLE_CHAR(x) ((x >= 32) && (x <= 126))
uint32_t heartbeat_color;
SoftwareSerial keyboard_serial(RX_PIN, TX_PIN, true); // RX, TX, inverted
TinyPICO tp = TinyPICO();
int check_battery(void);
int batLevel = check_battery();
BleKeyboard ble_kb("PICO_PPK", "LysTech", batLevel);
char key_map[128] = { 0 };
char fn_key_map[128] = { 0 };
char last_byte = 0;
char key_byte = 0;
int fn_key_down = 0;
int boot_state = 0; //keyboard boot state (0/1)
unsigned long last_comm = 0;
unsigned long last_bat = 0;
unsigned long last_heartbeat = 0;
unsigned long heartbeat_length = 750; //milliseconds
void config_keymap()
{
// y0 row
key_map[0b00000000] = '1';
key_map[0b00000001] = '2';
key_map[0b00000010] = '3';
key_map[0b00000011] = 'z';
key_map[0b00000100] = '4';
key_map[0b00000101] = '5';
key_map[0b00000110] = '6';
key_map[0b00000111] = '7';
// y1 row
key_map[0b00001000] = KEY_LEFT_GUI; // "CMMD" or "Cmd"
key_map[0b00001001] = 'q';
key_map[0b00001010] = 'w';
key_map[0b00001011] = 'e';
key_map[0b00001100] = 'r';
key_map[0b00001101] = 't';
key_map[0b00001110] = 'y';
key_map[0b00001111] = '`';
// y2 row
key_map[0b00010000] = 'x';
key_map[0b00010001] = 'a';
key_map[0b00010010] = 's';
key_map[0b00010011] = 'd';
key_map[0b00010100] = 'f';
key_map[0b00010101] = 'g';
key_map[0b00010110] = 'h';
key_map[0b00010111] = ' '; // "Space 1"
// y3 row
key_map[0b00011000] = KEY_CAPS_LOCK;
key_map[0b00011001] = KEY_TAB;
key_map[0b00011010] = KEY_LEFT_CTRL;
key_map[0b00011011] = 0;
key_map[0b00011100] = 0;
key_map[0b00011101] = 0;
key_map[0b00011110] = 0;
key_map[0b00011111] = 0;
// y4 row
key_map[0b00100000] = 0;
key_map[0b00100001] = 0;
key_map[0b00100010] = 0; // Fn key
key_map[0b00100011] = KEY_LEFT_ALT;
key_map[0b00100100] = 0;
key_map[0b00100101] = 0;
key_map[0b00100110] = 0;
key_map[0b00100111] = 0;
// y5 row
key_map[0b00101000] = 0;
key_map[0b00101001] = 0;
key_map[0b00101010] = 0;
key_map[0b00101011] = 0;
key_map[0b00101100] = 'c';
key_map[0b00101101] = 'v';
key_map[0b00101110] = 'b';
key_map[0b00101111] = 'n';
// y6 row
key_map[0b00110000] = '-';
key_map[0b00110001] = '=';
key_map[0b00110010] = KEY_BACKSPACE;
key_map[0b00110011] = 0; // "Special Function One"
key_map[0b00110100] = '8';
key_map[0b00110101] = '9';
key_map[0b00110110] = '0';
key_map[0b00110111] = ' '; // "Space 2"
// y7 row
key_map[0b00111000] = '[';
key_map[0b00111001] = ']';
key_map[0b00111010] = '\\';
key_map[0b00111011] = 0; // "Special Function Two"
key_map[0b00111100] = 'u';
key_map[0b00111101] = 'i';
key_map[0b00111110] = 'o';
key_map[0b00111111] = 'p';
// y8 row
key_map[0b01000000] = '\'';
key_map[0b01000001] = KEY_RETURN;
key_map[0b01000010] = 0; // "Special Function Three"
key_map[0b01000011] = 0;
key_map[0b01000100] = 'j';
key_map[0b01000101] = 'k';
key_map[0b01000110] = 'l';
key_map[0b01000111] = ';';
// y9 row
key_map[0b01001000] = '/';
key_map[0b01001001] = KEY_UP_ARROW;
key_map[0b01001010] = 0; // "Special Function Four"
key_map[0b01001011] = 0;
key_map[0b01001100] = 'm';
key_map[0b01001101] = ',';
key_map[0b01001110] = '.';
key_map[0b01001111] = 0; // "DONE" or "Done"
// y10 row
key_map[0b01010000] = KEY_DELETE;
key_map[0b01010001] = KEY_LEFT_ARROW;
key_map[0b01010010] = KEY_DOWN_ARROW;
key_map[0b01010011] = KEY_RIGHT_ARROW;
key_map[0b01010100] = 0;
key_map[0b01010101] = 0;
key_map[0b01010110] = 0;
key_map[0b01010111] = 0;
// y11 row
key_map[0b01011000] = KEY_LEFT_SHIFT;
key_map[0b01011001] = KEY_RIGHT_SHIFT;
key_map[0b01011010] = 0;
key_map[0b01011011] = 0;
key_map[0b01011100] = 0;
key_map[0b01011101] = 0;
key_map[0b01011110] = 0;
key_map[0b01011111] = 0;
}
void config_fnkeymap()
{
fn_key_map[0b01010001] = KEY_HOME; // left arrow
fn_key_map[0b01010010] = KEY_PAGE_DOWN; // down arrow
fn_key_map[0b01010011] = KEY_END; // right arrow
fn_key_map[0b01001001] = KEY_PAGE_UP; // up arrow
fn_key_map[0b00011001] = KEY_ESC; // tab key
fn_key_map[0b00000000] = KEY_F1; // 1
fn_key_map[0b00000001] = KEY_F2; // 2
fn_key_map[0b00000010] = KEY_F3; // 3
fn_key_map[0b00000100] = KEY_F4; // 4
fn_key_map[0b00000101] = KEY_F5; // 5
fn_key_map[0b00000110] = KEY_F6; // 6
fn_key_map[0b00000111] = KEY_F7; // 7
fn_key_map[0b00110100] = KEY_F8; // 8
fn_key_map[0b00110101] = KEY_F9; // 9
fn_key_map[0b00110110] = KEY_F10; // 0
fn_key_map[0b00110000] = KEY_F11; // -
fn_key_map[0b00110001] = KEY_F12; // =
}
void print_byte_bin(char bin_byte)
{
Serial.print("0b");
for (int i = 7; i > -1; i--) Serial.print(int((bin_byte & (1 << i)) >> i));
}
void print_keychange(char key_byte, char key_code, int key_up)
{
if (key_up) Serial.print("released: "); else Serial.print("pressed: ");
print_byte_bin(key_byte);
Serial.print(" mapped to ");
if (key_code)
{
print_byte_bin(key_code);
if(PRINTABLE_CHAR(key_code))
{
Serial.print(" (");
Serial.print(key_code);
Serial.print(")");
}
else
{
Serial.print(" (unprintable)");
}
}
// Fn has no keycode, special case it
else if (key_byte == 34)
{
Serial.print("Fn");
}
else
{
Serial.print("nothing");
}
Serial.println("");
}
void boot_keyboard()
{
if (PPK_DEBUG)
{
// delay for a bit to allow for opening serial monitor etc.
for (int i = 0; i < 15; delay(1000 + i++)) Serial.print(".");
Serial.println("beginning keyboard boot sequence");
}
tp.DotStar_SetPower(true);
tp.DotStar_SetPixelColor(0x0000FF);
pinMode(VCC_PIN, OUTPUT);
pinMode(GND_PIN, OUTPUT);
pinMode(PULLDOWN_PIN, OUTPUT);
pinMode(RX_PIN, INPUT); //Add external Pulldown
pinMode(DCD_PIN, INPUT);
pinMode(RTS_PIN, INPUT);
digitalWrite(VCC_PIN, LOW);
digitalWrite(GND_PIN, LOW);
digitalWrite(PULLDOWN_PIN, LOW);
digitalWrite(VCC_PIN, HIGH);
keyboard_serial.begin(9600);
keyboard_serial.listen();
if (PPK_DEBUG) Serial.print("waiting for keyboard response...");
while(digitalRead(DCD_PIN) != HIGH) {;};
if (PPK_DEBUG) Serial.println(" done");
if (PPK_DEBUG) Serial.print("finishing handshake...");
if (digitalRead(RTS_PIN) == LOW)
{
delay(10);
pinMode(RTS_PIN, OUTPUT);
digitalWrite(RTS_PIN, HIGH);
}
else
{
pinMode(RTS_PIN, OUTPUT);
digitalWrite(RTS_PIN, HIGH);
digitalWrite(RTS_PIN, LOW);
delay(10);
digitalWrite(RTS_PIN, HIGH);
}
delay(5);
if (PPK_DEBUG) Serial.println(" done");
if (PPK_DEBUG) Serial.print("waiting for keyboard serial ID...");
while (keyboard_serial.available() < 2) {;};
if (PPK_DEBUG) Serial.println(" done");
int byte1 = keyboard_serial.read();
int byte2 = keyboard_serial.read();
if (!((byte1 == 0xFA) && (byte2 == 0xFD)))
{
if (PPK_DEBUG) Serial.println("got wrong bytes? giving up here");
tp.DotStar_SetPower(true);
tp.DotStar_SetPixelColor(0xFF00FF);
while (1) {;};
}
last_comm = millis();
tp.DotStar_SetPower(true);
tp.DotStar_SetPixelColor(0x00FFFF);
}
int check_battery()
{
//batVolt has voltage levels every 5% of charge level. Adjust as necessary. Slightly better than a straight linear interp of voltage
float batVolt_lookup[] = {3.27, 3.61, 3.69, 3.71, 3.73, 3.75, 3.77, 3.79, 3.80, 3.82, 3.84, 3.85, 3.87, 3.91, 3.95, 3.98, 4.02, 4.08, 4.11, 4.15, 4.20};
float batVolt = 0.0; //volts
int batLevel = -1; //0 to 100 (percent as int)
batVolt = tp.GetBatteryVoltage();
for (int n=0; n<=21; n++)
{
if (batVolt < batVolt_lookup[n])
{
batLevel = 5*n;
break;
}
}
if (batLevel < 0)
{
//You shouldn't be here, batVolt was not found in range of lookup values.
batLevel = 100;
}
return batLevel;
}
void check_connection()
{
if(digitalRead(DET_PIN))
{
boot_state = 0;
ble_kb.end();
delay(500);
tp.DotStar_SetPower(true);
tp.DotStar_SetPixelColor(0x0000FF);
}
}
void setup()
{
if (PPK_DEBUG)
{
Serial.begin(9600);
Serial.print("compiled in debug mode with PPK_VERSION ");
Serial.println(PPK_VERSION);
}
config_keymap();
config_fnkeymap();
pinMode(DET_PIN, INPUT_PULLUP);
boot_keyboard();
ble_kb.begin();
tp.DotStar_SetPower(true);
//tp.DotStar_SetBrightness(64); //25% brightness
last_bat = BAT_CHECK_TIME;
if (PPK_DEBUG) Serial.println("setup completed");
}
void loop()
{
if (keyboard_serial.available())
{
for (int i = keyboard_serial.available(); i > 0; i--)
{
key_byte = keyboard_serial.read();
int key_up = key_byte & UPDOWN_MASK;
int key_x = 0 + (key_byte & X_MASK);
int key_y = 0 + ((key_byte & Y_MASK) >> 3);
char key_code = 0;
if (fn_key_down && fn_key_map[key_byte & MAP_MASK])
{
key_code += fn_key_map[key_byte & MAP_MASK];
}
else
{
key_code += key_map[key_byte & MAP_MASK];
}
// keyboard duplicates the final key-up byte
if (key_byte == last_byte)
{
ble_kb.releaseAll();
}
else
{
if (PPK_DEBUG) print_keychange(key_byte & MAP_MASK, key_code, key_up);
if (key_code != 0)
{
if (key_up)
{
ble_kb.release(key_code);
}
else
{
ble_kb.press(key_code);
}
}
else
{
// special case the Fn key
if ((key_byte & MAP_MASK) == 34)
{
fn_key_down = !key_up;
}
}
}
last_byte = key_byte;
last_comm = millis();
}
}
else
{
// reboot if no recent comms, otherwise keyboard falls asleep
if ((millis() - last_comm) > TIMEOUT)
{
if (PPK_DEBUG) Serial.println("rebooting keyboard for timeout");
digitalWrite(VCC_PIN, LOW);
boot_keyboard();
}
}
if ((millis() - last_bat) > BAT_CHECK_TIME)
{
batLevel = check_battery();
ble_kb.setBatteryLevel(batLevel);
if (batLevel > 30) //30-100%
{
//set green heartbeat
heartbeat_color = 0x00FF00;
}
else if (batLevel >= 15)//15-30%
{
//set yellow/orange heartbeat
heartbeat_color = 0xFF8000;
}
else // <15%
{
//set red heartbeat
heartbeat_color = 0xFF0000;
}
tp.DotStar_SetPower(false);
last_bat = millis();
}
if ((millis() - last_heartbeat) > (HEARTBEAT_TIME + heartbeat_length))
{
tp.DotStar_Clear();
tp.DotStar_SetPower(false);
last_heartbeat = millis();
}
else if ((millis() - last_heartbeat) > HEARTBEAT_TIME)
{
tp.DotStar_SetPower(true);
tp.DotStar_SetPixelColor(heartbeat_color);
//check_connection();
}
}