class Group(Plugin): implements(ITelldusLiveObserver) def __init__(self): self.devices = [] self.deviceManager = DeviceManager(self.context) for d in self.deviceManager.retrieveDevices('group'): p = d.params() device = GroupDevice() self.devices.append(device) device.setNodeId(d.id()) device.setParams(p) self.deviceManager.addDevice(device) self.deviceManager.finishedLoading('group') self.live = TelldusLive(self.context) def addDevice(self, name, devices): if type(devices) != list: return device = GroupDevice() device.setName(name) device.setParams({ 'devices': devices }) self.devices.append(device) self.deviceManager.addDevice(device) @TelldusLive.handler('group') def __handleCommand(self, msg): data = msg.argument(0).toNative() action = data['action'] if action == 'addGroup': self.addDevice(data['name'], data['devices']) elif action == 'editGroup': deviceId = data['device'] for device in self.devices: if device.id() == deviceId: device.setParams({ 'devices': data['devices'], }) device.paramUpdated('') break elif action == 'groupInfo': deviceId = data['device'] for device in self.devices: if device.id() == deviceId: params = device.params() params['deviceId'] = deviceId self.live.pushToWeb('group', 'groupInfo', params) return elif action == 'remove': deviceId = data['device'] for device in self.devices: if device.id() == deviceId: self.deviceManager.removeDevice(deviceId) self.devices.remove(device) return
def __requestLocalKey(self, msg): args = msg.argument(0).toNative() 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.SHA1()), algorithm=hashes.SHA1(), label=None ) ) live = TelldusLive(self.context) live.pushToWeb('api', 'localkey', { 'key': base64.b64encode(ciphertext), 'ttl': ttl, 'uuid': args.get('uuid', ''), })
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()
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 __init__(self): self.devices = [] self.deviceManager = DeviceManager(self.context) # pylint: disable=too-many-function-args for oldDevice in self.deviceManager.retrieveDevices('group'): params = oldDevice.params() device = GroupDevice() self.devices.append(device) device.setNodeId(oldDevice.id()) device.setParams(params) self.deviceManager.addDevice(device) self.deviceManager.finishedLoading('group') self.live = TelldusLive(self.context) # pylint: disable=too-many-function-args
def __init__(self): self.devices = [] self.deviceManager = DeviceManager(self.context) for d in self.deviceManager.retrieveDevices('group'): p = d.params() device = GroupDevice() self.devices.append(device) device.setNodeId(d.id()) device.setParams(p) self.deviceManager.addDevice(device) self.deviceManager.finishedLoading('group') self.live = TelldusLive(self.context)
class Led(Plugin): implements(ITelldusLiveObserver) def __init__(self): self.gpio = Gpio(self.context) self.live = TelldusLive(self.context) self.gpio.initPin('status:red') self.gpio.initPin('status:green') self.setNetworkLed() def liveConnected(self): self.setNetworkLed() def liveRegistered(self, __msg, __refreshRequired): self.setNetworkLed() def liveDisconnected(self): self.setNetworkLed() def setNetworkLed(self): if self.live.isRegistered(): # We check live status first since we might have connection on another network interface self.gpio.setPin('status:red', 0) self.gpio.setPin('status:green', 1, brightness=50) return if self.live.isConnected(): self.gpio.setPin('status:red', 0) self.gpio.setPin('status:green', 1, brightness=50, freq=1) return if Led.__getIp(Board.networkInterface()) is None: self.gpio.setPin('status:red', 1, freq=1) self.gpio.setPin('status:green', 0) return self.gpio.setPin('status:red', 1, brightness=50) self.gpio.setPin('status:green', 0) @staticmethod def __getIp(iface): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sockfd = sock.fileno() SIOCGIFADDR = 0x8915 # pylint:disable=C0103 ifreq = struct.pack('16sH14s', str(iface), socket.AF_INET, '\x00' * 14) try: res = fcntl.ioctl(sockfd, SIOCGIFADDR, ifreq) except Exception as __error: return None ipAddr = struct.unpack('16sH2x4s8x', res)[2] return socket.inet_ntoa(ipAddr)
def tunnelhandler(self, client, chan): sock = socket.socket() try: sock.connect(('localhost', 22)) except Exception as e: logging.exception(e) return while True: r, w, x = select.select([sock, chan], [], [], 3) if sock in r: data = sock.recv(1024) if len(data) == 0: break chan.send(data) if chan in r: data = chan.recv(1024) if len(data) == 0: break sock.send(data) chan.close() sock.close() TelldusLive(self.context).pushToWeb('remotesupport', 'disconnected', None) client.close() client = None
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 waitForConnection(self, client, transport): chan = transport.accept(60) if chan is None: transport.close() TelldusLive(self.context).pushToWeb('remotesupport', 'disconnected', None) return thr = threading.Thread(target=self.tunnelhandler, args=(client, chan,)) thr.setDaemon(True) thr.start()
def execute(self, triggerInfo=None): del triggerInfo if self.objectType == 'room': room = self.roomManager.rooms.get(self.objectId, None) if room and room.get('responsible', '') == TelldusLive( self.roomManager.context).uuid: self.roomManager.setMode(self.objectId, self.modeId, self.setAlways) else: msg = LiveMessage('RequestRoomModeSet') msg.append({ 'id': self.objectId, 'mode': self.modeId, 'setAlways': self.setAlways }) TelldusLive(self.roomManager.context).send(msg) else: logging.error('Cannot handle mode change for type %s', self.objectType)
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 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
class WebRequestHandler(Plugin): implements(IWebRequestHandler, IWebRequestAuthenticationHandler) def __init__(self): self.store = memstore.MemoryStore() def getTemplatesDirs(self): return [resource_filename('tellduslive', 'web/templates')] def isUrlAuthorized(self, request): return request.session('loggedIn', False) def handleAuthenticationForUrl(self, request): return False def handleRequest(self, plugin, path, params, request, **kwargs): if plugin != 'tellduslive': return None oidconsumer = consumer.Consumer({}, self.store) if path == 'login': try: authrequest = oidconsumer.begin('http://login.telldus.com') except consumer.DiscoveryFailure, exc: logging.error(str(exc[0])) return None # TODO(micke): Error sregRequest = sreg.SRegRequest(required=['fullname', 'email']) authrequest.addExtension(sregRequest) trustRoot = request.base() returnTo = '%s/tellduslive/authorize' % request.base() url = authrequest.redirectURL(trustRoot, returnTo) return WebResponseRedirect(url) if path == 'authorize': url = '%s/tellduslive/authorize' % request.base() info = oidconsumer.complete(params, url) displayIdentifier = info.getDisplayIdentifier() if info.status == consumer.FAILURE and displayIdentifier: return None # TODO(micke): Error elif info.status == consumer.SUCCESS: sregResp = sreg.SRegResponse.fromSuccessResponse(info) data = dict(sregResp.items()) if 'email' not in data: return None # TODO(micke): Error tellduslive = TelldusLive(self.context) if data['email'] != tellduslive.email: return 'loginFailed.html', { 'reason': 1, 'loginEmail': data['email'], 'registeredEmail': tellduslive.email } request.setSession('loggedIn', True) return request.loggedIn() else: return None # TODO(micke): Error return None
def __init__(self): self.version = 0 self.hwVersion = None self.devices = [] self.sensors = [] self.rawEnabled = False self.rawEnabledAt = 0 self.dev = Adapter(self, Board.rf433Port()) self.deviceManager = DeviceManager(self.context) self.registerSensorCleanup() for dev in self.deviceManager.retrieveDevices('433'): params = dev.params() if 'type' not in params: continue if params['type'] == 'sensor': device = SensorNode() self.sensors.append(device) elif params['type'] == 'device': device = DeviceNode(self.dev) # pylint: disable=R0204 self.devices.append(device) else: continue device.setNodeId(dev.id()) device.setParams(params) if params['type'] == 'sensor': # already loaded, keep it that way! device._packageCount = 7 # pylint: disable=W0212 device._sensorValues = dev._sensorValues # pylint: disable=W0212 device.batteryLevel = dev.batteryLevel if hasattr(dev, 'declaredDead'): device.declaredDead = dev.declaredDead self.deviceManager.addDevice(device) self.deviceManager.finishedLoading('433') self.dev.queue( RF433Msg('V', success=self.__version, failure=self.__noVersion)) self.dev.queue( RF433Msg('H', success=self.__hwVersion, failure=self.__noHWVersion)) self.live = TelldusLive(self.context)
def handle(self): sock = self.request[1] product = ''.join(x.capitalize() for x in Board.product().split('-')) live = TelldusLive(Application.defaultContext()) msg = '%s:%s:%s:%s:%s' % ( product, AutoDiscoveryHandler.getMacAddr(Board.networkInterface()), Board.secret(), Board.firmwareVersion(), live.uuid, ) sock.sendto(msg, self.client_address)
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 start(self, server, username): client = paramiko.SSHClient() client.load_system_host_keys() client.set_missing_host_key_policy(paramiko.WarningPolicy()) try: client.connect(server, 22, username=username, key_filename=resource_filename('remotesupport', 'id_rsa')) except Exception as e: logging.exception(e) return transport = client.get_transport() port = transport.request_port_forward('', 0) TelldusLive(self.context).pushToWeb('remotesupport', 'connected', port) thr = threading.Thread(target=self.waitForConnection, args=(client,transport,)) thr.setDaemon(True) thr.start()
def __handleCommand(self, msg): data = msg.argument(0).toNative() action = data['action'] if action == 'addScene': self.addDevice(data['name'], data['devices']) elif action == 'editScene': deviceId = data['device'] for sceneId in self.scenes: device = self.scenes[sceneId] if device.id() == deviceId: device.setParams({ 'devices': data['devices'], }) device.paramUpdated('') break elif action == 'sceneInfo': deviceId = data['device'] for sceneId in self.scenes: device = self.scenes[sceneId] if device.id() == deviceId: params = device.params() params['deviceId'] = deviceId live = TelldusLive(self.context) live.pushToWeb('scene', 'sceneInfo', params) return elif action == 'remove': deviceId = data['device'] for sceneId in self.scenes: device = self.scenes[sceneId] if device.id() == deviceId: self.deviceManager.removeDevice(deviceId) del self.scenes[sceneId] return
def handleRequest(self, plugin, path, params, request, **kwargs): if plugin != 'tellduslive': return None oidconsumer = consumer.Consumer({}, self.store) if path == 'login': try: authrequest = oidconsumer.begin('http://login.telldus.com') except consumer.DiscoveryFailure as exc: logging.error(str(exc[0])) return None # TODO(micke): Error sregRequest = sreg.SRegRequest(required=['fullname', 'email']) authrequest.addExtension(sregRequest) trustRoot = request.base() returnTo = '%s/tellduslive/authorize' % request.base() url = authrequest.redirectURL(trustRoot, returnTo) return WebResponseRedirect(url) if path == 'authorize': url = '%s/tellduslive/authorize' % request.base() info = oidconsumer.complete(params, url) displayIdentifier = info.getDisplayIdentifier() if info.status == consumer.FAILURE and displayIdentifier: return None # TODO(micke): Error elif info.status == consumer.SUCCESS: sregResp = sreg.SRegResponse.fromSuccessResponse(info) data = dict(list(sregResp.items())) if 'email' not in data: return None # TODO(micke): Error tellduslive = TelldusLive(self.context) if not tellduslive.registered or tellduslive.email == '': return 'loginFailed.html', { 'reason': 1, 'loginEmail': data['email'] } if data['email'] != tellduslive.email: return 'loginFailed.html', { 'reason': 2, 'loginEmail': data['email'], 'registeredEmail': tellduslive.email } request.setSession('loggedIn', True) return request.loggedIn() else: return None # TODO(micke): Error return None
def __webMessage(self, msg): live = TelldusLive(self.context) # pylint: disable=too-many-function-args data = msg.argument(0).toNative() print("Store", data) loader = Loader(self.context) if data['action'] == 'getState': live.pushToWeb( 'plugins', 'state', { 'plugins': [plugin.infoObject() for plugin in loader.plugins], 'suggested': list(loader.suggestedPlugins), }) return if data['action'] == 'getStorePlugins': live.pushToWeb('plugins', 'storePlugins', self.storePlugins()) return if data['action'] == 'install': live.pushToWeb('plugins', 'installStatus', self.installStorePlugin(data.get('plugin'))) return
def __init__(self): Application().registerShutdown(self.onShutdown) self.live = TelldusLive(self.context) # pylint: disable=too-many-function-args self.discovered_flag = False self.mqtt_connected_flag = False self.client = mqtt.Client() self.client.on_disconnect = self.onMqttDisconnect self.client.on_connect = self.onMqttConnect self.client.on_message = self.onMqttMessage username = self.config('username') password = self.config('password') # if username setup mqtt login if username != '': self.client.username_pw_set(username, password) useConfigUrl = self.config('useConfigUrl') configUrl = 'https://live.telldus.se' if self.config( 'configUrl') == 'live' else ('http://%s' % getIpAddr()) self.hub = devs.HaHub(self.config('device_name'), self._buildTopic, configUrl if useConfigUrl else None) self._debug('Hub: %s' % json.dumps(self._getDeviceConfig(self.hub))) self.staticDevices = [ self.hub, devs.HaLiveConnection(self.hub, self.live, self._buildTopic), devs.HaIpAddr(self.hub, self._buildTopic), devs.HaCpu(self.hub, self._buildTopic), devs.HaRamFree(self.hub, self._buildTopic), devs.HaNetIORecv(self.hub, self._buildTopic), devs.HaNetIOSent(self.hub, self._buildTopic) ] self.devices = self.staticDevices + [] Application().queue(self.discoverAndConnect) Application().registerScheduledTask(self._updateTimedSensors, seconds=30)
def __init__(self): self.gpio = Gpio(self.context) self.live = TelldusLive(self.context) self.gpio.initPin('status:red') self.gpio.initPin('status:green') self.setNetworkLed()
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']()
class RF433(Plugin): implements(ITelldusLiveObserver) fwVersions = {'18F25K50': 1} def __init__(self): self.version = 0 self.hwVersion = None self.devices = [] self.sensors = [] self.rawEnabled = False self.rawEnabledAt = 0 self.dev = Adapter(self, Board.rf433Port()) deviceNode = DeviceNode(self.dev) self.deviceManager = DeviceManager(self.context) self.registerSensorCleanup() for d in self.deviceManager.retrieveDevices('433'): p = d.params() if 'type' not in p: continue if p['type'] == 'sensor': device = SensorNode() self.sensors.append(device) elif p['type'] == 'device': device = DeviceNode(self.dev) self.devices.append(device) else: continue device.setNodeId(d.id()) device.setParams(p) if p['type'] == 'sensor': device._packageCount = 7 # already loaded, keep it that way! device._sensorValues = d._sensorValues device.batteryLevel = d.batteryLevel self.deviceManager.addDevice(device) self.deviceManager.finishedLoading('433') self.dev.queue( RF433Msg('V', success=self.__version, failure=self.__noVersion)) self.dev.queue( RF433Msg('H', success=self.__hwVersion, failure=self.__noHWVersion)) self.live = TelldusLive(self.context) def addDevice(self, protocol, model, name, params): device = DeviceNode(self.dev) device.setName(name) device.setParams({ 'protocol': protocol, 'model': model, 'protocolParams': params }) self.devices.append(device) self.deviceManager.addDevice(device) def cleanupSensors(self): numberOfSensorsBefore = len(self.sensors) for i, sensor in enumerate(self.sensors): if not sensor.isValid(): self.deviceManager.removeDevice(sensor.id()) del self.sensors[i] self.deviceManager.sensorsUpdated() @TelldusLive.handler('rf433') def __handleCommand(self, msg): data = msg.argument(0).toNative() action = data['action'] if action == 'addDevice': self.addDevice(data['protocol'], data['model'], data['name'], data['parameters']) elif action == 'deviceInfo': deviceId = data['device'] for device in self.devices: if device.id() == deviceId: params = device.params() params['deviceId'] = deviceId self.live.pushToWeb('rf433', 'deviceInfo', params) return elif action == 'editDevice': deviceId = data['device'] for device in self.devices: if device.id() == deviceId: device.setParams({ 'protocol': data['protocol'], 'model': data['model'], 'protocolParams': data['parameters'] }) device.paramUpdated('') break elif action == 'remove': deviceId = data['device'] for device in self.devices: if device.id() == deviceId: self.deviceManager.removeDevice(deviceId) self.devices.remove(device) return elif action == 'rawEnabled': if data['value']: self.rawEnabled = True self.rawEnabledAt = time.time() else: self.rawEnabled = False else: logging.warning("Unknown rf433 command %s", action) @signal('rf433RawData') def decode(self, msg): """ Signal send on any raw data received from 433 receiver. Please note that the TellStick must contain a receiver for this signal to be sent. Not all models contains a receiver. """ if 'class' in msg and msg['class'] == 'sensor': self.decodeSensor(msg) return msg = Protocol.decodeData(msg) for m in msg: self.decodeCommandData(m) if self.rawEnabled: if self.rawEnabledAt < (time.time() - 600): # timeout, only allow scan for 10 minutes at a time self.rawEnabled = False continue self.live.pushToWeb('client', 'rawData', m) def decodeCommandData(self, msg): protocol = msg['protocol'] model = msg['model'] method = msg['method'] methods = Protocol.methodsForProtocol(protocol, model) if not method & methods: return for device in self.devices: params = device.params() if params['protocol'] != protocol: continue if not method & device.methods(): continue deviceParams = params['protocolParams'] thisDevice = True for parameter in Protocol.parametersForProtocol(protocol, model): if parameter not in msg: thisDevice = False break if parameter not in deviceParams: thisDevice = False break if msg[parameter] != deviceParams[parameter]: thisDevice = False break if thisDevice: device.setState(method, None) def decodeData(self, cmd, params): if cmd == 'W': self.decode(params) elif cmd == 'V': # New version received, probably after firmware upload self.__version(params) else: logging.debug("Unknown data: %s", str(cmd)) def decodeSensor(self, msg): protocol = Protocol.protocolInstance(msg['protocol']) if not protocol: logging.error("No known protocol for %s", msg['protocol']) return data = protocol.decodeData(msg) if not data: return p = data['protocol'] m = data['model'] sensorId = data['id'] sensorData = data['values'] sensor = None for s in self.sensors: if s.compare(p, m, sensorId): sensor = s break if sensor is None: sensor = SensorNode() sensor.setParams({'protocol': p, 'model': m, 'sensorId': sensorId}) sensor.setManager(self.deviceManager) self.sensors.append(sensor) if 'battery' in data: sensor.batteryLevel = data['battery'] sensor.updateValues(sensorData) """ Register scheduled job to clean up sensors that have not been updated for a while""" def registerSensorCleanup(self): Application().registerScheduledTask(self.cleanupSensors, hours=12) # every 12th hour t = Timer(10, self.cleanupSensors) # run a first time after 10 minutes t.daemon = True t.name = 'Sensor cleanup' t.start() def __noVersion(self): logging.warning( "Could not get firmware version for RF433, force upgrade") def __noHWVersion(self): logging.warning("Could not get hw version for RF433") def __hwVersion(self, version): logging.debug("Got HW version %s", version) self.hwVersion = version if version not in RF433.fwVersions: return fwVersion = RF433.fwVersions[self.hwVersion] if fwVersion != self.version: logging.info("Version %i is to old, update firmware", self.version) # TODO: implement def __version(self, version): self.version = version logging.info("RF433 version: %i", self.version)
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 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()) 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 ignored return msg = LiveMessage("SensorEvent") sensor = { 'name': device.name(), 'protocol': device.protocol(), 'model': device.model(), 'sensor_id': device.id(), } battery = device.battery() if battery is not None: sensor['battery'] = battery msg.append(sensor) values = device.sensorValues() valueList = [] for valueType in values: for value in values[valueType]: valueList.append({ 'type': valueType, 'lastUp': str(int(time.time())), 'value': str(value['value']), 'scale': value['scale'] }) msg.append(valueList) 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( True) # force name change even for ignored sensor 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 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() data.append({ "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() }) 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, forceIgnored=False): if not self.live.registered: return l = [] for d in self.devices: if d.isSensor() == False or (d.ignored() and not forceIgnored): 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 = [] # TODO(micke): Add current values sensorFrame.append(valueList) l.append(sensorFrame) msg = LiveMessage("SensorsReport") msg.append(l) self.live.send(msg)
def syncRoom(self): TelldusLive(self.context).send(LiveMessage("roomsync-request"))
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.localId>` """ 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. :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 deviceParamUpdated(self, device, param): self.save() if param == 'name': if device.isDevice(): self.__sendDeviceReport() if device.isSensor: self.__sendSensorReport() 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 sensors value is updated. """ if device.isSensor() is 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") # 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: for value in values[valueType]: valueList.append({ 'type': valueType, '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() is 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, extras['origin']) 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() is False: return extras = { 'reason': reason, } if origin: extras['origin'] = origin else: extras['origin'] = 'Unknown' (state, stateValue) = device.state() self.__deviceStateChanged(device, state, stateValue, extras['origin']) 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'] 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']) # 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 isinstance(args['name'], int): dev.setName(str(args['name'])) else: dev.setName(args['name'].decode('UTF-8')) return @TelldusLive.handler('device-requestdata') def __handleDeviceParametersRequest(self, msg): args = msg.argument(0).toNative() device = self.device(args.get('id', 0)) if not device: return reply = LiveMessage('device-datareport') data = {'id': args['id']} if args.get('parameters', 0) == 1: parameters = json.dumps(device.allParameters(), separators=(',', ':'), sort_keys=True) data['parameters'] = parameters data['parametersHash'] = hashlib.sha1(parameters).hexdigest() if args.get('metadata', 0) == 1: 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) @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)) # send an updated sensor report, so that this sensor is hopefully # cleaned up self.__sendSensorReport() def liveRegistered(self, __msg): 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('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() dev = { "id": device.id(), "loadCount": device.loadCount(), "localId": device.localId(), "type": device.typeString(), "name": device.name(), "params": device.params(), "methods": device.methods(), "state": state, "stateValue": stateValue, "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() 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(), 'name': device.name(), 'methods': device.methods(), 'state': state, 'stateValue': str(stateValue), '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") logging.warning("DR %s", lst) 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 __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()
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
def __notifyFrontend(self, action, data): # pylint: disable=too-many-function-args Server(self.context).webSocketSend('plugins', action, data) TelldusLive(self.context).pushToWeb('plugins', action, data)