BLE switch interface with GROVE joystic for micro:bit http://mahoro-ba.net/e2073.html
BLE_HID/KeyboardService.h
- Committer:
- masakjm
- Date:
- 2019-04-09
- Revision:
- 21:961da341b755
- Parent:
- 0:28fb3e9ef81a
File content as of revision 21:961da341b755:
/* 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 <errno.h> #include "mbed.h" #include "CircularBuffer.h" #include "HIDServiceBase.h" #include "Keyboard_types.h" /* TODO: make this easier to configure by application (e.g. as a template parameter for * KeyboardService) */ #ifndef KEYBUFFER_SIZE #define KEYBUFFER_SIZE 512 #endif /** * Report descriptor for a standard 101 keys keyboard, following the HID specification example: * - 8 bytes input report (1 byte for modifiers and 6 for keys) * - 1 byte output report (LEDs) */ report_map_t KEYBOARD_REPORT_MAP = { USAGE_PAGE(1), 0x01, // Generic Desktop Ctrls USAGE(1), 0x06, // Keyboard COLLECTION(1), 0x01, // Application USAGE_PAGE(1), 0x07, // Kbrd/Keypad USAGE_MINIMUM(1), 0xE0, USAGE_MAXIMUM(1), 0xE7, LOGICAL_MINIMUM(1), 0x00, LOGICAL_MAXIMUM(1), 0x01, REPORT_SIZE(1), 0x01, // 1 byte (Modifier) REPORT_COUNT(1), 0x08, INPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position REPORT_COUNT(1), 0x01, // 1 byte (Reserved) REPORT_SIZE(1), 0x08, INPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position REPORT_COUNT(1), 0x05, // 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana) REPORT_SIZE(1), 0x01, USAGE_PAGE(1), 0x08, // LEDs USAGE_MINIMUM(1), 0x01, // Num Lock USAGE_MAXIMUM(1), 0x05, // Kana OUTPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile REPORT_COUNT(1), 0x01, // 3 bits (Padding) REPORT_SIZE(1), 0x03, OUTPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile REPORT_COUNT(1), 0x06, // 6 bytes (Keys) REPORT_SIZE(1), 0x08, LOGICAL_MINIMUM(1), 0x00, LOGICAL_MAXIMUM(1), 0x65, // 101 keys USAGE_PAGE(1), 0x07, // Kbrd/Keypad USAGE_MINIMUM(1), 0x00, USAGE_MAXIMUM(1), 0x65, INPUT(1), 0x00, // Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position END_COLLECTION(0), }; /// "keys pressed" report static uint8_t inputReportData[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; /// "keys released" report static const uint8_t emptyInputReportData[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; /// LEDs report static uint8_t outputReportData[] = { 0 }; /** * @class KeyBuffer * * Buffer used to store keys to send. * Internally, it is a CircularBuffer, with the added capability of putting the last char back in, * when we're unable to send it (ie. when BLE stack is busy) */ class KeyBuffer: public CircularBuffer<uint8_t, KEYBUFFER_SIZE> { public: KeyBuffer() : CircularBuffer(), dataIsPending (false), keyUpIsPending (false) { } /** * Mark a character as pending. When a freshly popped character cannot be sent, because the * underlying stack is busy, we set it as pending, and it will get popped in priority by @ref * getPending once reports can be sent again. * * @param data The character to send in priority. The second keyUp report is implied. */ void setPending(uint8_t data) { MBED_ASSERT(dataIsPending == false); dataIsPending = true; pendingData = data; keyUpIsPending = true; } /** * Get pending char. Either from the high priority buffer (set with setPending), or from the * circular buffer. * * @param data Filled with the pending data, when present * @return true if data was filled */ bool getPending(uint8_t &data) { if (dataIsPending) { dataIsPending = false; data = pendingData; return true; } return pop(data); } bool isSomethingPending(void) { return dataIsPending || keyUpIsPending || !empty(); } /** * Signal that a keyUp report is pending. This means that a character has successfully been * sent, but the subsequent keyUp report failed. This report is of highest priority than the * next character. */ void setKeyUpPending(void) { keyUpIsPending = true; } /** * Signal that no high-priority report is pending anymore, we can go back to the normal queue. */ void clearKeyUpPending(void) { keyUpIsPending = false; } bool isKeyUpPending(void) { return keyUpIsPending; } protected: bool dataIsPending; uint8_t pendingData; bool keyUpIsPending; }; /** * @class KeyboardService * @brief HID-over-Gatt keyboard service * * Send keyboard reports over BLE. Users should rely on the high-level functions provided by the * Stream API. Because we can't send batches of HID reports, we store pending keys in a circular * buffer and rely on the report ticker to spread them over time. * * @code * BLE ble; * KeyboardService kbd(ble); * * void once_connected_and_paired_callback(void) * { * // Sequentially send keys 'Shift'+'h', 'e', 'l', 'l', 'o', '!' and <enter> * kbd.printf("Hello!\n"); * } * @endcode */ class KeyboardService : public HIDServiceBase, public Stream { public: KeyboardService(BLE &_ble) : HIDServiceBase(_ble, KEYBOARD_REPORT_MAP, sizeof(KEYBOARD_REPORT_MAP), inputReport = emptyInputReportData, outputReport = outputReportData, featureReport = NULL, inputReportLength = sizeof(inputReportData), outputReportLength = sizeof(outputReportData), featureReportLength = 0, reportTickerDelay = 24), failedReports(0) { } virtual void onConnection(const Gap::ConnectionCallbackParams_t *params) { HIDServiceBase::onConnection(params); /* Drain buffer, in case we've been disconnected while transmitting */ if (!reportTickerIsActive && keyBuffer.isSomethingPending()) startReportTicker(); } virtual void onDisconnection(const Gap::DisconnectionCallbackParams_t *params) { stopReportTicker(); HIDServiceBase::onDisconnection(params); } /** * Send raw report. Should only be called by sendCallback. */ virtual ble_error_t send(const report_t report) { static unsigned int consecutiveFailures = 0; ble_error_t ret = HIDServiceBase::send(report); /* * Wait until a buffer is available (onDataSent) * TODO. This won't work, because BUSY error is not only returned when we're short of * notification buffers, but in other cases as well (e.g. when disconnected). We need to * find a reliable way of knowing when we actually need to wait for onDataSent to be called. if (ret == BLE_STACK_BUSY) stopReportTicker(); */ if (ret == BLE_STACK_BUSY) consecutiveFailures++; else consecutiveFailures = 0; if (consecutiveFailures > 20) { /* * We're not transmitting anything anymore. Might as well avoid overloading the * system in case it can magically fix itself. Ticker will start again on next _putc * call. It could also be started on next connection, but we can't register a callback * for that, currently. */ stopReportTicker(); consecutiveFailures = 0; } return ret; } /** * Send an empty report, representing keyUp event */ ble_error_t keyUpCode(void) { return send(emptyInputReportData); } /** * Send a character, defined by a modifier (CTRL, SHIFT, ALT) and the key * * @param key Character to send (as defined in USB HID Usage Tables) * @param modifier Optional modifiers (logical OR of enum MODIFIER_KEY) * * @returns BLE_ERROR_NONE on success, or an error code otherwise. */ ble_error_t keyDownCode(uint8_t key, uint8_t modifier) { inputReportData[0] = modifier; inputReportData[2] = keymap[key].usage; return send(inputReportData); } /** * Push a key on the internal FIFO * * @param c ASCII character to send * * @returns 0 on success, or ENOMEM when the FIFO is full. */ virtual int _putc(int c) { if (keyBuffer.full()) { return ENOMEM; } keyBuffer.push((unsigned char)c); if (!reportTickerIsActive) startReportTicker(); return 0; } uint8_t lockStatus() { // TODO: implement numlock/capslock/scrolllock return 0; } /** * Pop a key from the internal FIFO, and attempt to send it over BLE */ virtual void sendCallback(void) { ble_error_t ret; uint8_t c; if (!keyBuffer.isSomethingPending()) { /* Stop until the next call to putc */ stopReportTicker(); return; } if (!keyBuffer.isKeyUpPending()) { bool hasData = keyBuffer.getPending(c); /* * If something is pending and is not a keyUp, getPending *must* return something. The * following is only a sanity check. */ MBED_ASSERT(hasData); if (hasData) { ret = keyDownCode(c, keymap[c].modifier); if (ret) { keyBuffer.setPending(c); failedReports++; return; } } } ret = keyUpCode(); if (ret) { keyBuffer.setKeyUpPending(); failedReports++; } else { keyBuffer.clearKeyUpPending(); } } /** * Restart report ticker if it was disabled, after too many consecutive failures. * * This is called by the BLE stack. * * @param count Number of reports (notifications) sent */ virtual void onDataSent(unsigned count) { if (!reportTickerIsActive && keyBuffer.isSomethingPending()) startReportTicker(); } unsigned long failedReports; protected: virtual int _getc() { return 0; } protected: KeyBuffer keyBuffer; //GattCharacteristic boot_keyboard_input_report; //GattCharacteristic boot_keyboard_output_report; };