コード例 #1
0
	def onMessage(self, client, userdata, msg):
		try:
			#topic = msg.topic
			payload = msg.payload

			#topicType = topic.split('/')[-1]
			deviceManager = DeviceManager(userdata.context)
			
			device_id = int(msg.topic.split('/')[-2])
			device = deviceManager.device(device_id)
			deviceType = userdata.getDeviceType(device)
			if not deviceType:
				return

			userdata.debug(json.dumps({
				'type': 'command',
				'device_id': device_id,
				'device_type': deviceType,
				'command': payload
			}))

			if deviceType == 'light':
				payload = json.loads(payload)
				if 'brightness' in payload:
					if int(payload['brightness']) == 0:
						device.command(
							Device.TURNOFF, 
							origin = 'mqtt_hass'
						)
					else:
						device.command(
							Device.DIM, 
							value = int(payload['brightness']), 
							origin = 'mqtt_hass'
						)
				else:
					device.command(
						Device.TURNON if payload['state'].upper() == 'ON' \
						else Device.TURNOFF, 
						value = 255, 
						origin = 'mqtt_hass'
					)

			elif deviceType == 'switch':
				device.command(
					Device.TURNON if payload.upper() == 'ON' \
					else Device.BELL if payload.upper() == 'BELL' \
					else Device.TURNOFF, 
					origin = 'mqtt_hass'
				)

			elif deviceType == 'cover':
				device.command(
					Device.UP if payload.upper() == 'OPEN' \
					else Device.DOWN if payload.upper() == 'CLOSE' else \
					Device.STOP, 
					origin = 'mqtt_hass'
				)
		except Exception as e:
			userdata.debug('onMessage exception %s' % str(e))
コード例 #2
0
ファイル: Scheduler.py プロジェクト: TarraAB/tellstick-server
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']()
コード例 #3
0
class Shelly(Plugin):
    implements(IWebReactHandler)
    implements(IWebRequestHandler)

    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()

    def setupPing(self):
        self.ping_count = 0
        self.ping_interval = PING_INTERVAL

        self.ping()

        def loop():
            while not self.stop_ping_loop.wait(self.ping_interval):
                self.ping()

        self.ping_thread = threading.Thread(target=loop)
        self.ping_thread.daemon = True
        self.ping_thread.start()

    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)

    def _initPyShelly(self):
        try:
            self.pyShelly.close()
        except:
            pass
        pys = self.pyShelly = pyShelly()
        pys.igmpFixEnabled = True  #Enable IGMP fix for ZNet
        pys.cb_device_added.append(self._device_added)
        pys.update_status_interval = timedelta(seconds=30)

        self._read_settings()
        ######
        # pys.cb_block_added.append(self._block_added)
        # pys.cb_device_added.append(self._device_added)
        # pys.cb_device_removed.append(self._device_removed)
        pys.cb_save_cache = self._save_cache
        pys.cb_load_cache = self._load_cache
        # pys.username = conf.get(CONF_USERNAME)
        # pys.password = conf.get(CONF_PASSWORD)
        # pys.cloud_auth_key = conf.get(CONF_CLOUD_AUTH_KEY)
        # pys.cloud_server = conf.get(CONF_CLOUD_SERVER)
        # if zeroconf_async_get_instance:
        #     pys.zeroconf = await zeroconf_async_get_instance(self.hass)
        # tmpl_name = conf.get(CONF_TMPL_NAME)
        # if tmpl_name:
        #     pys.tmpl_name = tmpl_name
        # if additional_info:
        #     pys.update_status_interval = timedelta(seconds=update_interval)

        # if pys.only_device_id:
        #     pys.only_device_id = pys.only_device_id.upper()
        # pys.igmp_fix_enabled = conf.get(CONF_IGMPFIX)
        # pys.mdns_enabled = conf.get(CONF_MDNS)
        ###
        pys.start()
        pys.discover()

    def _save_cache(self, name, data):
        settings = Settings('Shelly.cache')
        settings[name] = data

    def _load_cache(self, name):
        settings = Settings('Shelly.cache')
        return json.loads(settings[name])

    def ping(self):
        try:
            headers = {
                "Content-type": "application/x-www-form-urlencoded",
                "Accept": "text/plain",
                "Connection": "close"
            }
            self.ping_count += 1
            params = urllib.urlencode({
                'shelly': __version__,
                'pyShelly': self.pyShelly.version(),
                'uuid': self.uuid,
                'pluginid': 1,
                'ping': self.ping_count,
                'devices': len(self.pyShelly.blocks),
                'level': self.logHandler.logLevel,
                'interval': self.ping_interval
            })
            conn = httplib.HTTPConnection("api.tarra.se")
            conn.request("POST", "/telldus/ping", params, headers)
            resp = conn.getresponse()
            body = resp.read()
            resp = json.loads(body)
            self.logHandler.logLevel = resp['level']
            self.ping_interval = resp['interval']
            conn.close()
        except:
            pass

    @staticmethod
    def getReactComponents():
        return {
            'shelly': {
                'title': 'Shelly',
                'script': 'shelly/shelly.js',
                'tags': ['menu'],
            }
        }

    def matchRequest(self, plugin, path):
        LOGGER.debug("MATCH %s %s", plugin, path)
        if plugin != 'shelly':
            return False
        #if path in ['reset', 'state']:
        #return True
        return True

    def _getConfig(self):
        settings = Settings(CONFIG)
        return {
            'cloud_server': settings["cloud_server"],
            'cloud_auth_key': settings["cloud_auth_key"]
        }

    def _getData(self, all_devs=False):
        shellyDevices = \
            self.deviceManager.retrieveDevices(None if all_devs else "Shelly")
        devices = []
        for d in shellyDevices:
            try:
                buttons = {}
                methods = d.methods()
                if methods & Device.TURNON:
                    buttons["on"] = True
                    buttons["off"] = True
                if methods & Device.UP:
                    buttons["up"] = True
                    buttons["down"] = True
                    buttons["stop"] = True
                buttons["firmware"] = getattr(d, "has_firmware_update", False)
                dev = {
                    'id': d.id(),
                    'localid': d.localId(),
                    'name': d.name(),
                    'isDevice': d.isDevice(),
                    'state': d.state()[0],
                    'params': d.params(),
                    'available': False,
                    'buttons': buttons,
                    'typeName': getattr(d, 'type_name', '')
                }
                if hasattr(d, 'dev'):
                    _dev = d.dev
                    dev["available"] = _dev.available()
                    if (hasattr(_dev, 'rgb') and _dev.rgb is not None):
                        dev['rgb'] = '#' + ''.join('%02x' % v
                                                   for v in _dev.rgb)
                    if hasattr(_dev, "get_dim_value"):
                        dev["brightness"] = _dev.get_dim_value()
                sensors = {}
                values = d.sensorValues()
                if 1 in values:
                    sensors['temp'] = \
                        "%.1f" % float(values[1][0]['value'])
                if 2 in values:
                    sensors['hum'] = \
                        "%.1f" % float(values[2][0]['value'])
                if 256 in values:
                    sensors['consumption'] = \
                        "%.1f" % float(values[256][0]['value'])
                if sensors:
                    dev["sensors"] = sensors
                devices.append(dev)
            except Exception as ex:
                LOGGER.exception("Error reading cache")
        devices.sort(key=lambda x: x['name'])
        return {
            'devices': devices,
            'pyShellyVer': self.pyShelly.version() if self.pyShelly else "",
            'ver': __version__,
            'id': self.uuid
        }

    def refreshClient(self):
        data = self._getData()
        if self.last_sent_data != data:
            self.last_sent_data = data
            Server(self.context).webSocketSend('shelly', 'refresh', data)

    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'})

    def _device_added(self, dev, code):
        LOGGER.info('Add device ' + dev.id + ' ' + str(code))
        if (dev.device_type != "POWERMETER" and dev.device_type != "SWITCH" \
            and dev.device_sub_type != "humidity") \
           or dev.master_unit or dev.major_unit:
            device = ShellyDevice(dev, self)
            self.deviceManager.addDevice(device)

    def shutdown(self):
        if self.pyShelly is not None:
            self.pyShelly.close()
            self.pyShelly = None
        if self.stop_ping_loop is not None:
            self.stop_ping_loop.set()

    def tearDown(self):
        deviceManager = DeviceManager(self.context)
        deviceManager.removeDevicesByType('shelly')
コード例 #4
0
  def onMessage(self, client, userdata, msg):
    try:
      topic = msg.topic
      payload = msg.payload

      if topic.split('/')[-1] == 'set':
        topicType = 'set'
      else:
        topicType = topic.split('/')[-1]
      deviceManager = DeviceManager(userdata.context)
      
      device_id = int(msg.topic.split('/')[3])
      device = deviceManager.device(device_id)
      deviceType = userdata.getDeviceType(device)
      if not deviceType:
        return

      userdata.debug(json.dumps({
        'type': 'command',
        'device_id': device_id,
        'device_type': deviceType,
        'command': payload
      }))

      def failed(reason, **__kwargs):
        self.debug('Device command failed: %s' % reason)

      if deviceType == 'climate':
        if topicType == 'mode':
          mode = { 'fan_only': Thermostat.MODE_FAN }.get(payload, payload)
          setpoint = self.getClimateSetPoint(device, mode)
          if setpoint is not None:
            value = { 
              'mode': mode, 
              'changeMode': True, 
              'temperature': self.getClimateSetPoint(device, mode) 
            }
            self.debug('Command THERMOSTAT value: %s' % value)
            device.command(
              Device.THERMOSTAT, 
              value = value,
              origin = 'mqtt_hass',
              failure = failed
            )
          else:
            self.debug('Can not set mode, setpoint none')
        if topicType == 'setpoint':
          setpoint = float(payload) if payload else None
          if setpoint is not None:
            value = { 
              'mode': self.getClimateMode(device), 
              'changeMode': False, 
              'temperature': setpoint
            }
            self.debug('Command THERMOSTAT value: %s' % value)
            device.command(
              Device.THERMOSTAT,
              value = value,
              origin = 'mqtt_hass',
              failure = failed
            )
          else:
            self.debug('Can not update setpoint, setpoint none (%s)' % payload)

      elif deviceType == 'light':
        payload = json.loads(payload)
        if 'brightness' in payload:
          if int(payload['brightness']) == 0:
            device.command(
              Device.TURNOFF, 
              origin = 'mqtt_hass',
              failure = failed
            )
          else:
            device.command(
              Device.DIM, 
              value = int(payload['brightness']), 
              origin = 'mqtt_hass',
              failure = failed
            )
        else:
          device.command(
            Device.TURNON if payload['state'].upper() == 'ON' \
            else Device.TURNOFF, 
            value = 255, 
            origin = 'mqtt_hass',
            failure = failed
          )

      elif deviceType == 'switch':
        device.command(
          Device.TURNON if payload.upper() == 'ON' \
          else Device.BELL if payload.upper() == 'BELL' \
          else Device.TURNOFF, 
          origin = 'mqtt_hass',
          failure = failed
        )

      elif deviceType == 'cover':
        device.command(
          Device.UP if payload.upper() == 'OPEN' \
          else Device.DOWN if payload.upper() == 'CLOSE' else \
          Device.STOP, 
          origin = 'mqtt_hass',
          failure = failed
        )
    except Exception as e:
      userdata.debug('onMessage exception %s' % str(e))
コード例 #5
0
class TelldusCore(Plugin):
	TELLSTICK_SUCCESS = 0
	TELLSTICK_ERROR_DEVICE_NOT_FOUND = -3
	TELLSTICK_ERROR_UNKNOWN = -99

	implements(IDeviceChange)

	def __init__(self):
		self.deviceManager = DeviceManager(self.context)
		self.clientListener = ConnectionListener('TelldusClient', self.clientMessage)
		self.events = ConnectionListener('TelldusEvents', self.eventMessage)
		Application().registerShutdown(self.shutdown)

	def clientMessage(self, client, msg):
		(func, msg) = TelldusCore.takeString(msg)

		if func == 'tdTurnOn':
			self.doCommand(msg, 'turnon', client)
		elif func == 'tdTurnOff':
			self.doCommand(msg, 'turnoff', client)
		elif func == 'tdBell':
			self.doCommand(msg, '', client)
		#elif func == 'tdDim':
			#pass
		elif func == 'tdExecute':
			self.doCommand(msg, 'execute', client)
		elif func == 'tdUp':
			self.doCommand(msg, 'up', client)
		elif func == 'tdDown':
			self.doCommand(msg, 'down', client)
		elif func == 'tdStop':
			self.doCommand(msg, 'stop', client)
		elif func == 'tdLearn':
			self.doCommand(msg, 'learn', client)
		elif func == 'tdLastSentCommand':
			(deviceId, msg) = TelldusCore.takeInt(msg)
			(supportedMethods, msg) = TelldusCore.takeInt(msg)
			device = self.deviceManager.device(deviceId)
			if not device:
				client.respond(TelldusCore.TELLSTICK_ERROR_DEVICE_NOT_FOUND)
			state, stateValue = device.state()
			client.respond(Device.maskUnsupportedMethods(state, supportedMethods))
		#elif func == 'tdLastSentValue':
			#pass
		elif func == 'tdGetNumberOfDevices':
			client.respond(len(self.__filteredDevices()))
		elif func == 'tdGetDeviceId':
			(deviceIndex, msg) = TelldusCore.takeInt(msg)
			deviceList = self.__filteredDevices()
			if deviceIndex > len(deviceList) - 1:
				client.respond(TelldusCore.TELLSTICK_ERROR_DEVICE_NOT_FOUND)
			device = deviceList[deviceIndex]
			client.respond(device.id())
		#elif func == 'tdGetDeviceType':
			#pass
		elif func == 'tdGetName':
			(deviceId, msg) = TelldusCore.takeInt(msg)
			device = self.deviceManager.device(deviceId)
			if not device:
				client.respond('')
			client.respond(device.name())
		#elif func == 'tdSetName':
			#pass
		#elif func == 'tdGetProtocol':
			#pass
		#elif func == 'tdSetProtocol':
			#pass
		#elif func == 'tdGetModel':
			#pass
		#elif func == 'tdSetModel':
			#pass
		#elif func == 'tdGetDeviceParameter':
			#pass
		#elif func == 'tdSetDeviceParameter':
			#pass
		#elif func == 'tdAddDevice':
			#pass
		#elif func == 'tdRemoveDevice':
			#pass
		elif func == 'tdMethods':
			(deviceId, msg) = TelldusCore.takeInt(msg)
			(supportedMethods, msg) = TelldusCore.takeInt(msg)
			device = self.deviceManager.device(deviceId)
			if not device:
				client.respond(TelldusCore.TELLSTICK_ERROR_DEVICE_NOT_FOUND)
			client.respond(Device.maskUnsupportedMethods(device.methods(), supportedMethods))
		#elif func == 'tdSendRawCommand':
			#pass
		#elif func == 'tdConnectTellStickController':
			#pass
		#elif func == 'tdDisconnectTellStickController':
			#pass
		#elif func == 'tdSensor':
			#pass
		#elif func == 'tdSensorValue':
			#pass
		#elif func == 'tdController':
			#pass
		#elif func == 'tdControllerValue':
			#pass
		#elif func == 'tdSetControllerValue':
			#pass
		#elif func == 'tdRemoveController':
			#pass
		else:
			client.respond(TelldusCore.TELLSTICK_ERROR_UNKNOWN)

	def doCommand(self, msg, action, client):
		(deviceId, msg) = TelldusCore.takeInt(msg)
		device = self.deviceManager.device(deviceId)
		if device is None:
			client.respond(TelldusCore.TELLSTICK_ERROR_DEVICE_NOT_FOUND)
		device.command(action, origin='TelldusCore')
		client.respond(TelldusCore.TELLSTICK_SUCCESS)

	def eventMessage(self, client, msg):
		pass

	def shutdown(self):
		self.clientListener.close()
		self.events.close()

	def stateChanged(self, device, method, statevalue):
		if statevalue is None:
			statevalue = ''
		self.events.broadcast('TDDeviceEvent', device.id(), method, statevalue)

	def __filteredDevices(self):
		return [x for x in self.deviceManager.devices if x.isDevice()]

	@staticmethod
	def takeInt(msg):
		if msg[0] != 'i':
			return ('', msg)
		index = msg.find('s')
		if (index < 0):
			return ('', msg)
		try:
			value = int(msg[1:index], 10)
		except:
			return ('', msg)
		return (value, msg[index+1:])

	@staticmethod
	def takeString(msg):
		if not msg[0].isdigit():
			return ('', msg)
		index = msg.find(':')
		if (index < 0):
			return ('', msg)
		try:
			length = int(msg[:index], 10)
		except:
			return ('', msg)
		value = msg[index+1:index+length+1]
		return (value, msg[index+length+1:])
コード例 #6
0
class Client(Plugin):
    implements(ISignalObserver)

    def __init__(self):
        self.deviceManager = DeviceManager(self.context)

        self.client = mqtt.Client('telldus')
        self.client.on_connect = self.onConnect
        self.client.on_message = self.onMessage
        self.client.on_publish = self.onPublish
        self.client.on_subscribe = self.onSubscribe
        if self.config('hostname') != '':
            self.connect()

    def configWasUpdated(self, key, __value):
        # TODO: handle other changes
        if key == 'hostname':
            self.connect()

    def connect(self):
        if self.config('username') != '':
            self.client.username_pw_set(self.config('username'),
                                        self.config('password'))
        self.client.will_set('%s/status' % self.config('topic'),
                             payload='Offline',
                             qos=0,
                             retain=True)
        self.client.connect_async(self.config('hostname'), self.config('port'))
        self.client.loop_start()

    def subscribeDevice(self, deviceId):
        self.client.subscribe('%s/device/%s/cmd' %
                              (self.config('topic'), deviceId))

    def unsubscribeDevice(self, deviceId):
        self.client.unsubscribe('%s/device/%s/cmd' %
                                (self.config('topic'), deviceId))

    @slot('deviceAdded')
    def onDeviceAdded(self, device):
        self.subscribeDevice(device.id())

    @slot('deviceRemoved')
    def onDeviceRemoved(self, deviceId):
        self.unsubscribeDevice(deviceId)

    @slot('deviceStateChanged')
    def onDeviceStateChanged(self, device, state, stateValue, origin=None):
        del origin
        self.client.publish(
            '%s/device/%s/state' % (self.config('topic'), device.id()),
            json.dumps({
                'name': device.name(),
                'state': state,
                'stateValue': stateValue,
            }))

    @slot('sensorValueUpdated')
    def onSensorValueUpdated(self, device, valueType, value, scale):
        self.client.publish(
            '%s/sensor/%s/value' % (self.config('topic'), device.id()),
            json.dumps({
                'name': device.name(),
                'value': FloatWrapper(value),
                'valueType': valueType,
                'scale': scale,
            }))

    def onConnect(self, client, userdata, flags, result):
        for device in self.deviceManager.retrieveDevices():
            self.subscribeDevice(device.id())
        self.client.publish('%s/status' % self.config('topic'),
                            payload='Online',
                            qos=0,
                            retain=True)

    def onMessage(self, client, userdata, msg):
        try:
            data = json.loads(str(msg.payload.decode('utf-8')))
            deviceId = msg.topic.split('/')[-2]  # _topic_/device/_id_/cmd
            device = self.deviceManager.device(int(deviceId))
            if device:
                device.command(data.get('action'), data.get('value'))
        except ValueError as e:
            logging.error('Could not decode JSON payload %s', e)
        except Exception as e:
            logging.error('Could not perform a command %s', e)

    def onPublish(self, client, obj, mid):
        pass

    def onSubscribe(self, client, obj, mid, granted_qos):
        pass