4 Copyright (c) 2016 Markus Zehnder
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25 /* IR to USB keyboard optimized for KODI openELEC running on Asus Chromebox.
26 * IR device: Logitech Harmony 880 with remote profile: 'Panasonic TV TX-43CXW754'
29 * - Arduino Leonardo & SparkFun Pro Micro, ATmega32u4 (5V, 16MHz)
35 * - IRLremote doesn't work with Logitech Plex Remote profile. Now (ab)using Panasonic TV TX-43CXW754 device template.
36 * - Watch dog reset doesn't work, according to Google this is most likely an Arduino bootloader bug.
37 * - IRLremote doesn't work with MattairTech MT-DB-U4 1.6.9-mt1
39 // --------GLOBAL FLAGS ---------------
40 // enable debugging output over usb
44 // --------INCLUDES ---------------
49 #include <HID-Project.h>
50 #include <IRLremote.h>
52 // either include Panasonic or Sony definitions
53 #include "Panasonic.h"
56 // --------CONSTANTS ---------------
60 #define POWER_SENSE_PIN 8
61 #define POWER_BTN_PIN 9
63 // min 500ms hold time seems to be required (< 500ms was too short for power on)
64 #define POWER_BTN_HOLD_TIME 700
69 KeyboardKeycode keyCode;
72 // Key press time must be slightly higher then the repeat IR code duration.
73 // My Logitech resends the first repeat after 40ms and afterwards every 110ms
74 const int KEY_PRESS_TIME = 150;
76 const int LOOP_DELAY = KEY_PRESS_TIME / 3;
78 // IR code to key mapping
79 const uint8_t KEY_CTRL = 1;
80 const uint8_t KEY_ALT = 2;
81 const uint8_t KEY_SHIFT = 4;
82 const uint8_t KEY_GUI = 8;
84 // Kodi keyboard controls: http://kodi.wiki/view/Keyboard_controls
85 const CodeMap irToKeyMap[] = {
86 {REMOTE_LEFT , 0, KEY_LEFT_ARROW},
87 {REMOTE_RIGHT , 0, KEY_RIGHT_ARROW},
88 {REMOTE_UP , 0, KEY_UP_ARROW},
89 {REMOTE_DOWN , 0, KEY_DOWN_ARROW},
90 {REMOTE_OK , 0, KEY_RETURN},
91 {REMOTE_ENTER , 0, KEY_TAB}, // Fullscreen playback
92 {REMOTE_MENU , 0, KEY_C},
93 {REMOTE_CLEAR , 0, KEY_BACKSPACE},
94 {REMOTE_EXIT , 0, KEY_ESC},
95 {REMOTE_GUIDE , 0, KEY_E},
96 {REMOTE_INFO , 0, KEY_I},
97 {REMOTE_STOP , 0, KEY_X},
98 {REMOTE_PLAY , 0, KEY_P},
99 {REMOTE_PAUSE , 0, KEY_SPACE},
100 {REMOTE_REC , 0, KEY_B},
101 {REMOTE_REW , 0, KEY_R},
102 {REMOTE_FWD , 0, KEY_F},
103 {REMOTE_PREV , 0, KEY_QUOTE}, // FIXME doesn't seem to work with non-us keyboard layout
104 {REMOTE_SKIP , 0, KEY_PERIOD},
105 {REMOTE_REPLAY , 0, KEY_COMMA},
106 {REMOTE_SUBTITLE, 0, KEY_T}, // toggle subtitles
107 {REMOTE_BLUE , 0, KEY_O}, // Codec Info
108 {REMOTE_RED , 0, KEY_W}, // Marked as watched / unwatched
109 {REMOTE_GREEN , 0, KEY_S}, // shutdown / suspend / reboot menu
110 {REMOTE_YELLOW , 0, KEY_DELETE},
111 {REMOTE_1 , 0, KEY_1},
112 {REMOTE_2 , 0, KEY_2},
113 {REMOTE_3 , 0, KEY_3},
114 {REMOTE_4 , 0, KEY_4},
115 {REMOTE_5 , 0, KEY_5},
116 {REMOTE_6 , 0, KEY_6},
117 {REMOTE_7 , 0, KEY_7},
118 {REMOTE_8 , 0, KEY_8},
119 {REMOTE_9 , 0, KEY_9},
120 {REMOTE_0 , 0, KEY_0},
121 {REMOTE_CH_UP , 0, KEY_PAGE_UP}, // PgUp / Skip to next queued video or next chapter if no videos are queued. / Increase Rating
122 {REMOTE_CH_DOWN , 0, KEY_PAGE_DOWN}, // PgDown / Skip to previous queued video or previous chapter if no videos are queued. / Decrease Rating
123 {REMOTE_ASPECT , 0, KEY_Z}, // Zoom/aspect ratio
124 {REMOTE_MUTE , 0, KEY_VOLUME_MUTE},
125 {REMOTE_VOL_UP , 0, KEY_VOLUME_UP},
126 {REMOTE_VOL_DOWN, 0, KEY_VOLUME_DOWN},
127 {REMOTE_F1 , 0, KEY_A}, // Audio delay control
128 {REMOTE_F2 , 0, KEY_D}, // Move item down (Playlist editor & Favorites window)
129 {REMOTE_F3 , 0, KEY_U}, // Move item up (Playlist editor & Favorites window)
130 {REMOTE_F4 , 0, KEY_Q}, // Queue
131 {REMOTE_F5 , 0, KEY_V}, // Teletext / Visualisation settings
132 {REMOTE_F6 , 0, KEY_Y}, // Switch/choose player
133 {REMOTE_F7 , 0, KEY_HOME}, // Jump to the top of the menu
134 {REMOTE_REC , 0, KEY_PRINTSCREEN}, // Screenshot
136 {REMOTE_ARROW_DOWN, KEY_CTRL, KEY_DOWN_ARROW}, // Move subtitles down
137 {REMOTE_ARROW_UP , KEY_CTRL, KEY_UP_ARROW}, // Move subtitles up
138 {REMOTE_F8 , KEY_CTRL, KEY_D}, // boot: ChromeOS TODO test...
139 //{REMOTE_F9 , KEY_CTRL, KEY_W}, // boot: openELEC TODO test...
142 const int IR_KEY_MAP_SIZE = sizeof(irToKeyMap) / sizeof(CodeMap);
144 //------------ VARIABLES ---------------------
146 unsigned long lastIRValue = 0; // previously received IR code value
147 unsigned long timeKeyDown = 0; // time of key press initiation
149 // temporary variables to save latest IR input
150 uint8_t IRProtocol = 0;
151 uint16_t IRAddress = 0;
152 uint32_t IRCommand = 0;
154 //========================================
157 // open debug console
160 Serial.begin(115200); // afaik the baudrate is ignored on a 32u4
162 DEBUG_PRINTLN("Chromebox_REMOTE_USB_Keyboard");
164 pinMode(POWER_SENSE_PIN, INPUT);
165 pinMode(POWER_BTN_PIN, OUTPUT);
167 // initialize control over the keyboard:
168 BootKeyboard.begin();
170 // Start the receiver
171 attachInterrupt(digitalPinToInterrupt(RECV_PIN), IRLinterrupt<irType>, CHANGE);
179 //========================================
183 //Test if watchdog interrupt enabled
184 // http://forum.arduino.cc/index.php?topic=295345.msg2628807#msg2628807
185 if (WDTCSR & (1<<WDIE)) {
186 //Prolong watchdog timer
189 //No interrupt enabled - Test if watchdog reset enabled
190 else if (WDTCSR & (1<<WDE)) {
191 //Bootloader about to reset - do not prolong watchdog
193 //It has ben disabled - Enable and prolong
195 //Watchdog disabled - Enable again
200 // temporary disable interrupts and print newest input
201 // TODO read up on interrup handling
202 uint8_t oldSREG = SREG;
206 DEBUG_PRINT(IRProtocol);DEBUG_PRINT("/0x");DEBUG_PRINT(IRAddress, HEX);DEBUG_PRINT("/0x");DEBUG_PRINTLN(IRCommand, HEX);
208 if (IRProtocol == irType) {
209 // check if it's a NEC repeat code
210 if (IRCommand == 0xFFFF) {
211 IRCommand = lastIRValue;
213 lastIRValue = IRCommand;
216 if (IRCommand != lastIRValue) {
217 // immediately release last key if a different IR value is received. We don't want multiple keys pressed at the same time.
223 case REMOTE_POWER_TOGGLE :
224 DEBUG_PRINTLN("Power toggle");
227 case REMOTE_POWER_OFF :
228 DEBUG_PRINT("Power off: ");
229 if (digitalRead(POWER_SENSE_PIN) == HIGH) {
232 DEBUG_PRINTLN("ignored, device already off");
235 case REMOTE_POWER_ON :
236 DEBUG_PRINT("Power on: ");
237 if (digitalRead(POWER_SENSE_PIN) == LOW) {
240 DEBUG_PRINTLN("ignored, device already on");
245 for (int i = 0; i < IR_KEY_MAP_SIZE; i++) {
246 if (irToKeyMap[i].irCommand == IRCommand) {
247 // only press key if not yet pressed
248 if (timeKeyDown == 0) {
249 KeyboardKeycode keyCode = irToKeyMap[i].keyCode;
250 DEBUG_PRINT(millis()); DEBUG_PRINT(" Press key: 0x"); DEBUG_PRINT(keyCode, HEX);
252 if (irToKeyMap[i].modifier & KEY_CTRL) {
253 BootKeyboard.press(KEY_LEFT_CTRL);
254 DEBUG_PRINT(" + CTRL ");
256 if (irToKeyMap[i].modifier & KEY_ALT) {
257 BootKeyboard.press(KEY_LEFT_ALT);
258 DEBUG_PRINT(" + ALT ");
260 if (irToKeyMap[i].modifier & KEY_SHIFT) {
261 BootKeyboard.press(KEY_LEFT_SHIFT);
262 DEBUG_PRINT(" + SHIFT ");
264 if (irToKeyMap[i].modifier & KEY_GUI) {
265 BootKeyboard.press(KEY_LEFT_GUI);
266 DEBUG_PRINT(" + GUI ");
269 BootKeyboard.press(keyCode);
271 timeKeyDown = millis();
279 // check if it's time to release a previously pressed key
280 if (timeKeyDown > 0 && (millis() - timeKeyDown >= KEY_PRESS_TIME)) {
293 //========================================
297 BootKeyboard.releaseAll();
298 DEBUG_PRINT(millis()); DEBUG_PRINTLN(" Release keys");
301 //========================================
303 void pushPowerButton() {
304 digitalWrite(POWER_BTN_PIN, HIGH);
305 delay(POWER_BTN_HOLD_TIME);
306 digitalWrite(POWER_BTN_PIN, LOW);
307 DEBUG_PRINTLN("power button triggered");
310 //========================================
312 // IR decoding callback: update the values to the newest valid input
313 void IREvent(uint8_t protocol, uint16_t address, uint32_t command) {
314 // called when directly received a valid IR signal.
315 // do not use Serial inside, it can crash your program!
317 // dont update value if not yet processed
319 IRProtocol = protocol;