Add power on/off, keyboard modifier, more key mappings
authorMarkus Zehnder <zehnm@users.noreply.github.com>
Sun, 20 Mar 2016 17:29:48 +0000 (18:29 +0100)
committerMarkus Zehnder <zehnm@users.noreply.github.com>
Sun, 20 Mar 2016 17:29:48 +0000 (18:29 +0100)
README.md
src/IR_USB_Keyboard/Debug.h
src/IR_USB_Keyboard/IR_USB_Keyboard.ino
src/IR_USB_Keyboard/Panasonic.h
src/IR_USB_Keyboard/Sony.h

dissimilarity index 70%
index 3cd51d5..5284031 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,25 +1,26 @@
-# arduino-kodi-ir-keyboard / IRLremote + BootKeyboard branch
-Kodi optimized infrared USB keyboard for an Asus Chromebox running openELEC.
-
-IR device: Logitech Harmony 880 with remote profile: 'Panasonic TV TX-43CXW754'
-
-Testing with: 
-- SparkFun Pro Micro, ATmega32u4 (5V, 16MHz)
-- Arduino IDE 1.6.7
-- HID Project 2.4.3
-- IRLremote   1.7.4
-
-Initial proof of concept with HID-Project instead of standard Keyboard library: 
-- Kodi control works pretty well already :-)
-- key repeat works
-- boot protocol mode works (tested on a MacMini)
-
-## 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.
-  
-## TODO:
-- Power off / sleep / wakeup
-- CTRL+D / CTRL+W key combinations for boot selection 
-- Chromebox hardware modification to control power toggle switch with Arduino
+# arduino-kodi-ir-keyboard
+(IRLremote + BootKeyboard branch)
+Kodi optimized infrared USB keyboard for an Asus Chromebox running openELEC.
+
+IR device: Logitech Harmony 880 with remote profile: 'Panasonic TV TX-43CXW754'
+
+Tested with: 
+- Arduino Leonardo & SparkFun Pro Micro, ATmega32u4 (5V, 16MHz)
+- Arduino IDE 1.6.7
+- [HID Project](https://github.com/NicoHood/HID "HID Project") 2.4.3
+- [IRLremote](https://github.com/NicoHood/IRLremote "IRLremote") 1.7.4
+
+## Features
+- Full Kodi control (at least what I use ;)
+- Power on / off with Chromebox hardware modification
+- Key repeat
+- Boot protocol mode (tested on a MacMini)
+
+## Issues
+- Watch dog reset doesn't work, according to Google this is most likely an Arduino bootloader bug.
+- KEY_QUOTE doesn't work with non-us keyboard layout
+  
+## TODO
+- CTRL+D / CTRL+W key commands for boot selection 
+- sleep / wakeup (IF openELEC supports it)
index b2ba6e6..615bdc4 100644 (file)
@@ -2,11 +2,15 @@
 #define Debug_H
 
 #ifdef DEBUG_SKETCH
- #define DEBUG_PRINT(...) Serial.print(__VA_ARGS__);
- #define DEBUG_PRINTLN(...) Serial.println(__VA_ARGS__);
+  #ifndef DEBUG_OUT 
+    // TODO enhance with 2nd serial port and LCD output
+    #define DEBUG_OUT Serial
+  #endif
+  #define DEBUG_PRINT(...) DEBUG_OUT.print(__VA_ARGS__);
+  #define DEBUG_PRINTLN(...) DEBUG_OUT.println(__VA_ARGS__);
 #else
- #define DEBUG_PRINT(...)
- #define DEBUG_PRINTLN(...)
 #define DEBUG_PRINT(...)
 #define DEBUG_PRINTLN(...)
 #endif
 
 #endif
index 74819dd..75efd2c 100644 (file)
@@ -25,29 +25,22 @@ SOFTWARE.
 /* 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
@@ -64,9 +57,15 @@ SOFTWARE.
 
 // 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;
 };
 
@@ -74,50 +73,70 @@ struct CodeMap {
 // 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);
@@ -141,6 +160,9 @@ void setup() {
       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();
@@ -161,7 +183,7 @@ void loop() {
     //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
@@ -194,19 +216,62 @@ void loop() {
             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;
+                      }
+                  }
             }
         }     
     
@@ -222,7 +287,6 @@ void loop() {
     
     sei();
     
-    // TODO required?
     delay(LOOP_DELAY);
 }
 
@@ -236,6 +300,15 @@ void releaseKeys() {
 
 //========================================
 
+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.
index e2a6d58..8267f34 100644 (file)
@@ -31,7 +31,7 @@ const IRType irType = IR_PANASONIC;
 #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
@@ -39,20 +39,31 @@ const IRType irType = IR_PANASONIC;
 #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
@@ -82,19 +93,27 @@ const IRType irType = IR_PANASONIC;
 #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
 
index fdae434..57c42c8 100644 (file)
@@ -1,5 +1,6 @@
 const IRType irType = IR_SONY12;
 
+// The Sony device was just used for testing the device specific IR codes abstraction
 #define IR_SONY_1            0x0000
 #define IR_SONY_2            0x0001
 #define IR_SONY_3            0x0002
@@ -48,8 +49,8 @@ const IRType irType = IR_SONY12;
 #define REMOTE_MUTE         IR_SONY_MUTE
 #define REMOTE_VOL_UP       IR_SONY_VOL_UP
 #define REMOTE_VOL_DOWN     IR_SONY_VOL_DOWN
-#define REMOTE_PG_DOWN      0
-#define REMOTE_PG_UP        0
+#define REMOTE_ARROW_DOWN   IR_SONY_SUBTITLE
+#define REMOTE_ARROW_UP     0
 #define REMOTE_CH_DOWN      IR_SONY_CH_DOWN
 #define REMOTE_CH_UP        IR_SONY_CH_UP
 #define REMOTE_PREV         IR_SONY_PREV
@@ -79,7 +80,6 @@ const IRType irType = IR_SONY12;
 #define REMOTE_7            IR_SONY_7
 #define REMOTE_8            IR_SONY_8
 #define REMOTE_9            IR_SONY_9
-#define REMOTE_PLUS         0
 #define REMOTE_0            IR_SONY_0
 #define REMOTE_ENTER        IR_SONY_SYNC_MENU
 
@@ -87,7 +87,7 @@ const IRType irType = IR_SONY12;
 #define REMOTE_POWER_TOGGLE IR_SONY_POWER_TOGGLE
 #define REMOTE_POWER_ON     IR_SONY_POWER_ON
 #define REMOTE_POWER_OFF    IR_SONY_POWER_OFF
-#define REMOTE_BACK         IR_SONY_BACK
+#define REMOTE_CLEAR        IR_SONY_BACK
 #define REMOTE_RED          IR_SONY_RED
 #define REMOTE_GREEN        IR_SONY_GREEN
 #define REMOTE_YELLOW       IR_SONY_YELLOW
@@ -95,3 +95,12 @@ const IRType irType = IR_SONY12;
 #define REMOTE_SUBTITLE     IR_SONY_SUBTITLE
 #define REMOTE_ASPECT       0
 
+#define REMOTE_F1           0
+#define REMOTE_F2           0
+#define REMOTE_F3           0
+#define REMOTE_F4           0
+#define REMOTE_F5           0
+#define REMOTE_F6           0
+#define REMOTE_F7           0
+#define REMOTE_F8           0
+