From 669c89e8c0c422d550f0ebaf5fb6bb37c8e57317 Mon Sep 17 00:00:00 2001 From: Jc2k Date: Sat, 11 Jan 2020 00:33:48 +0000 Subject: [PATCH] Fix HomeKit with entity registry restoration where supported_features is a non-None falsey (#30657) * Fix homekit with #30094 * Fix test --- homeassistant/helpers/entity_registry.py | 6 +- tests/components/homekit/test_type_covers.py | 73 +++++++++++++++ tests/components/homekit/test_type_fans.py | 40 ++++++++ tests/components/homekit/test_type_lights.py | 36 ++++++++ .../homekit/test_type_media_players.py | 56 ++++++++++++ .../homekit/test_type_thermostats.py | 91 +++++++++++++++++++ tests/helpers/test_entity_registry.py | 2 +- 7 files changed, 300 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/entity_registry.py b/homeassistant/helpers/entity_registry.py index a8a7fdab2c8..acb155ae594 100644 --- a/homeassistant/helpers/entity_registry.py +++ b/homeassistant/helpers/entity_registry.py @@ -502,13 +502,13 @@ def async_setup_entity_restore( attrs: Dict[str, Any] = {ATTR_RESTORED: True} - if entry.capabilities: + if entry.capabilities is not None: attrs.update(entry.capabilities) - if entry.supported_features: + if entry.supported_features is not None: attrs[ATTR_SUPPORTED_FEATURES] = entry.supported_features - if entry.device_class: + if entry.device_class is not None: attrs[ATTR_DEVICE_CLASS] = entry.device_class states.async_set(entry.entity_id, STATE_UNAVAILABLE, attrs) diff --git a/tests/components/homekit/test_type_covers.py b/tests/components/homekit/test_type_covers.py index 7bf92b28de2..fb73c132e30 100644 --- a/tests/components/homekit/test_type_covers.py +++ b/tests/components/homekit/test_type_covers.py @@ -13,6 +13,7 @@ from homeassistant.components.homekit.const import ATTR_VALUE from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, + EVENT_HOMEASSISTANT_START, STATE_CLOSED, STATE_CLOSING, STATE_OPEN, @@ -20,6 +21,8 @@ from homeassistant.const import ( STATE_UNAVAILABLE, STATE_UNKNOWN, ) +from homeassistant.core import CoreState +from homeassistant.helpers import entity_registry from tests.common import async_mock_service from tests.components.homekit.common import patch_debounce @@ -308,3 +311,73 @@ async def test_window_open_close_stop(hass, hk_driver, cls, events): assert acc.char_position_state.value == 2 assert len(events) == 3 assert events[-1].data[ATTR_VALUE] is None + + +async def test_window_basic_restore(hass, hk_driver, cls, events): + """Test setting up an entity from state in the event registry.""" + hass.state = CoreState.not_running + + registry = await entity_registry.async_get_registry(hass) + + registry.async_get_or_create( + "cover", "generic", "1234", suggested_object_id="simple", + ) + registry.async_get_or_create( + "cover", + "generic", + "9012", + suggested_object_id="all_info_set", + capabilities={}, + supported_features=SUPPORT_STOP, + device_class="mock-device-class", + ) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) + await hass.async_block_till_done() + + acc = cls.window_basic(hass, hk_driver, "Cover", "cover.simple", 2, None) + assert acc.category == 14 + assert acc.char_current_position is not None + assert acc.char_target_position is not None + assert acc.char_position_state is not None + + acc = cls.window_basic(hass, hk_driver, "Cover", "cover.all_info_set", 2, None) + assert acc.category == 14 + assert acc.char_current_position is not None + assert acc.char_target_position is not None + assert acc.char_position_state is not None + + +async def test_window_restore(hass, hk_driver, cls, events): + """Test setting up an entity from state in the event registry.""" + hass.state = CoreState.not_running + + registry = await entity_registry.async_get_registry(hass) + + registry.async_get_or_create( + "cover", "generic", "1234", suggested_object_id="simple", + ) + registry.async_get_or_create( + "cover", + "generic", + "9012", + suggested_object_id="all_info_set", + capabilities={}, + supported_features=SUPPORT_STOP, + device_class="mock-device-class", + ) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) + await hass.async_block_till_done() + + acc = cls.window(hass, hk_driver, "Cover", "cover.simple", 2, None) + assert acc.category == 14 + assert acc.char_current_position is not None + assert acc.char_target_position is not None + assert acc.char_position_state is not None + + acc = cls.window(hass, hk_driver, "Cover", "cover.all_info_set", 2, None) + assert acc.category == 14 + assert acc.char_current_position is not None + assert acc.char_target_position is not None + assert acc.char_position_state is not None diff --git a/tests/components/homekit/test_type_fans.py b/tests/components/homekit/test_type_fans.py index 5631791d7a2..9bcca3cc452 100644 --- a/tests/components/homekit/test_type_fans.py +++ b/tests/components/homekit/test_type_fans.py @@ -24,10 +24,13 @@ from homeassistant.components.homekit.util import HomeKitSpeedMapping from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, + EVENT_HOMEASSISTANT_START, STATE_OFF, STATE_ON, STATE_UNKNOWN, ) +from homeassistant.core import CoreState +from homeassistant.helpers import entity_registry from tests.common import async_mock_service from tests.components.homekit.common import patch_debounce @@ -226,3 +229,40 @@ async def test_fan_speed(hass, hk_driver, cls, events): assert call_set_speed[0].data[ATTR_SPEED] == "ludicrous" assert len(events) == 1 assert events[-1].data[ATTR_VALUE] == "ludicrous" + + +async def test_fan_restore(hass, hk_driver, cls, events): + """Test setting up an entity from state in the event registry.""" + hass.state = CoreState.not_running + + registry = await entity_registry.async_get_registry(hass) + + registry.async_get_or_create( + "fan", "generic", "1234", suggested_object_id="simple", + ) + registry.async_get_or_create( + "fan", + "generic", + "9012", + suggested_object_id="all_info_set", + capabilities={"speed_list": ["off", "low", "medium", "high"]}, + supported_features=SUPPORT_SET_SPEED | SUPPORT_OSCILLATE | SUPPORT_DIRECTION, + device_class="mock-device-class", + ) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) + await hass.async_block_till_done() + + acc = cls.fan(hass, hk_driver, "Fan", "fan.simple", 2, None) + assert acc.category == 3 + assert acc.char_active is not None + assert acc.char_direction is None + assert acc.char_speed is None + assert acc.char_swing is None + + acc = cls.fan(hass, hk_driver, "Fan", "fan.all_info_set", 2, None) + assert acc.category == 3 + assert acc.char_active is not None + assert acc.char_direction is not None + assert acc.char_speed is not None + assert acc.char_swing is not None diff --git a/tests/components/homekit/test_type_lights.py b/tests/components/homekit/test_type_lights.py index 510cfa4f666..c1811a2e2fc 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -17,10 +17,13 @@ from homeassistant.components.light import ( from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, + EVENT_HOMEASSISTANT_START, STATE_OFF, STATE_ON, STATE_UNKNOWN, ) +from homeassistant.core import CoreState +from homeassistant.helpers import entity_registry from tests.common import async_mock_service from tests.components.homekit.common import patch_debounce @@ -205,3 +208,36 @@ async def test_light_rgb_color(hass, hk_driver, cls, events): assert call_turn_on[0].data[ATTR_HS_COLOR] == (145, 75) assert len(events) == 1 assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)" + + +async def test_light_restore(hass, hk_driver, cls, events): + """Test setting up an entity from state in the event registry.""" + hass.state = CoreState.not_running + + registry = await entity_registry.async_get_registry(hass) + + registry.async_get_or_create( + "light", "hue", "1234", suggested_object_id="simple", + ) + registry.async_get_or_create( + "light", + "hue", + "9012", + suggested_object_id="all_info_set", + capabilities={"max": 100}, + supported_features=5, + device_class="mock-device-class", + ) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) + await hass.async_block_till_done() + + acc = cls.light(hass, hk_driver, "Light", "light.simple", 2, None) + assert acc.category == 5 # Lightbulb + assert acc.chars == [] + assert acc.char_on.value == 0 + + acc = cls.light(hass, hk_driver, "Light", "light.all_info_set", 2, None) + assert acc.category == 5 # Lightbulb + assert acc.chars == ["Brightness"] + assert acc.char_on.value == 0 diff --git a/tests/components/homekit/test_type_media_players.py b/tests/components/homekit/test_type_media_players.py index aa007b4d04c..366617ee988 100644 --- a/tests/components/homekit/test_type_media_players.py +++ b/tests/components/homekit/test_type_media_players.py @@ -24,12 +24,15 @@ from homeassistant.const import ( ATTR_DEVICE_CLASS, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, + EVENT_HOMEASSISTANT_START, STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING, ) +from homeassistant.core import CoreState +from homeassistant.helpers import entity_registry from tests.common import async_mock_service @@ -336,3 +339,56 @@ async def test_media_player_television_basic(hass, hk_driver, events, caplog): assert acc.char_active.value == 1 assert not caplog.messages or "Error" not in caplog.messages[-1] + + +async def test_tv_restore(hass, hk_driver, events): + """Test setting up an entity from state in the event registry.""" + hass.state = CoreState.not_running + + registry = await entity_registry.async_get_registry(hass) + + registry.async_get_or_create( + "media_player", + "generic", + "1234", + suggested_object_id="simple", + device_class=DEVICE_CLASS_TV, + ) + registry.async_get_or_create( + "media_player", + "generic", + "9012", + suggested_object_id="all_info_set", + capabilities={ + ATTR_INPUT_SOURCE_LIST: ["HDMI 1", "HDMI 2", "HDMI 3", "HDMI 4"], + }, + supported_features=3469, + device_class=DEVICE_CLASS_TV, + ) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) + await hass.async_block_till_done() + + acc = TelevisionMediaPlayer( + hass, hk_driver, "MediaPlayer", "media_player.simple", 2, None + ) + assert acc.category == 31 + assert acc.chars_tv == [] + assert acc.chars_speaker == [] + assert acc.support_select_source is False + assert not hasattr(acc, "char_input_source") + + acc = TelevisionMediaPlayer( + hass, hk_driver, "MediaPlayer", "media_player.all_info_set", 2, None + ) + assert acc.category == 31 + assert acc.chars_tv == ["RemoteKey"] + assert acc.chars_speaker == [ + "Name", + "Active", + "VolumeControlType", + "VolumeSelector", + "Volume", + ] + assert acc.support_select_source is True + assert acc.char_input_source is not None diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index 174b72f780a..c96cfdae602 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -41,8 +41,11 @@ from homeassistant.const import ( ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, CONF_TEMPERATURE_UNIT, + EVENT_HOMEASSISTANT_START, TEMP_FAHRENHEIT, ) +from homeassistant.core import CoreState +from homeassistant.helpers import entity_registry from tests.common import async_mock_service from tests.components.homekit.common import patch_debounce @@ -517,6 +520,51 @@ async def test_thermostat_temperature_step_whole(hass, hk_driver, cls): assert acc.char_target_temp.properties[PROP_MIN_STEP] == 1.0 +async def test_thermostat_restore(hass, hk_driver, cls, events): + """Test setting up an entity from state in the event registry.""" + hass.state = CoreState.not_running + + registry = await entity_registry.async_get_registry(hass) + + registry.async_get_or_create( + "climate", "generic", "1234", suggested_object_id="simple", + ) + registry.async_get_or_create( + "climate", + "generic", + "9012", + suggested_object_id="all_info_set", + capabilities={ + ATTR_MIN_TEMP: 60, + ATTR_MAX_TEMP: 70, + ATTR_HVAC_MODES: [HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF], + }, + supported_features=0, + device_class="mock-device-class", + ) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) + await hass.async_block_till_done() + + acc = cls.thermostat(hass, hk_driver, "Climate", "climate.simple", 2, None) + assert acc.category == 9 + assert acc.get_temperature_range() == (7, 35) + assert set(acc.char_target_heat_cool.properties["ValidValues"].keys()) == { + "cool", + "heat", + "heat_cool", + "off", + } + + acc = cls.thermostat(hass, hk_driver, "Climate", "climate.all_info_set", 2, None) + assert acc.category == 9 + assert acc.get_temperature_range() == (60.0, 70.0) + assert set(acc.char_target_heat_cool.properties["ValidValues"].keys()) == { + "heat_cool", + "off", + } + + async def test_thermostat_hvac_modes(hass, hk_driver, cls): """Test if unsupported HVAC modes are deactivated in HomeKit.""" entity_id = "climate.test" @@ -671,3 +719,46 @@ async def test_water_heater_get_temperature_range(hass, hk_driver, cls): ) await hass.async_block_till_done() assert acc.get_temperature_range() == (15.5, 21.0) + + +async def test_water_heater_restore(hass, hk_driver, cls, events): + """Test setting up an entity from state in the event registry.""" + hass.state = CoreState.not_running + + registry = await entity_registry.async_get_registry(hass) + + registry.async_get_or_create( + "water_heater", "generic", "1234", suggested_object_id="simple", + ) + registry.async_get_or_create( + "water_heater", + "generic", + "9012", + suggested_object_id="all_info_set", + capabilities={ATTR_MIN_TEMP: 60, ATTR_MAX_TEMP: 70}, + supported_features=0, + device_class="mock-device-class", + ) + + hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {}) + await hass.async_block_till_done() + + acc = cls.thermostat(hass, hk_driver, "WaterHeater", "water_heater.simple", 2, None) + assert acc.category == 9 + assert acc.get_temperature_range() == (7, 35) + assert set(acc.char_current_heat_cool.properties["ValidValues"].keys()) == { + "Cool", + "Heat", + "Off", + } + + acc = cls.thermostat( + hass, hk_driver, "WaterHeater", "water_heater.all_info_set", 2, None + ) + assert acc.category == 9 + assert acc.get_temperature_range() == (60.0, 70.0) + assert set(acc.char_current_heat_cool.properties["ValidValues"].keys()) == { + "Cool", + "Heat", + "Off", + } diff --git a/tests/helpers/test_entity_registry.py b/tests/helpers/test_entity_registry.py index 7f45ff0d174..e532d99f333 100644 --- a/tests/helpers/test_entity_registry.py +++ b/tests/helpers/test_entity_registry.py @@ -511,7 +511,7 @@ async def test_restore_states(hass): simple = hass.states.get("light.simple") assert simple is not None assert simple.state == STATE_UNAVAILABLE - assert simple.attributes == {"restored": True} + assert simple.attributes == {"restored": True, "supported_features": 0} disabled = hass.states.get("light.disabled") assert disabled is None