From b475480eea2b7eac47bbab9cf802c0ec1814f394 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Sun, 1 Apr 2018 21:34:29 +0200 Subject: [PATCH 1/6] sensor --- homeassistant/components/sensor/qwikswitch.py | 68 ++++++++++++++++--- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/sensor/qwikswitch.py b/homeassistant/components/sensor/qwikswitch.py index 19b32e936708b..09634d560bd42 100644 --- a/homeassistant/components/sensor/qwikswitch.py +++ b/homeassistant/components/sensor/qwikswitch.py @@ -34,28 +34,31 @@ class QSSensor(Entity): def __init__(self, sensor_name, sensor_id): """Initialize the sensor.""" self._name = sensor_name - self.qsid = sensor_id + dat = sensor_id.split(':') + self.qsid = dat[0] + self._params = {} + if dat[1]: + self._params['channel'] = dat[1] + self.sensor = dat[2] + self._decode, self.unit = SENSORS[self.sensor] def update_packet(self, packet): """Receive update packet from QSUSB.""" _LOGGER.debug("Update %s (%s): %s", self.entity_id, self.qsid, packet) - self._val = packet - self.async_schedule_update_ha_state() + val = self._decode(packet.get('data'), **self._params) + if val: + self._val = val + self.async_schedule_update_ha_state() @property def state(self): """Return the value of the sensor.""" - return self._val.get('data', 0) - - @property - def device_state_attributes(self): - """Return the state attributes of the sensor.""" return self._val @property def unit_of_measurement(self): """Return the unit the value is expressed in.""" - return None + return self.unit @property def poll(self): @@ -67,3 +70,50 @@ async def async_added_to_hass(self): # Part of Entity/ToggleEntity self.hass.helpers.dispatcher.async_dispatcher_connect( self.qsid, self.update_packet) + + +# byte 0: +# 4e = imod +# 46 = Door sensor +# byte 1: firmware +# byte 2: bit values +# 00/64: Door open / Close +# 17/xx: All open / Channels 1-4 at 0004 0321 +# byte 3: last change (imod) + + +def decode_qwikcord_ctavg(val): + """Extract the qwikcord current measurements from val (CTavg, _).""" + if len(val) != 16: + return None + return int(val[6:12], 16) + + +def decode_qwikcord_ctsum(val): + """Extract the qwikcord current measurements from val (_, CTsum).""" + if len(val) != 16: + return None + return int(val[12:], 16) + + +def decode_door(val): + """Decode a door sensor.""" + if len(val) == 6 and val.startswith('46'): + return val[-1] == '0' + return None + + +def decode_imod(val, channel=0): + """Decode an 4 channel imod.""" + if len(val) == 8 and val.startswith('4e') and channel < 4: + _map = ((5, 1), (5, 2), (5, 4), (4, 1))[channel] + return (int(val[_map[0]]) & _map[1]) == 0 + return None + + +SENSORS = { + 'imod': (decode_imod, None), + 'door': (decode_door, None), + 'qwikcord_ctavg': (decode_qwikcord_ctavg, 'A/s'), + 'qwikcord_ctsum': (decode_qwikcord_ctsum, 'A/s'), +} From 489941d952ae9ff6e7b3ee0b18c2b55b3e8e2c3e Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Sun, 1 Apr 2018 21:44:25 +0200 Subject: [PATCH 2/6] int --- homeassistant/components/sensor/qwikswitch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/qwikswitch.py b/homeassistant/components/sensor/qwikswitch.py index 09634d560bd42..4a777d34789b6 100644 --- a/homeassistant/components/sensor/qwikswitch.py +++ b/homeassistant/components/sensor/qwikswitch.py @@ -38,7 +38,7 @@ def __init__(self, sensor_name, sensor_id): self.qsid = dat[0] self._params = {} if dat[1]: - self._params['channel'] = dat[1] + self._params['channel'] = int(dat[1]) self.sensor = dat[2] self._decode, self.unit = SENSORS[self.sensor] From 04d80ac106770c97b6e6fa183496310d344a1e80 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Mon, 2 Apr 2018 21:13:02 +0200 Subject: [PATCH 3/6] name & base 16 --- homeassistant/components/sensor/qwikswitch.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/sensor/qwikswitch.py b/homeassistant/components/sensor/qwikswitch.py index 4a777d34789b6..0ea35ae26c36a 100644 --- a/homeassistant/components/sensor/qwikswitch.py +++ b/homeassistant/components/sensor/qwikswitch.py @@ -29,7 +29,7 @@ async def async_setup_platform(hass, _, add_devices, discovery_info=None): class QSSensor(Entity): """Sensor based on a Qwikswitch relay/dimmer module.""" - _val = {} + _val = None def __init__(self, sensor_name, sensor_id): """Initialize the sensor.""" @@ -42,10 +42,16 @@ def __init__(self, sensor_name, sensor_id): self.sensor = dat[2] self._decode, self.unit = SENSORS[self.sensor] + @property + def name(self): + """Return the name of the sensor.""" + return self._name + def update_packet(self, packet): """Receive update packet from QSUSB.""" - _LOGGER.debug("Update %s (%s): %s", self.entity_id, self.qsid, packet) val = self._decode(packet.get('data'), **self._params) + _LOGGER.debug("Update %s (%s) decoded as %s: %s: %s", + self.entity_id, self.qsid, val, self._params, packet) if val: self._val = val self.async_schedule_update_ha_state() @@ -53,7 +59,7 @@ def update_packet(self, packet): @property def state(self): """Return the value of the sensor.""" - return self._val + return str(self._val) @property def unit_of_measurement(self): @@ -67,7 +73,6 @@ def poll(self): async def async_added_to_hass(self): """Listen for updates from QSUSb via dispatcher.""" - # Part of Entity/ToggleEntity self.hass.helpers.dispatcher.async_dispatcher_connect( self.qsid, self.update_packet) @@ -107,7 +112,7 @@ def decode_imod(val, channel=0): """Decode an 4 channel imod.""" if len(val) == 8 and val.startswith('4e') and channel < 4: _map = ((5, 1), (5, 2), (5, 4), (4, 1))[channel] - return (int(val[_map[0]]) & _map[1]) == 0 + return (int(val[_map[0]], 16) & _map[1]) == 0 return None From 2792f6be5477db50291890106b00beec3c294676 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Tue, 3 Apr 2018 22:03:22 +0200 Subject: [PATCH 4/6] test --- .coveragerc | 4 +- homeassistant/components/sensor/qwikswitch.py | 2 +- requirements_test_all.txt | 3 + script/gen_requirements_all.py | 1 + tests/components/sensor/test_qwikswitch.py | 87 +++++++++++++++++++ 5 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 tests/components/sensor/test_qwikswitch.py diff --git a/.coveragerc b/.coveragerc index e9c69d137e2a0..6b1ca91a574b3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -190,8 +190,8 @@ omit = homeassistant/components/pilight.py homeassistant/components/*/pilight.py - homeassistant/components/qwikswitch.py - homeassistant/components/*/qwikswitch.py + homeassistant/components/switch/qwikswitch.py + homeassistant/components/light/qwikswitch.py homeassistant/components/rachio.py homeassistant/components/*/rachio.py diff --git a/homeassistant/components/sensor/qwikswitch.py b/homeassistant/components/sensor/qwikswitch.py index 0ea35ae26c36a..da56fe397c7f9 100644 --- a/homeassistant/components/sensor/qwikswitch.py +++ b/homeassistant/components/sensor/qwikswitch.py @@ -52,7 +52,7 @@ def update_packet(self, packet): val = self._decode(packet.get('data'), **self._params) _LOGGER.debug("Update %s (%s) decoded as %s: %s: %s", self.entity_id, self.qsid, val, self._params, packet) - if val: + if val is not None: self._val = val self.async_schedule_update_ha_state() diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 645b56b9e62df..8fb3eb7190ea8 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -145,6 +145,9 @@ pymonoprice==0.3 # homeassistant.components.binary_sensor.nx584 pynx584==0.4 +# homeassistant.components.qwikswitch +pyqwikswitch==0.6 + # homeassistant.components.sensor.darksky # homeassistant.components.weather.darksky python-forecastio==1.4.0 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index d5bb2701e9bdb..708d9dbd30b77 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -73,6 +73,7 @@ 'pylitejet', 'pymonoprice', 'pynx584', + 'pyqwikswitch', 'python-forecastio', 'pyunifi', 'pywebpush', diff --git a/tests/components/sensor/test_qwikswitch.py b/tests/components/sensor/test_qwikswitch.py new file mode 100644 index 0000000000000..3cdd0bb670f01 --- /dev/null +++ b/tests/components/sensor/test_qwikswitch.py @@ -0,0 +1,87 @@ +"""Test qwikswitch sensors.""" +import asyncio +import logging + +import pytest + +from homeassistant.const import EVENT_HOMEASSISTANT_START +from homeassistant.components.qwikswitch import DOMAIN as QWIKSWITCH +from homeassistant.bootstrap import async_setup_component +from tests.test_util.aiohttp import mock_aiohttp_client + + +_LOGGER = logging.getLogger(__name__) + + +class AiohttpClientMockResponseList(list): + """List that fires an event on empty pop, for aiohttp Mocker.""" + + def decode(self, _): + """Return next item from list.""" + try: + res = list.pop(self) + _LOGGER.debug("MockResponseList popped %s: %s", res, self) + return res + except IndexError: + _LOGGER.debug("MockResponseList empty") + return "" + + async def wait_till_empty(self, hass): + """Wait until empty.""" + while self: + await asyncio.sleep(1) + await hass.async_block_till_done() + await hass.async_block_till_done() + + +LISTEN = AiohttpClientMockResponseList() + + +@pytest.fixture +def aioclient_mock(): + """HTTP client listen and devices.""" + devices = """[ + {"id":"@000001","name":"Switch 1","type":"rel","val":"OFF", + "time":"1522777506","rssi":"51%"}, + {"id":"@000002","name":"Light 2","type":"rel","val":"ON", + "time":"1522777507","rssi":"45%"}, + {"id":"@000003","name":"Dim 3","type":"dim","val":"280c00", + "time":"1522777544","rssi":"62%"}]""" + + with mock_aiohttp_client() as mock_session: + mock_session.get("http://127.0.0.1:2020/&listen", content=LISTEN) + mock_session.get("http://127.0.0.1:2020/&device", text=devices) + yield mock_session + + +# @asyncio.coroutine +async def test_sensor_device(hass, aioclient_mock): + """Test a sensor device.""" + config = { + 'qwikswitch': { + 'sensors': { + 's1': '@a00001:0:imod', + } + } + } + await async_setup_component(hass, QWIKSWITCH, config) + await hass.async_block_till_done() + + state_obj = hass.states.get('sensor.s1') + assert state_obj + assert state_obj.state == 'None' + hass.bus.async_fire(EVENT_HOMEASSISTANT_START) + + LISTEN.append( # Close + """{"id":"@a00001","cmd":"","data":"4e0e1601","rssi":"61%"}""") + await hass.async_block_till_done() + state_obj = hass.states.get('sensor.s1') + assert state_obj.state == 'True' + + # Causes a 30second delay: can be uncommented when upstream library + # allows cancellation of asyncio.sleep(30) on failed packet ("") + # LISTEN.append( # Open + # """{"id":"@a00001","cmd":"","data":"4e0e1701","rssi":"61%"}""") + # await LISTEN.wait_till_empty(hass) + # state_obj = hass.states.get('sensor.s1') + # assert state_obj.state == 'False' From 90e0af358659723c0f6a2f04d4811874edbd15d7 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Fri, 6 Apr 2018 22:12:53 +0200 Subject: [PATCH 5/6] to_library --- homeassistant/components/light/qwikswitch.py | 4 +- homeassistant/components/qwikswitch.py | 61 ++++++++------- homeassistant/components/sensor/qwikswitch.py | 76 ++++--------------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/sensor/test_qwikswitch.py | 5 +- 6 files changed, 53 insertions(+), 97 deletions(-) diff --git a/homeassistant/components/light/qwikswitch.py b/homeassistant/components/light/qwikswitch.py index 26741525b8fc5..528f4f73c53d5 100644 --- a/homeassistant/components/light/qwikswitch.py +++ b/homeassistant/components/light/qwikswitch.py @@ -27,9 +27,9 @@ class QSLight(QSToggleEntity, Light): @property def brightness(self): """Return the brightness of this light (0-255).""" - return self._qsusb[self.qsid, 1] if self._dim else None + return self.device.value if self.device.is_dimmer else None @property def supported_features(self): """Flag supported features.""" - return SUPPORT_BRIGHTNESS if self._dim else 0 + return SUPPORT_BRIGHTNESS if self.device.is_dimmer else 0 diff --git a/homeassistant/components/qwikswitch.py b/homeassistant/components/qwikswitch.py index 708eff7cf118a..f4216f987d68d 100644 --- a/homeassistant/components/qwikswitch.py +++ b/homeassistant/components/qwikswitch.py @@ -18,7 +18,7 @@ from homeassistant.components.light import ATTR_BRIGHTNESS import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyqwikswitch==0.6'] +REQUIREMENTS = ['pyqwikswitch==0.7'] _LOGGER = logging.getLogger(__name__) @@ -34,7 +34,13 @@ vol.Coerce(str), vol.Optional(CONF_DIMMER_ADJUST, default=1): CV_DIM_VALUE, vol.Optional(CONF_BUTTON_EVENTS, default=[]): cv.ensure_list_csv, - vol.Optional(CONF_SENSORS, default={}): vol.Schema({cv.slug: str}), + vol.Optional(CONF_SENSORS, default=[]): vol.All( + cv.ensure_list, [vol.Schema({ + vol.Required('id'): str, + vol.Optional('channel', default=1): int, + vol.Required('name'): str, + vol.Required('type'): str, + })]), vol.Optional(CONF_SWITCHES, default=[]): vol.All( cv.ensure_list, [str]) })}, extra=vol.ALLOW_EXTRA) @@ -57,12 +63,9 @@ class QSToggleEntity(Entity): def __init__(self, qsid, qsusb): """Initialize the ToggleEntity.""" - from pyqwikswitch import (QS_NAME, QSDATA, QS_TYPE, QSType) self.qsid = qsid - self._qsusb = qsusb.devices - dev = qsusb.devices[qsid] - self._dim = dev[QS_TYPE] == QSType.dimmer - self._name = dev[QSDATA][QS_NAME] + self.devices = qsusb.devices + self.device = qsusb.devices[qsid] @property def should_poll(self): @@ -72,21 +75,21 @@ def should_poll(self): @property def name(self): """Return the name of the light.""" - return self._name + return self.device.name @property def is_on(self): """Check if device is on (non-zero).""" - return self._qsusb[self.qsid, 1] > 0 + return self.device.value > 0 async def async_turn_on(self, **kwargs): """Turn the device on.""" new = kwargs.get(ATTR_BRIGHTNESS, 255) - self._qsusb.set_value(self.qsid, new) + self.devices.set_value(self.qsid, new) async def async_turn_off(self, **_): """Turn the device off.""" - self._qsusb.set_value(self.qsid, 0) + self.devices.set_value(self.qsid, 0) def _update(self, _packet=None): """Schedule an update - match dispather_send signature.""" @@ -101,8 +104,7 @@ async def async_added_to_hass(self): async def async_setup(hass, config): """Qwiskswitch component setup.""" from pyqwikswitch.async_ import QSUsb - from pyqwikswitch import ( - CMD_BUTTONS, QS_CMD, QS_ID, QS_TYPE, QSType) + from pyqwikswitch import CMD_BUTTONS, QS_CMD, QS_ID, QSType # Add cmd's to in /&listen packets will fire events # By default only buttons of type [TOGGLE,SCENE EXE,LEVEL] @@ -131,17 +133,17 @@ def callback_value_changed(_qsd, qsid, _val): hass.data[DOMAIN] = qsusb _new = {'switch': [], 'light': [], 'sensor': sensors} - for _id, item in qsusb.devices: - if _id in switches: - if item[QS_TYPE] != QSType.relay: + for qsid, dev in qsusb.devices.items(): + if qsid in switches: + if dev.qstype != QSType.relay: _LOGGER.warning( - "You specified a switch that is not a relay %s", _id) + "You specified a switch that is not a relay %s", qsid) continue - _new['switch'].append(_id) - elif item[QS_TYPE] in [QSType.relay, QSType.dimmer]: - _new['light'].append(_id) + _new['switch'].append(qsid) + elif dev.qstype in (QSType.relay, QSType.dimmer): + _new['light'].append(qsid) else: - _LOGGER.warning("Ignored unknown QSUSB device: %s", item) + _LOGGER.warning("Ignored unknown QSUSB device: %s", dev) continue # Load platforms @@ -149,24 +151,21 @@ def callback_value_changed(_qsd, qsid, _val): if comp_conf: load_platform(hass, comp_name, DOMAIN, {DOMAIN: comp_conf}, config) - def callback_qs_listen(item): + def callback_qs_listen(qspacket): """Typically a button press or update signal.""" # If button pressed, fire a hass event - if QS_ID in item: - if item.get(QS_CMD, '') in cmd_buttons: + if QS_ID in qspacket: + if qspacket.get(QS_CMD, '') in cmd_buttons: hass.bus.async_fire( - 'qwikswitch.button.{}'.format(item[QS_ID]), item) + 'qwikswitch.button.{}'.format(qspacket[QS_ID]), qspacket) return - # Private method due to bad __iter__ design in qsusb - # qsusb.devices returns a list of tuples - if item[QS_ID] not in \ - qsusb.devices._data: # pylint: disable=protected-access + if qspacket[QS_ID] not in qsusb.devices: # Not a standard device in, component can handle packet # i.e. sensors - _LOGGER.debug("Dispatch %s ((%s))", item[QS_ID], item) + _LOGGER.debug("Dispatch %s ((%s))", qspacket[QS_ID], qspacket) hass.helpers.dispatcher.async_dispatcher_send( - item[QS_ID], item) + qspacket[QS_ID], qspacket) # Update all ha_objects hass.async_add_job(qsusb.update_from_devices) diff --git a/homeassistant/components/sensor/qwikswitch.py b/homeassistant/components/sensor/qwikswitch.py index da56fe397c7f9..0d511d088a92f 100644 --- a/homeassistant/components/sensor/qwikswitch.py +++ b/homeassistant/components/sensor/qwikswitch.py @@ -15,14 +15,13 @@ async def async_setup_platform(hass, _, add_devices, discovery_info=None): - """Add lights from the main Qwikswitch component.""" + """Add sensor from the main Qwikswitch component.""" if discovery_info is None: return qsusb = hass.data[QWIKSWITCH] _LOGGER.debug("Setup qwikswitch.sensor %s, %s", qsusb, discovery_info) - devs = [QSSensor(name, qsid) - for name, qsid in discovery_info[QWIKSWITCH].items()] + devs = [QSSensor(sensor) for sensor in discovery_info[QWIKSWITCH]] add_devices(devs) @@ -31,16 +30,18 @@ class QSSensor(Entity): _val = None - def __init__(self, sensor_name, sensor_id): + def __init__(self, sensor): """Initialize the sensor.""" - self._name = sensor_name - dat = sensor_id.split(':') - self.qsid = dat[0] - self._params = {} - if dat[1]: - self._params['channel'] = int(dat[1]) - self.sensor = dat[2] - self._decode, self.unit = SENSORS[self.sensor] + from pyqwikswitch import SENSORS + + self._name = sensor['name'] + self.qsid = sensor['id'] + self.channel = sensor['channel'] + self.sensor_type = sensor['type'] + + self._decode, self.unit = SENSORS[self.sensor_type] + if isinstance(self.unit, type): + self.unit = "{}:{}".format(self.sensor_type, self.channel) @property def name(self): @@ -49,9 +50,9 @@ def name(self): def update_packet(self, packet): """Receive update packet from QSUSB.""" - val = self._decode(packet.get('data'), **self._params) + val = self._decode(packet.get('data'), channel=self.channel) _LOGGER.debug("Update %s (%s) decoded as %s: %s: %s", - self.entity_id, self.qsid, val, self._params, packet) + self.entity_id, self.qsid, val, self.channel, packet) if val is not None: self._val = val self.async_schedule_update_ha_state() @@ -75,50 +76,3 @@ async def async_added_to_hass(self): """Listen for updates from QSUSb via dispatcher.""" self.hass.helpers.dispatcher.async_dispatcher_connect( self.qsid, self.update_packet) - - -# byte 0: -# 4e = imod -# 46 = Door sensor -# byte 1: firmware -# byte 2: bit values -# 00/64: Door open / Close -# 17/xx: All open / Channels 1-4 at 0004 0321 -# byte 3: last change (imod) - - -def decode_qwikcord_ctavg(val): - """Extract the qwikcord current measurements from val (CTavg, _).""" - if len(val) != 16: - return None - return int(val[6:12], 16) - - -def decode_qwikcord_ctsum(val): - """Extract the qwikcord current measurements from val (_, CTsum).""" - if len(val) != 16: - return None - return int(val[12:], 16) - - -def decode_door(val): - """Decode a door sensor.""" - if len(val) == 6 and val.startswith('46'): - return val[-1] == '0' - return None - - -def decode_imod(val, channel=0): - """Decode an 4 channel imod.""" - if len(val) == 8 and val.startswith('4e') and channel < 4: - _map = ((5, 1), (5, 2), (5, 4), (4, 1))[channel] - return (int(val[_map[0]], 16) & _map[1]) == 0 - return None - - -SENSORS = { - 'imod': (decode_imod, None), - 'door': (decode_door, None), - 'qwikcord_ctavg': (decode_qwikcord_ctavg, 'A/s'), - 'qwikcord_ctsum': (decode_qwikcord_ctsum, 'A/s'), -} diff --git a/requirements_all.txt b/requirements_all.txt index 7af7bdb95ec11..18587b3051f09 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -885,7 +885,7 @@ pyowm==2.8.0 pypollencom==1.1.1 # homeassistant.components.qwikswitch -pyqwikswitch==0.6 +pyqwikswitch==0.7 # homeassistant.components.rainbird pyrainbird==0.1.3 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 8fb3eb7190ea8..12c09b3c8cb82 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -146,7 +146,7 @@ pymonoprice==0.3 pynx584==0.4 # homeassistant.components.qwikswitch -pyqwikswitch==0.6 +pyqwikswitch==0.7 # homeassistant.components.sensor.darksky # homeassistant.components.weather.darksky diff --git a/tests/components/sensor/test_qwikswitch.py b/tests/components/sensor/test_qwikswitch.py index 3cdd0bb670f01..d9799b8530e72 100644 --- a/tests/components/sensor/test_qwikswitch.py +++ b/tests/components/sensor/test_qwikswitch.py @@ -60,7 +60,10 @@ async def test_sensor_device(hass, aioclient_mock): config = { 'qwikswitch': { 'sensors': { - 's1': '@a00001:0:imod', + 'name': 's1', + 'id': '@a00001', + 'channel': 1, + 'type': 'imod', } } } From c06e52da5bc9c286a30672fb36f99e223f3b6e14 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Sun, 8 Apr 2018 01:20:09 +0200 Subject: [PATCH 6/6] Extract QSEntity --- homeassistant/components/qwikswitch.py | 63 ++++++++++--------- homeassistant/components/sensor/qwikswitch.py | 23 +------ 2 files changed, 37 insertions(+), 49 deletions(-) diff --git a/homeassistant/components/qwikswitch.py b/homeassistant/components/qwikswitch.py index f4216f987d68d..36bd726fa2dc2 100644 --- a/homeassistant/components/qwikswitch.py +++ b/homeassistant/components/qwikswitch.py @@ -46,11 +46,36 @@ })}, extra=vol.ALLOW_EXTRA) -class QSToggleEntity(Entity): - """Representation of a Qwikswitch Entity. +class QSEntity(Entity): + """Qwikswitch Entity base.""" + + def __init__(self, qsid, name): + """Initialize the QSEntity.""" + self._name = name + self.qsid = qsid + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def poll(self): + """QS sensors gets packets in update_packet.""" + return False + + def update_packet(self, packet): + """Receive update packet from QSUSB. Match dispather_send signature.""" + self.async_schedule_update_ha_state() + + async def async_added_to_hass(self): + """Listen for updates from QSUSb via dispatcher.""" + self.hass.helpers.dispatcher.async_dispatcher_connect( + self.qsid, self.update_packet) - Implement base QS methods. Modeled around HA ToggleEntity[1] & should only - be used in a class that extends both QSToggleEntity *and* ToggleEntity. + +class QSToggleEntity(QSEntity): + """Representation of a Qwikswitch Toggle Entity. Implemented: - QSLight extends QSToggleEntity and Light[2] (ToggleEntity[1]) @@ -63,19 +88,8 @@ class QSToggleEntity(Entity): def __init__(self, qsid, qsusb): """Initialize the ToggleEntity.""" - self.qsid = qsid - self.devices = qsusb.devices self.device = qsusb.devices[qsid] - - @property - def should_poll(self): - """No polling needed.""" - return False - - @property - def name(self): - """Return the name of the light.""" - return self.device.name + super().__init__(qsid, self.device.name) @property def is_on(self): @@ -85,20 +99,11 @@ def is_on(self): async def async_turn_on(self, **kwargs): """Turn the device on.""" new = kwargs.get(ATTR_BRIGHTNESS, 255) - self.devices.set_value(self.qsid, new) + self.hass.data[DOMAIN].devices.set_value(self.qsid, new) async def async_turn_off(self, **_): """Turn the device off.""" - self.devices.set_value(self.qsid, 0) - - def _update(self, _packet=None): - """Schedule an update - match dispather_send signature.""" - self.async_schedule_update_ha_state() - - async def async_added_to_hass(self): - """Listen for updates from QSUSb via dispatcher.""" - self.hass.helpers.dispatcher.async_dispatcher_connect( - self.qsid, self._update) + self.hass.data[DOMAIN].devices.set_value(self.qsid, 0) async def async_setup(hass, config): @@ -114,8 +119,8 @@ async def async_setup(hass, config): url = config[DOMAIN][CONF_URL] dimmer_adjust = config[DOMAIN][CONF_DIMMER_ADJUST] - sensors = config[DOMAIN]['sensors'] - switches = config[DOMAIN]['switches'] + sensors = config[DOMAIN][CONF_SENSORS] + switches = config[DOMAIN][CONF_SWITCHES] def callback_value_changed(_qsd, qsid, _val): """Update entity values based on device change.""" diff --git a/homeassistant/components/sensor/qwikswitch.py b/homeassistant/components/sensor/qwikswitch.py index 0d511d088a92f..98c67b7a21c98 100644 --- a/homeassistant/components/sensor/qwikswitch.py +++ b/homeassistant/components/sensor/qwikswitch.py @@ -6,8 +6,7 @@ """ import logging -from homeassistant.components.qwikswitch import DOMAIN as QWIKSWITCH -from homeassistant.helpers.entity import Entity +from homeassistant.components.qwikswitch import DOMAIN as QWIKSWITCH, QSEntity DEPENDENCIES = [QWIKSWITCH] @@ -25,7 +24,7 @@ async def async_setup_platform(hass, _, add_devices, discovery_info=None): add_devices(devs) -class QSSensor(Entity): +class QSSensor(QSEntity): """Sensor based on a Qwikswitch relay/dimmer module.""" _val = None @@ -34,8 +33,7 @@ def __init__(self, sensor): """Initialize the sensor.""" from pyqwikswitch import SENSORS - self._name = sensor['name'] - self.qsid = sensor['id'] + super().__init__(sensor['id'], sensor['name']) self.channel = sensor['channel'] self.sensor_type = sensor['type'] @@ -43,11 +41,6 @@ def __init__(self, sensor): if isinstance(self.unit, type): self.unit = "{}:{}".format(self.sensor_type, self.channel) - @property - def name(self): - """Return the name of the sensor.""" - return self._name - def update_packet(self, packet): """Receive update packet from QSUSB.""" val = self._decode(packet.get('data'), channel=self.channel) @@ -66,13 +59,3 @@ def state(self): def unit_of_measurement(self): """Return the unit the value is expressed in.""" return self.unit - - @property - def poll(self): - """QS sensors gets packets in update_packet.""" - return False - - async def async_added_to_hass(self): - """Listen for updates from QSUSb via dispatcher.""" - self.hass.helpers.dispatcher.async_dispatcher_connect( - self.qsid, self.update_packet)