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