From 5b4fc4f346b94ca95ed376958c008aa31dbaaec3 Mon Sep 17 00:00:00 2001 From: Arthur Andersen Date: Sat, 7 Nov 2015 15:57:28 +0100 Subject: [PATCH 1/4] [Zwave] Add check for missing `discovery_service` --- homeassistant/components/sensor/zwave.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py index 0cfc0682454..9a7a867d196 100644 --- a/homeassistant/components/sensor/zwave.py +++ b/homeassistant/components/sensor/zwave.py @@ -31,6 +31,16 @@ DEVICE_MAPPINGS = { def setup_platform(hass, config, add_devices, discovery_info=None): """ Sets up Z-Wave sensors. """ + + # Return on empty `discovery_info`. Given you configure HA with: + # + # sensor: + # platform: zwave + # + # `setup_platform` will be called without `discovery_info`. + if discovery_info is None: + return + node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]] value = node.values[discovery_info[zwave.ATTR_VALUE_ID]] From 5565e418f8fa53668d99a4f8788d345fdfaafdca Mon Sep 17 00:00:00 2001 From: Arthur Andersen Date: Sat, 7 Nov 2015 15:53:29 +0100 Subject: [PATCH 2/4] [Zwave] Add type and genre to value filter --- homeassistant/components/zwave.py | 53 ++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/zwave.py b/homeassistant/components/zwave.py index 9f7df64312d..ee78f532eb2 100644 --- a/homeassistant/components/zwave.py +++ b/homeassistant/components/zwave.py @@ -27,10 +27,21 @@ COMMAND_CLASS_SENSOR_BINARY = 48 COMMAND_CLASS_SENSOR_MULTILEVEL = 49 COMMAND_CLASS_BATTERY = 128 -# list of tuple (DOMAIN, discovered service, supported command classes) +GENRE_WHATEVER = None +GENRE_USER = "User" + +TYPE_WHATEVER = None +TYPE_BYTE = "Byte" +TYPE_BOOL = "Bool" + +# list of tuple (DOMAIN, discovered service, supported command +# classes, value type) DISCOVERY_COMPONENTS = [ - ('sensor', DISCOVER_SENSORS, - [COMMAND_CLASS_SENSOR_BINARY, COMMAND_CLASS_SENSOR_MULTILEVEL]), + ('sensor', + DISCOVER_SENSORS, + [COMMAND_CLASS_SENSOR_BINARY, COMMAND_CLASS_SENSOR_MULTILEVEL], + TYPE_WHATEVER, + GENRE_WHATEVER), ] ATTR_NODE_ID = "node_id" @@ -110,19 +121,31 @@ def setup(hass, config): def value_added(node, value): """ Called when a value is added to a node on the network. """ - for component, discovery_service, command_ids in DISCOVERY_COMPONENTS: - if value.command_class in command_ids: - # Ensure component is loaded - bootstrap.setup_component(hass, component, config) - # Fire discovery event - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { - ATTR_SERVICE: discovery_service, - ATTR_DISCOVERED: { - ATTR_NODE_ID: node.node_id, - ATTR_VALUE_ID: value.value_id, - } - }) + for (component, + discovery_service, + command_ids, + value_type, + value_genre) in DISCOVERY_COMPONENTS: + + if value.command_class not in command_ids: + continue + if value_type is not None and value_type != value.type: + continue + if value_genre is not None and value_genre != value.genre: + continue + + # Ensure component is loaded + bootstrap.setup_component(hass, component, config) + + # Fire discovery event + hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { + ATTR_SERVICE: discovery_service, + ATTR_DISCOVERED: { + ATTR_NODE_ID: node.node_id, + ATTR_VALUE_ID: value.value_id, + } + }) dispatcher.connect( value_added, ZWaveNetwork.SIGNAL_VALUE_ADDED, weak=False) From 84f81480bbbc6bed5d98d07fd56b5bb3085af817 Mon Sep 17 00:00:00 2001 From: Arthur Andersen Date: Sat, 7 Nov 2015 15:56:28 +0100 Subject: [PATCH 3/4] [Zwave] Add light zwave component --- homeassistant/components/light/__init__.py | 3 +- homeassistant/components/light/zwave.py | 132 +++++++++++++++++++++ homeassistant/components/zwave.py | 7 ++ 3 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 homeassistant/components/light/zwave.py diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index ab39474c093..9a3b742ba0b 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -55,7 +55,7 @@ import logging import os import csv -from homeassistant.components import group, discovery, wink, isy994 +from homeassistant.components import group, discovery, wink, isy994, zwave from homeassistant.config import load_yaml_config_file from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) @@ -105,6 +105,7 @@ DISCOVERY_PLATFORMS = { wink.DISCOVER_LIGHTS: 'wink', isy994.DISCOVER_LIGHTS: 'isy994', discovery.SERVICE_HUE: 'hue', + zwave.DISCOVER_LIGHTS: 'zwave', } PROP_TO_ATTR = { diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py new file mode 100644 index 00000000000..3091817103b --- /dev/null +++ b/homeassistant/components/light/zwave.py @@ -0,0 +1,132 @@ +""" +homeassistant.components.light.zwave +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +""" +# pylint: disable=import-error +from openzwave.network import ZWaveNetwork +from pydispatch import dispatcher + +import homeassistant.components.zwave as zwave + +from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.components.light import (Light, ATTR_BRIGHTNESS) +from time import sleep + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Find and add zwave lights. """ + if discovery_info is None: + return + + node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]] + value = node.values[discovery_info[zwave.ATTR_VALUE_ID]] + + if value.command_class != zwave.COMMAND_CLASS_SWITCH_MULTILEVEL: + return + if value.type != zwave.TYPE_BYTE: + return + if value.genre != zwave.GENRE_USER: + return + + value.set_change_verified(False) + add_devices([ZwaveDimmer(value)]) + + +def brightness_state(value): + """ Returns the brightness and state according to the current + data of given value. """ + if value.data > 0: + return (value.data / 99) * 255, STATE_ON + else: + return 255, STATE_OFF + + +class ZwaveDimmer(Light): + """ Provides a zwave dimmer. """ + # pylint: disable=too-many-arguments + def __init__(self, value): + self._value = value + self._node = value.node + + self._brightness, self._state = brightness_state(value) + + # Used for value change event handling + self._refreshing = False + self._expect = None + + dispatcher.connect( + self._value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED) + + def _value_changed(self, value): + """ Called when a value has changed on the network. """ + if self._value.value_id == value.value_id: + # leoc: Since my multilevel switches dim slowly between + # brightness levels / states, the value_change event does + # not return the new end state, but rather the state the + # the switch was at, before changing. Thus we have to wait + # 2 seconds until the change is done... + if self._refreshing: + self._refreshing = False + brightness, state = brightness_state(value) + print("Refresh: ", brightness, ", ", state, ", ", self._expect) + if self._expect is None or self._expect == state: + print("Is expected!") + self._brightness, self._state = brightness, state + self._expect = None + self.update_ha_state() + else: + print("Not expected!") + else: + self._refreshing = True + print("Value change: sleeping") + sleep(2) + value.refresh() + + @property + def should_poll(self): + """ No polling needed for a light. """ + return False + + @property + def name(self): + """ Returns the name of the device if any. """ + name = self._node.name or "{}".format(self._node.product_name) + + return "{}".format(name or self._value.label) + + @property + def brightness(self): + """ Brightness of this light between 0..255. """ + return self._brightness + + @property + def is_on(self): + """ True if device is on. """ + return self._state == STATE_ON + + def turn_on(self, **kwargs): + """ Turn the device on. """ + + if ATTR_BRIGHTNESS in kwargs: + self._brightness = kwargs[ATTR_BRIGHTNESS] + + # Zwave multilevel switches use a range of [0, 99] to control + # brightness ... + brightness = (self._brightness / 255) * 99 + + print("Turn on", self._brightness, brightness) + + self._expect = STATE_ON + if self._node.set_dimmer(self._value.value_id, brightness): + self._state = STATE_ON + self.update_ha_state() + + def turn_off(self, **kwargs): + """ Turn the device off. """ + print("Turn off") + + self._expect = STATE_OFF + if self._node.set_dimmer(self._value.value_id, 0): + self._state = STATE_OFF + self.update_ha_state() diff --git a/homeassistant/components/zwave.py b/homeassistant/components/zwave.py index ee78f532eb2..0885defb13d 100644 --- a/homeassistant/components/zwave.py +++ b/homeassistant/components/zwave.py @@ -22,7 +22,9 @@ DEFAULT_CONF_USB_STICK_PATH = "/zwaveusbstick" CONF_DEBUG = "debug" DISCOVER_SENSORS = "zwave.sensors" +DISCOVER_LIGHTS = "zwave.light" +COMMAND_CLASS_SWITCH_MULTILEVEL = 38 COMMAND_CLASS_SENSOR_BINARY = 48 COMMAND_CLASS_SENSOR_MULTILEVEL = 49 COMMAND_CLASS_BATTERY = 128 @@ -42,6 +44,11 @@ DISCOVERY_COMPONENTS = [ [COMMAND_CLASS_SENSOR_BINARY, COMMAND_CLASS_SENSOR_MULTILEVEL], TYPE_WHATEVER, GENRE_WHATEVER), + ('light', + DISCOVER_LIGHTS, + [COMMAND_CLASS_SWITCH_MULTILEVEL], + TYPE_BYTE, + GENRE_USER), ] ATTR_NODE_ID = "node_id" From 665436cd910035b25805fa62b63f357c6eeef37e Mon Sep 17 00:00:00 2001 From: Arthur Andersen Date: Tue, 10 Nov 2015 19:55:40 +0100 Subject: [PATCH 4/4] [Zwave] Use threading.Timer for value refresh delay --- homeassistant/components/light/zwave.py | 53 ++++++++++--------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index 3091817103b..10e75125e12 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -11,7 +11,7 @@ import homeassistant.components.zwave as zwave from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.components.light import (Light, ATTR_BRIGHTNESS) -from time import sleep +from threading import Timer def setup_platform(hass, config, add_devices, discovery_info=None): @@ -53,35 +53,32 @@ class ZwaveDimmer(Light): # Used for value change event handling self._refreshing = False - self._expect = None + self._timer = None dispatcher.connect( self._value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED) def _value_changed(self, value): """ Called when a value has changed on the network. """ - if self._value.value_id == value.value_id: - # leoc: Since my multilevel switches dim slowly between - # brightness levels / states, the value_change event does - # not return the new end state, but rather the state the - # the switch was at, before changing. Thus we have to wait - # 2 seconds until the change is done... - if self._refreshing: - self._refreshing = False - brightness, state = brightness_state(value) - print("Refresh: ", brightness, ", ", state, ", ", self._expect) - if self._expect is None or self._expect == state: - print("Is expected!") - self._brightness, self._state = brightness, state - self._expect = None - self.update_ha_state() - else: - print("Not expected!") - else: + if self._value.value_id != value.value_id: + return + + if self._refreshing: + self._refreshing = False + self._brightness, self._state = brightness_state(value) + else: + def _refresh_value(): + """Used timer callback for delayed value refresh.""" self._refreshing = True - print("Value change: sleeping") - sleep(2) - value.refresh() + self._value.refresh() + + if self._timer is not None and self._timer.isAlive(): + self._timer.cancel() + + self._timer = Timer(2, _refresh_value) + self._timer.start() + + self.update_ha_state() @property def should_poll(self): @@ -112,21 +109,13 @@ class ZwaveDimmer(Light): self._brightness = kwargs[ATTR_BRIGHTNESS] # Zwave multilevel switches use a range of [0, 99] to control - # brightness ... + # brightness. brightness = (self._brightness / 255) * 99 - print("Turn on", self._brightness, brightness) - - self._expect = STATE_ON if self._node.set_dimmer(self._value.value_id, brightness): self._state = STATE_ON - self.update_ha_state() def turn_off(self, **kwargs): """ Turn the device off. """ - print("Turn off") - - self._expect = STATE_OFF if self._node.set_dimmer(self._value.value_id, 0): self._state = STATE_OFF - self.update_ha_state()