예제 #1
0
    def __init__(self, port, receive_notify=True):
        """ControlPoint class constructor.

        @param receive_notify: if False, ignores notify messages from devices.
        Default value is True and it can be set during runtime

        @type receive_notify: boolean
        """
        self._ssdp_server = SSDPServer("BRisa Control Point",
                                       None,
                                       receive_notify=receive_notify)
        #        self._ssdp_server = SSDPServer("BRisa Control Point", 'sonoscp.xml',
        #                                      receive_notify=receive_notify)
        self._ssdp_server.subscribe("new_device_event", self._new_device_event)
        self._ssdp_server.subscribe("removed_device_event",
                                    self._removed_device_event)
        #DO WE NEED THIS? WIRESHARK...
        #        self._ssdp_server.subscribe("device_event", self._on_event)

        self._msearch = MSearch(self._ssdp_server, start=False)
        self._event_listener = EventListenerServer(self, port)
        self._multicast_event_listener = MulticastEventListener(self,
                                                                start=False)
        self.event_host = self._event_listener.host()
        self._callbacks = {}
        self._known_devices = {}
예제 #2
0
    def __init__(self, *args, **kwargs):
        """ Constructor for the Device class.

        @param device_type: device type as described on the device reference
        @param friendly_name: a friendly name

        Optional parameters follow below:

        @param udn: uuid for the device. If not specified will be
                    automatically generated.
        @param parent: parent device
        @param manufacturer: manufacturer
        @param manufacturer_url: manufacturer url
        @param model_description: model description
        @param model_name: model name
        @param model_number: model number
        @param model_url: model url
        @param serial_number: serial number
        @param upc: upc
        @param presentation_url: presentation url
        @param create_webserver: see class description for more information.
                                 Default value is True and you should normally
                                 don't pass anything
        @param force_listen_url: forces the webserver to listen on a specific
                                 address. If it's not possible to listen on
                                 that address, another random one will be
                                 generated and used automatically.
	@param additional_ssdp_headers: can be used to add more field in the
                                        notify message or http responses.

        @type device_type: string
        @type friendly_name: string
        @type udn: string
        @type parent: Device
        @type manufacturer: string
        @type manufacturer_url: string
        @type model_description: string
        @type model_name: string
        @type model_number: string
        @type model_url: string
        @type serial_number: string
        @type upc: string
        @type presentation_url: string
        @type create_webserver: bool
        @type force_listen_url: bool
        @type additional_ssdp_headers: dict
        """
        create_webserver = kwargs.pop('create_webserver', True)
        force_listen_url = kwargs.pop('force_listen_url', '')
        additional_ssdp_headers = kwargs.pop('additional_ssdp_headers', {})
        BaseDevice.__init__(self, *args, **kwargs)
        self._generate_xml()
        self.SSDP = SSDPServer(self.friendly_name, self.xml_filename,
			       additional_headers=additional_ssdp_headers)
        self.webserver = None
        if create_webserver:
            self._create_webserver(force_listen_url)
        self._event_reload_time = None
        self._force_event_reload = None
        self.extra_elts = []
예제 #3
0
    def __init__(self, *args, **kwargs):
        """ Constructor for the Device class.

        @param device_type: device type as described on the device reference
        @param friendly_name: a friendly name

        Optional parameters follow below:

        @param udn: uuid for the device. If not specified will be
                    automatically generated.
        @param parent: parent device
        @param manufacturer: manufacturer
        @param manufacturer_url: manufacturer url
        @param model_description: model description
        @param model_name: model name
        @param model_number: model number
        @param model_url: model url
        @param serial_number: serial number
        @param upc: upc
        @param presentation_url: presentation url
        @param create_webserver: see class description for more information.
                                 Default value is True and you should normally
                                 don't pass anything
        @param force_listen_url: forces the webserver to listen on a specific
                                 address. If it's not possible to listen on
                                 that address, another random one will be
                                 generated and used automatically.
        @param additional_ssdp_headers: can be used to add more field in the
                                        notify message or http responses.

        @type device_type: string
        @type friendly_name: string
        @type udn: string
        @type parent: Device
        @type manufacturer: string
        @type manufacturer_url: string
        @type model_description: string
        @type model_name: string
        @type model_number: string
        @type model_url: string
        @type serial_number: string
        @type upc: string
        @type presentation_url: string
        @type create_webserver: bool
        @type force_listen_url: bool
        @type additional_ssdp_headers: dict
        """
        create_webserver = kwargs.pop('create_webserver', True)
        force_listen_url = kwargs.pop('force_listen_url', '')
        additional_ssdp_headers = kwargs.pop('additional_ssdp_headers', {})
        BaseDevice.__init__(self, *args, **kwargs)
        self._generate_xml()
        self.SSDP = SSDPServer(self.friendly_name, self.xml_filename, additional_headers=additional_ssdp_headers)
        self.webserver = None
        if create_webserver:
            self._create_webserver(force_listen_url)
        self._event_reload_time = None
        self._force_event_reload = None
        self.extra_elts = []
예제 #4
0
    def __init__(self, receive_notify=True):
        """ControlPoint class constructor.

        @param receive_notify: if False, ignores notify messages from devices.
        Default value is True and it can be set during runtime

        @type receive_notify: boolean
        """
        self._ssdp_server = SSDPServer("BRisa Control Point", None,
                                      receive_notify=receive_notify)
        self._ssdp_server.subscribe("new_device_event", self._new_device_event)
        self._ssdp_server.subscribe("removed_device_event",
                                   self._removed_device_event)
        self._msearch = MSearch(self._ssdp_server, start=False)
        self._event_listener = EventListenerServer(self)
        self._multicast_event_listener = MulticastEventListener(self, start=False)
        self.event_host = self._event_listener.host()
        self._callbacks = {}
        self._known_devices = {}
예제 #5
0
class ControlPoint(object):
    """ This class implements UPnP Control Point functionalities.

    The simplest way of using it is by subscribing for the device events,
    starting it with start() and search for devices with start_search().
    Note that after start() the control point will be already listening for
    notifications. It will be listening for search responses only after
    start_search().

    Available events for subscription:
        - new_device_event     - triggered when a new device is found
        - removed_device_event - triggered when a device announces its departure
        - device_event         - triggered when a device sends an event message

    You may stop the control point anytime with stop_control_point() and it can be reused by
    calling start() again. If you want to stop it definitely, you may use
    destroy().
    """
    msg_already_started = 'tried to start() ControlPoint when already started'
    msg_already_stopped = 'tried to stop() ControlPoint when already stopped'

    def __init__(self, port, receive_notify=True):
        """ControlPoint class constructor.

        @param receive_notify: if False, ignores notify messages from devices.
        Default value is True and it can be set during runtime

        @type receive_notify: boolean
        """
        self._ssdp_server = SSDPServer("BRisa Control Point",
                                       None,
                                       receive_notify=receive_notify)
        #        self._ssdp_server = SSDPServer("BRisa Control Point", 'sonoscp.xml',
        #                                      receive_notify=receive_notify)
        self._ssdp_server.subscribe("new_device_event", self._new_device_event)
        self._ssdp_server.subscribe("removed_device_event",
                                    self._removed_device_event)
        #DO WE NEED THIS? WIRESHARK...
        #        self._ssdp_server.subscribe("device_event", self._on_event)

        self._msearch = MSearch(self._ssdp_server, start=False)
        self._event_listener = EventListenerServer(self, port)
        self._multicast_event_listener = MulticastEventListener(self,
                                                                start=False)
        self.event_host = self._event_listener.host()
        self._callbacks = {}
        self._known_devices = {}

    def get_devices(self):
        """ Returns a dict of devices found.
        """
        return self._known_devices

    def is_running(self):
        return self._ssdp_server.is_running() and \
               self._event_listener.is_running() and \
               self._multicast_event_listener.is_running()

    def start(self):
        """ Starts the control point.
        """
        #        print "ControlPoint.start"
        if not self.is_running():
            #            print "ControlPoint.start _ssdp_server"
            self._ssdp_server.start()
            #            print "ControlPoint.start _event_listener"
            self._event_listener.start(self.event_host)
            #            print "ControlPoint.start _multicast_event_listener"
            self._multicast_event_listener.start()
        else:
            log.warning(self.msg_already_started)

#    def stop(self): - name conflicts with stop for AVT

    def stop_control_point(self):
        """ Stops the control point.
        """
        if self.is_running():
            if self.is_msearch_running():
                self.stop_search()
            self._ssdp_server.stop()
            self._event_listener.stop()
            self._multicast_event_listener.stop()
        else:
            log.warning(self.msg_already_stopped)

    def destroy(self):
        """ Destroys and quits the control point definitely.
        """
        if self.is_running():
            self.stop_control_point()
        self._msearch.destroy()
        self._ssdp_server.destroy()
        self._event_listener.destroy()
        self._multicast_event_listener.destroy()
        self._cleanup()

    def _cleanup(self):
        """ Cleanup references.
        """
        self._known_devices.clear()
        self._msearch = None
        self._ssdp_server = None
        self._event_listener = None
        self._multicast_event_listener = None

    def subscribe(self, name, callback):
        """ Subscribes the callback for an event.

        @param name: event name
        @param callback: callback which will listen on the event

        @type name: string
        @type callback: callable
        """
        self._callbacks.setdefault(name, []).append(callback)

    def unsubscribe(self, name, callback):
        """ Unsubscribes the callback for an event.

        @param name: event name
        @param callback: callback which listens for the event

        @type name: string
        @type callback: callable
        """
        callbacks = self._callbacks.get(name, [])
        if callback in callbacks:
            callbacks.remove(callback)

    def start_search(self, interval, search_type="ssdp:all", reset=False):
        """ Sends a multicast M-SEARCH message to discover UPnP devices.

        @param interval: interval to wait between sending search messages
        @param search_type: UPnP type search. Default value is "ssdp:all"
        @param reset: clears the device list from any previous search

        @type interval: float
        @type search_type: string
        @type reset: boolean
        """
        if reset:
            self._ssdp_server.clear_device_list()
        self._msearch.start(interval, search_type)

    def stop_search(self):
        """ Stops the device search.
        """
        self._msearch.stop()

    def force_discovery(self, search_type="ssdp:all"):
        """ Forces a multicast MSearch bypassing the time interval. This method
        force msearch to send discovery message, bypassing the initial time
        interval passed to start_search function. Note this method doesn't
        cause any effect if the start_search call was never called.

        @param search_type: UPnP type search
        @type search_type: string
        """
        log.debug('force_discovery, search_type: %s', search_type)
        self._msearch.double_discover(search_type)

    def is_msearch_running(self):
        """ Returns whether MSEARCH is running or not.

        @return: Status of the MSearch
        @rtype: boolean
        """
        return self._msearch.is_running()

    def _get_recv_notify(self):
        """ GET function for the receive_notify property. Use
        self.receive_notify instead.

        @return: The receive_notify status
        @rtype: boolean
        """
        return self._ssdp_server.receive_notify

    def _set_recv_notify(self, n):
        """ SET function for the receive_notify property. Use
        self.receive_notify instead.

        @param n: The value to be set.
        @type n: boolean
        """
        self._ssdp_server.receive_notify = n

    receive_notify = property(_get_recv_notify,
                              _set_recv_notify,
                              doc='If False, the control point ignores NOTIFY\
                              messages from devices.')

    def _new_device_event(self, st, device_info):
        """ Receives a new device event.

        @param st: defines the device type
        @param device_info: informations about the device

        @type st: string
        @type device_info: dict
        """

        #        print "control_point _new_device_event usn: " + str(device_info['USN'])

        log.debug('st: %s, device_info: %s', st, device_info)

        # Callback assigned for new device event, processes asynchronously
        if 'LOCATION' not in device_info:
            return
        Device.get_from_location_async(device_info['LOCATION'],
                                       self._new_device_event_impl,
                                       device_info)

    def _new_device_event_impl(self, device_info, device):
        """ Real implementation of the new device event handler.

        @param device_info: informations about the device
        @param device: the device object itself

        @type device_info: dict
        @type device: Device
        """

        #        print "control_point _new_device_event_impl udn: " + str(device.udn)

        log.debug('device_info: %s, device: %s', device_info, device)

        if not device and self._ssdp_server:
            # Device creation failed, tell SSDPSearch to forget it
            self._ssdp_server.discovered_device_failed(device_info)
            return

        self._known_devices[device.udn] = device
        self._callback("new_device_event", device)
        log.info('Device found: %s' % device.friendly_name)

    def _removed_device_event(self, device_info):
        """ Receives a removed device event.

        @param device_info: information about the device

        @type device_info: dict
        """
        udn = device_info['USN'].split('::')[0]
        if udn in self._known_devices:
            log.info('Device is gone: %s' %
                     self._known_devices[udn].friendly_name)

        self._known_devices.pop(udn, None)
        self._callback("removed_device_event", udn)

    def _on_event(self, sid, changed_vars):
        """ Receives an event.

        @param sid: Service id
        @param changed_vars: Variables that have changed

        @type sid: str
        @type changed_vars: dict
        """

        self._callback("device_event", sid, changed_vars)

    def _on_event_seq(self, sid, seq, changed_vars):
        """ Receives an event.

        @param sid: Service id
        @param sid: sequence number
        @param changed_vars: Variables that have changed

        @type sid: str
        @type seq: str
        @type changed_vars: dict
        """

        self._callback("device_event_seq", sid, seq, changed_vars)

    def _callback(self, name, *args):
        """ Callback for any event. Forwards the event to the subscribed
        callbacks.

        @param name: event name
        @param args: arguments for the callbacks

        @type name: string
        @type args: tuple
        """
        for callback in self._callbacks.get(name, []):
            callback(*args)
예제 #6
0
class ControlPoint(object):
    """ This class implements UPnP Control Point functionalities.

    The simplest way of using it is by subscribing for the device events,
    starting it with start() and search for devices with start_search().
    Note that after start() the control point will be already listening for
    notifications. It will be listening for search responses only after
    start_search().

    Available events for subscription:
        - new_device_event     - triggered when a new device is found
        - removed_device_event - triggered when a device announces its departure
        - device_event         - triggered when a device sends an event message

    You may stop the control point anytime with stop_control_point() and it can be reused by
    calling start() again. If you want to stop it definitely, you may use
    destroy().
    """
    msg_already_started = 'tried to start() ControlPoint when already started'
    msg_already_stopped = 'tried to stop() ControlPoint when already stopped'

    def __init__(self, port, receive_notify=True):
        """ControlPoint class constructor.

        @param receive_notify: if False, ignores notify messages from devices.
        Default value is True and it can be set during runtime

        @type receive_notify: boolean
        """
        self._ssdp_server = SSDPServer("BRisa Control Point", None,
                                      receive_notify=receive_notify)
#        self._ssdp_server = SSDPServer("BRisa Control Point", 'sonoscp.xml',
#                                      receive_notify=receive_notify)
        self._ssdp_server.subscribe("new_device_event", self._new_device_event)
        self._ssdp_server.subscribe("removed_device_event",
                                   self._removed_device_event)
#DO WE NEED THIS? WIRESHARK...
#        self._ssdp_server.subscribe("device_event", self._on_event)

        self._msearch = MSearch(self._ssdp_server, start=False)
        self._event_listener = EventListenerServer(self, port)
        self._multicast_event_listener = MulticastEventListener(self, start=False)
        self.event_host = self._event_listener.host()
        self._callbacks = {}
        self._known_devices = {}


    def get_devices(self):
        """ Returns a dict of devices found.
        """
        return self._known_devices

    def is_running(self):
        return self._ssdp_server.is_running() and \
               self._event_listener.is_running() and \
               self._multicast_event_listener.is_running()

    def start(self):
        """ Starts the control point.
        """
#        print "ControlPoint.start"
        if not self.is_running():
#            print "ControlPoint.start _ssdp_server"
            self._ssdp_server.start()
#            print "ControlPoint.start _event_listener"
            self._event_listener.start(self.event_host)
#            print "ControlPoint.start _multicast_event_listener"
            self._multicast_event_listener.start()
        else:
            log.warning(self.msg_already_started)

#    def stop(self): - name conflicts with stop for AVT
    def stop_control_point(self):
        """ Stops the control point.
        """
        if self.is_running():
            if self.is_msearch_running():
                self.stop_search()
            self._ssdp_server.stop()
            self._event_listener.stop()
            self._multicast_event_listener.stop()
        else:
            log.warning(self.msg_already_stopped)

    def destroy(self):
        """ Destroys and quits the control point definitely.
        """
        if self.is_running():
            self.stop_control_point()
        self._msearch.destroy()
        self._ssdp_server.destroy()
        self._event_listener.destroy()
        self._multicast_event_listener.destroy()
        self._cleanup()

    def _cleanup(self):
        """ Cleanup references.
        """
        self._known_devices.clear()
        self._msearch = None
        self._ssdp_server = None
        self._event_listener = None
        self._multicast_event_listener = None

    def subscribe(self, name, callback):
        """ Subscribes the callback for an event.

        @param name: event name
        @param callback: callback which will listen on the event

        @type name: string
        @type callback: callable
        """
        self._callbacks.setdefault(name, []).append(callback)

    def unsubscribe(self, name, callback):
        """ Unsubscribes the callback for an event.

        @param name: event name
        @param callback: callback which listens for the event

        @type name: string
        @type callback: callable
        """
        callbacks = self._callbacks.get(name, [])
        if callback in callbacks:
            callbacks.remove(callback)

    def start_search(self, interval, search_type="ssdp:all", reset=False):
        """ Sends a multicast M-SEARCH message to discover UPnP devices.

        @param interval: interval to wait between sending search messages
        @param search_type: UPnP type search. Default value is "ssdp:all"
        @param reset: clears the device list from any previous search

        @type interval: float
        @type search_type: string
        @type reset: boolean
        """
        if reset:
            self._ssdp_server.clear_device_list()
        self._msearch.start(interval, search_type)

    def stop_search(self):
        """ Stops the device search.
        """
        self._msearch.stop()

    def force_discovery(self, search_type="ssdp:all"):
        """ Forces a multicast MSearch bypassing the time interval. This method
        force msearch to send discovery message, bypassing the initial time
        interval passed to start_search function. Note this method doesn't
        cause any effect if the start_search call was never called.

        @param search_type: UPnP type search
        @type search_type: string
        """
        log.debug('force_discovery, search_type: %s', search_type)
        self._msearch.double_discover(search_type)

    def is_msearch_running(self):
        """ Returns whether MSEARCH is running or not.

        @return: Status of the MSearch
        @rtype: boolean
        """
        return self._msearch.is_running()

    def _get_recv_notify(self):
        """ GET function for the receive_notify property. Use
        self.receive_notify instead.

        @return: The receive_notify status
        @rtype: boolean
        """
        return self._ssdp_server.receive_notify

    def _set_recv_notify(self, n):
        """ SET function for the receive_notify property. Use
        self.receive_notify instead.

        @param n: The value to be set.
        @type n: boolean
        """
        self._ssdp_server.receive_notify = n

    receive_notify = property(_get_recv_notify,
                              _set_recv_notify,
                              doc='If False, the control point ignores NOTIFY\
                              messages from devices.')

    def _new_device_event(self, st, device_info):
        """ Receives a new device event.

        @param st: defines the device type
        @param device_info: informations about the device

        @type st: string
        @type device_info: dict
        """

#        print "control_point _new_device_event usn: " + str(device_info['USN'])

        log.debug('st: %s, device_info: %s', st, device_info)
        
        # Callback assigned for new device event, processes asynchronously
        if 'LOCATION' not in device_info:
            return
        Device.get_from_location_async(device_info['LOCATION'],
                                       self._new_device_event_impl,
                                       device_info)

    def _new_device_event_impl(self, device_info, device):
        """ Real implementation of the new device event handler.

        @param device_info: informations about the device
        @param device: the device object itself

        @type device_info: dict
        @type device: Device
        """

#        print "control_point _new_device_event_impl udn: " + str(device.udn)

        log.debug('device_info: %s, device: %s', device_info, device)

        if not device and self._ssdp_server:
            # Device creation failed, tell SSDPSearch to forget it
            self._ssdp_server.discovered_device_failed(device_info)
            return

        self._known_devices[device.udn] = device
        self._callback("new_device_event", device)
        log.info('Device found: %s' % device.friendly_name)

    def _removed_device_event(self, device_info):
        """ Receives a removed device event.

        @param device_info: information about the device

        @type device_info: dict
        """
        udn = device_info['USN'].split('::')[0]
        if udn in self._known_devices:
            log.info('Device is gone: %s' %
                     self._known_devices[udn].friendly_name)

        self._known_devices.pop(udn, None)
        self._callback("removed_device_event", udn)

    def _on_event(self, sid, changed_vars):
        """ Receives an event.

        @param sid: Service id
        @param changed_vars: Variables that have changed

        @type sid: str
        @type changed_vars: dict
        """
        
        self._callback("device_event", sid, changed_vars)

    def _on_event_seq(self, sid, seq, changed_vars):
        """ Receives an event.

        @param sid: Service id
        @param sid: sequence number
        @param changed_vars: Variables that have changed

        @type sid: str
        @type seq: str
        @type changed_vars: dict
        """
        
        self._callback("device_event_seq", sid, seq, changed_vars)

    def _callback(self, name, *args):
        """ Callback for any event. Forwards the event to the subscribed
        callbacks.

        @param name: event name
        @param args: arguments for the callbacks

        @type name: string
        @type args: tuple
        """
        for callback in self._callbacks.get(name, []):
            callback(*args)
예제 #7
0
class Device(BaseDevice):
    """ Class that represents a device.

    When used with default settings, usage should be minimized to
    instantiation, start() and stop().

    The special setting is a create_webserver keyword that can be passed during
    construction. If False, it disables the automatic creation of the internal
    webserver for serving device files, so, the user should create his own
    webserver and set Device.webserver to it.
    """

    def __init__(self, *args, **kwargs):
        """ Constructor for the Device class.

        @param device_type: device type as described on the device reference
        @param friendly_name: a friendly name

        Optional parameters follow below:

        @param udn: uuid for the device. If not specified will be
                    automatically generated.
        @param parent: parent device
        @param manufacturer: manufacturer
        @param manufacturer_url: manufacturer url
        @param model_description: model description
        @param model_name: model name
        @param model_number: model number
        @param model_url: model url
        @param serial_number: serial number
        @param upc: upc
        @param presentation_url: presentation url
        @param create_webserver: see class description for more information.
                                 Default value is True and you should normally
                                 don't pass anything
        @param force_listen_url: forces the webserver to listen on a specific
                                 address. If it's not possible to listen on
                                 that address, another random one will be
                                 generated and used automatically.
	@param additional_ssdp_headers: can be used to add more field in the
                                        notify message or http responses.

        @type device_type: string
        @type friendly_name: string
        @type udn: string
        @type parent: Device
        @type manufacturer: string
        @type manufacturer_url: string
        @type model_description: string
        @type model_name: string
        @type model_number: string
        @type model_url: string
        @type serial_number: string
        @type upc: string
        @type presentation_url: string
        @type create_webserver: bool
        @type force_listen_url: bool
        @type additional_ssdp_headers: dict
        """
        create_webserver = kwargs.pop('create_webserver', True)
        force_listen_url = kwargs.pop('force_listen_url', '')
        additional_ssdp_headers = kwargs.pop('additional_ssdp_headers', {})
        BaseDevice.__init__(self, *args, **kwargs)
        self._generate_xml()
        self.SSDP = SSDPServer(self.friendly_name, self.xml_filename,
			       additional_headers=additional_ssdp_headers)
        self.webserver = None
        if create_webserver:
            self._create_webserver(force_listen_url)
        self._event_reload_time = None
        self._force_event_reload = None
        self.extra_elts = []

    def add_extra_elts(self, elts):
        """ Append extra elements to the device description, such as DLNA
        descriptive elements. Extra elements must be added before start().

        @param elts: list of Element objects
        @type elts: list
        """
        self.extra_elts = elts[:]

    def add_service(self, service):
        """ Adds a service to the device.
        """
        assert self.location, 'service does not have a location attribute yet'\
                              '. It must have a location set before adding '\
                              'services. If you passed create_webserver=Fal'\
                              'se, you must create the webserver in your '\
                              'own fashion and set self.location to the '\
                              'listening URL before adding services. This '\
                              'problem may also occur if you pass an '\
                              'invalid force_listen_url parameter.'
        service.url_base = self.location
        service.parent_udn = self.udn
        BaseDevice.add_service(self, service)

    def set_event_reload_time(self, time):
        """ Sets a event reload time for all the services.

        @param time: event reload time in seconds
        @type time: int 
        """
        self._event_reload_time = time

    def force_event_reload(self, force):
        """ Forces event reload.

        @param force: forces event reload
        @type force: bool 
        """
        self._force_event_reload = force

    def __add__(self, obj):
        if issubclass(obj.__class__, Device):
            log.debug("Adding device %s as embedded for %s", obj, self)
            self.add_device(obj)
        elif issubclass(obj.__class__, BaseService):
            log.debug("Adding service %s as service for %s", obj, self)
            self.add_service(obj)
        else:
            log.warning("Ignored invalid addition: %s + %s", self, obj)
        return self

    def _create_webserver(self, force_listen_url=''):
        if force_listen_url:
            p = network.parse_url(force_listen_url)
            self.webserver = webserver.WebServer(host=p.hostname, port=p.port)
        else:
            self.webserver = webserver.WebServer()

        self.location = self.webserver.get_listen_url()

    def _generate_xml(self):
        self.xml_filename = '%s-root-device.xml' % self.friendly_name
        self.xml_filename = self.xml_filename.replace(' ', '')
        self._xml_filepath = path.join(config.manager.brisa_home, 'tmp_xml')
        if not path.exists(self._xml_filepath):
            mkdir(self._xml_filepath)
        self._xml_filepath = path.join(self._xml_filepath, self.xml_filename)

    def _publish(self):
        assert self.webserver is not None, 'Device was told not to create '\
                                           'webserver (with False on the '\
                                           'create_webserver parameter) and'\
                                           'device.webserver was not set'\
                                           'manually as it should.'
        log.info('Publishing device %s' % self.friendly_name)
        DeviceXMLBuilder(self,
                         self.extra_elts).generate_to_file(self._xml_filepath)
        self.webserver.add_static_file(webserver.StaticFile(self.xml_filename,
                                                            self._xml_filepath))

        log.info('Publishing device\'s services')
        for v in list(self.services.values()):
            v.publish(self.webserver)

    def start(self):
        """ Starts the device.
        """
        log.info('Starting device %s' % self.friendly_name)
        log.info('Starting device\'s services')
        for k, v in list(self.services.items()):
            try:
                if self._force_event_reload:
                    v._set_force_event_reload(self._force_event_reload)
                else:
                    if self._event_reload_time:
                        v._set_event_reload_time(self._event_reload_time)
                log.info('Starting service %s' % k)
                v.start()
            except Exception as e:
                log.error('Error starting service %s: %s' % (k, str(e)))
        self._publish()
        self.SSDP.register_device(self)
        self.webserver.start()
        self.SSDP.start()
        self.SSDP.announce_device()
        log.info('Finished starting device %s' % self.friendly_name)

    def stop(self):
        """ Stops the device.
        """
        self.SSDP.stop()
        self.webserver.stop()

    def is_running(self):
        return self.webserver.is_running() and \
               self.SSDP.is_running()

    def destroy(self):
        if self.is_running():
            self.stop()
        self._cleanup()

    def _cleanup(self):
        self.SSDP = None
        self.webserver = None