From ec8dc25c9cb3c053662ad35de1b178dcda2fecbb Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Fri, 24 Jun 2016 17:44:24 +0200 Subject: [PATCH] Zwave garagedoor (#2361) * First go at zwave Garage door * Refactor of zwave discovery * Allaround fixes for rollershutter and garage door --- homeassistant/components/garage_door/zwave.py | 70 ++++++++++ .../components/rollershutter/zwave.py | 20 +-- homeassistant/components/zwave.py | 130 +++++++++++++----- 3 files changed, 168 insertions(+), 52 deletions(-) create mode 100644 homeassistant/components/garage_door/zwave.py diff --git a/homeassistant/components/garage_door/zwave.py b/homeassistant/components/garage_door/zwave.py new file mode 100644 index 00000000000..18a2ea96b86 --- /dev/null +++ b/homeassistant/components/garage_door/zwave.py @@ -0,0 +1,70 @@ +""" +Support for Zwave garage door components. + +For more details about this platform, please refer to the documentation +https://home-assistant.io/components/garagedoor.zwave/ +""" +# Because we do not compile openzwave on CI +# pylint: disable=import-error +import logging +from homeassistant.components.garage_door import DOMAIN +from homeassistant.components.zwave import ZWaveDeviceEntity +from homeassistant.components import zwave +from homeassistant.components.garage_door import GarageDoorDevice + +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 garage door device.""" + 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_BINARY: + return + if value.type != zwave.TYPE_BOOL: + return + if value.genre != zwave.GENRE_USER: + return + + value.set_change_verified(False) + add_devices([ZwaveGarageDoor(value)]) + + +class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, GarageDoorDevice): + """Representation of an Zwave garage door device.""" + + def __init__(self, value): + """Initialize the zwave garage door.""" + from openzwave.network import ZWaveNetwork + from pydispatch import dispatcher + ZWaveDeviceEntity.__init__(self, value, DOMAIN) + self._node = value.node + self._state = value.data + 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._state = value.data + self.update_ha_state(True) + _LOGGER.debug("Value changed on network %s", value) + + @property + def is_closed(self): + """Return the current position of Zwave garage door.""" + return self._state + + def close_door(self): + """Close the garage door.""" + self._value.node.set_switch(self._value.value_id, False) + + def open_door(self): + """Open the garage door.""" + self._value.node.set_switch(self._value.value_id, True) diff --git a/homeassistant/components/rollershutter/zwave.py b/homeassistant/components/rollershutter/zwave.py index 45928d1bfb4..288488fe057 100644 --- a/homeassistant/components/rollershutter/zwave.py +++ b/homeassistant/components/rollershutter/zwave.py @@ -28,7 +28,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if value.command_class != zwave.COMMAND_CLASS_SWITCH_MULTILEVEL: return - if value.index != 1: + if value.index != 0: return value.set_change_verified(False) @@ -56,26 +56,15 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, RollershutterDevice): @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 + return self._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 + self._node.set_dimmer(self._value.value_id, 0) 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 + self._node.set_dimmer(self._value.value_id, 100) def stop(self, **kwargs): """Stop the roller shutter.""" @@ -84,3 +73,4 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, RollershutterDevice): # 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 + break diff --git a/homeassistant/components/zwave.py b/homeassistant/components/zwave.py index 36bf0163424..98a24240a00 100644 --- a/homeassistant/components/zwave.py +++ b/homeassistant/components/zwave.py @@ -39,23 +39,39 @@ SERVICE_TEST_NETWORK = "test_network" EVENT_SCENE_ACTIVATED = "zwave.scene_activated" -COMMAND_CLASS_SWITCH_MULTILEVEL = 38 -COMMAND_CLASS_DOOR_LOCK = 98 -COMMAND_CLASS_SWITCH_BINARY = 37 -COMMAND_CLASS_SENSOR_BINARY = 48 +COMMAND_CLASS_WHATEVER = None COMMAND_CLASS_SENSOR_MULTILEVEL = 49 COMMAND_CLASS_METER = 50 +COMMAND_CLASS_ALARM = 113 +COMMAND_CLASS_SWITCH_BINARY = 37 +COMMAND_CLASS_SENSOR_BINARY = 48 +COMMAND_CLASS_SWITCH_MULTILEVEL = 38 +COMMAND_CLASS_DOOR_LOCK = 98 +COMMAND_CLASS_THERMOSTAT_SETPOINT = 67 +COMMAND_CLASS_THERMOSTAT_FAN_MODE = 68 COMMAND_CLASS_BATTERY = 128 -COMMAND_CLASS_ALARM = 113 # 0x71 -COMMAND_CLASS_THERMOSTAT_SETPOINT = 67 # 0x43 -COMMAND_CLASS_THERMOSTAT_FAN_MODE = 68 # 0x44 + +GENERIC_COMMAND_CLASS_WHATEVER = None +GENERIC_COMMAND_CLASS_MULTILEVEL_SWITCH = 17 +GENERIC_COMMAND_CLASS_BINARY_SWITCH = 16 +GENERIC_COMMAND_CLASS_ENTRY_CONTROL = 64 +GENERIC_COMMAND_CLASS_BINARY_SENSOR = 32 +GENERIC_COMMAND_CLASS_MULTILEVEL_SENSOR = 33 +GENERIC_COMMAND_CLASS_METER = 49 +GENERIC_COMMAND_CLASS_ALARM_SENSOR = 161 +GENERIC_COMMAND_CLASS_THERMOSTAT = 8 SPECIFIC_DEVICE_CLASS_WHATEVER = None +SPECIFIC_DEVICE_CLASS_NOT_USED = 0 SPECIFIC_DEVICE_CLASS_MULTILEVEL_POWER_SWITCH = 1 +SPECIFIC_DEVICE_CLASS_ADVANCED_DOOR_LOCK = 2 SPECIFIC_DEVICE_CLASS_MULTIPOSITION_MOTOR = 3 +SPECIFIC_DEVICE_CLASS_SECURE_KEYPAD_DOOR_LOCK = 3 SPECIFIC_DEVICE_CLASS_MULTILEVEL_SCENE = 4 +SPECIFIC_DEVICE_CLASS_SECURE_DOOR = 5 SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_A = 5 SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_B = 6 +SPECIFIC_DEVICE_CLASS_SECURE_BARRIER_ADD_ON = 7 SPECIFIC_DEVICE_CLASS_MOTOR_CONTROL_CLASS_C = 7 GENRE_WHATEVER = None @@ -71,51 +87,67 @@ TYPE_DECIMAL = "Decimal" # value type, genre type, specific device class). DISCOVERY_COMPONENTS = [ ('sensor', + [GENERIC_COMMAND_CLASS_WHATEVER], + [SPECIFIC_DEVICE_CLASS_WHATEVER], [COMMAND_CLASS_SENSOR_MULTILEVEL, COMMAND_CLASS_METER, COMMAND_CLASS_ALARM], TYPE_WHATEVER, - GENRE_USER, - SPECIFIC_DEVICE_CLASS_WHATEVER), + GENRE_USER), ('light', + [GENERIC_COMMAND_CLASS_MULTILEVEL_SWITCH], + [SPECIFIC_DEVICE_CLASS_MULTILEVEL_POWER_SWITCH, + SPECIFIC_DEVICE_CLASS_MULTILEVEL_SCENE], [COMMAND_CLASS_SWITCH_MULTILEVEL], TYPE_BYTE, - GENRE_USER, - [SPECIFIC_DEVICE_CLASS_MULTILEVEL_POWER_SWITCH, - SPECIFIC_DEVICE_CLASS_MULTILEVEL_SCENE]), + GENRE_USER), ('switch', + [GENERIC_COMMAND_CLASS_BINARY_SWITCH], + [SPECIFIC_DEVICE_CLASS_WHATEVER], [COMMAND_CLASS_SWITCH_BINARY], TYPE_BOOL, - GENRE_USER, - SPECIFIC_DEVICE_CLASS_WHATEVER), + GENRE_USER), ('binary_sensor', + [GENERIC_COMMAND_CLASS_BINARY_SENSOR], + [SPECIFIC_DEVICE_CLASS_WHATEVER], [COMMAND_CLASS_SENSOR_BINARY], TYPE_BOOL, - GENRE_USER, - SPECIFIC_DEVICE_CLASS_WHATEVER), + GENRE_USER), ('thermostat', + [GENERIC_COMMAND_CLASS_THERMOSTAT], + [SPECIFIC_DEVICE_CLASS_WHATEVER], [COMMAND_CLASS_THERMOSTAT_SETPOINT], TYPE_WHATEVER, - GENRE_WHATEVER, - SPECIFIC_DEVICE_CLASS_WHATEVER), + GENRE_WHATEVER), ('hvac', + [GENERIC_COMMAND_CLASS_THERMOSTAT], + [SPECIFIC_DEVICE_CLASS_WHATEVER], [COMMAND_CLASS_THERMOSTAT_FAN_MODE], TYPE_WHATEVER, - GENRE_WHATEVER, - SPECIFIC_DEVICE_CLASS_WHATEVER), + GENRE_WHATEVER), ('lock', + [GENERIC_COMMAND_CLASS_ENTRY_CONTROL], + [SPECIFIC_DEVICE_CLASS_ADVANCED_DOOR_LOCK, + SPECIFIC_DEVICE_CLASS_SECURE_KEYPAD_DOOR_LOCK], [COMMAND_CLASS_DOOR_LOCK], TYPE_BOOL, - GENRE_USER, - SPECIFIC_DEVICE_CLASS_WHATEVER), + GENRE_USER), ('rollershutter', - [COMMAND_CLASS_SWITCH_MULTILEVEL], - TYPE_WHATEVER, - GENRE_USER, + [GENERIC_COMMAND_CLASS_MULTILEVEL_SWITCH], [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]), + SPECIFIC_DEVICE_CLASS_MULTIPOSITION_MOTOR], + [COMMAND_CLASS_WHATEVER], + TYPE_WHATEVER, + GENRE_USER), + ('garage_door', + [GENERIC_COMMAND_CLASS_ENTRY_CONTROL], + [SPECIFIC_DEVICE_CLASS_SECURE_BARRIER_ADD_ON, + SPECIFIC_DEVICE_CLASS_SECURE_DOOR], + [COMMAND_CLASS_SWITCH_BINARY], + TYPE_BOOL, + GENRE_USER) ] @@ -244,25 +276,49 @@ def setup(hass, config): def value_added(node, value): """Called when a value is added to a node on the network.""" for (component, - command_ids, + generic_device_class, + specific_device_class, + command_class, value_type, - value_genre, - specific_device_class) in DISCOVERY_COMPONENTS: + value_genre) in DISCOVERY_COMPONENTS: - if value.command_class not in command_ids: + _LOGGER.debug("Component=%s Node_id=%s query start", + component, node.node_id) + if node.generic not in generic_device_class and \ + None not in generic_device_class: + _LOGGER.debug("node.generic %s not None and in \ + generic_device_class %s", + node.generic, generic_device_class) continue - if value_type is not None and value_type != value.type: + if node.specific not in specific_device_class and \ + None not in specific_device_class: + _LOGGER.debug("node.specific %s is not None and in \ + specific_device_class %s", node.specific, + specific_device_class) continue - if value_genre is not None and value_genre != value.genre: + if value.command_class not in command_class and \ + None not in command_class: + _LOGGER.debug("value.command_class %s is not None \ + and in command_class %s", + value.command_class, command_class) continue - if specific_device_class is not None and \ - specific_device_class != node.specific: + if value_type != value.type and value_type is not None: + _LOGGER.debug("value.type %s != value_type %s", + value.type, value_type) + continue + if value_genre != value.genre and value_genre is not None: + _LOGGER.debug("value.genre %s != value_genre %s", + value.genre, value_genre) 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) + _LOGGER.debug("Adding Node_id=%s Generic_command_class=%s, \ + Specific_command_class=%s, \ + Command_class=%s, Value type=%s, \ + Genre=%s", node.node_id, + node.generic, node.specific, + value.command_class, value.type, + value.genre) name = "{}.{}".format(component, _object_id(value)) node_config = customize.get(name, {})