From 23f278e090641c2e33f3da5b12e8a7667c43c355 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 17 Apr 2020 11:59:27 -0500 Subject: [PATCH] Fix emulated_hue brightness off by one (#34185) * Fix emulated_hue brightness off by one Hue uses a max brightness of 254, Home Assistant uses a max brightness of 255. Values > 127 were off by one. * use constant * fix test * add debug * Revert "add debug" This reverts commit 242220a02e8d8e9ec5e21930470b2adb97c75e2b. --- .../components/emulated_hue/hue_api.py | 36 +++++++---- tests/components/emulated_hue/test_hue_api.py | 62 ++++++++++++++++--- 2 files changed, 77 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index ecb0241c724..9637b0fb371 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -367,7 +367,7 @@ class HueOneLightChangeView(HomeAssistantView): cover.DOMAIN, climate.DOMAIN, ]: - # Convert 0-255 to 0-100 + # Convert 0-254 to 0-100 level = (parsed[STATE_BRIGHTNESS] / HUE_API_STATE_BRI_MAX) * 100 parsed[STATE_BRIGHTNESS] = round(level) parsed[STATE_ON] = True @@ -390,7 +390,9 @@ class HueOneLightChangeView(HomeAssistantView): if parsed[STATE_ON]: if entity_features & SUPPORT_BRIGHTNESS: if parsed[STATE_BRIGHTNESS] is not None: - data[ATTR_BRIGHTNESS] = parsed[STATE_BRIGHTNESS] + data[ATTR_BRIGHTNESS] = hue_brightness_to_hass( + parsed[STATE_BRIGHTNESS] + ) if entity_features & SUPPORT_COLOR: if any((parsed[STATE_HUE], parsed[STATE_SATURATION])): @@ -536,7 +538,9 @@ def get_entity_state(config, entity): data[STATE_ON] = entity.state != STATE_OFF if data[STATE_ON]: - data[STATE_BRIGHTNESS] = entity.attributes.get(ATTR_BRIGHTNESS, 0) + data[STATE_BRIGHTNESS] = hass_to_hue_brightness( + entity.attributes.get(ATTR_BRIGHTNESS, 0) + ) hue_sat = entity.attributes.get(ATTR_HS_COLOR) if hue_sat is not None: hue = hue_sat[0] @@ -563,32 +567,32 @@ def get_entity_state(config, entity): pass elif entity.domain == climate.DOMAIN: temperature = entity.attributes.get(ATTR_TEMPERATURE, 0) - # Convert 0-100 to 0-255 - data[STATE_BRIGHTNESS] = round(temperature * 255 / 100) + # Convert 0-100 to 0-254 + data[STATE_BRIGHTNESS] = round(temperature * HUE_API_STATE_BRI_MAX / 100) elif entity.domain == media_player.DOMAIN: level = entity.attributes.get( ATTR_MEDIA_VOLUME_LEVEL, 1.0 if data[STATE_ON] else 0.0 ) - # Convert 0.0-1.0 to 0-255 - data[STATE_BRIGHTNESS] = round(min(1.0, level) * 255) + # Convert 0.0-1.0 to 0-254 + data[STATE_BRIGHTNESS] = round(min(1.0, level) * HUE_API_STATE_BRI_MAX) elif entity.domain == fan.DOMAIN: speed = entity.attributes.get(ATTR_SPEED, 0) - # Convert 0.0-1.0 to 0-255 + # Convert 0.0-1.0 to 0-254 data[STATE_BRIGHTNESS] = 0 if speed == SPEED_LOW: data[STATE_BRIGHTNESS] = 85 elif speed == SPEED_MEDIUM: data[STATE_BRIGHTNESS] = 170 elif speed == SPEED_HIGH: - data[STATE_BRIGHTNESS] = 255 + data[STATE_BRIGHTNESS] = HUE_API_STATE_BRI_MAX elif entity.domain == cover.DOMAIN: level = entity.attributes.get(ATTR_CURRENT_POSITION, 0) - data[STATE_BRIGHTNESS] = round(level / 100 * 255) + data[STATE_BRIGHTNESS] = round(level / 100 * HUE_API_STATE_BRI_MAX) else: data = cached_state # Make sure brightness is valid if data[STATE_BRIGHTNESS] is None: - data[STATE_BRIGHTNESS] = 255 if data[STATE_ON] else 0 + data[STATE_BRIGHTNESS] = HUE_API_STATE_BRI_MAX if data[STATE_ON] else 0 # Make sure hue/saturation are valid if (data[STATE_HUE] is None) or (data[STATE_SATURATION] is None): @@ -723,3 +727,13 @@ def create_list_of_entities(config, request): json_response[number] = entity_to_json(config, entity) return json_response + + +def hue_brightness_to_hass(value): + """Convert hue brightness 1..254 to hass format 0..255.""" + return min(255, round((value / HUE_API_STATE_BRI_MAX) * 255)) + + +def hass_to_hue_brightness(value): + """Convert hass brightness 0..255 to hue 1..254 scale.""" + return max(1, round((value / 255) * HUE_API_STATE_BRI_MAX)) diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index a6b4113e314..052228e7aab 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -384,7 +384,7 @@ async def test_get_light_state(hass_hue, hue_client): await perform_get_light_state(hue_client, "light.kitchen_lights", 401) -async def test_put_light_state(hass_hue, hue_client): +async def test_put_light_state(hass, hass_hue, hue_client): """Test the setting of light states.""" await perform_put_test_on_ceiling_lights(hass_hue, hue_client) @@ -400,6 +400,21 @@ async def test_put_light_state(hass_hue, hue_client): assert ceiling_lights.state == STATE_ON assert ceiling_lights.attributes[light.ATTR_BRIGHTNESS] == 153 + # update light state through api + await perform_put_light_state( + hass_hue, + hue_client, + "light.ceiling_lights", + True, + hue=4369, + saturation=127, + brightness=128, + ) + + assert ( + hass.states.get("light.ceiling_lights").attributes[light.ATTR_BRIGHTNESS] == 129 + ) + # update light state through api await perform_put_light_state( hass_hue, @@ -411,6 +426,10 @@ async def test_put_light_state(hass_hue, hue_client): brightness=123, ) + assert ( + hass.states.get("light.ceiling_lights").attributes[light.ATTR_BRIGHTNESS] == 123 + ) + # go through api to get the state back ceiling_json = await perform_get_light_state( hue_client, "light.ceiling_lights", 200 @@ -419,6 +438,25 @@ async def test_put_light_state(hass_hue, hue_client): assert ceiling_json["state"][HUE_API_STATE_HUE] == 4369 assert ceiling_json["state"][HUE_API_STATE_SAT] == 127 + # update light state through api + await perform_put_light_state( + hass_hue, + hue_client, + "light.ceiling_lights", + True, + hue=4369, + saturation=127, + brightness=255, + ) + + # go through api to get the state back + ceiling_json = await perform_get_light_state( + hue_client, "light.ceiling_lights", 200 + ) + assert ceiling_json["state"][HUE_API_STATE_BRI] == 254 + assert ceiling_json["state"][HUE_API_STATE_HUE] == 4369 + assert ceiling_json["state"][HUE_API_STATE_SAT] == 127 + # Go through the API to turn it off ceiling_result = await perform_put_light_state( hass_hue, hue_client, "light.ceiling_lights", False @@ -454,7 +492,7 @@ async def test_put_light_state(hass_hue, hue_client): assert kitchen_result.status == HTTP_NOT_FOUND -async def test_put_light_state_script(hass_hue, hue_client): +async def test_put_light_state_script(hass, hass_hue, hue_client): """Test the setting of script variables.""" # Turn the kitchen light off first await hass_hue.services.async_call( @@ -464,9 +502,9 @@ async def test_put_light_state_script(hass_hue, hue_client): blocking=True, ) - # Emulated hue converts 0-100% to 0-255. + # Emulated hue converts 0-100% to 0-254. level = 23 - brightness = round(level * 255 / 100) + brightness = round(level * 254 / 100) script_result = await perform_put_light_state( hass_hue, hue_client, "script.set_kitchen_light", True, brightness @@ -481,11 +519,15 @@ async def test_put_light_state_script(hass_hue, hue_client): assert kitchen_light.state == "on" assert kitchen_light.attributes[light.ATTR_BRIGHTNESS] == level + assert ( + hass.states.get("light.kitchen_lights").attributes[light.ATTR_BRIGHTNESS] == 23 + ) + async def test_put_light_state_climate_set_temperature(hass_hue, hue_client): """Test setting climate temperature.""" brightness = 19 - temperature = round(brightness / 255 * 100) + temperature = round(brightness / 254 * 100) hvac_result = await perform_put_light_state( hass_hue, hue_client, "climate.hvac", True, brightness @@ -517,9 +559,9 @@ async def test_put_light_state_media_player(hass_hue, hue_client): blocking=True, ) - # Emulated hue converts 0.0-1.0 to 0-255. + # Emulated hue converts 0.0-1.0 to 0-254. level = 0.25 - brightness = round(level * 255) + brightness = round(level * 254) mp_result = await perform_put_light_state( hass_hue, hue_client, "media_player.walkman", True, brightness @@ -602,7 +644,7 @@ async def test_set_position_cover(hass_hue, hue_client): assert cover_test.state == "closed" level = 20 - brightness = round(level / 100 * 255) + brightness = round(level / 100 * 254) # Go through the API to open cover_result = await perform_put_light_state( @@ -644,9 +686,9 @@ async def test_put_light_state_fan(hass_hue, hue_client): blocking=True, ) - # Emulated hue converts 0-100% to 0-255. + # Emulated hue converts 0-100% to 0-254. level = 43 - brightness = round(level * 255 / 100) + brightness = round(level * 254 / 100) fan_result = await perform_put_light_state( hass_hue, hue_client, "fan.living_room_fan", True, brightness