├── mkdocs.yml ├── version ├── doc ├── index.md ├── HID.md ├── HIDService.md └── img │ └── Linux-HOGP.svg ├── apache-2.0.txt ├── TODO ├── README.md ├── examples ├── examples_common.h ├── examples_common.cpp ├── mouse_scroll.cpp ├── keyboard_stream.cpp └── microbit_joystick.cpp └── BLE_HID ├── USBHID_Types.h ├── JoystickService.h ├── HIDServiceBase.h ├── MouseService.h ├── HIDServiceBase.cpp ├── KeyboardService.h └── Keyboard_types.h /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: BLE HID 2 | 3 | docs_dir: doc/ 4 | 5 | pages: 6 | - ['index.md', 'Home'] 7 | - ['HID.md', 'Introduction to USB HID'] 8 | - ['HIDService.md', 'Using the HID service on mbed'] 9 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | # Informal version number to keep track of API changes during development and 2 | # keep in sync with the various lib and example repos. Once the HID library is 3 | # stable, it will probably get merged with BLE_API, and this file can be 4 | # removed. 5 | 6 | 0.4 7 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | # Human Interface Device over Bluetooth Low Energy 2 | 3 | This documentation is about the HID-over-Gatt service for mbed's BLE API. 4 | We'll first introduce the Human Interface Device ([HID][USBHID]) protocol, and 5 | then describe how to use its implementation as a Gatt service for the [BLE API][BLEAPI]. 6 | 7 | [USBHID]: http://www.usb.org/developers/hidpage/HID1_11.pdf "USB HID 1.11 specification" 8 | [BLEAPI]: https://developer.mbed.org/teams/Bluetooth-Low-Energy/ 9 | -------------------------------------------------------------------------------- /apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 ARM Limited 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | Somewhat sorted by priority: 2 | 3 | * MacOSX testing (10.9) 4 | 5 | * Windows testing 6 | 7 | * IOS testing 8 | 9 | * Size optimizations 10 | 11 | * Output reports (e.g. keyboard LEDs) 12 | 13 | * Low-energy: avoid sending spurious reports. 14 | 15 | * Factor out common headers with mbed's USBHID. That means HID types and 16 | keyboard types. 17 | 18 | * Multiple input/output reports 19 | For instance, when a device can act as mouse/keyboard combo, there will be 20 | two input reports characteristics with different IDs 21 | 22 | * Boot protocol 23 | If BIOS and firmwares start supporting BLE and GATT one day... 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Human Interface Device over Bluetooth Low Energy 2 | 3 | Human Interface Device ([HID][USBHID]) is a standard that defines the format 4 | used to communicate human inputs to a computer. It was originally created for 5 | USB keyboards, mice, joysticks, digitizers, audio controllers and so on. 6 | 7 | You can use the [HID Over GATT Profile][HOGP] (HOGP) to send HID information 8 | over Bluetooth Low Energy, using the same data format as USB HID. 9 | 10 | Warning: this is not the same as the HID profile used on Classic Bluetooth. 11 | 12 | ## Content 13 | 14 | This repository contains the HID Service implementation, example code, and 15 | various documentation. 16 | 17 | ### Code 18 | 19 | - `BLE_HID/HIDServiceBase.*`: 20 | the HID Service implementation; requires *BLE\_API*. 21 | - `BLE_HID/KeyboardService.h`: 22 | an example use of HIDServiceBase, which sends Keycode reports. 23 | - `BLE_HID/MouseService.h`: 24 | a service that sends mouse events: linear speed along X/Y axis, scroll speed 25 | and clicks. 26 | - `BLE_HID/JoystickService.h`: 27 | a service that sends joystick events: moves along X/Y/Z axis, rotation around 28 | X, and buttons. 29 | - `examples/keyboard_stream.cpp`: 30 | an example use of KeyboardService, which sends strings through a series of HID 31 | reports. 32 | - `examples/mouse_scroll.cpp`: 33 | an example use of MouseService, which sends scroll reports. 34 | 35 | ### Documentation 36 | 37 | - [doc/HID](doc/HID.md): 38 | introduction to the USB HID specification, and to the parts that are reused 39 | in BLE. 40 | - [doc/HIDService](doc/HIDService.md): 41 | description of the BLE HID service, and how to use this implementation. 42 | 43 | 44 | [USBHID]: http://www.usb.org/developers/hidpage/HID1_11.pdf "USB HID 1.11 specification" 45 | [HOGP]: https://developer.bluetooth.org/TechnologyOverview/Pages/HOGP.aspx "HID-over-GATT profile" 46 | [HIDS]: https://developer.bluetooth.org/TechnologyOverview/Pages/HIDS.aspx "BLE HID Sevice" 47 | -------------------------------------------------------------------------------- /examples/examples_common.h: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2015 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef HID_EXAMPLES_COMMON_H_ 18 | #define HID_EXAMPLES_COMMON_H_ 19 | 20 | /** 21 | * Functions and configuration common to all HID demos 22 | */ 23 | 24 | #include "ble/BLE.h" 25 | 26 | #include "HIDServiceBase.h" 27 | 28 | /** 29 | * IO capabilities of the device. During development, you most likely want "JustWorks", which means 30 | * no IO capabilities. 31 | * It is also possible to use IO_CAPS_DISPLAY_ONLY to generate and show a pincode on the serial 32 | * output. 33 | */ 34 | #ifndef HID_SECURITY_IOCAPS 35 | #define HID_SECURITY_IOCAPS (SecurityManager::IO_CAPS_NONE) 36 | #endif 37 | 38 | /** 39 | * Security level. MITM disabled forces "Just Works". If you require MITM, HID_SECURITY_IOCAPS must 40 | * be at least IO_CAPS_DISPLAY_ONLY. 41 | */ 42 | #ifndef HID_SECURITY_REQUIRE_MITM 43 | #define HID_SECURITY_REQUIRE_MITM false 44 | #endif 45 | 46 | /** 47 | * Disable debug messages by setting NDEBUG 48 | */ 49 | #ifndef NDEBUG 50 | #define HID_DEBUG(...) printf(__VA_ARGS__) 51 | #else 52 | #define HID_DEBUG(...) 53 | #endif 54 | 55 | /** 56 | * Initialize security manager: set callback functions and required security level 57 | */ 58 | void initializeSecurity(BLE &ble); 59 | 60 | /** 61 | * - Initialize auxiliary services required by the HID-over-GATT Profile. 62 | * - Initialize common Gap advertisement. 63 | * 64 | * Demos only have to set a custom device name and appearance, and their HID 65 | * service. 66 | */ 67 | void initializeHOGP(BLE &ble); 68 | 69 | #endif /* !BLE_HID_COMMON_H_ */ 70 | -------------------------------------------------------------------------------- /examples/examples_common.cpp: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2015 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "ble/services/BatteryService.h" 18 | #include "ble/services/DeviceInformationService.h" 19 | 20 | #include "examples_common.h" 21 | 22 | static void passkeyDisplayCallback(Gap::Handle_t handle, const SecurityManager::Passkey_t passkey) 23 | { 24 | printf("Input passKey: "); 25 | for (unsigned i = 0; i < Gap::ADDR_LEN; i++) { 26 | printf("%c", passkey[i]); 27 | } 28 | printf("\r\n"); 29 | } 30 | 31 | static void securitySetupCompletedCallback(Gap::Handle_t handle, SecurityManager::SecurityCompletionStatus_t status) 32 | { 33 | if (status == SecurityManager::SEC_STATUS_SUCCESS) { 34 | printf("Security success %d\r\n", status); 35 | } else { 36 | printf("Security failed %d\r\n", status); 37 | } 38 | } 39 | 40 | static void securitySetupInitiatedCallback(Gap::Handle_t, bool allowBonding, bool requireMITM, SecurityManager::SecurityIOCapabilities_t iocaps) 41 | { 42 | printf("Security setup initiated\r\n"); 43 | } 44 | 45 | void initializeSecurity(BLE &ble) 46 | { 47 | bool enableBonding = true; 48 | bool requireMITM = HID_SECURITY_REQUIRE_MITM; 49 | 50 | ble.securityManager().onSecuritySetupInitiated(securitySetupInitiatedCallback); 51 | ble.securityManager().onPasskeyDisplay(passkeyDisplayCallback); 52 | ble.securityManager().onSecuritySetupCompleted(securitySetupCompletedCallback); 53 | 54 | ble.securityManager().init(enableBonding, requireMITM, HID_SECURITY_IOCAPS); 55 | } 56 | 57 | void initializeHOGP(BLE &ble) 58 | { 59 | static const uint16_t uuid16_list[] = {GattService::UUID_HUMAN_INTERFACE_DEVICE_SERVICE, 60 | GattService::UUID_DEVICE_INFORMATION_SERVICE, 61 | GattService::UUID_BATTERY_SERVICE}; 62 | 63 | DeviceInformationService deviceInfo(ble, "ARM", "m1", "abc", "def", "ghi", "jkl"); 64 | 65 | BatteryService batteryInfo(ble, 80); 66 | 67 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | 68 | GapAdvertisingData::LE_GENERAL_DISCOVERABLE); 69 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS, 70 | (uint8_t *)uuid16_list, sizeof(uuid16_list)); 71 | 72 | // see 5.1.2: HID over GATT Specification (pg. 25) 73 | ble.gap().setAdvertisingType(GapAdvertisingParams::ADV_CONNECTABLE_UNDIRECTED); 74 | // 30ms to 50ms is recommended (5.1.2) 75 | ble.gap().setAdvertisingInterval(50); 76 | } 77 | -------------------------------------------------------------------------------- /BLE_HID/USBHID_Types.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2010-2011 mbed.org, MIT License 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | * and associated documentation files (the "Software"), to deal in the Software without 5 | * restriction, including without limitation the rights to use, copy, modify, merge, publish, 6 | * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 7 | * Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or 10 | * substantial portions of the Software. 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 13 | * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | */ 18 | 19 | #ifndef USBCLASS_HID_TYPES 20 | #define USBCLASS_HID_TYPES 21 | 22 | #include 23 | 24 | /* */ 25 | #define HID_VERSION_1_11 (0x0111) 26 | 27 | /* HID Class */ 28 | #define HID_CLASS (3) 29 | #define HID_SUBCLASS_NONE (0) 30 | #define HID_PROTOCOL_NONE (0) 31 | 32 | /* Descriptors */ 33 | #define HID_DESCRIPTOR (33) 34 | #define HID_DESCRIPTOR_LENGTH (0x09) 35 | #define REPORT_DESCRIPTOR (34) 36 | 37 | /* Class requests */ 38 | #define GET_REPORT (0x1) 39 | #define GET_IDLE (0x2) 40 | #define SET_REPORT (0x9) 41 | #define SET_IDLE (0xa) 42 | 43 | /* HID Class Report Descriptor */ 44 | /* Short items: size is 0, 1, 2 or 3 specifying 0, 1, 2 or 4 (four) bytes */ 45 | /* of data as per HID Class standard */ 46 | 47 | /* Main items */ 48 | #define INPUT(size) (0x80 | size) 49 | #define OUTPUT(size) (0x90 | size) 50 | #define FEATURE(size) (0xb0 | size) 51 | #define COLLECTION(size) (0xa0 | size) 52 | #define END_COLLECTION(size) (0xc0 | size) 53 | 54 | /* Global items */ 55 | #define USAGE_PAGE(size) (0x04 | size) 56 | #define LOGICAL_MINIMUM(size) (0x14 | size) 57 | #define LOGICAL_MAXIMUM(size) (0x24 | size) 58 | #define PHYSICAL_MINIMUM(size) (0x34 | size) 59 | #define PHYSICAL_MAXIMUM(size) (0x44 | size) 60 | #define UNIT_EXPONENT(size) (0x54 | size) 61 | #define UNIT(size) (0x64 | size) 62 | #define REPORT_SIZE(size) (0x74 | size) 63 | #define REPORT_ID(size) (0x84 | size) 64 | #define REPORT_COUNT(size) (0x94 | size) 65 | #define PUSH(size) (0xa4 | size) 66 | #define POP(size) (0xb4 | size) 67 | 68 | /* Local items */ 69 | #define USAGE(size) (0x08 | size) 70 | #define USAGE_MINIMUM(size) (0x18 | size) 71 | #define USAGE_MAXIMUM(size) (0x28 | size) 72 | #define DESIGNATOR_INDEX(size) (0x38 | size) 73 | #define DESIGNATOR_MINIMUM(size) (0x48 | size) 74 | #define DESIGNATOR_MAXIMUM(size) (0x58 | size) 75 | #define STRING_INDEX(size) (0x78 | size) 76 | #define STRING_MINIMUM(size) (0x88 | size) 77 | #define STRING_MAXIMUM(size) (0x98 | size) 78 | #define DELIMITER(size) (0xa8 | size) 79 | 80 | /* HID Report */ 81 | /* Where report IDs are used the first byte of 'data' will be the */ 82 | /* report ID and 'length' will include this report ID byte. */ 83 | 84 | #define MAX_HID_REPORT_SIZE (64) 85 | 86 | typedef struct { 87 | uint32_t length; 88 | uint8_t data[MAX_HID_REPORT_SIZE]; 89 | } HID_REPORT; 90 | 91 | #endif 92 | -------------------------------------------------------------------------------- /examples/mouse_scroll.cpp: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2015 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "mbed.h" 18 | 19 | #include "ble/BLE.h" 20 | #include "MouseService.h" 21 | 22 | #include "examples_common.h" 23 | /* 24 | * Simplest use of MouseService: scroll up and down when buttons are pressed 25 | * To do that, we change wheel speed in HID reports when a button is pushed, 26 | * and reset it to 0 when it is released. 27 | */ 28 | 29 | BLE ble; 30 | 31 | MouseService *mouseServicePtr; 32 | static const char DEVICE_NAME[] = "TrivialMouse"; 33 | static const char SHORT_DEVICE_NAME[] = "mouse0"; 34 | 35 | DigitalOut waiting_led(LED1); 36 | DigitalOut connected_led(LED2); 37 | 38 | InterruptIn button1(BUTTON1); 39 | InterruptIn button2(BUTTON2); 40 | 41 | void button1_down() { 42 | mouseServicePtr->setSpeed(0, 0, 1); 43 | } 44 | 45 | void button1_up() { 46 | mouseServicePtr->setSpeed(0, 0, 0); 47 | } 48 | 49 | void button2_down() { 50 | mouseServicePtr->setSpeed(0, 0, -1); 51 | } 52 | 53 | void button2_up() { 54 | mouseServicePtr->setSpeed(0, 0, 0); 55 | } 56 | 57 | 58 | static void onDisconnect(const Gap::DisconnectionCallbackParams_t *params) 59 | { 60 | HID_DEBUG("disconnected\r\n"); 61 | connected_led = 0; 62 | 63 | ble.gap().startAdvertising(); // restart advertising 64 | } 65 | 66 | static void onConnect(const Gap::ConnectionCallbackParams_t *params) 67 | { 68 | HID_DEBUG("connected\r\n"); 69 | waiting_led = 0; 70 | } 71 | 72 | static void waiting() { 73 | if (!mouseServicePtr->isConnected()) 74 | waiting_led = !waiting_led; 75 | else 76 | connected_led = !connected_led; 77 | } 78 | 79 | int main() 80 | { 81 | Ticker heartbeat; 82 | 83 | button1.rise(button1_up); 84 | button1.fall(button1_down); 85 | button2.rise(button2_up); 86 | button2.fall(button2_down); 87 | 88 | HID_DEBUG("initialising ticker\r\n"); 89 | 90 | heartbeat.attach(waiting, 1); 91 | 92 | HID_DEBUG("initialising ble\r\n"); 93 | ble.init(); 94 | 95 | ble.gap().onDisconnection(onDisconnect); 96 | ble.gap().onConnection(onConnect); 97 | 98 | initializeSecurity(ble); 99 | 100 | HID_DEBUG("adding hid service\r\n"); 101 | 102 | MouseService mouseService(ble); 103 | mouseServicePtr = &mouseService; 104 | 105 | HID_DEBUG("adding dev info and battery service\r\n"); 106 | initializeHOGP(ble); 107 | 108 | HID_DEBUG("setting up gap\r\n"); 109 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::MOUSE); 110 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, 111 | (const uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME)); 112 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME, 113 | (const uint8_t *)SHORT_DEVICE_NAME, sizeof(SHORT_DEVICE_NAME)); 114 | 115 | ble.gap().setDeviceName((const uint8_t *)DEVICE_NAME); 116 | 117 | HID_DEBUG("advertising\r\n"); 118 | ble.gap().startAdvertising(); 119 | 120 | while (true) { 121 | ble.waitForEvent(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /BLE_HID/JoystickService.h: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2015 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "mbed.h" 18 | 19 | #include "HIDServiceBase.h" 20 | 21 | enum ButtonState 22 | { 23 | BUTTON_UP, 24 | BUTTON_DOWN 25 | }; 26 | 27 | enum JoystickButton 28 | { 29 | JOYSTICK_BUTTON_1 = 0x1, 30 | JOYSTICK_BUTTON_2 = 0x2, 31 | }; 32 | 33 | report_map_t JOYSTICK_REPORT_MAP = { 34 | USAGE_PAGE(1), 0x01, // Generic Desktop 35 | USAGE(1), 0x04, // Joystick 36 | COLLECTION(1), 0x01, // Application 37 | COLLECTION(1), 0x00, // Physical 38 | USAGE_PAGE(1), 0x09, // Buttons 39 | USAGE_MINIMUM(1), 0x01, 40 | USAGE_MAXIMUM(1), 0x03, 41 | LOGICAL_MINIMUM(1), 0x00, 42 | LOGICAL_MAXIMUM(1), 0x01, 43 | REPORT_COUNT(1), 0x03, // 2 bits (Buttons) 44 | REPORT_SIZE(1), 0x01, 45 | INPUT(1), 0x02, // Data, Variable, Absolute 46 | REPORT_COUNT(1), 0x01, // 6 bits (Padding) 47 | REPORT_SIZE(1), 0x05, 48 | INPUT(1), 0x01, // Constant 49 | USAGE_PAGE(1), 0x01, // Generic Desktop 50 | USAGE(1), 0x30, // X 51 | USAGE(1), 0x31, // Y 52 | USAGE(1), 0x32, // Z 53 | USAGE(1), 0x33, // Rx 54 | LOGICAL_MINIMUM(1), 0x81, // -127 55 | LOGICAL_MAXIMUM(1), 0x7f, // 127 56 | REPORT_SIZE(1), 0x08, // Three bytes 57 | REPORT_COUNT(1), 0x04, 58 | INPUT(1), 0x02, // Data, Variable, Absolute (unlike mouse) 59 | END_COLLECTION(0), 60 | END_COLLECTION(0), 61 | }; 62 | 63 | uint8_t report[] = { 0, 0, 0, 0, 0 }; 64 | 65 | class JoystickService: public HIDServiceBase 66 | { 67 | public: 68 | JoystickService(BLE &_ble) : 69 | HIDServiceBase(_ble, 70 | JOYSTICK_REPORT_MAP, sizeof(JOYSTICK_REPORT_MAP), 71 | inputReport = report, 72 | outputReport = NULL, 73 | featureReport = NULL, 74 | inputReportLength = sizeof(inputReport), 75 | outputReportLength = 0, 76 | featureReportLength = 0, 77 | reportTickerDelay = 20), 78 | buttonsState (0), 79 | failedReports (0) 80 | { 81 | speed[0] = 0; 82 | speed[1] = 0; 83 | speed[2] = 0; 84 | speed[3] = 0; 85 | 86 | startReportTicker(); 87 | } 88 | 89 | int setSpeed(int8_t x, int8_t y, int8_t z) 90 | { 91 | speed[0] = x; 92 | speed[1] = y; 93 | speed[2] = z; 94 | 95 | return 0; 96 | } 97 | 98 | int setButton(JoystickButton button, ButtonState state) 99 | { 100 | if (state == BUTTON_UP) 101 | buttonsState &= ~(button); 102 | else 103 | buttonsState |= button; 104 | 105 | return 0; 106 | } 107 | 108 | virtual void sendCallback(void) { 109 | if (!connected) 110 | return; 111 | 112 | report[0] = buttonsState & 0x7; 113 | report[1] = speed[0]; 114 | report[2] = speed[1]; 115 | report[3] = speed[2]; 116 | report[4] = speed[3]; 117 | 118 | if (send(report)) 119 | failedReports++; 120 | } 121 | 122 | protected: 123 | uint8_t buttonsState; 124 | uint8_t speed[4]; 125 | 126 | public: 127 | uint32_t failedReports; 128 | }; 129 | -------------------------------------------------------------------------------- /examples/keyboard_stream.cpp: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2015 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "mbed.h" 18 | 19 | #include "ble/BLE.h" 20 | #include "KeyboardService.h" 21 | 22 | #include "examples_common.h" 23 | 24 | /** 25 | * This program implements a complete HID-over-Gatt Profile: 26 | * - HID is provided by KeyboardService 27 | * - Battery Service 28 | * - Device Information Service 29 | * 30 | * Complete strings can be sent over BLE using printf. Please note, however, than a 12char string 31 | * will take about 500ms to transmit, principally because of the limited notification rate in BLE. 32 | * KeyboardService uses a circular buffer to store the strings to send, and calls to putc will fail 33 | * once this buffer is full. This will result in partial strings being sent to the client. 34 | */ 35 | 36 | DigitalOut waiting_led(LED1); 37 | DigitalOut connected_led(LED2); 38 | 39 | InterruptIn button1(BUTTON1); 40 | InterruptIn button2(BUTTON2); 41 | 42 | BLE ble; 43 | KeyboardService *kbdServicePtr; 44 | 45 | static const char DEVICE_NAME[] = "uKbd"; 46 | static const char SHORT_DEVICE_NAME[] = "kbd1"; 47 | 48 | static void onDisconnect(const Gap::DisconnectionCallbackParams_t *params) 49 | { 50 | HID_DEBUG("disconnected\r\n"); 51 | connected_led = 0; 52 | 53 | ble.gap().startAdvertising(); // restart advertising 54 | } 55 | 56 | static void onConnect(const Gap::ConnectionCallbackParams_t *params) 57 | { 58 | HID_DEBUG("connected\r\n"); 59 | waiting_led = false; 60 | } 61 | 62 | static void waiting() { 63 | if (!kbdServicePtr->isConnected()) 64 | waiting_led = !waiting_led; 65 | else 66 | connected_led = !connected_led; 67 | } 68 | 69 | void send_string(const char * c) { 70 | if (!kbdServicePtr) 71 | return; 72 | 73 | if (!kbdServicePtr->isConnected()) { 74 | HID_DEBUG("we haven't connected yet..."); 75 | } else { 76 | int len = strlen(c); 77 | kbdServicePtr->printf(c); 78 | HID_DEBUG("sending %d chars\r\n", len); 79 | } 80 | } 81 | 82 | void send_stuff() { 83 | send_string("hello world!\n"); 84 | } 85 | 86 | void send_more_stuff() { 87 | send_string("All work and no play makes Jack a dull boy\n"); 88 | } 89 | 90 | int main() 91 | { 92 | Ticker heartbeat; 93 | 94 | button1.rise(send_stuff); 95 | button2.rise(send_more_stuff); 96 | 97 | HID_DEBUG("initialising ticker\r\n"); 98 | 99 | heartbeat.attach(waiting, 1); 100 | 101 | HID_DEBUG("initialising ble\r\n"); 102 | ble.init(); 103 | 104 | ble.gap().onDisconnection(onDisconnect); 105 | ble.gap().onConnection(onConnect); 106 | 107 | initializeSecurity(ble); 108 | 109 | HID_DEBUG("adding hid service\r\n"); 110 | KeyboardService kbdService(ble); 111 | kbdServicePtr = &kbdService; 112 | 113 | HID_DEBUG("adding device info and battery service\r\n"); 114 | initializeHOGP(ble); 115 | 116 | HID_DEBUG("setting up gap\r\n"); 117 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::KEYBOARD); 118 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, 119 | (const uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME)); 120 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME, 121 | (const uint8_t *)SHORT_DEVICE_NAME, sizeof(SHORT_DEVICE_NAME)); 122 | 123 | ble.gap().setDeviceName((const uint8_t *)DEVICE_NAME); 124 | 125 | HID_DEBUG("advertising\r\n"); 126 | ble.gap().startAdvertising(); 127 | 128 | while (true) { 129 | ble.waitForEvent(); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /BLE_HID/HIDServiceBase.h: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2015 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef HID_SERVICE_BASE_H_ 18 | #define HID_SERVICE_BASE_H_ 19 | 20 | #include "mbed.h" 21 | 22 | #include "ble/BLE.h" 23 | #include "USBHID_Types.h" 24 | 25 | #define BLE_UUID_DESCRIPTOR_REPORT_REFERENCE 0x2908 26 | 27 | typedef const uint8_t report_map_t[]; 28 | typedef const uint8_t * report_t; 29 | 30 | typedef struct { 31 | uint16_t bcdHID; 32 | uint8_t bCountryCode; 33 | uint8_t flags; 34 | } HID_information_t; 35 | 36 | enum ReportType { 37 | INPUT_REPORT = 0x1, 38 | OUTPUT_REPORT = 0x2, 39 | FEATURE_REPORT = 0x3, 40 | }; 41 | 42 | enum ProtocolMode { 43 | BOOT_PROTOCOL = 0x0, 44 | REPORT_PROTOCOL = 0x1, 45 | }; 46 | 47 | typedef struct { 48 | uint8_t ID; 49 | uint8_t type; 50 | } report_reference_t; 51 | 52 | 53 | class HIDServiceBase { 54 | public: 55 | /** 56 | * Constructor 57 | * 58 | * @param _ble 59 | * BLE object to add this service to 60 | * @param reportMap 61 | * Byte array representing the input/output report formats. In USB HID jargon, it 62 | * is called "HID report descriptor". 63 | * @param reportMapLength 64 | * Size of the reportMap array 65 | * @param outputReportLength 66 | * Maximum length of a sent report (up to 64 bytes) (default: 64 bytes) 67 | * @param inputReportLength 68 | * Maximum length of a received report (up to 64 bytes) (default: 64 bytes) 69 | * @param inputReportTickerDelay 70 | * Delay between input report notifications, in ms. Acceptable values depend directly on 71 | * GAP's connInterval parameter, so it shouldn't be less than 12ms 72 | * Preferred GAP connection interval is set after this value, in order to send 73 | * notifications as quick as possible: minimum connection interval will be set to 74 | * (inputReportTickerDelay / 2) 75 | */ 76 | HIDServiceBase(BLE &_ble, 77 | report_map_t reportMap, 78 | uint8_t reportMapLength, 79 | report_t inputReport, 80 | report_t outputReport, 81 | report_t featureReport, 82 | uint8_t inputReportLength = 0, 83 | uint8_t outputReportLength = 0, 84 | uint8_t featureReportLength = 0, 85 | uint8_t inputReportTickerDelay = 50); 86 | 87 | /** 88 | * Send Report 89 | * 90 | * @param report Report to send. Must be of size @ref inputReportLength 91 | * @return The write status 92 | * 93 | * @note Don't call send() directly for multiple reports! Use reportTicker for that, in order 94 | * to avoid overloading the BLE stack, and let it handle events between each report. 95 | */ 96 | virtual ble_error_t send(const report_t report); 97 | 98 | /** 99 | * Read Report 100 | * 101 | * @param report Report to fill. Must be of size @ref outputReportLength 102 | * @return The read status 103 | */ 104 | virtual ble_error_t read(report_t report); 105 | 106 | virtual void onConnection(const Gap::ConnectionCallbackParams_t *params); 107 | virtual void onDisconnection(const Gap::DisconnectionCallbackParams_t *params); 108 | 109 | virtual bool isConnected(void) 110 | { 111 | return connected; 112 | } 113 | 114 | protected: 115 | /** 116 | * Called by BLE API when data has been successfully sent. 117 | * 118 | * @param count Number of reports sent 119 | * 120 | * @note Subclasses can override this to avoid starting the report ticker when there is nothing 121 | * to send 122 | */ 123 | virtual void onDataSent(unsigned count); 124 | 125 | /** 126 | * Start the ticker that sends input reports at regular interval 127 | * 128 | * @note reportTickerIsActive describes the state of the ticker and can be used by HIDS 129 | * implementations. 130 | */ 131 | virtual void startReportTicker(void); 132 | 133 | /** 134 | * Stop the input report ticker 135 | */ 136 | virtual void stopReportTicker(void); 137 | 138 | /** 139 | * Called by input report ticker at regular interval (reportTickerDelay). This must be 140 | * overriden by HIDS implementations to call the @ref send() with a report, if necessary. 141 | */ 142 | virtual void sendCallback(void) = 0; 143 | 144 | /** 145 | * Create the Gatt descriptor for a report characteristic 146 | */ 147 | GattAttribute** inputReportDescriptors(); 148 | GattAttribute** outputReportDescriptors(); 149 | GattAttribute** featureReportDescriptors(); 150 | 151 | /** 152 | * Create the HID information structure 153 | */ 154 | HID_information_t* HIDInformation(); 155 | 156 | protected: 157 | BLE &ble; 158 | bool connected; 159 | 160 | int reportMapLength; 161 | 162 | report_t inputReport; 163 | report_t outputReport; 164 | report_t featureReport; 165 | 166 | uint8_t inputReportLength; 167 | uint8_t outputReportLength; 168 | uint8_t featureReportLength; 169 | 170 | uint8_t controlPointCommand; 171 | uint8_t protocolMode; 172 | 173 | report_reference_t inputReportReferenceData; 174 | report_reference_t outputReportReferenceData; 175 | report_reference_t featureReportReferenceData; 176 | 177 | GattAttribute inputReportReferenceDescriptor; 178 | GattAttribute outputReportReferenceDescriptor; 179 | GattAttribute featureReportReferenceDescriptor; 180 | 181 | // Optional gatt characteristics: 182 | GattCharacteristic protocolModeCharacteristic; 183 | 184 | // Report characteristics (each sort of optional) 185 | GattCharacteristic inputReportCharacteristic; 186 | GattCharacteristic outputReportCharacteristic; 187 | GattCharacteristic featureReportCharacteristic; 188 | 189 | // Required gatt characteristics: Report Map, Information, Control Point 190 | GattCharacteristic reportMapCharacteristic; 191 | ReadOnlyGattCharacteristic HIDInformationCharacteristic; 192 | GattCharacteristic HIDControlPointCharacteristic; 193 | 194 | Ticker reportTicker; 195 | uint32_t reportTickerDelay; 196 | bool reportTickerIsActive; 197 | }; 198 | 199 | #endif /* !HID_SERVICE_BASE_H_ */ 200 | -------------------------------------------------------------------------------- /BLE_HID/MouseService.h: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2015 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "mbed.h" 18 | 19 | #include "HIDServiceBase.h" 20 | 21 | enum ButtonState 22 | { 23 | BUTTON_UP, 24 | BUTTON_DOWN 25 | }; 26 | 27 | enum MouseButton 28 | { 29 | MOUSE_BUTTON_LEFT = 0x1, 30 | MOUSE_BUTTON_RIGHT = 0x2, 31 | MOUSE_BUTTON_MIDDLE = 0x4, 32 | }; 33 | 34 | /** 35 | * Report descriptor for a standard 3 buttons + wheel mouse with relative X/Y 36 | * moves 37 | */ 38 | report_map_t MOUSE_REPORT_MAP = { 39 | USAGE_PAGE(1), 0x01, // Generic Desktop 40 | USAGE(1), 0x02, // Mouse 41 | COLLECTION(1), 0x01, // Application 42 | USAGE(1), 0x01, // Pointer 43 | COLLECTION(1), 0x00, // Physical 44 | USAGE_PAGE(1), 0x09, // Buttons 45 | USAGE_MINIMUM(1), 0x01, 46 | USAGE_MAXIMUM(1), 0x03, 47 | LOGICAL_MINIMUM(1), 0x00, 48 | LOGICAL_MAXIMUM(1), 0x01, 49 | REPORT_COUNT(1), 0x03, // 3 bits (Buttons) 50 | REPORT_SIZE(1), 0x01, 51 | INPUT(1), 0x02, // Data, Variable, Absolute 52 | REPORT_COUNT(1), 0x01, // 5 bits (Padding) 53 | REPORT_SIZE(1), 0x05, 54 | INPUT(1), 0x01, // Constant 55 | USAGE_PAGE(1), 0x01, // Generic Desktop 56 | USAGE(1), 0x30, // X 57 | USAGE(1), 0x31, // Y 58 | USAGE(1), 0x38, // Wheel 59 | LOGICAL_MINIMUM(1), 0x81, // -127 60 | LOGICAL_MAXIMUM(1), 0x7f, // 127 61 | REPORT_SIZE(1), 0x08, // Three bytes 62 | REPORT_COUNT(1), 0x03, 63 | INPUT(1), 0x06, // Data, Variable, Relative 64 | END_COLLECTION(0), 65 | END_COLLECTION(0), 66 | }; 67 | 68 | uint8_t report[] = { 0, 0, 0, 0 }; 69 | 70 | /** 71 | * @class MouseService 72 | * @brief HID-over-Gatt mouse service. 73 | * 74 | * Send mouse moves and button informations over BLE. 75 | * 76 | * @code 77 | * BLE ble; 78 | * MouseService mouse(ble); 79 | * 80 | * Timeout timeout; 81 | * 82 | * void stop_mouse_move(void) 83 | * { 84 | * // Set mouse state to immobile 85 | * mouse.setButton(MOUSE_BUTTON_LEFT, MOUSE_UP); 86 | * mouse.setSpeed(0, 0, 0); 87 | * } 88 | * 89 | * void start_mouse_move(void) 90 | * { 91 | * // Move left with a left button down. If the focus is on a drawing 92 | * // software, for instance, this should draw a line. 93 | * mouse.setButton(MOUSE_BUTTON_LEFT, MOUSE_DOWN); 94 | * mouse.setSpeed(1, 0, 0); 95 | * 96 | * timeout.attach(stop_mouse_move, 0.2); 97 | * } 98 | * @endcode 99 | */ 100 | class MouseService: public HIDServiceBase 101 | { 102 | public: 103 | MouseService(BLE &_ble) : 104 | HIDServiceBase(_ble, 105 | MOUSE_REPORT_MAP, sizeof(MOUSE_REPORT_MAP), 106 | inputReport = report, 107 | outputReport = NULL, 108 | featureReport = NULL, 109 | inputReportLength = sizeof(inputReport), 110 | outputReportLength = 0, 111 | featureReportLength = 0, 112 | reportTickerDelay = 20), 113 | buttonsState (0), 114 | failedReports (0) 115 | { 116 | speed[0] = 0; 117 | speed[1] = 0; 118 | speed[2] = 0; 119 | 120 | startReportTicker(); 121 | } 122 | 123 | virtual void onConnection(const Gap::ConnectionCallbackParams_t *params) 124 | { 125 | HIDServiceBase::onConnection(params); 126 | startReportTicker(); 127 | } 128 | 129 | virtual void onDisconnection(const Gap::DisconnectionCallbackParams_t *params) 130 | { 131 | stopReportTicker(); 132 | HIDServiceBase::onDisconnection(params); 133 | } 134 | 135 | /** 136 | * Set X, Y, Z speed of the mouse. Parameters are sticky and will be 137 | * transmitted on every tick. Users should therefore reset them to 0 when 138 | * the device is immobile. 139 | * 140 | * @param x Speed on hoizontal axis 141 | * @param y Speed on vertical axis 142 | * @param wheel Scroll speed 143 | * 144 | * @returns A status code 145 | * 146 | * @note Directions depend on the operating system's configuration. It is 147 | * customary to increase values on the X axis from left to right, and on the 148 | * Y axis from top to bottom. 149 | * Wheel is less standard, although positive values will usually scroll up. 150 | */ 151 | int setSpeed(int8_t x, int8_t y, int8_t wheel) 152 | { 153 | speed[0] = x; 154 | speed[1] = y; 155 | speed[2] = wheel; 156 | 157 | startReportTicker(); 158 | 159 | return 0; 160 | } 161 | 162 | /** 163 | * Toggle the state of one button 164 | * 165 | * @returns A status code 166 | */ 167 | int setButton(MouseButton button, ButtonState state) 168 | { 169 | if (state == BUTTON_UP) 170 | buttonsState &= ~(button); 171 | else 172 | buttonsState |= button; 173 | 174 | startReportTicker(); 175 | 176 | return 0; 177 | } 178 | 179 | /** 180 | * Called by the report ticker 181 | */ 182 | virtual void sendCallback(void) { 183 | uint8_t buttons = buttonsState & 0x7; 184 | 185 | if (!connected) 186 | return; 187 | 188 | bool can_sleep = (report[0] == 0 189 | && report[1] == 0 190 | && report[2] == 0 191 | && report[3] == 0 192 | && report[0] == buttons 193 | && report[1] == speed[0] 194 | && report[2] == speed[1] 195 | && report[3] == speed[2]); 196 | 197 | if (can_sleep) { 198 | /* TODO: find out why there always is two more calls to sendCallback after this 199 | * stopReportTicker(). */ 200 | stopReportTicker(); 201 | return; 202 | } 203 | 204 | report[0] = buttons; 205 | report[1] = speed[0]; 206 | report[2] = speed[1]; 207 | report[3] = speed[2]; 208 | 209 | if (send(report)) 210 | failedReports++; 211 | } 212 | 213 | protected: 214 | uint8_t buttonsState; 215 | uint8_t speed[3]; 216 | 217 | public: 218 | uint32_t failedReports; 219 | }; 220 | -------------------------------------------------------------------------------- /doc/HID.md: -------------------------------------------------------------------------------- 1 | # The USB HID protocol 2 | 3 | ## HID reports 4 | 5 | This document covers: 6 | 7 | * [Keyboard reports](#keyboard). 8 | * [Mouse reports](#mouse). 9 | * [Further reading](#further-reading). 10 | 11 | ### Keyboard 12 | 13 | The keyboard is still the most important interface device (because the current 14 | version of HID doesn't support brain-computer interface). 15 | 16 | #### Input reports 17 | 18 | A keyboard will send "press key" and "release key" information to the 19 | operating system, which will interpret it differently depending on the 20 | current application. For example, many computer games accept single letters as commands, whereas a text application will simply add the letter to the text. 21 | 22 | When the OS receives a "press key" report, it writes the letter or uses it as a command as explained above. It will keep performing the same action, again and again, until it receives a 23 | "release key" report. 24 | 25 | The usual format for keyboard reports is the following byte array: 26 | 27 | [modifier, reserved, Key1, Key2, Key3, Key4, Key6, Key7] 28 | 29 | Note that this report can send six simultaneous key events. Their order in 30 | the report doesn't matter, since the OS must keep track of the timing of each 31 | key press. 32 | 33 | The reserved byte might be used by vendors, but is mostly useless. 34 | 35 | When you press the letter 'a' on a USB keyboard, the following report will be 36 | sent over the USB interrupt pipe: 37 | 38 | 'a' report: [0, 0, 4, 0, 0, 0, 0, 0] 39 | 40 | This '4' value is the Keycode for the letter 'a', as described in [USB HID 41 | Usage Tables][USBHID UsageTables] (Section 10: Keyboard/Keypad Page). That 42 | document defines the report formats for all standardized HIDs. 43 | 44 | After releasing the key, the following report will be sent: 45 | 46 | null report: [0, 0, 0, 0, 0, 0, 0, 0] 47 | 48 | '4' is replaced with '0'; an array of zeros means nothing is being pressed. 49 | 50 | For an uppercase 'A', the report will also need to contain a 'Left Shift' 51 | modifier. The modifier byte is actually a bitmap, which means that each bit 52 | corresponds to one key: 53 | 54 | - bit 0: left control 55 | - bit 1: left shift 56 | - bit 2: left alt 57 | - bit 3: left GUI (Win/Apple/Meta key) 58 | - bit 4: right control 59 | - bit 5: right shift 60 | - bit 6: right alt 61 | - bit 7: right GUI 62 | 63 | With left shift pressed, out report will look like that: 64 | 65 | 'A' report: [2, 0, 4, 0, 0, 0, 0, 0] 66 | 67 | #### Output reports 68 | 69 | We want the OS to be able to control our keyboard's LEDs, for 70 | instance when another keyboard enables Caps Lock. To do this, the OS writes 71 | a separate one-byte output report that defines the state of each LED. 72 | 73 | #### Feature reports 74 | 75 | There is another type of report, Feature Reports, which allows to set the 76 | internal properties of a device. 77 | 78 | #### Report descriptors 79 | 80 | The power of USB HID resides in report descriptors. Instead of defining a fixed 81 | report format for each possible device, the USB HID specification provides a 82 | way for devices to describe what their reports will look like. 83 | 84 | A report descriptor is a list of *items*. They are a short series of bytes (two or 85 | three), containing each item type and its value. For instance, a "Usage Page" item 86 | of size 1 has type 0x05, and is followed by a byte describing the HID Usage 87 | Page ([USBHID UsageTables]) in which the device lies. 88 | 89 | Here are the items of our keyboard: 90 | 91 | - *Usage Page: Generic Desktop Control (0x1)*: 92 | Refers to the Usage Pages table (Section 3), in [USBHID UsageTables]. 93 | 94 | - *Usage: keyboard (0x06)*: 95 | This is only the high-level description of the device. Its capabilities will 96 | be described in the following collections. A keyboard might contain a 97 | pointing device in addition to its keys. In that case, each input report will 98 | need to be prefixed with a report ID. 99 | 100 | - *Collection: application (0x01)*. 101 | - *Usage Page: Keyboard or keypad (0x07)*. The table in section 10 shows the 102 | meaning of each key. 103 | - **Modifier declaration:** 104 | - *Usage minimum: Left Control (0xe0)*. 105 | - *Usage maximum: Right GUI (0xe7)*. 106 | - *Logical minimum: 0*. 107 | - *Logical maximum: 1*. 108 | - *Report size: 1* - size in bits of the report fields. 109 | - *Report count: 8* - number of data fields. 110 | - *Input: Dynamic Flag (0x2)* - does the actual declaration. 111 | - **Reserved field:** 112 | - *Report count: 1*. 113 | - *Report size: 1*. 114 | - *Input: Static Value (0x1)*. This byte is reserved (constant). 115 | - **Key code array:** 116 | - *Report count: 6*. 117 | - *Report size: 8*. 118 | - *Usage minimum: 0*. 119 | - *Usage maximum: 101*. 120 | - *Logical minimum: 0*. 121 | - *Logical maximum: 101*. 122 | - *Input: Selector (0x0)*. This means that all items in this array will 123 | always have one value from the Keyboard or Keypad page, ranging from 0 124 | to 101, with 0 meaning "no event". 125 | - **LED output report (five bits for LEDs, and three bits for padding)**: 126 | - *Usage page: LEDs (0x8)*. 127 | - *Report count: 5*. 128 | - *Report size: 1*. 129 | - *Usage minimum: Num Lock (1)*. 130 | - *Usage maximum: Kana (5)*. 131 | - *Output: Dynamic Flag (0x2)*. 132 | - *Report count: 3*. 133 | - *Report size: 1*. 134 | - *Output: Static Value (0x1)*. 135 | 136 | - *End Collection*. 137 | 138 | ### Mouse 139 | 140 | The Mouse report format is simpler: 141 | 142 | [Buttons, X-translation, Y-translation] 143 | 144 | And the report descriptor is now pretty easy to understand: 145 | 146 | - *Usage Page: Generic Desktop Control*. 147 | 148 | - *Usage: Mouse*. 149 | 150 | - *Collection: application*. 151 | 152 | - *Usage: Pointer*. 153 | - *Collection: physical*. 154 | - **First byte is a bitmap of three buttons:** 155 | - *Report Count: 3*. 156 | - *Report Size: 1*. 157 | - *Usage Page: Buttons*. 158 | - *Usage Minimum: 1*. 159 | - *Usage Maximum: 3*. 160 | - *Logical Minimum: 0*. 161 | - *Logical Maximum: 1*. 162 | - *Input: DynamicFlags*. 163 | - *Report Count: 1*. 164 | - *Report Size: 5* bits padding. 165 | - *Input: Static Value*. 166 | - **Then two speed reports:** 167 | - *Report Size: 8*. 168 | - *Report Count: 1*. 169 | - *Usage Page: Generic Desktop*. 170 | - *Usage: X*. 171 | - *Usage: Y*. 172 | - *Logical Minimum: -127*. 173 | - *Logical Maximum: 127*. 174 | - *Input: Dynamic value*. 175 | - *End Collection*. 176 | - *End Collection*. 177 | 178 | ## Further reading 179 | 180 | The USB specification is quite bulky, and navigation is not always easy. Here 181 | are some pointers to documentation sections that are of interest to BLE HID: 182 | 183 | I don't have a proper index in my PDF version of [USBHID], so here is the one I 184 | use to navigate: 185 | 186 | - 5.2, 5.3, 5.4 Report Descriptor (page *14*). 187 | 188 | - 6.2.2 Report Descriptor (page *23*). 189 | 190 | - 6.2.2.4 Main Items (page *28*). 191 | 192 | - 6.2.2.5 Input/Output/Feature Items (page *30*). 193 | 194 | - 6.2.2.6 Collections (page *33*). 195 | 196 | - 6.2.2.7 Global Items (page *35*). 197 | 198 | - 6.2.2.8 Local Items (page *40*). 199 | 200 | - Appendix B: Keyboard and Mouse boot descriptors (page *59*). 201 | Standardized report descriptors that are implemented by the BIOS without having 202 | to parse a descriptor. You'll note that this descriptor closely follows the examples above. 203 | 204 | - Appendix C: Implementation requirements for a USB keyboard (page *62*). 205 | 206 | - Appendix D: Other examples of report descriptors (page *64*). 207 | 208 | - Appendix H: Glossary (page *79*). 209 | 210 | The [USBHID UsageTables] is annex A of the previous document, and contains all 211 | standardized usage: 212 | 213 | - Table 1 in section 3 is a summary of the available Usage Pages. 214 | 215 | - 3.4 Usage Types shows how to use Input/Output/Feature items. 216 | 217 | 218 | [USBHID]: http://www.usb.org/developers/hidpage/HID1_11.pdf "USB HID 1.11 specification" 219 | [USBHID UsageTables]: http://www.usb.org/developers/hidpage/Hut1_12v2.pdf "USB HID Usage Tables" 220 | 221 | Next: [HID-over-GATT](HIDService.md) 222 | -------------------------------------------------------------------------------- /doc/HIDService.md: -------------------------------------------------------------------------------- 1 | # HID-over-GATT 2 | 3 | ## HOGP 4 | 5 | To be recognised as an HID, a device must implement the [HID-over-GATT Profile][HOGP], 6 | which means at least the following services: 7 | 8 | - HID. 9 | - Battery. 10 | - Device information. 11 | 12 | [HOGP]: https://developer.bluetooth.org/TechnologyOverview/Pages/HOGP.aspx "HID-over-GATT Profile" 13 | 14 | ## BLE HID Service 15 | 16 | All of the structure formats described in [HID](HID.md) are used in HID-over-GATT. 17 | 18 | The nomenclature is not ideal, though: 19 | 20 | - Report Map: what the USB HID calls Report Descriptor. 21 | - Report Reference Characteristic Descriptor is the BLE way of setting a report 22 | characteristic's metadata. It contains the type (Input/Output/Feature) 23 | and ID of a report. 24 | 25 | The HID Service defines the following characteristics: 26 | 27 | - *Protocol Mode*: the default is Report mode, but you can change that to Boot mode. 28 | - *Report Map*: the HID Report descriptor, defining the possible format for 29 | Input/Output/Feature reports. 30 | - *Report*: a characteristic used as a vehicle for HID reports. Unlike USB, where 31 | the ID is sent as a prefix when there is more than one report per type, the ID 32 | is stored in a characteristic descriptor. This means that there will be one 33 | characteristic per report described in the Report Map. 34 | - *Boot Keyboard Input Report*: when the device is a keyboard, it must define 35 | boot reports. 36 | - *Boot Keyboard Output Report*. 37 | - *Boot Mouse Input Report*. 38 | - *HID Information*: HID version, localization and some capability flags. 39 | - *HID Control Point*: inform the device that the host is entering or leaving suspend 40 | state. 41 | 42 | Instead of USB interrupt pipes, input reports are sent using notifications. 43 | They can also be read by the host. 44 | 45 | ## Implementation with BLE_API 46 | 47 | A custom HID device will need to inherit from HIDServiceBase and provide it 48 | with the necessary informations: 49 | 50 | * A report map (USB's report descriptor). In the following example, we will use 51 | the keyboard map described in appendix B.1 of the USB HID specification. 52 | * The report arrays and their sizes. 53 | * The report rate, when reports need to be sent asynchronously. To start with a 54 | simple example, we'll only use synchronous reports. 55 | 56 | We want to send the string "Hello" to an OS. We can first write a method `putc` 57 | that takes a char as argument and returns a status code. This function must 58 | send two reports: one that means "key down" and one that means "key up". 59 | 60 | Given a `keymap` array that associates characters to their keycode, we can 61 | write `putc` as follows: 62 | 63 | 64 | int KeyboardService::putc(char c) 65 | { 66 | static uint8_t report[8] = {0, 0, 0, 0, 0, 0, 0, 0}; 67 | int err = 0; 68 | 69 | report[0] = keymap[c].modifier; 70 | report[2] = keymap[c].usage; 71 | 72 | err = send(report); 73 | if (err) 74 | return err; 75 | 76 | report[0] = 0; 77 | report[2] = 0; 78 | 79 | return send(report); 80 | } 81 | 82 | And a string could be sent with this: 83 | 84 | void KeyboardService::puts(const char *s) 85 | { 86 | for (; *s; s++) 87 | putc(*s); 88 | } 89 | 90 | 91 | There are two major issues with this code: 92 | 93 | 1. If the "key up" report fails, the key will seem to be stuck until the next 94 | report, from the OS' point of view. 95 | 96 | 2. Since we're using a very limited amount of resources, reports will soon fail 97 | if we attempt to send them sequentially. 98 | 99 | To illustrate those issues, let's take as example a nRF51 chip with a S110 100 | SoftDevice. The firmware is using seven buffers to store outgoing 101 | notifications. For a call to `keyboardService.puts("Hello")`, `putc` will be 102 | called for each character until the end of string, before returning and 103 | allowing the main loop to inspect BLE events. 104 | Calls to `send()` will start to fail during the fourth report, since the seven 105 | notification buffers will be in use. Instead of seeing "Hello" on the OS, 106 | we'll see "Helllllllll...", which won't look good in a demo. 107 | 108 | ### KeyboardService 109 | 110 | KeyboardService uses a buffer to dissociate calls to `putc` from the `send` 111 | thread. 112 | This thread is provided by `HIDServiceBase` in the form of a ticker. When 113 | enabled, it calls the `sendCallback` method at a rate specified with the 114 | `reportTickerDelay` parameter. 115 | 116 | We send key reports with KeyboardService through its putc or printf 117 | methods. For example, with `kbdService.printf("Hello world!")`, the string 118 | "Hello world!" will go in a circular buffer, and the ticker will consume from 119 | this buffer every 20ms. 120 | First, the letter 'H' will be sent by writing the following values into the 121 | inputReport characteristic: 122 | 123 | [0x2, 0, 0x0b, 0, 0, 0, 0, 0] 124 | [0, 0, 0, 0, 0, 0, 0, 0] 125 | 126 | If the first report fails, the callback puts it back in the circular buffer and 127 | will try again on next call. Same goes for the empty report. 128 | 129 | On the next tick, we'll send two reports for the letter 'e', and so on. 130 | 131 | ### MouseService 132 | 133 | A mouse will need to send reports at regular interval, because the OS will only 134 | move the cursor upon receiving an input report. As explained in [HID](HID.md), 135 | our report will contain three bytes; one bitmap contains the button status, the 136 | next two are signed and represent the immediate speed. 137 | 138 | 139 | Note that since we're using GATT notifications, there is no way to know if the 140 | OS got the message and understood it correctly. 141 | 142 | ## Support in common operating systems 143 | 144 | Bluetooth Low Energy support is still at an early stage, and the HID service is 145 | even less supported. At this stage, we can only say that examples work 146 | relatively well on Linux and Android. 147 | 148 | ### Windows 149 | 150 | Untested. 151 | 152 | ### Mac OS 153 | 154 | * The keyboard and mouse examples work on MacOSX 10.10. The OS takes complete 155 | control over HID Services, so you can connect to a device using the bluetooth 156 | system panel. 157 | 158 | * All tests failed on MacOSX 10.9; it seems to simply ignore all input reports. 159 | * Mouse report map has been tested with USB HID and worked well. So we can 160 | assume that's not the issue. 161 | * Examples often manage to connect, but sending HID reports doesn't have any 162 | apparent effect. 163 | One way of "solving" this on MacOSX 10.10 was to for security on Device 164 | Information Service. Security requirements need to be investigated. 165 | 166 | ### iOS 167 | 168 | Untested. 169 | 170 | ### Android 171 | 172 | Android may be running either Bluez (see Linux), or the custom BlueDroid 173 | implementation. Either way, all initial tests succeeded: HID keyboard and 174 | mouse over BLE worked great. 175 | You should be able to just connect to HID devices from the bluetooth 176 | configuration panel. 177 | 178 | ### Linux 179 | 180 | [Bluez][bluez] is the way to go on Linux. Bluetooth support up until version 181 | 4.2 is in the Kernel, and userspace implementation depends on the distribution. 182 | 183 | On Archlinux, for instance, systemd launches the Bluetooth daemon, which 184 | communicates with the kernel driver. Any client can then send commands using 185 | dbus. One such client is bluetoothctl, another is blueman. 186 | 187 | I used the Bluetooth page on [the Archlinux wiki][archbt] as a starting point. 188 | It's a good source of info about Bluez userspace. 189 | 190 | The HID part is a bit more intricate: 191 | 192 | * Bluetooth packets go through Bluez and Bluetooth daemon. The driver itself 193 | doesn't handle GATT. 194 | * The daemon recognizes GATT packets associated with the HID Service. 195 | * HID reports are sent to UHID (Userspace HID), and re-routed through the 196 | generic HID component in the kernel. 197 | * Everything goes through the input drivers and out to the userspace again. 198 | 199 | ![HID Subsystem with BLE in Linux](img/Linux-HOGP.svg) 200 | 201 | (Most of this diagram is courtesy of the kernel's 202 | Documentation/hid/hid-transport.txt) 203 | 204 | I'm still having issues when a device is disconnected: the daemon seems unable 205 | to register the kernel hidraw device again on its own. My current solution is 206 | to remove the pairing infos manually with bluetoothctl's `remove` command and 207 | let the agent do the rest. 208 | 209 | [bluez]: http://www.bluez.org/download/ "Bluez" 210 | [archbt]: https://wiki.archlinux.org/index.php/Bluetooth "Archlinux wiki: bluetooth" 211 | -------------------------------------------------------------------------------- /examples/microbit_joystick.cpp: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2015 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "mbed.h" 18 | 19 | #include "ble/BLE.h" 20 | 21 | #ifdef USE_JOYSTICK 22 | #include "JoystickService.h" 23 | #define HID_BUTTON_1 JOYSTICK_BUTTON_1 24 | #define HID_BUTTON_2 JOYSTICK_BUTTON_2 25 | #else 26 | #include "MouseService.h" 27 | #define HID_BUTTON_1 MOUSE_BUTTON_LEFT 28 | #define HID_BUTTON_2 MOUSE_BUTTON_RIGHT 29 | #endif 30 | 31 | #include "examples_common.h" 32 | 33 | /* 34 | * This demo drives the joystick/mouse HID service with the micro:bit's accelerometer, an MMA8653. 35 | * The accelerometer is polled every 40ms or so, and the HIDService sends speed reports. 36 | * 37 | * How it works: when immobile, the accelerometer reports an acceleration of 1g = 9.8m/s^2. 38 | * When horizontal, ax = ay = 0, and az = g. Otherwise, g will be projected on each axis. This demo 39 | * uses that projection on ax and ay, to control the speed of the joystick. 40 | * 41 | * Linear moves will be negligible compared to g reports, and are almost impossible to detect 42 | * without adding at least a gyro in the mix. 43 | */ 44 | 45 | #define MMA8653_ADDR 0x3a 46 | #define MMA8653_OUT_X_MSB 0x01 47 | #define MMA8653_WHOAMI 0x0d 48 | #define MMA8653_XYZ_DATA_CFG 0x0e 49 | #define MMA8653_CTRL_REG1 0x2a 50 | #define MMA8653_CTRL_REG4 0x2d 51 | #define MMA8653_CTRL_REG5 0x2e 52 | 53 | I2C i2c(p30, p0); 54 | 55 | BLE ble; 56 | 57 | #ifdef USE_JOYSTICK 58 | JoystickService *hidServicePtr; 59 | #else 60 | MouseService *hidServicePtr; 61 | #endif 62 | 63 | static const char DEVICE_NAME[] = "uJoy"; 64 | static const char SHORT_DEVICE_NAME[] = "joystick0"; 65 | 66 | DigitalOut waiting_led(LED1); 67 | DigitalOut connected_led(LED2); 68 | 69 | InterruptIn button1(BUTTON_A); 70 | InterruptIn button2(BUTTON_B); 71 | 72 | void button1_down() 73 | { 74 | if (hidServicePtr) 75 | hidServicePtr->setButton(HID_BUTTON_1, BUTTON_DOWN); 76 | } 77 | 78 | void button1_up() 79 | { 80 | if (hidServicePtr) 81 | hidServicePtr->setButton(HID_BUTTON_1, BUTTON_UP); 82 | } 83 | 84 | void button2_down() 85 | { 86 | if (hidServicePtr) 87 | hidServicePtr->setButton(HID_BUTTON_2, BUTTON_DOWN); 88 | } 89 | 90 | void button2_up() 91 | { 92 | if (hidServicePtr) 93 | hidServicePtr->setButton(HID_BUTTON_2, BUTTON_UP); 94 | } 95 | 96 | 97 | /* ---- MMA8653 handling ---- */ 98 | int write_accel(uint8_t reg, uint8_t data) 99 | { 100 | uint8_t command[2]; 101 | 102 | command[0] = reg; 103 | command[1] = data; 104 | 105 | return i2c.write(MMA8653_ADDR, (const char*)command, 2, true); 106 | } 107 | 108 | int read_accel(uint8_t reg, uint8_t *buffer, int length) 109 | { 110 | int err = i2c.write(MMA8653_ADDR, (const char *)®, 1, true); 111 | 112 | if (err) { 113 | HID_DEBUG("init write failed\r\n"); 114 | return err; 115 | } 116 | return i2c.read(MMA8653_ADDR, (char *)buffer, length); 117 | } 118 | 119 | void init_accel(void) 120 | { 121 | uint8_t whoami; 122 | int err; 123 | 124 | /* Put device in standby mode */ 125 | err = write_accel(MMA8653_CTRL_REG1, 0x00); 126 | if (err) 127 | HID_DEBUG("CTRL_REG1 1 failed\r\n"); 128 | 129 | /* Interrupt DRDY */ 130 | err = write_accel(MMA8653_CTRL_REG4, 0x00); 131 | if (err) 132 | HID_DEBUG("CTRL_REG4 failed\r\n"); 133 | 134 | /* same, CFG */ 135 | err = write_accel(MMA8653_CTRL_REG5, 0x00); 136 | if (err) 137 | HID_DEBUG("CTRL_REG5 failed\r\n"); 138 | 139 | /* +/- 2g */ 140 | err = write_accel(MMA8653_XYZ_DATA_CFG, 0x00); 141 | if (err) 142 | HID_DEBUG("CTRL_DATA_CFG failed\r\n"); 143 | 144 | /* 145 | * Data rate = 50Hz 146 | * 10 bits of data 147 | */ 148 | err = write_accel(MMA8653_CTRL_REG1, 0x21); 149 | if (err) 150 | HID_DEBUG("CTRL_REG1 2 failed\r\n"); 151 | 152 | read_accel(MMA8653_WHOAMI, &whoami, 1); 153 | HID_DEBUG("Accel is %x\r\n", whoami); 154 | MBED_ASSERT(whoami == 0x5a); 155 | } 156 | 157 | /** Integer square root of an uint16 */ 158 | static uint8_t sqrti(uint16_t v) 159 | { 160 | static const uint16_t sqr_table[] = { 161 | 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 162 | 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961 163 | }; 164 | static const unsigned sqr_table_size = sizeof(sqr_table) / sizeof(sqr_table[0]); 165 | uint8_t i; 166 | 167 | for (i = 0; i < sqr_table_size; i++) 168 | { 169 | if (sqr_table[i] > v) 170 | return i; 171 | } 172 | 173 | return sqr_table[sqr_table_size - 1]; 174 | } 175 | 176 | /** 177 | * Invert sign and attempt to smoothen the acceleration value. 178 | * Disclaimer: the process used to write the following functions was exteremly chaotic. 179 | * No calculation whatsoever was involved. 180 | */ 181 | static int8_t soften_accel(int16_t v) 182 | { 183 | int sign = -1; 184 | 185 | if (v < 0) { 186 | sign = 1; 187 | v = -v; 188 | } 189 | 190 | return sign * sqrti(v / 5); 191 | } 192 | 193 | void poll_accel(void) 194 | { 195 | static const int maxv = 128; 196 | int8_t data[6]; 197 | 198 | static int vx = 0; 199 | static int vy = 0; 200 | 201 | int ax = 0; 202 | int ay = 0; 203 | 204 | read_accel(MMA8653_OUT_X_MSB, (uint8_t *)data, 6); 205 | 206 | ax = data[0]; 207 | ay = data[2]; 208 | 209 | ax = soften_accel(ax); 210 | ay = soften_accel(ay); 211 | 212 | vx = ax + vx; 213 | vy = ay + vy; 214 | 215 | /* Clamp down on speed */ 216 | if (vx > maxv) 217 | vx = maxv; 218 | if (vx < -maxv) 219 | vx = -maxv; 220 | if (vy > maxv) 221 | vy = maxv; 222 | if (vy < -maxv) 223 | vy = -maxv; 224 | 225 | if (hidServicePtr) { 226 | /* 227 | * Reduce speed a little bit. HID report values must be in [-127; 127], but above 32 is 228 | * generally too high anyway. 229 | */ 230 | hidServicePtr->setSpeed(vx / 4, vy / 4, 0); 231 | } 232 | 233 | /** 234 | * Decrease over time. We need this to remove integration drifts due to noise, but it could well 235 | * be improved, as this prevents from having steady low speeds. 236 | */ 237 | vx *= 0.999; 238 | vy *= 0.999; 239 | } 240 | 241 | void onDisconnect(const Gap::DisconnectionCallbackParams_t *params) 242 | { 243 | HID_DEBUG("disconnected\r\n"); 244 | connected_led = 0; 245 | 246 | ble.gap().startAdvertising(); // restart advertising 247 | } 248 | 249 | void onConnect(const Gap::ConnectionCallbackParams_t *params) 250 | { 251 | HID_DEBUG("connected\r\n"); 252 | waiting_led = 0; 253 | } 254 | 255 | void waiting() { 256 | if (!hidServicePtr->isConnected()) 257 | waiting_led = !waiting_led; 258 | else 259 | connected_led = !connected_led; 260 | } 261 | 262 | int main() 263 | { 264 | Ticker accel_poll_ticker; 265 | Ticker heartbeat; 266 | 267 | init_accel(); 268 | 269 | accel_poll_ticker.attach(poll_accel, 0.02); 270 | 271 | button1.rise(button1_up); 272 | button1.fall(button1_down); 273 | button2.rise(button2_up); 274 | button2.fall(button2_down); 275 | 276 | HID_DEBUG("initialising ticker\r\n"); 277 | 278 | heartbeat.attach(waiting, 1); 279 | 280 | HID_DEBUG("initialising ble\r\n"); 281 | 282 | ble.init(); 283 | 284 | initializeSecurity(ble); 285 | 286 | ble.gap().onDisconnection(onDisconnect); 287 | ble.gap().onConnection(onConnect); 288 | 289 | HID_DEBUG("adding hid service\r\n"); 290 | 291 | #ifdef USE_JOYSTICK 292 | JoystickService joystickService(ble); 293 | hidServicePtr = &joystickService; 294 | 295 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::JOYSTICK); 296 | #else 297 | MouseService mouseService(ble); 298 | hidServicePtr = &mouseService; 299 | 300 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::MOUSE); 301 | #endif 302 | 303 | HID_DEBUG("setting up gap\r\n"); 304 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LOCAL_NAME, 305 | (const uint8_t *)DEVICE_NAME, sizeof(DEVICE_NAME)); 306 | ble.gap().accumulateAdvertisingPayload(GapAdvertisingData::SHORTENED_LOCAL_NAME, 307 | (const uint8_t *)SHORT_DEVICE_NAME, sizeof(SHORT_DEVICE_NAME)); 308 | 309 | ble.gap().setDeviceName((const uint8_t *)DEVICE_NAME); 310 | 311 | HID_DEBUG("adding dev info and battery service\r\n"); 312 | initializeHOGP(ble); 313 | 314 | HID_DEBUG("advertising\r\n"); 315 | ble.gap().startAdvertising(); 316 | 317 | while (true) { 318 | ble.waitForEvent(); 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /BLE_HID/HIDServiceBase.cpp: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2015 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "mbed.h" 18 | #include "HIDServiceBase.h" 19 | 20 | HIDServiceBase::HIDServiceBase(BLE &_ble, 21 | report_map_t reportMap, 22 | uint8_t reportMapSize, 23 | report_t inputReport, 24 | report_t outputReport, 25 | report_t featureReport, 26 | uint8_t inputReportLength, 27 | uint8_t outputReportLength, 28 | uint8_t featureReportLength, 29 | uint8_t inputReportTickerDelay) : 30 | ble(_ble), 31 | connected (false), 32 | reportMapLength(reportMapSize), 33 | 34 | inputReport(inputReport), 35 | outputReport(outputReport), 36 | featureReport(featureReport), 37 | 38 | inputReportLength(inputReportLength), 39 | outputReportLength(outputReportLength), 40 | featureReportLength(featureReportLength), 41 | 42 | protocolMode(REPORT_PROTOCOL), 43 | 44 | inputReportReferenceDescriptor(BLE_UUID_DESCRIPTOR_REPORT_REFERENCE, 45 | (uint8_t *)&inputReportReferenceData, 2, 2), 46 | outputReportReferenceDescriptor(BLE_UUID_DESCRIPTOR_REPORT_REFERENCE, 47 | (uint8_t *)&outputReportReferenceData, 2, 2), 48 | featureReportReferenceDescriptor(BLE_UUID_DESCRIPTOR_REPORT_REFERENCE, 49 | (uint8_t *)&featureReportReferenceData, 2, 2), 50 | 51 | protocolModeCharacteristic(GattCharacteristic::UUID_PROTOCOL_MODE_CHAR, &protocolMode, 1, 1, 52 | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ 53 | | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE), 54 | 55 | inputReportCharacteristic(GattCharacteristic::UUID_REPORT_CHAR, 56 | (uint8_t *)inputReport, inputReportLength, inputReportLength, 57 | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ 58 | | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY 59 | | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE, 60 | inputReportDescriptors(), 1), 61 | 62 | outputReportCharacteristic(GattCharacteristic::UUID_REPORT_CHAR, 63 | (uint8_t *)outputReport, outputReportLength, outputReportLength, 64 | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ 65 | | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE 66 | | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE, 67 | outputReportDescriptors(), 1), 68 | 69 | featureReportCharacteristic(GattCharacteristic::UUID_REPORT_CHAR, 70 | (uint8_t *)featureReport, featureReportLength, featureReportLength, 71 | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ 72 | | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE, 73 | featureReportDescriptors(), 1), 74 | 75 | /* 76 | * We need to set reportMap content as const, in order to let the compiler put it into flash 77 | * instead of RAM. The characteristic is read-only so it won't be written, but 78 | * GattCharacteristic constructor takes non-const arguments only. Hence the cast. 79 | */ 80 | reportMapCharacteristic(GattCharacteristic::UUID_REPORT_MAP_CHAR, 81 | const_cast(reportMap), reportMapLength, reportMapLength, 82 | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ), 83 | 84 | HIDInformationCharacteristic(GattCharacteristic::UUID_HID_INFORMATION_CHAR, HIDInformation()), 85 | HIDControlPointCharacteristic(GattCharacteristic::UUID_HID_CONTROL_POINT_CHAR, 86 | &controlPointCommand, 1, 1, 87 | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE), 88 | 89 | reportTickerDelay(inputReportTickerDelay), 90 | reportTickerIsActive(false) 91 | { 92 | static GattCharacteristic *characteristics[] = { 93 | &HIDInformationCharacteristic, 94 | &reportMapCharacteristic, 95 | &protocolModeCharacteristic, 96 | &HIDControlPointCharacteristic, 97 | NULL, 98 | NULL, 99 | NULL, 100 | NULL, 101 | NULL 102 | }; 103 | 104 | unsigned int charIndex = 4; 105 | /* 106 | * Report characteristics are optional, and depend on the reportMap descriptor 107 | * Note: at least one should be present, but we don't check that at the moment. 108 | */ 109 | if (inputReportLength) 110 | characteristics[charIndex++] = &inputReportCharacteristic; 111 | if (outputReportLength) 112 | characteristics[charIndex++] = &outputReportCharacteristic; 113 | if (featureReportLength) 114 | characteristics[charIndex++] = &featureReportCharacteristic; 115 | 116 | /* TODO: let children add some more characteristics, namely boot keyboard and mouse (They are 117 | * mandatory as per HIDS spec.) Ex: 118 | * 119 | * addExtraCharacteristics(characteristics, int& charIndex); 120 | */ 121 | 122 | GattService service(GattService::UUID_HUMAN_INTERFACE_DEVICE_SERVICE, 123 | characteristics, charIndex); 124 | 125 | ble.gattServer().addService(service); 126 | 127 | ble.gap().onConnection(this, &HIDServiceBase::onConnection); 128 | ble.gap().onDisconnection(this, &HIDServiceBase::onDisconnection); 129 | 130 | ble.gattServer().onDataSent(this, &HIDServiceBase::onDataSent); 131 | 132 | /* 133 | * Change preferred connection params, in order to optimize the notification frequency. Most 134 | * OSes seem to respect this, even though they are not required to. 135 | * 136 | * Some OSes don't handle reconnection well, at the moment, so we set the maximum possible 137 | * timeout, 32 seconds 138 | */ 139 | uint16_t minInterval = Gap::MSEC_TO_GAP_DURATION_UNITS(reportTickerDelay / 2); 140 | if (minInterval < 6) 141 | minInterval = 6; 142 | uint16_t maxInterval = minInterval * 2; 143 | Gap::ConnectionParams_t params = {minInterval, maxInterval, 0, 3200}; 144 | 145 | ble.gap().setPreferredConnectionParams(¶ms); 146 | 147 | SecurityManager::SecurityMode_t securityMode = SecurityManager::SECURITY_MODE_ENCRYPTION_NO_MITM; 148 | protocolModeCharacteristic.requireSecurity(securityMode); 149 | reportMapCharacteristic.requireSecurity(securityMode); 150 | inputReportCharacteristic.requireSecurity(securityMode); 151 | outputReportCharacteristic.requireSecurity(securityMode); 152 | featureReportCharacteristic.requireSecurity(securityMode); 153 | } 154 | 155 | void HIDServiceBase::startReportTicker(void) { 156 | if (reportTickerIsActive) 157 | return; 158 | reportTicker.attach_us(this, &HIDServiceBase::sendCallback, reportTickerDelay * 1000); 159 | reportTickerIsActive = true; 160 | } 161 | 162 | void HIDServiceBase::stopReportTicker(void) { 163 | reportTicker.detach(); 164 | reportTickerIsActive = false; 165 | } 166 | 167 | void HIDServiceBase::onDataSent(unsigned count) { 168 | startReportTicker(); 169 | } 170 | 171 | GattAttribute** HIDServiceBase::inputReportDescriptors() { 172 | inputReportReferenceData.ID = 0; 173 | inputReportReferenceData.type = INPUT_REPORT; 174 | 175 | static GattAttribute * descs[] = { 176 | &inputReportReferenceDescriptor, 177 | }; 178 | return descs; 179 | } 180 | 181 | GattAttribute** HIDServiceBase::outputReportDescriptors() { 182 | outputReportReferenceData.ID = 0; 183 | outputReportReferenceData.type = OUTPUT_REPORT; 184 | 185 | static GattAttribute * descs[] = { 186 | &outputReportReferenceDescriptor, 187 | }; 188 | return descs; 189 | } 190 | 191 | GattAttribute** HIDServiceBase::featureReportDescriptors() { 192 | featureReportReferenceData.ID = 0; 193 | featureReportReferenceData.type = FEATURE_REPORT; 194 | 195 | static GattAttribute * descs[] = { 196 | &featureReportReferenceDescriptor, 197 | }; 198 | return descs; 199 | } 200 | 201 | 202 | HID_information_t* HIDServiceBase::HIDInformation() { 203 | static HID_information_t info = {HID_VERSION_1_11, 0x00, 0x03}; 204 | 205 | return &info; 206 | } 207 | 208 | ble_error_t HIDServiceBase::send(const report_t report) { 209 | return ble.gattServer().write(inputReportCharacteristic.getValueHandle(), 210 | report, 211 | inputReportLength); 212 | } 213 | 214 | ble_error_t HIDServiceBase::read(report_t report) { 215 | // TODO. For the time being, we'll just have HID input reports... 216 | return BLE_ERROR_NOT_IMPLEMENTED; 217 | } 218 | 219 | void HIDServiceBase::onConnection(const Gap::ConnectionCallbackParams_t *params) 220 | { 221 | this->connected = true; 222 | } 223 | 224 | void HIDServiceBase::onDisconnection(const Gap::DisconnectionCallbackParams_t *params) 225 | { 226 | this->connected = false; 227 | } 228 | -------------------------------------------------------------------------------- /BLE_HID/KeyboardService.h: -------------------------------------------------------------------------------- 1 | /* mbed Microcontroller Library 2 | * Copyright (c) 2015 ARM Limited 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include "mbed.h" 19 | #include "CircularBuffer.h" 20 | 21 | #include "HIDServiceBase.h" 22 | #include "Keyboard_types.h" 23 | 24 | /* TODO: make this easier to configure by application (e.g. as a template parameter for 25 | * KeyboardService) */ 26 | #ifndef KEYBUFFER_SIZE 27 | #define KEYBUFFER_SIZE 256 28 | #endif 29 | 30 | /** 31 | * Report descriptor for a standard 101 keys keyboard, following the HID specification example: 32 | * - 8 bytes input report (1 byte for modifiers and 6 for keys) 33 | * - 1 byte output report (LEDs) 34 | */ 35 | report_map_t KEYBOARD_REPORT_MAP = { 36 | USAGE_PAGE(1), 0x01, // Generic Desktop Ctrls 37 | USAGE(1), 0x06, // Keyboard 38 | COLLECTION(1), 0x01, // Application 39 | USAGE_PAGE(1), 0x07, // Kbrd/Keypad 40 | USAGE_MINIMUM(1), 0xE0, 41 | USAGE_MAXIMUM(1), 0xE7, 42 | LOGICAL_MINIMUM(1), 0x00, 43 | LOGICAL_MAXIMUM(1), 0x01, 44 | REPORT_SIZE(1), 0x01, // 1 byte (Modifier) 45 | REPORT_COUNT(1), 0x08, 46 | INPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position 47 | REPORT_COUNT(1), 0x01, // 1 byte (Reserved) 48 | REPORT_SIZE(1), 0x08, 49 | INPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position 50 | REPORT_COUNT(1), 0x05, // 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana) 51 | REPORT_SIZE(1), 0x01, 52 | USAGE_PAGE(1), 0x08, // LEDs 53 | USAGE_MINIMUM(1), 0x01, // Num Lock 54 | USAGE_MAXIMUM(1), 0x05, // Kana 55 | OUTPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile 56 | REPORT_COUNT(1), 0x01, // 3 bits (Padding) 57 | REPORT_SIZE(1), 0x03, 58 | OUTPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile 59 | REPORT_COUNT(1), 0x06, // 6 bytes (Keys) 60 | REPORT_SIZE(1), 0x08, 61 | LOGICAL_MINIMUM(1), 0x00, 62 | LOGICAL_MAXIMUM(1), 0x65, // 101 keys 63 | USAGE_PAGE(1), 0x07, // Kbrd/Keypad 64 | USAGE_MINIMUM(1), 0x00, 65 | USAGE_MAXIMUM(1), 0x65, 66 | INPUT(1), 0x00, // Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position 67 | END_COLLECTION(0), 68 | }; 69 | 70 | /// "keys pressed" report 71 | static uint8_t inputReportData[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; 72 | /// "keys released" report 73 | static const uint8_t emptyInputReportData[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; 74 | /// LEDs report 75 | static uint8_t outputReportData[] = { 0 }; 76 | 77 | 78 | /** 79 | * @class KeyBuffer 80 | * 81 | * Buffer used to store keys to send. 82 | * Internally, it is a CircularBuffer, with the added capability of putting the last char back in, 83 | * when we're unable to send it (ie. when BLE stack is busy) 84 | */ 85 | class KeyBuffer: public CircularBuffer 86 | { 87 | public: 88 | KeyBuffer() : 89 | CircularBuffer(), 90 | dataIsPending (false), 91 | keyUpIsPending (false) 92 | { 93 | } 94 | 95 | /** 96 | * Mark a character as pending. When a freshly popped character cannot be sent, because the 97 | * underlying stack is busy, we set it as pending, and it will get popped in priority by @ref 98 | * getPending once reports can be sent again. 99 | * 100 | * @param data The character to send in priority. The second keyUp report is implied. 101 | */ 102 | void setPending(uint8_t data) 103 | { 104 | MBED_ASSERT(dataIsPending == false); 105 | 106 | dataIsPending = true; 107 | pendingData = data; 108 | keyUpIsPending = true; 109 | } 110 | 111 | /** 112 | * Get pending char. Either from the high priority buffer (set with setPending), or from the 113 | * circular buffer. 114 | * 115 | * @param data Filled with the pending data, when present 116 | * @return true if data was filled 117 | */ 118 | bool getPending(uint8_t &data) 119 | { 120 | if (dataIsPending) { 121 | dataIsPending = false; 122 | data = pendingData; 123 | return true; 124 | } 125 | 126 | return pop(data); 127 | } 128 | 129 | bool isSomethingPending(void) 130 | { 131 | return dataIsPending || keyUpIsPending || !empty(); 132 | } 133 | 134 | /** 135 | * Signal that a keyUp report is pending. This means that a character has successfully been 136 | * sent, but the subsequent keyUp report failed. This report is of highest priority than the 137 | * next character. 138 | */ 139 | void setKeyUpPending(void) 140 | { 141 | keyUpIsPending = true; 142 | } 143 | 144 | /** 145 | * Signal that no high-priority report is pending anymore, we can go back to the normal queue. 146 | */ 147 | void clearKeyUpPending(void) 148 | { 149 | keyUpIsPending = false; 150 | } 151 | 152 | bool isKeyUpPending(void) 153 | { 154 | return keyUpIsPending; 155 | } 156 | 157 | protected: 158 | bool dataIsPending; 159 | uint8_t pendingData; 160 | bool keyUpIsPending; 161 | }; 162 | 163 | 164 | /** 165 | * @class KeyboardService 166 | * @brief HID-over-Gatt keyboard service 167 | * 168 | * Send keyboard reports over BLE. Users should rely on the high-level functions provided by the 169 | * Stream API. Because we can't send batches of HID reports, we store pending keys in a circular 170 | * buffer and rely on the report ticker to spread them over time. 171 | * 172 | * @code 173 | * BLE ble; 174 | * KeyboardService kbd(ble); 175 | * 176 | * void once_connected_and_paired_callback(void) 177 | * { 178 | * // Sequentially send keys 'Shift'+'h', 'e', 'l', 'l', 'o', '!' and 179 | * kbd.printf("Hello!\n"); 180 | * } 181 | * @endcode 182 | */ 183 | class KeyboardService : public HIDServiceBase, public Stream 184 | { 185 | public: 186 | KeyboardService(BLE &_ble) : 187 | HIDServiceBase(_ble, 188 | KEYBOARD_REPORT_MAP, sizeof(KEYBOARD_REPORT_MAP), 189 | inputReport = emptyInputReportData, 190 | outputReport = outputReportData, 191 | featureReport = NULL, 192 | inputReportLength = sizeof(inputReportData), 193 | outputReportLength = sizeof(outputReportData), 194 | featureReportLength = 0, 195 | reportTickerDelay = 24), 196 | failedReports(0) 197 | { 198 | } 199 | 200 | virtual void onConnection(const Gap::ConnectionCallbackParams_t *params) 201 | { 202 | HIDServiceBase::onConnection(params); 203 | 204 | /* Drain buffer, in case we've been disconnected while transmitting */ 205 | if (!reportTickerIsActive && keyBuffer.isSomethingPending()) 206 | startReportTicker(); 207 | } 208 | 209 | virtual void onDisconnection(const Gap::DisconnectionCallbackParams_t *params) 210 | { 211 | stopReportTicker(); 212 | HIDServiceBase::onDisconnection(params); 213 | } 214 | 215 | /** 216 | * Send raw report. Should only be called by sendCallback. 217 | */ 218 | virtual ble_error_t send(const report_t report) 219 | { 220 | static unsigned int consecutiveFailures = 0; 221 | ble_error_t ret = HIDServiceBase::send(report); 222 | 223 | /* 224 | * Wait until a buffer is available (onDataSent) 225 | * TODO. This won't work, because BUSY error is not only returned when we're short of 226 | * notification buffers, but in other cases as well (e.g. when disconnected). We need to 227 | * find a reliable way of knowing when we actually need to wait for onDataSent to be called. 228 | if (ret == BLE_STACK_BUSY) 229 | stopReportTicker(); 230 | */ 231 | if (ret == BLE_STACK_BUSY) 232 | consecutiveFailures++; 233 | else 234 | consecutiveFailures = 0; 235 | 236 | if (consecutiveFailures > 20) { 237 | /* 238 | * We're not transmitting anything anymore. Might as well avoid overloading the 239 | * system in case it can magically fix itself. Ticker will start again on next _putc 240 | * call, or on next connection. 241 | */ 242 | stopReportTicker(); 243 | consecutiveFailures = 0; 244 | } 245 | 246 | return ret; 247 | } 248 | 249 | /** 250 | * Send an empty report, representing keyUp event 251 | */ 252 | ble_error_t keyUpCode(void) 253 | { 254 | return send(emptyInputReportData); 255 | } 256 | 257 | /** 258 | * Send a character, defined by a modifier (CTRL, SHIFT, ALT) and the key 259 | * 260 | * @param key Character to send (as defined in USB HID Usage Tables) 261 | * @param modifier Optional modifiers (logical OR of enum MODIFIER_KEY) 262 | * 263 | * @returns BLE_ERROR_NONE on success, or an error code otherwise. 264 | */ 265 | ble_error_t keyDownCode(uint8_t key, uint8_t modifier) 266 | { 267 | inputReportData[0] = modifier; 268 | inputReportData[2] = keymap[key].usage; 269 | 270 | return send(inputReportData); 271 | } 272 | 273 | /** 274 | * Push a key on the internal FIFO 275 | * 276 | * @param c ASCII character to send 277 | * 278 | * @returns 0 on success, or ENOMEM when the FIFO is full. 279 | */ 280 | virtual int _putc(int c) { 281 | if (keyBuffer.full()) { 282 | return ENOMEM; 283 | } 284 | 285 | keyBuffer.push((unsigned char)c); 286 | 287 | if (!reportTickerIsActive) 288 | startReportTicker(); 289 | 290 | return 0; 291 | } 292 | 293 | uint8_t lockStatus() { 294 | // TODO: implement numlock/capslock/scrolllock 295 | return 0; 296 | } 297 | 298 | /** 299 | * Pop a key from the internal FIFO, and attempt to send it over BLE 300 | * 301 | * keyUp reports should theoretically be sent after every keyDown, but we optimize the 302 | * throughput by only sending one when strictly necessary: 303 | * - when we need to repeat the same key 304 | * - when there is no more key to report 305 | * 306 | * In case of error, put the key event back in the buffer, and retry on next tick. 307 | */ 308 | virtual void sendCallback(void) { 309 | ble_error_t ret; 310 | uint8_t c; 311 | static uint8_t previousKey = 0; 312 | 313 | if (keyBuffer.isSomethingPending() && !keyBuffer.isKeyUpPending()) { 314 | bool hasData = keyBuffer.getPending(c); 315 | 316 | /* 317 | * If something is pending and is not a keyUp, getPending *must* return something. The 318 | * following is only a sanity check. 319 | */ 320 | MBED_ASSERT(hasData); 321 | 322 | if (!hasData) 323 | return; 324 | 325 | if (previousKey == c) { 326 | /* 327 | * When the same key needs to be sent twice, we need to interleave a keyUp report, 328 | * or else the OS won't be able to differentiate them. 329 | * Push the key back into the buffer, and continue to keyUpCode. 330 | */ 331 | keyBuffer.setPending(c); 332 | } else { 333 | ret = keyDownCode(c, keymap[c].modifier); 334 | if (ret) { 335 | keyBuffer.setPending(c); 336 | failedReports++; 337 | } else { 338 | previousKey = c; 339 | } 340 | 341 | return; 342 | } 343 | } 344 | 345 | ret = keyUpCode(); 346 | if (ret) { 347 | keyBuffer.setKeyUpPending(); 348 | failedReports++; 349 | } else { 350 | keyBuffer.clearKeyUpPending(); 351 | previousKey = 0; 352 | 353 | /* Idle when there is nothing more to send */ 354 | if (!keyBuffer.isSomethingPending()) 355 | stopReportTicker(); 356 | } 357 | } 358 | 359 | /** 360 | * Restart report ticker if it was disabled, after too many consecutive failures. 361 | * 362 | * This is called by the BLE stack. 363 | * 364 | * @param count Number of reports (notifications) sent 365 | */ 366 | virtual void onDataSent(unsigned count) 367 | { 368 | if (!reportTickerIsActive && keyBuffer.isSomethingPending()) 369 | startReportTicker(); 370 | } 371 | 372 | unsigned long failedReports; 373 | 374 | protected: 375 | virtual int _getc() { 376 | return 0; 377 | } 378 | 379 | protected: 380 | KeyBuffer keyBuffer; 381 | 382 | //GattCharacteristic boot_keyboard_input_report; 383 | //GattCharacteristic boot_keyboard_output_report; 384 | }; 385 | 386 | -------------------------------------------------------------------------------- /BLE_HID/Keyboard_types.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015 mbed.org, MIT License 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | * and associated documentation files (the "Software"), to deal in the Software without 5 | * restriction, including without limitation the rights to use, copy, modify, merge, publish, 6 | * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 7 | * Software is furnished to do so, subject to the following conditions: 8 | * 9 | * The above copyright notice and this permission notice shall be included in all copies or 10 | * substantial portions of the Software. 11 | * 12 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 13 | * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | * 18 | * Note: this file was pulled from different parts of the USBHID library, in mbed SDK 19 | */ 20 | 21 | #ifndef KEYBOARD_DEFS_H 22 | #define KEYBOARD_DEFS_H 23 | 24 | #define REPORT_ID_KEYBOARD 1 25 | #define REPORT_ID_VOLUME 3 26 | 27 | /* Modifiers */ 28 | enum MODIFIER_KEY { 29 | KEY_CTRL = 1, 30 | KEY_SHIFT = 2, 31 | KEY_ALT = 4, 32 | }; 33 | 34 | 35 | enum MEDIA_KEY { 36 | KEY_NEXT_TRACK, /*!< next Track Button */ 37 | KEY_PREVIOUS_TRACK, /*!< Previous track Button */ 38 | KEY_STOP, /*!< Stop Button */ 39 | KEY_PLAY_PAUSE, /*!< Play/Pause Button */ 40 | KEY_MUTE, /*!< Mute Button */ 41 | KEY_VOLUME_UP, /*!< Volume Up Button */ 42 | KEY_VOLUME_DOWN, /*!< Volume Down Button */ 43 | }; 44 | 45 | enum FUNCTION_KEY { 46 | KEY_F1 = 128, /* F1 key */ 47 | KEY_F2, /* F2 key */ 48 | KEY_F3, /* F3 key */ 49 | KEY_F4, /* F4 key */ 50 | KEY_F5, /* F5 key */ 51 | KEY_F6, /* F6 key */ 52 | KEY_F7, /* F7 key */ 53 | KEY_F8, /* F8 key */ 54 | KEY_F9, /* F9 key */ 55 | KEY_F10, /* F10 key */ 56 | KEY_F11, /* F11 key */ 57 | KEY_F12, /* F12 key */ 58 | 59 | KEY_PRINT_SCREEN, /* Print Screen key */ 60 | KEY_SCROLL_LOCK, /* Scroll lock */ 61 | KEY_CAPS_LOCK, /* caps lock */ 62 | KEY_NUM_LOCK, /* num lock */ 63 | KEY_INSERT, /* Insert key */ 64 | KEY_HOME, /* Home key */ 65 | KEY_PAGE_UP, /* Page Up key */ 66 | KEY_PAGE_DOWN, /* Page Down key */ 67 | 68 | RIGHT_ARROW, /* Right arrow */ 69 | LEFT_ARROW, /* Left arrow */ 70 | DOWN_ARROW, /* Down arrow */ 71 | UP_ARROW, /* Up arrow */ 72 | }; 73 | 74 | typedef struct { 75 | unsigned char usage; 76 | unsigned char modifier; 77 | } KEYMAP; 78 | 79 | #ifdef US_KEYBOARD 80 | /* US keyboard (as HID standard) */ 81 | #define KEYMAP_SIZE (152) 82 | const KEYMAP keymap[KEYMAP_SIZE] = { 83 | {0, 0}, /* NUL */ 84 | {0, 0}, /* SOH */ 85 | {0, 0}, /* STX */ 86 | {0, 0}, /* ETX */ 87 | {0, 0}, /* EOT */ 88 | {0, 0}, /* ENQ */ 89 | {0, 0}, /* ACK */ 90 | {0, 0}, /* BEL */ 91 | {0x2a, 0}, /* BS */ /* Keyboard Delete (Backspace) */ 92 | {0x2b, 0}, /* TAB */ /* Keyboard Tab */ 93 | {0x28, 0}, /* LF */ /* Keyboard Return (Enter) */ 94 | {0, 0}, /* VT */ 95 | {0, 0}, /* FF */ 96 | {0, 0}, /* CR */ 97 | {0, 0}, /* SO */ 98 | {0, 0}, /* SI */ 99 | {0, 0}, /* DEL */ 100 | {0, 0}, /* DC1 */ 101 | {0, 0}, /* DC2 */ 102 | {0, 0}, /* DC3 */ 103 | {0, 0}, /* DC4 */ 104 | {0, 0}, /* NAK */ 105 | {0, 0}, /* SYN */ 106 | {0, 0}, /* ETB */ 107 | {0, 0}, /* CAN */ 108 | {0, 0}, /* EM */ 109 | {0, 0}, /* SUB */ 110 | {0, 0}, /* ESC */ 111 | {0, 0}, /* FS */ 112 | {0, 0}, /* GS */ 113 | {0, 0}, /* RS */ 114 | {0, 0}, /* US */ 115 | {0x2c, 0}, /* */ 116 | {0x1e, KEY_SHIFT}, /* ! */ 117 | {0x34, KEY_SHIFT}, /* " */ 118 | {0x20, KEY_SHIFT}, /* # */ 119 | {0x21, KEY_SHIFT}, /* $ */ 120 | {0x22, KEY_SHIFT}, /* % */ 121 | {0x24, KEY_SHIFT}, /* & */ 122 | {0x34, 0}, /* ' */ 123 | {0x26, KEY_SHIFT}, /* ( */ 124 | {0x27, KEY_SHIFT}, /* ) */ 125 | {0x25, KEY_SHIFT}, /* * */ 126 | {0x2e, KEY_SHIFT}, /* + */ 127 | {0x36, 0}, /* , */ 128 | {0x2d, 0}, /* - */ 129 | {0x37, 0}, /* . */ 130 | {0x38, 0}, /* / */ 131 | {0x27, 0}, /* 0 */ 132 | {0x1e, 0}, /* 1 */ 133 | {0x1f, 0}, /* 2 */ 134 | {0x20, 0}, /* 3 */ 135 | {0x21, 0}, /* 4 */ 136 | {0x22, 0}, /* 5 */ 137 | {0x23, 0}, /* 6 */ 138 | {0x24, 0}, /* 7 */ 139 | {0x25, 0}, /* 8 */ 140 | {0x26, 0}, /* 9 */ 141 | {0x33, KEY_SHIFT}, /* : */ 142 | {0x33, 0}, /* ; */ 143 | {0x36, KEY_SHIFT}, /* < */ 144 | {0x2e, 0}, /* = */ 145 | {0x37, KEY_SHIFT}, /* > */ 146 | {0x38, KEY_SHIFT}, /* ? */ 147 | {0x1f, KEY_SHIFT}, /* @ */ 148 | {0x04, KEY_SHIFT}, /* A */ 149 | {0x05, KEY_SHIFT}, /* B */ 150 | {0x06, KEY_SHIFT}, /* C */ 151 | {0x07, KEY_SHIFT}, /* D */ 152 | {0x08, KEY_SHIFT}, /* E */ 153 | {0x09, KEY_SHIFT}, /* F */ 154 | {0x0a, KEY_SHIFT}, /* G */ 155 | {0x0b, KEY_SHIFT}, /* H */ 156 | {0x0c, KEY_SHIFT}, /* I */ 157 | {0x0d, KEY_SHIFT}, /* J */ 158 | {0x0e, KEY_SHIFT}, /* K */ 159 | {0x0f, KEY_SHIFT}, /* L */ 160 | {0x10, KEY_SHIFT}, /* M */ 161 | {0x11, KEY_SHIFT}, /* N */ 162 | {0x12, KEY_SHIFT}, /* O */ 163 | {0x13, KEY_SHIFT}, /* P */ 164 | {0x14, KEY_SHIFT}, /* Q */ 165 | {0x15, KEY_SHIFT}, /* R */ 166 | {0x16, KEY_SHIFT}, /* S */ 167 | {0x17, KEY_SHIFT}, /* T */ 168 | {0x18, KEY_SHIFT}, /* U */ 169 | {0x19, KEY_SHIFT}, /* V */ 170 | {0x1a, KEY_SHIFT}, /* W */ 171 | {0x1b, KEY_SHIFT}, /* X */ 172 | {0x1c, KEY_SHIFT}, /* Y */ 173 | {0x1d, KEY_SHIFT}, /* Z */ 174 | {0x2f, 0}, /* [ */ 175 | {0x31, 0}, /* \ */ 176 | {0x30, 0}, /* ] */ 177 | {0x23, KEY_SHIFT}, /* ^ */ 178 | {0x2d, KEY_SHIFT}, /* _ */ 179 | {0x35, 0}, /* ` */ 180 | {0x04, 0}, /* a */ 181 | {0x05, 0}, /* b */ 182 | {0x06, 0}, /* c */ 183 | {0x07, 0}, /* d */ 184 | {0x08, 0}, /* e */ 185 | {0x09, 0}, /* f */ 186 | {0x0a, 0}, /* g */ 187 | {0x0b, 0}, /* h */ 188 | {0x0c, 0}, /* i */ 189 | {0x0d, 0}, /* j */ 190 | {0x0e, 0}, /* k */ 191 | {0x0f, 0}, /* l */ 192 | {0x10, 0}, /* m */ 193 | {0x11, 0}, /* n */ 194 | {0x12, 0}, /* o */ 195 | {0x13, 0}, /* p */ 196 | {0x14, 0}, /* q */ 197 | {0x15, 0}, /* r */ 198 | {0x16, 0}, /* s */ 199 | {0x17, 0}, /* t */ 200 | {0x18, 0}, /* u */ 201 | {0x19, 0}, /* v */ 202 | {0x1a, 0}, /* w */ 203 | {0x1b, 0}, /* x */ 204 | {0x1c, 0}, /* y */ 205 | {0x1d, 0}, /* z */ 206 | {0x2f, KEY_SHIFT}, /* { */ 207 | {0x31, KEY_SHIFT}, /* | */ 208 | {0x30, KEY_SHIFT}, /* } */ 209 | {0x35, KEY_SHIFT}, /* ~ */ 210 | {0,0}, /* DEL */ 211 | 212 | {0x3a, 0}, /* F1 */ 213 | {0x3b, 0}, /* F2 */ 214 | {0x3c, 0}, /* F3 */ 215 | {0x3d, 0}, /* F4 */ 216 | {0x3e, 0}, /* F5 */ 217 | {0x3f, 0}, /* F6 */ 218 | {0x40, 0}, /* F7 */ 219 | {0x41, 0}, /* F8 */ 220 | {0x42, 0}, /* F9 */ 221 | {0x43, 0}, /* F10 */ 222 | {0x44, 0}, /* F11 */ 223 | {0x45, 0}, /* F12 */ 224 | 225 | {0x46, 0}, /* PRINT_SCREEN */ 226 | {0x47, 0}, /* SCROLL_LOCK */ 227 | {0x39, 0}, /* CAPS_LOCK */ 228 | {0x53, 0}, /* NUM_LOCK */ 229 | {0x49, 0}, /* INSERT */ 230 | {0x4a, 0}, /* HOME */ 231 | {0x4b, 0}, /* PAGE_UP */ 232 | {0x4e, 0}, /* PAGE_DOWN */ 233 | 234 | {0x4f, 0}, /* RIGHT_ARROW */ 235 | {0x50, 0}, /* LEFT_ARROW */ 236 | {0x51, 0}, /* DOWN_ARROW */ 237 | {0x52, 0}, /* UP_ARROW */ 238 | }; 239 | 240 | #else 241 | /* UK keyboard */ 242 | #define KEYMAP_SIZE (152) 243 | const KEYMAP keymap[KEYMAP_SIZE] = { 244 | {0, 0}, /* NUL */ 245 | {0, 0}, /* SOH */ 246 | {0, 0}, /* STX */ 247 | {0, 0}, /* ETX */ 248 | {0, 0}, /* EOT */ 249 | {0, 0}, /* ENQ */ 250 | {0, 0}, /* ACK */ 251 | {0, 0}, /* BEL */ 252 | {0x2a, 0}, /* BS */ /* Keyboard Delete (Backspace) */ 253 | {0x2b, 0}, /* TAB */ /* Keyboard Tab */ 254 | {0x28, 0}, /* LF */ /* Keyboard Return (Enter) */ 255 | {0, 0}, /* VT */ 256 | {0, 0}, /* FF */ 257 | {0, 0}, /* CR */ 258 | {0, 0}, /* SO */ 259 | {0, 0}, /* SI */ 260 | {0, 0}, /* DEL */ 261 | {0, 0}, /* DC1 */ 262 | {0, 0}, /* DC2 */ 263 | {0, 0}, /* DC3 */ 264 | {0, 0}, /* DC4 */ 265 | {0, 0}, /* NAK */ 266 | {0, 0}, /* SYN */ 267 | {0, 0}, /* ETB */ 268 | {0, 0}, /* CAN */ 269 | {0, 0}, /* EM */ 270 | {0, 0}, /* SUB */ 271 | {0, 0}, /* ESC */ 272 | {0, 0}, /* FS */ 273 | {0, 0}, /* GS */ 274 | {0, 0}, /* RS */ 275 | {0, 0}, /* US */ 276 | {0x2c, 0}, /* */ 277 | {0x1e, KEY_SHIFT}, /* ! */ 278 | {0x1f, KEY_SHIFT}, /* " */ 279 | {0x32, 0}, /* # */ 280 | {0x21, KEY_SHIFT}, /* $ */ 281 | {0x22, KEY_SHIFT}, /* % */ 282 | {0x24, KEY_SHIFT}, /* & */ 283 | {0x34, 0}, /* ' */ 284 | {0x26, KEY_SHIFT}, /* ( */ 285 | {0x27, KEY_SHIFT}, /* ) */ 286 | {0x25, KEY_SHIFT}, /* * */ 287 | {0x2e, KEY_SHIFT}, /* + */ 288 | {0x36, 0}, /* , */ 289 | {0x2d, 0}, /* - */ 290 | {0x37, 0}, /* . */ 291 | {0x38, 0}, /* / */ 292 | {0x27, 0}, /* 0 */ 293 | {0x1e, 0}, /* 1 */ 294 | {0x1f, 0}, /* 2 */ 295 | {0x20, 0}, /* 3 */ 296 | {0x21, 0}, /* 4 */ 297 | {0x22, 0}, /* 5 */ 298 | {0x23, 0}, /* 6 */ 299 | {0x24, 0}, /* 7 */ 300 | {0x25, 0}, /* 8 */ 301 | {0x26, 0}, /* 9 */ 302 | {0x33, KEY_SHIFT}, /* : */ 303 | {0x33, 0}, /* ; */ 304 | {0x36, KEY_SHIFT}, /* < */ 305 | {0x2e, 0}, /* = */ 306 | {0x37, KEY_SHIFT}, /* > */ 307 | {0x38, KEY_SHIFT}, /* ? */ 308 | {0x34, KEY_SHIFT}, /* @ */ 309 | {0x04, KEY_SHIFT}, /* A */ 310 | {0x05, KEY_SHIFT}, /* B */ 311 | {0x06, KEY_SHIFT}, /* C */ 312 | {0x07, KEY_SHIFT}, /* D */ 313 | {0x08, KEY_SHIFT}, /* E */ 314 | {0x09, KEY_SHIFT}, /* F */ 315 | {0x0a, KEY_SHIFT}, /* G */ 316 | {0x0b, KEY_SHIFT}, /* H */ 317 | {0x0c, KEY_SHIFT}, /* I */ 318 | {0x0d, KEY_SHIFT}, /* J */ 319 | {0x0e, KEY_SHIFT}, /* K */ 320 | {0x0f, KEY_SHIFT}, /* L */ 321 | {0x10, KEY_SHIFT}, /* M */ 322 | {0x11, KEY_SHIFT}, /* N */ 323 | {0x12, KEY_SHIFT}, /* O */ 324 | {0x13, KEY_SHIFT}, /* P */ 325 | {0x14, KEY_SHIFT}, /* Q */ 326 | {0x15, KEY_SHIFT}, /* R */ 327 | {0x16, KEY_SHIFT}, /* S */ 328 | {0x17, KEY_SHIFT}, /* T */ 329 | {0x18, KEY_SHIFT}, /* U */ 330 | {0x19, KEY_SHIFT}, /* V */ 331 | {0x1a, KEY_SHIFT}, /* W */ 332 | {0x1b, KEY_SHIFT}, /* X */ 333 | {0x1c, KEY_SHIFT}, /* Y */ 334 | {0x1d, KEY_SHIFT}, /* Z */ 335 | {0x2f, 0}, /* [ */ 336 | {0x64, 0}, /* \ */ 337 | {0x30, 0}, /* ] */ 338 | {0x23, KEY_SHIFT}, /* ^ */ 339 | {0x2d, KEY_SHIFT}, /* _ */ 340 | {0x35, 0}, /* ` */ 341 | {0x04, 0}, /* a */ 342 | {0x05, 0}, /* b */ 343 | {0x06, 0}, /* c */ 344 | {0x07, 0}, /* d */ 345 | {0x08, 0}, /* e */ 346 | {0x09, 0}, /* f */ 347 | {0x0a, 0}, /* g */ 348 | {0x0b, 0}, /* h */ 349 | {0x0c, 0}, /* i */ 350 | {0x0d, 0}, /* j */ 351 | {0x0e, 0}, /* k */ 352 | {0x0f, 0}, /* l */ 353 | {0x10, 0}, /* m */ 354 | {0x11, 0}, /* n */ 355 | {0x12, 0}, /* o */ 356 | {0x13, 0}, /* p */ 357 | {0x14, 0}, /* q */ 358 | {0x15, 0}, /* r */ 359 | {0x16, 0}, /* s */ 360 | {0x17, 0}, /* t */ 361 | {0x18, 0}, /* u */ 362 | {0x19, 0}, /* v */ 363 | {0x1a, 0}, /* w */ 364 | {0x1b, 0}, /* x */ 365 | {0x1c, 0}, /* y */ 366 | {0x1d, 0}, /* z */ 367 | {0x2f, KEY_SHIFT}, /* { */ 368 | {0x64, KEY_SHIFT}, /* | */ 369 | {0x30, KEY_SHIFT}, /* } */ 370 | {0x32, KEY_SHIFT}, /* ~ */ 371 | {0,0}, /* DEL */ 372 | 373 | {0x3a, 0}, /* F1 */ 374 | {0x3b, 0}, /* F2 */ 375 | {0x3c, 0}, /* F3 */ 376 | {0x3d, 0}, /* F4 */ 377 | {0x3e, 0}, /* F5 */ 378 | {0x3f, 0}, /* F6 */ 379 | {0x40, 0}, /* F7 */ 380 | {0x41, 0}, /* F8 */ 381 | {0x42, 0}, /* F9 */ 382 | {0x43, 0}, /* F10 */ 383 | {0x44, 0}, /* F11 */ 384 | {0x45, 0}, /* F12 */ 385 | 386 | {0x46, 0}, /* PRINT_SCREEN */ 387 | {0x47, 0}, /* SCROLL_LOCK */ 388 | {0x39, 0}, /* CAPS_LOCK */ 389 | {0x53, 0}, /* NUM_LOCK */ 390 | {0x49, 0}, /* INSERT */ 391 | {0x4a, 0}, /* HOME */ 392 | {0x4b, 0}, /* PAGE_UP */ 393 | {0x4e, 0}, /* PAGE_DOWN */ 394 | 395 | {0x4f, 0}, /* RIGHT_ARROW */ 396 | {0x50, 0}, /* LEFT_ARROW */ 397 | {0x51, 0}, /* DOWN_ARROW */ 398 | {0x52, 0}, /* UP_ARROW */ 399 | }; 400 | #endif 401 | 402 | #endif 403 | 404 | -------------------------------------------------------------------------------- /doc/img/Linux-HOGP.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 42 | 44 | 50 | 56 | 57 | 60 | 64 | 65 | 68 | 72 | 73 | 79 | 85 | 86 | 94 | 102 | 106 | 110 | 111 | 114 | 118 | 119 | 123 | 127 | 128 | 132 | 136 | 137 | 143 | 149 | 150 | 151 | 153 | 154 | 156 | image/svg+xml 157 | 159 | 160 | 161 | 162 | 163 | 166 | 168 | 175 | UHID 185 | 186 | 193 | 196 | 198 | 205 | BT-HIDP 215 | 216 | 217 | 220 | 222 | 229 | USB-HID 239 | 240 | 241 | 244 | 251 | HID Core 261 | 262 | 267 | 272 | 277 | 280 | 287 | I2C-HID 297 | 298 | 303 | 306 | 313 | USB 323 | 324 | 328 | 335 | I2C 345 | 346 | 350 | 357 | I/O Driver 367 | 368 | 373 | 378 | 381 | 383 | 386 | 393 | BT 403 | 404 | 405 | 406 | 411 | 416 | BT classicwith HID Profile 429 | BT LEwith HID-over-GATT Profile 442 | 445 | 448 | 455 | BT daemon 465 | 470 | Kernel 480 | User 490 | 495 | 496 | 497 | 504 | HID Driver 514 | 519 | hidraw, hid-input, ... 530 | Transport Driver 540 | 541 | 542 | --------------------------------------------------------------------------------