HID-over-GATT implementation with the BLE API. This library allows to create devices such as mouse, keyboard or joystick, over Bluetooth Low Energy.

Dependents:   BLENano_HID BLE_HID_MouseScrollDemo BLE_HID_KeyboardStreamDemo Shervs_TestKeyboard_TinyBLE ... more

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers KeyboardService.h Source File

KeyboardService.h

00001 /* mbed Microcontroller Library
00002  * Copyright (c) 2015 ARM Limited
00003  *
00004  * Licensed under the Apache License, Version 2.0 (the "License");
00005  * you may not use this file except in compliance with the License.
00006  * You may obtain a copy of the License at
00007  *
00008  *     http://www.apache.org/licenses/LICENSE-2.0
00009  *
00010  * Unless required by applicable law or agreed to in writing, software
00011  * distributed under the License is distributed on an "AS IS" BASIS,
00012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00013  * See the License for the specific language governing permissions and
00014  * limitations under the License.
00015  */
00016 
00017 #include <errno.h>
00018 #include "mbed.h"
00019 #include "CircularBuffer.h"
00020 
00021 #include "HIDServiceBase.h"
00022 #include "Keyboard_types.h"
00023 
00024 /* TODO: make this easier to configure by application (e.g. as a template parameter for
00025  * KeyboardService) */
00026 #ifndef KEYBUFFER_SIZE
00027 #define KEYBUFFER_SIZE 256
00028 #endif
00029 
00030 /**
00031  * Report descriptor for a standard 101 keys keyboard, following the HID specification example:
00032  * - 8 bytes input report (1 byte for modifiers and 6 for keys)
00033  * - 1 byte output report (LEDs)
00034  */
00035 report_map_t KEYBOARD_REPORT_MAP = {
00036     USAGE_PAGE(1),      0x01,       // Generic Desktop Ctrls
00037     USAGE(1),           0x06,       // Keyboard
00038     COLLECTION(1),      0x01,       // Application
00039     USAGE_PAGE(1),      0x07,       //   Kbrd/Keypad
00040     USAGE_MINIMUM(1),   0xE0,
00041     USAGE_MAXIMUM(1),   0xE7,
00042     LOGICAL_MINIMUM(1), 0x00,
00043     LOGICAL_MAXIMUM(1), 0x01,
00044     REPORT_SIZE(1),     0x01,       //   1 byte (Modifier)
00045     REPORT_COUNT(1),    0x08,
00046     INPUT(1),           0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position
00047     REPORT_COUNT(1),    0x01,       //   1 byte (Reserved)
00048     REPORT_SIZE(1),     0x08,
00049     INPUT(1),           0x01,       //   Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
00050     REPORT_COUNT(1),    0x05,       //   5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
00051     REPORT_SIZE(1),     0x01,
00052     USAGE_PAGE(1),      0x08,       //   LEDs
00053     USAGE_MINIMUM(1),   0x01,       //   Num Lock
00054     USAGE_MAXIMUM(1),   0x05,       //   Kana
00055     OUTPUT(1),          0x02,       //   Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
00056     REPORT_COUNT(1),    0x01,       //   3 bits (Padding)
00057     REPORT_SIZE(1),     0x03,
00058     OUTPUT(1),          0x01,       //   Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
00059     REPORT_COUNT(1),    0x06,       //   6 bytes (Keys)
00060     REPORT_SIZE(1),     0x08,
00061     LOGICAL_MINIMUM(1), 0x00,
00062     LOGICAL_MAXIMUM(1), 0x65,       //   101 keys
00063     USAGE_PAGE(1),      0x07,       //   Kbrd/Keypad
00064     USAGE_MINIMUM(1),   0x00,
00065     USAGE_MAXIMUM(1),   0x65,
00066     INPUT(1),           0x00,       //   Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
00067     END_COLLECTION(0),
00068 };
00069 
00070 /// "keys pressed" report
00071 static uint8_t inputReportData[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
00072 /// "keys released" report
00073 static const uint8_t emptyInputReportData[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
00074 /// LEDs report
00075 static uint8_t outputReportData[] = { 0 };
00076 
00077 
00078 /**
00079  * @class KeyBuffer
00080  *
00081  * Buffer used to store keys to send.
00082  * Internally, it is a CircularBuffer, with the added capability of putting the last char back in,
00083  * when we're unable to send it (ie. when BLE stack is busy)
00084  */
00085 class KeyBuffer: public CircularBuffer<uint8_t, KEYBUFFER_SIZE>
00086 {
00087 public:
00088     KeyBuffer() :
00089         CircularBuffer(),
00090         dataIsPending (false),
00091         keyUpIsPending (false)
00092     {
00093     }
00094 
00095     /**
00096      * Mark a character as pending. When a freshly popped character cannot be sent, because the
00097      * underlying stack is busy, we set it as pending, and it will get popped in priority by @ref
00098      * getPending once reports can be sent again.
00099      *
00100      * @param data  The character to send in priority. The second keyUp report is implied.
00101      */
00102     void setPending(uint8_t data)
00103     {
00104         MBED_ASSERT(dataIsPending == false);
00105 
00106         dataIsPending = true;
00107         pendingData = data;
00108         keyUpIsPending = true;
00109     }
00110 
00111     /**
00112      * Get pending char. Either from the high priority buffer (set with setPending), or from the
00113      * circular buffer.
00114      *
00115      * @param   data Filled with the pending data, when present
00116      * @return  true if data was filled
00117      */
00118     bool getPending(uint8_t &data)
00119     {
00120         if (dataIsPending) {
00121             dataIsPending = false;
00122             data = pendingData;
00123             return true;
00124         }
00125 
00126         return pop(data);
00127     }
00128 
00129     bool isSomethingPending(void)
00130     {
00131         return dataIsPending || keyUpIsPending || !empty();
00132     }
00133 
00134     /**
00135      * Signal that a keyUp report is pending. This means that a character has successfully been
00136      * sent, but the subsequent keyUp report failed. This report is of highest priority than the
00137      * next character.
00138      */
00139     void setKeyUpPending(void)
00140     {
00141         keyUpIsPending = true;
00142     }
00143 
00144     /**
00145      * Signal that no high-priority report is pending anymore, we can go back to the normal queue.
00146      */
00147     void clearKeyUpPending(void)
00148     {
00149         keyUpIsPending = false;
00150     }
00151 
00152     bool isKeyUpPending(void)
00153     {
00154         return keyUpIsPending;
00155     }
00156 
00157 protected:
00158     bool dataIsPending;
00159     uint8_t pendingData;
00160     bool keyUpIsPending;
00161 };
00162 
00163 
00164 /**
00165  * @class KeyboardService
00166  * @brief HID-over-Gatt keyboard service
00167  *
00168  * Send keyboard reports over BLE. Users should rely on the high-level functions provided by the
00169  * Stream API. Because we can't send batches of HID reports, we store pending keys in a circular
00170  * buffer and rely on the report ticker to spread them over time.
00171  *
00172  * @code
00173  * BLE ble;
00174  * KeyboardService kbd(ble);
00175  *
00176  * void once_connected_and_paired_callback(void)
00177  * {
00178  *     // Sequentially send keys 'Shift'+'h', 'e', 'l', 'l', 'o', '!' and <enter>
00179  *     kbd.printf("Hello!\n");
00180  * }
00181  * @endcode
00182  */
00183 class KeyboardService : public HIDServiceBase, public Stream
00184 {
00185 public:
00186     KeyboardService(BLE &_ble) :
00187         HIDServiceBase(_ble,
00188                 KEYBOARD_REPORT_MAP, sizeof(KEYBOARD_REPORT_MAP),
00189                 inputReport         = emptyInputReportData,
00190                 outputReport        = outputReportData,
00191                 featureReport       = NULL,
00192                 inputReportLength   = sizeof(inputReportData),
00193                 outputReportLength  = sizeof(outputReportData),
00194                 featureReportLength = 0,
00195                 reportTickerDelay   = 24),
00196         failedReports(0)
00197     {
00198     }
00199 
00200     virtual void onConnection(const Gap::ConnectionCallbackParams_t *params)
00201     {
00202         HIDServiceBase::onConnection(params);
00203 
00204         /* Drain buffer, in case we've been disconnected while transmitting */
00205         if (!reportTickerIsActive && keyBuffer.isSomethingPending())
00206             startReportTicker();
00207     }
00208 
00209     virtual void onDisconnection(const Gap::DisconnectionCallbackParams_t *params)
00210     {
00211         stopReportTicker();
00212         HIDServiceBase::onDisconnection(params);
00213     }
00214 
00215     /**
00216      * Send raw report. Should only be called by sendCallback.
00217      */
00218     virtual ble_error_t send(const report_t report)
00219     {
00220         static unsigned int consecutiveFailures = 0;
00221         ble_error_t ret = HIDServiceBase::send(report);
00222 
00223         /*
00224          * Wait until a buffer is available (onDataSent)
00225          * TODO. This won't work, because BUSY error is not only returned when we're short of
00226          * notification buffers, but in other cases as well (e.g. when disconnected). We need to
00227          * find a reliable way of knowing when we actually need to wait for onDataSent to be called.
00228         if (ret == BLE_STACK_BUSY)
00229             stopReportTicker();
00230          */
00231         if (ret == BLE_STACK_BUSY)
00232             consecutiveFailures++;
00233         else
00234             consecutiveFailures = 0;
00235 
00236         if (consecutiveFailures > 20) {
00237             /*
00238              * We're not transmitting anything anymore. Might as well avoid overloading the
00239              * system in case it can magically fix itself. Ticker will start again on next _putc
00240              * call, or on next connection.
00241              */
00242             stopReportTicker();
00243             consecutiveFailures = 0;
00244         }
00245 
00246         return ret;
00247     }
00248 
00249     /**
00250      * Send an empty report, representing keyUp event
00251      */
00252     ble_error_t keyUpCode(void)
00253     {
00254         return send(emptyInputReportData);
00255     }
00256 
00257     /**
00258      * Send a character, defined by a modifier (CTRL, SHIFT, ALT) and the key
00259      *
00260      * @param key Character to send (as defined in USB HID Usage Tables)
00261      * @param modifier Optional modifiers (logical OR of enum MODIFIER_KEY)
00262      *
00263      * @returns BLE_ERROR_NONE on success, or an error code otherwise.
00264      */
00265     ble_error_t keyDownCode(uint8_t key, uint8_t modifier)
00266     {
00267         inputReportData[0] = modifier;
00268         inputReportData[2] = keymap[key].usage;
00269 
00270         return send(inputReportData);
00271     }
00272 
00273     /**
00274      * Push a key on the internal FIFO
00275      *
00276      * @param c ASCII character to send
00277      *
00278      * @returns 0 on success, or ENOMEM when the FIFO is full.
00279      */
00280     virtual int _putc(int c) {
00281         if (keyBuffer.full()) {
00282             return ENOMEM;
00283         }
00284 
00285         keyBuffer.push((unsigned char)c);
00286 
00287         if (!reportTickerIsActive)
00288             startReportTicker();
00289 
00290         return 0;
00291     }
00292 
00293     uint8_t lockStatus() {
00294         // TODO: implement numlock/capslock/scrolllock
00295         return 0;
00296     }
00297 
00298     /**
00299      * Pop a key from the internal FIFO, and attempt to send it over BLE
00300      *
00301      * keyUp reports should theoretically be sent after every keyDown, but we optimize the
00302      * throughput by only sending one when strictly necessary:
00303      * - when we need to repeat the same key
00304      * - when there is no more key to report
00305      *
00306      * In case of error, put the key event back in the buffer, and retry on next tick.
00307      */
00308     virtual void sendCallback(void) {
00309         ble_error_t ret;
00310         uint8_t c;
00311         static uint8_t previousKey = 0;
00312 
00313         if (keyBuffer.isSomethingPending() && !keyBuffer.isKeyUpPending()) {
00314             bool hasData = keyBuffer.getPending(c);
00315 
00316             /*
00317              * If something is pending and is not a keyUp, getPending *must* return something. The
00318              * following is only a sanity check.
00319              */
00320             MBED_ASSERT(hasData);
00321 
00322             if (!hasData)
00323                 return;
00324 
00325             if (previousKey == c) {
00326                 /*
00327                  * When the same key needs to be sent twice, we need to interleave a keyUp report,
00328                  * or else the OS won't be able to differentiate them.
00329                  * Push the key back into the buffer, and continue to keyUpCode.
00330                  */
00331                 keyBuffer.setPending(c);
00332             } else {
00333                 ret = keyDownCode(c, keymap[c].modifier);
00334                 if (ret) {
00335                     keyBuffer.setPending(c);
00336                     failedReports++;
00337                 } else {
00338                     previousKey = c;
00339                 }
00340 
00341                 return;
00342             }
00343         }
00344 
00345         ret = keyUpCode();
00346         if (ret) {
00347             keyBuffer.setKeyUpPending();
00348             failedReports++;
00349         } else {
00350             keyBuffer.clearKeyUpPending();
00351             previousKey = 0;
00352 
00353             /* Idle when there is nothing more to send */
00354             if (!keyBuffer.isSomethingPending())
00355                 stopReportTicker();
00356         }
00357     }
00358 
00359     /**
00360      * Restart report ticker if it was disabled, after too many consecutive failures.
00361      *
00362      * This is called by the BLE stack.
00363      *
00364      * @param count Number of reports (notifications) sent
00365      */
00366     virtual void onDataSent(unsigned count)
00367     {
00368         if (!reportTickerIsActive && keyBuffer.isSomethingPending())
00369             startReportTicker();
00370     }
00371 
00372     unsigned long failedReports;
00373 
00374 protected:
00375     virtual int _getc() {
00376         return 0;
00377     }
00378 
00379 protected:
00380     KeyBuffer keyBuffer;
00381 
00382     //GattCharacteristic boot_keyboard_input_report;
00383     //GattCharacteristic boot_keyboard_output_report;
00384 };
00385