Beispiel #1
0
class EventManager(Plugin):
    implements(ITelldusLiveObserver)

    observers = ObserverCollection(IEventFactory)

    def __init__(self):
        self.events = {}
        self.settings = Settings('telldus.event')
        self.schedulersettings = Settings('telldus.scheduler')
        self.live = TelldusLive(self.context)
        self.timezone = self.schedulersettings.get('tz', 'UTC')
        self.latitude = self.schedulersettings.get('latitude', '55.699592')
        self.longitude = self.schedulersettings.get('longitude', '13.187836')
        self.loadLocalEvents()

    def loadEvent(self, eventId, data, storeddata):
        event = Event(self, eventId, data['minRepeatInterval'],
                      data['description'])
        event.loadActions(data['actions'], storeddata)
        event.loadConditions(data['conditions'])
        event.loadTriggers(data['triggers'])
        self.events[eventId] = event

    def liveRegistered(self, msg):
        changed = False
        if 'latitude' in msg and msg['latitude'] != self.latitude:
            changed = True
            self.latitude = msg['latitude']
            self.schedulersettings['latitude'] = self.latitude
        if 'longitude' in msg and msg['longitude'] != self.longitude:
            changed = True
            self.longitude = msg['longitude']
            self.schedulersettings['longitude'] = self.longitude
        if 'tz' in msg and msg['tz'] != self.timezone:
            changed = True
            self.timezone = msg['tz']
            self.schedulersettings['tz'] = self.timezone

        if changed:
            self.recalcTriggers()

    @mainthread
    def loadLocalEvents(self):
        if len(self.events) == 0:
            # only load local events if no report has been received (highly improbable though)
            data = self.settings.get('events', {})
            for eventId in data:
                if eventId not in self.events and data[eventId] != "":
                    self.loadEvent(eventId, data[eventId], {})

    def recalcTriggers(self):
        for observer in self.observers:
            observer.recalcTrigger()

    def requestAction(self, type, **kwargs):
        for observer in self.observers:
            if type == 'url':
                return UrlAction(type=type, **kwargs)
            action = observer.createAction(type=type, **kwargs)
            if action is not None:
                return action
        return None

    def requestCondition(self, type, **kwargs):
        for observer in self.observers:
            condition = observer.createCondition(type=type, **kwargs)
            if condition is not None:
                return condition
        return None

    def requestTrigger(self, type, **kwargs):
        for observer in self.observers:
            trigger = observer.createTrigger(type=type, **kwargs)
            if trigger is not None:
                return trigger
        return None

    @TelldusLive.handler('events-report')
    def receiveEventsFromServer(self, msg):
        data = msg.argument(0).toNative()
        for eventId in self.events:
            # clear old timers
            self.events[eventId].close()
        self.events = {}
        storeddata = self.settings.get('events', {})
        self.settings['events'] = data
        for observer in self.observers:
            observer.clearAll()
        for eventId in data:
            if eventId not in self.events:
                self.loadEvent(eventId, data[eventId], storeddata)

    @TelldusLive.handler('one-event-deleted')
    def receiveDeletedEventFromServer(self, msg):
        eventId = msg.argument(0).toNative()['eventId']
        if eventId in self.events:
            self.events[eventId].close()
            del self.events[eventId]
        storeddata = self.settings.get('events', {})
        storeddata[str(eventId)] = ""
        self.settings['events'] = storeddata

    @TelldusLive.handler('one-event-report')
    def receiveEventFromServer(self, msg):
        data = msg.argument(0).toNative()
        eventId = data['eventId']
        if eventId in self.events:
            self.events[eventId].close()
            del self.events[eventId]
        storeddata = self.settings.get('events', {})
        newstoreddata = storeddata.copy()
        newstoreddata[str(eventId)] = data
        self.settings['events'] = newstoreddata
        self.loadEvent(eventId, data, storeddata)

    @TelldusLive.handler('event-conditionresult')
    def receiveConditionResultFromServer(self, msg):
        data = msg.argument(0).toNative()
        for eid in self.events:
            event = self.events[eid]
            for cgid in event.conditions:
                conditionGroup = event.conditions[cgid]
                for cid in conditionGroup.conditions:
                    if cid == data['condition']:
                        try:
                            conditionGroup.conditions[
                                cid].receivedResultFromServer(data['status'])
                        except AttributeError:
                            # Not a RemoteCondition
                            pass
                        return
Beispiel #2
0
class DeviceManager(Plugin):
    """The devicemanager holds and manages all the devices in the server"""
    implements(ITelldusLiveObserver)

    observers = ObserverCollection(IDeviceChange)

    public = True

    def __init__(self):
        self.devices = []
        self.settings = Settings('telldus.devicemanager')
        self.nextId = self.settings.get('nextId', 0)
        self.live = TelldusLive(self.context)
        self.registered = False
        self.__load()

    @mainthread
    def addDevice(self, device):
        """
		Call this function to register a new device to the device manager.

		.. note::
		    The :func:`Device.localId() <telldus.Device.localId>` function in the device must return
		    a unique id for the transport type returned by
		    :func:`Device.typeString() <telldus.Device.typeString>`
		"""
        cachedDevice = None
        for i, delDevice in enumerate(self.devices):
            # Delete the cached device from loaded devices, since it is replaced
            # by a confirmed/specialised one
            if delDevice.localId() == device.localId() \
               and device.typeString() == delDevice.typeString() \
               and not delDevice.confirmed():
                cachedDevice = delDevice
                del self.devices[i]
                break
        self.devices.append(device)
        device.setManager(self)

        if not cachedDevice:  # New device, not stored in local cache
            self.nextId = self.nextId + 1
            device.setId(self.nextId)
        else:  # Transfer parameters from the loaded one
            device.loadCached(cachedDevice)
        self.save()

        if not cachedDevice:
            self.__deviceAdded(device)
            if self.live.registered and device.isDevice():
                (state, stateValue) = device.state()
                parameters = json.dumps(device.allParameters(),
                                        separators=(',', ':'),
                                        sort_keys=True)
                deviceDict = {
                    'id': device.id(),
                    'uuid': device.uuidAsString(),
                    'name': device.name(),
                    'methods': device.methods(),
                    'state': state,
                    'stateValue': stateValue,
                    'stateValues': device.stateValues(),
                    'protocol': device.protocol(),
                    'model': device.model(),
                    'parameters': parameters,
                    'parametersHash': hashlib.sha1(parameters).hexdigest(),
                    'transport': device.typeString()
                }
                msg = LiveMessage("DeviceAdded")
                msg.append(deviceDict)
                self.live.send(msg)
        else:
            # Previously cached device is now confirmed, TODO notify Live! about this too?
            self.observers.deviceConfirmed(device)

    def device(self, deviceId):
        """Retrieves a device.

		:param int deviceId: The id of the device to be returned.
		:returns: the device specified by `deviceId` or None of no device was found
		"""
        for device in self.devices:
            if device.id() == deviceId:
                return device
        return None

    def deviceMetadataUpdated(self, device, param):
        self.save()
        if param and param != '':
            sendParameters = False
            if param == 'devicetype':
                # This effects device parameters also
                sendParameters = True
            self.__sendDeviceParameterReport(device,
                                             sendParameters=sendParameters,
                                             sendMetadata=True)

    def deviceParamUpdated(self, device, param):
        self.save()
        self.__deviceUpdated(device, [param])
        if param == 'name':
            if device.isDevice():
                self.__sendDeviceReport()
            if device.isSensor:
                self.__sendSensorReport()
            return
        if param and param != '':
            self.__sendDeviceParameterReport(device,
                                             sendParameters=True,
                                             sendMetadata=False)

    def findByName(self, name):
        for device in self.devices:
            if device.name() == name:
                return device
        return None

    @mainthread
    def finishedLoading(self, deviceType):
        """
		Finished loading all devices of this type. If there are any unconfirmed,
		these should be deleted
		"""
        for device in self.devices:
            if device.typeString() == deviceType and not device.confirmed():
                self.removeDevice(device.id())

    @mainthread
    def removeDevice(self, deviceId):
        """
		Removes a device.

		.. warning::
		    This function may only be called by the module supplying the device
		    since removing of a device may be transport specific.
		"""
        isDevice = True
        for i, device in enumerate(self.devices):
            if device.id() == deviceId:
                self.__deviceRemoved(deviceId)
                isDevice = self.devices[i].isDevice()
                del self.devices[i]
                break
        self.save()
        if self.live.registered and isDevice:
            msg = LiveMessage("DeviceRemoved")
            msg.append({'id': deviceId})
            self.live.send(msg)

    @mainthread
    def removeDevicesByType(self, deviceType):
        """
		.. versionadded:: 1.1.0

		Remove all devices of a specific device type

		:param str deviceType: The type of devices to remove
		"""
        deviceIds = []
        for device in self.devices:
            if device.typeString() == deviceType:
                deviceIds.append(device.id())
        for deviceId in deviceIds:
            self.removeDevice(deviceId)

    def retrieveDevices(self, deviceType=None):
        """Retrieve a list of devices.

		:param deviceType: If this parameter is set only devices with this type is returned
		:type deviceType: str or None
		:returns: a list of devices
		"""
        lst = []
        for device in self.devices:
            if deviceType is not None and device.typeString() != deviceType:
                continue
            lst.append(device)
        return lst

    @signal
    def sensorValueUpdated(self, device, valueType, value, scale):
        """
		Called every time a sensor value is updated.

		:param device: The device/sensor being updated
		:param valueType: Type of updated value
		:param value: The updated value
		:param scale: Scale of updated value
		"""
        pass

    @signal
    def sensorValuesUpdated(self, device, values):
        """
		Called every time a sensor value is updated.

		:param device: The device/sensor being updated
		:param values: A list with all values updated
		"""
        if device.isSensor() is False:
            return
        shouldUpdateLive = False
        for valueElement in values:
            valueType = valueElement['type']
            value = valueElement['value']
            scale = valueElement['scale']
            self.observers.sensorValueUpdated(device, valueType, value, scale)
            self.sensorValueUpdated(device, valueType, value, scale)
            if valueType not in device.lastUpdatedLive \
               or valueType not in device.valueChangedTime \
               or device.valueChangedTime[valueType] > device.lastUpdatedLive[valueType] \
               or device.lastUpdatedLive[valueType] < (int(time.time()) - 300):
                shouldUpdateLive = True
                break

        if not self.live.registered or device.ignored(
        ) or not shouldUpdateLive:
            # don't send if not connected to live, sensor is ignored or if
            # values haven't changed and five minutes hasn't passed yet
            return

        msg = LiveMessage("SensorEvent")
        # pcc = packageCountChecked - already checked package count,
        # just accept it server side directly
        sensor = {
            'name': device.name(),
            'protocol': device.protocol(),
            'model': device.model(),
            'sensor_id': device.id(),
            'pcc': 1,
        }

        battery = device.battery()
        if battery is not None:
            sensor['battery'] = battery
        msg.append(sensor)
        # small clarification: valueType etc that is sent in here is only used for sending
        # information about what have changed on to observers, below is instead all the values
        # of the sensor picked up and sent in a sensor event-message (the sensor values
        # have already been updated in other words)
        values = device.sensorValues()
        valueList = []
        for valueType in values:
            device.lastUpdatedLive[valueType] = int(time.time())
            for value in values[valueType]:
                valueList.append({
                    'type': valueType,
                    'lastUp': str(value['lastUpdated']),
                    'value': str(value['value']),
                    'scale': value['scale']
                })
        msg.append(valueList)
        self.live.send(msg)

    def stateUpdated(self,
                     device,
                     ackId=None,
                     origin=None,
                     executedState=None,
                     executedStateValue=None):
        if device.isDevice() is False:
            return
        (state, stateValue) = device.state()
        extras = {'deviceState': state, 'stateValues': device.stateValues()}
        if ackId:
            extras['ACK'] = ackId
        if origin:
            extras['origin'] = origin
        else:
            extras['origin'] = 'Incoming signal'
        if executedStateValue:
            stateValue = executedStateValue
        elif executedState and executedState != state:
            # "state" is the current state of the device, but not
            # what was actually executed
            stateValue = device.stateValue(executedState)

        self.__deviceStateChanged(device, executedState, stateValue,
                                  extras['origin'])

        if isinstance(stateValue, dict):
            stateValue = json.dumps(stateValue)
        else:
            stateValue = str(stateValue)
        self.save()

        if not self.live.registered:
            return
        msg = LiveMessage("DeviceEvent")
        msg.append(device.id())
        msg.append(executedState)
        msg.append(str(stateValue))
        msg.append(extras)
        self.live.send(msg)

    """
	Send report to Live server that state change failed
	"""

    def stateUpdatedFail(self, device, state, stateValue, reason, origin):
        if not self.live.registered:
            return
        if device.isDevice() is False:
            return
        if not reason:
            reason = Device.FAILED_STATUS_UNKNOWN
        extras = {
            'reason': reason,
        }
        if origin:
            extras['origin'] = origin
        else:
            extras['origin'] = 'Unknown'
        msg = LiveMessage('DeviceFailEvent')
        msg.append(device.id())
        msg.append(state)
        msg.append(stateValue)
        msg.append(extras)
        self.live.send(msg)

    @TelldusLive.handler('command')
    def __handleCommand(self, msg):
        args = msg.argument(0).toNative()
        action = args['action']
        value = args['value'] if 'value' in args else None
        deviceId = args['id']
        originId = None
        extras = msg.argument(1).toNative()
        if extras and 'origin' in extras:
            originId = extras['origin']
        device = None
        for dev in self.devices:
            if dev.id() == deviceId:
                device = dev
                break

        def success(state, stateValue):
            if 'ACK' in args:
                device.setState(state,
                                stateValue,
                                ack=args['ACK'],
                                executedStateValue=value)
                # Abort the DeviceEvent this triggered
                raise DeviceAbortException()

        def fail(reason):
            # We failed to set status for some reason, nack the server
            if not reason:
                # reason 0 will count as success in device log history
                reason = Device.FAILED_STATUS_UNKNOWN
            if 'ACK' in args:
                msg = LiveMessage('NACK')
                msg.append({
                    'ackid': args['ACK'],
                    'reason': reason,
                })
                self.live.send(msg)
                # Abort the DeviceEvent this triggered
                raise DeviceAbortException()

        device.command(action,
                       value,
                       success=success,
                       failure=fail,
                       origin=originId)

    @TelldusLive.handler('device')
    def __handleDeviceCommand(self, msg):
        args = msg.argument(0).toNative()
        if 'action' not in args:
            return
        if args['action'] == 'setName':
            if 'name' not in args:
                return
            for dev in self.devices:
                if dev.id() != args['device']:
                    continue
                if isinstance(args['name'], int):
                    dev.setName(str(args['name']))
                else:
                    dev.setName(args['name'].decode('UTF-8'))
                return
        elif args['action'] in ('setParameter', 'setMetadata'):
            device = None
            for dev in self.devices:
                if dev.id() == args['device']:
                    device = dev
                    break
            if device is None:
                return
            name = args.get('name', '')
            value = args.get('value', '')
            if args['action'] == 'setParameter':
                if name == 'room':
                    device.setRoom(value)
                else:
                    device.setParameter(name, value)
            else:
                device.setMetadata(name, value)

    @TelldusLive.handler('device-requestdata')
    def __handleDeviceParametersRequest(self, msg):
        args = msg.argument(0).toNative()
        device = self.device(args.get('id', 0))
        if not device:
            return
        sendParameters = True if args.get('parameters', 0) == 1 else False
        sendMetadata = True if args.get('metadata', 0) == 1 else False
        self.__sendDeviceParameterReport(device, sendParameters, sendMetadata)

    @TelldusLive.handler('reload')
    def __handleSensorUpdate(self, msg):
        reloadType = msg.argument(0).toNative()
        if reloadType != 'sensor':
            # not for us
            return
        data = msg.argument(1).toNative()
        if not msg.argument(2) or 'sensorId' not in msg.argument(2).toNative():
            # nothing to do, might be an orphaned zwave sensor
            return
        sensorId = msg.argument(2).toNative()['sensorId']
        updateType = data['type']
        for dev in self.devices:
            if dev.id() == sensorId:
                if updateType == 'updateignored':
                    value = data['ignored']
                    dev.setIgnored(value)
                self.__sendSensorChange(sensorId, updateType, value)
                return
        if updateType == 'updateignored' and len(self.devices) > 0:
            # we don't have this sensor, do something! (can't send sensor change
            # back (__sendSensorChange), because can't create message when
            # sensor is unknown (could create special workaround, but only do
            # that if it's still a problem in the future))
            logging.warning(
                'Requested ignore change for non-existing sensor %s',
                str(sensorId))
            # send an updated sensor report, so that this sensor is hopefully
            # cleaned up
            self.__sendSensorReport()

    def liveRegistered(self, __msg, refreshRequired):
        self.registered = True
        self.__sendDeviceReport()
        self.__sendSensorReport()

    def __load(self):
        self.store = self.settings.get('devices', [])
        for dev in self.store:
            if 'type' not in dev or 'localId' not in dev:
                continue  # This should not be possible
            device = CachedDevice(dev)
            # If we have loaded this device from cache 5 times in a row it's
            # considered dead
            if device.loadCount() < 5:
                self.devices.append(device)

    @signal('deviceAdded')
    def __deviceAdded(self, device):
        """
		Called every time a device is added/created
		"""
        self.observers.deviceAdded(device)

    @signal('deviceRemoved')
    def __deviceRemoved(self, deviceId):
        """
		Called every time a device is removed. The parameter deviceId is the old
		device id. The ref to the device is no longer available
		"""
        self.observers.deviceRemoved(deviceId)

    @signal('deviceUpdated')
    def __deviceUpdated(self, device, parameters):
        """
		Called every time a device parameter is updated
		"""
        self.observers.deviceUpdated(device, parameters)

    @signal('deviceStateChanged')
    def __deviceStateChanged(self, device, state, stateValue, origin):
        """
		Called every time the state of a device is changed.
		"""
        del origin  # Remove pylint warning
        self.observers.stateChanged(device, state, stateValue)

    def save(self):
        data = []
        for device in self.devices:
            (state, __stateValue) = device.state()
            stateValues = device.stateValues()
            dev = {
                "id": device.id(),
                "uuid": device.getOrCreateUUID(),
                "loadCount": device.loadCount(),
                "localId": device.localId(),
                "type": device.typeString(),
                "name": device.name(),
                "params": device.params(),
                "metadata": device.metadata(),
                "methods": device.methods(),
                "state": state,
                "stateValues": stateValues,
                "ignored": device.ignored(),
                "isSensor": device.isSensor()
            }
            if len(device.sensorValues()) > 0:
                dev['sensorValues'] = device.sensorValues()
            battery = device.battery()
            if battery is not None:
                dev['battery'] = battery
            if hasattr(device, 'declaredDead') and device.declaredDead:
                dev['declaredDead'] = device.declaredDead
            data.append(dev)
        self.settings['devices'] = data
        self.settings['nextId'] = self.nextId

    def __sendDeviceReport(self):
        logging.warning("Send Devices Report")
        if not self.live.registered:
            return
        lst = []
        for device in self.devices:
            if not device.isDevice():
                continue
            (state, stateValue) = device.state()
            if isinstance(stateValue, dict):
                stateValue = json.dumps(stateValue)
            else:
                stateValue = str(stateValue)
            parametersHash = hashlib.sha1(
                json.dumps(device.allParameters(),
                           separators=(',', ':'),
                           sort_keys=True))
            metadataHash = hashlib.sha1(
                json.dumps(device.metadata(),
                           separators=(',', ':'),
                           sort_keys=True))
            dev = {
                'id': device.id(),
                'uuid': device.uuidAsString(),
                'name': device.name(),
                'methods': device.methods(),
                'state': state,
                'stateValue': stateValue,
                'stateValues': device.stateValues(),
                'protocol': device.protocol(),
                'model': device.model(),
                'parametersHash': parametersHash.hexdigest(),
                'metadataHash': metadataHash.hexdigest(),
                'transport': device.typeString(),
                'ignored': device.ignored()
            }
            battery = device.battery()
            if battery is not None:
                dev['battery'] = battery
            lst.append(dev)
        msg = LiveMessage("DevicesReport")
        msg.append(lst)
        self.live.send(msg)

    def __sendSensorChange(self, sensorid, valueType, value):
        msg = LiveMessage("SensorChange")
        device = None
        for dev in self.devices:
            if dev.id() == sensorid:
                device = dev
                break
        if not device:
            return
        sensor = {
            'protocol': device.typeString(),
            'model': device.model(),
            'sensor_id': device.id(),
        }
        msg.append(sensor)
        msg.append(valueType)
        msg.append(value)
        self.live.send(msg)

    def __sendDeviceParameterReport(self, device, sendParameters,
                                    sendMetadata):
        reply = LiveMessage('device-datareport')
        data = {'id': device.id()}
        if sendParameters:
            parameters = json.dumps(device.allParameters(),
                                    separators=(',', ':'),
                                    sort_keys=True)
            data['parameters'] = parameters
            data['parametersHash'] = hashlib.sha1(parameters).hexdigest()
        if sendMetadata:
            metadata = json.dumps(device.metadata(),
                                  separators=(',', ':'),
                                  sort_keys=True)
            data['metadata'] = metadata
            data['metadataHash'] = hashlib.sha1(metadata).hexdigest()
        reply.append(data)
        self.live.send(reply)

    def __sendSensorReport(self):
        if not self.live.registered:
            return
        lst = []
        for device in self.devices:
            if device.isSensor() is False:
                continue
            sensorFrame = []
            sensor = {
                'name': device.name(),
                'protocol': device.protocol(),
                'model': device.model(),
                'sensor_id': device.id(),
            }
            if device.params() and 'sensorId' in device.params():
                sensor['channelId'] = device.params()['sensorId']

            battery = device.battery()
            if battery is not None:
                sensor['battery'] = battery
            if hasattr(device, 'declaredDead') and device.declaredDead:
                # Sensor shouldn't be removed for a while, but don't update it on server side
                sensor['declaredDead'] = 1
            sensorFrame.append(sensor)
            valueList = []
            values = device.sensorValues()
            for valueType in values:
                for value in values[valueType]:
                    valueList.append({
                        'type': valueType,
                        'lastUp': str(value['lastUpdated']),
                        'value': str(value['value']),
                        'scale': value['scale']
                    })
                    # Telldus Live! does not aknowledge sensorreportupdates yet,
                    # so don't count this yet (wait for Cassandra only)
                    # device.lastUpdatedLive[valueType] = int(time.time())
            sensorFrame.append(valueList)
            lst.append(sensorFrame)
        msg = LiveMessage("SensorsReport")
        msg.append(lst)
        self.live.send(msg)

    def sensorsUpdated(self):
        self.__sendSensorReport()
class DeviceManager(Plugin):
    """The devicemanager holds and manages all the devices in the server"""
    implements(ITelldusLiveObserver)

    observers = ObserverCollection(IDeviceChange)

    def __init__(self):
        self.devices = []
        self.s = Settings('telldus.devicemanager')
        self.nextId = self.s.get('nextId', 0)
        self.live = TelldusLive(self.context)
        self.registered = False
        self.__load()

    @mainthread
    def addDevice(self, device):
        """Call this function to register a new device to the device manager.

		.. note::
		    The :func:`localId` function in the device must return a unique id for
		    the transport type returned by :func:`typeString`
		"""
        cachedDevice = None
        for i, delDevice in enumerate(self.devices):
            # Delete the cached device from loaded devices, since it is replaced by a confirmed/specialised one
            if delDevice.localId() == device.localId() and device.typeString(
            ) == delDevice.typeString() and not delDevice.confirmed():
                cachedDevice = delDevice
                del self.devices[i]
                break
        self.devices.append(device)
        device.setManager(self)

        if not cachedDevice:  # New device, not stored in local cache
            self.nextId = self.nextId + 1
            device.setId(self.nextId)
        else:  # Transfer parameters from the loaded one
            device.loadCached(cachedDevice)
        self.save()

        if not cachedDevice:
            self.__deviceAdded(device)
            if self.live.registered and device.isDevice():
                (state, stateValue) = device.state()
                deviceDict = {
                    'id': device.id(),
                    'name': device.name(),
                    'methods': device.methods(),
                    'state': state,
                    'stateValue': stateValue,
                    'protocol': device.protocol(),
                    'model': device.model(),
                    'transport': device.typeString()
                }
                msg = LiveMessage("DeviceAdded")
                msg.append(deviceDict)
                self.live.send(msg)
        else:
            # Previously cached device is now confirmed, TODO notify Live! about this too?
            self.observers.deviceConfirmed(device)

    def device(self, deviceId):
        """Retrieves a device.

		Returns:
		  the device specified by `deviceId` or None of no device was found
		"""
        for d in self.devices:
            if d.id() == deviceId:
                return d
        return None

    def findByName(self, name):
        for d in self.devices:
            if d.name() == name:
                return d
        return None

    @mainthread
    def finishedLoading(self, type):
        """ Finished loading all devices of this type. If there are any unconfirmed, these should be deleted """
        for device in self.devices:
            if device.typeString() == type and not device.confirmed():
                self.removeDevice(device.id())

    @mainthread
    def removeDevice(self, deviceId):
        """Removes a device.

		.. warning::
		    This function may only be called by the module supplying the device
		    since removing of a device may be transport specific.
		"""
        isDevice = True
        for i, device in enumerate(self.devices):
            if device.id() == deviceId:
                self.__deviceRemoved(deviceId)
                isDevice = self.devices[i].isDevice()
                del self.devices[i]
                break
        self.save()
        if self.live.registered and isDevice:
            msg = LiveMessage("DeviceRemoved")
            msg.append({'id': deviceId})
            self.live.send(msg)

    def retrieveDevices(self, deviceType=None):
        """Retrieve a list of devices.

		Args:
		    :deviceType: If this parameter is set only devices with this type is returned

		Returns:
		    Returns a list of devices
		"""
        l = []
        for d in self.devices:
            if deviceType is not None and d.typeString() != deviceType:
                continue
            l.append(d)
        return l

    @signal
    def sensorValueUpdated(self, device, valueType, value, scale):
        """
		Called every time a sensors value is updated.
		"""
        if device.isSensor() == False:
            return
        self.observers.sensorValueUpdated(device, valueType, value, scale)
        if not self.live.registered or device.ignored():
            # don't send if not connected to live or sensor is ignored
            return
        if valueType in device.lastUpdatedLive and (
                valueType in device.valueChangedTime
                and device.valueChangedTime[valueType] <
                device.lastUpdatedLive[valueType]
        ) and device.lastUpdatedLive[valueType] > (int(time.time()) - 300):
            # no values have changed since the last live-update, and the last time this sensor was sent to live was less than 5 minutes ago
            return

        msg = LiveMessage("SensorEvent")
        sensor = {
            'name': device.name(),
            'protocol': device.protocol(),
            'model': device.model(),
            'sensor_id': device.id(),
            'pcc': 1,
        }  # pcc = packageCountChecked - already checked package count, just accept it server side directly

        battery = device.battery()
        if battery is not None:
            sensor['battery'] = battery
        msg.append(sensor)
        # small clarification: valueType etc that is sent in here is only used for sending
        # information about what have changed on to observers, below is instead all the values
        # of the sensor picked up and sent in a sensor event-message (the sensor values
        # have already been updated in other words)
        values = device.sensorValues()
        valueList = []
        for vt in values:
            for value in values[vt]:
                valueList.append({
                    'type': vt,
                    'lastUp': str(value['lastUpdated']),
                    'value': str(value['value']),
                    'scale': value['scale']
                })
        msg.append(valueList)
        device.lastUpdatedLive[valueType] = int(time.time())
        self.live.send(msg)

    def stateUpdated(self, device, ackId=None, origin=None):
        if device.isDevice() == False:
            return
        extras = {}
        if ackId:
            extras['ACK'] = ackId
        if origin:
            extras['origin'] = origin
        else:
            extras['origin'] = 'Incoming signal'
        (state, stateValue) = device.state()
        self.__deviceStateChanged(device, state, stateValue)
        self.save()
        if not self.live.registered:
            return
        msg = LiveMessage("DeviceEvent")
        msg.append(device.id())
        msg.append(state)
        msg.append(str(stateValue))
        msg.append(extras)
        self.live.send(msg)

    def stateUpdatedFail(self, device, state, stateValue, reason, origin):
        if not self.live.registered:
            return
        if device.isDevice() == False:
            return
        extras = {
            'reason': reason,
        }
        if origin:
            extras['origin'] = origin
        else:
            extras['origin'] = 'Unknown'
        (state, stateValue) = device.state()
        self.__deviceStateChanged(device, state, stateValue)
        msg = LiveMessage('DeviceFailEvent')
        msg.append(device.id())
        msg.append(state)
        msg.append(stateValue)
        msg.append(extras)
        self.live.send(msg)

    @TelldusLive.handler('command')
    def __handleCommand(self, msg):
        args = msg.argument(0).toNative()
        action = args['action']
        value = args['value'] if 'value' in args else None
        id = args['id']
        device = None
        for dev in self.devices:
            if dev.id() == id:
                device = dev
                break

        def success(state, stateValue):
            if 'ACK' in args:
                device.setState(state, stateValue, ack=args['ACK'])
                # Abort the DeviceEvent this triggered
                raise DeviceAbortException()

        def fail(reason):
            # We failed to set status for some reason, nack the server
            if 'ACK' in args:
                msg = LiveMessage('NACK')
                msg.append({
                    'ackid': args['ACK'],
                    'reason': reason,
                })
                self.live.send(msg)
                # Abort the DeviceEvent this triggered
                raise DeviceAbortException()

        device.command(action, value, success=success, failure=fail)

    @TelldusLive.handler('device')
    def __handleDeviceCommand(self, msg):
        args = msg.argument(0).toNative()
        if 'action' not in args:
            return
        if args['action'] == 'setName':
            if 'name' not in args or args['name'] == '':
                return
            for dev in self.devices:
                if dev.id() != args['device']:
                    continue
                if type(args['name']) is int:
                    dev.setName(str(args['name']))
                else:
                    dev.setName(args['name'].decode('UTF-8'))
                if dev.isDevice():
                    self.__sendDeviceReport()
                if dev.isSensor:
                    self.__sendSensorReport()
                return

    @TelldusLive.handler('reload')
    def __handleSensorUpdate(self, msg):
        reloadType = msg.argument(0).toNative()
        if reloadType != 'sensor':
            # not for us
            return
        data = msg.argument(1).toNative()
        if not msg.argument(2) or 'sensorId' not in msg.argument(2).toNative():
            # nothing to do, might be an orphaned zwave sensor
            return
        sensorId = msg.argument(2).toNative()['sensorId']
        updateType = data['type']
        for dev in self.devices:
            if dev.id() == sensorId:
                if updateType == 'updateignored':
                    value = data['ignored']
                    if dev.ignored() == value:
                        return
                    dev.setIgnored(value)
                self.__sendSensorChange(sensorId, updateType, value)
                return
        if updateType == 'updateignored' and len(self.devices) > 0:
            # we don't have this sensor, do something! (can't send sensor change back (__sendSensorChange), because can't create message when sensor is unknown (could create special workaround, but only do that if it's still a problem in the future))
            logging.warning(
                'Requested ignore change for non-existing sensor %s',
                str(sensorId))
            self.__sendSensorReport(
            )  # send an updated sensor report, so that this sensor is hopefully cleaned up

    def liveRegistered(self, msg):
        self.registered = True
        self.__sendDeviceReport()
        self.__sendSensorReport()

    def __load(self):
        self.store = self.s.get('devices', [])
        for dev in self.store:
            if 'type' not in dev or 'localId' not in dev:
                continue  # This should not be possible
            d = CachedDevice(dev)
            # If we have loaded this device from cache 5 times in a row it's
            # considered dead
            if d.loadCount() < 5:
                self.devices.append(d)

    @signal('deviceAdded')
    def __deviceAdded(self, device):
        """
		Called every time a device is added/created
		"""
        self.observers.deviceAdded(device)

    @signal('deviceRemoved')
    def __deviceRemoved(self, deviceId):
        """
		Called every time a device is removed. The parameter deviceId is the old
		device id. The ref to the device is no longer available
		"""
        self.observers.deviceRemoved(deviceId)

    @signal('deviceStateChanged')
    def __deviceStateChanged(self, device, state, stateValue):
        """
		Called every time the state of a device is changed.
		"""
        self.observers.stateChanged(device, state, stateValue)

    def save(self):
        data = []
        for d in self.devices:
            (state, stateValue) = d.state()
            dev = {
                "id": d.id(),
                "loadCount": d.loadCount(),
                "localId": d.localId(),
                "type": d.typeString(),
                "name": d.name(),
                "params": d.params(),
                "methods": d.methods(),
                "state": state,
                "stateValue": stateValue,
                "ignored": d.ignored(),
                "isSensor": d.isSensor()
            }
            if len(d.sensorValues()) > 0:
                dev['sensorValues'] = d.sensorValues()
            battery = d.battery()
            if battery is not None:
                dev['battery'] = battery
            data.append(dev)
        self.s['devices'] = data
        self.s['nextId'] = self.nextId

    def __sendDeviceReport(self):
        if not self.live.registered:
            return
        l = []
        for d in self.devices:
            if not d.isDevice():
                continue
            (state, stateValue) = d.state()
            device = {
                'id': d.id(),
                'name': d.name(),
                'methods': d.methods(),
                'state': state,
                'stateValue': stateValue,
                'protocol': d.protocol(),
                'model': d.model(),
                'transport': d.typeString(),
                'ignored': d.ignored()
            }
            battery = d.battery()
            if battery is not None:
                device['battery'] = battery
            l.append(device)
        msg = LiveMessage("DevicesReport")
        msg.append(l)
        self.live.send(msg)

    def __sendSensorChange(self, sensorid, valueType, value):
        msg = LiveMessage("SensorChange")
        device = None
        for d in self.devices:
            if d.id() == sensorid:
                device = d
                break
        if not device:
            return
        sensor = {
            'protocol': device.typeString(),
            'model': device.model(),
            'sensor_id': device.id(),
        }
        msg.append(sensor)
        msg.append(valueType)
        msg.append(value)
        self.live.send(msg)

    def __sendSensorReport(self):
        if not self.live.registered:
            return
        l = []
        for d in self.devices:
            if d.isSensor() == False:
                continue
            sensorFrame = []
            sensor = {
                'name': d.name(),
                'protocol': d.protocol(),
                'model': d.model(),
                'sensor_id': d.id(),
            }
            battery = d.battery()
            if battery is not None:
                sensor['battery'] = battery
            sensorFrame.append(sensor)
            valueList = []
            values = d.sensorValues()
            for valueType in values:
                for value in values[valueType]:
                    valueList.append({
                        'type': valueType,
                        'lastUp': str(value['lastUpdated']),
                        'value': str(value['value']),
                        'scale': value['scale']
                    })
                    #d.lastUpdatedLive[valueType] = int(time.time())  # Telldus Live! does not aknowledge sensorreportupdates yet, so don't count this yet (wait for Cassandra only)
            sensorFrame.append(valueList)
            l.append(sensorFrame)
        msg = LiveMessage("SensorsReport")
        msg.append(l)
        self.live.send(msg)

    def sensorsUpdated(self):
        self.__sendSensorReport()
Beispiel #4
0
class RequestHandler(object):
    observers = ObserverCollection(IWebRequestHandler)

    def __init__(self, context):
        self.templates = None
        self.context = context

    @staticmethod
    def loadTemplate(filename, dirs):
        if not isinstance(dirs, list):
            dirs = []
        dirs.append(resource_filename('web', 'templates'))
        templates = TemplateLoader(dirs)
        return templates.load(filename)

    def handle(self, plugin, path, **params):
        path = '/'.join(path)
        # First check for the file in htdocs
        try:
            if plugin != '' and \
               resource_exists(plugin, 'htdocs/' + path) and \
               resource_isdir(plugin, 'htdocs/' + path) is False:
                mimetype, __encoding = mimetypes.guess_type(path, strict=False)
                if mimetype is not None:
                    cherrypy.response.headers['Content-Type'] = mimetype
                return resource_stream(plugin, 'htdocs/' + path)
        except Exception as __error:
            pass
        menu = []
        for observer in self.observers:
            arr = observer.getMenuItems()
            if isinstance(arr, list):
                menu.extend(arr)
        template = None
        templateDirs = []
        response = None
        request = WebRequest(cherrypy.request)
        for observer in self.observers:
            if not observer.matchRequest(plugin, path):
                continue
            requireAuth = observer.requireAuthentication(plugin, path)
            if requireAuth != False:
                WebRequestHandler(self.context).isUrlAuthorized(request)
            response = observer.handleRequest(plugin,
                                              path,
                                              params,
                                              request=request)
            templateDirs = observer.getTemplatesDirs()
            break
        if response is None:
            raise cherrypy.NotFound()
        if isinstance(response, WebResponseRedirect):
            if response.url[:4] == 'http':
                raise cherrypy.HTTPRedirect(response.url)
            raise cherrypy.HTTPRedirect(
                '/%s%s%s' %
                (plugin, '' if response.url[0] == '/' else '/', response.url))
        elif isinstance(response, WebResponse):
            if isinstance(response, WebResponseHtml):
                response.setDirs(plugin, templateDirs)
            cherrypy.response.status = response.statusCode
            response.output(cherrypy.response)
            return response.data
        template, data = response
        if template is None:
            raise cherrypy.NotFound()
        tmpl = self.loadTemplate(template, templateDirs)
        data['menu'] = menu
        stream = tmpl.generate(title='TellStick ZNet', **data)
        return stream.render('html', doctype='html')

    def __call__(self, plugin='', *args, **kwargs):
        if plugin == 'ws':
            # Ignore, this is for websocket
            return
        path = [x for x in args]
        return self.handle(plugin, path, **kwargs)
class TelldusLive(Plugin):
    implements(ISignalObserver)
    observers = ObserverCollection(ITelldusLiveObserver)

    def __init__(self):
        logging.info("Telldus Live! loading")
        self.email = ''
        self.supportedMethods = 0
        self.connected = False
        self.refreshRequired = True
        self.registered = False
        self.running = False
        self.serverList = ServerList()
        self.lastBackedUpConfig = 0
        Application().registerShutdown(self.stop)
        self.settings = Settings('tellduslive.config')
        self.uuid = self.settings['uuid']
        self.conn = ServerConnection()
        self.pingTimer = 0
        self.thread = threading.Thread(target=self.run)
        if self.conn.publicKey != '':
            # Only connect if the keys has been set.
            self.thread.start()

    @slot('configurationWritten')
    def configurationWritten(self, path):
        if time.time() - self.lastBackedUpConfig < 86400:
            # Only send the backup once per day
            return
        self.lastBackedUpConfig = time.time()
        uploadPath = 'http://%s/upload/config' % Board.liveServer()
        logging.info('Upload backup to %s', uploadPath)
        with open(path, 'rb') as fd:
            fileData = fd.read()
        fileData = bz2.compress(fileData)  # Compress it
        fileData = TelldusLive.deviceSpecificEncrypt(fileData)  # Encrypt it
        requests.post(uploadPath,
                      data={'mac': Board.getMacAddr()},
                      files={'Telldus.conf.bz2': fileData})

    @mainthread
    def handleMessage(self, message):
        if (message.name() == "notregistered"):
            self.email = ''
            self.connected = True
            self.registered = False
            params = message.argument(0).dictVal
            self.uuid = params['uuid'].stringVal
            self.settings['uuid'] = self.uuid
            logging.info(
                "This client isn't activated, please activate it using this url:\n%s",
                params['url'].stringVal)
            self.liveConnected()
            return

        if (message.name() == "registered"):
            self.connected = True
            self.registered = True
            data = message.argument(0).toNative()
            if 'email' in data:
                self.email = data['email']
            if 'uuid' in data and data['uuid'] != self.uuid:
                self.uuid = data['uuid']
                self.settings['uuid'] = self.uuid
            self.liveRegistered(data, self.refreshRequired)
            self.refreshRequired = False
            return

        if (message.name() == "command"):
            # Extract ACK and handle it
            args = message.argument(0).dictVal
            if 'ACK' in args:
                msg = LiveMessage("ACK")
                msg.append(args['ACK'].intVal)
                self.send(msg)

        if (message.name() == "pong"):
            return

        if (message.name() == "disconnect"):
            self.conn.close()
            self.__disconnected()
            return

        handled = False
        for observer in self.observers:
            for func in getattr(observer, '_telldusLiveHandlers',
                                {}).get(message.name(), []):
                func(observer, message)
                handled = True
        if not handled:
            logging.warning("Did not understand: %s", message.toByteArray())

    def isConnected(self):
        return self.connected

    def isRegistered(self):
        return self.registered

    @signal
    def liveConnected(self):
        """This signal is sent when we have succesfully connected to a Live! server"""
        self.observers.liveConnected()

    @signal
    def liveDisconnected(self):
        """
		This signal is sent when we are disconnected. Please note that it is normal for it di be
		disconnected and happens regularly
		"""
        self.observers.liveDisconnected()

    @signal
    def liveRegistered(self, options, refreshRequired):
        """This signal is sent when we have succesfully registered with a Live! server"""
        self.observers.liveRegistered(options, refreshRequired)

    def run(self):
        self.running = True

        wait = 0
        pongTimer, self.pingTimer = (0, 0)
        while self.running:
            if wait > 0:
                wait = wait - 1
                time.sleep(1)
                continue
            state = self.conn.process()
            if state == ServerConnection.CLOSED:
                server = self.serverList.popServer()
                if not server:
                    wait = random.randint(60, 300)
                    logging.warning("No servers found, retry in %i seconds",
                                    wait)
                    continue
                if not self.conn.connect(server['address'], int(
                        server['port'])):
                    wait = random.randint(60, 300)
                    logging.warning("Could not connect, retry in %i seconds",
                                    wait)

            elif state == ServerConnection.CONNECTED:
                if (pongTimer + 43200) < time.time():
                    # 12 hours since last online
                    self.refreshRequired = True
                pongTimer, self.pingTimer = (time.time(), time.time())
                self.__sendRegisterMessage()

            elif state == ServerConnection.MSG_RECEIVED:
                msg = self.conn.popMessage()
                if msg is None:
                    continue
                pongTimer = time.time()
                self.handleMessage(msg)

            elif state == ServerConnection.DISCONNECTED:
                wait = random.randint(10, 50)
                logging.warning("Disconnected, reconnect in %i seconds", wait)
                self.__disconnected()

            else:
                if (time.time() - pongTimer >= 360):  # No pong received
                    self.conn.close()
                    wait = random.randint(10, 50)
                    logging.warning(
                        "No pong received, disconnecting. Reconnect in %i seconds",
                        wait)
                    self.__disconnected()
                elif (time.time() - self.pingTimer >= 120):
                    # Time to ping
                    self.conn.send(LiveMessage("Ping"))
                    self.pingTimer = time.time()

    def stop(self):
        self.running = False

    def send(self, message):
        self.conn.send(message)
        self.pingTimer = time.time()

    def pushToWeb(self, module, action, data):
        msg = LiveMessage("sendToWeb")
        msg.append(module)
        msg.append(action)
        msg.append(data)
        self.send(msg)

    def __disconnected(self):
        self.email = ''
        self.connected = False
        self.registered = False

        def sendNotification():
            self.liveDisconnected()

        # Syncronize signal with main thread
        Application().queue(sendNotification)

    @staticmethod
    def handler(message):
        def call(func):
            import sys
            frame = sys._getframe(1)  # pylint: disable=W0212
            frame.f_locals.setdefault('_telldusLiveHandlers',
                                      {}).setdefault(message, []).append(func)
            return func

        return call

    def __sendRegisterMessage(self):
        print("Send register")
        msg = LiveMessage('Register')
        msg.append({
            'key': self.conn.publicKey,
            'mac': Board.getMacAddr(),
            'secret': Board.secret(),
            'hash': 'sha1'
        })
        msg.append({
            'protocol': 3,
            'version': Board.firmwareVersion(),
            'os': 'linux',
            'os-version': 'telldus'
        })
        self.conn.send(msg)

    @staticmethod
    def deviceSpecificEncrypt(payload):
        # TODO: Use security plugin once available
        password = Board.secret()
        iv = os.urandom(16)  # pylint: disable=C0103
        key = PBKDF2(password, iv).read(32)
        encryptor = AES.new(key, AES.MODE_CBC, iv)

        buff = StringIO()
        buff.write(struct.pack('<Q', len(payload)))
        buff.write(iv)
        if len(payload) % 16 != 0:
            # Pad payload
            payload += ' ' * (16 - len(payload) % 16)
        buff.write(encryptor.encrypt(payload))
        return buff.getvalue()
Beispiel #6
0
class ApiManager(Plugin):
	implements(IWebRequestHandler)
	implements(ITelldusLiveObserver)

	observers = ObserverCollection(IApiCallHandler)

	def __init__(self):
		self.tokens = {}
		self.tokenKey = None

	@staticmethod
	def getTemplatesDirs():
		return [resource_filename('api', 'templates')]

	@staticmethod
	def matchRequest(plugin, __path):
		if plugin != 'api':
			return False
		return True

	def handleRequest(self, plugin, path, params, request, **__kwargs):
		del plugin
		if path == '':
			methods = {}
			for observer in self.observers:
				for module, actions in getattr(observer, '_apicalls', {}).iteritems():
					for action, func in actions.iteritems():
						methods.setdefault(module, {})[action] = {'doc': func.__doc__}
			return 'index.html', {'methods': methods}
		if path == 'token':
			if request.method() == 'PUT':
				token = uuid.uuid4().hex
				self.tokens[token] = {
					'app': request.post('app'),
					'authorized': False,
				}
				return WebResponseJson({
					'authUrl': '%s/api/authorize?token=%s' % (request.base(), token),
					'token': token
				})
			if request.method() == 'GET':
				token = params.get('token', None)
				if token is None:
					return WebResponseJson({'error': 'No token specified'}, statusCode=400)
				if token not in self.tokens:
					return WebResponseJson({'error': 'No such token'}, statusCode=404)
				if self.tokens[token]['authorized'] is not True:
					return WebResponseJson({'error': 'Token is not authorized'}, statusCode=403)
				claims = {
					'aud': self.tokens[token]['app'],
					'exp': int(time.time()+self.tokens[token]['ttl']),
				}
				body = {}
				if self.tokens[token]['allowRenew'] is True:
					body['renew'] = True
					body['ttl'] = self.tokens[token]['ttl']
				accessToken = self.__generateToken(body, claims)
				resp = WebResponseJson({
					'token': accessToken,
					'expires': claims['exp'],
					'allowRenew': self.tokens[token]['allowRenew'],
				})
				del self.tokens[token]
				return resp
		if path == 'authorize':
			if 'token' not in params:
				return WebResponseJson({'error': 'No token specified'}, statusCode=400)
			token = params['token']
			if token not in self.tokens:
				return WebResponseJson({'error': 'No such token'}, statusCode=404)
			if request.method() == 'POST':
				self.tokens[token]['authorized'] = True
				self.tokens[token]['allowRenew'] = bool(request.post('extend', False))
				self.tokens[token]['ttl'] = int(request.post('ttl', 0))*60
			return 'authorize.html', {'token': self.tokens[token]}

		# Check authorization
		token = request.header('Authorization')
		if token is None:
			return WebResponseJson({'error': 'No token was found in the request'}, statusCode=401)
		if not token.startswith('Bearer '):
			return WebResponseJson(
				{
					'error': 'The autorization token must be supplied as a bearer token'
				},
				statusCode=401
			)
		token = token[7:]
		try:
			body = jwt.decode(token, self.__tokenKey(), algorithms='HS256')
		except JWSError as error:
			return WebResponseJson({'error': str(error)}, statusCode=401)
		except JWTError as error:
			return WebResponseJson({'error': str(error)}, statusCode=401)
		claims = jwt.get_unverified_headers(token)
		if 'exp' not in claims or claims['exp'] < time.time():
			return WebResponseJson({'error': 'The token has expired'}, statusCode=401)
		if 'aud' not in claims or claims['aud'] is None:
			return WebResponseJson({'error': 'No app was configured in the token'}, statusCode=401)
		aud = claims['aud']

		if path == 'refreshToken':
			if 'renew' not in body or not body['renew']:
				return WebResponseJson({'error': 'The token is not authorized for refresh'}, statusCode=403)
			if 'ttl' not in body:
				return WebResponseJson({'error': 'No TTL was specified in the token'}, statusCode=401)
			ttl = body['ttl']
			exp = int(time.time()+ttl)
			accessToken = self.__generateToken({
				'renew': True,
				'ttl': ttl
			}, {
				'aud': aud,
				'exp': exp,
			})
			return WebResponseJson({
				'token': accessToken,
				'expires': exp,
			})
		paths = path.split('/')
		if len(paths) < 2:
			return None
		module = paths[0]
		action = paths[1]
		for observer in self.observers:
			func = getattr(observer, '_apicalls', {}).get(module, {}).get(action, None)
			if func is None:
				continue
			try:
				params['app'] = aud
				if request.method() == 'POST':
					retval = func(observer, **request.params())
				else:
					retval = func(observer, **params)
			except Exception as error:
				logging.exception(error)
				return WebResponseJson({'error': str(error)})
			if retval is True:
				retval = {'status': 'success'}
			return WebResponseJson(retval)
		return WebResponseJson(
			{'error': 'The method %s/%s does not exist' % (module, action)},
			statusCode=404
		)

	@staticmethod
	def requireAuthentication(plugin, path):
		if plugin != 'api':
			return True
		if path in ['', 'authorize']:
			return True
		return False

	@TelldusLive.handler('requestlocalkey')
	def __requestLocalKey(self, msg):
		args = msg.argument(0).toNative()
		live = TelldusLive(self.context)
		try:
			publicKey = serialization.load_pem_public_key(
				args.get('publicKey', ''),
				backend=default_backend(),
			)
			ttl = int(time.time()+2629743)  # One month
			accessToken = self.__generateToken({}, {
				'aud': args.get('app', 'Unknown'),
				'exp': ttl,
			})
			ciphertext = publicKey.encrypt(
				str(accessToken),
				padding.OAEP(
					mgf=padding.MGF1(algorithm=hashes.SHA256()),
					algorithm=hashes.SHA256(),
					label=None
				)
			)
		except Exception as error:
			live.pushToWeb('api', 'localkey', {
				'success': False,
				'error': str(error)
			})
			return
		live.pushToWeb('api', 'localkey', {
			'key': base64.b64encode(ciphertext),
			'ttl': ttl,
			'uuid': args.get('uuid', ''),
			'client': live.uuid,
		})

	def __tokenKey(self):
		if self.tokenKey is not None:
			return self.tokenKey
		password = Board.secret()
		settings = Settings('telldus.api')
		tokenKey = settings.get('tokenKey', '')
		backend = default_backend()
		if tokenKey == '':
			self.tokenKey = os.urandom(32)
			# Store it
			salt = os.urandom(16)
			kdf = PBKDF2HMAC(
				algorithm=hashes.SHA1(),
				length=32,
				salt=salt,
				iterations=1000,
				backend=backend
			)
			key = kdf.derive(password)
			pwhash = ApiManager.pbkdf2crypt(password)
			settings['salt'] = base64.b64encode(salt)
			settings['pw'] = pwhash
			# Encrypt token key
			cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend)
			encryptor = cipher.encryptor()
			settings['tokenKey'] = base64.b64encode(bytes(encryptor.update(self.tokenKey)))
		else:
			# Decode it
			salt = base64.b64decode(settings.get('salt', ''))
			pwhash = settings.get('pw', '')
			if ApiManager.pbkdf2crypt(password, pwhash) != pwhash:
				logging.warning('Could not decrypt token key, wrong password')
				return None
			kdf = PBKDF2HMAC(
				algorithm=hashes.SHA1(),
				length=32,
				salt=salt,
				iterations=1000,
				backend=backend
			)
			key = kdf.derive(password)
			enc = base64.b64decode(tokenKey)
			cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend)
			decryptor = cipher.decryptor()
			self.tokenKey = bytes(decryptor.update(enc))

		return self.tokenKey

	def __generateToken(self, body, claims):
		return jwt.encode(body, self.__tokenKey(), algorithm='HS256', headers=claims)

	@staticmethod
	def apicall(module, action):
		def call(func):
			import sys
			frame = sys._getframe(1)  # pylint: disable=W0212
			frame.f_locals.setdefault('_apicalls', {}).setdefault(module, {})[action] = func
			return func
		return call

	@staticmethod
	def pbkdf2crypt(password, salt=None):
		if salt is None:
			binarysalt = b''.join([struct.pack("@H", random.randint(0, 0xffff)) for _i in range(3)])
			salt = "$p5k2$$" + base64.b64encode(binarysalt, "./")
		elif salt.startswith("$p5k2$"):
			salt = "$p5k2$$" + salt.split("$")[3]
		kdf = PBKDF2HMAC(
			algorithm=hashes.SHA1(),
			length=24,
			salt=salt,
			iterations=400,
			backend=default_backend()
		)
		rawhash = kdf.derive(password)
		return salt + "$" + base64.b64encode(rawhash, "./")