示例#1
0
class WebSocketFactory(ClientFactory):
    """
    Implements device factory over websocket protocol.
    """
    
    implements(IWebSocketProtocolCallback, IProtoFactory)
    
    url = 'localhost'
    host = 'localhost'
    port = 80
    proto = None
    handler = None
    callbacks = dict()
    
    def __init__(self, handler):
        """
        @type handler: C{object}
        @param handler: handler has to implement C{IProtoHandler} interface
        """
        self.handler = handler
        if IProtoHandler.implementedBy(self.handler.__class__) :
            self.handler.factory = self
        else :
            raise TypeError('handler should implements IProtoHandler interface')
        self.devices = {}
    
    def buildProtocol(self, addr):
        self.proto = WebSocketDeviceHiveProtocol(self, '/device')
        if not IWebSocketMessanger.implementedBy(self.proto.__class__) :
            raise TypeError('Protocol has to implement IWebSocketMessanger interface.')
        return self.proto
    
    def clientConnectionFailed(self, connector, reason):
        """
        TODO: rename on_connection_failed method
        """
        LOG_ERR('Failed to connect to {0}, host: {1}, port: {2}. Reason: {3}.'.format(self.url, self.host, self.port, reason))
        self.handler.on_connection_failed(reason)
    
    def clientConnectionLost(self, connector, reason):
        """
        TODO: notify handler about disconnection.
        """
        pass
    
    def send_message(self, message):
        return self.proto.send_message(message)
    
    # begin IWebSocketProtocolCallback implementation
    def failure(self, reason, connector):
        LOG_ERR('Critial error. Reason: {0}.'.format(reason))
        self.handler.on_failure(None, reason)
    
    def connected(self):
        self.handler.on_connected()
    
    def closing_connection(self):
        self.handler.on_closing_connection()
    
    def frame_received(self, message):
        if ('action' in message) and (message['action'] == 'command/insert') :
            if not 'deviceGuid' in message :
                LOG_ERR('Malformed command/insert message {0}.'.format(message))
            else :
                device_id = str(message['deviceGuid']).lower() if ('deviceGuid' in message) and (message['deviceGuid'] is not None) else None
                if device_id in self.devices :
                    self.on_command_insert(WsCommand.create(message), self.devices[device_id])
                else :
                    LOG_ERR('Unable to process command {0}. Device {1} is not registered.'.format(message, device_id))
    # End of IWebSocketProtocolCallback interface implementation
    
    def on_command_insert(self, cmd, info):
        """
        @type cmd: C{object}
        @param cmd: object which implements C{ICommand}
        
        @type info: C{object}
        @param info: C{IDeviceInfo} object which is receiving the command
        """
        LOG_MSG('Command {0} has been received for device {1}.'.format(cmd, info))
        def on_ok(result):
            LOG_MSG('The command "{0}" successfully processed. Result: {1}.'.format(cmd, result))
            if isinstance(result, CommandResult) :
                cmd.status = result.status
                cmd.result = result.result
            else :
                cmd.status = 'Success'
                cmd.result = result
            self.update_command(cmd, device_id = info.id, device_key = info.key)
        #
        def on_err(reason):
            LOG_ERR('Failed to process command "{0}". Reason: {1}.'.format(cmd, reason))
            if isinstance(reason, Exception) :
                cmd.status = 'Failed'
                cmd.result = reason.message
            elif hasattr(reason, 'value') :
                if isinstance(reason.value, CommandResult) :
                    cmd.status = reason.value.status
                    cmd.result = reason.value.result
                elif isinstance(reason.value, Exception) :
                    cmd.status = 'Failed'
                    cmd.result = reason.value.message
                else :
                    cmd.status = 'Failed'
                    cmd.result = reason.value
            else :
                cmd.status = 'Failed'
                cmd.result = 'Unhandled Exception'
            self.update_command(cmd, device_id = info.id, device_key = info.key)
        #
        finished = Deferred()
        finished.addCallbacks(on_ok, on_err)
        try :
            self.handler.on_command(info.id, cmd, finished)
        except Exception as ex:
            err = DhError('Failed to invoke command {0}. Reason: {1}.'.format(cmd, ex.message))
            LOG_ERR(err.message)
            on_err(err)
    
    def update_command(self, command, device_id = None, device_key = None):
        if not ICommand.implementedBy(command.__class__) :
            raise DhError('{0}.update_command expects ICommand'.format(self.__class__.__name__))
        request = {'action': 'command/update', 'commandId': command.id, 'command': command.to_dict()}
        if device_id is not None :
            request['deviceId'] = device_id
        if device_key is not None :
            request['deviceKey'] = device_key
        return self.send_message(request)
    
    # begin IProtoFactory implementation
    def authenticate(self, device_id, device_key):
        request = {'action': 'authenticate',
                   'deviceId': device_id,
                   'deviceKey': device_key}
        return self.send_message(request)
    
    def notify(self, notification, params, device_id = None, device_key = None):
        request = {'action': 'notification/insert', 'notification': {'notification': notification, 'parameters': params}}
        if (device_id is not None) :
            request['deviceId'] = device_id
        if (device_key is not None) :
            request['deviceKey'] = device_key
        return self.send_message(request)
    
    def subscribe(self, device_id = None, device_key = None):
        LOG_MSG('Subscribe device {0}.'.format(device_id))
        request = {'action': 'command/subscribe'}
        if device_id is not None :
            request['deviceId'] = device_id
        if device_key is not None :
            request['deviceKey'] = device_key
        return self.send_message(request)
    
    def unsubscribe(self, device_id = None, device_key = None):
        request = {'action': 'command/unsubscribe'}
        if device_id is not None :
            request['deviceId'] = device_id
        if device_key is not None :
            request['deviceKey'] = device_key
        return self.send_message(request)
    
    def device_save(self, info):
        LOG_MSG('device_save {0}'.format(info))
        if not IDeviceInfo.implementedBy(info.__class__) :
            raise WebSocketError('info parameter has to implement IDeviceInfo interface')
        dev = {'key': info.key, 'name': info.name, 'equipment': [e.to_dict() for e in info.equipment]}
        if info.status is not None :
            dev['status'] = info.status
        if info.network is not None :
            dev['network'] = info.network.to_dict() if INetwork.implementedBy(info.network.__class__) else info.network
        if info.device_class is not None :
            dev['deviceClass'] = info.device_class.to_dict() if IDeviceClass.implementedBy(info.device_class.__class__) else info.device_class
        request = {'action': 'device/save', 'deviceId': info.id, 'deviceKey': info.key, 'device': dev}
        def on_ok(result):
            key = str(info.id).lower()
            self.devices[key] = info
        return self.send_message(request).addCallback(on_ok)
    
    def connect(self, url):
        reactor.connectDeviceHive(url, self)
示例#2
0
文件: ws.py 项目: FirstCypress/LiV
class WebSocketFactory(ClientFactory):
    """
    Implements client factory over websocket protocol.
    See devicehive.interfaces.IClientTransport for methods description.
    """

    implements(IClientTransport, IWebSocketProtocolCallback)

    url = 'ws://localhost'
    proto = None

    def __init__(self, handler):
        if not IClientApp.implementedBy(handler.__class__) :
            raise TypeError('handler must implement devicehive.interfaces.IClientApp interface.')
        self.handler = handler
        self.handler.factory = self
        self.command_callbacks = {}

    def get_client_url(self):
        if not self.url:
            return '/client'
        else:
            path = urlparse(self.url).path
            if path[-1:] != '/':
                path += '/'
            return urljoin(path, 'client')

    def buildProtocol(self, addr):
        self.proto = WebSocketDeviceHiveProtocol(self, self.get_client_url())
        return self.proto

    def clientConnectionFailed(self, connector, reason):
        LOG_ERR('Client connection failed. Reason: {0}.'.format(reason))
        self.handler.failure(reason)
    
    def clientConnectionLost(self, connector, reason):
        pass
    
    # IClientTransport interface implementation
    def authenticate(self, login, password):
        LOG_MSG('Authenticating the client library.')
        defer = Deferred()
        def on_ok(res):
            if res.get('status', 'error') == 'success' :
                LOG_MSG('Client library has been authenticated.')
                defer.callback(res)
            else :
                LOG_ERR('Client failed authenticate. Reason: {0}.'.format(res.get('error', 'unknown')))
                defer.errback(res)
        def on_err(reason):
            LOG_ERR('Failed to send authentication message. Reason: {0}.'.format(reason))
            defer.errback(reason)
        self.proto.send_message({'action': 'authenticate', 'requestId': None, 'login': login, 'password': password}).addCallbacks(on_ok, on_err)
        return defer
    
    def subscribe(self, device_ids):
        if not (isinstance(device_ids, list) or isinstance(device_ids, tuple)) :
            raise TypeError('device_ids should be a list or a tuple')
        LOG_MSG('About to subscribe to notifications for {0} devices.'.format(device_ids))
        defer = Deferred()
        def on_ok(res):
            if res.get('status', 'error') == 'success' :
                LOG_MSG('Subscribed.')
                defer.callback(res)
            else :
                LOG_ERR('Failed to subscribe to device(s) notifications. Reason: {0}.'.format(res.get('error', 'unknown')))
                defer.errback(res)
        def on_err(reason):
            LOG_ERR('Failed to send subscribe command. Reason: {0}.'.format(reason))
            defer.errback(reason)
        return self.proto.send_message({'action': 'notification/subscribe', 'requestId': None, 'deviceGuids': device_ids}).addCallbacks(on_ok, on_err)
    
    def unsubscribe(self, device_ids):
        if not (isinstance(device_ids, list) or isinstance(device_ids, tuple)) :
            raise TypeError('device_ids should be a list or a tuple')
        LOG_MSG('Unsubscibing from devices {0}.'.format(device_ids))
        defer = Deferred()
        def on_ok(res):
            if res.get('status', 'error') == 'success' :
                LOG_MSG('Unsubscribed from device(s) notifications.')
                defer.callback(res)
            else :
                LOG_ERR('Failed to unsubscribe from device(s) notifications. Reason: {0}.'.format(res.get('error', 'unknown')))
                defer.errback(res)
        def on_err(reason):
            LOG_ERR('Failed to send unsubscribe command. Reason: {0}.'.format(reason))
            defer.errback(reason)
        self.proto.send_message({'action': 'notification/unsubscribe',
                           'requestId': None,
                           'deviceGuids': device_ids}).addCallbacks(on_ok, on_err)
        return defer
    
    def command(self, device_id, cmd):
        if not (isinstance(device_id, str) or isinstance(device_id, unicode)) :
            raise TypeError('device_id should be a str or a unicode value')
        
        defer = Deferred()
        def on_ok(res):
            if res.get('status', 'error') == 'success' :
                LOG_MSG('Command successfully sent.')
                cmdid = res['command']['id']
                self.command_callbacks[cmdid] = defer
            else :
                LOG_ERR('Failed to send command {0}. Reason: {1}.'.format(cmd, res.get('error', 'unknown')))
                defer.errback(res)
        def on_err(reason):
            LOG_ERR('Failed to send command {0}. Reason: {1}.'.format(cmd, reason))
            defer.errback(reason)
        self.proto.send_message({'action': 'command/insert',
                                  'requestId': None,
                                  'deviceGuid': device_id,
                                  'command': cmd.to_dict()}).addCallbacks(on_ok, on_err)
        return defer
    
    def do_notification(self, msg):
        LOG_MSG('Notification {0} has been received.'.format(msg['notification']))
        self.handler.do_notification(msg['deviceGuid'], Notification(name = msg['notification']['notification'], parameters = msg['notification']['parameters']))
    
    def do_command_update(self, msg):
        if not isinstance(msg, dict) :
            raise TypeError('msg should be dict')
        cmd = msg.get('command', None)
        if cmd is not None :
            cmdid = cmd.get('id', None)
            if cmdid in self.command_callbacks :
                LOG_MSG('Command {0} update has been received.'.format(msg))
                defer = self.command_callbacks.pop(cmdid)
                ocmd = WsCommand.create(cmd)
                if (isinstance(ocmd.status, str) or isinstance(ocmd.status, unicode)) and ocmd.status.lower() == 'success' :
                    defer.callback(ocmd)
                else :
                    defer.errback(ocmd)
            else :
                LOG_ERR('Unattached command/update message {0} has been received.'.format(msg))
        else :
            LOG_ERR('Malformed command response {0} has been received.'.format(msg))
    
    def ping(self):
        return self.proto.ping()
    
    def connect(self, url):
        LOG_MSG('Connecting to {0} server.'.format(url))
        self.url = url
        reactor.connectDeviceHive(url, self)
    # end IClientTransport interface implementation
    
    # IWebSocketProtocolCallback interface implementation
    def failure(self, reason, connector):
        LOG_ERR('WebSocekt client failure. Reason: {0}.'.format(reason))
        self.handler.failure(reason)
    
    def connected(self):
        LOG_MSG('Client has connected to websocket server.')
        self.handler.connected()
    
    def closing_connection(self):
        LOG_MSG('WebSocket server has requested connection closing.')
        self.handler.closing_connection()
    
    def frame_received(self, message):
        action = message.get('action', '')
        if action == 'command/update' :
            self.do_command_update(message)
        elif action == 'notification/insert' :
            self.do_notification(message)
示例#3
0
class WebSocketFactory(ClientFactory):
    """
    Implements client factory over websocket protocol.
    See devicehive.interfaces.IClientTransport for methods description.
    """

    implements(IClientTransport, IWebSocketProtocolCallback)

    url = 'localhost'
    host = 'localhost'
    port = 8010
    proto = None

    def __init__(self, handler):
        if not IClientApp.implementedBy(handler.__class__):
            raise TypeError(
                'handler must implement devicehive.interfaces.IClientApp interface.'
            )
        self.handler = handler
        self.handler.factory = self
        self.command_callbacks = {}

    def buildProtocol(self, addr):
        self.proto = WebSocketDeviceHiveProtocol(self, '/client')
        return self.proto

    def clientConnectionFailed(self, connector, reason):
        LOG_ERR('Client connection failed. Reason: {0}.'.format(reason))
        self.handler.failure(reason)

    def clientConnectionLost(self, connector, reason):
        pass

    # IClientTransport interface implementation
    def authenticate(self, login, password):
        LOG_MSG('Authenticating the client library.')
        defer = Deferred()

        def on_ok(res):
            if res.get('status', 'error') == 'success':
                LOG_MSG('Client library has been authenticated.')
                defer.callback(res)
            else:
                LOG_ERR('Client failed authenticate. Reason: {0}.'.format(
                    res.get('error', 'unknown')))
                defer.errback(res)

        def on_err(reason):
            LOG_ERR(
                'Failed to send authentication message. Reason: {0}.'.format(
                    reason))
            defer.errback(reason)

        self.proto.send_message({
            'action': 'authenticate',
            'requestId': None,
            'login': login,
            'password': password
        }).addCallbacks(on_ok, on_err)
        return defer

    def subscribe(self, device_ids):
        if not (isinstance(device_ids, list) or isinstance(device_ids, tuple)):
            raise TypeError('device_ids should be a list or a tuple')
        LOG_MSG('About to subscribe to notifications for {0} devices.'.format(
            device_ids))
        defer = Deferred()

        def on_ok(res):
            if res.get('status', 'error') == 'success':
                LOG_MSG('Subscribed.')
                defer.callback(res)
            else:
                LOG_ERR(
                    'Failed to subscribe to device(s) notifications. Reason: {0}.'
                    .format(res.get('error', 'unknown')))
                defer.errback(res)

        def on_err(reason):
            LOG_ERR('Failed to send subscribe command. Reason: {0}.'.format(
                reason))
            defer.errback(reason)

        return self.proto.send_message({
            'action': 'notification/subscribe',
            'requestId': None,
            'deviceGuids': device_ids
        }).addCallbacks(on_ok, on_err)

    def unsubscribe(self, device_ids):
        if not (isinstance(device_ids, list) or isinstance(device_ids, tuple)):
            raise TypeError('device_ids should be a list or a tuple')
        LOG_MSG('Unsubscibing from devices {0}.'.format(device_ids))
        defer = Deferred()

        def on_ok(res):
            if res.get('status', 'error') == 'success':
                LOG_MSG('Unsubscribed from device(s) notifications.')
                defer.callback(res)
            else:
                LOG_ERR(
                    'Failed to unsubscribe from device(s) notifications. Reason: {0}.'
                    .format(res.get('error', 'unknown')))
                defer.errback(res)

        def on_err(reason):
            LOG_ERR('Failed to send unsubscribe command. Reason: {0}.'.format(
                reason))
            defer.errback(reason)

        self.proto.send_message({
            'action': 'notification/unsubscribe',
            'requestId': None,
            'deviceGuids': device_ids
        }).addCallbacks(on_ok, on_err)
        return defer

    def command(self, device_id, cmd):
        if not (isinstance(device_id, str) or isinstance(device_id, unicode)):
            raise TypeError('device_id should be a str or a unicode value')

        defer = Deferred()

        def on_ok(res):
            if res.get('status', 'error') == 'success':
                LOG_MSG('Command successfully sent.')
                cmdid = res['command']['id']
                self.command_callbacks[cmdid] = defer
            else:
                LOG_ERR('Failed to send command {0}. Reason: {1}.'.format(
                    cmd, res.get('error', 'unknown')))
                defer.errback(res)

        def on_err(reason):
            LOG_ERR('Failed to send command {0}. Reason: {1}.'.format(
                cmd, reason))
            defer.errback(reason)

        self.proto.send_message({
            'action': 'command/insert',
            'requestId': None,
            'deviceGuid': device_id,
            'command': cmd.to_dict()
        }).addCallbacks(on_ok, on_err)
        return defer

    def do_notification(self, msg):
        LOG_MSG('Notification {0} has been received.'.format(
            msg['notification']))
        self.handler.do_notification(
            msg['deviceGuid'],
            Notification(name=msg['notification']['notification'],
                         parameters=msg['notification']['parameters']))

    def do_command_update(self, msg):
        if not isinstance(msg, dict):
            raise TypeError('msg should be dict')
        cmd = msg.get('command', None)
        if cmd is not None:
            cmdid = cmd.get('id', None)
            if cmdid in self.command_callbacks:
                LOG_MSG('Command {0} update has been received.'.format(msg))
                defer = self.command_callbacks.pop(cmdid)
                ocmd = WsCommand.create(cmd)
                if (isinstance(ocmd.status, str) or isinstance(
                        ocmd.status,
                        unicode)) and ocmd.status.lower() == 'success':
                    defer.callback(ocmd)
                else:
                    defer.errback(ocmd)
            else:
                LOG_ERR(
                    'Unattached command/update message {0} has been received.'.
                    format(msg))
        else:
            LOG_ERR('Malformed command response {0} has been received.'.format(
                msg))

    def ping(self):
        return self.proto.ping()

    def connect(self, url):
        reactor.connectDeviceHive(url, self)

    # end IClientTransport interface implementation

    # IWebSocketProtocolCallback interface implementation
    def failure(self, reason, connector):
        LOG_ERR('WebSocekt client failure. Reason: {0}.'.format(reason))
        self.handler.failure(reason)

    def connected(self):
        LOG_MSG('Client has connected to websocket server.')
        self.handler.connected()

    def closing_connection(self):
        LOG_MSG('WebSocket server has requested connection closing.')
        self.handler.closing_connection()

    def frame_received(self, message):
        action = message.get('action', '')
        if action == 'command/update':
            self.do_command_update(message)
        elif action == 'notification/insert':
            self.do_notification(message)
class WebSocketFactory(ClientFactory):
    """
    Implements device factory over websocket protocol.
    """

    implements(IWebSocketProtocolCallback, IProtoFactory)

    url = 'localhost'
    host = 'localhost'
    port = 80
    proto = None
    handler = None
    callbacks = dict()

    def __init__(self, handler):
        """
        @type handler: C{object}
        @param handler: handler has to implement C{IProtoHandler} interface
        """
        self.handler = handler
        if IProtoHandler.implementedBy(self.handler.__class__):
            self.handler.factory = self
        else:
            raise TypeError(
                'handler should implements IProtoHandler interface')
        self.devices = {}

    def buildProtocol(self, addr):
        self.proto = WebSocketDeviceHiveProtocol(self, '/device')
        if not IWebSocketMessanger.implementedBy(self.proto.__class__):
            raise TypeError(
                'Protocol has to implement IWebSocketMessanger interface.')
        return self.proto

    def clientConnectionFailed(self, connector, reason):
        """
        TODO: rename on_connection_failed method
        """
        LOG_ERR('Failed to connect to {0}, host: {1}, port: {2}. Reason: {3}.'.
                format(self.url, self.host, self.port, reason))
        self.handler.on_connection_failed(reason)

    def clientConnectionLost(self, connector, reason):
        """
        TODO: notify handler about disconnection.
        """
        pass

    def send_message(self, message):
        return self.proto.send_message(message)

    # begin IWebSocketProtocolCallback implementation
    def failure(self, reason, connector):
        LOG_ERR('Critial error. Reason: {0}.'.format(reason))
        self.handler.on_failure(None, reason)

    def connected(self):
        self.handler.on_connected()

    def closing_connection(self):
        self.handler.on_closing_connection()

    def frame_received(self, message):
        if ('action' in message) and (message['action'] == 'command/insert'):
            if not 'deviceGuid' in message:
                LOG_ERR(
                    'Malformed command/insert message {0}.'.format(message))
            else:
                device_id = str(message['deviceGuid']).lower() if (
                    'deviceGuid' in message) and (message['deviceGuid']
                                                  is not None) else None
                if device_id in self.devices:
                    self.on_command_insert(WsCommand.create(message),
                                           self.devices[device_id])
                else:
                    LOG_ERR(
                        'Unable to process command {0}. Device {1} is not registered.'
                        .format(message, device_id))

    # End of IWebSocketProtocolCallback interface implementation

    def on_command_insert(self, cmd, info):
        """
        @type cmd: C{object}
        @param cmd: object which implements C{ICommand}
        
        @type info: C{object}
        @param info: C{IDeviceInfo} object which is receiving the command
        """
        LOG_MSG('Command {0} has been received for device {1}.'.format(
            cmd, info))

        def on_ok(result):
            LOG_MSG('The command "{0}" successfully processed. Result: {1}.'.
                    format(cmd, result))
            if isinstance(result, CommandResult):
                cmd.status = result.status
                cmd.result = result.result
            else:
                cmd.status = 'Success'
                cmd.result = result
            self.update_command(cmd, device_id=info.id, device_key=info.key)

        #
        def on_err(reason):
            LOG_ERR('Failed to process command "{0}". Reason: {1}.'.format(
                cmd, reason))
            if isinstance(reason, Exception):
                cmd.status = 'Failed'
                cmd.result = reason.message
            elif hasattr(reason, 'value'):
                if isinstance(reason.value, CommandResult):
                    cmd.status = reason.value.status
                    cmd.result = reason.value.result
                elif isinstance(reason.value, Exception):
                    cmd.status = 'Failed'
                    cmd.result = reason.value.message
                else:
                    cmd.status = 'Failed'
                    cmd.result = reason.value
            else:
                cmd.status = 'Failed'
                cmd.result = 'Unhandled Exception'
            self.update_command(cmd, device_id=info.id, device_key=info.key)

        #
        finished = Deferred()
        finished.addCallbacks(on_ok, on_err)
        try:
            self.handler.on_command(info.id, cmd, finished)
        except Exception as ex:
            err = DhError('Failed to invoke command {0}. Reason: {1}.'.format(
                cmd, ex.message))
            LOG_ERR(err.message)
            on_err(err)

    def update_command(self, command, device_id=None, device_key=None):
        if not ICommand.implementedBy(command.__class__):
            raise DhError('{0}.update_command expects ICommand'.format(
                self.__class__.__name__))
        request = {
            'action': 'command/update',
            'commandId': command.id,
            'command': command.to_dict()
        }
        if device_id is not None:
            request['deviceId'] = device_id
        if device_key is not None:
            request['deviceKey'] = device_key
        return self.send_message(request)

    # begin IProtoFactory implementation
    def authenticate(self, device_id, device_key):
        request = {
            'action': 'authenticate',
            'deviceId': device_id,
            'deviceKey': device_key
        }
        return self.send_message(request)

    def notify(self, notification, params, device_id=None, device_key=None):
        request = {
            'action': 'notification/insert',
            'notification': {
                'notification': notification,
                'parameters': params
            }
        }
        if (device_id is not None):
            request['deviceId'] = device_id
        if (device_key is not None):
            request['deviceKey'] = device_key
        return self.send_message(request)

    def subscribe(self, device_id=None, device_key=None):
        LOG_MSG('Subscribe device {0}.'.format(device_id))
        request = {'action': 'command/subscribe'}
        if device_id is not None:
            request['deviceId'] = device_id
        if device_key is not None:
            request['deviceKey'] = device_key
        return self.send_message(request)

    def unsubscribe(self, device_id=None, device_key=None):
        request = {'action': 'command/unsubscribe'}
        if device_id is not None:
            request['deviceId'] = device_id
        if device_key is not None:
            request['deviceKey'] = device_key
        return self.send_message(request)

    def device_save(self, info):
        LOG_MSG('device_save {0}'.format(info))
        if not IDeviceInfo.implementedBy(info.__class__):
            raise WebSocketError(
                'info parameter has to implement IDeviceInfo interface')
        dev = {
            'key': info.key,
            'name': info.name,
            'equipment': [e.to_dict() for e in info.equipment]
        }
        if info.status is not None:
            dev['status'] = info.status
        if info.network is not None:
            dev['network'] = info.network.to_dict() if INetwork.implementedBy(
                info.network.__class__) else info.network
        if info.device_class is not None:
            dev['deviceClass'] = info.device_class.to_dict(
            ) if IDeviceClass.implementedBy(
                info.device_class.__class__) else info.device_class
        request = {
            'action': 'device/save',
            'deviceId': info.id,
            'deviceKey': info.key,
            'device': dev
        }

        def on_ok(result):
            key = str(info.id).lower()
            self.devices[key] = info

        return self.send_message(request).addCallback(on_ok)

    def connect(self, url):
        reactor.connectDeviceHive(url, self)