Skip to content
Merged

2025.9.1 #151766

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 homeassistant/components/bmw_connected_drive/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/bmw_connected_drive",
"iot_class": "cloud_polling",
"loggers": ["bimmer_connected"],
"requirements": ["bimmer-connected[china]==0.17.2"]
"requirements": ["bimmer-connected[china]==0.17.3"]
}
10 changes: 4 additions & 6 deletions homeassistant/components/conversation/default_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
)
from hassil.string_matcher import UnmatchedRangeEntity, UnmatchedTextEntity
from hassil.trie import Trie
from hassil.util import merge_dict
from hassil.util import merge_dict, remove_punctuation
from home_assistant_intents import (
ErrorKey,
FuzzyConfig,
Expand Down Expand Up @@ -327,12 +327,10 @@ async def async_recognize_intent(

if self._exposed_names_trie is not None:
# Filter by input string
text_lower = user_input.text.strip().lower()
text = remove_punctuation(user_input.text).strip().lower()
slot_lists["name"] = TextSlotList(
name="name",
values=[
result[2] for result in self._exposed_names_trie.find(text_lower)
],
values=[result[2] for result in self._exposed_names_trie.find(text)],
)

start = time.monotonic()
Expand Down Expand Up @@ -1263,7 +1261,7 @@ async def _make_slot_lists(self) -> dict[str, SlotList]:
name_list = TextSlotList.from_tuples(exposed_entity_names, allow_template=False)
for name_value in name_list.values:
assert isinstance(name_value.text_in, TextChunk)
name_text = name_value.text_in.text.strip().lower()
name_text = remove_punctuation(name_value.text_in.text).strip().lower()
self._exposed_names_trie.insert(name_text, name_value)

self._slot_lists = {
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/fan/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"toggle": "[%key:common::device_automation::action_type::toggle%]",
"turn_on": "[%key:common::device_automation::action_type::turn_on%]",
"turn_off": "[%key:common::device_automation::action_type::turn_off%]"
},
"extra_fields": {
"for": "[%key:common::device_automation::extra_fields::for%]"
}
},
"entity_component": {
Expand Down
4 changes: 2 additions & 2 deletions homeassistant/components/fritz/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@
"description": "Sets a new password for the guest Wi-Fi. The password must be between 8 and 63 characters long. If no additional parameter is set, the password will be auto-generated with a length of 12 characters.",
"fields": {
"device_id": {
"name": "Fritz!Box Device",
"description": "Select the Fritz!Box to configure."
"name": "FRITZ!Box Device",
"description": "Select the FRITZ!Box to configure."
},
"password": {
"name": "[%key:common::config_flow::data::password%]",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/frontend/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20250903.2"]
"requirements": ["home-assistant-frontend==20250903.3"]
}
4 changes: 2 additions & 2 deletions homeassistant/components/hassio/ingress.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,9 @@ async def _websocket_forward(
elif msg.type is aiohttp.WSMsgType.BINARY:
await ws_to.send_bytes(msg.data)
elif msg.type is aiohttp.WSMsgType.PING:
await ws_to.ping()
await ws_to.ping(msg.data)
elif msg.type is aiohttp.WSMsgType.PONG:
await ws_to.pong()
await ws_to.pong(msg.data)
elif ws_to.closed:
await ws_to.close(code=ws_to.close_code, message=msg.extra) # type: ignore[arg-type]
except RuntimeError:
Expand Down
80 changes: 76 additions & 4 deletions homeassistant/components/hue/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
import aiohttp
from aiohue import LinkButtonNotPressed, create_app_key
from aiohue.discovery import DiscoveredHueBridge, discover_bridge, discover_nupnp
from aiohue.errors import AiohueException
from aiohue.util import normalize_bridge_id
from aiohue.v2 import HueBridgeV2
import slugify as unicode_slug
import voluptuous as vol

Expand Down Expand Up @@ -40,6 +42,9 @@
HUE_IGNORED_BRIDGE_NAMES = ["Home Assistant Bridge", "Espalexa"]
HUE_MANUAL_BRIDGE_ID = "manual"

BSB002_MODEL_ID = "BSB002"
BSB003_MODEL_ID = "BSB003"


class HueFlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle a Hue config flow."""
Expand Down Expand Up @@ -74,7 +79,14 @@ async def _get_bridge(
"""Return a DiscoveredHueBridge object."""
try:
bridge = await discover_bridge(
host, websession=aiohttp_client.async_get_clientsession(self.hass)
host,
websession=aiohttp_client.async_get_clientsession(
# NOTE: we disable SSL verification for now due to the fact that the (BSB003)
# Hue bridge uses a certificate from a on-bridge root authority.
# We need to specifically handle this case in a follow-up update.
self.hass,
verify_ssl=False,
),
)
except aiohttp.ClientError as err:
LOGGER.warning(
Expand Down Expand Up @@ -110,7 +122,9 @@ async def async_step_init(
try:
async with asyncio.timeout(5):
bridges = await discover_nupnp(
websession=aiohttp_client.async_get_clientsession(self.hass)
websession=aiohttp_client.async_get_clientsession(
self.hass, verify_ssl=False
)
)
except TimeoutError:
bridges = []
Expand Down Expand Up @@ -178,7 +192,9 @@ async def async_step_link(
app_key = await create_app_key(
bridge.host,
f"home-assistant#{device_name}",
websession=aiohttp_client.async_get_clientsession(self.hass),
websession=aiohttp_client.async_get_clientsession(
self.hass, verify_ssl=False
),
)
except LinkButtonNotPressed:
errors["base"] = "register_failed"
Expand Down Expand Up @@ -228,14 +244,21 @@ async def async_step_zeroconf(
self._abort_if_unique_id_configured(
updates={CONF_HOST: discovery_info.host}, reload_on_update=True
)

# we need to query the other capabilities too
bridge = await self._get_bridge(
discovery_info.host, discovery_info.properties["bridgeid"]
)
if bridge is None:
return self.async_abort(reason="cannot_connect")
self.bridge = bridge
if (
bridge.supports_v2
and discovery_info.properties.get("modelid") == BSB003_MODEL_ID
):
# try to handle migration of BSB002 --> BSB003
if await self._check_migrated_bridge(bridge):
return self.async_abort(reason="migrated_bridge")

return await self.async_step_link()

async def async_step_homekit(
Expand Down Expand Up @@ -272,6 +295,55 @@ async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResu
self.bridge = bridge
return await self.async_step_link()

async def _check_migrated_bridge(self, bridge: DiscoveredHueBridge) -> bool:
"""Check if the discovered bridge is a migrated bridge."""
# Try to handle migration of BSB002 --> BSB003.
# Once we detect a BSB003 bridge on the network which has not yet been
# configured in HA (otherwise we would have had a unique id match),
# we check if we have any existing (BSB002) entries and if we can connect to the
# new bridge with our previously stored api key.
# If that succeeds, we migrate the entry to the new bridge.
for conf_entry in self.hass.config_entries.async_entries(
DOMAIN, include_ignore=False, include_disabled=False
):
if conf_entry.data[CONF_API_VERSION] != 2:
continue
if conf_entry.data[CONF_HOST] == bridge.host:
continue
# found an existing (BSB002) bridge entry,
# check if we can connect to the new BSB003 bridge using the old credentials
api = HueBridgeV2(bridge.host, conf_entry.data[CONF_API_KEY])
try:
await api.fetch_full_state()
except (AiohueException, aiohttp.ClientError):
continue
old_bridge_id = conf_entry.unique_id
assert old_bridge_id is not None
# found a matching entry, migrate it
self.hass.config_entries.async_update_entry(
conf_entry,
data={
**conf_entry.data,
CONF_HOST: bridge.host,
},
unique_id=bridge.id,
)
# also update the bridge device
dev_reg = dr.async_get(self.hass)
if bridge_device := dev_reg.async_get_device(
identifiers={(DOMAIN, old_bridge_id)}
):
dev_reg.async_update_device(
bridge_device.id,
# overwrite identifiers with new bridge id
new_identifiers={(DOMAIN, bridge.id)},
# overwrite mac addresses with empty set to drop the old (incorrect) addresses
# this will be auto corrected once the integration is loaded
new_connections=set(),
)
return True
return False


class HueV1OptionsFlowHandler(OptionsFlow):
"""Handle Hue options for V1 implementation."""
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/hue/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["aiohue"],
"requirements": ["aiohue==4.7.4"],
"requirements": ["aiohue==4.7.5"],
"zeroconf": ["_hue._tcp.local."]
}
2 changes: 1 addition & 1 deletion homeassistant/components/imeon_inverter/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"integration_type": "device",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["imeon_inverter_api==0.3.14"],
"requirements": ["imeon_inverter_api==0.3.16"],
"ssdp": [
{
"manufacturer": "IMEON",
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/intent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ async def post(self, request: web.Request, data: dict[str, Any]) -> web.Response
intent_result = await intent.async_handle(
hass, DOMAIN, intent_name, slots, "", self.context(request)
)
except intent.IntentHandleError as err:
except (intent.IntentHandleError, intent.MatchFailedError) as err:
intent_result = intent.IntentResponse(language=language)
intent_result.async_set_speech(str(err))

Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/light/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
},
"extra_fields": {
"brightness_pct": "Brightness",
"flash": "Flash"
"flash": "Flash",
"for": "[%key:common::device_automation::extra_fields::for%]"
}
},
"entity_component": {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/mill/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/mill",
"iot_class": "local_polling",
"loggers": ["mill", "mill_local"],
"requirements": ["millheater==0.12.5", "mill-local==0.3.0"]
"requirements": ["millheater==0.13.1", "mill-local==0.3.0"]
}
7 changes: 0 additions & 7 deletions homeassistant/components/modbus/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@
CONF_VIRTUAL_COUNT,
CONF_WRITE_TYPE,
CONF_ZERO_SUPPRESS,
SIGNAL_START_ENTITY,
SIGNAL_STOP_ENTITY,
DataType,
)
Expand Down Expand Up @@ -143,7 +142,6 @@ def async_disable(self) -> None:
self._cancel_call()
self._cancel_call = None
self._attr_available = False
self.async_write_ha_state()

async def async_await_connection(self, _now: Any) -> None:
"""Wait for first connect."""
Expand All @@ -162,11 +160,6 @@ async def async_base_added_to_hass(self) -> None:
self.async_on_remove(
async_dispatcher_connect(self.hass, SIGNAL_STOP_ENTITY, self.async_disable)
)
self.async_on_remove(
async_dispatcher_connect(
self.hass, SIGNAL_START_ENTITY, self.async_local_update
)
)


class BaseStructPlatform(BasePlatform, RestoreEntity):
Expand Down
17 changes: 16 additions & 1 deletion homeassistant/components/ohme/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from ohme import ApiException, OhmeApiClient

from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import DOMAIN
Expand Down Expand Up @@ -83,6 +83,21 @@ class OhmeAdvancedSettingsCoordinator(OhmeBaseCoordinator):

coordinator_name = "Advanced Settings"

def __init__(
self, hass: HomeAssistant, config_entry: OhmeConfigEntry, client: OhmeApiClient
) -> None:
"""Initialise coordinator."""
super().__init__(hass, config_entry, client)

@callback
def _dummy_listener() -> None:
pass

# This coordinator is used by the API library to determine whether the
# charger is online and available. It is therefore required even if no
# entities are using it.
self.async_add_listener(_dummy_listener)

async def _internal_update_data(self) -> None:
"""Fetch data from API endpoint."""
await self.client.async_get_advanced_settings()
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/ohme/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"integration_type": "device",
"iot_class": "cloud_polling",
"quality_scale": "platinum",
"requirements": ["ohme==1.5.1"]
"requirements": ["ohme==1.5.2"]
}
3 changes: 3 additions & 0 deletions homeassistant/components/remote/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"changed_states": "[%key:common::device_automation::trigger_type::changed_states%]",
"turned_on": "[%key:common::device_automation::trigger_type::turned_on%]",
"turned_off": "[%key:common::device_automation::trigger_type::turned_off%]"
},
"extra_fields": {
"for": "[%key:common::device_automation::extra_fields::for%]"
}
},
"entity_component": {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/schlage/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/schlage",
"iot_class": "cloud_polling",
"requirements": ["pyschlage==2025.7.3"]
"requirements": ["pyschlage==2025.9.0"]
}
11 changes: 9 additions & 2 deletions homeassistant/components/sonos/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,15 @@ def available_soco_attributes(
if (
state := getattr(speaker.soco, select_data.soco_attribute, None)
) is not None:
setattr(speaker, select_data.speaker_attribute, state)
features.append(select_data)
try:
setattr(speaker, select_data.speaker_attribute, int(state))
features.append(select_data)
except ValueError:
_LOGGER.error(
"Invalid value for %s %s",
select_data.speaker_attribute,
state,
)
return features

async def _async_create_entities(speaker: SonosSpeaker) -> None:
Expand Down
7 changes: 6 additions & 1 deletion homeassistant/components/sonos/speaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,12 @@ def async_update_volume(self, event: SonosEvent) -> None:

for enum_var in (ATTR_DIALOG_LEVEL,):
if enum_var in variables:
setattr(self, f"{enum_var}_enum", variables[enum_var])
try:
setattr(self, f"{enum_var}_enum", int(variables[enum_var]))
except ValueError:
_LOGGER.error(
"Invalid value for %s %s", enum_var, variables[enum_var]
)

self.async_write_entity_states()

Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/switch/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
"changed_states": "[%key:common::device_automation::trigger_type::changed_states%]",
"turned_on": "[%key:common::device_automation::trigger_type::turned_on%]",
"turned_off": "[%key:common::device_automation::trigger_type::turned_off%]"
},
"extra_fields": {
"for": "[%key:common::device_automation::extra_fields::for%]"
}
},
"entity_component": {
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/update/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"changed_states": "{entity_name} update availability changed",
"turned_on": "{entity_name} got an update available",
"turned_off": "{entity_name} became up-to-date"
},
"extra_fields": {
"for": "[%key:common::device_automation::extra_fields::for%]"
}
},
"entity_component": {
Expand Down
Loading