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 __init__(self, server_name, xml_description_filename, max_age=1800, receive_notify=True, http_version="1.1", search_type="sspd:all", additional_headers={}): """ Constructor for the SSDPServer class. @param server_name: server name @param xml_description_filename: XML description filename @param max_age: max age parameter, default 1800. @param receive_notify: if False, ignores notify messages @type server_name: string @type xml_description_filename: @type max_age: integer @type receive_notify: boolean """ self.server_name = server_name self.xml_description_filename = xml_description_filename self.max_age = max_age self.receive_notify = receive_notify self.running = False self.http_version = http_version self.known_device = {} self.advertised = {} self._callbacks = {} self.additional_headers = additional_headers self.search_type = search_type self.udp_transport = UDPTransport() self.udp_listener = UDPListener(SSDP_ADDR, SSDP_PORT, data_callback=self._datagram_received) self.renew_loop = LoopingCall(self._renew_notifications) self.renew_loop.start(0.8 * self.max_age, now=True)
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.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 __init__(self, server_name, xml_description_filename, max_age=1800, receive_notify=True, udp_listener=""): """ Constructor for the SSDPServer class. @param server_name: server name @param xml_description_filename: XML description filename @param max_age: max age parameter, default 1800. @param receive_notify: if False, ignores notify messages @type server_name: string @type xml_description_filename: @type max_age: integer @type receive_notify: boolean """ self.server_name = server_name self.xml_description_filename = xml_description_filename self.max_age = max_age log.debug("max_age: %s", max_age) self.receive_notify = receive_notify self.running = False self.known_device = {} self.advertised = {} self._callbacks = {} self.udp_transport = UDPTransport() if udp_listener == "": self.udp_listener = UDPListener(SSDP_ADDR, SSDP_PORT, data_callback=self._datagram_received) else: self.udp_listener = None udp_listener.subscribe(self) self.renew_loop = LoopingCall(self._renew_notifications) self.renew_loop.start(0.8 * self.max_age, now=True)
def __init__(self, server_name, xml_description_filename, max_age=1800, receive_notify=True, udp_listener=''): """ Constructor for the SSDPServer class. @param server_name: server name @param xml_description_filename: XML description filename @param max_age: max age parameter, default 1800. @param receive_notify: if False, ignores notify messages @type server_name: string @type xml_description_filename: @type max_age: integer @type receive_notify: boolean """ self.server_name = server_name self.xml_description_filename = xml_description_filename self.max_age = max_age log.debug("max_age: %s", max_age) self.receive_notify = receive_notify self.running = False self.known_device = {} self.advertised = {} self._callbacks = {} self.udp_transport = UDPTransport() if udp_listener == '': self.udp_listener = UDPListener( SSDP_ADDR, SSDP_PORT, data_callback=self._datagram_received) else: self.udp_listener = None udp_listener.subscribe(self) self.renew_loop = LoopingCall(self._renew_notifications) self.renew_loop.start(0.8 * self.max_age, now=True)
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 __init__(self, parent_udn, service, event_reload_time, force_event_reload): self.service = service self.parent_udn = parent_udn 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.eventing_variables = {} self.event_key = 0 self.event_reload_time = event_reload_time self.force_event_reload = force_event_reload if not self.force_event_reload: self.l_call = LoopingCall(self.send_variables) reactor.add_after_stop_func(self.stop) self._is_running = False
class SSDPServer(object): """ Implementation of a SSDP server. The notify_received and search_received methods are called when the appropriate type of datagram is received by the server. """ msg_already_started = 'tried to start() SSDPServer when already started' msg_already_stopped = 'tried to stop() SSDPServer when already stopped' def __init__(self, server_name, xml_description_filename, max_age=1800, receive_notify=True, http_version="1.1", search_type="sspd:all", additional_headers={}): """ Constructor for the SSDPServer class. @param server_name: server name @param xml_description_filename: XML description filename @param max_age: max age parameter, default 1800. @param receive_notify: if False, ignores notify messages @type server_name: string @type xml_description_filename: @type max_age: integer @type receive_notify: boolean """ self.server_name = server_name self.xml_description_filename = xml_description_filename self.max_age = max_age self.receive_notify = receive_notify self.running = False self.http_version = http_version self.known_device = {} self.advertised = {} self._callbacks = {} self.additional_headers = additional_headers self.search_type = search_type self.udp_transport = UDPTransport() self.udp_listener = UDPListener(SSDP_ADDR, SSDP_PORT, data_callback=self._datagram_received) self.renew_loop = LoopingCall(self._renew_notifications) self.renew_loop.start(0.8 * self.max_age, now=True) def is_running(self): """ Returns True if the SSDPServer is running, False otherwise. """ return self.running def start(self): """ Starts the SSDPServer. """ if not self.is_running(): self.udp_listener.start() self.running = True else: log.warning(self.msg_already_started) def stop(self): """ Sends bye bye notifications and stops the SSDPServer. """ if self.is_running(): # Avoid racing conditions own_temp = self.advertised.copy() for usn in own_temp: self._do_byebye(usn) self.renew_loop.stop() self.udp_listener.stop() self.running = False else: log.warning(self.msg_already_stopped) def destroy(self): """ Destroys the SSDPServer. """ if self.is_running(): self.stop() self.renew_loop.destroy() self.udp_listener.destroy() self._cleanup() def clear_device_list(self): """ Clears the device list. """ self.known_device.clear() def discovered_device_failed(self, dev): """ Device could not be fully built, so forget it. """ usn = dev['USN'] if usn in self.known_device: self.known_device.pop(usn) def is_known_device(self, usn): """ Returns if the device with the passed usn is already known. @param usn: device's usn @type usn: string @return: True if it is known @rtype: boolean """ return usn in self.known_device def subscribe(self, name, callback): """ Subscribes a callback for an event. @param name: name of the event. May be "new_device_event" or "removed_device_event" @param callback: callback @type name: string @type callback: callable """ self._callbacks.setdefault(name, []).append(callback) def unsubscribe(self, name, callback): """ Unsubscribes a callback for an event. @param name: name of the event @param callback: callback @type name: string @type callback: callable """ callbacks = self._callbacks.get(name, []) [callbacks.remove(c) for c in callbacks] self._callbacks[name] = callbacks def announce_device(self): """ Announces the device. """ [self._do_notify(usn) for usn in self.advertised] def register_device(self, device): """ Registers a device on the SSDP server. @param device: device to be registered @type device: Device """ self._register_device(device) if device.is_root_device(): [self._register_device(d) for d in device.devices.values()] # Messaging def _datagram_received(self, data, (host, port)): """ Handles a received multicast datagram. @param data: raw data @param host: datagram source host @param port: datagram source port @type data: string @type host: string @type port: integer """ try: header, payload = data.split('\r\n\r\n') except ValueError, err: log.error('Error while receiving datagram packet: %s', str(err)) return
class SSDPServer(object): """ Implementation of a SSDP server. The notify_received and search_received methods are called when the appropriate type of datagram is received by the server. """ msg_already_started = 'tried to start() SSDPServer when already started' msg_already_stopped = 'tried to stop() SSDPServer when already stopped' def __init__(self, server_name, xml_description_filename, max_age=1800, receive_notify=True, udp_listener=''): """ Constructor for the SSDPServer class. @param server_name: server name @param xml_description_filename: XML description filename @param max_age: max age parameter, default 1800. @param receive_notify: if False, ignores notify messages @type server_name: string @type xml_description_filename: @type max_age: integer @type receive_notify: boolean """ self.server_name = server_name self.xml_description_filename = xml_description_filename self.max_age = max_age log.debug("max_age: %s", max_age) self.receive_notify = receive_notify self.running = False self.known_device = {} self.advertised = {} self._callbacks = {} self.udp_transport = UDPTransport() if udp_listener == '': self.udp_listener = UDPListener( SSDP_ADDR, SSDP_PORT, data_callback=self._datagram_received) else: self.udp_listener = None udp_listener.subscribe(self) self.renew_loop = LoopingCall(self._renew_notifications) self.renew_loop.start(0.8 * self.max_age, now=True) def is_running(self): """ Returns True if the SSDPServer is running, False otherwise. """ return self.running def start(self): """ Starts the SSDPServer. """ if not self.is_running(): if self.udp_listener != None: self.udp_listener.start() self.running = True else: log.warning(self.msg_already_started) def stop(self): """ Sends bye bye notifications and stops the SSDPServer. """ if self.is_running(): # Avoid racing conditions own_temp = self.advertised.copy() for usn in own_temp: self._do_byebye(usn) self.renew_loop.stop() if self.udp_listener != None: self.udp_listener.stop() self.running = False else: log.warning(self.msg_already_stopped) def destroy(self): """ Destroys the SSDPServer. """ if self.is_running(): self.stop() self.renew_loop.destroy() if self.udp_listener != None: self.udp_listener.destroy() self._cleanup() def clear_device_list(self): """ Clears the device list. """ self.known_device.clear() def discovered_device_failed(self, dev): """ Device could not be fully built, so forget it. """ usn = dev['USN'] if usn in self.known_device: self.known_device.pop(usn) def is_known_device(self, usn): """ Returns if the device with the passed usn is already known. @param usn: device's usn @type usn: string @return: True if it is known @rtype: boolean """ return usn in self.known_device def subscribe(self, name, callback): """ Subscribes a callback for an event. @param name: name of the event. May be "new_device_event" or "removed_device_event" @param callback: callback @type name: string @type callback: callable """ self._callbacks.setdefault(name, []).append(callback) def unsubscribe(self, name, callback): """ Unsubscribes a callback for an event. @param name: name of the event @param callback: callback @type name: string @type callback: callable """ callbacks = self._callbacks.get(name, []) [callbacks.remove(c) for c in callbacks] self._callbacks[name] = callbacks def announce_device(self): """ Announces the device. """ log.debug("announce_device") [self._do_notify(usn) for usn in self.advertised] def register_device(self, device): """ Registers a device on the SSDP server. @param device: device to be registered @type device: Device """ self._register_device(device) if device.is_root_device(): [self._register_device(d) for d in device.devices.values()] # Messaging def _datagram_received(self, data, (host, port)): """ Handles a received multicast datagram. @param data: raw data @param host: datagram source host @param port: datagram source port @type data: string @type host: string @type port: integer """ log.debug("SSDP._datagram_received host: %s, port: %s\ndata: %s", host, port, data) try: header, payload = data.split('\r\n\r\n') except ValueError, err: log.error('Error while receiving datagram packet: %s', str(err)) return
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
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.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): """ 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 """ if not self.is_running(): self.listen_udp.start() self.loopcall._args = (search_type, ) 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. """ log.info("Doing double discover for %s", search_type) self.discover(search_type) self.discover(search_type) def discover(self, type="ssdp:all"): """ Mounts and sends the discover message (MSearch). @param type: search type @type type: string """ 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) 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] == '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) self.ssdp._register(headers['usn'], headers['st'], headers['location'], headers['server'], headers['cache-control'])
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'])
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)