Store ZHA light brightness when fading off to turn on at the correct brightness (#26680)

* Use light's on_level in ZHA to turn on at the correct brightness

Previously, if the light is turned off with a time transition, the
brightness level stored in the light will be 1. The next time the light
is turned on with no explicit brightness, it will be at 1.

This is solved by storing the current brightness in on_level before
turning off, and then using that when turning on (by calling the
onOff cluster 'on' command).

* store off light level locally to avoid wearing device's flash memory

* store off brightness in HA attributes

* improve set/clear of off_brightness

* fix device_state_attributes; clear off_brightness when light goes on

* fix tests
This commit is contained in:
Abílio Costa 2019-09-23 14:08:44 +01:00 committed by David F. Mulcahey
parent 2c80ce3195
commit 61634d0a64
2 changed files with 21 additions and 6 deletions

View File

@ -25,8 +25,6 @@ from .entity import ZhaEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEFAULT_DURATION = 5
CAPABILITIES_COLOR_LOOP = 0x4 CAPABILITIES_COLOR_LOOP = 0x4
CAPABILITIES_COLOR_XY = 0x08 CAPABILITIES_COLOR_XY = 0x08
CAPABILITIES_COLOR_TEMP = 0x10 CAPABILITIES_COLOR_TEMP = 0x10
@ -91,6 +89,7 @@ class Light(ZhaEntity, light.Light):
self._color_temp = None self._color_temp = None
self._hs_color = None self._hs_color = None
self._brightness = None self._brightness = None
self._off_brightness = None
self._effect_list = [] self._effect_list = []
self._effect = None self._effect = None
self._on_off_channel = self.cluster_channels.get(CHANNEL_ON_OFF) self._on_off_channel = self.cluster_channels.get(CHANNEL_ON_OFF)
@ -130,7 +129,8 @@ class Light(ZhaEntity, light.Light):
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return state attributes.""" """Return state attributes."""
return self.state_attributes attributes = {"off_brightness": self._off_brightness}
return attributes
def set_level(self, value): def set_level(self, value):
"""Set the brightness of this light between 0..254. """Set the brightness of this light between 0..254.
@ -171,6 +171,8 @@ class Light(ZhaEntity, light.Light):
def async_set_state(self, state): def async_set_state(self, state):
"""Set the state.""" """Set the state."""
self._state = bool(state) self._state = bool(state)
if state:
self._off_brightness = None
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
async def async_added_to_hass(self): async def async_added_to_hass(self):
@ -191,6 +193,8 @@ class Light(ZhaEntity, light.Light):
self._state = last_state.state == STATE_ON self._state = last_state.state == STATE_ON
if "brightness" in last_state.attributes: if "brightness" in last_state.attributes:
self._brightness = last_state.attributes["brightness"] self._brightness = last_state.attributes["brightness"]
if "off_brightness" in last_state.attributes:
self._off_brightness = last_state.attributes["off_brightness"]
if "color_temp" in last_state.attributes: if "color_temp" in last_state.attributes:
self._color_temp = last_state.attributes["color_temp"] self._color_temp = last_state.attributes["color_temp"]
if "hs_color" in last_state.attributes: if "hs_color" in last_state.attributes:
@ -201,10 +205,13 @@ class Light(ZhaEntity, light.Light):
async def async_turn_on(self, **kwargs): async def async_turn_on(self, **kwargs):
"""Turn the entity on.""" """Turn the entity on."""
transition = kwargs.get(light.ATTR_TRANSITION) transition = kwargs.get(light.ATTR_TRANSITION)
duration = transition * 10 if transition is not None else DEFAULT_DURATION duration = transition * 10 if transition else 0
brightness = kwargs.get(light.ATTR_BRIGHTNESS) brightness = kwargs.get(light.ATTR_BRIGHTNESS)
effect = kwargs.get(light.ATTR_EFFECT) effect = kwargs.get(light.ATTR_EFFECT)
if brightness is None and self._off_brightness is not None:
brightness = self._off_brightness
t_log = {} t_log = {}
if ( if (
brightness is not None or transition brightness is not None or transition
@ -225,13 +232,14 @@ class Light(ZhaEntity, light.Light):
self._brightness = level self._brightness = level
if brightness is None or brightness: if brightness is None or brightness:
# since some lights don't always turn on with move_to_level_with_on_off,
# we should call the on command on the on_off cluster if brightness is not 0.
result = await self._on_off_channel.on() result = await self._on_off_channel.on()
t_log["on_off"] = result t_log["on_off"] = result
if not isinstance(result, list) or result[1] is not Status.SUCCESS: if not isinstance(result, list) or result[1] is not Status.SUCCESS:
self.debug("turned on: %s", t_log) self.debug("turned on: %s", t_log)
return return
self._state = True self._state = True
if ( if (
light.ATTR_COLOR_TEMP in kwargs light.ATTR_COLOR_TEMP in kwargs
and self.supported_features & light.SUPPORT_COLOR_TEMP and self.supported_features & light.SUPPORT_COLOR_TEMP
@ -289,6 +297,7 @@ class Light(ZhaEntity, light.Light):
t_log["color_loop_set"] = result t_log["color_loop_set"] = result
self._effect = None self._effect = None
self._off_brightness = None
self.debug("turned on: %s", t_log) self.debug("turned on: %s", t_log)
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
@ -296,6 +305,7 @@ class Light(ZhaEntity, light.Light):
"""Turn the entity off.""" """Turn the entity off."""
duration = kwargs.get(light.ATTR_TRANSITION) duration = kwargs.get(light.ATTR_TRANSITION)
supports_level = self.supported_features & light.SUPPORT_BRIGHTNESS supports_level = self.supported_features & light.SUPPORT_BRIGHTNESS
if duration and supports_level: if duration and supports_level:
result = await self._level_channel.move_to_level_with_on_off( result = await self._level_channel.move_to_level_with_on_off(
0, duration * 10 0, duration * 10
@ -306,6 +316,11 @@ class Light(ZhaEntity, light.Light):
if not isinstance(result, list) or result[1] is not Status.SUCCESS: if not isinstance(result, list) or result[1] is not Status.SUCCESS:
return return
self._state = False self._state = False
if duration and supports_level:
# store current brightness so that the next turn_on uses it.
self._off_brightness = self._brightness
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
async def async_update(self): async def async_update(self):

View File

@ -230,7 +230,7 @@ async def async_test_level_on_off_from_hass(
4, 4,
(types.uint8_t, types.uint16_t), (types.uint8_t, types.uint16_t),
10, 10,
5.0, 0,
expect_reply=True, expect_reply=True,
manufacturer=None, manufacturer=None,
) )