diff --git a/homeassistant/components/aprilaire/__init__.py b/homeassistant/components/aprilaire/__init__.py index 181969e2f2e21b..761db30ec77c88 100644 --- a/homeassistant/components/aprilaire/__init__.py +++ b/homeassistant/components/aprilaire/__init__.py @@ -27,7 +27,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AprilaireConfigEntry) -> host = entry.data[CONF_HOST] port = entry.data[CONF_PORT] - coordinator = AprilaireCoordinator(hass, entry.unique_id, host, port) + coordinator = AprilaireCoordinator(hass, entry, host, port) await coordinator.start_listen() async def ready_callback(ready: bool) -> None: diff --git a/homeassistant/components/aprilaire/coordinator.py b/homeassistant/components/aprilaire/coordinator.py index 83dee0211484ed..77417270997140 100644 --- a/homeassistant/components/aprilaire/coordinator.py +++ b/homeassistant/components/aprilaire/coordinator.py @@ -30,14 +30,15 @@ class AprilaireCoordinator(BaseDataUpdateCoordinatorProtocol): def __init__( self, hass: HomeAssistant, - unique_id: str | None, + config_entry: AprilaireConfigEntry | None, host: str, port: int, ) -> None: """Initialize the coordinator.""" self.hass = hass - self.unique_id = unique_id + self.config_entry = config_entry + self.unique_id = config_entry.unique_id if config_entry else None self.data: dict[str, Any] = {} self._listeners: dict[CALLBACK_TYPE, tuple[CALLBACK_TYPE, object | None]] = {} @@ -88,13 +89,16 @@ def async_set_updated_data(self, data: Any) -> None: new_device_info = self.create_device_info(data) if ( - old_device_info is not None + self.config_entry is not None + and old_device_info is not None and new_device_info is not None and old_device_info != new_device_info ): device_registry = dr.async_get(self.hass) - device = device_registry.async_get_device(old_device_info["identifiers"]) + device = device_registry.async_get_device_by_identifier( + next(iter(old_device_info["identifiers"])), self.config_entry.entry_id + ) if device is not None: new_device_info.pop("identifiers", None) diff --git a/homeassistant/components/assist_satellite/manifest.json b/homeassistant/components/assist_satellite/manifest.json index 2f5797593a25e7..253036290fd9f0 100644 --- a/homeassistant/components/assist_satellite/manifest.json +++ b/homeassistant/components/assist_satellite/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/assist_satellite", "integration_type": "entity", "quality_scale": "internal", - "requirements": ["hassil==3.8.0"] + "requirements": ["hassil==3.10.0"] } diff --git a/homeassistant/components/conversation/manifest.json b/homeassistant/components/conversation/manifest.json index b5466dfb5f3ee6..70bf103437851c 100644 --- a/homeassistant/components/conversation/manifest.json +++ b/homeassistant/components/conversation/manifest.json @@ -6,5 +6,5 @@ "documentation": "https://www.home-assistant.io/integrations/conversation", "integration_type": "entity", "quality_scale": "internal", - "requirements": ["hassil==3.8.0", "home-assistant-intents==2026.6.24"] + "requirements": ["hassil==3.10.0", "home-assistant-intents==2026.6.24"] } diff --git a/homeassistant/components/infrared/manifest.json b/homeassistant/components/infrared/manifest.json index 1c4681d5d61333..9b6947a99e6255 100644 --- a/homeassistant/components/infrared/manifest.json +++ b/homeassistant/components/infrared/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/infrared", "integration_type": "entity", "quality_scale": "internal", - "requirements": ["infrared-protocols==8.1.0"] + "requirements": ["infrared-protocols==8.2.0"] } diff --git a/homeassistant/components/izone/manifest.json b/homeassistant/components/izone/manifest.json index 5b89df9f9b912f..81afafc3a08770 100644 --- a/homeassistant/components/izone/manifest.json +++ b/homeassistant/components/izone/manifest.json @@ -10,5 +10,5 @@ "integration_type": "hub", "iot_class": "local_polling", "loggers": ["pizone"], - "requirements": ["python-izone==1.3.6"] + "requirements": ["python-izone==1.3.7"] } diff --git a/homeassistant/components/template/__init__.py b/homeassistant/components/template/__init__.py index 40a4cb80dd2cfd..17f168fbabb4fe 100644 --- a/homeassistant/components/template/__init__.py +++ b/homeassistant/components/template/__init__.py @@ -97,7 +97,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass, helper_config_entry_id=entry.entry_id, source_device_id=entry.options.get(CONF_DEVICE_ID), - sweep_helper_devices=True, + remove_all_devices=True, ) for key in (CONF_MAX, CONF_MIN, CONF_STEP): diff --git a/homeassistant/helpers/device.py b/homeassistant/helpers/device.py index cb134de31da06a..e129eb7123e296 100644 --- a/homeassistant/helpers/device.py +++ b/homeassistant/helpers/device.py @@ -87,7 +87,7 @@ def async_remove_stale_devices_links_keep_entity_device( Deprecated, does nothing: a device belongs to a single config entry, so a helper no longer adds its config entry to the source device and there is no stale config entry link to remove. Call helper_integration.async_remove_helper_devices with - sweep_helper_devices=True to remove devices a helper created for previously selected + remove_all_devices=True to remove devices a helper created for previously selected source devices. """ report_usage( @@ -95,7 +95,7 @@ def async_remove_stale_devices_links_keep_entity_device( "and does nothing: a device belongs to a single config entry, so a helper no " "longer adds its config entry to the source device. Call " "homeassistant.helpers.helper_integration.async_remove_helper_devices with " - "sweep_helper_devices=True instead", + "remove_all_devices=True instead", core_behavior=ReportBehavior.LOG, breaks_in_ha_version="2027.8.0", ) @@ -112,7 +112,7 @@ def async_remove_stale_devices_links_keep_current_device( Deprecated, does nothing: a device belongs to a single config entry, so a helper no longer adds its config entry to another device and there is no stale config entry link to remove. Call helper_integration.async_remove_helper_devices with - sweep_helper_devices=True to remove devices a helper created for previously selected + remove_all_devices=True to remove devices a helper created for previously selected source devices. """ report_usage( @@ -120,7 +120,7 @@ def async_remove_stale_devices_links_keep_current_device( "and does nothing: a device belongs to a single config entry, so a helper no " "longer adds its config entry to another device. Call " "homeassistant.helpers.helper_integration.async_remove_helper_devices with " - "sweep_helper_devices=True instead", + "remove_all_devices=True instead", core_behavior=ReportBehavior.LOG, breaks_in_ha_version="2027.8.0", ) diff --git a/homeassistant/helpers/helper_integration.py b/homeassistant/helpers/helper_integration.py index 222c5d749a1d59..5d4807aa53f0ee 100644 --- a/homeassistant/helpers/helper_integration.py +++ b/homeassistant/helpers/helper_integration.py @@ -136,7 +136,7 @@ def async_remove_helper_devices( *, helper_config_entry_id: str, source_device_id: str | None, - sweep_helper_devices: bool = False, + remove_all_devices: bool = False, keep_device_ids: Collection[str] = (), ) -> None: """Migrate a helper which has tried to own a device instead of just linking to it. @@ -154,7 +154,7 @@ def async_remove_helper_devices( device relinks the entities to it. A pre-migration composite id and None have no concrete device to hold the link, so the entities are detached; in targeted mode a composite or None source with no matching duplicate is a no-op. - :param sweep_helper_devices: By default only the helper's single duplicate of + :param remove_all_devices: By default only the helper's single duplicate of source_device_id (a split or fork) is removed. When True, every device the helper owns except source_device_id and keep_device_ids is removed instead - even when source_device_id no longer exists, in which case the helper's @@ -162,7 +162,7 @@ def async_remove_helper_devices( created but never removed itself, such as a fork left behind for each previously selected source device. :param keep_device_ids: Devices the helper legitimately owns which must not be removed. - Only consulted when sweep_helper_devices is True. + Only consulted when remove_all_devices is True. """ device_registry = dr.async_get(hass) entity_registry = er.async_get(hass) @@ -173,10 +173,10 @@ def async_remove_helper_devices( else None ) if source_device is None: - # No source device (gone, or none selected). In sweep mode the helper's devices are - # still removed, leaving its entities without a device; targeted mode has no duplicate - # to match. - if sweep_helper_devices: + # No source device (gone, or none selected). In remove-all mode the helper's devices + # are still removed, leaving its entities without a device; targeted mode has no + # duplicate to match. + if remove_all_devices: _sweep_helper_devices( device_registry, entity_registry, @@ -195,7 +195,7 @@ def async_remove_helper_devices( ) target_device_id = source_device_id if source_is_concrete else None - if sweep_helper_devices: + if remove_all_devices: _sweep_helper_devices( device_registry, entity_registry, diff --git a/homeassistant/package_constraints.txt b/homeassistant/package_constraints.txt index b766e33e60de74..892aaa56ed053b 100644 --- a/homeassistant/package_constraints.txt +++ b/homeassistant/package_constraints.txt @@ -37,7 +37,7 @@ go2rtc-client==0.4.0 ha-ffmpeg==3.2.2 habluetooth==6.26.5 hass-nabucasa==2.2.0 -hassil==3.8.0 +hassil==3.10.0 home-assistant-bluetooth==2.0.0 home-assistant-frontend==20260624.6 home-assistant-intents==2026.6.24 diff --git a/requirements.txt b/requirements.txt index 9e785316ce5627..f95681abc574a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -25,12 +25,12 @@ cryptography==48.0.1 fnv-hash-fast==2.0.3 ha-ffmpeg==3.2.2 hass-nabucasa==2.2.0 -hassil==3.8.0 +hassil==3.10.0 home-assistant-bluetooth==2.0.0 home-assistant-intents==2026.6.24 httpx==0.28.1 ifaddr==0.2.0 -infrared-protocols==8.1.0 +infrared-protocols==8.2.0 Jinja2==3.1.6 lru-dict==1.4.1 mutagen==1.48.1 diff --git a/requirements_all.txt b/requirements_all.txt index 2bfede3967264b..8d38f6f6b86220 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1241,7 +1241,7 @@ hass-splunk==0.1.4 # homeassistant.components.assist_satellite # homeassistant.components.conversation -hassil==3.8.0 +hassil==3.10.0 # homeassistant.components.jewish_calendar hdate[astral]==1.2.1 @@ -1374,7 +1374,7 @@ influxdb-client==1.50.0 influxdb==5.3.2 # homeassistant.components.infrared -infrared-protocols==8.1.0 +infrared-protocols==8.2.0 # homeassistant.components.inkbird inkbird-ble==1.4.4 @@ -2695,7 +2695,7 @@ python-homewizard-energy==10.1.0 python-hpilo==4.4.3 # homeassistant.components.izone -python-izone==1.3.6 +python-izone==1.3.7 # homeassistant.components.joaoapps_join python-join-api==0.1.1 diff --git a/tests/helpers/test_helper_integration.py b/tests/helpers/test_helper_integration.py index 8bf01c5915b866..bbbb03ac34a9cd 100644 --- a/tests/helpers/test_helper_integration.py +++ b/tests/helpers/test_helper_integration.py @@ -679,7 +679,7 @@ async def test_async_remove_helper_devices_sweep( A helper that forked a device for each source it linked to, without removing the old forks, accumulates stale devices that don't match the current source (so the - targeted match never sees them). sweep_helper_devices removes them and relinks the + targeted match never sees them). remove_all_devices removes them and relinks the helper's entities - including a stranded, device-less one - to the source, while keep_device_ids and the source device are preserved. """ @@ -737,7 +737,7 @@ async def test_async_remove_helper_devices_sweep( hass, helper_config_entry_id=helper_config_entry.entry_id, source_device_id=source_device.id, - sweep_helper_devices=True, + remove_all_devices=True, keep_device_ids={kept_device.id}, ) @@ -796,7 +796,7 @@ async def test_async_remove_helper_devices_sweep_no_source( hass, helper_config_entry_id=helper_config_entry.entry_id, source_device_id=source_device_id, - sweep_helper_devices=True, + remove_all_devices=True, ) # The helper's device is removed and its entity left without a device @@ -811,7 +811,7 @@ async def test_async_remove_helper_devices_none_source_targeted_noop( ) -> None: """Targeted mode is a no-op when no source device is selected. - Without sweep_helper_devices there is no duplicate to match against a missing source, so + Without remove_all_devices there is no duplicate to match against a missing source, so the helper's device and its entity's device link are left untouched. """ helper_config_entry = MockConfigEntry(domain=HELPER_DOMAIN)