├── LICENSE ├── HIDRemote.h ├── hid_remote.ino ├── README.md └── HIDRemote.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 drdnar 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /HIDRemote.h: -------------------------------------------------------------------------------- 1 | #ifndef HID_REMOTE_H 2 | #define HID_REMOTE_H 3 | 4 | #include "HID.h" 5 | 6 | #define HID_REMOTE_PLAY 1 7 | #define HID_REMOTE_PAUSE 2 8 | #define HID_REMOTE_RECORD 4 9 | #define HID_REMOTE_FAST_FORWARD 8 10 | #define HID_REMOTE_REWIND 0x10 11 | #define HID_REMOTE_NEXT 0x20 12 | #define HID_REMOTE_PREVIOUS 0x40 13 | #define HID_REMOTE_STOP 0x80 14 | #define HID_REMOTE_EJECT 0x100 15 | #define HID_REMOTE_RANDOM 0x200 16 | #define HID_REMOTE_VOLUME_UP 0x400 17 | #define HID_REMOTE_VOLUME_DOWN 0x800 18 | #define HID_REMOTE_SLEEP 0x1000 19 | #define HID_REMOTE_REPEAT 0x2000 20 | #define HID_REMOTE_PLAY_PAUSE 0x4000 21 | #define HID_REMOTE_MUTE 0x8000 22 | 23 | #define REMOTE_REPORT_LENGTH 2 24 | 25 | typedef struct { 26 | uint8_t keys[REMOTE_REPORT_LENGTH]; 27 | } RemoteReport; 28 | 29 | class HIDRemote_ 30 | { 31 | private: 32 | RemoteReport report; 33 | void sendReport(RemoteReport* report); 34 | void sendReport(void); 35 | public: 36 | HIDRemote_(void); 37 | void begin(void); 38 | void end(void); 39 | void press(uint16_t key); 40 | void release(uint16_t key); 41 | void pressAndRelease(uint16_t key); 42 | 43 | // Send an empty report to prevent repeated actions 44 | void releaseAll(void); 45 | }; 46 | extern HIDRemote_ HIDRemote; 47 | 48 | #endif // HID_REMOTE_H 49 | -------------------------------------------------------------------------------- /hid_remote.ino: -------------------------------------------------------------------------------- 1 | #include "HIDRemote.h" 2 | 3 | #define ROWS 3 4 | #define COLUMNS 3 5 | #define DEBOUNCE_TIME 5 6 | 7 | // Rows, used as drivers 8 | const uint8_t rows[] = { 5, 3, 2 }; 9 | // Columns, used for sense 10 | const uint8_t columns[] = { 7, 4, 6 }; 11 | 12 | /* Layout of keyboard matrix 13 | * C1 C2 C3 14 | * R1 Prev Next Vol up 15 | * R2 Stop Play/Ps Vol down 16 | * R3 RR FF Mute 17 | */ 18 | const uint16_t key_codes[3][3] = { 19 | { HID_REMOTE_PREVIOUS, HID_REMOTE_NEXT, HID_REMOTE_VOLUME_UP }, 20 | { HID_REMOTE_STOP, HID_REMOTE_PLAY_PAUSE, HID_REMOTE_VOLUME_DOWN }, 21 | { HID_REMOTE_REWIND, HID_REMOTE_FAST_FORWARD, HID_REMOTE_MUTE } 22 | }; 23 | 24 | uint8_t key_states[ROWS][COLUMNS] = { 25 | {0, 0, 0}, 26 | {0, 0, 0}, 27 | {0, 0, 0}, 28 | }; 29 | 30 | void idle_key_matrix() { 31 | for (uint8_t i = 0; i < ROWS; i++) 32 | digitalWrite(rows[i], LOW); 33 | } 34 | 35 | void reset_key_matrix() { 36 | for (uint8_t i = 0; i < ROWS; i++) 37 | digitalWrite(rows[i], HIGH); 38 | } 39 | 40 | bool check_key_matrix() { 41 | return digitalRead(columns[0]) && digitalRead(columns[1]) && digitalRead(columns[2]); 42 | } 43 | 44 | void setup() { 45 | HIDRemote.begin(); 46 | for (uint8_t i = 0; i < ROWS; i++) 47 | { 48 | pinMode(rows[i], OUTPUT); 49 | digitalWrite(rows[i], HIGH); 50 | } 51 | // Need to use a pull-up or pull-down to ensure 52 | // an undriven pin has a defined state. 53 | // Could use external pull-down resistors, 54 | // but using the internal pull-ups is less soldering. 55 | for (uint8_t i = 0; i < COLUMNS; i++) 56 | pinMode(columns[i], INPUT_PULLUP); 57 | } 58 | 59 | void loop() { 60 | for (uint8_t i = 0; i < ROWS; i++) 61 | { 62 | digitalWrite(rows[i], LOW); 63 | // Wait for a moment to negate any possible capacitive effects. 64 | delay(1); 65 | for (uint8_t j = 0; j < COLUMNS; j++) 66 | { 67 | bool key_state = digitalRead(columns[j]); 68 | // Recall that since we're using the internal pull-ups, 69 | // 0 means PRESSED and 1 means NOT pressed. 70 | if (key_state) 71 | { 72 | // Not pressed, so check debouncing countdown timer 73 | if (key_states[i][j]) 74 | { 75 | key_states[i][j]--; 76 | if (!key_states[i][j]) 77 | HIDRemote.release(key_codes[i][j]); 78 | } 79 | } 80 | else 81 | { 82 | // Pressed 83 | if (!key_states[i][j]) 84 | HIDRemote.press(key_codes[i][j]); 85 | // As long as button is pressed, reset countdown timer 86 | // so it doesn't decrement until button is released. 87 | key_states[i][j] = DEBOUNCE_TIME; 88 | } 89 | } 90 | digitalWrite(rows[i], HIGH); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino-HID-Remote 2 | The Arduino library already has support for acting as keyboard or mouse, but not media control buttons like play/pause and volume control. This Arduino skectch is about the simplest way to add such functionality. (A USB-capable Arduino like the Leonardo is obviously required.) Other libraries provide more flexibility, but are also more complicated to use and may generate larger code. The file `hid_remote.ino` contains a fully working example, using a simple keyboard matrix. (The random-seeming row and column pin assignments are just because that's what routing I happened to use on my board.) 3 | 4 | Due to the way the USB HID specifies how multimedia buttons works, support for them can't be trivially incorporated into a keyboard device. Instead, you have to add a new report and descriptor. Fortunately, at some point the Arduino USB library got rewritten to make doing that quite easy. 5 | 6 | This code is inspired by [this Stefan Jones blog post.](https://www.stefanjones.ca/blog/arduino-leonardo-remote-multimedia-keys/) However, his example predates the rewrite of the Arduino USB HID API, so I had to modify it to work with the new API. Ultimately, I ended up pretty much rewriting all the code completely. Even my descriptor is completely different. 7 | 8 | ### Usage 9 | 10 | Add the `HIDRemote.h` and `HIDRemote.cpp` files to you sketch folder and place `#include "HIDRemote.h"` at the top of your sketch. 11 | 12 | In your `setup()` routine, add `HIDRemote.begin();` at some point. 13 | 14 | The actual API mirrors the usage of the `Keyboard` library. There are `HIDRemote.press()` and `HIDRemote.release()` routines to simulate pressing and releasing keys, as well as `HIDRemote.releaseAll()` and `HIDRemote.pressAndRelease()`. 15 | 16 | The following constants can be passed to `press` or `release`. Some or all of these may have no effect on your particular operating system or application. **Note that they are 16-bit** i.e. use `uint16_t`. 17 | 18 | * `HID_REMOTE_PLAY` Play button; if media is already playing, should have no effect. 19 | * `HID_REMOTE_PAUSE` Pause button; if no media is playing, should have no effect. 20 | * `HID_REMOTE_RECORD` Record key, to start recording 21 | * `HID_REMOTE_FAST_FORWARD` Fast-forward 22 | * `HID_REMOTE_REWIND` Rewind 23 | * `HID_REMOTE_NEXT` Skip to next track 24 | * `HID_REMOTE_PREVIOUS` Skip to previous track 25 | * `HID_REMOTE_STOP` Stop media playback 26 | * `HID_REMOTE_EJECT` Eject media 27 | * `HID_REMOTE_RANDOM` Select randomized track playback order 28 | * `HID_REMOTE_VOLUME_UP` Increment volumne 29 | * `HID_REMOTE_VOLUME_DOWN` Decrement volumne 30 | * `HID_REMOTE_SLEEP` Enter sleep mode 31 | * `HID_REMOTE_REPEAT` Activate track or playlist repeating 32 | * `HID_REMOTE_PLAY_PAUSE` Play/pause button; if media playing, pause playback; if media is paused, resume playback 33 | * `HID_REMOTE_MUTE` Mute/unmute volumne 34 | 35 | You can change what buttons are available by changing the descriptor in `HIDRemote.cpp`. Remember to change the constants in `HIDRemote.h` to match. 36 | -------------------------------------------------------------------------------- /HIDRemote.cpp: -------------------------------------------------------------------------------- 1 | #include "HIDRemote.h" 2 | 3 | static const uint8_t _hidRemoteReportDescriptor[] PROGMEM = 4 | { 5 | /* These usage ID's come from the USB HID Usage Tables Consumer Page (0x0C) 6 | * See USB HID Usage Tables Version 1.12 chapter 15 Consumer Page (page 77) 7 | * for additional values you could specify. */ 8 | 0x05, 0x0c, // Usage Page (Consumer Devices) 9 | 0x09, 0x01, // Usage (Consumer Control) 10 | 0xa1, 0x01, // Collection (Application) 11 | 0x85, 0x04, // REPORT_ID (4) 12 | 0x15, 0x00, // Logical Minimum (0) 13 | 0x25, 0x01, // Logical Maximum (1) 14 | 15 | 0x19, 0xb0, // Usage Minimum (Play) 16 | 0x29, 0xb9, // Usage Maximum (Random Play) 17 | 0x75, 0x01, // Report Size (1) 18 | 0x95, 0x0A, // Report Count (10) 19 | 0x81, 0x06, // Input (Data, Variable, Relative) 20 | 21 | 0x09, 0xe9, // Usage (Volume Up) 22 | 0x09, 0xea, // Usage (Volume Down) 23 | 0x75, 0x01, // Report Size (1) 24 | 0x95, 0x02, // Report Count (2) 25 | 0x81, 0x06, // Input (Data, Variable, Relative) 26 | 27 | 0x09, 0x32, // Usage (Sleep) 28 | 0x95, 0x01, // Report Count (1) 29 | 0x81, 0x06, // Input (Data, Variable, Relative) 30 | 31 | 0x09, 0xbc, // Usage (Repeat) 32 | 0x95, 0x01, // Report Count (1) 33 | 0x81, 0x06, // Input (Data, Variable, Relative) 34 | 35 | 0x09, 0xcd, // Usage (Play/Pause) 36 | 0x95, 0x01, // Report Count (1) 37 | 0x81, 0x06, // Input (Data, Variable, Relative) 38 | 39 | 0x09, 0xe2, // Usage (Mute) 40 | 0x95, 0x01, // Report Count (1) 41 | 0x81, 0x06, // Input (Data, Variable, Relative) 42 | 43 | /* If you have a number of bits that isn't a multiple of eight, you need to add 44 | * padding. 45 | * Be careful when adding extra fields to the descriptor, there seems to be a 46 | * length limit somewhere after 77 bytes. */ 47 | /*0x95, 0x01, // Report Count (1) Number of bits remaining in byte 48 | 0x81, 0x07, // Input (Constant, Variable, Relative) */ 49 | 0xc0, // End Collection 50 | }; 51 | 52 | HIDRemote_::HIDRemote_(void) 53 | { 54 | static HIDSubDescriptor node(_hidRemoteReportDescriptor, sizeof(_hidRemoteReportDescriptor)); 55 | HID().AppendDescriptor(&node); 56 | } 57 | 58 | void HIDRemote_::begin(void) 59 | { 60 | report.keys[0] = 0; 61 | report.keys[1] = 0; 62 | } 63 | 64 | void HIDRemote_::end(void) 65 | { 66 | } 67 | 68 | void HIDRemote_::sendReport(RemoteReport* r) 69 | { 70 | HID().SendReport(4, r, sizeof(RemoteReport)); 71 | } 72 | 73 | void HIDRemote_::sendReport() 74 | { 75 | HID().SendReport(4, &report, sizeof(RemoteReport)); 76 | } 77 | 78 | void HIDRemote_::pressAndRelease(uint16_t key) 79 | { 80 | press(key); 81 | release(key); 82 | } 83 | 84 | void HIDRemote_::press(uint16_t key) 85 | { 86 | report.keys[0] |= key; 87 | report.keys[1] |= (key >> 8); 88 | sendReport(); 89 | } 90 | 91 | void HIDRemote_::release(uint16_t key) 92 | { 93 | report.keys[0] &= ~key; 94 | report.keys[1] &= ~(key >> 8); 95 | sendReport(); 96 | } 97 | 98 | void HIDRemote_::releaseAll(void) 99 | { 100 | report.keys[0] = 0; 101 | report.keys[1] = 0; 102 | sendReport(); 103 | } 104 | 105 | HIDRemote_ HIDRemote; 106 | --------------------------------------------------------------------------------