Problem with OpenHab, Lirc, XBMC & repeats

Hi all,

I am trying to control XBMC (Kodi) through Openhab, using a Harmony remote and Lirc. The flow is basically that the Harmony remote sends an RF signal to the harmonyhub, the hub to the OpenHab server which has an IR receiver, Lirc receves this and outputs it to a TCP socket, and finally a TCP item in openhab reads form this socket, does some transformation and using a switch statement in a rule I can send the right command to XBMC. This flow works, but I do have the problem that the Lirc commands are repeated in a very short timeframe (e.g it sends the 4 same commands in a ~100ms window). This also result in the rule being execute 4 times - and that does not work very well.

I have tried to modify Lirc (no repeats) and that works well when using irw, but not when using a TCP port. I tried some timer logic in the rule to ignore the same message within a timeframe, but seem to get multiple timers(?). Any suggestions on how to tackle this?

The issue in 1 line would be how to supress/ignore messages on the TCP bus that are identical within a given timeframe
(or can this be controlled by some lirc arguments that I have missed?) Any help is much appreciated! Items & rule is listed below

// remote item (for IR); transform in rule
String IR_command "IR Command [%s]" { tcp=">[127.0.0.1:8700:'REGEX((.*))']" } // socket Lirc sends data to

// Kodi items
String xbmcPlayerState "XBMC Player [%s]" { xbmc="<[#livingRoom|Player.State]" }
String xbmcPlayerTitle "Playing [%s]" { xbmc="<[#livingRoom|Player.Title]" }
String xbmcInputAction { xbmc=">[#livingRoom|Input.ExecuteAction]" }

// References
import org.openhab.core.library.types.*
import org.openhab.core.persistence.*
import org.openhab.model.script.actions.*
import java.util.concurrent.locks.ReentrantLock
import org.openhab.model.script.actions.Timer

var Timer IR_timer // track that command is actually different from prev (200ms)
var String IR_prevCmd = ""

// rules for remote
// execute function based on pressed key on harmony remote
rule "Handle IR Keys via TCP"
    when
        Item IR_command received update
    then
        var kodiPar = ""
        //logInfo("IR_command:", IR_command.state.toString())
        // strip and get key
        var String[] buffer = IR_command.state.toString.split(" ")
        // check if we can extract something
        if (buffer.size()>=2) {
            var command = buffer.get(2)
            // if diff cmd or tno timer execute
            if (IR_prevCmd != command && IR_timer==null) {
                IR_prevCmd = command
                logInfo("IR stripped command", "received: " + command +"\n");
                // set kodi param based on key
                switch command {
                    // top section
                    case "KEY_PREVIOUSSONG": { kodiPar="stepback" } // << or bigstepback or skipprevious
                    case "KEY_REWIND": { kodiPar="rewind" } // <
                    case "KEY_NEXTSONG": { kodiPar="stepforward" } // >> can also be bigstepfoward or skipnext
                    case "KEY_FASTFORWARD": { kodiPar="fastforward" } // >
                    case "KEY_PAUSE": { kodiPar="playpause" }
                    case "KEY_PLAY": { kodiPar="playpause" }
                    case "KEY_RECORD": { kodiPar="" }
                    case "KEY_STOPCD": { kodiPar="stop" }
                    // touch section
                    case "KEY_POWER": { kodiPar="" } // prevent shutdown!
                    // ...
                    // want to assign some custom keys here, e.g. spotify, games, based on keyb commands?
                    // e.g. pres touch-key "SPOTIFY" send KEY_ALT_Q -> calls Kodi run addon "Spotify" or something
                    // bottom section
                    case "KEY_ESC": { kodiPar="close" } // not sure if this is the right one
                    case "KEY_MENU": { kodiPar="contextmenu" } // UNASSIGNED IN REMOTE
                    case "KEY_UP": { kodiPar="up" }
                    case "KEY_DOWN": { kodiPar="down" }
                    case "KEY_LEFT": { kodiPar="left" }
                    case "KEY_RIGHT": { kodiPar="right" }
                    // special case: if playing, show OSD else enter
                    case "KEY_ENTER": { 
                        var player = xbmcPlayerState.state.toString()
                        switch player { 
                            case "Play": { kodiPar="enter" }
                            // default is select 
                            default: { kodiPar="select" } // must be select
                        }
                    }
                    case "KEY_VOLUMEUP": { kodiPar="volumeup" }
                    case "KEY_VOLUMEDOWN": { kodiPar="volumedown" }
                    case "KEY_CHANNELUP": { kodiPar="pageup" }
                    case "KEY_CHANNELDOWN": { kodiPar="pagedown" }
                    case "KEY_MUTE": { kodiPar="mute" }
                    case "KEY_BACK": { kodiPar="back" } // 
                    case "KEY_DVR": { kodiPar="" }
                    case "KEY_PROGRAM": { kodiPar="" } // guide
                    case "KEY_PROPS": { kodiPar="info" } // info
                    case "KEY_RED": { kodiPar="" }
                    case "KEY_GREEN": { kodiPar="" }
                    case "KEY_YELLOW": { kodiPar="" }
                    case "KEY_BLUE": { kodiPar="" }        
                }
                // if we have something, send it
                if (kodiPar != "") {
                    logInfo("Sending to Kodi:", kodiPar)
                    sendCommand(xbmcInputAction, kodiPar)
                }
                // set timer to reset the cmd
                IR_timer = createTimer(now.plusMillis(500)) [|
                    // reset
                    //postUpdate(IR_command, "");
                    logInfo("IR","Timer cancelled")
                ]
            } else {
                // nothing
                logInfo("IR","ignoring, repeat")
            }
    }
end

Thanks
Joost

Hi all,

Solved the problem, something wrong in the timer logic. Working rule below for those interested.

Joost

var Timer IR_timer // track that command is actually different from prev (200ms)
var String IR_prevCmd = ""

// rules for remote
// execute function based on pressed key on harmony remote
rule "Handle IR Keys via TCP"
    when
        Item IR_command received update
    then
        var kodiPar = ""
        //logInfo("IR_command:", IR_command.state.toString())
        // strip and get key
        var String[] buffer = IR_command.state.toString.split(" ")
        // check if we can extract something
        if (buffer.size()>=2) {
            var command = buffer.get(2)
            // check if new command, timer handles repeats
            if (IR_prevCmd != command) {
                IR_prevCmd = command
                logInfo("IR stripped command", "received: " + command +"\n");
                // set kodi param based on key
                switch command {
                    // top section
                    case "KEY_PREVIOUSSONG": { kodiPar="stepback" } // << or bigstepback or skipprevious
                    case "KEY_REWIND": { kodiPar="rewind" } // <
                    case "KEY_NEXTSONG": { kodiPar="stepforward" } // >> can also be bigstepfoward or skipnext
                    case "KEY_FASTFORWARD": { kodiPar="fastforward" } // >
                    case "KEY_PAUSE": { kodiPar="playpause" }
                    case "KEY_PLAY": { kodiPar="playpause" }
                    case "KEY_RECORD": { kodiPar="" }
                    case "KEY_STOPCD": { kodiPar="stop" }
                    // touch section
                    case "KEY_POWER": { kodiPar="" } // prevent shutdown!
                    // ...
                    // want to assign some custom keys here, e.g. spotify, games, based on keyb commands?
                    // e.g. pres touch-key "SPOTIFY" send KEY_ALT_Q -> calls Kodi run addon "Spotify" or something
                    // bottom section
                    case "KEY_ESC": { kodiPar="close" } // not sure if this is the right one
                    case "KEY_MENU": { kodiPar="contextmenu" } // UNASSIGNED IN REMOTE
                    case "KEY_UP": { kodiPar="up" }
                    case "KEY_DOWN": { kodiPar="down" }
                    case "KEY_LEFT": { kodiPar="left" }
                    case "KEY_RIGHT": { kodiPar="right" }
                    // special case: if playing, show OSD else enter
                    case "KEY_ENTER": { 
                        var player = xbmcPlayerState.state.toString()
                        switch player { 
                            case "Play": { kodiPar="enter" }
                            // default is select 
                            default: { kodiPar="select" } // must be select
                        }
                    }
                    case "KEY_VOLUMEUP": { kodiPar="volumeup" }
                    case "KEY_VOLUMEDOWN": { kodiPar="volumedown" }
                    case "KEY_CHANNELUP": { kodiPar="pageup" }
                    case "KEY_CHANNELDOWN": { kodiPar="pagedown" }
                    case "KEY_MUTE": { kodiPar="mute" }
                    case "KEY_BACK": { kodiPar="back" } // 
                    case "KEY_DVR": { kodiPar="" }
                    case "KEY_PROGRAM": { kodiPar="" } // guide
                    case "KEY_PROPS": { kodiPar="info" } // info
                    case "KEY_RED": { kodiPar="" }
                    case "KEY_GREEN": { kodiPar="" }
                    case "KEY_YELLOW": { kodiPar="" }
                    case "KEY_BLUE": { kodiPar="" }        
                }
                // if we have something, send it
                if (kodiPar != "") {
                    logInfo("Sending to Kodi:", kodiPar)
                    sendCommand(xbmcInputAction, kodiPar)
                }
                // set timer to rest the cmd
                IR_timer = createTimer(now.plusMillis(100)) [|
                    // reset
                    IR_prevCmd = "";
                    logInfo("IR","prevCmd reset")
                ]
            } else {
                // nothing
                logInfo("IR","ignoring, repeat")
            }
    }
end

A fascinating solution. Thanks for sharing.

Though I have to ask… was there a special reason that required so many twists and turns? :slight_smile:

My only thought was that you were controlling the devices over a long distance.

I am sure many of my rules look the same to others.

But could you not have used the Harmony Hub Binding? Or just communicate from the remote (set it as any device and then “train” the buttons you need) to the Kodi via IR (assuming you have the Touch, or if the Smart Control the IR could come from the Hub)

You can then report states back to OpenHAB to trigger rules, or fire off commands back to Kodi via JSON-RPC… but I think you are already doing that.

I guess my main question is why the Lirc side of things? and all the breaking down and reassembling of messages that goes into it.

Hopefully you can let us know, as I know it will bug me :smile:

Thanks

Hi Sam,

Glad you like it :smile:

I have a Harmony Touch, and the objective was to pass commands through OH first and handle logic there. The Harmony hub binding has the restriction that it can only handle activities (e.g. Watch TV), and I wanted to be able to define commands on the Harmony that would trigger something in OH and, if needed, send it to Kodi. I have dropped the IR part and now to try to use bluetooth instead, between Harmony hub and OH system.

So the way it now (should) work is that a command (e.g. Play) first goes to OH, then the rule can trigger something else (e.g. turn some Hue lights down) and then is send to Kodi using JSON-RPC (or actually via the 1.8 snapshot version of the Kodi binding which handles this) to actually play for example a movie. A “Pause” will turn the lights brighter and send a pause to Kodi.

In addition, the Harmony remote can be used to trigger other things. Using it as a bluetooth keyboard, I can define a custom named command in the touch section of the remote (e.g. Outdoor lights) that sends a key (e.g. “Shift-L”) to OH , which in turn can switch on/off RFXcom or Z-Wave controlled lights or sockets - something you can’t do without OH as controller in the middle as the Harmony/Hub combo does not support that.

Having the Harmony remote as bluetooth keyboard (Windows MCE keyboard device) also means I do not have to learn it anything, and have (almost) unlimited commands I can send. Still working on getting this to work though…

In the old setup, Lirc was used to handle the Harmony Remote -> Harmony Hub -> IR signal -> Lirc -> TCP -> Openhab flow. This was a bit more complex but it worked, but has the limitation of only being able to send a limited set of IR commands, based on whatever the Harmony IR device definition contains. Bluetooth seems to be the most flexible solution.

So, this basically allows me to have 1 single remote to control everything on a very granular level, regardless of the protocols used, as long as OH can handle it - a proper ultimate remote :wink:

Note that in the end, the actual problem that I needed solving was in the Harmony software - the number of repeats can be controlled from there …

Joost

I did something similar to Joost with Harmony and my openhab installation. I’m detailing how I made it work in case its useful for anyone else. I paired my harmony remote as a keyboard to the Bluetooth dongle on my openhab computer. Following these instructions to pair, trust, and reconnect devices on restart: Arch BT Keyboard Wiki

Then I created a python script that I run from /etc/rc.local on startup. rc.local line: sudo /scriptlocation/keyboardlisten.py &

The keyboardlisten.py script is below. This script requires evdev (for input device) and requests (for http get/post). This script first waits until a device exists at /dev/input/event0 which happens after the keyboard (harmony remote) connects to the computer. I have switch items that either directly control a device like hue lights or activate a rule. The bluetooth doesn’t actually have to be paired to openhab, it can be any computer like a raspberry pi that is in vicinity of the remote since it communicates via REST over the network. I then just mapped different keyboard key codes (e.g., 59=F1 key) to buttons on the remote.

#!/usr/bin/python

from evdev import InputDevice, categorize, ecodes
from select import select
import requests
import time
import os.path


# wait until keyboard device is there, run loop until input file exists
while not(os.path.exists('/dev/input/event0')):
        time.sleep(5) #wait five seconds before checking again


dev = InputDevice('/dev/input/event0')

while True:
        r,w,x = select([dev], [], [])
        for event in dev.read():
                if event.type == ecodes.EV_KEY:
                        keypressed = event.code
                        if event.value == 1: # keypress down event
                                keycode = event.code
                                if event.code == 59: # F1 Key Send a rest command
                                        res =requests.post('http://127.0.0.1:8080/CMD?switchone=TOGGLE', auth=('USERNAME','PASSWORD'))
                                if event.code == 60: # F2 Key send a REST command 
                                        res =requests.post('http://127.0.0.1:8080/CMD?switchtwo=OFF', auth=('USERNAME','PASSWORD'))
                                        res =requests.post('http://127.0.0.1:8080/CMD?switchthree=ON', auth=('USERNAME','PASSWORD'))
        time.sleep(0.2)