Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ on:
type: boolean

env:
CACHE_VERSION: 6
CACHE_VERSION: 7
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 1
HA_SHORT_VERSION: "2025.9"
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/assist_satellite/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/assist_satellite",
"integration_type": "entity",
"quality_scale": "internal",
"requirements": ["hassil==3.1.0"]
"requirements": ["hassil==3.2.0"]
}
42 changes: 42 additions & 0 deletions homeassistant/components/config/config_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def async_setup(hass: HomeAssistant) -> bool:
websocket_api.async_register_command(hass, config_entries_flow_subscribe)
websocket_api.async_register_command(hass, ignore_config_flow)

websocket_api.async_register_command(hass, config_subentry_update)
websocket_api.async_register_command(hass, config_subentry_delete)
websocket_api.async_register_command(hass, config_subentry_list)

Expand Down Expand Up @@ -731,6 +732,47 @@ async def config_subentry_list(
connection.send_result(msg["id"], result)


@websocket_api.require_admin
@websocket_api.websocket_command(
{
"type": "config_entries/subentries/update",
"entry_id": str,
"subentry_id": str,
vol.Optional("title"): str,
}
)
@websocket_api.async_response
async def config_subentry_update(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Update a subentry of a config entry."""
entry = get_entry(hass, connection, msg["entry_id"], msg["id"])
if entry is None:
connection.send_error(
msg["entry_id"], websocket_api.const.ERR_NOT_FOUND, "Config entry not found"
)
return

subentry = entry.subentries.get(msg["subentry_id"])
if subentry is None:
connection.send_error(
msg["id"], websocket_api.const.ERR_NOT_FOUND, "Config subentry not found"
)
return

changes = dict(msg)
changes.pop("id")
changes.pop("type")
changes.pop("entry_id")
changes.pop("subentry_id")

hass.config_entries.async_update_subentry(entry, subentry, **changes)

connection.send_result(msg["id"])


@websocket_api.require_admin
@websocket_api.websocket_command(
{
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/conversation/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/conversation",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["hassil==3.1.0", "home-assistant-intents==2025.7.30"]
"requirements": ["hassil==3.2.0", "home-assistant-intents==2025.7.30"]
}
2 changes: 1 addition & 1 deletion homeassistant/components/homekit/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"iot_class": "local_push",
"loggers": ["pyhap"],
"requirements": [
"HAP-python==4.9.2",
"HAP-python==5.0.0",
"fnv-hash-fast==1.5.0",
"PyQRCode==1.2.1",
"base36==0.1.1"
Expand Down
19 changes: 14 additions & 5 deletions homeassistant/components/homekit_controller/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,9 @@ def async_set_available_state(self, available: bool) -> None:
_LOGGER.debug(
"Called async_set_available_state with %s for %s", available, self.unique_id
)
if self.available == available:
# Don't mark entities as unavailable during shutdown to preserve their last known state
# Also skip if the availability state hasn't changed
if (self.hass.is_stopping and not available) or self.available == available:
return
self.available = available
for callback_ in self._availability_callbacks:
Expand Down Expand Up @@ -294,7 +296,6 @@ async def async_setup(self) -> None:
await self.pairing.async_populate_accessories_state(
force_update=True, attempts=attempts
)
self._async_start_polling()

entry.async_on_unload(pairing.dispatcher_connect(self.process_new_events))
entry.async_on_unload(
Expand All @@ -307,6 +308,12 @@ async def async_setup(self) -> None:

await self.async_process_entity_map()

if transport != Transport.BLE:
# Do a single poll to make sure the chars are
# up to date so we don't restore old data.
await self.async_update()
self._async_start_polling()

# If everything is up to date, we can create the entities
# since we know the data is not stale.
await self.async_add_new_entities()
Expand Down Expand Up @@ -711,9 +718,11 @@ async def async_unload(self) -> None:
"""Stop interacting with device and prepare for removal from hass."""
await self.pairing.shutdown()

await self.hass.config_entries.async_unload_platforms(
self.config_entry, self.platforms
)
# Skip platform unloading during shutdown to preserve entity states
if not self.hass.is_stopping:
await self.hass.config_entries.async_unload_platforms(
self.config_entry, self.platforms
)

def process_config_changed(self, config_num: int) -> None:
"""Handle a config change notification from the pairing."""
Expand Down
19 changes: 19 additions & 0 deletions homeassistant/components/niko_home_control/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,25 @@ class NikoHomeControlConfigFlow(ConfigFlow, domain=DOMAIN):

MINOR_VERSION = 2

async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration of the integration."""
errors: dict[str, str] = {}
if user_input is not None:
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
error = await test_connection(user_input[CONF_HOST])
if not error:
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
data_updates=user_input,
)
errors["base"] = error

return self.async_show_form(
step_id="reconfigure", data_schema=DATA_SCHEMA, errors=errors
)

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
Expand Down
11 changes: 10 additions & 1 deletion homeassistant/components/niko_home_control/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,22 @@
"data_description": {
"host": "The hostname or IP address of the Niko Home Control controller."
}
},
"reconfigure": {
"data": {
"host": "[%key:common::config_flow::data::host%]"
},
"data_description": {
"host": "[%key:component::niko_home_control::config::step::user::data_description::host%]"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
}
}
}
2 changes: 1 addition & 1 deletion homeassistant/components/stiebel_eltron/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ async def _async_import(hass: HomeAssistant, config: ConfigType) -> None:
hass,
DOMAIN,
"deprecated_yaml",
breaks_in_ha_version="2025.9.0",
breaks_in_ha_version="2025.11.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/vicare/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
"documentation": "https://www.home-assistant.io/integrations/vicare",
"iot_class": "cloud_polling",
"loggers": ["PyViCare"],
"requirements": ["PyViCare==2.50.0"]
"requirements": ["PyViCare==2.51.0"]
}
23 changes: 12 additions & 11 deletions homeassistant/helpers/device_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,20 +476,27 @@ class DeletedDeviceEntry:

def to_device_entry(
self,
config_entry_id: str,
config_entry: ConfigEntry,
config_subentry_id: str | None,
connections: set[tuple[str, str]],
identifiers: set[tuple[str, str]],
) -> DeviceEntry:
"""Create DeviceEntry from DeletedDeviceEntry."""
# Adjust disabled_by based on config entry state
disabled_by = self.disabled_by
if config_entry.disabled_by:
if disabled_by is None:
disabled_by = DeviceEntryDisabler.CONFIG_ENTRY
elif disabled_by == DeviceEntryDisabler.CONFIG_ENTRY:
disabled_by = None
return DeviceEntry(
area_id=self.area_id,
# type ignores: likely https://github.com/python/mypy/issues/8625
config_entries={config_entry_id}, # type: ignore[arg-type]
config_entries_subentries={config_entry_id: {config_subentry_id}},
config_entries={config_entry.entry_id}, # type: ignore[arg-type]
config_entries_subentries={config_entry.entry_id: {config_subentry_id}},
connections=self.connections & connections, # type: ignore[arg-type]
created_at=self.created_at,
disabled_by=self.disabled_by,
disabled_by=disabled_by,
identifiers=self.identifiers & identifiers, # type: ignore[arg-type]
id=self.id,
is_new=True,
Expand Down Expand Up @@ -922,7 +929,7 @@ def async_get_or_create(
else:
self.deleted_devices.pop(deleted_device.id)
device = deleted_device.to_device_entry(
config_entry_id,
config_entry,
# Interpret not specifying a subentry as None
config_subentry_id if config_subentry_id is not UNDEFINED else None,
connections,
Expand Down Expand Up @@ -1460,18 +1467,12 @@ def async_clear_config_entry(self, config_entry_id: str) -> None:
if config_entry_id not in config_entries:
continue
if config_entries == {config_entry_id}:
# Clear disabled_by if it was disabled by the config entry
if deleted_device.disabled_by is DeviceEntryDisabler.CONFIG_ENTRY:
disabled_by = None
else:
disabled_by = deleted_device.disabled_by
# Add a time stamp when the deleted device became orphaned
self.deleted_devices[deleted_device.id] = attr.evolve(
deleted_device,
orphaned_timestamp=now_time,
config_entries=set(),
config_entries_subentries={},
disabled_by=disabled_by,
)
else:
config_entries = config_entries - {config_entry_id}
Expand Down
19 changes: 10 additions & 9 deletions homeassistant/helpers/entity_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,15 @@ def async_get_or_create(
created_at = deleted_entity.created_at
device_class = deleted_entity.device_class
disabled_by = deleted_entity.disabled_by
# Adjust disabled_by based on config entry state
if config_entry and config_entry is not UNDEFINED:
if config_entry.disabled_by:
if disabled_by is None:
disabled_by = RegistryEntryDisabler.CONFIG_ENTRY
elif disabled_by == RegistryEntryDisabler.CONFIG_ENTRY:
disabled_by = None
elif disabled_by == RegistryEntryDisabler.CONFIG_ENTRY:
disabled_by = None
# Restore entity_id if it's available
if self._entity_id_available(deleted_entity.entity_id):
entity_id = deleted_entity.entity_id
Expand Down Expand Up @@ -1613,17 +1622,9 @@ def async_clear_config_entry(self, config_entry_id: str) -> None:
for key, deleted_entity in list(self.deleted_entities.items()):
if config_entry_id != deleted_entity.config_entry_id:
continue
# Clear disabled_by if it was disabled by the config entry
if deleted_entity.disabled_by is RegistryEntryDisabler.CONFIG_ENTRY:
disabled_by = None
else:
disabled_by = deleted_entity.disabled_by
# Add a time stamp when the deleted entity became orphaned
self.deleted_entities[key] = attr.evolve(
deleted_entity,
orphaned_timestamp=now_time,
config_entry_id=None,
disabled_by=disabled_by,
deleted_entity, orphaned_timestamp=now_time, config_entry_id=None
)
self.async_schedule_save()

Expand Down
4 changes: 2 additions & 2 deletions homeassistant/package_constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ go2rtc-client==0.2.1
ha-ffmpeg==3.2.2
habluetooth==5.1.0
hass-nabucasa==1.0.0
hassil==3.1.0
hassil==3.2.0
home-assistant-bluetooth==1.13.1
home-assistant-frontend==20250811.1
home-assistant-intents==2025.7.30
Expand Down Expand Up @@ -65,7 +65,7 @@ securetar==2025.2.1
SQLAlchemy==2.0.41
standard-aifc==3.13.0
standard-telnetlib==3.13.0
typing-extensions>=4.14.0,<5.0
typing-extensions>=4.15.0,<5.0
ulid-transform==1.4.0
urllib3>=2.0
uv==0.8.9
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ dependencies = [
"SQLAlchemy==2.0.41",
"standard-aifc==3.13.0",
"standard-telnetlib==3.13.0",
"typing-extensions>=4.14.0,<5.0",
"typing-extensions>=4.15.0,<5.0",
"ulid-transform==1.4.0",
"urllib3>=2.0",
"uv==0.8.9",
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions requirements_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions requirements_test_all.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion script/hassfest/docker/Dockerfile

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading