Qwikswitch async refactor & sensor (#13509)

This commit is contained in:
Johann Kellerman 2018-03-29 23:29:46 +02:00 committed by GitHub
parent 298e6eeef1
commit 3fdb0002a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 185 additions and 111 deletions

View file

@ -4,18 +4,32 @@ Support for Qwikswitch Relays and Dimmers.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.qwikswitch/ https://home-assistant.io/components/light.qwikswitch/
""" """
import logging from homeassistant.components.qwikswitch import (
QSToggleEntity, DOMAIN as QWIKSWITCH)
from homeassistant.components.light import SUPPORT_BRIGHTNESS, Light
DEPENDENCIES = ['qwikswitch'] DEPENDENCIES = [QWIKSWITCH]
# pylint: disable=unused-argument async def async_setup_platform(hass, _, add_devices, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Add lights from the main Qwikswitch component.""" """Add lights from the main Qwikswitch component."""
if discovery_info is None: if discovery_info is None:
logging.getLogger(__name__).error( return
"Configure Qwikswitch Light component failed")
return False
add_devices(hass.data['qwikswitch']['light']) qsusb = hass.data[QWIKSWITCH]
return True devs = [QSLight(qsid, qsusb) for qsid in discovery_info[QWIKSWITCH]]
add_devices(devs)
class QSLight(QSToggleEntity, Light):
"""Light based on a Qwikswitch relay/dimmer module."""
@property
def brightness(self):
"""Return the brightness of this light (0-255)."""
return self._qsusb[self.qsid, 1] if self._dim else None
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_BRIGHTNESS if self._dim else 0

View file

@ -4,21 +4,21 @@ Support for Qwikswitch devices.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/qwikswitch/ https://home-assistant.io/components/qwikswitch/
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, CONF_URL) EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, CONF_URL,
CONF_SENSORS, CONF_SWITCHES)
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.discovery import load_platform from homeassistant.helpers.discovery import load_platform
from homeassistant.components.light import ( from homeassistant.helpers.entity import Entity
ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.components.switch import SwitchDevice import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyqwikswitch==0.5'] REQUIREMENTS = ['pyqwikswitch==0.6']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -33,11 +33,14 @@ CONFIG_SCHEMA = vol.Schema({
vol.Required(CONF_URL, default='http://127.0.0.1:2020'): vol.Required(CONF_URL, default='http://127.0.0.1:2020'):
vol.Coerce(str), vol.Coerce(str),
vol.Optional(CONF_DIMMER_ADJUST, default=1): CV_DIM_VALUE, vol.Optional(CONF_DIMMER_ADJUST, default=1): CV_DIM_VALUE,
vol.Optional(CONF_BUTTON_EVENTS): vol.Coerce(str) vol.Optional(CONF_BUTTON_EVENTS, default=[]): cv.ensure_list_csv,
vol.Optional(CONF_SENSORS, default={}): vol.Schema({cv.slug: str}),
vol.Optional(CONF_SWITCHES, default=[]): vol.All(
cv.ensure_list, [str])
})}, extra=vol.ALLOW_EXTRA) })}, extra=vol.ALLOW_EXTRA)
class QSToggleEntity(object): class QSToggleEntity(Entity):
"""Representation of a Qwikswitch Entity. """Representation of a Qwikswitch Entity.
Implement base QS methods. Modeled around HA ToggleEntity[1] & should only Implement base QS methods. Modeled around HA ToggleEntity[1] & should only
@ -55,7 +58,7 @@ class QSToggleEntity(object):
def __init__(self, qsid, qsusb): def __init__(self, qsid, qsusb):
"""Initialize the ToggleEntity.""" """Initialize the ToggleEntity."""
from pyqwikswitch import (QS_NAME, QSDATA, QS_TYPE, QSType) from pyqwikswitch import (QS_NAME, QSDATA, QS_TYPE, QSType)
self._id = qsid self.qsid = qsid
self._qsusb = qsusb.devices self._qsusb = qsusb.devices
dev = qsusb.devices[qsid] dev = qsusb.devices[qsid]
self._dim = dev[QS_TYPE] == QSType.dimmer self._dim = dev[QS_TYPE] == QSType.dimmer
@ -74,129 +77,113 @@ class QSToggleEntity(object):
@property @property
def is_on(self): def is_on(self):
"""Check if device is on (non-zero).""" """Check if device is on (non-zero)."""
return self._qsusb[self._id, 1] > 0 return self._qsusb[self.qsid, 1] > 0
def turn_on(self, **kwargs): async def async_turn_on(self, **kwargs):
"""Turn the device on.""" """Turn the device on."""
new = kwargs.get(ATTR_BRIGHTNESS, 255) new = kwargs.get(ATTR_BRIGHTNESS, 255)
self._qsusb.set_value(self._id, new) self._qsusb.set_value(self.qsid, new)
@asyncio.coroutine async def async_turn_off(self, **_):
def async_turn_on(self, **kwargs):
"""Turn the device on."""
new = kwargs.get(ATTR_BRIGHTNESS, 255)
self._qsusb.set_value(self._id, new)
def turn_off(self, **kwargs): # pylint: disable=unused-argument
"""Turn the device off.""" """Turn the device off."""
self._qsusb.set_value(self._id, 0) self._qsusb.set_value(self.qsid, 0)
@asyncio.coroutine def _update(self, _packet=None):
def async_turn_off(self, **kwargs): # pylint: disable=unused-argument """Schedule an update - match dispather_send signature."""
"""Turn the device off.""" self.async_schedule_update_ha_state()
self._qsusb.set_value(self._id, 0)
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)
class QSSwitch(QSToggleEntity, SwitchDevice): async def async_setup(hass, config):
"""Switch based on a Qwikswitch relay module.""" """Qwiskswitch component setup."""
from pyqwikswitch.async_ import QSUsb
pass
class QSLight(QSToggleEntity, Light):
"""Light based on a Qwikswitch relay/dimmer module."""
@property
def brightness(self):
"""Return the brightness of this light (0-255)."""
return self._qsusb[self._id, 1] if self._dim else None
@property
def supported_features(self):
"""Flag supported features."""
return SUPPORT_BRIGHTNESS if self._dim else None
@asyncio.coroutine
def async_setup(hass, config):
"""Setup qwiskswitch component."""
from pyqwikswitch.async import QSUsb
from pyqwikswitch import ( from pyqwikswitch import (
CMD_BUTTONS, QS_CMD, QSDATA, QS_ID, QS_NAME, QS_TYPE, QSType) CMD_BUTTONS, QS_CMD, QS_ID, QS_TYPE, QSType)
hass.data[DOMAIN] = {} # Add cmd's to in /&listen packets will fire events
# Override which cmd's in /&listen packets will fire events
# By default only buttons of type [TOGGLE,SCENE EXE,LEVEL] # By default only buttons of type [TOGGLE,SCENE EXE,LEVEL]
cmd_buttons = config[DOMAIN].get(CONF_BUTTON_EVENTS, ','.join(CMD_BUTTONS)) cmd_buttons = set(CMD_BUTTONS)
cmd_buttons = cmd_buttons.split(',') for btn in config[DOMAIN][CONF_BUTTON_EVENTS]:
cmd_buttons.add(btn)
url = config[DOMAIN][CONF_URL] url = config[DOMAIN][CONF_URL]
dimmer_adjust = config[DOMAIN][CONF_DIMMER_ADJUST] dimmer_adjust = config[DOMAIN][CONF_DIMMER_ADJUST]
sensors = config[DOMAIN]['sensors']
switches = config[DOMAIN]['switches']
def callback_value_changed(qsdevices, key, new): \ def callback_value_changed(_qsd, qsid, _val):
# pylint: disable=unused-argument """Update entity values based on device change."""
"""Update entiry values based on device change.""" _LOGGER.debug("Dispatch %s (update from devices)", qsid)
entity = hass.data[DOMAIN].get(key) hass.helpers.dispatcher.async_dispatcher_send(qsid, None)
if entity is not None:
entity.schedule_update_ha_state() # Part of Entity/ToggleEntity
session = async_get_clientsession(hass) session = async_get_clientsession(hass)
qsusb = QSUsb(url=url, dim_adj=dimmer_adjust, session=session, qsusb = QSUsb(url=url, dim_adj=dimmer_adjust, session=session,
callback_value_changed=callback_value_changed) callback_value_changed=callback_value_changed)
@callback
def async_stop(event): # pylint: disable=unused-argument
"""Stop the listener queue and clean up."""
nonlocal qsusb
qsusb.stop()
qsusb = None
hass.data[DOMAIN] = {}
_LOGGER.info("Waiting for long poll to QSUSB to time out")
hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, async_stop)
# Discover all devices in QSUSB # Discover all devices in QSUSB
yield from qsusb.update_from_devices() if not await qsusb.update_from_devices():
hass.data[DOMAIN]['switch'] = [] return False
hass.data[DOMAIN]['light'] = []
hass.data[DOMAIN] = qsusb
_new = {'switch': [], 'light': [], 'sensor': sensors}
for _id, item in qsusb.devices: for _id, item in qsusb.devices:
if (item[QS_TYPE] == QSType.relay and if _id in switches:
item[QSDATA][QS_NAME].lower().endswith(' switch')): if item[QS_TYPE] != QSType.relay:
item[QSDATA][QS_NAME] = item[QSDATA][QS_NAME][:-7] # Remove switch _LOGGER.warning(
new_dev = QSSwitch(_id, qsusb) "You specified a switch that is not a relay %s", _id)
hass.data[DOMAIN]['switch'].append(new_dev) continue
_new['switch'].append(_id)
elif item[QS_TYPE] in [QSType.relay, QSType.dimmer]: elif item[QS_TYPE] in [QSType.relay, QSType.dimmer]:
new_dev = QSLight(_id, qsusb) _new['light'].append(_id)
hass.data[DOMAIN]['light'].append(new_dev)
else: else:
_LOGGER.warning("Ignored unknown QSUSB device: %s", item) _LOGGER.warning("Ignored unknown QSUSB device: %s", item)
continue continue
hass.data[DOMAIN][_id] = new_dev
# Load platforms # Load platforms
for comp_name in ('switch', 'light'): for comp_name, comp_conf in _new.items():
if hass.data[DOMAIN][comp_name]: if comp_conf:
load_platform(hass, comp_name, 'qwikswitch', {}, config) load_platform(hass, comp_name, DOMAIN, {DOMAIN: comp_conf}, config)
def callback_qs_listen(item): def callback_qs_listen(item):
"""Typically a button press or update signal.""" """Typically a button press or update signal."""
if qsusb is None: # Shutting down
return
# If button pressed, fire a hass event # If button pressed, fire a hass event
if item.get(QS_CMD, '') in cmd_buttons and QS_ID in item: if QS_ID in item:
hass.bus.async_fire('qwikswitch.button.{}'.format(item[QS_ID])) if item.get(QS_CMD, '') in cmd_buttons:
return hass.bus.async_fire(
'qwikswitch.button.{}'.format(item[QS_ID]), item)
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
# Not a standard device in, component can handle packet
# i.e. sensors
_LOGGER.debug("Dispatch %s ((%s))", item[QS_ID], item)
hass.helpers.dispatcher.async_dispatcher_send(
item[QS_ID], item)
# Update all ha_objects # Update all ha_objects
hass.async_add_job(qsusb.update_from_devices) hass.async_add_job(qsusb.update_from_devices)
@callback @callback
def async_start(event): # pylint: disable=unused-argument def async_start(_):
"""Start listening.""" """Start listening."""
hass.async_add_job(qsusb.listen, callback_qs_listen) hass.async_add_job(qsusb.listen, callback_qs_listen)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, async_start) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, async_start)
@callback
def async_stop(_):
"""Stop the listener queue and clean up."""
hass.data[DOMAIN].stop()
_LOGGER.info("Waiting for long poll to QSUSB to time out (max 30sec)")
hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, async_stop)
return True return True

View file

@ -0,0 +1,69 @@
"""
Support for Qwikswitch Sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.qwikswitch/
"""
import logging
from homeassistant.components.qwikswitch import DOMAIN as QWIKSWITCH
from homeassistant.helpers.entity import Entity
DEPENDENCIES = [QWIKSWITCH]
_LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, _, add_devices, discovery_info=None):
"""Add lights 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()]
add_devices(devs)
class QSSensor(Entity):
"""Sensor based on a Qwikswitch relay/dimmer module."""
_val = {}
def __init__(self, sensor_name, sensor_id):
"""Initialize the sensor."""
self._name = sensor_name
self.qsid = sensor_id
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()
@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
@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."""
# Part of Entity/ToggleEntity
self.hass.helpers.dispatcher.async_dispatcher_connect(
self.qsid, self.update_packet)

View file

@ -4,18 +4,22 @@ Support for Qwikswitch relays.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.qwikswitch/ https://home-assistant.io/components/switch.qwikswitch/
""" """
import logging from homeassistant.components.qwikswitch import (
QSToggleEntity, DOMAIN as QWIKSWITCH)
from homeassistant.components.switch import SwitchDevice
DEPENDENCIES = ['qwikswitch'] DEPENDENCIES = [QWIKSWITCH]
# pylint: disable=unused-argument async def async_setup_platform(hass, _, add_devices, discovery_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Add switches from the main Qwikswitch component.""" """Add switches from the main Qwikswitch component."""
if discovery_info is None: if discovery_info is None:
logging.getLogger(__name__).error( return
"Configure Qwikswitch Switch component failed")
return False
add_devices(hass.data['qwikswitch']['switch']) qsusb = hass.data[QWIKSWITCH]
return True devs = [QSSwitch(qsid, qsusb) for qsid in discovery_info[QWIKSWITCH]]
add_devices(devs)
class QSSwitch(QSToggleEntity, SwitchDevice):
"""Switch based on a Qwikswitch relay module."""

View file

@ -860,7 +860,7 @@ pyowm==2.8.0
pypollencom==1.1.1 pypollencom==1.1.1
# homeassistant.components.qwikswitch # homeassistant.components.qwikswitch
pyqwikswitch==0.5 pyqwikswitch==0.6
# homeassistant.components.rainbird # homeassistant.components.rainbird
pyrainbird==0.1.3 pyrainbird==0.1.3