diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index c863da14a3a..444f64b6f38 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -9,8 +9,7 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity import Entity -# We need an import from .config_flow, without it .config_flow is never loaded. -from .config_flow import HomekitControllerFlowHandler # noqa: F401 +from .config_flow import normalize_hkid from .connection import HKDevice, get_accessory_information from .const import CONTROLLER, DOMAIN, ENTITY_MAP, KNOWN_DEVICES from .storage import EntityMapStorage @@ -181,6 +180,12 @@ async def async_setup_entry(hass, entry): conn = HKDevice(hass, entry, entry.data) hass.data[KNOWN_DEVICES][conn.unique_id] = conn + # For backwards compat + if entry.unique_id is None: + hass.config_entries.async_update_entry( + entry, unique_id=normalize_hkid(conn.unique_id) + ) + if not await conn.async_setup(): del hass.data[KNOWN_DEVICES][conn.unique_id] raise ConfigEntryNotReady diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index f4eb1190727..6cc724e9fe5 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -46,6 +46,11 @@ def load_old_pairings(hass): return old_pairings +def normalize_hkid(hkid): + """Normalize a hkid so that it is safe to compare with other normalized hkids.""" + return hkid.lower() + + @callback def find_existing_host(hass, serial): """Return a set of the configured hosts.""" @@ -77,6 +82,9 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): key = user_input["device"] self.hkid = self.devices[key]["id"] self.model = self.devices[key]["md"] + await self.async_set_unique_id( + normalize_hkid(self.hkid), raise_on_progress=False + ) return await self.async_step_pair() all_hosts = await self.hass.async_add_executor_job(self.controller.discover, 5) @@ -120,18 +128,6 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): status_flags = int(properties["sf"]) paired = not status_flags & 0x01 - _LOGGER.debug("Discovered device %s (%s - %s)", name, model, hkid) - - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - self.context["hkid"] = hkid - self.context["title_placeholders"] = {"name": name} - - # If multiple HomekitControllerFlowHandler end up getting created - # for the same accessory dont let duplicates hang around - active_flows = self._async_in_progress() - if any(hkid == flow["context"]["hkid"] for flow in active_flows): - return self.async_abort(reason="already_in_progress") - # The configuration number increases every time the characteristic map # needs updating. Some devices use a slightly off-spec name so handle # both cases. @@ -143,21 +139,27 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow): ) config_num = None - if paired: - if hkid in self.hass.data.get(KNOWN_DEVICES, {}): - # The device is already paired and known to us - # According to spec we should monitor c# (config_num) for - # changes. If it changes, we check for new entities - conn = self.hass.data[KNOWN_DEVICES][hkid] - if conn.config_num != config_num: - _LOGGER.debug( - "HomeKit info %s: c# incremented, refreshing entities", hkid - ) - self.hass.async_create_task( - conn.async_refresh_entity_map(config_num) - ) - return self.async_abort(reason="already_configured") + # If the device is already paired and known to us we should monitor c# + # (config_num) for changes. If it changes, we check for new entities + if paired and hkid in self.hass.data.get(KNOWN_DEVICES, {}): + conn = self.hass.data[KNOWN_DEVICES][hkid] + if conn.config_num != config_num: + _LOGGER.debug( + "HomeKit info %s: c# incremented, refreshing entities", hkid + ) + self.hass.async_create_task(conn.async_refresh_entity_map(config_num)) + return self.async_abort(reason="already_configured") + _LOGGER.debug("Discovered device %s (%s - %s)", name, model, hkid) + + await self.async_set_unique_id(normalize_hkid(hkid)) + self._abort_if_unique_id_configured() + + # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 + self.context["hkid"] = hkid + self.context["title_placeholders"] = {"name": name} + + if paired: old_pairings = await self.hass.async_add_executor_job( load_old_pairings, self.hass ) diff --git a/tests/components/homekit_controller/test_config_flow.py b/tests/components/homekit_controller/test_config_flow.py index 22b486e1d51..4733581f136 100644 --- a/tests/components/homekit_controller/test_config_flow.py +++ b/tests/components/homekit_controller/test_config_flow.py @@ -74,6 +74,7 @@ async def test_discovery_works(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device enters pairing mode and displays code @@ -122,6 +123,7 @@ async def test_discovery_works_upper_case(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device enters pairing mode and displays code @@ -169,6 +171,7 @@ async def test_discovery_works_missing_csharp(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device enters pairing mode and displays code @@ -234,6 +237,7 @@ async def test_pair_already_paired_1(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -259,6 +263,7 @@ async def test_discovery_ignored_model(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -286,6 +291,7 @@ async def test_discovery_invalid_config_entry(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # Discovery of a HKID that is in a pairable state but for which there is @@ -315,10 +321,7 @@ async def test_discovery_already_configured(hass): result = await flow.async_step_zeroconf(discovery_info) assert result["type"] == "abort" assert result["reason"] == "already_configured" - assert flow.context == { - "hkid": "00:00:00:00:00:00", - "title_placeholders": {"name": "TestDevice"}, - } + assert flow.context == {} assert conn.async_config_num_changed.call_count == 0 @@ -343,10 +346,7 @@ async def test_discovery_already_configured_config_change(hass): result = await flow.async_step_zeroconf(discovery_info) assert result["type"] == "abort" assert result["reason"] == "already_configured" - assert flow.context == { - "hkid": "00:00:00:00:00:00", - "title_placeholders": {"name": "TestDevice"}, - } + assert flow.context == {} assert conn.async_refresh_entity_map.call_args == mock.call(2) @@ -369,6 +369,7 @@ async def test_pair_unable_to_pair(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device enters pairing mode and displays code @@ -402,6 +403,7 @@ async def test_pair_abort_errors_on_start(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device refuses to enter pairing mode @@ -414,6 +416,7 @@ async def test_pair_abort_errors_on_start(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -436,6 +439,7 @@ async def test_pair_form_errors_on_start(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device refuses to enter pairing mode @@ -448,6 +452,7 @@ async def test_pair_form_errors_on_start(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -470,6 +475,7 @@ async def test_pair_abort_errors_on_finish(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device enters pairing mode and displays code @@ -486,6 +492,7 @@ async def test_pair_abort_errors_on_finish(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -508,6 +515,7 @@ async def test_pair_form_errors_on_finish(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } # User initiates pairing - device enters pairing mode and displays code @@ -524,6 +532,7 @@ async def test_pair_form_errors_on_finish(hass, exception, expected): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -712,6 +721,7 @@ async def test_parse_new_homekit_json(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -763,6 +773,7 @@ async def test_parse_old_homekit_json(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", } @@ -823,4 +834,5 @@ async def test_parse_overlapping_homekit_json(hass): assert flow.context == { "hkid": "00:00:00:00:00:00", "title_placeholders": {"name": "TestDevice"}, + "unique_id": "00:00:00:00:00:00", }