Based on the example USB HID keyboard https://developer.mbed.org/users/jbru/code/BLE_HID_KeyboardStreamDemo/

Dependencies:   BLE_API mbed nRF51822

Fork of HID-kb by Microbug

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 512
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. It could also be started on next connection, but we can't register a callback
00241              * for that, currently.
00242              */
00243             stopReportTicker();
00244             consecutiveFailures = 0;
00245         }
00246 
00247         return ret;
00248     }
00249 
00250     /**
00251      * Send an empty report, representing keyUp event
00252      */
00253     ble_error_t keyUpCode(void)
00254     {
00255         return send(emptyInputReportData);
00256     }
00257 
00258     /**
00259      * Send a character, defined by a modifier (CTRL, SHIFT, ALT) and the key
00260      *
00261      * @param key Character to send (as defined in USB HID Usage Tables)
00262      * @param modifier Optional modifiers (logical OR of enum MODIFIER_KEY)
00263      *
00264      * @returns BLE_ERROR_NONE on success, or an error code otherwise.
00265      */
00266     ble_error_t keyDownCode(uint8_t key, uint8_t modifier)
00267     {
00268         inputReportData[0] = modifier;
00269         inputReportData[2] = keymap[key].usage;
00270 
00271         return send(inputReportData);
00272     }
00273 
00274     /**
00275      * Push a key on the internal FIFO
00276      *
00277      * @param c ASCII character to send
00278      *
00279      * @returns 0 on success, or ENOMEM when the FIFO is full.
00280      */
00281     virtual int _putc(int c) {
00282         if (keyBuffer.full()) {
00283             return ENOMEM;
00284         }
00285 
00286         keyBuffer.push((unsigned char)c);
00287 
00288         if (!reportTickerIsActive)
00289             startReportTicker();
00290 
00291         return 0;
00292     }
00293 
00294     uint8_t lockStatus() {
00295         // TODO: implement numlock/capslock/scrolllock
00296         return 0;
00297     }
00298 
00299     /**
00300      * Pop a key from the internal FIFO, and attempt to send it over BLE
00301      */
00302     virtual void sendCallback(void) {
00303         ble_error_t ret;
00304         uint8_t c;
00305 
00306         if (!keyBuffer.isSomethingPending()) {
00307             /* Stop until the next call to putc */
00308             stopReportTicker();
00309             return;
00310         }
00311 
00312         if (!keyBuffer.isKeyUpPending()) {
00313             bool hasData = keyBuffer.getPending(c);
00314 
00315             /*
00316              * If something is pending and is not a keyUp, getPending *must* return something. The
00317              * following is only a sanity check.
00318              */
00319             MBED_ASSERT(hasData);
00320 
00321             if (hasData) {
00322                 ret = keyDownCode(c, keymap[c].modifier);
00323                 if (ret) {
00324                     keyBuffer.setPending(c);
00325                     failedReports++;
00326                     return;
00327                 }
00328             }
00329         }
00330 
00331         ret = keyUpCode();
00332         if (ret) {
00333             keyBuffer.setKeyUpPending();
00334             failedReports++;
00335         } else {
00336             keyBuffer.clearKeyUpPending();
00337         }
00338     }
00339 
00340     /**
00341      * Restart report ticker if it was disabled, after too many consecutive failures.
00342      *
00343      * This is called by the BLE stack.
00344      *
00345      * @param count Number of reports (notifications) sent
00346      */
00347     virtual void onDataSent(unsigned count)
00348     {
00349         if (!reportTickerIsActive && keyBuffer.isSomethingPending())
00350             startReportTicker();
00351     }
00352 
00353     unsigned long failedReports;
00354 
00355 protected:
00356     virtual int _getc() {
00357         return 0;
00358     }
00359 
00360 protected:
00361     KeyBuffer keyBuffer;
00362 
00363     //GattCharacteristic boot_keyboard_input_report;
00364     //GattCharacteristic boot_keyboard_output_report;
00365 };
00366 
00367