Files
Iris/mopidy_iris/snapcast.py
2018-11-05 16:33:36 +13:00

184 lines
5.3 KiB
Python
Executable File

from threading import Thread
import os, logging, subprocess, socket, json, random, string, select
# import logger
logger = logging.getLogger(__name__)
class IrisSnapcast(object):
socket = None
listen = False
def __init__(self, config):
self.config = config
self.path = os.path.dirname(__file__)
##
# This thread has been started
##
def connect(self):
host = str(self.config['iris']['snapcast_host'])
port = int(self.config['iris']['snapcast_port'])
try:
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(10)
self.sock.connect((host, port))
logger.debug("Snapcast connection established on "+host+":"+str(port))
except socket.gaierror, e:
raise Exception(e);
except socket.error, e:
raise Exception(e);
##
# Listen for socket activity
# Broadcast events to the piped broadcaster
#
# @param broadcast = Method
##
def listen(self, broadcast):
self.connect()
self.listen = True
logger.info("Established Snapcast listener")
broadcast(data={'method':'snapcast_connected'})
messages = []
data = ""
select.select([], [self.sock], [])
while self.listen:
# This loop will run constantly in the background, so we need a relatively short
# timeout as this would otherwise block the shutdown sequence
timeout = 1
readlist, writelist, exceptionlist = select.select([self.sock], [], [], timeout)
# Check if we've got any lists
if [readlist, writelist, exceptionlist] != [[], [], []]:
# Rread and print the available data on any of the read list
for socket in readlist:
# Allow a relatively large buffer size to handle large JSON payloads
message = socket.recv(8192)
try:
message = json.loads(message)
# Prefix with snapcast
message['method'] = "snapcast_"+message['method']
# Broadcast to all clients
broadcast(data=message)
except:
logger.error("Malformed Snapcast message: "+message)
##
# Stop our listener
#
# @param broadcast = Method
##
def stop_listening(self):
self.listen = False
##
# Send a single request to Snapcast
#
# @param data = Dict
##
def request(self, data):
# No socket to make request on
if self.sock == None:
raise Exception("Socket not established");
# Construct our request, based on the provided data
request = {
'id': self.generateGuid(),
'jsonrpc': '2.0',
'method': data['method'],
'params': data['params'] if 'params' in data else {}
}
logger.debug("Outgoing Snapcast message")
logger.debug(request)
# Convert to string. For some really nuts reason we need an extra trailing curly brace...
request = json.dumps(request)+'}'
# Attempt to send the request
try:
self.sock.send(request.encode('ascii')+b"\n")
except socket.error, e:
logger.error("Iris could not send request to Snapcast: %s" % e)
socket_response = {
'error': {
'message': "Failed to send request to Snapcast",
'data': str(e)
}
}
# Wait for response
while True:
try:
response = self.sock.recv(8192)
except socket.error, e:
logger.error("Iris failed to receive Snapcast response: %s" % e)
socket_response = {
'error': {
'message': "Failed to receive Snapcast response",
'data': str(e)
}
}
if not len(response):
break
try:
response = json.loads(response)
logger.debug("Incoming Snapcast message")
logger.debug(response)
if 'result' in response:
socket_response = response['result']
else:
socket_response = response['error']
except:
logger.error("Iris received malformed Snapcast response: "+response)
socket_response = {
'error': {
'message': "Malformed Snapcast response",
'data': response
}
}
return socket_response
##
# Generate a random string
#
# Used for connection_ids where none is provided by client
# @return string
##
def generateGuid(self):
length = 12
return ''.join(random.choice(string.lowercase) for i in range(length))