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)
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, (host, port)): """ 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 """ log.debug("cp.event._datagram_received host: %s, port: %s\ndata: %s", host, port, data) try: cmd, headers = parse_http_response(data) body = data[data.find("<"):data.rfind(">") + 1] except Exception, err: log.error('Error while receiving datagram packet: %s', str(err)) return
class MSearch(object): """ Represents a MSearch. Contains some control functions for starting and stopping the search. While running, search will be repeated in regular intervals specified at construction or passed to the start() method. """ msg_already_started = 'tried to start() MSearch when already started' msg_already_stopped = 'tried to stop() MSearch when already stopped' def __init__(self, ssdp, start=True, interval=DEFAULT_SEARCH_TIME, ssdp_addr=DEFAULT_SSDP_ADDR, ssdp_port=1900): """ Constructor for the MSearch class. @param ssdp: ssdp server instance that will receive new device events and subscriptions @param start: if True starts the search when constructed @param interval: interval between searchs @param ssdp_addr: ssdp address for listening (UDP) @param ssdp_port: ssdp port for listening (UDP) @type ssdp: SSDPServer @type start: boolean @type interval: float @type ssdp_addr: string @type ssdp_port integer """ self.ssdp = ssdp self.ssdp_addr = ssdp_addr self.ssdp_port = ssdp_port self.udp_transport = UDPTransport() self.listen_udp = UDPListener(ssdp_addr, data_callback=self._datagram_received, shared_socket=self.udp_transport.socket) self.loopcall = LoopingCall(self.double_discover) if start: self.start(interval) def is_running(self): """ Returns True if the search is running (it's being repeated in the interval given). @rtype: boolean """ return self.loopcall.is_running() def start(self, interval=DEFAULT_SEARCH_TIME, search_type=DEFAULT_SEARCH_TYPE, http_version="1.1", man='"ssdp:discover"', mx=1, additionals={}): """ Starts the search. @param interval: interval between searchs. Default is 600.0 seconds @param search_type: type of the search, default is "ssdp:all" @param http_version: http version for m-search (default is 1.1) @param man: man field for m-search (default is ssdp:discover) @param mx: mx field for m-search (default is 1) @param additionals: dict containing additional field to be appended in the end of the m-search message (default is a empty dictionary) @type interval: float @type search_type: string @type http_version: string @type man: string @type mx: int @type additionals: dict """ if not self.is_running(): self.ssdp.search_type = search_type self.listen_udp.start() self.loopcall._args = ( search_type, http_version, man, mx, additionals, ) self.loopcall.start(interval, now=True) log.debug('MSearch started') else: log.warning(self.msg_already_started) def stop(self): """ Stops the search. """ if self.is_running(): log.debug('MSearch stopped') self.listen_udp.stop() self.loopcall.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.loopcall.destroy() self._cleanup() def double_discover(self, search_type=DEFAULT_SEARCH_TYPE, http_version="1.1", man='"ssdp:discover"', mx=1, additionals={}): """ Sends a MSearch imediatelly. Each call to this method will yield a MSearch message, that is, it won't repeat automatically. """ log.info("Doing double discover for %s, HTTP_VERSION=%s, MAN=%s, \ MX=%d, additionals=%s" % (search_type, http_version, man, mx, additionals)) self.discover(search_type, http_version, man, mx, additionals) def discover(self, search_type=DEFAULT_SEARCH_TYPE, http_version="1.1", man='"ssdp:discover"', mx=1, additionals={}): """ Builds and sends the discover message (MSearch). @param type: search type @type type: string """ if (mx > 120): mx = 120 elif (mx < 1): mx = 1 req = [ 'M-SEARCH * HTTP/%s' % http_version, 'HOST: %s:%d' % (self.ssdp_addr, self.ssdp_port), 'MAN: %s' % man, 'MX: %s' % mx, 'ST: %s' % search_type ] append = req.append [append('%s: %s' % (k, v)) for k, v in additionals.items()] append('') append('') req = '\r\n'.join(req) self.udp_transport.send_data(req, self.ssdp_addr, self.ssdp_port) def _datagram_received(self, data, (host, port)): """ 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 """ cmd, headers = parse_http_response(data) if cmd[0].startswith('HTTP/1.') and cmd[1] == '200': if self.ssdp != None: if not self.ssdp.is_known_device(headers['usn']): log.debug('Received MSearch answer %s,%s from %s:%s', headers['usn'], headers['st'], host, port) default_fields_name = [ "usn", "st", "location", "server", "cache-control", "ext" ] default_header = {} for field in default_fields_name: default_header[field] = headers.pop(field, "") self.ssdp.register(default_header['usn'], default_header['st'], default_header['location'], default_header['server'], default_header['cache-control'], "remote", headers)
class MSearch(object): """ Represents a MSearch. Contains some control functions for starting and stopping the search. While running, search will be repeated in regular intervals specified at construction or passed to the start() method. """ msg_already_started = 'tried to start() MSearch when already started' msg_already_stopped = 'tried to stop() MSearch when already stopped' def __init__(self, ssdp, start=True, interval=DEFAULT_SEARCH_TIME, ssdp_addr='239.255.255.250', ssdp_port=1900): """ Constructor for the MSearch class. @param ssdp: ssdp server instance that will receive new device events and subscriptions @param start: if True starts the search when constructed @param interval: interval between searchs @param ssdp_addr: ssdp address for listening (UDP) @param ssdp_port: ssdp port for listening (UDP) @type ssdp: SSDPServer @type start: boolean @type interval: float @type ssdp_addr: string @type ssdp_port integer """ self.ssdp = ssdp self.ssdp_addr = ssdp_addr self.ssdp_port = ssdp_port self.search_type = DEFAULT_SEARCH_TYPE self.udp_transport = UDPTransport() # self.listen_udp = UDPListener(ssdp_addr, ssdp_port, self.listen_udp = UDPListener(ssdp_addr, 2149, # WMP is not picked up if 1900 is used for source data_callback=self._datagram_received, shared_socket=self.udp_transport.socket) self.loopcall = LoopingCall(self.double_discover) if start: self.start(interval) def is_running(self): """ Returns True if the search is running (it's being repeated in the interval given). @rtype: boolean """ return self.loopcall.is_running() def start(self, interval=DEFAULT_SEARCH_TIME, search_type=DEFAULT_SEARCH_TYPE): """ Starts the search. @param interval: interval between searchs. Default is 600.0 seconds @param search_type: type of the search, default is "ssdp:all" @type interval: float @type search_type: string """ # interval = 30.0 if not self.is_running(): self.search_type = search_type self.listen_udp.start() # print ">>>>>>>>> interval: " + str(interval) self.loopcall.start(interval, now=True) log.debug('MSearch started') else: log.warning(self.msg_already_started) def stop(self): """ Stops the search. """ if self.is_running(): log.debug('MSearch stopped') self.listen_udp.stop() self.loopcall.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.loopcall.destroy() self._cleanup() def double_discover(self, search_type=DEFAULT_SEARCH_TYPE): """ Sends a MSearch imediatelly. Each call to this method will yield a MSearch message, that is, it won't repeat automatically. """ # print "<<<<<<<<< start double discover >>>>>>>>>" self.discover(search_type) self.discover(search_type) # print "<<<<<<<<< end double discover >>>>>>>>>" def discover(self, type="ssdp:all"): # def discover(self, type="upnp:rootdevice"): """ Mounts and sends the discover message (MSearch). @param type: search type @type type: string """ # type = "urn:schemas-upnp-org:device:MediaServer:1" type = "upnp:rootdevice" # req = ['M-SEARCH * HTTP/1.1', # 'HOST: %s:%d' % (self.ssdp_addr, self.ssdp_port), # 'MAN: "ssdp:discover"', # 'MX: 5', # 'ST: ' + type, '', ''] # req = '\r\n'.join(req) req = ['M-SEARCH * HTTP/1.1', 'HOST:%s:%d' % (self.ssdp_addr, self.ssdp_port), 'MAN:"ssdp:discover"', # 'Host:%s:%d' % (self.ssdp_addr, self.ssdp_port), # 'Man:"ssdp:discover"', 'MX:5', 'ST:' + type, '', '', ''] req = '\r\n'.join(req) self.udp_transport.send_data(req, self.ssdp_addr, self.ssdp_port) def _datagram_received(self, data, (host, port)): """ 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 """ # print "datagram_received start" cmd, headers = parse_http_response(data) if cmd[0] == 'HTTP/1.1' and cmd[1] == '200': if self.ssdp != None: if not self.ssdp.is_known_device(headers['usn']): log.debug('Received MSearch answer %s,%s from %s:%s', headers['usn'], headers['st'], host, port) # print "_datagram_received _register" # print "_datagram_received headers: " + str(headers) self.ssdp._register(headers['usn'], headers['st'], headers['location'], headers['server'], headers['cache-control'])