diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index ad6b0541cd6..05e5c1ece07 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -57,6 +57,7 @@ from homeassistant.const import ( SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_SET, + STATE_CLOSED, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, @@ -73,6 +74,7 @@ from homeassistant.util.network import is_local from .config import Config _LOGGER = logging.getLogger(__name__) +_OFF_STATES: dict[str, str] = {cover.DOMAIN: STATE_CLOSED} # How long to wait for a state change to happen STATE_CHANGE_WAIT_TIMEOUT = 5.0 @@ -394,7 +396,7 @@ class HueOneLightChangeView(HomeAssistantView): return self.json_message("Bad request", HTTPStatus.BAD_REQUEST) parsed[STATE_ON] = request_json[HUE_API_STATE_ON] else: - parsed[STATE_ON] = entity.state != STATE_OFF + parsed[STATE_ON] = _hass_to_hue_state(entity) for key, attr in ( (HUE_API_STATE_BRI, STATE_BRIGHTNESS), @@ -585,7 +587,7 @@ class HueOneLightChangeView(HomeAssistantView): ) if service is not None: - state_will_change = parsed[STATE_ON] != (entity.state != STATE_OFF) + state_will_change = parsed[STATE_ON] != _hass_to_hue_state(entity) hass.async_create_task( hass.services.async_call(domain, service, data, blocking=True) @@ -643,7 +645,7 @@ def get_entity_state_dict(config: Config, entity: State) -> dict[str, Any]: cached_state = entry_state elif time.time() - entry_time < STATE_CACHED_TIMEOUT and entry_state[ STATE_ON - ] == (entity.state != STATE_OFF): + ] == _hass_to_hue_state(entity): # We only want to use the cache if the actual state of the entity # is in sync so that it can be detected as an error by Alexa. cached_state = entry_state @@ -676,7 +678,7 @@ def get_entity_state_dict(config: Config, entity: State) -> dict[str, Any]: @lru_cache(maxsize=512) def _build_entity_state_dict(entity: State) -> dict[str, Any]: """Build a state dict for an entity.""" - is_on = entity.state != STATE_OFF + is_on = _hass_to_hue_state(entity) data: dict[str, Any] = { STATE_ON: is_on, STATE_BRIGHTNESS: None, @@ -891,6 +893,11 @@ def hass_to_hue_brightness(value: int) -> int: return max(1, round((value / 255) * HUE_API_STATE_BRI_MAX)) +def _hass_to_hue_state(entity: State) -> bool: + """Convert hass entity states to simple True/False on/off state for Hue.""" + return entity.state != _OFF_STATES.get(entity.domain, STATE_OFF) + + async def wait_for_state_change_or_timeout( hass: core.HomeAssistant, entity_id: str, timeout: float ) -> None: diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 3febc42730b..167562578f2 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -1019,6 +1019,12 @@ async def test_set_position_cover(hass_hue, hue_client) -> None: cover_test = hass_hue.states.get(cover_id) assert cover_test.state == "closed" + cover_json = await perform_get_light_state( + hue_client, "cover.living_room_window", HTTPStatus.OK + ) + assert cover_json["state"][HUE_API_STATE_ON] is False + assert cover_json["state"][HUE_API_STATE_BRI] == 1 + level = 20 brightness = round(level / 100 * 254) @@ -1095,6 +1101,7 @@ async def test_put_light_state_fan(hass_hue, hue_client) -> None: fan_json = await perform_get_light_state( hue_client, "fan.living_room_fan", HTTPStatus.OK ) + assert fan_json["state"][HUE_API_STATE_ON] is True assert round(fan_json["state"][HUE_API_STATE_BRI] * 100 / 254) == 33 await perform_put_light_state( @@ -1112,6 +1119,7 @@ async def test_put_light_state_fan(hass_hue, hue_client) -> None: fan_json = await perform_get_light_state( hue_client, "fan.living_room_fan", HTTPStatus.OK ) + assert fan_json["state"][HUE_API_STATE_ON] is True assert ( round(fan_json["state"][HUE_API_STATE_BRI] * 100 / 254) == 66 ) # small rounding error in inverse operation @@ -1132,8 +1140,27 @@ async def test_put_light_state_fan(hass_hue, hue_client) -> None: fan_json = await perform_get_light_state( hue_client, "fan.living_room_fan", HTTPStatus.OK ) + assert fan_json["state"][HUE_API_STATE_ON] is True assert round(fan_json["state"][HUE_API_STATE_BRI] * 100 / 254) == 100 + await perform_put_light_state( + hass_hue, + hue_client, + "fan.living_room_fan", + False, + brightness=0, + ) + assert ( + hass_hue.states.get("fan.living_room_fan").attributes[fan.ATTR_PERCENTAGE] == 0 + ) + with patch.object(hue_api, "STATE_CACHED_TIMEOUT", 0.000001): + await asyncio.sleep(0.000001) + fan_json = await perform_get_light_state( + hue_client, "fan.living_room_fan", HTTPStatus.OK + ) + assert fan_json["state"][HUE_API_STATE_ON] is False + assert fan_json["state"][HUE_API_STATE_BRI] == 1 + async def test_put_with_form_urlencoded_content_type(hass_hue, hue_client) -> None: """Test the form with urlencoded content."""