├── KeyboardParser.h ├── BTHID.ino ├── KeyboardReporter.h ├── Readme.md └── KeyboardReporter.cpp /KeyboardParser.h: -------------------------------------------------------------------------------- 1 | #ifndef __kbdrptparser_h_ 2 | #define __kbdrptparser_h_ 3 | 4 | #include 5 | #include "KeyboardReporter.h" 6 | 7 | // comment if you're not using apple magic keyboard. 8 | #define APPLE_MAGIC_KBD 9 | 10 | 11 | class KbdRptParser : public HIDReportParser 12 | { 13 | public: 14 | virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf); 15 | }; 16 | 17 | bool ledOn = false; 18 | 19 | void KbdRptParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) 20 | { 21 | #ifdef APPLE_MAGIC_KBD 22 | // for apple magic keyboard, should also send report id (one byte before current *buf). 23 | buf -= 1; 24 | len += 1; 25 | #endif 26 | 27 | for (int i = 0; i < len; i ++) { 28 | Serial.print(buf[i]); 29 | Serial.print(" "); 30 | } 31 | Serial.print("\r\n"); 32 | 33 | ledOn = !ledOn; 34 | if (ledOn) TXLED1; 35 | else TXLED0; 36 | 37 | KeyboardReporter.sendReport(buf, len); 38 | }; 39 | 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /BTHID.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "KeyboardParser.h" 4 | 5 | // Satisfy the IDE, which needs to see the include statment in the ino too. 6 | #ifdef dobogusinclude 7 | #include 8 | #endif 9 | #include 10 | 11 | USB Usb; 12 | // USBHub Hub1(&Usb); // Some dongles have a hub inside 13 | BTD Btd(&Usb); // You have to create the Bluetooth Dongle instance like so 14 | 15 | /* You can create the instance of the class in two ways */ 16 | // This will start an inquiry and then pair with your device - you only have to do this once 17 | // If you are using a Bluetooth keyboard, then you should type in the password on the keypad and then press enter 18 | 19 | BTHID bthid(&Btd, PAIR, "0000"); 20 | 21 | // After that you can simply create the instance like so and then press any button on the device 22 | // BTHID bthid(&Btd); 23 | 24 | KbdRptParser keyboardPrs; 25 | 26 | void setup() { 27 | Serial.begin(115200); 28 | TXLED1; 29 | 30 | if (Usb.Init() == -1) { 31 | Serial.print(F("\r\nOSC did not start")); 32 | TXLED0; 33 | while (1); // Halt 34 | } 35 | 36 | bthid.SetReportParser(KEYBOARD_PARSER_ID, &keyboardPrs); 37 | 38 | // If "Boot Protocol Mode" does not work, then try "Report Protocol Mode" 39 | // If that does not work either, then uncomment PRINTREPORT in BTHID.cpp to see the raw report 40 | // bthid.setProtocolMode(USB_HID_BOOT_PROTOCOL); // Boot Protocol Mode 41 | bthid.setProtocolMode(HID_RPT_PROTOCOL); // Report Protocol Mode 42 | 43 | Serial.print(F("\r\nHID Bluetooth Library Started")); 44 | TXLED1; 45 | } 46 | void loop() { 47 | Usb.Task(); 48 | } 49 | -------------------------------------------------------------------------------- /KeyboardReporter.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014-2015 NicoHood 3 | See the readme for credit to other people. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | */ 23 | 24 | // Include guard 25 | #pragma once 26 | 27 | #include 28 | #include "HID.h" 29 | #include "HID-Settings.h" 30 | #include "HID-APIs/DefaultKeyboardAPI.h" 31 | 32 | 33 | class KeyboardReporter_ : public PluggableUSBModule, public DefaultKeyboardAPI 34 | { 35 | public: 36 | KeyboardReporter_(void); 37 | uint8_t getLeds(void); 38 | uint8_t getProtocol(void); 39 | void wakeupHost(void); 40 | 41 | void setFeatureReport(void* report, int length){ 42 | if(length > 0){ 43 | featureReport = (uint8_t*)report; 44 | featureLength = length; 45 | 46 | // Disable feature report by default 47 | disableFeatureReport(); 48 | } 49 | } 50 | 51 | int availableFeatureReport(void){ 52 | if(featureLength < 0){ 53 | return featureLength & ~0x8000; 54 | } 55 | return 0; 56 | } 57 | 58 | void enableFeatureReport(void){ 59 | featureLength &= ~0x8000; 60 | } 61 | 62 | void disableFeatureReport(void){ 63 | featureLength |= 0x8000; 64 | } 65 | 66 | virtual int send(void) final; 67 | virtual int sendReport(uint8_t *buf, size_t len) final; 68 | 69 | protected: 70 | // Implementation of the PUSBListNode 71 | int getInterface(uint8_t* interfaceCount); 72 | int getDescriptor(USBSetup& setup); 73 | bool setup(USBSetup& setup); 74 | 75 | EPTYPE_DESCRIPTOR_SIZE epType[1]; 76 | uint8_t protocol; 77 | uint8_t idle; 78 | 79 | uint8_t leds; 80 | 81 | uint8_t* featureReport; 82 | int featureLength; 83 | }; 84 | extern KeyboardReporter_ KeyboardReporter; 85 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Arduino Bluetooth HID Proxy 2 | 3 | Bridge Bluetooth keyboard to wired USB. (i.e. Bluetooth HID Proxy Mode). 4 | Works like a Logitech Unifying Receiver but for bluetooth devices. 5 | 6 |

7 | 8 | 9 | (Image idea from pihidproxy) 10 |

11 | 12 | > [!NOTE] 13 | > If you are looking for a project that turns a wired keyboard into a wireless keyboard. 14 | This is not the right project. This project does the reverse. 15 | 16 | It can be useful in two scenarios: 17 | 18 | 1. You have a bluetooth keyboard, but it doesn't work in BIOS, Bootloader or TV. 19 | 2. You have a bluetooth keyboard and you want turn it into a programmable keyboard (with [TMK USB to USB Converter](https://geekhack.org/index.php?topic=69169.0)) 20 | 21 | Previously, I used MikerR's [pihidproxy](https://github.com/mikerr/pihidproxy) but it's not transparency enough and I don't like the start-up time of RPi. 22 | So I made this one with Arduino. 23 | 24 | # Guide 25 | 26 | ## Hardware 27 | 28 | ### Shopping List 29 | - [SparkFun Micro Pro 3.3V](https://www.sparkfun.com/products/12587) (Other ATmega32U4 based board should also work) 30 | - [USB Host Shield for Arduino Pro Mini](https://shop.tkjelectronics.dk/product_info.php?products_id=45) 31 | (Out of stock due to COVID, 32 | I'm using [this](https://www.amazon.com/HiLetgo-Development-Compatible-Interface-Arduino/dp/B01EWW9R1E) cheaper clone) 33 | - USB Bluetooth dongle 34 | (should [supported](https://github.com/felis/USB_Host_Shield_2.0/wiki/Bluetooth-dongles) by USB Host Library 2.0, 35 | I've tested [this](https://www.amazon.com/gp/product/B07G9TSDCG/) and [this](https://www.amazon.com/gp/product/B007MKMJGO/)) 36 | - a Micro-USB to USB Cable, Soldering Iron Kit, Electronic Wire, Male Header Pin 37 | 38 | ### Assembling 39 | 40 | Follow [this guide](https://geekhack.org/index.php?topic=80421.0) to connect Micro Pro and USB Host Shield. Or tl;dr: 41 | 1. Cut the line between VBUS and 2k2 to get 5.0V 42 | 2. Two Jump wires on VCC and RST, VBUS and RAW 43 | 3. Put Micro Pro on USB Host shield, align, and connect: (Top) INT, GND, (Bottom) SS, MOSI, MISO, SCK, VCC, GND, RAW 44 | 45 | ## Programming 46 | 47 | > [!NOTE] 48 | > if you are NOT using an Apple Magic Keyboard, make sure to finish step 5 49 | 50 | 1. Open in Arduino IDE 51 | 2. In `Tools -> Manage Libraries`, search and install `USB Host Shield Library 2.0`, `HID-Project` 52 | 3. Let's test if USB Host shield is working: `File -> Examples -> USB Host Shield Library 2.0 -> board_qc` 53 | 1. Upload 54 | 2. `Tools -> Serial Monitor` 55 | 3. There'll be a GPIO error, ignore it and send any key in the serial monitor. 56 | 4. Wait until `All tests passed.` 57 | 5. [This article](https://joshcaplin.wordpress.com/tag/board_qc/) could be helpful if you meet any issue. 58 | 4. `git clone https://github.com/houkanshan/arduino-bt-hid-proxy.git` 59 | 5. In `KeyboardParser.h`, comment `#define APPLE_MAGIC_KBD` (adding `//` to the beginning) if you're not using an Apple Magic Keyboard. 60 | 61 | ## Uploading for the first pairing 62 | 63 | 1. Upload the project, open `Tools -> Serial Monitor` 64 | 2. Turn on your bluetooth device and start pairing 65 | 3. Wait for bluetooth scanning 66 | 4. Follow the guide in the Serial Monitor and finish the pairing 67 | 5. Now the paired address is remembered by arduino. 68 | 69 | ## Uploading for paired 70 | 71 | The current code will try to re-pair every time after power on. Let's stop it. 72 | 73 | 1. In `BTHID.ino`, comment `BTHID bthid(&Btd, PAIR, "0000");` and uncomment `BTHID bthid(&Btd);` 74 | 2. Upload 75 | 3. Press any button on the device, then it should be connected. 76 | 77 | ## Debug 78 | 79 | ### USB Host Library 80 | 81 | 1. In `Documents/Arduino/libraries/USB_Host_Shield_2.0/settings.h`, change `#define ENABLE_UHS_DEBUGGING 0` to `1`. 82 | It will display detailed log in serial monitor 83 | 2. For more detailed log, uncomment `#define EXTRADEBUG` in `*.cpp` file you want to inspect. 84 | 85 | # Thanks to 86 | 87 | - [mikerr/pihidproxy](https://github.com/mikerr/pihidproxy) 88 | - [felis/USB_Host_Shield_2.0](https://github.com/felis/USB_Host_Shield_2.0) 89 | - [NicoHood/HID](https://github.com/NicoHood/HID) 90 | - [TMK USB to USB Converter](https://geekhack.org/index.php?topic=69169.0) 91 | - [usb_usb converter and Apple Magic keyboard](https://github.com/tmk/tmk_keyboard/issues/606) 92 | -------------------------------------------------------------------------------- /KeyboardReporter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2014-2015 NicoHood 3 | See the readme for credit to other people. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | */ 23 | 24 | #include "KeyboardReporter.h" 25 | 26 | 27 | static const uint8_t _hidReportDescriptorKeyboard__[] PROGMEM = { 28 | // Keyboard 29 | 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) 47 */ 30 | 0x09, 0x06, /* USAGE (Keyboard) */ 31 | 0xa1, 0x01, /* COLLECTION (Application) */ 32 | 0x05, 0x07, /* USAGE_PAGE (Keyboard) */ 33 | 34 | /* Keyboard Modifiers (shift, alt, ...) */ 35 | 0x19, 0xe0, /* USAGE_MINIMUM (Keyboard LeftControl) */ 36 | 0x29, 0xe7, /* USAGE_MAXIMUM (Keyboard Right GUI) */ 37 | 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ 38 | 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ 39 | 0x75, 0x01, /* REPORT_SIZE (1) */ 40 | 0x95, 0x08, /* REPORT_COUNT (8) */ 41 | 0x81, 0x02, /* INPUT (Data,Var,Abs) */ 42 | 43 | /* Reserved byte, used for consumer reports, only works with linux */ 44 | 0x05, 0x0C, /* Usage Page (Consumer) */ 45 | 0x95, 0x01, /* REPORT_COUNT (1) */ 46 | 0x75, 0x08, /* REPORT_SIZE (8) */ 47 | 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ 48 | 0x26, 0xFF, 0x00, /* LOGICAL_MAXIMUM (255) */ 49 | 0x19, 0x00, /* USAGE_MINIMUM (0) */ 50 | 0x29, 0xFF, /* USAGE_MAXIMUM (255) */ 51 | 0x81, 0x00, /* INPUT (Data,Ary,Abs) */ 52 | 53 | /* 5 LEDs for num lock etc, 3 left for advanced, custom usage */ 54 | 0x05, 0x08, /* USAGE_PAGE (LEDs) */ 55 | 0x19, 0x01, /* USAGE_MINIMUM (Num Lock) */ 56 | 0x29, 0x08, /* USAGE_MAXIMUM (Kana + 3 custom)*/ 57 | 0x95, 0x08, /* REPORT_COUNT (8) */ 58 | 0x75, 0x01, /* REPORT_SIZE (1) */ 59 | 0x91, 0x02, /* OUTPUT (Data,Var,Abs) */ 60 | 61 | /* 6 Keyboard keys */ 62 | 0x05, 0x07, /* USAGE_PAGE (Keyboard) */ 63 | 0x95, 0x06, /* REPORT_COUNT (6) */ 64 | 0x75, 0x08, /* REPORT_SIZE (8) */ 65 | 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ 66 | 0x26, 0xE7, 0x00, /* LOGICAL_MAXIMUM (231) */ 67 | 0x19, 0x00, /* USAGE_MINIMUM (Reserved (no event indicated)) */ 68 | 0x29, 0xE7, /* USAGE_MAXIMUM (Keyboard Right GUI) */ 69 | 0x81, 0x00, /* INPUT (Data,Ary,Abs) */ 70 | 71 | /* End */ 72 | 0xc0 /* END_COLLECTION */ 73 | }; 74 | 75 | static const uint8_t _hidReportDescriptorKeyboard[] PROGMEM = { 76 | 0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x07, 0x15, 0x00, 77 | 0x25, 0x01, 0x19, 0xe0, 0x29, 0xe7, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 78 | 0x95, 0x05, 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x91, 0x02, 79 | 0x95, 0x01, 0x75, 0x03, 0x91, 0x03, 0x95, 0x08, 0x75, 0x01, 0x15, 0x00, 80 | 0x25, 0x01, 0x06, 0x00, 0xff, 0x09, 0x03, 0x81, 0x03, 0x95, 0x06, 0x75, 81 | 0x08, 0x15, 0x00, 0x25, 0x65, 0x05, 0x07, 0x19, 0x00, 0x29, 0x65, 0x81, 82 | 0x00, 0x95, 0x01, 0x75, 0x01, 0x15, 0x00, 0x25, 0x01, 0x05, 0x0c, 0x09, 83 | 0xb8, 0x81, 0x02, 0x95, 0x01, 0x75, 0x01, 0x06, 0x00, 0xff, 0x09, 0x03, 84 | 0x81, 0x02, 0x95, 0x01, 0x75, 0x06, 0x81, 0x03, 0x06, 0x02, 0xff, 0x09, 85 | 0x55, 0x85, 0x55, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x40, 86 | 0xb1, 0xa2, 0xc0, 0x06, 0x00, 0xff, 0x09, 0x14, 0xa1, 0x01, 0x85, 0x90, 87 | 0x05, 0x84, 0x75, 0x01, 0x95, 0x03, 0x15, 0x00, 0x25, 0x01, 0x09, 0x61, 88 | 0x05, 0x85, 0x09, 0x44, 0x09, 0x46, 0x81, 0x02, 0x95, 0x05, 0x81, 0x01, 89 | 0x75, 0x08, 0x95, 0x01, 0x15, 0x00, 0x26, 0xff, 0x00, 0x09, 0x65, 0x81, 90 | 0x02, 0xc0 // apple magic keyboard descriper. 91 | }; 92 | 93 | KeyboardReporter_::KeyboardReporter_(void) : PluggableUSBModule(1, 1, epType), protocol(HID_REPORT_PROTOCOL), idle(1), leds(0), featureReport(NULL), featureLength(0) 94 | { 95 | epType[0] = EP_TYPE_INTERRUPT_IN; 96 | PluggableUSB().plug(this); 97 | } 98 | 99 | int KeyboardReporter_::getInterface(uint8_t* interfaceCount) 100 | { 101 | *interfaceCount += 1; // uses 1 102 | HIDDescriptor hidInterface = { 103 | D_INTERFACE(pluggedInterface, 1, USB_DEVICE_CLASS_HUMAN_INTERFACE, HID_SUBCLASS_BOOT_INTERFACE, HID_PROTOCOL_KEYBOARD), 104 | D_HIDREPORT(sizeof(_hidReportDescriptorKeyboard)), 105 | D_ENDPOINT(USB_ENDPOINT_IN(pluggedEndpoint), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x01) 106 | }; 107 | return USB_SendControl(0, &hidInterface, sizeof(hidInterface)); 108 | } 109 | 110 | int KeyboardReporter_::getDescriptor(USBSetup& setup) 111 | { 112 | // In a HID Class Descriptor wIndex cointains the interface number 113 | if (setup.wIndex != pluggedInterface) { return 0; } 114 | 115 | // Check if this is a HID Class Descriptor request 116 | if (setup.bmRequestType != REQUEST_DEVICETOHOST_STANDARD_INTERFACE) { return 0; } 117 | 118 | if (setup.wValueH == HID_HID_DESCRIPTOR_TYPE) { 119 | // Apple UEFI and USBCV wants it 120 | HIDDescDescriptor desc = D_HIDREPORT(sizeof(_hidReportDescriptorKeyboard)); 121 | return USB_SendControl(0, &desc, sizeof(desc)); 122 | } else if (setup.wValueH == HID_REPORT_DESCRIPTOR_TYPE) { 123 | // Reset the protocol on reenumeration. Normally the host should not assume the state of the protocol 124 | // due to the USB specs, but Windows and Linux just assumes its in report mode. 125 | protocol = HID_REPORT_PROTOCOL; 126 | return USB_SendControl(TRANSFER_PGM, _hidReportDescriptorKeyboard, sizeof(_hidReportDescriptorKeyboard)); 127 | } 128 | 129 | return 0; 130 | } 131 | 132 | bool KeyboardReporter_::setup(USBSetup& setup) 133 | { 134 | if (pluggedInterface != setup.wIndex) { 135 | return false; 136 | } 137 | 138 | uint8_t request = setup.bRequest; 139 | uint8_t requestType = setup.bmRequestType; 140 | 141 | if (requestType == REQUEST_DEVICETOHOST_CLASS_INTERFACE) 142 | { 143 | if (request == HID_GET_REPORT) { 144 | // TODO: HID_GetReport(); 145 | return true; 146 | } 147 | if (request == HID_GET_PROTOCOL) { 148 | // TODO improve 149 | #ifdef __AVR__ 150 | UEDATX = protocol; 151 | #endif 152 | return true; 153 | } 154 | if (request == HID_GET_IDLE) { 155 | // TODO improve 156 | #ifdef __AVR__ 157 | UEDATX = idle; 158 | #endif 159 | return true; 160 | } 161 | } 162 | 163 | if (requestType == REQUEST_HOSTTODEVICE_CLASS_INTERFACE) 164 | { 165 | if (request == HID_SET_PROTOCOL) { 166 | protocol = setup.wValueL; 167 | return true; 168 | } 169 | if (request == HID_SET_IDLE) { 170 | idle = setup.wValueH; 171 | return true; 172 | } 173 | if (request == HID_SET_REPORT) 174 | { 175 | // Check if data has the correct length afterwards 176 | int length = setup.wLength; 177 | 178 | // Feature (set feature report) 179 | if(setup.wValueH == HID_REPORT_TYPE_FEATURE){ 180 | // No need to check for negative featureLength values, 181 | // except the host tries to send more then 32k bytes. 182 | // We dont have that much ram anyways. 183 | if (length == featureLength) { 184 | USB_RecvControl(featureReport, featureLength); 185 | 186 | // Block until data is read (make length negative) 187 | disableFeatureReport(); 188 | return true; 189 | } 190 | // TODO fake clear data? 191 | } 192 | 193 | // Output (set led states) 194 | else if(setup.wValueH == HID_REPORT_TYPE_OUTPUT){ 195 | if(length == sizeof(leds)){ 196 | USB_RecvControl(&leds, length); 197 | return true; 198 | } 199 | } 200 | 201 | // Input (set HID report) 202 | else if(setup.wValueH == HID_REPORT_TYPE_INPUT) 203 | { 204 | if(length == sizeof(_keyReport)){ 205 | USB_RecvControl(&_keyReport, length); 206 | return true; 207 | } 208 | } 209 | } 210 | } 211 | 212 | return false; 213 | } 214 | 215 | uint8_t KeyboardReporter_::getLeds(void){ 216 | return leds; 217 | } 218 | 219 | uint8_t KeyboardReporter_::getProtocol(void){ 220 | return protocol; 221 | } 222 | 223 | int KeyboardReporter_::send(void){ 224 | return USB_Send(pluggedEndpoint | TRANSFER_RELEASE, &_keyReport, sizeof(_keyReport)); 225 | } 226 | 227 | int KeyboardReporter_::sendReport(uint8_t *buf, size_t len) { 228 | return USB_Send(pluggedEndpoint | TRANSFER_RELEASE, buf, len); 229 | } 230 | 231 | void KeyboardReporter_::wakeupHost(void){ 232 | #ifdef __AVR__ 233 | USBDevice.wakeupHost(); 234 | #endif 235 | } 236 | 237 | 238 | KeyboardReporter_ KeyboardReporter; 239 | --------------------------------------------------------------------------------