back up of work during May 2019
Dependencies: microbit
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
Generated on Mon Jul 18 2022 14:25:12 by 1.7.2