Source code for musiccast2mqtt.musiccast_listener

'''
Listener for MusicCast events over the network.

.. reviewed 13 Oct 2018
   TODO: document
'''

import socket
import select
import json
import threading
import Queue
import time
import logging

import musiccast2mqtt as mcc

LOG = logging.getLogger(__name__)

[docs]class musiccastListener(object): ''' docstring''' def __init__(self, port=mcc.DEFAULT_LISTEN_PORT): if port is None or port < 0 or port > 65535: self._port = mcc.DEFAULT_LISTEN_PORT else: self._port = port self._event_queue = Queue.Queue(maxsize=mcc.MAX_QUEUE_SIZE) self._loop_stop_event = threading.Event() self._socket = None self._create_socket() return
[docs] def get_musiccast_events_queue(self): ''' Returns the event queue.''' return self._event_queue
[docs] def loop_start(self): ''' Starts the loop in a separate thread.''' loop_thread = threading.Thread(target=self._loop, name='Event Listener') self._loop_stop_event.clear() loop_thread.start() LOG.debug('Listener loop started.') return
[docs] def loop_stop(self): ''' Stops the loop via the appropriate events.''' self._loop_stop_event.set() return
[docs] def _loop(self): ''' The actual loop. It does a full search then waits either for a 'refresh' event or for the full cycle, then tests the 'loop_stop' event if to exit or loop again.''' while True: if self._socket is None: self._create_socket() event = self._listen() if event is not None: self._event_queue.put(event) if self._loop_stop_event.is_set(): break LOG.debug('Listener loop ended.') return
[docs] def _create_socket(self): ''' Creates the socket.''' self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #, socket.IPPROTO_UDP) self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Re-use socket. self._socket.setblocking(0) self._socket.bind(('', self._port)) return
[docs] def _release_socket(self): ''' Releases the socket in case of error. ''' self._socket.close() self._socket = None return
[docs] def _listen(self): ''' Listens for new events. The 'body' of the event (see below) is in the form: '{"main":{"power":"on"},"device_id":"00A0DED57E83"}' or: '{"main":{"volume":88},"zone2":{"volume":0}, "device_id":"00A0DED3FD57"}' The 'address' is a pair (host, port) as in ('192.168.1.44', 38507). ''' if self._socket is None: # wait a bit? TODO: decide what can be done better here. time.sleep(mcc.SOCKET_ERROR_SLEEP) return None try: trigger = select.select([self._socket], [], [], mcc.LISTEN_TIMEOUT) except select.error as err: # log only for now. TODO: restart socket? LOG.debug(''.join(('Socket error: <', err[1], '>. Ignore.'))) return None if trigger[0]: # there is an event waiting in the socket body, address = self._socket.recvfrom(mcc.SOCKET_BUFFER_SIZE) LOG.debug(''.join(('Event received: <', str(body), '> from <', str(address), '>.'))) try: event = json.loads(body) except ValueError as err: # log only for now LOG.debug(''.join(('Error while reading JSON: ', str(err)))) return None return event else: return None
if __name__ == '__main__': LISTENER = musiccastListener() QUEUE = LISTENER.get_musiccast_events_queue() LISTENER.loop_start() BLOCK = threading.Event() BLOCK.wait()