diff --git a/homeassistant/components/light/zwave.py b/homeassistant/components/light/zwave.py index a2ba8e7c2a0..45660474fde 100644 --- a/homeassistant/components/light/zwave.py +++ b/homeassistant/components/light/zwave.py @@ -10,8 +10,8 @@ import logging # pylint: disable=import-error from threading import Timer from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \ - ATTR_RGB_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, \ - SUPPORT_RGB_COLOR, DOMAIN, Light + ATTR_RGB_COLOR, ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, \ + SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, DOMAIN, Light from homeassistant.components import zwave from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import from homeassistant.const import STATE_OFF, STATE_ON @@ -43,11 +43,6 @@ TEMP_MID_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 2 + HASS_COLOR_MIN TEMP_WARM_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 3 * 2 + HASS_COLOR_MIN TEMP_COLD_HASS = (HASS_COLOR_MAX - HASS_COLOR_MIN) / 3 + HASS_COLOR_MIN -SUPPORT_ZWAVE_DIMMER = SUPPORT_BRIGHTNESS -SUPPORT_ZWAVE_COLOR = SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR -SUPPORT_ZWAVE_COLORTEMP = (SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR - | SUPPORT_COLOR_TEMP) - def get_device(node, values, node_config, **kwargs): """Create zwave entity device.""" @@ -72,6 +67,13 @@ def brightness_state(value): return 0, STATE_OFF +def ct_to_rgb(temp): + """Convert color temperature (mireds) to RGB.""" + colorlist = list( + color_temperature_to_rgb(color_temperature_mired_to_kelvin(temp))) + return [int(val) for val in colorlist] + + class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): """Representation of a Z-Wave dimmer.""" @@ -80,6 +82,7 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN) self._brightness = None self._state = None + self._supported_features = None self._delay = delay self._refresh_value = refresh self._zw098 = None @@ -100,6 +103,7 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): self._timer = None _LOGGER.debug('self._refreshing=%s self.delay=%s', self._refresh_value, self._delay) + self.value_added() self.update_properties() def update_properties(self): @@ -107,6 +111,12 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): # Brightness self._brightness, self._state = brightness_state(self.values.primary) + def value_added(self): + """Called when a new value is added to this entity.""" + self._supported_features = SUPPORT_BRIGHTNESS + if self.values.dimming_duration is not None: + self._supported_features |= SUPPORT_TRANSITION + def value_changed(self): """Called when a value for this entity's node has changed.""" if self._refresh_value: @@ -139,10 +149,43 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): @property def supported_features(self): """Flag supported features.""" - return SUPPORT_ZWAVE_DIMMER + return self._supported_features + + def _set_duration(self, **kwargs): + """Set the transition time for the brightness value. + + Zwave Dimming Duration values: + 0x00 = instant + 0x01-0x7F = 1 second to 127 seconds + 0x80-0xFE = 1 minute to 127 minutes + 0xFF = factory default + """ + if self.values.dimming_duration is None: + if ATTR_TRANSITION in kwargs: + _LOGGER.debug("Dimming not supported by %s.", self.entity_id) + return + + if ATTR_TRANSITION not in kwargs: + self.values.dimming_duration.data = 0xFF + return + + transition = kwargs[ATTR_TRANSITION] + if transition <= 127: + self.values.dimming_duration.data = int(transition) + elif transition > 7620: + self.values.dimming_duration.data = 0xFE + _LOGGER.warning("Transition clipped to 127 minutes for %s.", + self.entity_id) + else: + minutes = int(transition / 60) + _LOGGER.debug("Transition rounded to %d minutes for %s.", + minutes, self.entity_id) + self.values.dimming_duration.data = minutes + 0x7F def turn_on(self, **kwargs): """Turn the device on.""" + self._set_duration(**kwargs) + # Zwave multilevel switches use a range of [0, 99] to control # brightness. Level 255 means to set it to previous value. if ATTR_BRIGHTNESS in kwargs: @@ -156,17 +199,12 @@ class ZwaveDimmer(zwave.ZWaveDeviceEntity, Light): def turn_off(self, **kwargs): """Turn the device off.""" + self._set_duration(**kwargs) + if self.node.set_dimmer(self.values.primary.value_id, 0): self._state = STATE_OFF -def ct_to_rgb(temp): - """Convert color temperature (mireds) to RGB.""" - colorlist = list( - color_temperature_to_rgb(color_temperature_mired_to_kelvin(temp))) - return [int(val) for val in colorlist] - - class ZwaveColorLight(ZwaveDimmer): """Representation of a Z-Wave color changing light.""" @@ -178,6 +216,14 @@ class ZwaveColorLight(ZwaveDimmer): super().__init__(values, refresh, delay) + def value_added(self): + """Called when a new value is added to this entity.""" + super().value_added() + + self._supported_features |= SUPPORT_RGB_COLOR + if self._zw098: + self._supported_features |= SUPPORT_COLOR_TEMP + def update_properties(self): """Update internal properties based on zwave values.""" super().update_properties() @@ -288,11 +334,3 @@ class ZwaveColorLight(ZwaveDimmer): self.values.color.data = rgbw super().turn_on(**kwargs) - - @property - def supported_features(self): - """Flag supported features.""" - if self._zw098: - return SUPPORT_ZWAVE_COLORTEMP - else: - return SUPPORT_ZWAVE_COLOR diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py index 4eea502d40a..efef8b39b64 100755 --- a/homeassistant/components/zwave/__init__.py +++ b/homeassistant/components/zwave/__init__.py @@ -669,6 +669,7 @@ class ZWaveDeviceEntityValues(): continue self._values[name] = value if self._entity: + self._entity.value_added() self._entity.value_changed() self._check_entity_ready() @@ -778,6 +779,10 @@ class ZWaveDeviceEntity(ZWaveBaseEntity): if value.value_id in [v.value_id for v in self.values if v]: return self.value_changed() + def value_added(self): + """Called when a new value is added to this entity.""" + pass + def value_changed(self): """Called when a value for this entity's node has changed.""" self._update_attributes() diff --git a/homeassistant/components/zwave/discovery_schemas.py b/homeassistant/components/zwave/discovery_schemas.py index 12a8c8c4375..f6e56ce79c8 100644 --- a/homeassistant/components/zwave/discovery_schemas.py +++ b/homeassistant/components/zwave/discovery_schemas.py @@ -121,6 +121,13 @@ DISCOVERY_SCHEMAS = [ const.DISC_GENRE: const.GENRE_USER, const.DISC_TYPE: const.TYPE_BYTE, }, + 'dimming_duration': { + const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SWITCH_MULTILEVEL], + const.DISC_GENRE: const.GENRE_SYSTEM, + const.DISC_TYPE: const.TYPE_BYTE, + const.DISC_LABEL: 'Dimming Duration', + const.DISC_OPTIONAL: True, + }, 'color': { const.DISC_COMMAND_CLASS: [const.COMMAND_CLASS_SWITCH_COLOR], const.DISC_GENRE: const.GENRE_USER, diff --git a/tests/components/light/test_zwave.py b/tests/components/light/test_zwave.py index dc4096ac68c..0afe9ec8f6a 100644 --- a/tests/components/light/test_zwave.py +++ b/tests/components/light/test_zwave.py @@ -4,7 +4,9 @@ from unittest.mock import patch, MagicMock import homeassistant.components.zwave from homeassistant.components.zwave import const from homeassistant.components.light import ( - zwave, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR) + zwave, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_TRANSITION, + SUPPORT_BRIGHTNESS, SUPPORT_TRANSITION, SUPPORT_RGB_COLOR, + SUPPORT_COLOR_TEMP) from tests.mock.zwave import ( MockNode, MockValue, MockEntityValues, value_changed) @@ -15,6 +17,7 @@ class MockLightValues(MockEntityValues): def __init__(self, **kwargs): """Initialize the mock zwave values.""" + self.dimming_duration = None self.color = None self.color_channels = None super().__init__(**kwargs) @@ -28,7 +31,7 @@ def test_get_device_detects_dimmer(mock_openzwave): device = zwave.get_device(node=node, values=values, node_config={}) assert isinstance(device, zwave.ZwaveDimmer) - assert device.supported_features == zwave.SUPPORT_ZWAVE_DIMMER + assert device.supported_features == SUPPORT_BRIGHTNESS def test_get_device_detects_colorlight(mock_openzwave): @@ -39,7 +42,7 @@ def test_get_device_detects_colorlight(mock_openzwave): device = zwave.get_device(node=node, values=values, node_config={}) assert isinstance(device, zwave.ZwaveColorLight) - assert device.supported_features == zwave.SUPPORT_ZWAVE_COLOR + assert device.supported_features == SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR def test_get_device_detects_zw098(mock_openzwave): @@ -50,7 +53,8 @@ def test_get_device_detects_zw098(mock_openzwave): values = MockLightValues(primary=value) device = zwave.get_device(node=node, values=values, node_config={}) assert isinstance(device, zwave.ZwaveColorLight) - assert device.supported_features == zwave.SUPPORT_ZWAVE_COLORTEMP + assert device.supported_features == ( + SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR | SUPPORT_COLOR_TEMP) def test_dimmer_turn_on(mock_openzwave): @@ -77,6 +81,57 @@ def test_dimmer_turn_on(mock_openzwave): assert value_id == value.value_id assert brightness == 46 # int(120 / 255 * 99) + with patch.object(zwave, '_LOGGER', MagicMock()) as mock_logger: + device.turn_on(**{ATTR_TRANSITION: 35}) + assert mock_logger.debug.called + assert node.set_dimmer.called + msg, entity_id = mock_logger.debug.mock_calls[0][1] + assert entity_id == device.entity_id + + +def test_dimmer_transitions(mock_openzwave): + """Test dimming transition on a dimmable Z-Wave light.""" + node = MockNode() + value = MockValue(data=0, node=node) + duration = MockValue(data=0, node=node) + values = MockLightValues(primary=value, dimming_duration=duration) + device = zwave.get_device(node=node, values=values, node_config={}) + assert device.supported_features == SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION + + # Test turn_on + # Factory Default + device.turn_on() + assert duration.data == 0xFF + + # Seconds transition + device.turn_on(**{ATTR_TRANSITION: 45}) + assert duration.data == 45 + + # Minutes transition + device.turn_on(**{ATTR_TRANSITION: 245}) + assert duration.data == 0x83 + + # Clipped transition + device.turn_on(**{ATTR_TRANSITION: 10000}) + assert duration.data == 0xFE + + # Test turn_off + # Factory Default + device.turn_off() + assert duration.data == 0xFF + + # Seconds transition + device.turn_off(**{ATTR_TRANSITION: 45}) + assert duration.data == 45 + + # Minutes transition + device.turn_off(**{ATTR_TRANSITION: 245}) + assert duration.data == 0x83 + + # Clipped transition + device.turn_off(**{ATTR_TRANSITION: 10000}) + assert duration.data == 0xFE + def test_dimmer_turn_off(mock_openzwave): """Test turning off a dimmable Z-Wave light."""