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

main.cpp

Committer:
shervinemami
Date:
2018-08-26
Revision:
1:c6659c8882c9
Parent:
0:3435302551b3

File content as of revision 1:c6659c8882c9:

/**
 * 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
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
#include "mbed.h"
 
#include "ble/BLE.h"
#include "KeyboardService.h"
 
#include "examples_common.h"



#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[] = "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("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("conn\n\r");
    waiting_led = LED_OFF;
    connected_led = LED_ON;
}



void send_keypress(int keycode, int finger) {
    if (!kbdServicePtr)
        return;
 
    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("no conn yet\n\r");
    } else {
        int len = strlen(c);
        kbdServicePtr->printf(c);
        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;
}
 
 
int main()
{
#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\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;
 
    HID_DEBUG("initialising ble\n\r");
    ble.init();
 
    ble.gap().onDisconnection(onDisconnect);
    ble.gap().onConnection(onConnect);
 
    initializeSecurity(ble);
 
    HID_DEBUG("adding hid service\n\r");
    KeyboardService kbdService(ble);
    kbdServicePtr = &kbdService;
 
    HID_DEBUG("adding device info and battery service\n\r");
    initializeHOGP(ble);
 
    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\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);
                
            }
        }
    }
}