mirror of
https://github.com/home-assistant/core.git
synced 2025-04-24 01:08:12 +00:00
Support for zwave light transitions (#6868)
* Support for zwave light transitions * Dimming duration is optional * Updated supported_features to show transition
This commit is contained in:
parent
01e581aced
commit
06e1c21b1f
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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."""
|
||||
|
Loading…
x
Reference in New Issue
Block a user