Skip to content

Commit

Permalink
Fix mqtt light brightness slider (#17075)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
thinkl33t authored and emlove committed Oct 20, 2018
1 parent e980d1b commit 2e973c7
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 2 deletions.
34 changes: 32 additions & 2 deletions homeassistant/components/light/mqtt.py
Expand Up @@ -211,7 +211,11 @@ def __init__(self, name, unique_id, effect_list, topic, templates,
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 = (
Expand All @@ -233,7 +237,8 @@ def __init__(self, name, unique_id, effect_list, topic, templates,
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)
Expand Down Expand Up @@ -325,6 +330,10 @@ def rgb_received(topic, payload, qos):

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:
Expand Down Expand Up @@ -616,6 +625,27 @@ async def async_turn_on(self, **kwargs):
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:
Expand Down
68 changes: 68 additions & 0 deletions tests/components/light/test_mqtt.py
Expand Up @@ -393,6 +393,41 @@ def test_brightness_controlling_scale(self):
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):
Expand Down Expand Up @@ -894,6 +929,39 @@ def test_on_command_brightness(self):
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, {
Expand Down

0 comments on commit 2e973c7

Please sign in to comment.