Exclude hidden entities from homekit (#68552)
This commit is contained in:
parent
49bc572d6d
commit
1c57e65cea
4 changed files with 144 additions and 8 deletions
|
@ -667,8 +667,8 @@ class HomeKit:
|
||||||
if ent_reg_ent := ent_reg.async_get(entity_id):
|
if ent_reg_ent := ent_reg.async_get(entity_id):
|
||||||
if (
|
if (
|
||||||
ent_reg_ent.entity_category is not None
|
ent_reg_ent.entity_category is not None
|
||||||
and not self._filter.explicitly_included(entity_id)
|
or ent_reg_ent.hidden_by is not None
|
||||||
):
|
) and not self._filter.explicitly_included(entity_id):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
await self._async_set_device_info_attributes(
|
await self._async_set_device_info_attributes(
|
||||||
|
|
|
@ -538,16 +538,19 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||||
if not entities:
|
if not entities:
|
||||||
entities = entity_filter.get(CONF_EXCLUDE_ENTITIES, [])
|
entities = entity_filter.get(CONF_EXCLUDE_ENTITIES, [])
|
||||||
ent_reg = entity_registry.async_get(self.hass)
|
ent_reg = entity_registry.async_get(self.hass)
|
||||||
entity_cat_entities = set()
|
excluded_entities = set()
|
||||||
for entity_id in all_supported_entities:
|
for entity_id in all_supported_entities:
|
||||||
if ent_reg_ent := ent_reg.async_get(entity_id):
|
if ent_reg_ent := ent_reg.async_get(entity_id):
|
||||||
if ent_reg_ent.entity_category is not None:
|
if (
|
||||||
entity_cat_entities.add(entity_id)
|
ent_reg_ent.entity_category is not None
|
||||||
|
or ent_reg_ent.hidden_by is not None
|
||||||
|
):
|
||||||
|
excluded_entities.add(entity_id)
|
||||||
# Remove entity category entities since we will exclude them anyways
|
# Remove entity category entities since we will exclude them anyways
|
||||||
all_supported_entities = {
|
all_supported_entities = {
|
||||||
k: v
|
k: v
|
||||||
for k, v in all_supported_entities.items()
|
for k, v in all_supported_entities.items()
|
||||||
if k not in entity_cat_entities
|
if k not in excluded_entities
|
||||||
}
|
}
|
||||||
# Strip out entities that no longer exist to prevent error in the UI
|
# Strip out entities that no longer exist to prevent error in the UI
|
||||||
default_value = [
|
default_value = [
|
||||||
|
|
|
@ -13,7 +13,7 @@ from homeassistant.components.homekit.const import (
|
||||||
from homeassistant.config_entries import SOURCE_IGNORE, SOURCE_IMPORT
|
from homeassistant.config_entries import SOURCE_IGNORE, SOURCE_IMPORT
|
||||||
from homeassistant.const import CONF_NAME, CONF_PORT
|
from homeassistant.const import CONF_NAME, CONF_PORT
|
||||||
from homeassistant.helpers.entity import EntityCategory
|
from homeassistant.helpers.entity import EntityCategory
|
||||||
from homeassistant.helpers.entity_registry import RegistryEntry
|
from homeassistant.helpers.entity_registry import RegistryEntry, RegistryEntryHider
|
||||||
from homeassistant.helpers.entityfilter import CONF_INCLUDE_DOMAINS
|
from homeassistant.helpers.entityfilter import CONF_INCLUDE_DOMAINS
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
@ -1403,3 +1403,81 @@ async def test_options_flow_exclude_mode_skips_category_entities(
|
||||||
"include_entities": [],
|
"include_entities": [],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@patch(f"{PATH_HOMEKIT}.async_port_is_available", return_value=True)
|
||||||
|
async def test_options_flow_exclude_mode_skips_hidden_entities(
|
||||||
|
port_mock, hass, mock_get_source_ip, hk_driver, mock_async_zeroconf, entity_reg
|
||||||
|
):
|
||||||
|
"""Ensure exclude mode does not offer hidden entities."""
|
||||||
|
config_entry = _mock_config_entry_with_options_populated()
|
||||||
|
await async_init_entry(hass, config_entry)
|
||||||
|
|
||||||
|
hass.states.async_set("media_player.tv", "off")
|
||||||
|
hass.states.async_set("media_player.sonos", "off")
|
||||||
|
hass.states.async_set("switch.other", "off")
|
||||||
|
|
||||||
|
sonos_hidden_switch: RegistryEntry = entity_reg.async_get_or_create(
|
||||||
|
"switch",
|
||||||
|
"sonos",
|
||||||
|
"config",
|
||||||
|
device_id="1234",
|
||||||
|
hidden_by=RegistryEntryHider.INTEGRATION,
|
||||||
|
)
|
||||||
|
hass.states.async_set(sonos_hidden_switch.entity_id, "off")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
result = await hass.config_entries.options.async_init(
|
||||||
|
config_entry.entry_id, context={"show_advanced_options": False}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "init"
|
||||||
|
assert result["data_schema"]({}) == {
|
||||||
|
"domains": [
|
||||||
|
"fan",
|
||||||
|
"humidifier",
|
||||||
|
"vacuum",
|
||||||
|
"media_player",
|
||||||
|
"climate",
|
||||||
|
"alarm_control_panel",
|
||||||
|
],
|
||||||
|
"mode": "bridge",
|
||||||
|
"include_exclude_mode": "exclude",
|
||||||
|
}
|
||||||
|
|
||||||
|
result2 = await hass.config_entries.options.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={
|
||||||
|
"domains": ["media_player", "switch"],
|
||||||
|
"mode": "bridge",
|
||||||
|
"include_exclude_mode": "exclude",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result2["step_id"] == "exclude"
|
||||||
|
assert _get_schema_default(result2["data_schema"].schema, "entities") == []
|
||||||
|
|
||||||
|
# sonos_hidden_switch.entity_id is a hidden entity
|
||||||
|
# so it should not be selectable since it will always be excluded
|
||||||
|
with pytest.raises(voluptuous.error.MultipleInvalid):
|
||||||
|
await hass.config_entries.options.async_configure(
|
||||||
|
result2["flow_id"],
|
||||||
|
user_input={"entities": [sonos_hidden_switch.entity_id]},
|
||||||
|
)
|
||||||
|
|
||||||
|
result4 = await hass.config_entries.options.async_configure(
|
||||||
|
result2["flow_id"],
|
||||||
|
user_input={"entities": ["media_player.tv", "switch.other"]},
|
||||||
|
)
|
||||||
|
assert result4["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert config_entry.options == {
|
||||||
|
"mode": "bridge",
|
||||||
|
"filter": {
|
||||||
|
"exclude_domains": [],
|
||||||
|
"exclude_entities": ["media_player.tv", "switch.other"],
|
||||||
|
"include_domains": ["media_player", "switch"],
|
||||||
|
"include_entities": [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ from homeassistant.const import (
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistantError, State
|
from homeassistant.core import HomeAssistantError, State
|
||||||
from homeassistant.helpers import device_registry
|
from homeassistant.helpers import device_registry, entity_registry as er
|
||||||
from homeassistant.helpers.entityfilter import (
|
from homeassistant.helpers.entityfilter import (
|
||||||
CONF_EXCLUDE_DOMAINS,
|
CONF_EXCLUDE_DOMAINS,
|
||||||
CONF_EXCLUDE_ENTITIES,
|
CONF_EXCLUDE_ENTITIES,
|
||||||
|
@ -485,6 +485,61 @@ async def test_homekit_entity_glob_filter_with_config_entities(
|
||||||
assert hass.states.get("select.keep") in filtered_states
|
assert hass.states.get("select.keep") in filtered_states
|
||||||
|
|
||||||
|
|
||||||
|
async def test_homekit_entity_glob_filter_with_hidden_entities(
|
||||||
|
hass, mock_async_zeroconf, entity_reg
|
||||||
|
):
|
||||||
|
"""Test the entity filter with hidden entities."""
|
||||||
|
entry = await async_init_integration(hass)
|
||||||
|
|
||||||
|
from homeassistant.helpers.entity_registry import RegistryEntry
|
||||||
|
|
||||||
|
select_config_entity: RegistryEntry = entity_reg.async_get_or_create(
|
||||||
|
"select",
|
||||||
|
"any",
|
||||||
|
"any",
|
||||||
|
device_id="1234",
|
||||||
|
hidden_by=er.RegistryEntryHider.INTEGRATION,
|
||||||
|
)
|
||||||
|
hass.states.async_set(select_config_entity.entity_id, "off")
|
||||||
|
|
||||||
|
switch_config_entity: RegistryEntry = entity_reg.async_get_or_create(
|
||||||
|
"switch",
|
||||||
|
"any",
|
||||||
|
"any",
|
||||||
|
device_id="1234",
|
||||||
|
hidden_by=er.RegistryEntryHider.INTEGRATION,
|
||||||
|
)
|
||||||
|
hass.states.async_set(switch_config_entity.entity_id, "off")
|
||||||
|
hass.states.async_set("select.keep", "open")
|
||||||
|
|
||||||
|
hass.states.async_set("cover.excluded_test", "open")
|
||||||
|
hass.states.async_set("light.included_test", "on")
|
||||||
|
|
||||||
|
entity_filter = generate_filter(
|
||||||
|
["select"],
|
||||||
|
["switch.test", switch_config_entity.entity_id],
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
["*.included_*"],
|
||||||
|
["*.excluded_*"],
|
||||||
|
)
|
||||||
|
homekit = _mock_homekit(hass, entry, HOMEKIT_MODE_BRIDGE, entity_filter)
|
||||||
|
|
||||||
|
homekit.bridge = Mock()
|
||||||
|
homekit.bridge.accessories = {}
|
||||||
|
|
||||||
|
filtered_states = await homekit.async_configure_accessories()
|
||||||
|
assert (
|
||||||
|
hass.states.get(switch_config_entity.entity_id) in filtered_states
|
||||||
|
) # explicitly included
|
||||||
|
assert (
|
||||||
|
hass.states.get(select_config_entity.entity_id) not in filtered_states
|
||||||
|
) # not explicted included and its a hidden entity
|
||||||
|
assert hass.states.get("cover.excluded_test") not in filtered_states
|
||||||
|
assert hass.states.get("light.included_test") in filtered_states
|
||||||
|
assert hass.states.get("select.keep") in filtered_states
|
||||||
|
|
||||||
|
|
||||||
async def test_homekit_start(hass, hk_driver, mock_async_zeroconf, device_reg):
|
async def test_homekit_start(hass, hk_driver, mock_async_zeroconf, device_reg):
|
||||||
"""Test HomeKit start method."""
|
"""Test HomeKit start method."""
|
||||||
entry = await async_init_integration(hass)
|
entry = await async_init_integration(hass)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue