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

- refactored as a Kodi addon

parent e2b1b0c1
No related branches found
No related tags found
No related merge requests found
# kodi2mqtt
Kodi to MQTT adapter
kodi2mqtt
=========
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)
Copyright (c) 2015 Oliver Wagner
......@@ -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
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