Skip to content
Snippets Groups Projects
Commit 4a7809e1 authored by owagner's avatar owagner
Browse files

- refactored as a Kodi addon

parent e2b1b0c1
Branches
Tags
No related merge requests found
# kodi2mqtt kodi2mqtt
Kodi to MQTT adapter =========
Written and (C) 2015 Oliver Wagner <owagner@tellerulam.com>
Provided under the terms of the MIT license.
Overview
--------
kodi2mqtt is a Kodi addon which acts as an adapter between a Kodi media center instance and MQTT.
It publishes Kodi's playback state on MQTT topics, and provides remote control capability via
messages to MQTT topics.
It's intended as a building block in heterogenous smart home environments where an MQTT message broker is used as the centralized message bus.
See https://github.com/mqtt-smarthome for a rationale and architectural overview.
Dependencies
------------
* Kodi 14 Helix (or newer)
* Eclipse Paho for Python - http://www.eclipse.org/paho/clients/python/
(used for MQTT communication)
See also
--------
- Project overview: https://github.com/mqtt-smarthome
Changelog
---------
Please see kodi2mqtt-addon/changelog.txt for the change log
\ No newline at end of file
#
# Bridge between a Kodi instanc and MQTT.
#
# Written and (C) 2015 by Oliver Wagner <owagner@tellerulam.com>
# Provided under the terms of the MIT license
#
# Requires:
# - Eclipse Paho for Python - http://www.eclipse.org/paho/clients/python/
#
import argparse
import logging
import logging.handlers
import time
import json
import socket
import paho.mqtt.client as mqtt
version="0.1"
hostname=socket.gethostname()
parser = argparse.ArgumentParser(description='Bridge between Kodi and MQTT')
parser.add_argument('--mqtt-host', default='localhost', help='MQTT server address. Defaults to "localhost"')
parser.add_argument('--mqtt-port', default='1883', type=int, help='MQTT server port. Defaults to 1883')
parser.add_argument('--mqtt-topic', default="kodi/"+hostname+"/", help='Topic prefix to be used for subscribing/publishing. Defaults to "kodi/<hostname>/"')
parser.add_argument('--log', help='set log level to the specified value. Defaults to WARNING. Try DEBUG for maximum detail')
parser.add_argument('--syslog', action='store_true', help='enable logging to syslog')
args=parser.parse_args()
if args.log:
logging.getLogger().setLevel(args.log)
if args.syslog:
logging.getLogger().addHandler(logging.handlers.SysLogHandler())
topic=args.mqtt_topic
if not topic.endswith("/"):
topic+="/"
logging.info('Starting kodi2mqtt V%s with topic prefix \"%s\"' %(version, topic))
def processnotify(data):
try:
params=json.loads(data)
except ValueError:
parts=data.split(None,2)
params={"title":parts[0],"message":parts[1]}
sendrpc("GUI.ShowNotification",params,1)
def processcommand(topic,data):
if topic=="notify":
processnotify(data)
else:
logging.warning("Unknown command "+topic)
def msghandler(mqc,userdata,msg):
try:
global topic
if msg.retain:
return
mytopic=msg.topic[len(topic):]
if mytopic.startswith("command/"):
processcommand(mytopic[8:],msg.payload)
except Exception as e:
logging.warning("Error processing message %s: %s" % (type(e).__name__,e))
def connecthandler(mqc,userdata,rc):
logging.info("Connected to MQTT broker with rc=%d" % (rc))
mqc.subscribe(topic+"command/#",qos=0)
def disconnecthandler(mqc,userdata,rc):
logging.warning("Disconnected from MQTT broker with rc=%d" % (rc))
time.sleep(5)
mqc.reconnect()
def publish(suffix,val,more):
global topic,mqc
robj={}
robj["val"]=val
if more is not None:
robj.update(more)
mqc.publish(topic+"status/"+suffix,json.dumps(robj),qos=0,retain=True)
def sendrpc(method,params,id):
jso={"jsonrpc":"2.0","method":method,"params":params,"id":id}
txt=json.dumps(jso)
logging.debug("KODI>>"+txt)
s.sendall(txt+"\n")
def requeststreamdetails():
sendrpc("Player.GetItem",{"playerid":activeplayerid,"properties":["title","streamdetails","file"]},3)
def requestplaystate():
sendrpc("Player.GetProperties",{"playerid":activeplayerid,"properties":["speed","currentsubtitle","currentaudiostream","repeat","subtitleenabled"]},5)
def requesttime():
sendrpc("Player.GetProperties",{"playerid":activeplayerid,"properties":["percentage","time","totaltime"]},4)
def requestping():
sendrpc("JSONRPC.Ping",{},1)
mqc=mqtt.Client()
mqc.on_message=msghandler
mqc.on_connect=connecthandler
mqc.on_disconnect=disconnecthandler
mqc.will_set(topic+"connected",0,qos=2,retain=True)
mqc.connect(args.mqtt_host,args.mqtt_port,60)
mqc.publish(topic+"connected",1,qos=1,retain=True)
mqc.loop_start()
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("localhost",9090))
s.settimeout(30)
sendrpc("JSONRPC.SetConfiguration",{ "notifications": { "player": True }},1)
sendrpc("Player.GetActivePlayers",{},2)
mqc.publish(topic+"connected",2,qos=1,retain=True)
activeplayerid=-1
playstate=0
def handleplay(jso):
global activeplayerid
activeplayerid=jso["params"]["data"]["player"]["playerid"]
requeststreamdetails()
requesttime()
def convtime(timejso):
return("%02d:%02d:%02d" % (timejso["hours"],timejso["minutes"],timejso["seconds"]))
def setplaystate(newplaystate):
playstate=newplaystate
publish("playstate",playstate,None)
def handleplaystate(jso):
if not "error" in jso["result"]:
publish("playstate",1 if jso["result"]["speed"]>0 else 2,{"kodi_playbackdetails":jso["result"]})
else:
publish("playstate","0")
def handleresponse(jso):
if jso["id"]==2:
if len(jso["result"])>0:
global activeplayerid
activeplayerid=jso["result"][0]["playerid"]
requeststreamdetails()
requesttime()
requestplaystate()
elif jso["id"]==3:
publish("title",jso["result"]["item"]["title"],{"kodi_details":jso["result"]["item"]})
elif jso["id"]==4:
if not "error" in jso["result"]:
publish("progress",round(jso["result"]["percentage"],1),{"kodi_time":convtime(jso["result"]["time"]),"kodi_totaltime":convtime(jso["result"]["totaltime"])})
elif jso["id"]==5:
handleplaystate(jso)
fh=s.makefile("r",16384)
while True:
bc=0
l=bytes()
while True:
try:
ch=s.recv(1)
except socket.timeout:
if activeplayerid>=0:
requesttime()
else:
requestping()
continue
l+=ch
if ch=="{":
bc+=1
elif ch=="}":
bc-=1
if bc==0:
break;
jso=json.loads(l.decode("UTF-8"))
logging.debug("KODI<<%s",jso)
if "method" in jso:
if(jso["method"]=="Player.OnPlay"):
handleplay(jso)
requestplaystate()
elif(jso["method"]=="Player.OnPause"):
requestplaystate()
elif(jso["method"]=="Player.OnStop"):
requestplaystate()
else:
handleresponse(jso)
The kodi2mqtt addon is licensed under the terms of the MIT license:
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2015 Oliver Wagner Copyright (c) 2015 Oliver Wagner
...@@ -20,3 +22,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ...@@ -20,3 +22,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
---------------------------------------------------------------------
For the included Eclipse Paho MQTT library:
This project is dual licensed under the Eclipse Public License 1.0 and the
Eclipse Distribution License 1.0 as described in the epl-v10 and edl-v10 files.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="service.mqtt" name="MQTT Adapter" version="0.2" provider-name="owagner">
<requires>
<import addon="xbmc.python" version="2.19.0"/>
</requires>
<extension point="xbmc.service" library="service.py" start="login" />
<extension point="xbmc.addon.metadata">
<summary lang="en">MQTT Adapter, adhering to mqtt-smarthome specification</summary>
<description lang="en">The addon is an adapter to a MQTT broker. It will publish information about what is playing, and provides remote control capability. It adheres to the mqtt-smarthome specification. </description>
<disclaimer lang="en"></disclaimer>
<platform>all</platform>
<license>MIT</license>
<forum></forum>
<website></website>
<email></email>
<source>https://github.com/owagner/kodi2mqtt</source>
</extension>
</addon>
\ No newline at end of file
service.mqtt/icon.png

8.72 KiB

__version__ = "1.1"
This diff is collapsed.
# Kodi Media Center language file
# Addon Name: MQTT Adapter
# Addon id: service.mqtt
# Addon Provider: owagner
msgid ""
msgstr ""
"Project-Id-Version: XBMC Addons\n"
"Report-Msgid-Bugs-To: alanwww1@xbmc.org\n"
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Kodi Translation Team\n"
"Language-Team: English (http://www.transifex.com/projects/p/xbmc-addons/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgctxt "#30001"
msgid "General"
msgstr ""
msgctxt "#30011"
msgid "MQTT Broker IP"
msgstr ""
msgctxt "#30012"
msgid "MQTT Broker Port"
msgstr ""
msgctxt "#30013"
msgid "MQTT Topic Prefix"
msgstr ""
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<settings>
<category label="30001">
<setting label="30011" type="ipaddress" id="mqtthost" default="127.0.0.1"/>
<setting label="30012" type="number" id="mqttport" default="1883"/>
<setting label="30013" type="text" id="mqtttopic" default="kodi/"/>
</category>
</settings>
\ No newline at end of file
#!/usr/bin/python
# -*- coding: utf-8 -*-
import xbmc,xbmcaddon
import json
from lib import client as mqtt
__addon__ = xbmcaddon.Addon()
__version__ = __addon__.getAddonInfo('version')
def publish(suffix,val,more):
global topic,mqc
robj={}
robj["val"]=val
if more is not None:
robj.update(more)
jsonstr=json.dumps(robj)
fulltopic=topic+"status/"+suffix
xbmc.log("MQTT: Publishing @"+fulltopic+": "+jsonstr)
mqc.publish(fulltopic,jsonstr,qos=0,retain=True)
def setplaystate(state):
publish("playbackstate",state,None)
def publishdetails():
global player
if not player.isPlayer():
return
state={}
state["file"]=player.getPlayingFile()
if player.isPlayingVideo():
it=player.getVideoInfoTag()
title=it.getTitle()
state["file"]=it.getFile()
elif player.isPlayingAudio():
it=player.getMusicInfoTag()
title=it.getTitle()
state["file"]=it.getFile()
publish("title",title,{"kodi_details":state})
class MQTTMonitor(xbmc.Monitor):
def onSettingsChanged(self):
global mqc
xbmc.log("MQTT: Settings changed, reconnecting broker")
mqc.loop_stop(True)
startmqtt()
class MQTTPlayer(xbmc.Player):
def onPlayBackStarted(self):
setplaystate(1)
def onPlayBackPaused(self):
setplaystate(2)
def onPlayBackResumed(self):
setplaystate(1)
def onPlayBackEnded(self):
setplaystate(0)
def onPlayBackStopped(self):
setplaystate(0)
def msghandler(mqc,userdata,msg):
try:
global topic
if msg.retain:
return
mytopic=msg.topic[len(topic):]
if mytopic.startswith("command/"):
processcommand(mytopic[8:],msg.payload)
except Exception as e:
xbmc.log("MQTT: Error processing message %s: %s" % (type(e).__name__,e))
def connecthandler(mqc,userdata,rc):
xbmc.log("MQTT: Connected to MQTT broker with rc=%d" % (rc))
mqc.subscribe(topic+"command/#",qos=0)
def disconnecthandler(mqc,userdata,rc):
xbmc.log("MQTT: Disconnected from MQTT broker with rc=%d" % (rc))
time.sleep(5)
mqc.reconnect()
def startmqtt():
global topic,mqc
mqc=mqtt.Client()
mqc.on_message=msghandler
mqc.on_connect=connecthandler
mqc.on_disconnect=disconnecthandler
topic=__addon__.getSetting("mqtttopic")
if not topic.endswith("/"):
topic+="/"
mqc.will_set(topic+"connected",0,qos=2,retain=True)
xbmc.log("MQTT: Connecting to MQTT broker at %s:%s" % (__addon__.getSetting("mqtthost"),__addon__.getSetting("mqttport")))
mqc.connect(__addon__.getSetting("mqtthost"),__addon__.getSetting("mqttport"),60)
mqc.publish(topic+"connected",2,qos=1,retain=True)
mqc.loop_start()
if (__name__ == "__main__"):
global monitor,player
xbmc.log('MQTT: MQTT Adapter Version %s started' % __version__)
monitor=MQTTMonitor()
player=MQTTPlayer()
startmqtt()
monitor.waitForAbort()
mqc.loop_stop(True)
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment