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: Seeed_Tiny_BLE_FTHR_Peripheral
Fork of BLE_HID by
KeyboardService.h
- Committer:
- Jean-Philippe Brucker
- Date:
- 2015-09-15
- Revision:
- 0:cfd70fa91663
- Child:
- 1:7a6c2e2c9371
File content as of revision 0:cfd70fa91663:
#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_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), }; static uint8_t inputReportData[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; static const uint8_t emptyInputReportData[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; 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 : 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 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 tranmitting 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); } ble_error_t keyDownCode(uint8_t key, uint8_t modifier) { inputReportData[0] = modifier; inputReportData[2] = keymap[key].usage; return send(inputReportData); } 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; } 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(); } } 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; };