Improve robustness of linking homekit yaml to config entries (#79386)
parent
8ff12eacd4
commit
ec8901b9af
|
@ -193,14 +193,21 @@ def _async_all_homekit_instances(hass: HomeAssistant) -> list[HomeKit]:
|
|||
]
|
||||
|
||||
|
||||
def _async_get_entries_by_name(
|
||||
def _async_get_imported_entries_indices(
|
||||
current_entries: list[ConfigEntry],
|
||||
) -> dict[str, ConfigEntry]:
|
||||
"""Return a dict of the entries by name."""
|
||||
) -> tuple[dict[str, ConfigEntry], dict[int, ConfigEntry]]:
|
||||
"""Return a dicts of the entries by name and port."""
|
||||
|
||||
# For backwards compat, its possible the first bridge is using the default
|
||||
# name.
|
||||
return {entry.data.get(CONF_NAME, BRIDGE_NAME): entry for entry in current_entries}
|
||||
entries_by_name: dict[str, ConfigEntry] = {}
|
||||
entries_by_port: dict[int, ConfigEntry] = {}
|
||||
for entry in current_entries:
|
||||
if entry.source != SOURCE_IMPORT:
|
||||
continue
|
||||
entries_by_name[entry.data.get(CONF_NAME, BRIDGE_NAME)] = entry
|
||||
entries_by_port[entry.data.get(CONF_PORT, DEFAULT_PORT)] = entry
|
||||
return entries_by_name, entries_by_port
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
@ -218,10 +225,14 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
return True
|
||||
|
||||
current_entries = hass.config_entries.async_entries(DOMAIN)
|
||||
entries_by_name = _async_get_entries_by_name(current_entries)
|
||||
entries_by_name, entries_by_port = _async_get_imported_entries_indices(
|
||||
current_entries
|
||||
)
|
||||
|
||||
for index, conf in enumerate(config[DOMAIN]):
|
||||
if _async_update_config_entry_if_from_yaml(hass, entries_by_name, conf):
|
||||
if _async_update_config_entry_from_yaml(
|
||||
hass, entries_by_name, entries_by_port, conf
|
||||
):
|
||||
continue
|
||||
|
||||
conf[CONF_ENTRY_INDEX] = index
|
||||
|
@ -237,8 +248,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||
|
||||
|
||||
@callback
|
||||
def _async_update_config_entry_if_from_yaml(
|
||||
hass: HomeAssistant, entries_by_name: dict[str, ConfigEntry], conf: ConfigType
|
||||
def _async_update_config_entry_from_yaml(
|
||||
hass: HomeAssistant,
|
||||
entries_by_name: dict[str, ConfigEntry],
|
||||
entries_by_port: dict[int, ConfigEntry],
|
||||
conf: ConfigType,
|
||||
) -> bool:
|
||||
"""Update a config entry with the latest yaml.
|
||||
|
||||
|
@ -246,27 +260,24 @@ def _async_update_config_entry_if_from_yaml(
|
|||
|
||||
Returns False if there is no matching config entry
|
||||
"""
|
||||
bridge_name = conf[CONF_NAME]
|
||||
|
||||
if (
|
||||
bridge_name in entries_by_name
|
||||
and entries_by_name[bridge_name].source == SOURCE_IMPORT
|
||||
if not (
|
||||
matching_entry := entries_by_name.get(conf.get(CONF_NAME, BRIDGE_NAME))
|
||||
or entries_by_port.get(conf.get(CONF_PORT, DEFAULT_PORT))
|
||||
):
|
||||
entry = entries_by_name[bridge_name]
|
||||
# If they alter the yaml config we import the changes
|
||||
# since there currently is no practical way to support
|
||||
# all the options in the UI at this time.
|
||||
data = conf.copy()
|
||||
options = {}
|
||||
for key in CONFIG_OPTIONS:
|
||||
if key in data:
|
||||
options[key] = data[key]
|
||||
del data[key]
|
||||
return False
|
||||
|
||||
hass.config_entries.async_update_entry(entry, data=data, options=options)
|
||||
return True
|
||||
# If they alter the yaml config we import the changes
|
||||
# since there currently is no practical way to support
|
||||
# all the options in the UI at this time.
|
||||
data = conf.copy()
|
||||
options = {}
|
||||
for key in CONFIG_OPTIONS:
|
||||
if key in data:
|
||||
options[key] = data[key]
|
||||
del data[key]
|
||||
|
||||
return False
|
||||
hass.config_entries.async_update_entry(matching_entry, data=data, options=options)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
@ -451,10 +462,14 @@ def _async_register_events_and_services(hass: HomeAssistant) -> None:
|
|||
return
|
||||
|
||||
current_entries = hass.config_entries.async_entries(DOMAIN)
|
||||
entries_by_name = _async_get_entries_by_name(current_entries)
|
||||
entries_by_name, entries_by_port = _async_get_imported_entries_indices(
|
||||
current_entries
|
||||
)
|
||||
|
||||
for conf in config[DOMAIN]:
|
||||
_async_update_config_entry_if_from_yaml(hass, entries_by_name, conf)
|
||||
_async_update_config_entry_from_yaml(
|
||||
hass, entries_by_name, entries_by_port, conf
|
||||
)
|
||||
|
||||
reload_tasks = [
|
||||
hass.config_entries.async_reload(entry.entry_id)
|
||||
|
|
|
@ -35,7 +35,7 @@ from homeassistant.components.homekit.const import (
|
|||
from homeassistant.components.homekit.type_triggers import DeviceTriggerAccessory
|
||||
from homeassistant.components.homekit.util import get_persist_fullpath_for_entry_id
|
||||
from homeassistant.components.sensor import SensorDeviceClass
|
||||
from homeassistant.config_entries import SOURCE_IMPORT
|
||||
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_ZEROCONF
|
||||
from homeassistant.const import (
|
||||
ATTR_DEVICE_CLASS,
|
||||
ATTR_DEVICE_ID,
|
||||
|
@ -1394,6 +1394,82 @@ async def test_yaml_updates_update_config_entry_for_name(hass, mock_async_zeroco
|
|||
mock_homekit().async_start.assert_called()
|
||||
|
||||
|
||||
async def test_yaml_can_link_with_default_name(hass, mock_async_zeroconf):
|
||||
"""Test async_setup with imported config linked by default name."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
source=SOURCE_IMPORT,
|
||||
data={},
|
||||
options={},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, patch(
|
||||
"homeassistant.components.network.async_get_source_ip", return_value="1.2.3.4"
|
||||
):
|
||||
mock_homekit.return_value = homekit = Mock()
|
||||
type(homekit).async_start = AsyncMock()
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"homekit",
|
||||
{"homekit": {"entity_config": {"camera.back_camera": {"stream_count": 3}}}},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_homekit.reset_mock()
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
assert entry.options["entity_config"]["camera.back_camera"]["stream_count"] == 3
|
||||
|
||||
|
||||
async def test_yaml_can_link_with_port(hass, mock_async_zeroconf):
|
||||
"""Test async_setup with imported config linked by port."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
source=SOURCE_IMPORT,
|
||||
data={"name": "random", "port": 12345},
|
||||
options={},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
entry2 = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
source=SOURCE_IMPORT,
|
||||
data={"name": "random", "port": 12346},
|
||||
options={},
|
||||
)
|
||||
entry2.add_to_hass(hass)
|
||||
entry3 = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
source=SOURCE_ZEROCONF,
|
||||
data={"name": "random", "port": 12347},
|
||||
options={},
|
||||
)
|
||||
entry3.add_to_hass(hass)
|
||||
with patch(f"{PATH_HOMEKIT}.HomeKit") as mock_homekit, patch(
|
||||
"homeassistant.components.network.async_get_source_ip", return_value="1.2.3.4"
|
||||
):
|
||||
mock_homekit.return_value = homekit = Mock()
|
||||
type(homekit).async_start = AsyncMock()
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"homekit",
|
||||
{
|
||||
"homekit": {
|
||||
"port": 12345,
|
||||
"entity_config": {"camera.back_camera": {"stream_count": 3}},
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_homekit.reset_mock()
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
await hass.async_block_till_done()
|
||||
assert entry.options["entity_config"]["camera.back_camera"]["stream_count"] == 3
|
||||
assert entry2.options == {}
|
||||
assert entry3.options == {}
|
||||
|
||||
|
||||
async def test_homekit_uses_system_zeroconf(hass, hk_driver, mock_async_zeroconf):
|
||||
"""Test HomeKit uses system zeroconf."""
|
||||
entry = MockConfigEntry(
|
||||
|
|
Loading…
Reference in New Issue