Fix mqtt light brightness slider (#17075)

* Enable brightness slider for RGB

If we are using RGB with no brightness topic, the brighness slider
should still be visible, as we can scale the RGB amount to give us the
brightness.

* Output RGB scaled by brightness

If we are outputting to an RGB device, but do not have a dedicated
brightness topic set, when the brightness slider is changed, we should
output the current colour's HS, with the V coming from the brightness
slider.

* Brightness from RGB when we're not using a brightness topic

When we aren't using a brightness topic, set the brightness slider based
on the received value from an RGB -> HSV conversion.

* Test for new brightness state scaled by RGB

This adds a test to make sure the brightness stored in the state is
being computed correctly from the RGB value when a dedicated brightness
topic is not set.

* Changes from review

Fixes formatting of supported features flags, and checks HS colour
hasn't been set when operating in RGB-only mode

* Set optimistic brightness correctly in rgb mode

When we're using rgb mode to set the brightness, we want to set
optimistic brightness if:

we are running in optimistic mode
OR
the brightness state topic isn't set and we have a brightness command topic
OR
the rgb state topic isn't set and we don't have a brightness command topic

* Add test for turn_on in RGB brightness mode
pull/17642/head
Bob Clough 2018-10-20 17:37:25 +01:00 committed by Adam Mills
parent e980d1b9fe
commit 2e973c7572
2 changed files with 100 additions and 2 deletions

View File

@ -211,7 +211,11 @@ class MqttLight(MqttAvailability, MqttDiscoveryUpdate, Light):
self._optimistic_rgb = \
optimistic or topic[CONF_RGB_STATE_TOPIC] is None
self._optimistic_brightness = (
optimistic or topic[CONF_BRIGHTNESS_STATE_TOPIC] is None)
optimistic or
(topic[CONF_BRIGHTNESS_COMMAND_TOPIC] is not None and
topic[CONF_BRIGHTNESS_STATE_TOPIC] is None) or
(topic[CONF_BRIGHTNESS_COMMAND_TOPIC] is None and
topic[CONF_RGB_STATE_TOPIC] is None))
self._optimistic_color_temp = (
optimistic or topic[CONF_COLOR_TEMP_STATE_TOPIC] is None)
self._optimistic_effect = (
@ -233,7 +237,8 @@ class MqttLight(MqttAvailability, MqttDiscoveryUpdate, Light):
self._white_value = None
self._supported_features = 0
self._supported_features |= (
topic[CONF_RGB_COMMAND_TOPIC] is not None and SUPPORT_COLOR)
topic[CONF_RGB_COMMAND_TOPIC] is not None and
(SUPPORT_COLOR | SUPPORT_BRIGHTNESS))
self._supported_features |= (
topic[CONF_BRIGHTNESS_COMMAND_TOPIC] is not None and
SUPPORT_BRIGHTNESS)
@ -325,6 +330,10 @@ class MqttLight(MqttAvailability, MqttDiscoveryUpdate, Light):
rgb = [int(val) for val in payload.split(',')]
self._hs = color_util.color_RGB_to_hs(*rgb)
if self._topic[CONF_BRIGHTNESS_STATE_TOPIC] is None:
percent_bright = \
float(color_util.color_RGB_to_hsv(*rgb)[2]) / 100.0
self._brightness = int(percent_bright * 255)
self.async_schedule_update_ha_state()
if self._topic[CONF_RGB_STATE_TOPIC] is not None:
@ -616,6 +625,27 @@ class MqttLight(MqttAvailability, MqttDiscoveryUpdate, Light):
if self._optimistic_brightness:
self._brightness = kwargs[ATTR_BRIGHTNESS]
should_update = True
elif ATTR_BRIGHTNESS in kwargs and ATTR_HS_COLOR not in kwargs and\
self._topic[CONF_RGB_COMMAND_TOPIC] is not None:
rgb = color_util.color_hsv_to_RGB(
self._hs[0], self._hs[1], kwargs[ATTR_BRIGHTNESS] / 255 * 100)
tpl = self._templates[CONF_RGB_COMMAND_TEMPLATE]
if tpl:
rgb_color_str = tpl.async_render({
'red': rgb[0],
'green': rgb[1],
'blue': rgb[2],
})
else:
rgb_color_str = '{},{},{}'.format(*rgb)
mqtt.async_publish(
self.hass, self._topic[CONF_RGB_COMMAND_TOPIC],
rgb_color_str, self._qos, self._retain)
if self._optimistic_brightness:
self._brightness = kwargs[ATTR_BRIGHTNESS]
should_update = True
if ATTR_COLOR_TEMP in kwargs and \
self._topic[CONF_COLOR_TEMP_COMMAND_TOPIC] is not None:

View File

@ -393,6 +393,41 @@ class TestLightMQTT(unittest.TestCase):
self.assertEqual(255,
light_state.attributes['brightness'])
def test_brightness_from_rgb_controlling_scale(self):
"""Test the brightness controlling scale."""
with assert_setup_component(1, light.DOMAIN):
assert setup_component(self.hass, light.DOMAIN, {
light.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'test_scale_rgb/status',
'command_topic': 'test_scale_rgb/set',
'rgb_state_topic': 'test_scale_rgb/rgb/status',
'rgb_command_topic': 'test_scale_rgb/rgb/set',
'qos': 0,
'payload_on': 'on',
'payload_off': 'off'
}
})
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)
self.assertIsNone(state.attributes.get('brightness'))
self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE))
fire_mqtt_message(self.hass, 'test_scale_rgb/status', 'on')
fire_mqtt_message(self.hass, 'test_scale_rgb/rgb/status', '255,0,0')
self.hass.block_till_done()
state = self.hass.states.get('light.test')
self.assertEqual(255, state.attributes.get('brightness'))
fire_mqtt_message(self.hass, 'test_scale_rgb/rgb/status', '127,0,0')
self.hass.block_till_done()
state = self.hass.states.get('light.test')
self.assertEqual(127, state.attributes.get('brightness'))
def test_white_value_controlling_scale(self):
"""Test the white_value controlling scale."""
with assert_setup_component(1, light.DOMAIN):
@ -894,6 +929,39 @@ class TestLightMQTT(unittest.TestCase):
mock.call('test_light/bright', 50, 0, False)
], any_order=True)
def test_on_command_rgb(self):
"""Test on command in RGB brightness mode."""
config = {light.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'command_topic': 'test_light/set',
'rgb_command_topic': "test_light/rgb",
}}
with assert_setup_component(1, light.DOMAIN):
assert setup_component(self.hass, light.DOMAIN, config)
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)
common.turn_on(self.hass, 'light.test', brightness=127)
self.hass.block_till_done()
# Should get the following MQTT messages.
# test_light/rgb: '127,127,127'
# test_light/set: 'ON'
self.mock_publish.async_publish.assert_has_calls([
mock.call('test_light/rgb', '127,127,127', 0, False),
mock.call('test_light/set', 'ON', 0, False),
], any_order=True)
self.mock_publish.async_publish.reset_mock()
common.turn_off(self.hass, 'light.test')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'test_light/set', 'OFF', 0, False)
def test_default_availability_payload(self):
"""Test availability by default payload with defined topic."""
self.assertTrue(setup_component(self.hass, light.DOMAIN, {