class Broadlink(Plugin):
    def __init__(self):
        self.deviceManager = DeviceManager(self.context)
        self.devices = None
        thread = Thread(target=self.detectBroadlink,
                        name="Detect Broadlink Devices")
        thread.start()

    def detectBroadlink(self):
        self.devices = broadlink.discover(timeout=5)
        for device in self.devices:
            self.deviceManager.addDevice(BroadDevice(device))
        self.deviceManager.finishedLoading('broadlink')
        Application().registerScheduledTask(self.updateValues,
                                            seconds=300,
                                            runAtOnce=True)

    def tearDown(self):
        self.deviceManager.removeDevicesByType('broadlink')

    def updateValues(self):
        for device in self.deviceManager.retrieveDevices("broadlink"):
            device.updateValue()
Exemple #2
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')
Exemple #3
0
 def tearDown(self):
     deviceManager = DeviceManager(self.context)
     deviceManager.removeDevicesByType('shelly')
class Hue(Plugin):
    implements(IWebRequestHandler)
    implements(IWebReactHandler)
    implements(ISSDPNotifier)
    STATE_NO_BRIDGE, STATE_UNAUTHORIZED, STATE_AUTHORIZED = range(3)

    def __init__(self):
        self.deviceManager = DeviceManager(self.context)
        self.username = None
        self.ssdp = None
        config = self.config('bridge')
        self.activated = config.get('activated', False)
        self.username = config.get('username', '')
        self.bridge = config.get('bridge', '')
        self.state = Hue.STATE_NO_BRIDGE
        self.lights = {}
        self.ssdp = SSDP(self.context)
        if self.activated:
            Application().queue(self.selectBridge, config.get('bridge'))
        Application().registerScheduledTask(self.update, minutes=1)

    @mainthread
    def authorize(self):
        if self.username is None or self.username == '':
            data = self.doCall('POST', '/api',
                               '{"devicetype": "Telldus#TellStick"}')
            resp = data[0]
            if resp.get('error', {}).get('type', None) == 101:
                # Unauthorized, the user needs to press the button. Try again in 5 seconds
                thread = threading.Timer(5.0, self.authorize)
                thread.name = 'Philips Hue authorization poll timer'
                thread.daemon = True
                thread.start()
                return
            if 'success' in resp:
                self.username = resp['success']['username']
                self.activated = True
                self.saveConfig()
                self.setState(Hue.STATE_AUTHORIZED)
            else:
                return
        # Check if username is ok
        data = self.doCall('GET', '/api/%s/lights' % self.username)
        self.setState(Hue.STATE_AUTHORIZED)
        self.parseLights(data)
        self.deviceManager.finishedLoading('hue')

    def doCall(self, requestType, endpoint, body='', bridge=None):
        if bridge is None:
            bridge = self.bridge
        conn = httplib.HTTPConnection(bridge)
        try:
            conn.request(requestType, endpoint, body)
        except Exception:
            return [{'error': 'Could not connect'}]
        response = conn.getresponse()
        try:
            rawData = response.read()
            data = json.loads(rawData)
        except Exception:
            logging.warning("Could not parse JSON")
            logging.warning("%s", rawData)
            return [{'error': 'Could not parse JSON'}]
        return data

    def getReactComponents(self):  # pylint: disable=R0201
        return {
            'hue': {
                'title': 'Philips Hue',
                'script': 'hue/hue.js',
            }
        }

    def matchRequest(self, plugin, path):  # pylint: disable=R0201
        if plugin != 'hue':
            return False
        if path in ['reset', 'state']:
            return True
        return False

    def handleRequest(self, plugin, path, __params, **__kwargs):
        if plugin != 'hue':
            return None

        if path == 'state':
            if self.state == Hue.STATE_NO_BRIDGE:
                # If ssdp fails to detect, use the hue remote service
                self.searchNupnp()
            return WebResponseJson({'state': self.state})

        if path == 'reset':
            self.setState(Hue.STATE_NO_BRIDGE)
            return WebResponseJson({'success': True})

    def parseLights(self, lights):
        if isinstance(lights, list) and len(lights) and 'error' in lights[0]:
            return False
        oldDevices = self.lights.keys()
        for i in lights:
            lightData = lights[i]
            if 'uniqueid' not in lightData:
                continue
            lightId = lightData['uniqueid']
            if lightId in oldDevices:
                # Find any removed lights
                oldDevices.remove(lightId)
            name = lightData.get('name', '')
            if lightId in self.lights:
                light = self.lights[lightId]
                if light.name() != name:
                    light.setName(name)
            else:
                light = Light(lightId, i, self)
                self.lights[lightId] = light
                if 'type' in lightData:
                    light.setType(lightData['type'])
                self.deviceManager.addDevice(light)
                light.setName(name)
            if 'state' in lightData:
                state = lightData['state']
                ourState, ourStateValue = light.state()
                if state['on'] is False:
                    hueState = Device.TURNOFF
                    hueStateValue = ourStateValue
                elif state['bri'] == 254:
                    hueState = Device.TURNON
                    hueStateValue = ourStateValue
                else:
                    hueState = Device.DIM
                    hueStateValue = state['bri']
                if ourState != hueState or ourStateValue != hueStateValue:
                    light.setState(hueState, hueStateValue)
        for lightId in oldDevices:
            light = self.lights[lightId]
            self.deviceManager.removeDevice(light.id())
            del self.lights[lightId]

    def saveConfig(self):
        self.setConfig(
            'bridge', {
                'bridge': self.bridge,
                'username': self.username,
                'activated': self.activated,
            })

    def searchNupnp(self):
        conn = httplib.HTTPSConnection('www.meethue.com')
        conn.request('GET', '/api/nupnp')
        response = conn.getresponse()
        try:
            rawData = response.read()
            data = json.loads(rawData)
        except Exception:
            logging.warning("Could not parse JSON")
            logging.warning("%s", rawData)
            return
        for bridge in data:
            if 'internalipaddress' not in bridge:
                continue
            self.selectBridge(bridge['internalipaddress'])
            return

    def setState(self, newState):
        if newState == Hue.STATE_NO_BRIDGE:
            self.bridge = None
            self.username = None
            self.activated = False
            self.saveConfig()
        elif newState == Hue.STATE_UNAUTHORIZED:
            Application().queue(self.authorize)
        elif newState == Hue.STATE_AUTHORIZED:
            pass
        self.state = newState
        # Notify websocket
        Server(self.context).webSocketSend('hue', 'status',
                                           {'state': self.state})

    def ssdpDeviceFound(self, device):
        if device.type != 'basic:1':
            return
        url = urlparse.urlparse(device.location)
        if self.state == Hue.STATE_NO_BRIDGE:
            self.selectBridge(url.netloc)
        elif self.state == Hue.STATE_AUTHORIZED:
            if url.netloc == self.bridge:
                return
            # Ip to bridge may have has changed
            data = self.doCall('GET',
                               '/api/%s/lights' % self.username,
                               bridge=url.netloc)
            try:
                if 'error' in data[0]:
                    return
            except Exception as __error:
                pass
            # Save new address
            self.bridge = url.netloc
            self.saveConfig()
            self.parseLights(data)

    def selectBridge(self, urlbase):
        if urlbase == '' or urlbase is None:
            self.setState(Hue.STATE_NO_BRIDGE)
            return
        self.bridge = urlbase
        self.saveConfig()
        self.setState(Hue.STATE_UNAUTHORIZED)

    def tearDown(self):
        self.deviceManager.removeDevicesByType('hue')

    def update(self):
        if self.state != Hue.STATE_AUTHORIZED:
            # Skip
            return
        data = self.doCall('GET', '/api/%s/lights' % self.username)
        self.parseLights(data)