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

The development repository is currently hosted on github. It contains examples and documentation. This is a snapshot of the library. The documentation can be read on github, or on docs.mbed.com.

Committer:
Jean-Philippe Brucker
Date:
Thu Nov 19 15:00:39 2015 +0000
Revision:
3:4f8429a1905b
Parent:
2:3d9adb26bdc5
Version 0.3

Who changed what in which revision?

UserRevisionLine numberNew contents of line
Jean-Philippe Brucker 1:7a6c2e2c9371 1 /* mbed Microcontroller Library
Jean-Philippe Brucker 1:7a6c2e2c9371 2 * Copyright (c) 2015 ARM Limited
Jean-Philippe Brucker 1:7a6c2e2c9371 3 *
Jean-Philippe Brucker 1:7a6c2e2c9371 4 * Licensed under the Apache License, Version 2.0 (the "License");
Jean-Philippe Brucker 1:7a6c2e2c9371 5 * you may not use this file except in compliance with the License.
Jean-Philippe Brucker 1:7a6c2e2c9371 6 * You may obtain a copy of the License at
Jean-Philippe Brucker 1:7a6c2e2c9371 7 *
Jean-Philippe Brucker 1:7a6c2e2c9371 8 * http://www.apache.org/licenses/LICENSE-2.0
Jean-Philippe Brucker 1:7a6c2e2c9371 9 *
Jean-Philippe Brucker 1:7a6c2e2c9371 10 * Unless required by applicable law or agreed to in writing, software
Jean-Philippe Brucker 1:7a6c2e2c9371 11 * distributed under the License is distributed on an "AS IS" BASIS,
Jean-Philippe Brucker 1:7a6c2e2c9371 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Jean-Philippe Brucker 1:7a6c2e2c9371 13 * See the License for the specific language governing permissions and
Jean-Philippe Brucker 1:7a6c2e2c9371 14 * limitations under the License.
Jean-Philippe Brucker 1:7a6c2e2c9371 15 */
Jean-Philippe Brucker 1:7a6c2e2c9371 16
Jean-Philippe Brucker 0:cfd70fa91663 17 #include <errno.h>
Jean-Philippe Brucker 0:cfd70fa91663 18 #include "mbed.h"
Jean-Philippe Brucker 0:cfd70fa91663 19 #include "CircularBuffer.h"
Jean-Philippe Brucker 0:cfd70fa91663 20
Jean-Philippe Brucker 0:cfd70fa91663 21 #include "HIDServiceBase.h"
Jean-Philippe Brucker 0:cfd70fa91663 22 #include "Keyboard_types.h"
Jean-Philippe Brucker 0:cfd70fa91663 23
Jean-Philippe Brucker 0:cfd70fa91663 24 /* TODO: make this easier to configure by application (e.g. as a template parameter for
Jean-Philippe Brucker 0:cfd70fa91663 25 * KeyboardService) */
Jean-Philippe Brucker 0:cfd70fa91663 26 #ifndef KEYBUFFER_SIZE
Jean-Philippe Brucker 2:3d9adb26bdc5 27 #define KEYBUFFER_SIZE 256
Jean-Philippe Brucker 0:cfd70fa91663 28 #endif
Jean-Philippe Brucker 0:cfd70fa91663 29
Jean-Philippe Brucker 1:7a6c2e2c9371 30 /**
Jean-Philippe Brucker 1:7a6c2e2c9371 31 * Report descriptor for a standard 101 keys keyboard, following the HID specification example:
Jean-Philippe Brucker 1:7a6c2e2c9371 32 * - 8 bytes input report (1 byte for modifiers and 6 for keys)
Jean-Philippe Brucker 1:7a6c2e2c9371 33 * - 1 byte output report (LEDs)
Jean-Philippe Brucker 1:7a6c2e2c9371 34 */
Jean-Philippe Brucker 0:cfd70fa91663 35 report_map_t KEYBOARD_REPORT_MAP = {
Jean-Philippe Brucker 0:cfd70fa91663 36 USAGE_PAGE(1), 0x01, // Generic Desktop Ctrls
Jean-Philippe Brucker 0:cfd70fa91663 37 USAGE(1), 0x06, // Keyboard
Jean-Philippe Brucker 0:cfd70fa91663 38 COLLECTION(1), 0x01, // Application
Jean-Philippe Brucker 0:cfd70fa91663 39 USAGE_PAGE(1), 0x07, // Kbrd/Keypad
Jean-Philippe Brucker 0:cfd70fa91663 40 USAGE_MINIMUM(1), 0xE0,
Jean-Philippe Brucker 0:cfd70fa91663 41 USAGE_MAXIMUM(1), 0xE7,
Jean-Philippe Brucker 0:cfd70fa91663 42 LOGICAL_MINIMUM(1), 0x00,
Jean-Philippe Brucker 0:cfd70fa91663 43 LOGICAL_MAXIMUM(1), 0x01,
Jean-Philippe Brucker 0:cfd70fa91663 44 REPORT_SIZE(1), 0x01, // 1 byte (Modifier)
Jean-Philippe Brucker 0:cfd70fa91663 45 REPORT_COUNT(1), 0x08,
Jean-Philippe Brucker 0:cfd70fa91663 46 INPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position
Jean-Philippe Brucker 0:cfd70fa91663 47 REPORT_COUNT(1), 0x01, // 1 byte (Reserved)
Jean-Philippe Brucker 0:cfd70fa91663 48 REPORT_SIZE(1), 0x08,
Jean-Philippe Brucker 0:cfd70fa91663 49 INPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
Jean-Philippe Brucker 0:cfd70fa91663 50 REPORT_COUNT(1), 0x05, // 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
Jean-Philippe Brucker 0:cfd70fa91663 51 REPORT_SIZE(1), 0x01,
Jean-Philippe Brucker 0:cfd70fa91663 52 USAGE_PAGE(1), 0x08, // LEDs
Jean-Philippe Brucker 0:cfd70fa91663 53 USAGE_MINIMUM(1), 0x01, // Num Lock
Jean-Philippe Brucker 0:cfd70fa91663 54 USAGE_MAXIMUM(1), 0x05, // Kana
Jean-Philippe Brucker 0:cfd70fa91663 55 OUTPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
Jean-Philippe Brucker 0:cfd70fa91663 56 REPORT_COUNT(1), 0x01, // 3 bits (Padding)
Jean-Philippe Brucker 0:cfd70fa91663 57 REPORT_SIZE(1), 0x03,
Jean-Philippe Brucker 0:cfd70fa91663 58 OUTPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
Jean-Philippe Brucker 0:cfd70fa91663 59 REPORT_COUNT(1), 0x06, // 6 bytes (Keys)
Jean-Philippe Brucker 0:cfd70fa91663 60 REPORT_SIZE(1), 0x08,
Jean-Philippe Brucker 0:cfd70fa91663 61 LOGICAL_MINIMUM(1), 0x00,
Jean-Philippe Brucker 0:cfd70fa91663 62 LOGICAL_MAXIMUM(1), 0x65, // 101 keys
Jean-Philippe Brucker 0:cfd70fa91663 63 USAGE_PAGE(1), 0x07, // Kbrd/Keypad
Jean-Philippe Brucker 0:cfd70fa91663 64 USAGE_MINIMUM(1), 0x00,
Jean-Philippe Brucker 0:cfd70fa91663 65 USAGE_MAXIMUM(1), 0x65,
Jean-Philippe Brucker 0:cfd70fa91663 66 INPUT(1), 0x00, // Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
Jean-Philippe Brucker 0:cfd70fa91663 67 END_COLLECTION(0),
Jean-Philippe Brucker 0:cfd70fa91663 68 };
Jean-Philippe Brucker 0:cfd70fa91663 69
Jean-Philippe Brucker 1:7a6c2e2c9371 70 /// "keys pressed" report
Jean-Philippe Brucker 0:cfd70fa91663 71 static uint8_t inputReportData[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
Jean-Philippe Brucker 1:7a6c2e2c9371 72 /// "keys released" report
Jean-Philippe Brucker 0:cfd70fa91663 73 static const uint8_t emptyInputReportData[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
Jean-Philippe Brucker 1:7a6c2e2c9371 74 /// LEDs report
Jean-Philippe Brucker 0:cfd70fa91663 75 static uint8_t outputReportData[] = { 0 };
Jean-Philippe Brucker 0:cfd70fa91663 76
Jean-Philippe Brucker 1:7a6c2e2c9371 77
Jean-Philippe Brucker 0:cfd70fa91663 78 /**
Jean-Philippe Brucker 0:cfd70fa91663 79 * @class KeyBuffer
Jean-Philippe Brucker 0:cfd70fa91663 80 *
Jean-Philippe Brucker 0:cfd70fa91663 81 * Buffer used to store keys to send.
Jean-Philippe Brucker 0:cfd70fa91663 82 * Internally, it is a CircularBuffer, with the added capability of putting the last char back in,
Jean-Philippe Brucker 0:cfd70fa91663 83 * when we're unable to send it (ie. when BLE stack is busy)
Jean-Philippe Brucker 0:cfd70fa91663 84 */
Jean-Philippe Brucker 0:cfd70fa91663 85 class KeyBuffer: public CircularBuffer<uint8_t, KEYBUFFER_SIZE>
Jean-Philippe Brucker 0:cfd70fa91663 86 {
Jean-Philippe Brucker 0:cfd70fa91663 87 public:
Jean-Philippe Brucker 0:cfd70fa91663 88 KeyBuffer() :
Jean-Philippe Brucker 0:cfd70fa91663 89 CircularBuffer(),
Jean-Philippe Brucker 0:cfd70fa91663 90 dataIsPending (false),
Jean-Philippe Brucker 0:cfd70fa91663 91 keyUpIsPending (false)
Jean-Philippe Brucker 0:cfd70fa91663 92 {
Jean-Philippe Brucker 0:cfd70fa91663 93 }
Jean-Philippe Brucker 0:cfd70fa91663 94
Jean-Philippe Brucker 0:cfd70fa91663 95 /**
Jean-Philippe Brucker 0:cfd70fa91663 96 * Mark a character as pending. When a freshly popped character cannot be sent, because the
Jean-Philippe Brucker 0:cfd70fa91663 97 * underlying stack is busy, we set it as pending, and it will get popped in priority by @ref
Jean-Philippe Brucker 0:cfd70fa91663 98 * getPending once reports can be sent again.
Jean-Philippe Brucker 0:cfd70fa91663 99 *
Jean-Philippe Brucker 0:cfd70fa91663 100 * @param data The character to send in priority. The second keyUp report is implied.
Jean-Philippe Brucker 0:cfd70fa91663 101 */
Jean-Philippe Brucker 0:cfd70fa91663 102 void setPending(uint8_t data)
Jean-Philippe Brucker 0:cfd70fa91663 103 {
Jean-Philippe Brucker 0:cfd70fa91663 104 MBED_ASSERT(dataIsPending == false);
Jean-Philippe Brucker 0:cfd70fa91663 105
Jean-Philippe Brucker 0:cfd70fa91663 106 dataIsPending = true;
Jean-Philippe Brucker 0:cfd70fa91663 107 pendingData = data;
Jean-Philippe Brucker 0:cfd70fa91663 108 keyUpIsPending = true;
Jean-Philippe Brucker 0:cfd70fa91663 109 }
Jean-Philippe Brucker 0:cfd70fa91663 110
Jean-Philippe Brucker 0:cfd70fa91663 111 /**
Jean-Philippe Brucker 0:cfd70fa91663 112 * Get pending char. Either from the high priority buffer (set with setPending), or from the
Jean-Philippe Brucker 0:cfd70fa91663 113 * circular buffer.
Jean-Philippe Brucker 0:cfd70fa91663 114 *
Jean-Philippe Brucker 0:cfd70fa91663 115 * @param data Filled with the pending data, when present
Jean-Philippe Brucker 0:cfd70fa91663 116 * @return true if data was filled
Jean-Philippe Brucker 0:cfd70fa91663 117 */
Jean-Philippe Brucker 0:cfd70fa91663 118 bool getPending(uint8_t &data)
Jean-Philippe Brucker 0:cfd70fa91663 119 {
Jean-Philippe Brucker 0:cfd70fa91663 120 if (dataIsPending) {
Jean-Philippe Brucker 0:cfd70fa91663 121 dataIsPending = false;
Jean-Philippe Brucker 0:cfd70fa91663 122 data = pendingData;
Jean-Philippe Brucker 0:cfd70fa91663 123 return true;
Jean-Philippe Brucker 0:cfd70fa91663 124 }
Jean-Philippe Brucker 0:cfd70fa91663 125
Jean-Philippe Brucker 0:cfd70fa91663 126 return pop(data);
Jean-Philippe Brucker 0:cfd70fa91663 127 }
Jean-Philippe Brucker 0:cfd70fa91663 128
Jean-Philippe Brucker 0:cfd70fa91663 129 bool isSomethingPending(void)
Jean-Philippe Brucker 0:cfd70fa91663 130 {
Jean-Philippe Brucker 0:cfd70fa91663 131 return dataIsPending || keyUpIsPending || !empty();
Jean-Philippe Brucker 0:cfd70fa91663 132 }
Jean-Philippe Brucker 0:cfd70fa91663 133
Jean-Philippe Brucker 0:cfd70fa91663 134 /**
Jean-Philippe Brucker 0:cfd70fa91663 135 * Signal that a keyUp report is pending. This means that a character has successfully been
Jean-Philippe Brucker 0:cfd70fa91663 136 * sent, but the subsequent keyUp report failed. This report is of highest priority than the
Jean-Philippe Brucker 0:cfd70fa91663 137 * next character.
Jean-Philippe Brucker 0:cfd70fa91663 138 */
Jean-Philippe Brucker 0:cfd70fa91663 139 void setKeyUpPending(void)
Jean-Philippe Brucker 0:cfd70fa91663 140 {
Jean-Philippe Brucker 0:cfd70fa91663 141 keyUpIsPending = true;
Jean-Philippe Brucker 0:cfd70fa91663 142 }
Jean-Philippe Brucker 0:cfd70fa91663 143
Jean-Philippe Brucker 0:cfd70fa91663 144 /**
Jean-Philippe Brucker 0:cfd70fa91663 145 * Signal that no high-priority report is pending anymore, we can go back to the normal queue.
Jean-Philippe Brucker 0:cfd70fa91663 146 */
Jean-Philippe Brucker 0:cfd70fa91663 147 void clearKeyUpPending(void)
Jean-Philippe Brucker 0:cfd70fa91663 148 {
Jean-Philippe Brucker 0:cfd70fa91663 149 keyUpIsPending = false;
Jean-Philippe Brucker 0:cfd70fa91663 150 }
Jean-Philippe Brucker 0:cfd70fa91663 151
Jean-Philippe Brucker 0:cfd70fa91663 152 bool isKeyUpPending(void)
Jean-Philippe Brucker 0:cfd70fa91663 153 {
Jean-Philippe Brucker 0:cfd70fa91663 154 return keyUpIsPending;
Jean-Philippe Brucker 0:cfd70fa91663 155 }
Jean-Philippe Brucker 0:cfd70fa91663 156
Jean-Philippe Brucker 0:cfd70fa91663 157 protected:
Jean-Philippe Brucker 0:cfd70fa91663 158 bool dataIsPending;
Jean-Philippe Brucker 0:cfd70fa91663 159 uint8_t pendingData;
Jean-Philippe Brucker 0:cfd70fa91663 160 bool keyUpIsPending;
Jean-Philippe Brucker 0:cfd70fa91663 161 };
Jean-Philippe Brucker 0:cfd70fa91663 162
Jean-Philippe Brucker 1:7a6c2e2c9371 163
Jean-Philippe Brucker 1:7a6c2e2c9371 164 /**
Jean-Philippe Brucker 1:7a6c2e2c9371 165 * @class KeyboardService
Jean-Philippe Brucker 1:7a6c2e2c9371 166 * @brief HID-over-Gatt keyboard service
Jean-Philippe Brucker 1:7a6c2e2c9371 167 *
Jean-Philippe Brucker 1:7a6c2e2c9371 168 * Send keyboard reports over BLE. Users should rely on the high-level functions provided by the
Jean-Philippe Brucker 1:7a6c2e2c9371 169 * Stream API. Because we can't send batches of HID reports, we store pending keys in a circular
Jean-Philippe Brucker 1:7a6c2e2c9371 170 * buffer and rely on the report ticker to spread them over time.
Jean-Philippe Brucker 1:7a6c2e2c9371 171 *
Jean-Philippe Brucker 1:7a6c2e2c9371 172 * @code
Jean-Philippe Brucker 1:7a6c2e2c9371 173 * BLE ble;
Jean-Philippe Brucker 1:7a6c2e2c9371 174 * KeyboardService kbd(ble);
Jean-Philippe Brucker 1:7a6c2e2c9371 175 *
Jean-Philippe Brucker 1:7a6c2e2c9371 176 * void once_connected_and_paired_callback(void)
Jean-Philippe Brucker 1:7a6c2e2c9371 177 * {
Jean-Philippe Brucker 1:7a6c2e2c9371 178 * // Sequentially send keys 'Shift'+'h', 'e', 'l', 'l', 'o', '!' and <enter>
Jean-Philippe Brucker 1:7a6c2e2c9371 179 * kbd.printf("Hello!\n");
Jean-Philippe Brucker 1:7a6c2e2c9371 180 * }
Jean-Philippe Brucker 1:7a6c2e2c9371 181 * @endcode
Jean-Philippe Brucker 1:7a6c2e2c9371 182 */
Jean-Philippe Brucker 0:cfd70fa91663 183 class KeyboardService : public HIDServiceBase, public Stream
Jean-Philippe Brucker 0:cfd70fa91663 184 {
Jean-Philippe Brucker 0:cfd70fa91663 185 public:
Jean-Philippe Brucker 0:cfd70fa91663 186 KeyboardService(BLE &_ble) :
Jean-Philippe Brucker 0:cfd70fa91663 187 HIDServiceBase(_ble,
Jean-Philippe Brucker 0:cfd70fa91663 188 KEYBOARD_REPORT_MAP, sizeof(KEYBOARD_REPORT_MAP),
Jean-Philippe Brucker 0:cfd70fa91663 189 inputReport = emptyInputReportData,
Jean-Philippe Brucker 0:cfd70fa91663 190 outputReport = outputReportData,
Jean-Philippe Brucker 0:cfd70fa91663 191 featureReport = NULL,
Jean-Philippe Brucker 0:cfd70fa91663 192 inputReportLength = sizeof(inputReportData),
Jean-Philippe Brucker 0:cfd70fa91663 193 outputReportLength = sizeof(outputReportData),
Jean-Philippe Brucker 0:cfd70fa91663 194 featureReportLength = 0,
Jean-Philippe Brucker 0:cfd70fa91663 195 reportTickerDelay = 24),
Jean-Philippe Brucker 0:cfd70fa91663 196 failedReports(0)
Jean-Philippe Brucker 0:cfd70fa91663 197 {
Jean-Philippe Brucker 0:cfd70fa91663 198 }
Jean-Philippe Brucker 0:cfd70fa91663 199
Jean-Philippe Brucker 1:7a6c2e2c9371 200 virtual void onConnection(const Gap::ConnectionCallbackParams_t *params)
Jean-Philippe Brucker 1:7a6c2e2c9371 201 {
Jean-Philippe Brucker 1:7a6c2e2c9371 202 HIDServiceBase::onConnection(params);
Jean-Philippe Brucker 1:7a6c2e2c9371 203
Jean-Philippe Brucker 1:7a6c2e2c9371 204 /* Drain buffer, in case we've been disconnected while transmitting */
Jean-Philippe Brucker 1:7a6c2e2c9371 205 if (!reportTickerIsActive && keyBuffer.isSomethingPending())
Jean-Philippe Brucker 1:7a6c2e2c9371 206 startReportTicker();
Jean-Philippe Brucker 1:7a6c2e2c9371 207 }
Jean-Philippe Brucker 1:7a6c2e2c9371 208
Jean-Philippe Brucker 1:7a6c2e2c9371 209 virtual void onDisconnection(const Gap::DisconnectionCallbackParams_t *params)
Jean-Philippe Brucker 1:7a6c2e2c9371 210 {
Jean-Philippe Brucker 1:7a6c2e2c9371 211 stopReportTicker();
Jean-Philippe Brucker 1:7a6c2e2c9371 212 HIDServiceBase::onDisconnection(params);
Jean-Philippe Brucker 1:7a6c2e2c9371 213 }
Jean-Philippe Brucker 1:7a6c2e2c9371 214
Jean-Philippe Brucker 1:7a6c2e2c9371 215 /**
Jean-Philippe Brucker 1:7a6c2e2c9371 216 * Send raw report. Should only be called by sendCallback.
Jean-Philippe Brucker 1:7a6c2e2c9371 217 */
Jean-Philippe Brucker 0:cfd70fa91663 218 virtual ble_error_t send(const report_t report)
Jean-Philippe Brucker 0:cfd70fa91663 219 {
Jean-Philippe Brucker 0:cfd70fa91663 220 static unsigned int consecutiveFailures = 0;
Jean-Philippe Brucker 0:cfd70fa91663 221 ble_error_t ret = HIDServiceBase::send(report);
Jean-Philippe Brucker 0:cfd70fa91663 222
Jean-Philippe Brucker 0:cfd70fa91663 223 /*
Jean-Philippe Brucker 0:cfd70fa91663 224 * Wait until a buffer is available (onDataSent)
Jean-Philippe Brucker 0:cfd70fa91663 225 * TODO. This won't work, because BUSY error is not only returned when we're short of
Jean-Philippe Brucker 0:cfd70fa91663 226 * notification buffers, but in other cases as well (e.g. when disconnected). We need to
Jean-Philippe Brucker 0:cfd70fa91663 227 * find a reliable way of knowing when we actually need to wait for onDataSent to be called.
Jean-Philippe Brucker 0:cfd70fa91663 228 if (ret == BLE_STACK_BUSY)
Jean-Philippe Brucker 0:cfd70fa91663 229 stopReportTicker();
Jean-Philippe Brucker 0:cfd70fa91663 230 */
Jean-Philippe Brucker 0:cfd70fa91663 231 if (ret == BLE_STACK_BUSY)
Jean-Philippe Brucker 0:cfd70fa91663 232 consecutiveFailures++;
Jean-Philippe Brucker 0:cfd70fa91663 233 else
Jean-Philippe Brucker 0:cfd70fa91663 234 consecutiveFailures = 0;
Jean-Philippe Brucker 0:cfd70fa91663 235
Jean-Philippe Brucker 0:cfd70fa91663 236 if (consecutiveFailures > 20) {
Jean-Philippe Brucker 0:cfd70fa91663 237 /*
Jean-Philippe Brucker 1:7a6c2e2c9371 238 * We're not transmitting anything anymore. Might as well avoid overloading the
Jean-Philippe Brucker 0:cfd70fa91663 239 * system in case it can magically fix itself. Ticker will start again on next _putc
Jean-Philippe Brucker 2:3d9adb26bdc5 240 * call, or on next connection.
Jean-Philippe Brucker 0:cfd70fa91663 241 */
Jean-Philippe Brucker 0:cfd70fa91663 242 stopReportTicker();
Jean-Philippe Brucker 0:cfd70fa91663 243 consecutiveFailures = 0;
Jean-Philippe Brucker 0:cfd70fa91663 244 }
Jean-Philippe Brucker 0:cfd70fa91663 245
Jean-Philippe Brucker 0:cfd70fa91663 246 return ret;
Jean-Philippe Brucker 0:cfd70fa91663 247 }
Jean-Philippe Brucker 0:cfd70fa91663 248
Jean-Philippe Brucker 0:cfd70fa91663 249 /**
Jean-Philippe Brucker 0:cfd70fa91663 250 * Send an empty report, representing keyUp event
Jean-Philippe Brucker 0:cfd70fa91663 251 */
Jean-Philippe Brucker 0:cfd70fa91663 252 ble_error_t keyUpCode(void)
Jean-Philippe Brucker 0:cfd70fa91663 253 {
Jean-Philippe Brucker 0:cfd70fa91663 254 return send(emptyInputReportData);
Jean-Philippe Brucker 0:cfd70fa91663 255 }
Jean-Philippe Brucker 0:cfd70fa91663 256
Jean-Philippe Brucker 1:7a6c2e2c9371 257 /**
Jean-Philippe Brucker 1:7a6c2e2c9371 258 * Send a character, defined by a modifier (CTRL, SHIFT, ALT) and the key
Jean-Philippe Brucker 1:7a6c2e2c9371 259 *
Jean-Philippe Brucker 1:7a6c2e2c9371 260 * @param key Character to send (as defined in USB HID Usage Tables)
Jean-Philippe Brucker 1:7a6c2e2c9371 261 * @param modifier Optional modifiers (logical OR of enum MODIFIER_KEY)
Jean-Philippe Brucker 1:7a6c2e2c9371 262 *
Jean-Philippe Brucker 1:7a6c2e2c9371 263 * @returns BLE_ERROR_NONE on success, or an error code otherwise.
Jean-Philippe Brucker 1:7a6c2e2c9371 264 */
Jean-Philippe Brucker 0:cfd70fa91663 265 ble_error_t keyDownCode(uint8_t key, uint8_t modifier)
Jean-Philippe Brucker 0:cfd70fa91663 266 {
Jean-Philippe Brucker 0:cfd70fa91663 267 inputReportData[0] = modifier;
Jean-Philippe Brucker 0:cfd70fa91663 268 inputReportData[2] = keymap[key].usage;
Jean-Philippe Brucker 0:cfd70fa91663 269
Jean-Philippe Brucker 0:cfd70fa91663 270 return send(inputReportData);
Jean-Philippe Brucker 0:cfd70fa91663 271 }
Jean-Philippe Brucker 0:cfd70fa91663 272
Jean-Philippe Brucker 1:7a6c2e2c9371 273 /**
Jean-Philippe Brucker 1:7a6c2e2c9371 274 * Push a key on the internal FIFO
Jean-Philippe Brucker 1:7a6c2e2c9371 275 *
Jean-Philippe Brucker 1:7a6c2e2c9371 276 * @param c ASCII character to send
Jean-Philippe Brucker 1:7a6c2e2c9371 277 *
Jean-Philippe Brucker 1:7a6c2e2c9371 278 * @returns 0 on success, or ENOMEM when the FIFO is full.
Jean-Philippe Brucker 1:7a6c2e2c9371 279 */
Jean-Philippe Brucker 0:cfd70fa91663 280 virtual int _putc(int c) {
Jean-Philippe Brucker 0:cfd70fa91663 281 if (keyBuffer.full()) {
Jean-Philippe Brucker 0:cfd70fa91663 282 return ENOMEM;
Jean-Philippe Brucker 0:cfd70fa91663 283 }
Jean-Philippe Brucker 0:cfd70fa91663 284
Jean-Philippe Brucker 0:cfd70fa91663 285 keyBuffer.push((unsigned char)c);
Jean-Philippe Brucker 0:cfd70fa91663 286
Jean-Philippe Brucker 0:cfd70fa91663 287 if (!reportTickerIsActive)
Jean-Philippe Brucker 0:cfd70fa91663 288 startReportTicker();
Jean-Philippe Brucker 0:cfd70fa91663 289
Jean-Philippe Brucker 0:cfd70fa91663 290 return 0;
Jean-Philippe Brucker 0:cfd70fa91663 291 }
Jean-Philippe Brucker 0:cfd70fa91663 292
Jean-Philippe Brucker 0:cfd70fa91663 293 uint8_t lockStatus() {
Jean-Philippe Brucker 0:cfd70fa91663 294 // TODO: implement numlock/capslock/scrolllock
Jean-Philippe Brucker 0:cfd70fa91663 295 return 0;
Jean-Philippe Brucker 0:cfd70fa91663 296 }
Jean-Philippe Brucker 0:cfd70fa91663 297
Jean-Philippe Brucker 1:7a6c2e2c9371 298 /**
Jean-Philippe Brucker 1:7a6c2e2c9371 299 * Pop a key from the internal FIFO, and attempt to send it over BLE
Jean-Philippe Brucker 2:3d9adb26bdc5 300 *
Jean-Philippe Brucker 2:3d9adb26bdc5 301 * keyUp reports should theoretically be sent after every keyDown, but we optimize the
Jean-Philippe Brucker 2:3d9adb26bdc5 302 * throughput by only sending one when strictly necessary:
Jean-Philippe Brucker 2:3d9adb26bdc5 303 * - when we need to repeat the same key
Jean-Philippe Brucker 2:3d9adb26bdc5 304 * - when there is no more key to report
Jean-Philippe Brucker 2:3d9adb26bdc5 305 *
Jean-Philippe Brucker 2:3d9adb26bdc5 306 * In case of error, put the key event back in the buffer, and retry on next tick.
Jean-Philippe Brucker 1:7a6c2e2c9371 307 */
Jean-Philippe Brucker 0:cfd70fa91663 308 virtual void sendCallback(void) {
Jean-Philippe Brucker 0:cfd70fa91663 309 ble_error_t ret;
Jean-Philippe Brucker 0:cfd70fa91663 310 uint8_t c;
Jean-Philippe Brucker 2:3d9adb26bdc5 311 static uint8_t previousKey = 0;
Jean-Philippe Brucker 0:cfd70fa91663 312
Jean-Philippe Brucker 2:3d9adb26bdc5 313 if (keyBuffer.isSomethingPending() && !keyBuffer.isKeyUpPending()) {
Jean-Philippe Brucker 0:cfd70fa91663 314 bool hasData = keyBuffer.getPending(c);
Jean-Philippe Brucker 0:cfd70fa91663 315
Jean-Philippe Brucker 0:cfd70fa91663 316 /*
Jean-Philippe Brucker 0:cfd70fa91663 317 * If something is pending and is not a keyUp, getPending *must* return something. The
Jean-Philippe Brucker 0:cfd70fa91663 318 * following is only a sanity check.
Jean-Philippe Brucker 0:cfd70fa91663 319 */
Jean-Philippe Brucker 0:cfd70fa91663 320 MBED_ASSERT(hasData);
Jean-Philippe Brucker 0:cfd70fa91663 321
Jean-Philippe Brucker 2:3d9adb26bdc5 322 if (!hasData)
Jean-Philippe Brucker 2:3d9adb26bdc5 323 return;
Jean-Philippe Brucker 2:3d9adb26bdc5 324
Jean-Philippe Brucker 2:3d9adb26bdc5 325 if (previousKey == c) {
Jean-Philippe Brucker 2:3d9adb26bdc5 326 /*
Jean-Philippe Brucker 2:3d9adb26bdc5 327 * When the same key needs to be sent twice, we need to interleave a keyUp report,
Jean-Philippe Brucker 2:3d9adb26bdc5 328 * or else the OS won't be able to differentiate them.
Jean-Philippe Brucker 2:3d9adb26bdc5 329 * Push the key back into the buffer, and continue to keyUpCode.
Jean-Philippe Brucker 2:3d9adb26bdc5 330 */
Jean-Philippe Brucker 2:3d9adb26bdc5 331 keyBuffer.setPending(c);
Jean-Philippe Brucker 2:3d9adb26bdc5 332 } else {
Jean-Philippe Brucker 0:cfd70fa91663 333 ret = keyDownCode(c, keymap[c].modifier);
Jean-Philippe Brucker 0:cfd70fa91663 334 if (ret) {
Jean-Philippe Brucker 0:cfd70fa91663 335 keyBuffer.setPending(c);
Jean-Philippe Brucker 0:cfd70fa91663 336 failedReports++;
Jean-Philippe Brucker 2:3d9adb26bdc5 337 } else {
Jean-Philippe Brucker 2:3d9adb26bdc5 338 previousKey = c;
Jean-Philippe Brucker 0:cfd70fa91663 339 }
Jean-Philippe Brucker 2:3d9adb26bdc5 340
Jean-Philippe Brucker 2:3d9adb26bdc5 341 return;
Jean-Philippe Brucker 0:cfd70fa91663 342 }
Jean-Philippe Brucker 0:cfd70fa91663 343 }
Jean-Philippe Brucker 0:cfd70fa91663 344
Jean-Philippe Brucker 0:cfd70fa91663 345 ret = keyUpCode();
Jean-Philippe Brucker 0:cfd70fa91663 346 if (ret) {
Jean-Philippe Brucker 0:cfd70fa91663 347 keyBuffer.setKeyUpPending();
Jean-Philippe Brucker 0:cfd70fa91663 348 failedReports++;
Jean-Philippe Brucker 0:cfd70fa91663 349 } else {
Jean-Philippe Brucker 0:cfd70fa91663 350 keyBuffer.clearKeyUpPending();
Jean-Philippe Brucker 2:3d9adb26bdc5 351 previousKey = 0;
Jean-Philippe Brucker 2:3d9adb26bdc5 352
Jean-Philippe Brucker 2:3d9adb26bdc5 353 /* Idle when there is nothing more to send */
Jean-Philippe Brucker 2:3d9adb26bdc5 354 if (!keyBuffer.isSomethingPending())
Jean-Philippe Brucker 2:3d9adb26bdc5 355 stopReportTicker();
Jean-Philippe Brucker 0:cfd70fa91663 356 }
Jean-Philippe Brucker 0:cfd70fa91663 357 }
Jean-Philippe Brucker 0:cfd70fa91663 358
Jean-Philippe Brucker 1:7a6c2e2c9371 359 /**
Jean-Philippe Brucker 1:7a6c2e2c9371 360 * Restart report ticker if it was disabled, after too many consecutive failures.
Jean-Philippe Brucker 1:7a6c2e2c9371 361 *
Jean-Philippe Brucker 1:7a6c2e2c9371 362 * This is called by the BLE stack.
Jean-Philippe Brucker 1:7a6c2e2c9371 363 *
Jean-Philippe Brucker 1:7a6c2e2c9371 364 * @param count Number of reports (notifications) sent
Jean-Philippe Brucker 1:7a6c2e2c9371 365 */
Jean-Philippe Brucker 0:cfd70fa91663 366 virtual void onDataSent(unsigned count)
Jean-Philippe Brucker 0:cfd70fa91663 367 {
Jean-Philippe Brucker 0:cfd70fa91663 368 if (!reportTickerIsActive && keyBuffer.isSomethingPending())
Jean-Philippe Brucker 0:cfd70fa91663 369 startReportTicker();
Jean-Philippe Brucker 0:cfd70fa91663 370 }
Jean-Philippe Brucker 0:cfd70fa91663 371
Jean-Philippe Brucker 0:cfd70fa91663 372 unsigned long failedReports;
Jean-Philippe Brucker 0:cfd70fa91663 373
Jean-Philippe Brucker 0:cfd70fa91663 374 protected:
Jean-Philippe Brucker 0:cfd70fa91663 375 virtual int _getc() {
Jean-Philippe Brucker 0:cfd70fa91663 376 return 0;
Jean-Philippe Brucker 0:cfd70fa91663 377 }
Jean-Philippe Brucker 0:cfd70fa91663 378
Jean-Philippe Brucker 0:cfd70fa91663 379 protected:
Jean-Philippe Brucker 0:cfd70fa91663 380 KeyBuffer keyBuffer;
Jean-Philippe Brucker 0:cfd70fa91663 381
Jean-Philippe Brucker 0:cfd70fa91663 382 //GattCharacteristic boot_keyboard_input_report;
Jean-Philippe Brucker 0:cfd70fa91663 383 //GattCharacteristic boot_keyboard_output_report;
Jean-Philippe Brucker 0:cfd70fa91663 384 };
Jean-Philippe Brucker 0:cfd70fa91663 385