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()
示例#2
0
    def __init__(self):
        self.running = False
        #self.runningJobsLock = threading.Lock() #TODO needed?
        self.jobsLock = threading.Lock()
        self.maintenanceJobsLock = threading.Lock()
        self.maintenanceJobs = []
        self.lastMaintenanceJobId = 0
        self.runningJobs = {}  #id:s as keys
        self.settings = Settings('telldus.scheduler')
        Application().registerShutdown(self.stop)
        Application().registerMaintenanceJobHandler(
            self.addMaintenanceJobGeneric)
        self.timezone = self.settings.get('tz', 'UTC')
        self.latitude = self.settings.get('latitude', '55.699592')
        self.longitude = self.settings.get('longitude', '13.187836')
        self.jobs = []
        self.fetchLocalJobs()
        self.live = TelldusLive(self.context)
        self.deviceManager = DeviceManager(self.context)
        if self.live.isRegistered():
            #probably not practically possible to end up here
            self.requestJobsFromServer()

        self.thread = threading.Thread(target=self.run)
        self.thread.start()
class SuntimeCondition(Condition):
	def __init__(self, **kwargs):
		super(SuntimeCondition,self).__init__(**kwargs)
		self.sunStatus = None
		self.sunriseOffset = None
		self.sunsetOffset = None
		self.s = Settings('telldus.scheduler')
		self.latitude = self.s.get('latitude', '55.699592')
		self.longitude = self.s.get('longitude', '13.187836')

	def parseParam(self, name, value):
		if name == 'sunStatus':
			self.sunStatus = int(value)
		if name == 'sunriseOffset':
			self.sunriseOffset = int(value)
		if name == 'sunsetOffset':
			self.sunsetOffset = int(value)

	def validate(self, success, failure):
		if self.sunStatus is None or self.sunriseOffset is None or self.sunsetOffset is None:
			# condition has not finished loading, impossible to validate it correctly
			failure()
			return
		sunCalc = SunCalculator()
		currentDate = pytz.utc.localize(datetime.utcnow())
		riseSet = sunCalc.nextRiseSet(timegm(currentDate.utctimetuple()), float(self.latitude), float(self.longitude))
		currentStatus = 1
		if (riseSet['sunrise'] + (self.sunriseOffset*60)) < (riseSet['sunset'] + (self.sunsetOffset*60)):
			# the sun is down (with offset)
			currentStatus = 0

		if self.sunStatus == currentStatus:
			success()
		else:
			failure()
示例#4
0
 def __tokenKey(self):
     if self.tokenKey is not None:
         return self.tokenKey
     password = Board.secret()
     s = Settings('telldus.api')
     tokenKey = s.get('tokenKey', '')
     if tokenKey == '':
         self.tokenKey = os.urandom(32)
         # Store it
         salt = os.urandom(16)
         key = PBKDF2(password, salt).read(32)
         pwhash = crypt(password)
         s['salt'] = base64.b64encode(salt)
         s['pw'] = pwhash
         # Encrypt token key
         cipher = AES.new(key, AES.MODE_ECB, '')
         s['tokenKey'] = base64.b64encode(cipher.encrypt(self.tokenKey))
     else:
         # Decode it
         salt = base64.b64decode(s.get('salt', ''))
         pwhash = s.get('pw', '')
         if crypt(password, pwhash) != pwhash:
             logging.warning('Could not decrypt token key, wrong password')
             return None
         key = PBKDF2(password, salt).read(32)
         enc = base64.b64decode(tokenKey)
         cipher = AES.new(key, AES.MODE_ECB, '')
         self.tokenKey = cipher.decrypt(enc)
     return self.tokenKey
	def __init__(self, **kwargs):
		super(SuntimeCondition,self).__init__(**kwargs)
		self.sunStatus = None
		self.sunriseOffset = None
		self.sunsetOffset = None
		self.s = Settings('telldus.scheduler')
		self.latitude = self.s.get('latitude', '55.699592')
		self.longitude = self.s.get('longitude', '13.187836')
	def __init__(self, **kwargs):
		super(TimeCondition,self).__init__(**kwargs)
		self.fromMinute = None
		self.fromHour = None
		self.toMinute = None
		self.toHour = None
		self.s = Settings('telldus.scheduler')
		self.timezone = self.s.get('tz', 'UTC')
 def __init__(self):
     self.publicKey = ''
     self.privateKey = ''
     self.state = ServerConnection.CLOSED
     self.msgs = []
     self.server = None
     s = Settings('tellduslive.config')
     self.useSSL = s.get('useSSL', True)
示例#8
0
class SuntimeCondition(Condition):
    def __init__(self, **kwargs):
        super(SuntimeCondition, self).__init__(**kwargs)
        self.sunStatus = None
        self.sunriseOffset = None
        self.sunsetOffset = None
        self.settings = Settings('telldus.scheduler')
        self.latitude = self.settings.get('latitude', '55.699592')
        self.longitude = self.settings.get('longitude', '13.187836')

    def parseParam(self, name, value):
        if name == 'sunStatus':
            self.sunStatus = int(value)
        if name == 'sunriseOffset':
            self.sunriseOffset = int(value)
        if name == 'sunsetOffset':
            self.sunsetOffset = int(value)

    def validate(self, success, failure):
        if self.sunStatus is None or self.sunriseOffset is None or self.sunsetOffset is None:
            # condition has not finished loading, impossible to validate it correctly
            failure()
            return
        sunCalc = SunCalculator()
        currentDate = pytz.utc.localize(datetime.utcnow())
        riseSet = sunCalc.nextRiseSet(timegm(currentDate.utctimetuple()),
                                      float(self.latitude),
                                      float(self.longitude))
        currentStatus = 1
        sunToday = sunCalc.riseset(timegm(currentDate.utctimetuple()),
                                   float(self.latitude), float(self.longitude))
        sunRise = None
        sunSet = None
        if sunToday['sunrise']:
            sunRise = sunToday['sunrise'] + (self.sunriseOffset * 60)
        if sunToday['sunset']:
            sunSet = sunToday['sunset'] + (self.sunsetOffset * 60)
        if sunRise or sunSet:
            if (sunRise
                    and time.time() < sunRise) or (sunSet
                                                   and time.time() > sunSet):
                currentStatus = 0
        else:
            # no sunset or sunrise, is it winter or summer?
            if riseSet['sunrise'] < riseSet['sunset']:
                # next is a sunrise, it's dark now (winter)
                if time.time() < (riseSet['sunrise'] +
                                  (self.sunriseOffset * 60)):
                    currentStatus = 0
            else:
                # next is a sunset, it's light now (summer)
                if time.time() > (riseSet['sunset'] +
                                  (self.sunriseOffset * 60)):
                    currentStatus = 0
        if self.sunStatus == currentStatus:
            success()
        else:
            failure()
	def __init__(self, manager, **kwargs):
		super(TimeTrigger,self).__init__(**kwargs)
		self.manager = manager
		self.minute = None
		self.hour = None
		self.setHour = None  # this is the hour actually set (not recalculated to UTC)
		self.active = True  # TimeTriggers are always active
		self.s = Settings('telldus.scheduler')
		self.timezone = self.s.get('tz', 'UTC')
示例#10
0
 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()
class TimeTrigger(Trigger):
	def __init__(self, manager, **kwargs):
		super(TimeTrigger,self).__init__(**kwargs)
		self.manager = manager
		self.minute = None
		self.hour = None
		self.setHour = None  # this is the hour actually set (not recalculated to UTC)
		self.active = True  # TimeTriggers are always active
		self.s = Settings('telldus.scheduler')
		self.timezone = self.s.get('tz', 'UTC')

	def close(self):
		self.manager.deleteTrigger(self)

	def parseParam(self, name, value):
		if name == 'minute':
			self.minute = int(value)
		elif name == 'hour':
			self.setHour = int(value)
			# recalculate hour to UTC
			if int(value) == -1:
				self.hour = int(value)
			else:
				local_timezone = timezone(self.timezone)
				currentDate = pytz.utc.localize(datetime.utcnow())
				local_datetime = local_timezone.localize(datetime(currentDate.year, currentDate.month, currentDate.day, int(value)))
				utc_datetime = pytz.utc.normalize(local_datetime.astimezone(pytz.utc))
				if datetime.now().hour > utc_datetime.hour:
					# retry it with new date (will have impact on daylight savings changes (but not sure it will actually help))
					currentDate = currentDate + timedelta(days=1)
				local_datetime = local_timezone.localize(datetime(currentDate.year, currentDate.month, currentDate.day, int(value)))
				utc_datetime = pytz.utc.normalize(local_datetime.astimezone(pytz.utc))
				self.hour = utc_datetime.hour
		if self.hour is not None and self.minute is not None:
			self.manager.addTrigger(self)

	def recalculate(self):
		if self.hour == -1:
			return False
		self.timezone = self.s.get('tz', 'UTC')
		currentHour = self.hour
		local_timezone = timezone(self.timezone)
		currentDate = pytz.utc.localize(datetime.utcnow())
		local_datetime = local_timezone.localize(datetime(currentDate.year, currentDate.month, currentDate.day, self.setHour))
		utc_datetime = pytz.utc.normalize(local_datetime.astimezone(pytz.utc))
		if datetime.now().hour > utc_datetime.hour:
			# retry it with new date (will have impact on daylight savings changes (but not sure it will actually help))
			currentDate = currentDate + timedelta(days=1)
		local_datetime = local_timezone.localize(datetime(currentDate.year, currentDate.month, currentDate.day, self.setHour))
		utc_datetime = pytz.utc.normalize(local_datetime.astimezone(pytz.utc))
		self.hour = utc_datetime.hour
		if currentHour == self.hour:
			#no change
			return False
		return True
示例#12
0
	def __init__(self, manager, eventId, minimumRepeatInterval, description):
		self.manager = manager
		self.eventId = eventId
		self.minimumRepeatInterval = minimumRepeatInterval
		self.description = description
		self.lastRun = None
		self.actions = {}
		self.conditions = {}
		self.triggers = {}
		self.evaluatingConditions = []
		self.s = Settings('telldus.event')
	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()
		blockSize = 16  # TODO: Get this programatically?
		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()
			buf = bytearray(len(self.tokenKey)+blockSize-1)
			lenEncrypted = encryptor.update_into(self.tokenKey, buf)
			settings['tokenKey'] = base64.b64encode(bytes(buf[:lenEncrypted]) + encryptor.finalize())
		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()
			buf = bytearray(len(enc)+blockSize-1)
			lenDecrypted = decryptor.update_into(enc, buf)
			self.tokenKey = bytes(buf[:lenDecrypted]) + decryptor.finalize()

		return self.tokenKey
示例#14
0
	def __init__(self, event, id, delay, delayPolicy, delayExecTime=None, *__args, **__kwargs):
		super(Action, self).__init__()
		self.event = event
		self.id = id  # pylint: disable=C0103
		self.delay = delay
		self.delayPolicy = delayPolicy
		self.delayExecTime = None
		if delayExecTime:
			self.delayExecTime = float(delayExecTime)
		self.timeout = None
		self.triggerInfo = None
		self.settings = Settings('telldus.event')
		Application().registerShutdown(self.stop)
示例#15
0
	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
示例#16
0
 def __init__(self,
              event,
              id,
              delay,
              delayPolicy,
              delayExecTime=None,
              *args,
              **kwargs):
     super(Action, self).__init__()
     self.event = event
     self.id = id
     self.delay = delay
     self.delayPolicy = delayPolicy
     self.delayExecTime = None
     if delayExecTime:
         self.delayExecTime = float(delayExecTime)
     self.timeout = None
     self.s = Settings('telldus.event')
     Application().registerShutdown(self.stop)
示例#17
0
    def _read_settings(self):
        settings = Settings(CONFIG)
        pys = self.pyShelly
        pys.set_cloud_settings(settings["cloud_server"],
                               settings["cloud_auth_key"])
        pys.update_status_interval = timedelta(seconds=30)
        pys.only_device_id = settings["only_device_id"]
        pys.mdns_enabled = False  #settings.get('mdns', True)

        if not settings['logger']:
            LOGGER.setLevel(logging.WARNING)
示例#18
0
 def __init__(self):
     settings = Settings('tellduslive.config')
     uuid = settings['uuid']
     self.xmpp = ClientXMPP('*****@*****.**' % uuid,
                            '%s:%s' % (Board.getMacAddr(), Board.secret()))
     self.xmpp.add_event_handler("session_start", self.sessionStart)
     self.xmpp.add_event_handler("register", self.register)
     self.xmpp.register_plugin('xep_0030')  # Service Discovery
     self.xmpp.register_plugin('xep_0077')  # In-band registration
     self.shuttingDown = False
     # Connect is blocking. Do this in a separate thread
     threading.Thread(target=self.__start, name='XMPP connector').start()
示例#19
0
class TimeCondition(Condition):
    def __init__(self, **kwargs):
        super(TimeCondition, self).__init__(**kwargs)
        self.fromMinute = None
        self.fromHour = None
        self.toMinute = None
        self.toHour = None
        self.settings = Settings('telldus.scheduler')
        self.timezone = self.settings.get('tz', 'UTC')

    def parseParam(self, name, value):
        if name == 'fromMinute':
            self.fromMinute = int(value)
        elif name == 'toMinute':
            self.toMinute = int(value)
        elif name == 'fromHour':
            self.fromHour = int(value)
        elif name == 'toHour':
            self.toHour = int(value)

    def validate(self, success, failure):
        currentDate = pytz.utc.localize(datetime.utcnow())
        local_timezone = timezone(self.timezone)

        if self.fromMinute is None \
           or self.toMinute is None \
           or self.fromHour is None \
           or self.toHour is None:
            # validate that all parameters have been loaded
            failure()
            return
        toTime = local_timezone.localize(
            datetime(currentDate.year, currentDate.month, currentDate.day,
                     self.toHour, self.toMinute, 0))
        fromTime = local_timezone.localize(
            datetime(currentDate.year, currentDate.month, currentDate.day,
                     self.fromHour, self.fromMinute, 0))
        if fromTime > toTime:
            if (currentDate >= fromTime or currentDate <= toTime):
                success()
            else:
                failure()
        else:
            # condition interval passes midnight
            if (currentDate >= fromTime and currentDate <= toTime):
                success()
            else:
                failure()
示例#20
0
 def __init__(self):
     logging.info("Telldus Live! loading")
     self.email = ''
     self.supportedMethods = 0
     self.connected = False
     self.registered = False
     self.serverList = ServerList()
     Application().registerShutdown(self.stop)
     self.s = Settings('tellduslive.config')
     self.uuid = self.s['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()
示例#21
0
    def __init__(self):
        self.last_sent_data = None
        self.stop_ping_loop = threading.Event()

        self.deviceManager = DeviceManager(self.context)

        Application().registerShutdown(self.shutdown)

        settings = Settings('tellduslive.config')
        self.uuid = settings['uuid']

        self.logHandler = ShellyLogger(self.uuid)
        LOGGER.addHandler(self.logHandler)

        self.setupPing()

        LOGGER.info('Init Shelly ' + __version__)
        self._initPyShelly()
class WeekdayCondition(Condition):
	def __init__(self, **kwargs):
		super(WeekdayCondition,self).__init__(**kwargs)
		self.weekdays = None
		self.s = Settings('telldus.scheduler')
		self.timezone = self.s.get('tz', 'UTC')

	def parseParam(self, name, value):
		if name == 'weekdays':
			self.weekdays = value

	def validate(self, success, failure):
		currentDate = pytz.utc.localize(datetime.utcnow())
		local_timezone = timezone(self.timezone)
		local_datetime = local_timezone.normalize(currentDate.astimezone(local_timezone))
		currentWeekday = local_datetime.weekday() + 1
		if str(currentWeekday) in self.weekdays:
			success()
		else:
			failure()
示例#23
0
 def __init__(self):
     self.rooms = {}
     self.settings = Settings('telldus.rooms')
     self.rooms = self.settings.get('rooms', {})
     self.roomlistEmpty = self.settings.get('roomlistEmpty', False)
示例#24
0
class RoomManager(Plugin):
    """The roommanager holds and manages all the rooms in the server"""
    implements(ISignalObserver)
    implements(ITelldusLiveObserver)
    public = True

    def __init__(self):
        self.rooms = {}
        self.settings = Settings('telldus.rooms')
        self.rooms = self.settings.get('rooms', {})
        self.roomlistEmpty = self.settings.get('roomlistEmpty', False)

    def getResponsibleRooms(self, responsible=None):
        if not responsible:
            live = TelldusLive(self.context)
            responsible = live.uuid
        rooms = {}
        for roomUUID in self.rooms:
            room = self.rooms[roomUUID]
            if room['responsible'] == responsible:
                rooms[roomUUID] = room
        return rooms

    def liveRegistered(self, msg, refreshRequired):
        if refreshRequired:
            self.syncRoom()

    def reportRooms(self, rooms, removedRooms=None):
        report = {}
        if not rooms and not self.roomlistEmpty and not removedRooms:
            # only allow empty room reports if we know it has been
            # explicitly emptied
            return
        if rooms or self.roomlistEmpty:
            report['rooms'] = rooms
        if removedRooms:
            report['removedRooms'] = removedRooms
        msg = LiveMessage('RoomReport')
        msg.append(report)
        TelldusLive(self.context).send(msg)

    def roomChanged(self, room1, room2):
        for prop in room1:
            if not room1[prop] == room2[prop]:
                return True
        return False

    def setMode(self, roomId, mode, setAlways=1):
        """
		Set a room to a new mode
		"""
        room = self.rooms.get(roomId, None)
        if not room:
            return
        setAlways = int(setAlways)
        if setAlways or room['mode'] != mode:
            if room['mode'] != mode:
                room['mode'] = mode
                self.settings['rooms'] = self.rooms
            live = TelldusLive(self.context)
            if live.registered and room.get('responsible', '') == live.uuid:
                # Notify live if we are the owner
                msg = LiveMessage('RoomModeSet')
                msg.append({'id': roomId, 'mode': mode})
                live.send(msg)
            self.__modeChanged(roomId, mode, 'room', room.get('name', ''))

    def syncRoom(self):
        TelldusLive(self.context).send(LiveMessage("roomsync-request"))

    @signal('modeChanged')
    def __modeChanged(self, objectId, modeId, objectType, objectName):
        """
		Called every time the mode changes for a room
		"""
        pass

    @TelldusLive.handler('room')
    def __handleRoom(self, msg):
        data = msg.argument(0).toNative()
        if 'name' in data:
            if isinstance(data['name'], int):
                data['name'] = str(data['name'])
            else:
                data['name'] = data['name'].decode('UTF-8')
        live = TelldusLive(self.context)
        if data['action'] == 'set':
            oldResponsible = ''
            if data['id'] in self.rooms:
                # existing room
                room = self.rooms[data['id']]
                oldResponsible = room['responsible']
                validKeys = ['name', 'color', 'content', 'icon', 'responsible']
                for key in validKeys:
                    if key in data:
                        room[key] = data.get(key, '')
                if 'mode' in data and room['mode'] != data.get('mode', ''):
                    room['mode'] = data.get('mode', '')
                    self.__modeChanged(data['id'], room['mode'], 'room',
                                       room['name'])
                self.rooms[data['id']] = room
            else:
                # new room
                self.rooms[data['id']] = {
                    'name': data.get('name', ''),
                    'parent': data.get('parent', ''),
                    'color': data.get('color', ''),
                    'content': data.get('content', ''),
                    'icon': data.get('icon', ''),
                    'responsible': data['responsible'],
                    'mode': data.get('mode', ''),
                }
            if live.registered and \
                (data['responsible'] == live.uuid or oldResponsible == live.uuid):
                room = self.rooms[data['id']]
                msg = LiveMessage('RoomSet')
                msg.append({
                    # No need to call get() on room here since we know every value has at least a
                    # default value above
                    'id': data['id'],
                    'name': room['name'],
                    'parent': room['parent'],
                    'color': room['color'],
                    'content': room['content'],
                    'icon': room['icon'],
                    'responsible': room['responsible'],
                    'mode': room['mode'],
                })
                live.send(msg)
            self.settings['rooms'] = self.rooms
            return

        if data['action'] == 'remove':
            room = self.rooms.pop(data['id'], None)
            if room is None:
                return
            if live.registered and room['responsible'] == live.uuid:
                msg = LiveMessage('RoomRemoved')
                msg.append({'id': data['id']})
                live.send(msg)
            if len(self.getResponsibleRooms()) == 0:
                self.settings['roomlistEmpty'] = True
                self.roomlistEmpty = True

            self.settings['rooms'] = self.rooms
            return

        if data['action'] == 'setMode':
            self.setMode(data.get('id', None), data.get('mode', ''),
                         data.get('setAlways', 1))
            return

        if data['action'] == 'sync':
            rooms = data['rooms']
            responsibleRooms = self.getResponsibleRooms()
            if not rooms and responsibleRooms:
                # list from server was completely empty but we have rooms locally,
                # this might be an error in the fetching, or we have added rooms locally
                # when offline. In any case, don't sync this time, just post our rooms
                # for next time
                self.reportRooms(responsibleRooms)
                return
            changedRooms = {}
            newRooms = {}
            removedRooms = []
            for roomUUID in rooms:
                room = rooms[roomUUID]
                if room['responsible'] == live.uuid:
                    # we are responsible for this room
                    if roomUUID not in self.rooms:
                        # this room does not exist locally anymore
                        removedRooms.append(roomUUID)
                        continue
                    localRoom = self.rooms[roomUUID]
                    if self.roomChanged(room, localRoom):
                        changedRooms[roomUUID] = localRoom
                else:
                    newRooms[roomUUID] = room

            newRooms.update(responsibleRooms)
            self.rooms = newRooms
            self.reportRooms(changedRooms, removedRooms)
            self.settings['rooms'] = self.rooms
示例#25
0
class Scheduler(Plugin):
    implements(ITelldusLiveObserver, IDeviceChange)

    def __init__(self):
        self.running = False
        #self.runningJobsLock = threading.Lock() #TODO needed?
        self.jobsLock = threading.Lock()
        self.maintenanceJobsLock = threading.Lock()
        self.maintenanceJobs = []
        self.lastMaintenanceJobId = 0
        self.runningJobs = {}  #id:s as keys
        self.settings = Settings('telldus.scheduler')
        Application().registerShutdown(self.stop)
        Application().registerMaintenanceJobHandler(
            self.addMaintenanceJobGeneric)
        self.timezone = self.settings.get('tz', 'UTC')
        self.latitude = self.settings.get('latitude', '55.699592')
        self.longitude = self.settings.get('longitude', '13.187836')
        self.jobs = []
        self.fetchLocalJobs()
        self.live = TelldusLive(self.context)
        self.deviceManager = DeviceManager(self.context)
        if self.live.isRegistered():
            #probably not practically possible to end up here
            self.requestJobsFromServer()

        self.thread = threading.Thread(target=self.run)
        self.thread.start()

    def addMaintenanceJobGeneric(self, job):
        self.addMaintenanceJob(job['nextRunTime'], job['callback'],
                               job['recurrence'])

    def addMaintenanceJob(self, nextRunTime, timeoutCallback, recurrence=0):
        """ nextRunTime - GMT timestamp, timeoutCallback - the method to run,
		recurrence - when to repeat it, in seconds
		Returns: An id for the newly added job (for removal and whatnot)
		Note, if the next nextRunTime needs to be calculated, it's better to do that
		in the callback-method, and add a new job from there, instead of using "recurrence" """
        jobData = {
            'nextRunTime': nextRunTime,
            'callback': timeoutCallback,
            'recurrence': recurrence
        }
        with self.maintenanceJobsLock:
            self.lastMaintenanceJobId = self.lastMaintenanceJobId + 1
            jobData[
                'id'] = self.lastMaintenanceJobId  # add an ID, make it possible to remove it someday
            self.maintenanceJobs.append(jobData)
            self.maintenanceJobs.sort(
                key=lambda jobData: jobData['nextRunTime'])
            return self.lastMaintenanceJobId

    def calculateJobs(self, jobs):
        """Calculate nextRunTime for all jobs in the supplied list, order it and assign it to self.jobs"""
        newJobs = []
        for job in jobs:
            self.checkNewlyLoadedJob(job)
            if self.calculateNextRunTime(job):
                newJobs.append(job)

        newJobs.sort(key=lambda job: job['nextRunTime'])
        with self.jobsLock:
            self.jobs = newJobs

    def calculateNextRunTime(self, job):
        """Calculates nextRunTime for a job, depending on time, weekday and timezone."""
        if not job['active'] or not job['weekdays']:
            job['nextRunTime'] = 253402214400  #set to max value, only run just before the end of time
            # just delete the job, until it's possible to edit schedules locally, inactive jobs has
            # no place at all here
            self.deleteJob(job['id'])
            return False
        today = datetime.now(timezone(self.timezone)).weekday()  # normalize?
        weekdays = [int(n) for n in job['weekdays'].split(',')]
        runToday = False
        firstWeekdayToRun = None
        nextWeekdayToRun = None
        runDate = None

        for weekday in weekdays:
            weekday = weekday - 1  #weekdays in python: 0-6, weekdays in our database: 1-7
            if weekday == today:
                runToday = True
            elif today < weekday and (nextWeekdayToRun is None
                                      or nextWeekdayToRun > weekday):
                nextWeekdayToRun = weekday
            elif today > weekday and (firstWeekdayToRun is None
                                      or weekday < firstWeekdayToRun):
                firstWeekdayToRun = weekday

        todayDate = datetime.now(timezone(self.timezone)).date()  # normalize?
        if runToday:
            #this weekday is included in the ones that this schedule should be run on
            runTimeToday = self.calculateRunTimeForDay(todayDate, job)
            if runTimeToday > time.time():
                job['nextRunTime'] = runTimeToday + random.randint(
                    0, job['random_interval']) * 60
                return True
            elif len(weekdays) == 1:
                #this job should only run on this weekday, since it has already passed today, run it next week
                runDate = todayDate + timedelta(days=7)

        if not runDate:
            if nextWeekdayToRun is not None:
                runDate = self.calculateNextWeekday(todayDate,
                                                    nextWeekdayToRun)

            else:
                runDate = self.calculateNextWeekday(todayDate,
                                                    firstWeekdayToRun)

            if not runDate:
                #something is wrong, no weekday to run
                job['nextRunTime'] = 253402214400
                # just delete the job, until it's possible to edit schedules locally, inactive jobs
                # has no place at all here
                self.deleteJob(job['id'])
                return False

        job['nextRunTime'] = self.calculateRunTimeForDay(runDate, job) \
                             + random.randint(0, job['random_interval']) * 60
        return True

    @staticmethod
    def calculateNextWeekday(todayDate, weekday):
        days_ahead = weekday - todayDate.weekday()
        if days_ahead <= 0:  # Target day already happened this week
            days_ahead += 7
        return todayDate + timedelta(days_ahead)

    def calculateRunTimeForDay(self, runDate, job):
        """
		Calculates and returns a timestamp for when this job should be run next.
		Takes timezone into consideration.
		"""
        runDate = datetime(runDate.year, runDate.month, runDate.day)
        if job['type'] == 'time':
            # TODO, sending timezone from the server now, but it's really a client setting, can I
            # get it from somewhere else?
            tzone = timezone(self.timezone)
            # won't random here, since this time may also be used to see if it's passed today or not
            runDate = runDate + timedelta(hours=job['hour'],
                                          minutes=job['minute'])
            # returning a timestamp, corrected for timezone settings
            return timegm(tzone.localize(runDate).utctimetuple())
        elif job['type'] == 'sunrise':
            sunCalc = SunCalculator()
            riseSet = sunCalc.nextRiseSet(timegm(runDate.utctimetuple()),
                                          float(self.latitude),
                                          float(self.longitude))
            return riseSet['sunrise'] + job['offset'] * 60
        elif job['type'] == 'sunset':
            sunCalc = SunCalculator()
            riseSet = sunCalc.nextRiseSet(timegm(runDate.utctimetuple()),
                                          float(self.latitude),
                                          float(self.longitude))
            return riseSet['sunset'] + job['offset'] * 60

    def checkNewlyLoadedJob(self, job):
        """Checks if any of the jobs (local or initially loaded) should be running right now"""
        if not job['active'] or not job['weekdays']:
            return

        weekdays = [int(n) for n in job['weekdays'].split(',')]
        i = 0
        while i < 2:
            #Check today and yesterday (might be around 12 in the evening)
            currentDate = date.today() + timedelta(days=-i)
            if (currentDate.weekday() + 1) in weekdays:
                #check for this day (today or yesterday)
                runTime = self.calculateRunTimeForDay(currentDate, job)
                runTimeMax = runTime + job['reps'] * 3 \
                             + job['retry_interval'] * 60 * (job['retries'] + 1) \
                             + 70 \
                             + job['random_interval'] * 60
                jobId = job['id']
                executedJobs = self.settings.get('executedJobs', {})
                if (str(jobId) not in executedJobs or executedJobs[str(jobId)] < runTime) \
                   and time.time() > runTime \
                   and time.time() < runTimeMax:
                    # run time for this job was passed during downtime, but it was passed within the
                    # max-runtime, and the last time it was executed (successfully) was before this
                    # run time, so it should be run again...
                    jobCopy = copy.deepcopy(job)
                    jobCopy['originalRepeats'] = job['reps']
                    jobCopy['nextRunTime'] = runTime
                    jobCopy[
                        'maxRunTime'] = runTimeMax  #approximate maxRunTime, sanity check
                    self.runningJobs[jobId] = jobCopy
                    return
            i = i + 1

    def deleteJob(self, jobId):
        with self.jobsLock:
            # Test this! It should be fast and keep original reference, they say (though it will
            # iterate all, even if it could end after one)
            self.jobs[:] = [x for x in self.jobs if x['id'] != jobId]
            if jobId in self.runningJobs:  #TODO this might require a lock too?
                self.runningJobs[jobId]['retries'] = 0

            executedJobs = self.settings.get('executedJobs', {})
            if str(jobId) in executedJobs:
                del executedJobs[str(jobId)]
                self.settings['executedJobs'] = executedJobs

    def deviceRemoved(self, deviceId):
        jobsToDelete = []
        for job in self.jobs:
            if job['id'] == deviceId:
                jobsToDelete.append[job['id']]
        for jobId in jobsToDelete:
            self.deleteJob(jobId)

    def fetchLocalJobs(self):
        """Fetch local jobs from settings"""
        try:
            jobs = self.settings.get('jobs', [])
        except ValueError:
            jobs = [
            ]  #something bad has been stored, just ignore it and continue?
            print "WARNING: Could not fetch schedules from local storage"
        self.calculateJobs(jobs)

    def liveRegistered(self, msg):
        if 'latitude' in msg:
            self.latitude = msg['latitude']
        if 'longitude' in msg:
            self.longitude = msg['longitude']
        if 'tz' in msg:
            self.timezone = msg['tz']

        self.requestJobsFromServer()

    @TelldusLive.handler('scheduler-remove')
    def removeOneJob(self, msg):
        if len(msg.argument(0).toNative()) != 0:
            scheduleDict = msg.argument(0).toNative()
            jobId = scheduleDict['id']
            self.deleteJob(jobId)
            self.settings['jobs'] = self.jobs  #save to storage
            self.live.pushToWeb('scheduler', 'removed', jobId)

    @TelldusLive.handler('scheduler-report')
    def receiveJobsFromServer(self, msg):
        """Receive list of jobs from server, saves to settings and calculate nextRunTimes"""
        if len(msg.argument(0).toNative()) == 0:
            jobs = []
        else:
            scheduleDict = msg.argument(0).toNative()
            jobs = scheduleDict['jobs']
        self.settings['jobs'] = jobs
        self.calculateJobs(jobs)

    @TelldusLive.handler('scheduler-update')
    def receiveOneJobFromServer(self, msg):
        """Receive one job from server, add or edit, save to settings and calculate nextRunTime"""
        if len(msg.argument(0).toNative()) == 0:
            jobs = []
        else:
            scheduleDict = msg.argument(0).toNative()
            job = scheduleDict['job']

        active = self.calculateNextRunTime(job)
        self.deleteJob(
            job['id'])  #delete the job if it already exists (update)
        if active:
            with self.jobsLock:
                self.jobs.append(job)
                self.jobs.sort(key=lambda job: job['nextRunTime'])
        self.settings['jobs'] = self.jobs  #save to storage
        # TODO is this a good idea? Trying to avoid cache problems where updates haven't come through?
        # But this may not work if the same schedule is saved many times in a row, or if changes
        # wasn't saved correctly to the database (not possible yet, only one database for schedules)
        # self.live.pushToWeb('scheduler', 'updated', job['id'])

    def requestJobsFromServer(self):
        self.live.send(LiveMessage("scheduler-requestjob"))

    def run(self):
        self.running = True
        while self.running:
            maintenanceJob = None
            with self.maintenanceJobsLock:
                if len(
                        self.maintenanceJobs
                ) > 0 and self.maintenanceJobs[0]['nextRunTime'] < time.time():
                    maintenanceJob = self.maintenanceJobs.pop(0)
            self.runMaintenanceJob(maintenanceJob)

            jobCopy = None
            with self.jobsLock:
                if len(self.jobs
                       ) > 0 and self.jobs[0]['nextRunTime'] < time.time():
                    #a job has passed its nextRunTime
                    job = self.jobs[0]
                    jobId = job['id']
                    jobCopy = copy.deepcopy(
                        job)  #make a copy, don't edit the original job

            if jobCopy:
                jobCopy['originalRepeats'] = job['reps']
                # approximate maxRunTime, sanity check
                jobCopy['maxRunTime'] = jobCopy['nextRunTime'] \
                                        + jobCopy['reps'] * 3 \
                                        + jobCopy['retry_interval'] * 60 * (jobCopy['retries'] + 1) \
                                        + 70 \
                                        + jobCopy['random_interval'] * 60
                self.runningJobs[jobId] = jobCopy
                self.calculateNextRunTime(job)
                with self.jobsLock:
                    self.jobs.sort(key=lambda job: job['nextRunTime'])

            jobsToRun = [
            ]  # jobs to run in a separate list, to avoid deadlocks (necessary?)
            # Iterating using .keys(9 since we are modifiyng the dict while iterating
            for runningJobId in self.runningJobs.keys():  # pylint: disable=C0201
                runningJob = self.runningJobs[runningJobId]
                if runningJob['nextRunTime'] < time.time():
                    if runningJob['maxRunTime'] > time.time():
                        if 'client_device_id' not in runningJob:
                            print "Missing client_device_id, this is an error, perhaps refetch jobs? "
                            print runningJob
                            continue
                        device = self.deviceManager.device(
                            runningJob['client_device_id'])
                        if not device:
                            print "Missing device, b: " + str(
                                runningJob['client_device_id'])
                            continue
                        if device.typeString(
                        ) == '433' and runningJob['originalRepeats'] > 1:
                            #repeats for 433-devices only
                            runningJob['reps'] = int(runningJob['reps']) - 1
                            if runningJob['reps'] >= 0:
                                runningJob['nextRunTime'] = time.time() + 3
                                jobsToRun.append(runningJob)
                                continue

                        if runningJob['retries'] > 0:
                            runningJob['nextRunTime'] = time.time() + (
                                runningJob['retry_interval'] * 60)
                            runningJob['retries'] = runningJob['retries'] - 1
                            runningJob['reps'] = runningJob['originalRepeats']
                            jobsToRun.append(runningJob)
                            continue

                    del self.runningJobs[
                        runningJobId]  #max run time passed or out of retries

            for jobToRun in jobsToRun:
                self.runJob(jobToRun)

            # TODO decide on a time (how often should we check for jobs to run, what resolution?)
            time.sleep(5)

    def stop(self):
        self.running = False

    def successfulJobRun(self, jobId, state, stateValue):
        """
		Called when job run was considered successful (acked by Z-Wave or sent away from 433),
		repeats should still be run
		"""
        del state, stateValue
        # save timestamp for when this was executed, to avoid rerun within maxRunTime on restart
        # TODO is this too much writing?
        executedJobs = self.settings.get('executedJobs', {})
        executedJobs[str(jobId)] = time.time(
        )  #doesn't work well with int type, for some reason
        self.settings['executedJobs'] = executedJobs
        #executedJobsTest = self.settings.get('executedJobs', {})
        if jobId in self.runningJobs:
            self.runningJobs[jobId]['retries'] = 0

    @mainthread
    def runJob(self, jobData):
        device = self.deviceManager.device(jobData['client_device_id'])
        if not device:
            print "Missing device: " + str(jobData['client_device_id'])
            return
        method = jobData['method']
        value = None
        if 'value' in jobData:
            value = jobData['value']

        device.command(method,
                       value=value,
                       origin='Scheduler',
                       success=self.successfulJobRun,
                       callbackArgs=[jobData['id']])

    @mainthread
    def runMaintenanceJob(self, jobData):
        if not jobData:
            return
        if jobData['recurrence']:
            # readd the job for another run
            self.addMaintenanceJob(time.time() + jobData['recurrence'],
                                   jobData['callback'], jobData['recurrence'])
        jobData['callback']()
示例#26
0
    def handleRequest(self, plugin, path, __params, **__kwargs):
        if path == 'list':
            return WebResponseJson(self._getData())

        if path == "config":
            if __params:
                settings = Settings(CONFIG)
                for param in __params:
                    if param in ['cloud_server', 'cloud_auth_key']:
                        settings[param] = __params[param]
                self._read_settings()
            return WebResponseJson(self._getConfig())

        if path == 'devices':
            devices = list(
                map(
                    lambda d: {
                        'id': d.id,
                        'name': d.friendly_name(),
                        'unit_id': d.unit_id,
                        'type': d.type,
                        'ip_addr': d.ip_addr,
                        'is_device': d.is_device,
                        'is_sensor': d.is_sensor,
                        'sub_name': d.sub_name,
                        'state_values': d.state_values,
                        'state': d.state,
                        'device_type': d.device_type,
                        'device_sub_type': d.device_sub_type,
                        'device_nr': d.device_nr,
                        'master_unit': d.master_unit,
                        'ext_sensor': d.ext_sensor,
                        'info_values': d.info_values,
                        'friendly_name': d.friendly_name()
                    }, self.pyShelly.devices))
            return WebResponseJson(devices)

        if path == 'blocks':
            blocks = list(
                map(
                    lambda d: {
                        'id': d.id,
                        'unit_id': d.unit_id,
                        'type': d.type,
                        'ip_addr': d.ip_addr,
                        'info_values': d.info_values
                    }, self.pyShelly.blocks.values()))
            return WebResponseJson(blocks)

        if path == 'dump':
            shellyDevices = self.deviceManager.retrieveDevices()
            devices = list(
                map(
                    lambda d: {
                        'id': d.id(),
                        'localid': d.localId(),
                        'name': d.name(),
                        'state': d.state(),
                        'params': d.params(),
                        'stateValues': d.stateValues(),
                        'sensorValues': d.sensorValues(),
                        'isDevice': d.isDevice(),
                        'isSensor': d.isSensor(),
                        'methods': d.methods(),
                        'parameters': d.parameters(),
                        'metadata': d.metadata(),
                        'type': d.typeString()
                    }, shellyDevices))
            return WebResponseJson({'devices': devices})

        if path in [
                'turnon', 'turnoff', 'up', 'down', 'stop', 'firmware_update'
        ]:
            LOGGER.info('Request ' + path)
            id = __params['id']
            device = self.deviceManager.device(int(id))
            if path == 'turnon':
                if hasattr(device.dev, 'brightness'):
                    device.dev.turn_on(brightness=100)
                else:
                    device.dev.turn_on()
            elif path == 'turnoff':
                device.dev.turn_off()
            elif path == 'up':
                device.dev.up()
            elif path == 'down':
                device.dev.down()
            elif path == 'stop':
                device.dev.stop()
            elif path == 'firmware_update':
                if device.dev.block:
                    device.dev.block.update_firmware()
            return WebResponseJson({})

        if path == "rgb":
            id = __params['id']
            r = __params['r']
            g = __params['g']
            b = __params['b']
            device = self.deviceManager.device(int(id))
            device.dev.set_values(rgb=[r, g, b])
            self.refreshClient()
            return WebResponseJson({})

        if path == "rename":
            id = __params['id']
            name = __params['name']
            device = self.deviceManager.device(int(id))
            device.local_name = name
            device.update_name()
            self.refreshClient()
            return WebResponseJson({})

        if path == "clean":
            self.deviceManager.removeDevicesByType('Shelly')
            self._initPyShelly()
            self.refreshClient()
            return WebResponseJson({'msg': 'Clean done'})

        if path == "discover":
            self.pyShelly.discover()
            return WebResponseJson({})

        if path == "addMember":
            LOGGER.debug("Add membership")
            import socket
            import struct
            mreq = struct.pack("=4sl", socket.inet_aton("224.0.1.187"),
                               socket.INADDR_ANY)
            self.pyShelly._socket.setsockopt(socket.IPPROTO_IP,
                                             socket.IP_ADD_MEMBERSHIP, mreq)
            return WebResponseJson({})

        if path == "dropMember":
            LOGGER.debug("Drop membership")
            import socket
            import struct
            mreq = struct.pack("=4sl", socket.inet_aton("224.0.1.187"),
                               socket.INADDR_ANY)
            self.pyShelly._socket.setsockopt(socket.IPPROTO_IP,
                                             socket.IP_DROP_MEMBERSHIP, mreq)
            return WebResponseJson({})

        if path == "initSocket":
            self.pyShelly.init_socket()
            return WebResponseJson({'msg': 'init socket done'})
示例#27
0
 def _getConfig(self):
     settings = Settings(CONFIG)
     return {
         'cloud_server': settings["cloud_server"],
         'cloud_auth_key': settings["cloud_auth_key"]
     }
示例#28
0
 def _load_cache(self, name):
     settings = Settings('Shelly.cache')
     return json.loads(settings[name])
示例#29
0
 def _save_cache(self, name, data):
     settings = Settings('Shelly.cache')
     settings[name] = data
示例#30
0
 def getSettings(self):
     return Settings('telldus.scheduler')