From f9b420a5a594f81cf2bf31f1ce337a14bdf9050b Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Tue, 5 May 2020 00:54:49 +0200 Subject: [PATCH] Make sure MQTT light brightness is not rounded to 0 (#35207) --- .../components/mqtt/light/schema_basic.py | 6 +- .../components/mqtt/light/schema_json.py | 11 +- tests/components/mqtt/test_light.py | 123 +++++++++++++++++- tests/components/mqtt/test_light_json.py | 81 +++++++++++- 4 files changed, 209 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index 2af63311e47..e3405a02c6e 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -732,11 +732,13 @@ class MqttLight( ATTR_BRIGHTNESS in kwargs and self._topic[CONF_BRIGHTNESS_COMMAND_TOPIC] is not None ): - percent_bright = float(kwargs[ATTR_BRIGHTNESS]) / 255 + brightness_normalized = kwargs[ATTR_BRIGHTNESS] / 255 brightness_scale = self._config[CONF_BRIGHTNESS_SCALE] device_brightness = min( - round(percent_bright * brightness_scale), brightness_scale + round(brightness_normalized * brightness_scale), brightness_scale ) + # Make sure the brightness is not rounded down to 0 + device_brightness = max(device_brightness, 1) mqtt.async_publish( self.hass, self._topic[CONF_BRIGHTNESS_COMMAND_TOPIC], diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index b803621fbb8..69bbab3970d 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -459,11 +459,14 @@ class MqttLightJson( message["transition"] = kwargs[ATTR_TRANSITION] if ATTR_BRIGHTNESS in kwargs and self._brightness is not None: - message["brightness"] = int( - kwargs[ATTR_BRIGHTNESS] - / float(DEFAULT_BRIGHTNESS_SCALE) - * self._config[CONF_BRIGHTNESS_SCALE] + brightness_normalized = kwargs[ATTR_BRIGHTNESS] / DEFAULT_BRIGHTNESS_SCALE + brightness_scale = self._config[CONF_BRIGHTNESS_SCALE] + device_brightness = min( + round(brightness_normalized * brightness_scale), brightness_scale ) + # Make sure the brightness is not rounded down to 0 + device_brightness = max(device_brightness, 1) + message["brightness"] = device_brightness if self._optimistic: self._brightness = kwargs[ATTR_BRIGHTNESS] diff --git a/tests/components/mqtt/test_light.py b/tests/components/mqtt/test_light.py index ccf5935cecc..3019b6b6ebd 100644 --- a/tests/components/mqtt/test_light.py +++ b/tests/components/mqtt/test_light.py @@ -980,7 +980,6 @@ async def test_on_command_first(hass, mqtt_mock): call("test_light/set", "ON", 0, False), call("test_light/bright", 50, 0, False), ], - any_order=True, ) mqtt_mock.async_publish.reset_mock() @@ -1015,7 +1014,6 @@ async def test_on_command_last(hass, mqtt_mock): call("test_light/bright", 50, 0, False), call("test_light/set", "ON", 0, False), ], - any_order=True, ) mqtt_mock.async_publish.reset_mock() @@ -1063,7 +1061,7 @@ async def test_on_command_brightness(hass, mqtt_mock): await common.async_turn_off(hass, "light.test") - # Turn on w/ just a color to insure brightness gets + # Turn on w/ just a color to ensure brightness gets # added and sent. await common.async_turn_on(hass, "light.test", rgb_color=[255, 128, 0]) @@ -1076,6 +1074,71 @@ async def test_on_command_brightness(hass, mqtt_mock): ) +async def test_on_command_brightness_scaled(hass, mqtt_mock): + """Test brightness scale.""" + config = { + light.DOMAIN: { + "platform": "mqtt", + "name": "test", + "command_topic": "test_light/set", + "brightness_command_topic": "test_light/bright", + "brightness_scale": 100, + "rgb_command_topic": "test_light/rgb", + "on_command_type": "brightness", + } + } + + assert await async_setup_component(hass, light.DOMAIN, config) + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + # Turn on w/ no brightness - should set to max + await common.async_turn_on(hass, "light.test") + + # Should get the following MQTT messages. + # test_light/bright: 100 + mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 100, 0, False) + mqtt_mock.async_publish.reset_mock() + + await common.async_turn_off(hass, "light.test") + + mqtt_mock.async_publish.assert_called_once_with("test_light/set", "OFF", 0, False) + mqtt_mock.async_publish.reset_mock() + + # Turn on w/ brightness + await common.async_turn_on(hass, "light.test", brightness=50) + + mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 20, 0, False) + mqtt_mock.async_publish.reset_mock() + + # Turn on w/ max brightness + await common.async_turn_on(hass, "light.test", brightness=255) + + mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 100, 0, False) + mqtt_mock.async_publish.reset_mock() + + # Turn on w/ min brightness + await common.async_turn_on(hass, "light.test", brightness=1) + + mqtt_mock.async_publish.assert_called_once_with("test_light/bright", 1, 0, False) + mqtt_mock.async_publish.reset_mock() + + await common.async_turn_off(hass, "light.test") + + # Turn on w/ just a color to ensure brightness gets + # added and sent. + await common.async_turn_on(hass, "light.test", rgb_color=[255, 128, 0]) + + mqtt_mock.async_publish.assert_has_calls( + [ + call("test_light/rgb", "255,128,0", 0, False), + call("test_light/bright", 1, 0, False), + ], + any_order=True, + ) + + async def test_on_command_rgb(hass, mqtt_mock): """Test on command in RGB brightness mode.""" config = { @@ -1106,10 +1169,64 @@ async def test_on_command_rgb(hass, mqtt_mock): ) mqtt_mock.async_publish.reset_mock() + await common.async_turn_on(hass, "light.test", brightness=255) + + # Should get the following MQTT messages. + # test_light/rgb: '255,255,255' + # test_light/set: 'ON' + mqtt_mock.async_publish.assert_has_calls( + [ + call("test_light/rgb", "255,255,255", 0, False), + call("test_light/set", "ON", 0, False), + ], + any_order=True, + ) + mqtt_mock.async_publish.reset_mock() + + await common.async_turn_on(hass, "light.test", brightness=1) + + # Should get the following MQTT messages. + # test_light/rgb: '1,1,1' + # test_light/set: 'ON' + mqtt_mock.async_publish.assert_has_calls( + [ + call("test_light/rgb", "1,1,1", 0, False), + call("test_light/set", "ON", 0, False), + ], + any_order=True, + ) + mqtt_mock.async_publish.reset_mock() + await common.async_turn_off(hass, "light.test") mqtt_mock.async_publish.assert_called_once_with("test_light/set", "OFF", 0, False) + # Ensure color gets scaled with brightness. + await common.async_turn_on(hass, "light.test", rgb_color=[255, 128, 0]) + + mqtt_mock.async_publish.assert_has_calls( + [ + call("test_light/rgb", "1,0,0", 0, False), + call("test_light/set", "ON", 0, False), + ], + any_order=True, + ) + mqtt_mock.async_publish.reset_mock() + + await common.async_turn_on(hass, "light.test", brightness=255) + + # Should get the following MQTT messages. + # test_light/rgb: '255,128,0' + # test_light/set: 'ON' + mqtt_mock.async_publish.assert_has_calls( + [ + call("test_light/rgb", "255,128,0", 0, False), + call("test_light/set", "ON", 0, False), + ], + any_order=True, + ) + mqtt_mock.async_publish.reset_mock() + async def test_on_command_rgb_template(hass, mqtt_mock): """Test on command in RGB brightness mode with RGB template.""" diff --git a/tests/components/mqtt/test_light_json.py b/tests/components/mqtt/test_light_json.py index d4712e9f835..f640d287a1b 100644 --- a/tests/components/mqtt/test_light_json.py +++ b/tests/components/mqtt/test_light_json.py @@ -577,7 +577,8 @@ async def test_sending_rgb_color_with_brightness(hass, mqtt_mock): await common.async_turn_on( hass, "light.test", brightness=50, xy_color=[0.123, 0.123] ) - await common.async_turn_on(hass, "light.test", brightness=50, hs_color=[359, 78]) + await common.async_turn_on(hass, "light.test", brightness=255, hs_color=[359, 78]) + await common.async_turn_on(hass, "light.test", brightness=1) await common.async_turn_on( hass, "light.test", rgb_color=[255, 128, 0], white_value=80 ) @@ -597,11 +598,86 @@ async def test_sending_rgb_color_with_brightness(hass, mqtt_mock): "test_light_rgb/set", JsonValidator( '{"state": "ON", "color": {"r": 255, "g": 56, "b": 59},' - ' "brightness": 50}' + ' "brightness": 255}' ), 0, False, ), + call( + "test_light_rgb/set", + JsonValidator('{"state": "ON", "brightness": 1}'), + 0, + False, + ), + call( + "test_light_rgb/set", + JsonValidator( + '{"state": "ON", "color": {"r": 255, "g": 128, "b": 0},' + ' "white_value": 80}' + ), + 0, + False, + ), + ], + ) + + +async def test_sending_rgb_color_with_scaled_brightness(hass, mqtt_mock): + """Test light.turn_on with hs color sends rgb color parameters.""" + assert await async_setup_component( + hass, + light.DOMAIN, + { + light.DOMAIN: { + "platform": "mqtt", + "schema": "json", + "name": "test", + "command_topic": "test_light_rgb/set", + "brightness": True, + "brightness_scale": 100, + "rgb": True, + } + }, + ) + + state = hass.states.get("light.test") + assert state.state == STATE_OFF + + await common.async_turn_on( + hass, "light.test", brightness=50, xy_color=[0.123, 0.123] + ) + await common.async_turn_on(hass, "light.test", brightness=255, hs_color=[359, 78]) + await common.async_turn_on(hass, "light.test", brightness=1) + await common.async_turn_on( + hass, "light.test", rgb_color=[255, 128, 0], white_value=80 + ) + + mqtt_mock.async_publish.assert_has_calls( + [ + call( + "test_light_rgb/set", + JsonValidator( + '{"state": "ON", "color": {"r": 0, "g": 123, "b": 255},' + ' "brightness": 20}' + ), + 0, + False, + ), + call( + "test_light_rgb/set", + JsonValidator( + '{"state": "ON", "color": {"r": 255, "g": 56, "b": 59},' + ' "brightness": 100}' + ), + 0, + False, + ), + call( + "test_light_rgb/set", + JsonValidator('{"state": "ON", "brightness": 1}'), + 0, + False, + ), call( "test_light_rgb/set", JsonValidator( @@ -612,7 +688,6 @@ async def test_sending_rgb_color_with_brightness(hass, mqtt_mock): False, ), ], - any_order=True, )