/* IR to USB keyboard optimized for KODI openELEC running on Asus Chromebox.
* IR device: Logitech Harmony 880 with remote profile: 'Panasonic TV TX-43CXW754'
*
- * Initial proof of concept.
- *
- * Testing with:
- * - SparkFun Pro Micro, ATmega32u4 (5V, 16MHz)
+ * Tested with:
+ * - Arduino Leonardo & SparkFun Pro Micro, ATmega32u4 (5V, 16MHz)
* - Arduino IDE 1.6.7
* - HID Project 2.4.3
* - IRLremote 1.7.4
*
* ISSUES:
- * - Most promising branch yet :)
* - IRLremote doesn't work with Logitech Plex Remote profile. Now (ab)using Panasonic TV TX-43CXW754 device template.
* - Watch dog reset doesn't work, according to Google this is most likely an Arduino bootloader bug.
+ * - IRLremote doesn't work with MattairTech MT-DB-U4 1.6.9-mt1
*/
// --------GLOBAL FLAGS ---------------
// enable debugging output over usb
#define DEBUG_SKETCH
//#define WATCHDOG
-// hack for MattairTech MT-DB-U4 1.6.9-mt1
-#ifndef USB_EP_SIZE
- #define USB_EP_SIZE USB_DEFAULT_EP_SIZE
-#endif
-
// --------INCLUDES ---------------
#ifdef WATCHDOG
// pin assignments
#define RECV_PIN 7
+#define POWER_SENSE_PIN 8
+#define POWER_BTN_PIN 9
+
+// min 500ms hold time seems to be required (< 500ms was too short for power on)
+#define POWER_BTN_HOLD_TIME 700
struct CodeMap {
uint32_t irCommand;
+ uint8_t modifier;
KeyboardKeycode keyCode;
};
// My Logitech resends the first repeat after 40ms and afterwards every 110ms
const int KEY_PRESS_TIME = 150;
-// TODO not sure yet if a loop delay is required. With the IRRemote library it increased realibility...
const int LOOP_DELAY = KEY_PRESS_TIME / 3;
// IR code to key mapping
+const uint8_t KEY_CTRL = 1;
+const uint8_t KEY_ALT = 2;
+const uint8_t KEY_SHIFT = 4;
+const uint8_t KEY_GUI = 8;
+
// Kodi keyboard controls: http://kodi.wiki/view/Keyboard_controls
const CodeMap irToKeyMap[] = {
- {REMOTE_LEFT , KEY_LEFT_ARROW},
- {REMOTE_RIGHT , KEY_RIGHT_ARROW},
- {REMOTE_UP , KEY_UP_ARROW},
- {REMOTE_DOWN , KEY_DOWN_ARROW},
- {REMOTE_OK , KEY_RETURN},
- {REMOTE_ENTER , KEY_TAB}, // Fullscreen playback
- {REMOTE_MENU , KEY_C},
- {REMOTE_BACK , KEY_BACKSPACE},
- {REMOTE_EXIT , KEY_ESC},
- {REMOTE_GUIDE , KEY_E},
- {REMOTE_INFO , KEY_I},
- {REMOTE_STOP , KEY_X},
- {REMOTE_PLAY , KEY_P},
- {REMOTE_PAUSE , KEY_SPACE},
- {REMOTE_REC , KEY_B},
- {REMOTE_REW , KEY_R},
- {REMOTE_FWD , KEY_F},
- {REMOTE_PREV , KEY_QUOTE}, // FIXME doesn't seem to work with non-us keyboard layout
- {REMOTE_SKIP , KEY_PERIOD},
- {REMOTE_REPLAY, KEY_COMMA},
- {REMOTE_BLUE , KEY_T}, // toggle subtitles
- {REMOTE_RED , KEY_W}, // Marked as watched / unwatched
- {REMOTE_GREEN , KEY_S}, // Shutdown menu
- {REMOTE_YELLOW, KEY_DELETE},
- {REMOTE_1 , KEY_1},
- {REMOTE_2 , KEY_2},
- {REMOTE_3 , KEY_3},
- {REMOTE_4 , KEY_4},
- {REMOTE_5 , KEY_5},
- {REMOTE_6 , KEY_6},
- {REMOTE_7 , KEY_7},
- {REMOTE_8 , KEY_8},
- {REMOTE_9 , KEY_9},
- {REMOTE_0 , KEY_0},
-
-// TODO:
-// CTRL + D -> boot ChromeOS
-// CTRL + W -> boot openELEC
+ {REMOTE_LEFT , 0, KEY_LEFT_ARROW},
+ {REMOTE_RIGHT , 0, KEY_RIGHT_ARROW},
+ {REMOTE_UP , 0, KEY_UP_ARROW},
+ {REMOTE_DOWN , 0, KEY_DOWN_ARROW},
+ {REMOTE_OK , 0, KEY_RETURN},
+ {REMOTE_ENTER , 0, KEY_TAB}, // Fullscreen playback
+ {REMOTE_MENU , 0, KEY_C},
+ {REMOTE_CLEAR , 0, KEY_BACKSPACE},
+ {REMOTE_EXIT , 0, KEY_ESC},
+ {REMOTE_GUIDE , 0, KEY_E},
+ {REMOTE_INFO , 0, KEY_I},
+ {REMOTE_STOP , 0, KEY_X},
+ {REMOTE_PLAY , 0, KEY_P},
+ {REMOTE_PAUSE , 0, KEY_SPACE},
+ {REMOTE_REC , 0, KEY_B},
+ {REMOTE_REW , 0, KEY_R},
+ {REMOTE_FWD , 0, KEY_F},
+ {REMOTE_PREV , 0, KEY_QUOTE}, // FIXME doesn't seem to work with non-us keyboard layout
+ {REMOTE_SKIP , 0, KEY_PERIOD},
+ {REMOTE_REPLAY , 0, KEY_COMMA},
+ {REMOTE_SUBTITLE, 0, KEY_T}, // toggle subtitles
+ {REMOTE_BLUE , 0, KEY_O}, // Codec Info
+ {REMOTE_RED , 0, KEY_W}, // Marked as watched / unwatched
+ {REMOTE_GREEN , 0, KEY_S}, // shutdown / suspend / reboot menu
+ {REMOTE_YELLOW , 0, KEY_DELETE},
+ {REMOTE_1 , 0, KEY_1},
+ {REMOTE_2 , 0, KEY_2},
+ {REMOTE_3 , 0, KEY_3},
+ {REMOTE_4 , 0, KEY_4},
+ {REMOTE_5 , 0, KEY_5},
+ {REMOTE_6 , 0, KEY_6},
+ {REMOTE_7 , 0, KEY_7},
+ {REMOTE_8 , 0, KEY_8},
+ {REMOTE_9 , 0, KEY_9},
+ {REMOTE_0 , 0, KEY_0},
+ {REMOTE_CH_UP , 0, KEY_PAGE_UP}, // PgUp / Skip to next queued video or next chapter if no videos are queued. / Increase Rating
+ {REMOTE_CH_DOWN , 0, KEY_PAGE_DOWN}, // PgDown / Skip to previous queued video or previous chapter if no videos are queued. / Decrease Rating
+ {REMOTE_ASPECT , 0, KEY_Z}, // Zoom/aspect ratio
+ {REMOTE_MUTE , 0, KEY_VOLUME_MUTE},
+ {REMOTE_VOL_UP , 0, KEY_VOLUME_UP},
+ {REMOTE_VOL_DOWN, 0, KEY_VOLUME_DOWN},
+ {REMOTE_F1 , 0, KEY_A}, // Audio delay control
+ {REMOTE_F2 , 0, KEY_D}, // Move item down (Playlist editor & Favorites window)
+ {REMOTE_F3 , 0, KEY_U}, // Move item up (Playlist editor & Favorites window)
+ {REMOTE_F4 , 0, KEY_Q}, // Queue
+ {REMOTE_F5 , 0, KEY_V}, // Teletext / Visualisation settings
+ {REMOTE_F6 , 0, KEY_Y}, // Switch/choose player
+ {REMOTE_F7 , 0, KEY_HOME}, // Jump to the top of the menu
+ {REMOTE_REC , 0, KEY_PRINTSCREEN}, // Screenshot
+
+ {REMOTE_ARROW_DOWN, KEY_CTRL, KEY_DOWN_ARROW}, // Move subtitles down
+ {REMOTE_ARROW_UP , KEY_CTRL, KEY_UP_ARROW}, // Move subtitles up
+ {REMOTE_F8 , KEY_CTRL, KEY_D}, // boot: ChromeOS TODO test...
+//{REMOTE_F9 , KEY_CTRL, KEY_W}, // boot: openELEC TODO test...
};
const int IR_KEY_MAP_SIZE = sizeof(irToKeyMap) / sizeof(CodeMap);
Serial.begin(115200); // afaik the baudrate is ignored on a 32u4
#endif
DEBUG_PRINTLN("Chromebox_REMOTE_USB_Keyboard");
+
+ pinMode(POWER_SENSE_PIN, INPUT);
+ pinMode(POWER_BTN_PIN, OUTPUT);
// initialize control over the keyboard:
BootKeyboard.begin();
//Test if watchdog interrupt enabled
// http://forum.arduino.cc/index.php?topic=295345.msg2628807#msg2628807
if (WDTCSR & (1<<WDIE)) {
- //Prolong wtachdog timer
+ //Prolong watchdog timer
wdt_reset();
}
//No interrupt enabled - Test if watchdog reset enabled
if (IRCommand != lastIRValue) {
// immediately release last key if a different IR value is received. We don't want multiple keys pressed at the same time.
releaseKeys();
- }
+ }
- for (int i = 0; i < IR_KEY_MAP_SIZE; i++) {
- if (irToKeyMap[i].irCommand == IRCommand) { // irToKeyMap[i].irAddress == IRAddress &&
- // only press key if not yet pressed
- if (timeKeyDown == 0) {
- KeyboardKeycode keyCode = irToKeyMap[i].keyCode;
- DEBUG_PRINT(millis()); DEBUG_PRINT(" Press key: 0x"); DEBUG_PRINTLN(keyCode, HEX);
- BootKeyboard.press(keyCode);
- }
- timeKeyDown = millis();
- break;
- }
+ switch (IRCommand) {
+ // special commands
+ case REMOTE_POWER_TOGGLE :
+ DEBUG_PRINTLN("Power toggle");
+ pushPowerButton();
+ break;
+ case REMOTE_POWER_OFF :
+ DEBUG_PRINT("Power off: ");
+ if (digitalRead(POWER_SENSE_PIN) == HIGH) {
+ pushPowerButton();
+ } else {
+ DEBUG_PRINTLN("ignored, device already off");
+ }
+ break;
+ case REMOTE_POWER_ON :
+ DEBUG_PRINT("Power on: ");
+ if (digitalRead(POWER_SENSE_PIN) == LOW) {
+ pushPowerButton();
+ } else {
+ DEBUG_PRINTLN("ignored, device already on");
+ }
+ break;
+ // keyboard commands
+ default :
+ for (int i = 0; i < IR_KEY_MAP_SIZE; i++) {
+ if (irToKeyMap[i].irCommand == IRCommand) {
+ // only press key if not yet pressed
+ if (timeKeyDown == 0) {
+ KeyboardKeycode keyCode = irToKeyMap[i].keyCode;
+ DEBUG_PRINT(millis()); DEBUG_PRINT(" Press key: 0x"); DEBUG_PRINT(keyCode, HEX);
+
+ if (irToKeyMap[i].modifier & KEY_CTRL) {
+ BootKeyboard.press(KEY_LEFT_CTRL);
+ DEBUG_PRINT(" + CTRL ");
+ }
+ if (irToKeyMap[i].modifier & KEY_ALT) {
+ BootKeyboard.press(KEY_LEFT_ALT);
+ DEBUG_PRINT(" + ALT ");
+ }
+ if (irToKeyMap[i].modifier & KEY_SHIFT) {
+ BootKeyboard.press(KEY_LEFT_SHIFT);
+ DEBUG_PRINT(" + SHIFT ");
+ }
+ if (irToKeyMap[i].modifier & KEY_GUI) {
+ BootKeyboard.press(KEY_LEFT_GUI);
+ DEBUG_PRINT(" + GUI ");
+ }
+ DEBUG_PRINTLN();
+ BootKeyboard.press(keyCode);
+ }
+ timeKeyDown = millis();
+ break;
+ }
+ }
}
}
sei();
- // TODO required?
delay(LOOP_DELAY);
}
//========================================
+void pushPowerButton() {
+ digitalWrite(POWER_BTN_PIN, HIGH);
+ delay(POWER_BTN_HOLD_TIME);
+ digitalWrite(POWER_BTN_PIN, LOW);
+ DEBUG_PRINTLN("power button triggered");
+}
+
+//========================================
+
// IR decoding callback: update the values to the newest valid input
void IREvent(uint8_t protocol, uint16_t address, uint32_t command) {
// called when directly received a valid IR signal.
#define IR_PANA_YELLOW 0xF2720080
#define IR_PANA_SUBTITLE 0x84050180
#define IR_PANA_POWER_ON 0xBE3E0080
-#define IR_PANA_POWER_OFF 0xBD3D0080
+#define IR_PANA_POWER_OFF 0xBF3F0080
#define IR_PANA_RIGHT 0xCF4F0080
#define IR_PANA_LEFT 0xCE4E0080
#define IR_PANA_INFO 0xB9390080
#define IR_PANA_REPLAY 0x55DC0980
#define IR_PANA_SKIP 0x54DD0980
#define IR_PANA_ASPECT 0x5ADE0480
-#define IR_PANA_GUIDE 0xE870980
+#define IR_PANA_GUIDE 0x0E870980
#define IR_PANA_MENU 0xD2520080
#define IR_PANA_EXIT 0x139A0980
#define IR_PANA_HOME 0x1C950980
#define IR_PANA_OK 0xC9490080
#define IR_PANA_UP 0xCA4A0080
#define IR_PANA_DOWN 0xCB4B0080
+#define IR_PANA_TV 0xB2300280
+#define IR_PANA_APPS 0x068F0980
+#define IR_PANA_AV 0x85050080
+#define IR_PANA_HDMI1 0x34B00480
+#define IR_PANA_HDMI2 0x35B10480
+#define IR_PANA_HDMI3 0x36B20480
+#define IR_PANA_VIDEO1 0x81010080
+#define IR_PANA_VIDEO2 0x82020080
+#define IR_PANA_TELETEXT 0x82030180
+#define IR_PANA_MEDIAPLAYER 0x22AB0980
+
// Standard Logitech 880 buttons
#define REMOTE_MUTE IR_PANA_MUTE
#define REMOTE_VOL_UP IR_PANA_VOL_UP
#define REMOTE_VOL_DOWN IR_PANA_VOL_DOWN
-#define REMOTE_PG_DOWN 0
-#define REMOTE_PG_UP 0
+#define REMOTE_ARROW_DOWN IR_PANA_VIDEO1
+#define REMOTE_ARROW_UP IR_PANA_VIDEO2
#define REMOTE_CH_DOWN IR_PANA_CH_DOWN
#define REMOTE_CH_UP IR_PANA_CH_UP
#define REMOTE_PREV IR_PANA_PREV
#define REMOTE_7 IR_PANA_7
#define REMOTE_8 IR_PANA_8
#define REMOTE_9 IR_PANA_9
-#define REMOTE_PLUS 0
#define REMOTE_0 IR_PANA_0
-#define REMOTE_ENTER 0
+#define REMOTE_ENTER IR_PANA_APPS
// Additional buttons
#define REMOTE_POWER_TOGGLE IR_PANA_POWER_TOGGLE
#define REMOTE_POWER_ON IR_PANA_POWER_ON
#define REMOTE_POWER_OFF IR_PANA_POWER_OFF
-#define REMOTE_BACK IR_PANA_BACK
+#define REMOTE_CLEAR IR_PANA_BACK
#define REMOTE_RED IR_PANA_RED
#define REMOTE_GREEN IR_PANA_GREEN
#define REMOTE_YELLOW IR_PANA_YELLOW
#define REMOTE_BLUE IR_PANA_BLUE
#define REMOTE_SUBTITLE IR_PANA_SUBTITLE
-#define REMOTE_ASPECT IR_PANA_ASPECT
+#define REMOTE_ASPECT IR_PANA_MEDIAPLAYER
+
+#define REMOTE_F1 IR_PANA_AV
+#define REMOTE_F2 IR_PANA_HDMI2
+#define REMOTE_F3 IR_PANA_HDMI1
+#define REMOTE_F4 IR_PANA_HDMI3
+#define REMOTE_F5 IR_PANA_TELETEXT
+#define REMOTE_F6 IR_PANA_ASPECT
+#define REMOTE_F7 IR_PANA_HOME
+#define REMOTE_F8 IR_PANA_TV