Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for zwave light transitions #6868

Merged
merged 3 commits into from Apr 3, 2017
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 43 additions & 8 deletions homeassistant/components/light/zwave.py
Expand Up @@ -10,7 +10,7 @@
# 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, \
ATTR_RGB_COLOR, ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, \
SUPPORT_RGB_COLOR, DOMAIN, Light
from homeassistant.components import zwave
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
Expand Down Expand Up @@ -72,6 +72,13 @@ def brightness_state(value):
return 0, STATE_OFF


def ct_to_rgb(temp):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated, but I just moved this up so it's not in-between the two classes.

"""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."""

Expand Down Expand Up @@ -141,8 +148,41 @@ def supported_features(self):
"""Flag supported features."""
return SUPPORT_ZWAVE_DIMMER

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you check in OZW_Log.txt if this assignment causes a zwave message to be sent when data doesn't actually changes? (i.e. it was already 0xFF)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'll take a look, but at least from a theoretical level it shouldn't. The dimming duration is actually just a parameter that gets sent with the multilevel switch set command.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it is. You can see where the dimming_duration is part of the SwitchMultilevelCmd_Set message to the network.

2017-04-01 15:41:20.398 Info, Node003, Value::Set - COMMAND_CLASS_SWITCH_MULTILEVEL - Dimming Duration - 5 - 1 - \FF
2017-04-01 15:41:20.398 Detail, Node003, Refreshed Value: old value=255, new value=255, type=byte
2017-04-01 15:41:20.398 Detail, Node003, Changes to this value are not verified
2017-04-01 15:41:20.398 Detail, Node003, Notification: ValueChanged
2017-04-01 15:41:20.398 Info, Node003, Value::Set - COMMAND_CLASS_SWITCH_MULTILEVEL - Level - 0 - 1 - 
2017-04-01 15:41:20.398 Info, Node003, SwitchMultilevel::Set - Setting to level 0
2017-04-01 15:41:20.398 Info, Node003,   Duration: Default
2017-04-01 15:41:20.399 Detail, Node003, Setting Encryption Flag on Message For Command Class COMMAND_CLASS_SWITCH_MULTILEVEL
2017-04-01 15:41:20.399 Detail, Node003, Queuing (Send) SwitchMultilevelCmd_Set (Node=3): 0x01, 0x0b, 0x00, 0x13, 0x03, 0x04, 0x26, 0x01, 0x00, 0xff, 0x25, 0x4d, 0x50
2017-04-01 15:41:20.399 Detail, Node003, Setting Encryption Flag on Message For Command Class COMMAND_CLASS_SWITCH_MULTILEVEL
2017-04-01 15:41:20.399 Detail, Node003, Queuing (Send) SwitchMultilevelCmd_Get (Node=3): 0x01, 0x09, 0x00, 0x13, 0x03, 0x02, 0x26, 0x02, 0x25, 0x4e, 0xab
2017-04-01 15:41:20.402 Detail, 

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:
Expand All @@ -156,17 +196,12 @@ def turn_on(self, **kwargs):

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."""

Expand Down
7 changes: 7 additions & 0 deletions homeassistant/components/zwave/discovery_schemas.py
Expand Up @@ -121,6 +121,13 @@
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,
Expand Down
53 changes: 52 additions & 1 deletion tests/components/light/test_zwave.py
Expand Up @@ -4,7 +4,7 @@
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)

from tests.mock.zwave import (
MockNode, MockValue, MockEntityValues, value_changed)
Expand All @@ -15,6 +15,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)
Expand Down Expand Up @@ -77,6 +78,56 @@ 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={})

# 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."""
Expand Down