From ba13951fff42ee0bf4ad744d174bf751291a0ea4 Mon Sep 17 00:00:00 2001 From: Jon Caruana <jon@joncaruana.com> Date: Tue, 1 Nov 2016 20:44:25 -0700 Subject: [PATCH] Add LiteJet (a lighting control system) component (#4125) * Initial submission of LiteJet integration. * Add LiteJet switch pressed automation trigger. (State changes are too slow to catch a press-release.) Add LiteJet scene, replacing commented out code that treated these as lights. Include LiteJet numbers in the device state so that it is easy to lookup entity -> number. * Fix missing global. * Allow light's brightness to be set explicitly. * Support optional 'ignore' key to ignore prefixes of loads, switches, and scenes that weren't configured for use in the LiteJet system. * Fix lint errors and warnings. * Cleanup header comments. Default to not creating LiteJet switches as these are generally not useful. * Lint fixes. * Fixes from pull request feedback. * Use hass.data instead of globals for data storage. * Fix lint warnings. --- .coveragerc | 3 + .../components/automation/litejet.py | 41 ++++++++ homeassistant/components/light/litejet.py | 94 +++++++++++++++++++ homeassistant/components/litejet.py | 53 +++++++++++ homeassistant/components/scene/litejet.py | 58 ++++++++++++ homeassistant/components/switch/litejet.py | 84 +++++++++++++++++ requirements_all.txt | 3 + 7 files changed, 336 insertions(+) create mode 100644 homeassistant/components/automation/litejet.py create mode 100644 homeassistant/components/light/litejet.py create mode 100644 homeassistant/components/litejet.py create mode 100644 homeassistant/components/scene/litejet.py create mode 100644 homeassistant/components/switch/litejet.py diff --git a/.coveragerc b/.coveragerc index b4c34555e16..3b821a8eeaf 100644 --- a/.coveragerc +++ b/.coveragerc @@ -37,6 +37,9 @@ omit = homeassistant/components/isy994.py homeassistant/components/*/isy994.py + homeassistant/components/litejet.py + homeassistant/components/*/litejet.py + homeassistant/components/modbus.py homeassistant/components/*/modbus.py diff --git a/homeassistant/components/automation/litejet.py b/homeassistant/components/automation/litejet.py new file mode 100644 index 00000000000..875a24540ee --- /dev/null +++ b/homeassistant/components/automation/litejet.py @@ -0,0 +1,41 @@ +""" +Trigger an automation when a LiteJet switch is released. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/automation.litejet/ +""" +import logging + +import voluptuous as vol + +from homeassistant.core import callback +from homeassistant.const import CONF_PLATFORM +import homeassistant.helpers.config_validation as cv + +DEPENDENCIES = ['litejet'] + +_LOGGER = logging.getLogger(__name__) + +CONF_NUMBER = 'number' + +TRIGGER_SCHEMA = vol.Schema({ + vol.Required(CONF_PLATFORM): 'litejet', + vol.Required(CONF_NUMBER): cv.positive_int +}) + + +def async_trigger(hass, config, action): + """Listen for events based on configuration.""" + number = config.get(CONF_NUMBER) + + @callback + def call_action(): + """Call action with right context.""" + hass.async_run_job(action, { + 'trigger': { + CONF_PLATFORM: 'litejet', + CONF_NUMBER: number + }, + }) + + hass.data['litejet_system'].on_switch_released(number, call_action) diff --git a/homeassistant/components/light/litejet.py b/homeassistant/components/light/litejet.py new file mode 100644 index 00000000000..c278cdc1332 --- /dev/null +++ b/homeassistant/components/light/litejet.py @@ -0,0 +1,94 @@ +""" +Support for LiteJet lights. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.litejet/ +""" + +import logging + +import homeassistant.components.litejet as litejet +from homeassistant.components.light import ATTR_BRIGHTNESS, Light + +DEPENDENCIES = ['litejet'] + +ATTR_NUMBER = 'number' + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup lights for the LiteJet platform.""" + litejet_ = hass.data['litejet_system'] + + devices = [] + for i in litejet_.loads(): + name = litejet_.get_load_name(i) + if not litejet.is_ignored(hass, name): + devices.append(LiteJetLight(hass, litejet_, i, name)) + add_devices(devices) + + +class LiteJetLight(Light): + """Represents a single LiteJet light.""" + + def __init__(self, hass, lj, i, name): + """Initialize a LiteJet light.""" + self._hass = hass + self._lj = lj + self._index = i + self._brightness = 0 + self._name = name + + lj.on_load_activated(i, self._on_load_changed) + lj.on_load_deactivated(i, self._on_load_changed) + + self.update() + + def _on_load_changed(self): + """Called on a LiteJet thread when a load's state changes.""" + _LOGGER.debug("Updating due to notification for %s", self._name) + self._hass.loop.create_task(self.async_update_ha_state(True)) + + @property + def name(self): + """The light's name.""" + return self._name + + @property + def brightness(self): + """Return the light's brightness.""" + return self._brightness + + @property + def is_on(self): + """Return if the light is on.""" + return self._brightness != 0 + + @property + def should_poll(self): + """Return that lights do not require polling.""" + return False + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + return { + ATTR_NUMBER: self._index + } + + def turn_on(self, **kwargs): + """Turn on the light.""" + if ATTR_BRIGHTNESS in kwargs: + brightness = int(kwargs[ATTR_BRIGHTNESS] / 255 * 99) + self._lj.activate_load_at(self._index, brightness, 0) + else: + self._lj.activate_load(self._index) + + def turn_off(self, **kwargs): + """Turn off the light.""" + self._lj.deactivate_load(self._index) + + def update(self): + """Retrieve the light's brightness from the LiteJet system.""" + self._brightness = self._lj.get_load_level(self._index) / 99 * 255 diff --git a/homeassistant/components/litejet.py b/homeassistant/components/litejet.py new file mode 100644 index 00000000000..70c3755144b --- /dev/null +++ b/homeassistant/components/litejet.py @@ -0,0 +1,53 @@ +"""Allows the LiteJet lighting system to be controlled by Home Assistant. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/litejet/ +""" +import logging +import voluptuous as vol + +from homeassistant.helpers import discovery +from homeassistant.const import CONF_URL +import homeassistant.helpers.config_validation as cv + +DOMAIN = 'litejet' + +REQUIREMENTS = ['pylitejet==0.1'] + +CONF_EXCLUDE_NAMES = 'exclude_names' +CONF_INCLUDE_SWITCHES = 'include_switches' + +_LOGGER = logging.getLogger(__name__) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_URL): cv.string, + vol.Optional(CONF_EXCLUDE_NAMES): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_INCLUDE_SWITCHES, default=False): cv.boolean + }) +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Initialize the LiteJet component.""" + from pylitejet import LiteJet + + url = config[DOMAIN].get(CONF_URL) + + hass.data['litejet_system'] = LiteJet(url) + hass.data['litejet_config'] = config[DOMAIN] + + discovery.load_platform(hass, 'light', DOMAIN, {}, config) + if config[DOMAIN].get(CONF_INCLUDE_SWITCHES): + discovery.load_platform(hass, 'switch', DOMAIN, {}, config) + discovery.load_platform(hass, 'scene', DOMAIN, {}, config) + + return True + + +def is_ignored(hass, name): + """Determine if a load, switch, or scene should be ignored.""" + for prefix in hass.data['litejet_config'].get(CONF_EXCLUDE_NAMES, []): + if name.startswith(prefix): + return True + return False diff --git a/homeassistant/components/scene/litejet.py b/homeassistant/components/scene/litejet.py new file mode 100644 index 00000000000..6e08ebfbee9 --- /dev/null +++ b/homeassistant/components/scene/litejet.py @@ -0,0 +1,58 @@ +""" +Support for LiteJet scenes. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/scene.litejet/ +""" +import logging +import homeassistant.components.litejet as litejet +from homeassistant.components.scene import Scene + +DEPENDENCIES = ['litejet'] + +ATTR_NUMBER = 'number' + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup scenes for the LiteJet platform.""" + litejet_ = hass.data['litejet_system'] + + devices = [] + for i in litejet_.scenes(): + name = litejet_.get_scene_name(i) + if not litejet.is_ignored(hass, name): + devices.append(LiteJetScene(litejet_, i, name)) + add_devices(devices) + + +class LiteJetScene(Scene): + """Represents a single LiteJet scene.""" + + def __init__(self, lj, i, name): + """Initialize the scene.""" + self._lj = lj + self._index = i + self._name = name + + @property + def name(self): + """Return the name of the scene.""" + return self._name + + @property + def should_poll(self): + """Return that polling is not necessary.""" + return False + + @property + def device_state_attributes(self): + """Return the device-specific state attributes.""" + return { + ATTR_NUMBER: self._index + } + + def activate(self, **kwargs): + """Activate the scene.""" + self._lj.activate_scene(self._index) diff --git a/homeassistant/components/switch/litejet.py b/homeassistant/components/switch/litejet.py new file mode 100644 index 00000000000..d058d648540 --- /dev/null +++ b/homeassistant/components/switch/litejet.py @@ -0,0 +1,84 @@ +""" +Support for LiteJet switch. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.litejet/ +""" +import logging +import homeassistant.components.litejet as litejet +from homeassistant.components.switch import SwitchDevice + +DEPENDENCIES = ['litejet'] + +ATTR_NUMBER = 'number' + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the LiteJet switch platform.""" + litejet_ = hass.data['litejet_system'] + + devices = [] + for i in litejet_.button_switches(): + name = litejet_.get_switch_name(i) + if not litejet.is_ignored(hass, name): + devices.append(LiteJetSwitch(hass, litejet_, i, name)) + add_devices(devices) + + +class LiteJetSwitch(SwitchDevice): + """Represents a single LiteJet switch.""" + + def __init__(self, hass, lj, i, name): + """Initialize a LiteJet switch.""" + self._hass = hass + self._lj = lj + self._index = i + self._state = False + self._name = name + + lj.on_switch_pressed(i, self._on_switch_pressed) + lj.on_switch_released(i, self._on_switch_released) + + self.update() + + def _on_switch_pressed(self): + _LOGGER.debug("Updating pressed for %s", self._name) + self._state = True + self._hass.loop.create_task(self.async_update_ha_state()) + + def _on_switch_released(self): + _LOGGER.debug("Updating released for %s", self._name) + self._state = False + self._hass.loop.create_task(self.async_update_ha_state()) + + @property + def name(self): + """Return the name of the switch.""" + return self._name + + @property + def is_on(self): + """Return if the switch is pressed.""" + return self._state + + @property + def should_poll(self): + """Return that polling is not necessary.""" + return False + + @property + def device_state_attributes(self): + """Return the device-specific state attributes.""" + return { + ATTR_NUMBER: self._index + } + + def turn_on(self, **kwargs): + """Press the switch.""" + self._lj.press_switch(self._index) + + def turn_off(self, **kwargs): + """Release the switch.""" + self._lj.release_switch(self._index) diff --git a/requirements_all.txt b/requirements_all.txt index 04317134ad4..d492c41c59b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -367,6 +367,9 @@ pyicloud==0.9.1 # homeassistant.components.sensor.lastfm pylast==1.6.0 +# homeassistant.components.litejet +pylitejet==0.1 + # homeassistant.components.sensor.loopenergy pyloopenergy==0.0.15 -- GitLab