From 30841ef4da692cb5d12b26fe08271fdf87c8c992 Mon Sep 17 00:00:00 2001 From: Michael Dubno Date: Fri, 21 Dec 2018 18:11:00 -0500 Subject: [PATCH] Add Lutron Homeworks component (#18311) * Added Lutron Homeworks components. * Made all requested changes other than config. * Removed commented out code. * Removed binary_sensor. Implemented new signal/events for button presses. Cleaned up some data passing. * Fixed minor formatting. * Got rid of unused config stuff. Reordered imports. * Missed removing vol, and forgot an extra line. * More requested changes Removed HomeworksController, it wasn't needed. * Removed stale code. * Imperative doc change. --- .coveragerc | 3 + homeassistant/components/homeworks.py | 146 ++++++++++++++++++++ homeassistant/components/light/homeworks.py | 104 ++++++++++++++ requirements_all.txt | 3 + 4 files changed, 256 insertions(+) create mode 100644 homeassistant/components/homeworks.py create mode 100644 homeassistant/components/light/homeworks.py diff --git a/.coveragerc b/.coveragerc index ef8a4af5209..7ed4746d1fc 100644 --- a/.coveragerc +++ b/.coveragerc @@ -164,6 +164,9 @@ omit = homeassistant/components/homematicip_cloud.py homeassistant/components/*/homematicip_cloud.py + homeassistant/components/homeworks.py + homeassistant/components/*/homeworks.py + homeassistant/components/huawei_lte.py homeassistant/components/*/huawei_lte.py diff --git a/homeassistant/components/homeworks.py b/homeassistant/components/homeworks.py new file mode 100644 index 00000000000..b0510cfe9b5 --- /dev/null +++ b/homeassistant/components/homeworks.py @@ -0,0 +1,146 @@ +"""Component for interfacing to Lutron Homeworks Series 4 and 8 systems. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/homeworks/ +""" +import logging + +import voluptuous as vol + +from homeassistant.core import callback +from homeassistant.const import ( + CONF_HOST, CONF_ID, CONF_NAME, CONF_PORT, EVENT_HOMEASSISTANT_STOP) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import load_platform +from homeassistant.helpers.dispatcher import ( + dispatcher_send, async_dispatcher_connect) +from homeassistant.util import slugify + +REQUIREMENTS = ['pyhomeworks==0.0.6'] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'homeworks' + +HOMEWORKS_CONTROLLER = 'homeworks' +ENTITY_SIGNAL = 'homeworks_entity_{}' +EVENT_BUTTON_PRESS = 'homeworks_button_press' +EVENT_BUTTON_RELEASE = 'homeworks_button_release' + +CONF_DIMMERS = 'dimmers' +CONF_KEYPADS = 'keypads' +CONF_ADDR = 'addr' +CONF_RATE = 'rate' + +FADE_RATE = 1. + +CV_FADE_RATE = vol.All(vol.Coerce(float), vol.Range(min=0, max=20)) + +DIMMER_SCHEMA = vol.Schema({ + vol.Required(CONF_ADDR): cv.string, + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_RATE, default=FADE_RATE): CV_FADE_RATE +}) + +KEYPAD_SCHEMA = vol.Schema({ + vol.Required(CONF_ADDR): cv.string, + vol.Required(CONF_NAME): cv.string, +}) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT): cv.port, + vol.Required(CONF_DIMMERS): vol.All(cv.ensure_list, [DIMMER_SCHEMA]), + vol.Optional(CONF_KEYPADS, default=[]): vol.All(cv.ensure_list, + [KEYPAD_SCHEMA]), + }), +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, base_config): + """Start Homeworks controller.""" + from pyhomeworks.pyhomeworks import Homeworks + + def hw_callback(msg_type, values): + """Dispatch state changes.""" + _LOGGER.debug('callback: %s, %s', msg_type, values) + addr = values[0] + signal = ENTITY_SIGNAL.format(addr) + dispatcher_send(hass, signal, msg_type, values) + + config = base_config.get(DOMAIN) + controller = Homeworks(config[CONF_HOST], config[CONF_PORT], hw_callback) + hass.data[HOMEWORKS_CONTROLLER] = controller + + def cleanup(event): + controller.close() + + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, cleanup) + + dimmers = config[CONF_DIMMERS] + load_platform(hass, 'light', DOMAIN, {CONF_DIMMERS: dimmers}, base_config) + + for key_config in config[CONF_KEYPADS]: + addr = key_config[CONF_ADDR] + name = key_config[CONF_NAME] + HomeworksKeypadEvent(hass, addr, name) + + return True + + +class HomeworksDevice(): + """Base class of a Homeworks device.""" + + def __init__(self, controller, addr, name): + """Controller, address, and name of the device.""" + self._addr = addr + self._name = name + self._controller = controller + + @property + def unique_id(self): + """Return a unique identifier.""" + return 'homeworks.{}'.format(self._addr) + + @property + def name(self): + """Device name.""" + return self._name + + @property + def should_poll(self): + """No need to poll.""" + return False + + +class HomeworksKeypadEvent: + """When you want signals instead of entities. + + Stateless sensors such as keypads are expected to generate an event + instead of a sensor entity in hass. + """ + + def __init__(self, hass, addr, name): + """Register callback that will be used for signals.""" + self._hass = hass + self._addr = addr + self._name = name + self._id = slugify(self._name) + signal = ENTITY_SIGNAL.format(self._addr) + async_dispatcher_connect( + self._hass, signal, self._update_callback) + + @callback + def _update_callback(self, msg_type, values): + """Fire events if button is pressed or released.""" + from pyhomeworks.pyhomeworks import ( + HW_BUTTON_PRESSED, HW_BUTTON_RELEASED) + if msg_type == HW_BUTTON_PRESSED: + event = EVENT_BUTTON_PRESS + elif msg_type == HW_BUTTON_RELEASED: + event = EVENT_BUTTON_RELEASE + else: + return + data = {CONF_ID: self._id, CONF_NAME: self._name, 'button': values[1]} + self._hass.bus.async_fire(event, data) diff --git a/homeassistant/components/light/homeworks.py b/homeassistant/components/light/homeworks.py new file mode 100644 index 00000000000..3ba5f805c52 --- /dev/null +++ b/homeassistant/components/light/homeworks.py @@ -0,0 +1,104 @@ +"""Component for interfacing to Lutron Homeworks lights. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/light.homeworks/ +""" +import logging + +from homeassistant.components.homeworks import ( + HomeworksDevice, HOMEWORKS_CONTROLLER, ENTITY_SIGNAL, + CONF_DIMMERS, CONF_ADDR, CONF_RATE) +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) +from homeassistant.const import CONF_NAME +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect) + +DEPENDENCIES = ['homeworks'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_entities, discover_info=None): + """Set up Homeworks lights.""" + if discover_info is None: + return + + controller = hass.data[HOMEWORKS_CONTROLLER] + devs = [] + for dimmer in discover_info[CONF_DIMMERS]: + dev = HomeworksLight(controller, dimmer[CONF_ADDR], + dimmer[CONF_NAME], dimmer[CONF_RATE]) + devs.append(dev) + add_entities(devs, True) + + +class HomeworksLight(HomeworksDevice, Light): + """Homeworks Light.""" + + def __init__(self, controller, addr, name, rate): + """Create device with Addr, name, and rate.""" + super().__init__(controller, addr, name) + self._rate = rate + self._level = 0 + self._prev_level = 0 + + async def async_added_to_hass(self): + """Call when entity is added to hass.""" + signal = ENTITY_SIGNAL.format(self._addr) + _LOGGER.debug('connecting %s', signal) + async_dispatcher_connect( + self.hass, signal, self._update_callback) + self._controller.request_dimmer_level(self._addr) + + @property + def supported_features(self): + """Supported features.""" + return SUPPORT_BRIGHTNESS + + def turn_on(self, **kwargs): + """Turn on the light.""" + if ATTR_BRIGHTNESS in kwargs: + new_level = kwargs[ATTR_BRIGHTNESS] + elif self._prev_level == 0: + new_level = 255 + else: + new_level = self._prev_level + self._set_brightness(new_level) + + def turn_off(self, **kwargs): + """Turn off the light.""" + self._set_brightness(0) + + @property + def brightness(self): + """Control the brightness.""" + return self._level + + def _set_brightness(self, level): + """Send the brightness level to the device.""" + self._controller.fade_dim( + float((level*100.)/255.), self._rate, + 0, self._addr) + + @property + def device_state_attributes(self): + """Supported attributes.""" + return {'homeworks_address': self._addr} + + @property + def is_on(self): + """Is the light on/off.""" + return self._level != 0 + + @callback + def _update_callback(self, msg_type, values): + """Process device specific messages.""" + from pyhomeworks.pyhomeworks import HW_LIGHT_CHANGED + + if msg_type == HW_LIGHT_CHANGED: + self._level = int((values[1] * 255.)/100.) + if self._level != 0: + self._prev_level = self._level + self.async_schedule_update_ha_state() diff --git a/requirements_all.txt b/requirements_all.txt index d1c3a16659c..ee9fa80f608 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1009,6 +1009,9 @@ pyhiveapi==0.2.14 # homeassistant.components.homematic pyhomematic==0.1.53 +# homeassistant.components.homeworks +pyhomeworks==0.0.6 + # homeassistant.components.sensor.hydroquebec pyhydroquebec==2.2.2