ble nano hid over gatt
Dependencies: BLE_API mbed-dev nRF51822
KeyboardService.h
- Committer:
- cho45
- Date:
- 2016-09-03
- Revision:
- 82:af52d37b1946
- Parent:
- 81:fbe7358e38a4
- Child:
- 83:2e940d154f8b
File content as of revision 82:af52d37b1946:
/* 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 "HIDServiceBase.h" #include "keyboard.h" #include "CircularBuffer.h" /** * 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) */ const 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 USAGE_PAGE(1), 0x00, // Undefined USAGE_MINIMUM(1), 0x00, USAGE_MAXIMUM(1), 0xFF, REPORT_COUNT(1), 0x01, // 1 byte REPORT_SIZE(1), 0x08, FEATURE(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile END_COLLECTION(0), }; class KeyboardService : public HIDServiceBase { union InputReportData { uint8_t raw[8]; struct { uint8_t modifier; uint8_t padding; uint8_t keycode[6]; } data; }; union OutputReportData { uint8_t raw[1]; }; union FeatureReportData { uint8_t raw[1]; }; /** * Boot Protocol * Share input/output report with Report Protocol for memmory saving */ GattCharacteristic bootKeyboardInputReportCharacteristic; GattCharacteristic bootKeyboardOutputReportCharacteristic; InputReportData inputReportDataPublished; InputReportData inputReportData; // 5ms ごとのスイッチ判定・20ms ごとの送信 // 人間の連打限界を16press/secとし keydown/keyup それぞれでレポートを送ることを考えると // 32report/sec = 31msec で、限界で連打しても20msごとの送信にまにあう // ただし上記は1キーに対してであり、複数キーを全力で押すとキューに溜まることがある // 実際にカウントして測ってみたところ、どんなに乱暴に連打しても8ぐらいまでしかいかない CircularBuffer<InputReportData, 8, uint8_t> inputReportBuffer; InputReportData inputReportDataSending; OutputReportData outputReportData; FeatureReportData featureReportData; bool isSending; static const uint8_t MODIFIER_LEFT_CONTROL = 1<<0; static const uint8_t MODIFIER_LEFT_SHIFT = 1<<1; static const uint8_t MODIFIER_LEFT_ALT = 1<<2; static const uint8_t MODIFIER_LEFT_GUI = 1<<3; static const uint8_t MODIFIER_RIGHT_CONTROL = 1<<4; static const uint8_t MODIFIER_RIGHT_SHIFT = 1<<5; static const uint8_t MODIFIER_RIGHT_ALT = 1<<6; static const uint8_t MODIFIER_RIGHT_GUI = 1<<7; public: bool sendAvailable; uint8_t bufferCount; KeyboardService(BLE& _ble) : HIDServiceBase( _ble, KEYBOARD_REPORT_MAP, sizeof(KEYBOARD_REPORT_MAP), inputReport = inputReportDataPublished.raw, outputReport = outputReportData.raw, featureReport = featureReportData.raw, inputReportLength = sizeof(inputReportData), outputReportLength = sizeof(outputReportData), featureReportLength = sizeof(featureReportData) ), bootKeyboardInputReportCharacteristic(GattCharacteristic::UUID_BOOT_KEYBOARD_INPUT_REPORT_CHAR, (uint8_t *)inputReport, inputReportLength, inputReportLength, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE ), bootKeyboardOutputReportCharacteristic(GattCharacteristic::UUID_BOOT_KEYBOARD_OUTPUT_REPORT_CHAR, (uint8_t *)outputReport, outputReportLength, outputReportLength, GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE ), isSending(false), sendAvailable(false), bufferCount(0) { for (int i = 0; i < 8; i++) { inputReportData.raw[i] = 0; } outputReportData.raw[0] = 0; inputReportBuffer.reset(); } virtual void addExtraCharacteristics(GattCharacteristic** characteristics, uint8_t& charIndex) { DEBUG_PRINTF_BLE("addExtraCharacteristics %d\r\n", charIndex); characteristics[charIndex++] = &bootKeyboardInputReportCharacteristic; characteristics[charIndex++] = &bootKeyboardOutputReportCharacteristic; } virtual ble_error_t send(const report_t report) { if (protocolMode == REPORT_PROTOCOL) { // this is default return ble.gattServer().write( inputReportCharacteristic.getValueHandle(), report, inputReportLength ); } else { return ble.gattServer().write( bootKeyboardInputReportCharacteristic.getValueHandle(), report, inputReportLength ); } } void appendReportData(const uint8_t keycode) { uint8_t modifier = toModifierBit(keycode); if (modifier) { inputReportData.data.modifier |= modifier; return; } for (int i = 0; i < 6; i++) { if (inputReportData.data.keycode[i] == 0) { inputReportData.data.keycode[i] = keycode; return; } } // TODO: report data is full } void deleteReportData(const uint8_t keycode) { uint8_t modifier = toModifierBit(keycode); if (modifier) { inputReportData.data.modifier &= ~modifier; return; } for (int i = 0; i < 6; i++) { if (inputReportData.data.keycode[i] == keycode) { inputReportData.data.keycode[i] = 0; return; } } } void queueCurrentReportData() { DEBUG_PRINTF_BLE("Q %d\r\n", bufferCount); bufferCount++; inputReportBuffer.push(inputReportData); sendAvailable = true; } uint8_t toModifierBit(const uint8_t keycode) const { switch (keycode) { case KEY_LeftControl: return MODIFIER_LEFT_CONTROL; case KEY_LeftShift: return MODIFIER_LEFT_SHIFT; case KEY_LeftAlt: return MODIFIER_LEFT_ALT; case KEY_LeftGUI: return MODIFIER_LEFT_GUI; case KEY_RightControl: return MODIFIER_RIGHT_CONTROL; case KEY_RightShift: return MODIFIER_RIGHT_SHIFT; case KEY_RightAlt: return MODIFIER_RIGHT_ALT; case KEY_RightGUI: return MODIFIER_RIGHT_GUI; } return 0; } bool isKeyPressed() { for (int i = 0; i < 8; i++) { if (inputReportData.raw[i]) { return 1; } } return 0; } void processSend() { if (!sendAvailable) { return; } sendAvailable = false; // isSending の場合現在送信中の report があり、再送中である可能性がある // そうではない場合のみ queue から pop して新しく send する if (!isSending) { if (!inputReportBuffer.pop(inputReportDataSending)) { // 送るデータがないなら送信をやめる return; } bufferCount--; } static uint8_t busyCount = 0; isSending = true; ble_error_t error = send(inputReportDataSending.raw); if (error == BLE_STACK_BUSY) { if (busyCount++ > 10) { DEBUG_PRINTF_BLE("BLE_STACK_BUSY\r\n"); busyCount = 0; sendAvailable = false; } // retry after return; } isSending = false; } virtual void onDataSent(unsigned int count) { sendAvailable = true; } virtual void onDataWritten(const GattWriteCallbackParams *params) { if (params->handle == inputReportCharacteristic.getValueHandle()) { DEBUG_PRINTF_BLE_INTERRUPT("onDataWritten: inputReport %d bytes\r\n", params->len); } else if (params->handle == outputReportCharacteristic.getValueHandle()) { DEBUG_PRINTF_BLE_INTERRUPT("onDataWritten: outputReport %d bytes\r\n", params->len); } else if (params->handle == featureReportCharacteristic.getValueHandle()) { DEBUG_PRINTF_BLE_INTERRUPT("onDataWritten: featureReport %d bytes\r\n", params->len); } else if (params->handle == protocolModeCharacteristic.getValueHandle()) { DEBUG_PRINTF_BLE_INTERRUPT("onDataWritten: protocolMode %d bytes %d\r\n", params->len, params->data[0]); protocolMode = params->data[0]; } } };