示例#1
0
    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)
示例#2
0
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
示例#3
0
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)
示例#4
0
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'])