Bluetooth BLE HID Keyboard for the AlterErgo device, based on Seeed Studio Tiny BLE.

Dependencies:   BLE_API BLE_HID mbed nRF51822

Fork of BLENano_HID by Yuuichi Akagawa

Revision:
1:c6659c8882c9
Parent:
0:3435302551b3
--- a/main.cpp	Sat Feb 27 05:08:02 2016 +0000
+++ b/main.cpp	Sun Aug 26 10:19:01 2018 +0000
@@ -1,3 +1,76 @@
+/**
+ * Handheld BLE finger input device based on Seeed Studio Tiny BLE (TinyBLE).
+ * By Shervin Emami (http://shervinemami.info/), Apr 2018.
+ * If the analog voltage on a "finger" pin is below a threshold voltage,
+ * then it sends a rare keypress using Bluetooth, so that any computer or
+ * smartphone can be configured to perform a desired function due to this input event.
+ *
+ * 
+ * This program implements a complete HID-over-Gatt Profile:
+ *  - HID is provided by KeyboardService
+ *  - Battery Service
+ *  - Device Information Service
+ *
+ * Complete strings can be sent over BLE using printf. Please note, however, than a 12char string
+ * will take about 500ms to transmit, principally because of the limited notification rate in BLE.
+ * KeyboardService uses a circular buffer to store the strings to send, and calls to putc will fail
+ * once this buffer is full. This will result in partial strings being sent to the client.
+ 
+ * Tested with mbed libraries for April 2nd, 2018. If you use a newer version of mbed or other
+ * libs and you have problems, try rolling back to April 2nd, 2018.
+ */ 
+
+// Configure the settings of my board.
+#include "tiny_ble.h"   // Seeed Studio Tiny BLE
+
+
+// Define LOG_MESSAGES if you want various debug messages to be sent to the PC via a UART cable.
+#define LOG_MESSAGES
+
+// Various USB Keyboard keycodes that we can send.
+#define KEYCODE_F4  131
+#define KEYCODE_F5  132
+#define KEYCODE_F6  133
+#define KEYCODE_F7  134
+#define KEYCODE_F8  135
+#define KEYCODE_F9  136
+#define KEYCODE_F10 137
+#define KEYCODE_F11 138
+#define KEYCODE_F12 139
+#define KEYCODE_HOME     145
+#define KEYCODE_PAGEUP   146
+#define KEYCODE_PAGEDOWN 147
+#define KEYCODE_RIGHT    148
+#define KEYCODE_LEFT     149
+#define KEYCODE_DOWN     150
+#define KEYCODE_UP       151
+#define KEYCODE_ESCAPE   157
+#define KEYCODE_RIGHTCLK 188
+#define KEYCODE_ACCENT    96
+#define KEYCODE_NUMLOCK  193
+
+
+
+
+const int FING1_KEYCODE = KEYCODE_F8;         // Set the keypress that will be sent over bluetooth.
+const int FING2DOWN_KEYCODE = KEYCODE_F4;         //
+const int FING2UP_KEYCODE = KEYCODE_NUMLOCK;
+const int FING3_KEYCODE = KEYCODE_UP;         //
+const int FING4_KEYCODE = KEYCODE_DOWN;         //
+
+const float FINGER1_PRESS = 0.42f;   // Set how much the person needs to press their finger in to trigger a keypress.
+const float FINGER2_PRESS = 0.41f;   // Note that each finger is slightly different strength, due to the mechanical design.
+const float FINGER3_PRESS = 0.38f;   //
+const float FINGER4_PRESS = 0.37f;   //
+
+const float FINGER1_RELEASE = 0.35f;   // Set how much the person needs to release their finger to finish a keypress.
+const float FINGER2_RELEASE = 0.35f;   // Note that each finger is slightly different strength, due to the mechanical design.
+const float FINGER3_RELEASE = 0.34f;   //
+const float FINGER4_RELEASE = 0.34f;   //
+
+const float BATTERY_NEEDS_CHARGE = 0.198f;  // 0.20 means >= 3.4V, 0.19 means >= 2.4V.
+
+ 
 /* mbed Microcontroller Library
  * Copyright (c) 2015 ARM Limited
  *
@@ -20,79 +93,146 @@
 #include "KeyboardService.h"
  
 #include "examples_common.h"
- 
-/**
- * This program implements a complete HID-over-Gatt Profile:
- *  - HID is provided by KeyboardService
- *  - Battery Service
- *  - Device Information Service
- *
- * Complete strings can be sent over BLE using printf. Please note, however, than a 12char string
- * will take about 500ms to transmit, principally because of the limited notification rate in BLE.
- * KeyboardService uses a circular buffer to store the strings to send, and calls to putc will fail
- * once this buffer is full. This will result in partial strings being sent to the client.
- */
- 
-DigitalOut waiting_led(LED1);
-DigitalOut connected_led(LED2);
- 
-InterruptIn button1(D2);
+
+
+
+#undef HID_DEBUG
+#undef LOG
+#ifdef LOG_MESSAGES
+    #define LOG(...)    { pc.printf(__VA_ARGS__); }
+    Serial pc(UART_TX, UART_RX);
+#else
+    #define LOG(...)
+#endif
+#define HID_DEBUG   LOG
+
+
+AnalogIn    battery(BATTERY_PIN);   // For measuring the battery level
+AnalogIn    finger1(FINGER1_PIN);   // For measuring a finger level
+AnalogIn    finger2(FINGER2_PIN);   //
+AnalogIn    finger3(FINGER3_PIN);   //
+AnalogIn    finger4(FINGER4_PIN);   //
+DigitalOut waiting_led(LED_RED);      // For showing BLE is trying to connect
+DigitalOut connected_led(LED_GREEN);  // For showing BLE is connected
+DigitalOut status_led(LED_BLUE);      // For showing BLE is busy
+DigitalOut enable_out3v3(OUT3V3_PIN); // For using OUT_3V3 pin of TinyBLE
+
+#define LED_ON   0      // LEDs on Tiny BLE need 0V to light up.
+#define LED_OFF  1      //
+
+
+bool haveAskedForRecharge = false;  // Only ask for a recharge once per poweron. Requires user to turn device off once per day!
+
+
+InterruptIn button1(BUTTON_PIN);    // For sending a BLE command
  
 BLE ble;
 KeyboardService *kbdServicePtr;
  
-static const char DEVICE_NAME[] = "uKbd";
-static const char SHORT_DEVICE_NAME[] = "kbd1";
- 
+static const char DEVICE_NAME[] = "ShervFingerControl";
+static const char SHORT_DEVICE_NAME[] = "ShFingerCtl";
+
+
+// Status flags that are updated in ISR callback functions.
+volatile bool check_fingers = false;
+volatile bool press_keyup[5] = {false};
+
+
+
 static void onDisconnect(const Gap::DisconnectionCallbackParams_t *params)
 {
-    HID_DEBUG("disconnected\r\n");
-    connected_led = 0;
+    HID_DEBUG("discon\n\r");
+    waiting_led = LED_ON;
+    connected_led = LED_OFF;
  
     ble.gap().startAdvertising(); // restart advertising
 }
  
 static void onConnect(const Gap::ConnectionCallbackParams_t *params)
 {
-    HID_DEBUG("connected\r\n");
-    waiting_led = false;
+    HID_DEBUG("conn\n\r");
+    waiting_led = LED_OFF;
+    connected_led = LED_ON;
 }
+
+
+
+void send_keypress(int keycode, int finger) {
+    if (!kbdServicePtr)
+        return;
  
-static void waiting() {
-    if (!kbdServicePtr->isConnected())
-        waiting_led = !waiting_led;
-    else
-        connected_led = !connected_led;
+    if (!kbdServicePtr->isConnected()) {
+        HID_DEBUG("no conn yet\n\r");
+    } else {
+        //LOG("send_keypress(%d, %d)\n\r", keycode, finger);
+        kbdServicePtr->keyDownCode(keycode, 0);
+        press_keyup[finger] = true;
+        
+    }
 }
- 
+
 void send_string(const char * c) {
     if (!kbdServicePtr)
         return;
  
     if (!kbdServicePtr->isConnected()) {
-        HID_DEBUG("we haven't connected yet...");
+        HID_DEBUG("no conn yet\n\r");
     } else {
         int len = strlen(c);
         kbdServicePtr->printf(c);
-        HID_DEBUG("sending %d chars\r\n", len);
+        HID_DEBUG("sending %d chars\n\r", len);
     }
 }
+
+
+// Call the button1_ISR function whenever the pushbutton is pressed.
+// Make sure there isn't anything slow like printf in this ISR!
+void button1_ISR() {
+    send_string("x\n");
+}
+
+
+// ISR callback function that is automatically called every 0.1 seconds or so.
+// Make sure there isn't anything slow like printf in this ISR!
+static void heartbeat_ISR() {
+    if (!kbdServicePtr->isConnected())
+        waiting_led = !waiting_led;
+    else {
+        //connected_led = !connected_led;
+        //waiting_led = 0;
+    }
+
+    // Signal that we should check the finger sensors soon.
+    check_fingers = true;
+}
  
-void send_stuff() {
-    send_string("hello world!\n");
-}
  
 int main()
 {
-    Ticker heartbeat;
- 
-    button1.rise(send_stuff);
+#ifdef LOG_MESSAGES
+    pc.baud(115200);  // Use fast UART for log messages instead of default 9600 bps.
+#endif
+    
+    waiting_led = LED_OFF;    // Set LEDs to blue, until ready.
+    connected_led = LED_OFF;
+    status_led = LED_ON;
+    
+    wait(1);    
+    LOG("---- Shervin's Finger Input device, using TinyBLE + BLE HID keyboard service ----\n\r");
+
+    // Call the button1_ISR function whenever the pushbutton is pressed.
+    button1.rise(button1_ISR);
  
-    HID_DEBUG("initialising ticker\r\n");
+    HID_DEBUG("initialising ticker\n\r");
+
+    // Call the heartbeat_ISR function every 0.1 seconds 
+    Ticker heartbeat;
+    heartbeat.attach(heartbeat_ISR, 0.1f);
+
+    HID_DEBUG("enabling finger sensors\n\r");
+    enable_out3v3 = 1;
  
-    heartbeat.attach(waiting, 1);
- 
-    HID_DEBUG("initialising ble\r\n");
+    HID_DEBUG("initialising ble\n\r");
     ble.init();
  
     ble.gap().onDisconnection(onDisconnect);
@@ -100,24 +240,146 @@
  
     initializeSecurity(ble);
  
-    HID_DEBUG("adding hid service\r\n");
+    HID_DEBUG("adding hid service\n\r");
     KeyboardService kbdService(ble);
     kbdServicePtr = &kbdService;
  
-    HID_DEBUG("adding device info and battery service\r\n");
+    HID_DEBUG("adding device info and battery service\n\r");
     initializeHOGP(ble);
  
-    HID_DEBUG("setting up gap\r\n");
+    HID_DEBUG("setting up gap\n\r");
     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::KEYBOARD);
     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME,
                                            (uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME));
     ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME,
                                            (uint8_t *)SHORT_DEVICE_NAME, sizeof(SHORT_DEVICE_NAME));
  
-    HID_DEBUG("advertising\r\n");
+    HID_DEBUG("advertising\n\r");
     ble.gap().startAdvertising();
- 
+
+    // Turn LEDs to green connected.
+    connected_led = LED_ON;
+    waiting_led = LED_OFF;
+    status_led = LED_OFF;
+
+    // Run forever ...
+    int counter1 = 0; 
+    int counter2 = 0; 
     while (true) {
         ble.waitForEvent();
+        
+        // Signal that we should check the finger sensors soon.
+        if (check_fingers) {
+            check_fingers = false;
+
+            // Measure all the finger sensors and battery level.
+            float fing1 = finger1.read();
+            float fing2 = finger2.read();
+            float fing3 = finger3.read();
+            float fing4 = finger4.read();
+            float batt = battery.read();
+            
+            // If a finger was pressed and now has been released, send the keyUp event soon after.
+            if (press_keyup[1] && fing1 < FINGER1_RELEASE) {
+                press_keyup[1] = false;    // Clear the flag
+                kbdServicePtr->keyUpCode();
+                HID_DEBUG("sent key up for 1.\n\r");
+            }
+            if (press_keyup[2] && fing2 < FINGER2_RELEASE) {
+                press_keyup[2] = false;    // Clear the flag
+                send_keypress(FING2UP_KEYCODE, 2);
+                HID_DEBUG("sent final key down for 2.\n\r");
+                ble.waitForEvent(); // Add a slight delay
+                //wait(0.1);          // Add a slight delay
+                //ble.waitForEvent(); // Add a slight delay
+                kbdServicePtr->keyUpCode();
+                HID_DEBUG("sent final key up for 2.\n\r");
+                press_keyup[2] = false;    // Clear the flag
+            }
+            if (press_keyup[3] && fing3 < FINGER3_RELEASE) {
+                press_keyup[3] = false;    // Clear the flag
+                kbdServicePtr->keyUpCode();
+                HID_DEBUG("sent key up for 3.\n\r");
+            }
+            if (press_keyup[4] && fing4 < FINGER4_RELEASE) {
+                press_keyup[4] = false;    // Clear the flag
+                kbdServicePtr->keyUpCode();
+                HID_DEBUG("sent key up for 4.\n\r");
+            }
+            
+
+            // Very occasionally, show the connected LED
+            counter1++;
+            if (counter1 == 1) {
+                if (kbdServicePtr->isConnected()) {
+                    connected_led = LED_ON;
+                    waiting_led = LED_OFF;
+                }
+            }
+            if (counter1 == 2) {
+                //counter2 = 0;
+                connected_led = LED_OFF;
+                waiting_led = LED_OFF;
+            }
+            
+
+            // Occasionally show some debug info
+            if (counter1 > 15) {
+                //counter2++;
+                LOG("%.2f%. F1=%.2f, F2=%.2f, F3=%.2f, F4=%.2f\n\r", batt, fing1, fing2, fing3, fing4);
+
+                // Check for low battery
+                if (batt < BATTERY_NEEDS_CHARGE) {
+                    // Toggle blue LED
+                    waiting_led = LED_OFF;
+                    connected_led = LED_OFF;
+                    status_led = !status_led;
+                    if (!haveAskedForRecharge) {
+                        send_string("RECHARGE BATTERY!");
+                        haveAskedForRecharge = true;
+                    }
+                    LOG("RECHARGE BATTERY!\n\r");
+                }
+                //else {
+                //}
+                counter1 = 0;
+            }
+            
+            // Check if a finger was pressed
+            if (fing1 > FINGER1_PRESS && !press_keyup[1]) {
+                send_keypress(FING1_KEYCODE, 1);
+                HID_DEBUG("sent keypress %d for 1.\n\r", FING1_KEYCODE);
+                //counter2+=20;
+                //LOG("%d\n\r", counter2);
+            }
+            if (fing2 > FINGER2_PRESS && !press_keyup[2]) {
+                send_keypress(FING2DOWN_KEYCODE, 2);
+                HID_DEBUG("sent keypress %d for 2.\n\r", FING2DOWN_KEYCODE);
+                // Finger 2 is treated differently. We want to be able to hold down finger 2
+                // Without causing repeated keystrokes, so we will send an up press straight after the down press, 
+                // and a different key down and up when the fingure is released, so the client software can
+                // figure out the duration that it was held for.
+                ble.waitForEvent(); // Add a slight delay
+                //wait(0.1);          // Add a slight delay
+                //ble.waitForEvent(); // Add a slight delay
+                kbdServicePtr->keyUpCode();
+                HID_DEBUG("sent initial key up for 2.\n\r");
+            }
+            if (fing3 > FINGER3_PRESS && !press_keyup[3]) {
+                send_keypress(FING3_KEYCODE, 3);               
+                HID_DEBUG("sent keypress %d for 3.\n\r", FING3_KEYCODE);
+                //counter2++;
+                //send_keypress(counter2, 3);
+                //LOG("%d\n\r", counter2);
+            }
+            if (fing4 > FINGER4_PRESS && !press_keyup[4]) {
+                send_keypress(FING4_KEYCODE, 4);
+                HID_DEBUG("sent keypress %d for 4.\n\r", FING4_KEYCODE);
+                //counter2--;
+                //send_keypress(counter2, 4);
+                //LOG("%d\n\r", counter2);
+                
+            }
+        }
     }
-}
+}
\ No newline at end of file