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
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
Though I have to ask… was there a special reason that required so many twists and turns?
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
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
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 …
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)