From 2e620536290d73d0a08f375e97e274ee882d5ccd Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Mon, 20 Jun 2016 07:30:57 +0200 Subject: [PATCH] Basic implementation of Zwave Rollershutters (#2313) * Basic implementation of Zwave Rollershutters * Better filtering, by @wokar * Fix typo * Remove polling from component, and loop fix * linter fix * Filter to channel devices to correct component * Remove overwriting of parent node name --- homeassistant/components/light/zwave.py | 1 - .../components/rollershutter/zwave.py | 86 +++++++++++++++++++ homeassistant/components/zwave.py | 49 +++++++++-- 3 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 homeassistant/components/rollershutter/zwave.py diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index c91a2ddd489..b4aaf5e2b4f 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -7,7 +7,6 @@ https://home-assistant.io/components/light.zwave/ # Because we do not compile openzwave on CI # pylint: disable=import-error from threading import Timer - from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN, Light from homeassistant.components import zwave from homeassistant.const import STATE_OFF, STATE_ON diff --git a/homeassistant/components/rollershutter/zwave.py b/homeassistant/components/rollershutter/zwave.py new file mode 100644 index 00000000000..45928d1bfb4 --- /dev/null +++ b/homeassistant/components/rollershutter/zwave.py @@ -0,0 +1,86 @@ +""" +Support for Zwave roller shutter components. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/rollershutter.zwave/ +""" +# Because we do not compile openzwave on CI +# pylint: disable=import-error +import logging +from homeassistant.components.rollershutter import DOMAIN +from homeassistant.components.zwave import ZWaveDeviceEntity +from homeassistant.components import zwave +from homeassistant.components.rollershutter import RollershutterDevice + +COMMAND_CLASS_SWITCH_MULTILEVEL = 0x26 # 38 +COMMAND_CLASS_SWITCH_BINARY = 0x25 # 37 + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Find and return Z-Wave roller shutters.""" + if discovery_info is None or zwave.NETWORK 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.index != 1: + return + + value.set_change_verified(False) + add_devices([ZwaveRollershutter(value)]) + + +class ZwaveRollershutter(zwave.ZWaveDeviceEntity, RollershutterDevice): + """Representation of an Zwave roller shutter.""" + + def __init__(self, value): + """Initialize the zwave rollershutter.""" + from openzwave.network import ZWaveNetwork + from pydispatch import dispatcher + ZWaveDeviceEntity.__init__(self, value, DOMAIN) + self._node = value.node + 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.node == value.node: + self.update_ha_state(True) + _LOGGER.debug("Value changed on network %s", value) + + @property + def current_position(self): + """Return the current position of Zwave roller shutter.""" + for value in self._node.get_values( + class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values(): + if value.command_class == 38 and value.index == 0: + return value.data + + def move_up(self, **kwargs): + """Move the roller shutter up.""" + for value in self._node.get_values( + class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values(): + if value.command_class == 38 and value.index == 0: + value.data = 255 + break + + def move_down(self, **kwargs): + """Move the roller shutter down.""" + for value in self._node.get_values( + class_id=COMMAND_CLASS_SWITCH_MULTILEVEL).values(): + if value.command_class == 38 and value.index == 0: + value.data = 0 + break + + def stop(self, **kwargs): + """Stop the roller shutter.""" + for value in self._node.get_values( + class_id=COMMAND_CLASS_SWITCH_BINARY).values(): + # Rollershutter will toggle between UP (True), DOWN (False). + # It also stops the shutter if the same value is sent while moving. + value.data = value.data diff --git a/homeassistant/components/zwave.py b/homeassistant/components/zwave.py index b2dd036074c..36bf0163424 100644 --- a/homeassistant/components/zwave.py +++ b/homeassistant/components/zwave.py @@ -50,6 +50,14 @@ COMMAND_CLASS_ALARM = 113 # 0x71 COMMAND_CLASS_THERMOSTAT_SETPOINT = 67 # 0x43 COMMAND_CLASS_THERMOSTAT_FAN_MODE = 68 # 0x44 +SPECIFIC_DEVICE_CLASS_WHATEVER = None +SPECIFIC_DEVICE_CLASS_MULTILEVEL_POWER_SWITCH = 1 +SPECIFIC_DEVICE_CLASS_MULTIPOSITION_MOTOR = 3 +SPECIFIC_DEVICE_CLASS_MULTILEVEL_SCENE = 4 +SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_A = 5 +SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_B = 6 +SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_C = 7 + GENRE_WHATEVER = None GENRE_USER = "User" @@ -60,38 +68,54 @@ TYPE_DECIMAL = "Decimal" # List of tuple (DOMAIN, discovered service, supported command classes, -# value type). +# value type, genre type, specific device class). DISCOVERY_COMPONENTS = [ ('sensor', [COMMAND_CLASS_SENSOR_MULTILEVEL, COMMAND_CLASS_METER, COMMAND_CLASS_ALARM], TYPE_WHATEVER, - GENRE_USER), + GENRE_USER, + SPECIFIC_DEVICE_CLASS_WHATEVER), ('light', [COMMAND_CLASS_SWITCH_MULTILEVEL], TYPE_BYTE, - GENRE_USER), + GENRE_USER, + [SPECIFIC_DEVICE_CLASS_MULTILEVEL_POWER_SWITCH, + SPECIFIC_DEVICE_CLASS_MULTILEVEL_SCENE]), ('switch', [COMMAND_CLASS_SWITCH_BINARY], TYPE_BOOL, - GENRE_USER), + GENRE_USER, + SPECIFIC_DEVICE_CLASS_WHATEVER), ('binary_sensor', [COMMAND_CLASS_SENSOR_BINARY], TYPE_BOOL, - GENRE_USER), + GENRE_USER, + SPECIFIC_DEVICE_CLASS_WHATEVER), ('thermostat', [COMMAND_CLASS_THERMOSTAT_SETPOINT], TYPE_WHATEVER, - GENRE_WHATEVER), + GENRE_WHATEVER, + SPECIFIC_DEVICE_CLASS_WHATEVER), ('hvac', [COMMAND_CLASS_THERMOSTAT_FAN_MODE], TYPE_WHATEVER, - GENRE_WHATEVER), + GENRE_WHATEVER, + SPECIFIC_DEVICE_CLASS_WHATEVER), ('lock', [COMMAND_CLASS_DOOR_LOCK], TYPE_BOOL, - GENRE_USER), + GENRE_USER, + SPECIFIC_DEVICE_CLASS_WHATEVER), + ('rollershutter', + [COMMAND_CLASS_SWITCH_MULTILEVEL], + TYPE_WHATEVER, + GENRE_USER, + [SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_A, + SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_B, + SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_C, + SPECIFIC_DEVICE_CLASS_MULTIPOSITION_MOTOR]), ] @@ -222,7 +246,8 @@ def setup(hass, config): for (component, command_ids, value_type, - value_genre) in DISCOVERY_COMPONENTS: + value_genre, + specific_device_class) in DISCOVERY_COMPONENTS: if value.command_class not in command_ids: continue @@ -230,8 +255,14 @@ def setup(hass, config): continue if value_genre is not None and value_genre != value.genre: continue + if specific_device_class is not None and \ + specific_device_class != node.specific: + continue # Configure node + _LOGGER.debug("Node_id=%s Value type=%s Genre=%s \ + Specific Device_class=%s", node.node_id, + value.type, value.genre, specific_device_class) name = "{}.{}".format(component, _object_id(value)) node_config = customize.get(name, {})