class MulticastEventListener: """ Represents a multicast event listener. Contains some control functions for starting and stopping the listener. """ msg_already_started = 'tried to start() MulticastEventListener when already started' msg_already_stopped = 'tried to stop() MulticastEventListener when already stopped' def __init__(self, control_point, start=True): """ Constructor for the MulticastEventListener class. @param ssdp: ssdp server instance that will receive new device events and subscriptions @param start: if True starts the search when constructed @param ssdp_addr: ssdp address for listening (UDP) @param ssdp_port: ssdp port for listening (UDP) @type ssdp: SSDPServer @type start: boolean @type ssdp_addr: string @type ssdp_port integer """ self.udp_transport = UDPTransport() self.listen_udp = UDPListener(UPnPDefaults.MULTICAST_EVENT_ADDR, UPnPDefaults.MULTICAST_EVENT_PORT, data_callback=self._datagram_received, shared_socket=self.udp_transport.socket) self.control_point = control_point if start: self.start() def _datagram_received(self, data, address): """ Callback for the UDPListener when messages arrive. @param data: raw data received @param host: host where data came from @param port: port where data came from @type data: string @type host: string @type port: integer """ (host, port) = address try: cmd, headers = parse_http_response(data) body = data[data.find("<"):data.rfind(">")+1] except Exception as err: log.error('Error while receiving datagram packet: %s', str(err)) return # Render notify message if not (cmd[0] == 'NOTIFY' and cmd[1] == '*' and cmd[2] == 'HTTP/1.0' and \ 'content-type' in headers and \ headers['content-type'] == 'text/xml; charset="utf-8"' and \ 'nt' in headers and headers['nt'] == 'upnp:event' and \ 'nts' in headers and headers['nts'] == 'upnp:propchange' and \ 'host' in headers and 'usn' in headers and \ 'svcid' in headers and 'seq' in headers and \ 'lvl' in headers and 'bootid.upnp.org' in headers and \ 'content-length' in headers): log.warning('Invalid message') return addr = headers['host'].split(':')[0] port = int(headers['host'].split(':')[1]) udn = headers['usn'].split('::')[0] service_type = headers['usn'].split('::')[1] svcid = headers['svcid'] seq = int(headers['seq']) lvl = headers['lvl'] content_length = int(headers['content-length']) bootid = int(headers['bootid.upnp.org']) if addr != UPnPDefaults.MULTICAST_EVENT_ADDR or \ port != UPnPDefaults.MULTICAST_EVENT_PORT: log.warning('Invalid address %s:%d' % (addr, port)) return changed_vars = read_notify_message_body(body) self.control_point._on_event('', changed_vars) for id, dev in list(self.control_point._known_devices.items()): service = self._find_service(dev, udn, service_type, svcid) if service != None: service._on_event(changed_vars) log.debug('Multicast event. Event changed vars: %s', changed_vars) def is_running(self): """ Returns True if the listener is running. @rtype: boolean """ return self.listen_udp.is_running() def start(self): """ Starts the listener. """ if not self.is_running(): self.listen_udp.start() log.debug('Multicast event listener started') else: log.warning(self.msg_already_started) def stop(self): """ Stops the search. """ if self.is_running(): log.debug('Multicast event listener stopped') self.listen_udp.stop() else: log.warning(self.msg_already_stopped) def destroy(self): """ Destroys and quits MSearch. """ if self.is_running(): self.stop() self.listen_udp.destroy() self._cleanup() def _find_service(self, device, udn, service_type, svcid): """ Method to find a service. @param device: instance of a device @param udn: device id @param service_type: service type @param svcid: service id @type device: RootDevice or Device @type udn: string @type service_type: string @type svcid: string @return: if found, the service @rtype: Service or None """ if device.udn != udn: for child_dev in list(device.devices.values()): service = self._find_service(child_dev, udn, service_type, svcid) if service: return service else: for k, service in list(device.services.items()): if service.service_type == service_type and \ str(service.id) == svcid: return service return None def _cleanup(self): """ Clean up references. """ self.ssdp = None self.listen_udp = None self.control_point = None