mirror of
https://github.com/home-assistant/core.git
synced 2025-09-19 09:59:43 +00:00
Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0cda883b56 | ||
![]() |
ae58e633f0 | ||
![]() |
06480bfd9d | ||
![]() |
625f586945 | ||
![]() |
7dbeaa475d | ||
![]() |
dff3d5f8af | ||
![]() |
89c335919a | ||
![]() |
2bb4573357 | ||
![]() |
7037ce989c | ||
![]() |
bfdd2053ba | ||
![]() |
fcc3f92f8c | ||
![]() |
8710267d53 | ||
![]() |
85b6adcc9a | ||
![]() |
beec6e86e0 | ||
![]() |
3dacffaaf9 | ||
![]() |
d90f2a1de1 | ||
![]() |
b6c9217429 | ||
![]() |
7fc8da6769 |
@@ -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"]
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ from hassil.recognize import (
|
||||
)
|
||||
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,
|
||||
@@ -327,12 +327,10 @@ class DefaultAgent(ConversationEntity):
|
||||
|
||||
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()
|
||||
@@ -1263,7 +1261,7 @@ class DefaultAgent(ConversationEntity):
|
||||
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 = {
|
||||
|
@@ -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": {
|
||||
|
@@ -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%]",
|
||||
|
@@ -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"]
|
||||
}
|
||||
|
@@ -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:
|
||||
|
@@ -9,7 +9,9 @@ from typing import Any
|
||||
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
|
||||
|
||||
@@ -40,6 +42,9 @@ HUE_MANUFACTURERURL = ("http://www.philips.com", "http://www.philips-hue.com")
|
||||
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."""
|
||||
@@ -74,7 +79,14 @@ class HueFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""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(
|
||||
@@ -110,7 +122,9 @@ class HueFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
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 = []
|
||||
@@ -178,7 +192,9 @@ class HueFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
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"
|
||||
@@ -228,7 +244,6 @@ class HueFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
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"]
|
||||
@@ -236,6 +251,14 @@ class HueFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
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(
|
||||
@@ -272,6 +295,55 @@ class HueFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
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."""
|
||||
|
@@ -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."]
|
||||
}
|
||||
|
@@ -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",
|
||||
|
@@ -615,7 +615,7 @@ class IntentHandleView(http.HomeAssistantView):
|
||||
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))
|
||||
|
||||
|
@@ -57,7 +57,8 @@
|
||||
},
|
||||
"extra_fields": {
|
||||
"brightness_pct": "Brightness",
|
||||
"flash": "Flash"
|
||||
"flash": "Flash",
|
||||
"for": "[%key:common::device_automation::extra_fields::for%]"
|
||||
}
|
||||
},
|
||||
"entity_component": {
|
||||
|
@@ -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"]
|
||||
}
|
||||
|
@@ -62,7 +62,6 @@ from .const import (
|
||||
CONF_VIRTUAL_COUNT,
|
||||
CONF_WRITE_TYPE,
|
||||
CONF_ZERO_SUPPRESS,
|
||||
SIGNAL_START_ENTITY,
|
||||
SIGNAL_STOP_ENTITY,
|
||||
DataType,
|
||||
)
|
||||
@@ -143,7 +142,6 @@ class BasePlatform(Entity):
|
||||
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."""
|
||||
@@ -162,11 +160,6 @@ class BasePlatform(Entity):
|
||||
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):
|
||||
|
@@ -10,7 +10,7 @@ import logging
|
||||
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
|
||||
@@ -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()
|
||||
|
@@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["ohme==1.5.1"]
|
||||
"requirements": ["ohme==1.5.2"]
|
||||
}
|
||||
|
@@ -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": {
|
||||
|
@@ -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"]
|
||||
}
|
||||
|
@@ -61,8 +61,15 @@ async def async_setup_entry(
|
||||
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:
|
||||
|
@@ -599,7 +599,12 @@ class SonosSpeaker:
|
||||
|
||||
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()
|
||||
|
||||
|
@@ -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": {
|
||||
|
@@ -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": {
|
||||
|
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2025
|
||||
MINOR_VERSION: Final = 9
|
||||
PATCH_VERSION: Final = "0"
|
||||
PATCH_VERSION: Final = "1"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 2)
|
||||
|
@@ -38,7 +38,7 @@ habluetooth==5.3.0
|
||||
hass-nabucasa==1.1.0
|
||||
hassil==3.2.0
|
||||
home-assistant-bluetooth==1.13.1
|
||||
home-assistant-frontend==20250903.2
|
||||
home-assistant-frontend==20250903.3
|
||||
home-assistant-intents==2025.9.3
|
||||
httpx==0.28.1
|
||||
ifaddr==0.2.0
|
||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2025.9.0"
|
||||
version = "2025.9.1"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
|
14
requirements_all.txt
generated
14
requirements_all.txt
generated
@@ -277,7 +277,7 @@ aiohomekit==3.2.15
|
||||
aiohttp_sse==2.2.0
|
||||
|
||||
# homeassistant.components.hue
|
||||
aiohue==4.7.4
|
||||
aiohue==4.7.5
|
||||
|
||||
# homeassistant.components.imap
|
||||
aioimaplib==2.0.1
|
||||
@@ -618,7 +618,7 @@ beautifulsoup4==4.13.3
|
||||
# beewi-smartclim==0.0.10
|
||||
|
||||
# homeassistant.components.bmw_connected_drive
|
||||
bimmer-connected[china]==0.17.2
|
||||
bimmer-connected[china]==0.17.3
|
||||
|
||||
# homeassistant.components.bizkaibus
|
||||
bizkaibus==0.1.1
|
||||
@@ -1178,7 +1178,7 @@ hole==0.9.0
|
||||
holidays==0.79
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20250903.2
|
||||
home-assistant-frontend==20250903.3
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2025.9.3
|
||||
@@ -1241,7 +1241,7 @@ igloohome-api==0.1.1
|
||||
ihcsdk==2.8.5
|
||||
|
||||
# homeassistant.components.imeon_inverter
|
||||
imeon_inverter_api==0.3.14
|
||||
imeon_inverter_api==0.3.16
|
||||
|
||||
# homeassistant.components.imgw_pib
|
||||
imgw_pib==1.5.4
|
||||
@@ -1441,7 +1441,7 @@ microBeesPy==0.3.5
|
||||
mill-local==0.3.0
|
||||
|
||||
# homeassistant.components.mill
|
||||
millheater==0.12.5
|
||||
millheater==0.13.1
|
||||
|
||||
# homeassistant.components.minio
|
||||
minio==7.1.12
|
||||
@@ -1580,7 +1580,7 @@ odp-amsterdam==6.1.2
|
||||
oemthermostat==1.1.1
|
||||
|
||||
# homeassistant.components.ohme
|
||||
ohme==1.5.1
|
||||
ohme==1.5.2
|
||||
|
||||
# homeassistant.components.ollama
|
||||
ollama==0.5.1
|
||||
@@ -2312,7 +2312,7 @@ pysabnzbd==1.1.1
|
||||
pysaj==0.0.16
|
||||
|
||||
# homeassistant.components.schlage
|
||||
pyschlage==2025.7.3
|
||||
pyschlage==2025.9.0
|
||||
|
||||
# homeassistant.components.sensibo
|
||||
pysensibo==1.2.1
|
||||
|
14
requirements_test_all.txt
generated
14
requirements_test_all.txt
generated
@@ -262,7 +262,7 @@ aiohomekit==3.2.15
|
||||
aiohttp_sse==2.2.0
|
||||
|
||||
# homeassistant.components.hue
|
||||
aiohue==4.7.4
|
||||
aiohue==4.7.5
|
||||
|
||||
# homeassistant.components.imap
|
||||
aioimaplib==2.0.1
|
||||
@@ -555,7 +555,7 @@ base36==0.1.1
|
||||
beautifulsoup4==4.13.3
|
||||
|
||||
# homeassistant.components.bmw_connected_drive
|
||||
bimmer-connected[china]==0.17.2
|
||||
bimmer-connected[china]==0.17.3
|
||||
|
||||
# homeassistant.components.eq3btsmart
|
||||
# homeassistant.components.esphome
|
||||
@@ -1027,7 +1027,7 @@ hole==0.9.0
|
||||
holidays==0.79
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20250903.2
|
||||
home-assistant-frontend==20250903.3
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2025.9.3
|
||||
@@ -1075,7 +1075,7 @@ ifaddr==0.2.0
|
||||
igloohome-api==0.1.1
|
||||
|
||||
# homeassistant.components.imeon_inverter
|
||||
imeon_inverter_api==0.3.14
|
||||
imeon_inverter_api==0.3.16
|
||||
|
||||
# homeassistant.components.imgw_pib
|
||||
imgw_pib==1.5.4
|
||||
@@ -1233,7 +1233,7 @@ microBeesPy==0.3.5
|
||||
mill-local==0.3.0
|
||||
|
||||
# homeassistant.components.mill
|
||||
millheater==0.12.5
|
||||
millheater==0.13.1
|
||||
|
||||
# homeassistant.components.minio
|
||||
minio==7.1.12
|
||||
@@ -1348,7 +1348,7 @@ objgraph==3.5.0
|
||||
odp-amsterdam==6.1.2
|
||||
|
||||
# homeassistant.components.ohme
|
||||
ohme==1.5.1
|
||||
ohme==1.5.2
|
||||
|
||||
# homeassistant.components.ollama
|
||||
ollama==0.5.1
|
||||
@@ -1924,7 +1924,7 @@ pyrympro==0.0.9
|
||||
pysabnzbd==1.1.1
|
||||
|
||||
# homeassistant.components.schlage
|
||||
pyschlage==2025.7.3
|
||||
pyschlage==2025.9.0
|
||||
|
||||
# homeassistant.components.sensibo
|
||||
pysensibo==1.2.1
|
||||
|
@@ -138,6 +138,7 @@
|
||||
'state': 'LOW',
|
||||
}),
|
||||
]),
|
||||
'urgent_check_control_messages': None,
|
||||
}),
|
||||
'climate': dict({
|
||||
'activity': 'INACTIVE',
|
||||
@@ -193,6 +194,24 @@
|
||||
'state': 'OK',
|
||||
}),
|
||||
]),
|
||||
'next_service_by_distance': dict({
|
||||
'due_date': '2024-12-01T00:00:00+00:00',
|
||||
'due_distance': list([
|
||||
50000,
|
||||
'km',
|
||||
]),
|
||||
'service_type': 'BRAKE_FLUID',
|
||||
'state': 'OK',
|
||||
}),
|
||||
'next_service_by_time': dict({
|
||||
'due_date': '2024-12-01T00:00:00+00:00',
|
||||
'due_distance': list([
|
||||
50000,
|
||||
'km',
|
||||
]),
|
||||
'service_type': 'BRAKE_FLUID',
|
||||
'state': 'OK',
|
||||
}),
|
||||
}),
|
||||
'data': dict({
|
||||
'attributes': dict({
|
||||
@@ -1053,6 +1072,7 @@
|
||||
'state': 'LOW',
|
||||
}),
|
||||
]),
|
||||
'urgent_check_control_messages': None,
|
||||
}),
|
||||
'climate': dict({
|
||||
'activity': 'HEATING',
|
||||
@@ -1108,6 +1128,24 @@
|
||||
'state': 'OK',
|
||||
}),
|
||||
]),
|
||||
'next_service_by_distance': dict({
|
||||
'due_date': '2024-12-01T00:00:00+00:00',
|
||||
'due_distance': list([
|
||||
50000,
|
||||
'km',
|
||||
]),
|
||||
'service_type': 'BRAKE_FLUID',
|
||||
'state': 'OK',
|
||||
}),
|
||||
'next_service_by_time': dict({
|
||||
'due_date': '2024-12-01T00:00:00+00:00',
|
||||
'due_distance': list([
|
||||
50000,
|
||||
'km',
|
||||
]),
|
||||
'service_type': 'BRAKE_FLUID',
|
||||
'state': 'OK',
|
||||
}),
|
||||
}),
|
||||
'data': dict({
|
||||
'attributes': dict({
|
||||
@@ -1858,6 +1896,7 @@
|
||||
'state': 'LOW',
|
||||
}),
|
||||
]),
|
||||
'urgent_check_control_messages': None,
|
||||
}),
|
||||
'climate': dict({
|
||||
'activity': 'INACTIVE',
|
||||
@@ -1922,6 +1961,24 @@
|
||||
'state': 'OK',
|
||||
}),
|
||||
]),
|
||||
'next_service_by_distance': dict({
|
||||
'due_date': '2024-12-01T00:00:00+00:00',
|
||||
'due_distance': list([
|
||||
50000,
|
||||
'km',
|
||||
]),
|
||||
'service_type': 'BRAKE_FLUID',
|
||||
'state': 'OK',
|
||||
}),
|
||||
'next_service_by_time': dict({
|
||||
'due_date': '2024-12-01T00:00:00+00:00',
|
||||
'due_distance': list([
|
||||
50000,
|
||||
'km',
|
||||
]),
|
||||
'service_type': 'BRAKE_FLUID',
|
||||
'state': 'OK',
|
||||
}),
|
||||
}),
|
||||
'data': dict({
|
||||
'attributes': dict({
|
||||
@@ -2621,6 +2678,7 @@
|
||||
'has_check_control_messages': False,
|
||||
'messages': list([
|
||||
]),
|
||||
'urgent_check_control_messages': None,
|
||||
}),
|
||||
'climate': dict({
|
||||
'activity': 'UNKNOWN',
|
||||
@@ -2658,6 +2716,16 @@
|
||||
'state': 'OK',
|
||||
}),
|
||||
]),
|
||||
'next_service_by_distance': None,
|
||||
'next_service_by_time': dict({
|
||||
'due_date': '2022-10-01T00:00:00+00:00',
|
||||
'due_distance': list([
|
||||
None,
|
||||
None,
|
||||
]),
|
||||
'service_type': 'BRAKE_FLUID',
|
||||
'state': 'OK',
|
||||
}),
|
||||
}),
|
||||
'data': dict({
|
||||
'attributes': dict({
|
||||
@@ -4991,6 +5059,7 @@
|
||||
'has_check_control_messages': False,
|
||||
'messages': list([
|
||||
]),
|
||||
'urgent_check_control_messages': None,
|
||||
}),
|
||||
'climate': dict({
|
||||
'activity': 'UNKNOWN',
|
||||
@@ -5028,6 +5097,16 @@
|
||||
'state': 'OK',
|
||||
}),
|
||||
]),
|
||||
'next_service_by_distance': None,
|
||||
'next_service_by_time': dict({
|
||||
'due_date': '2022-10-01T00:00:00+00:00',
|
||||
'due_distance': list([
|
||||
None,
|
||||
None,
|
||||
]),
|
||||
'service_type': 'BRAKE_FLUID',
|
||||
'state': 'OK',
|
||||
}),
|
||||
}),
|
||||
'data': dict({
|
||||
'attributes': dict({
|
||||
|
@@ -231,6 +231,29 @@ async def test_conversation_agent(hass: HomeAssistant) -> None:
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_components")
|
||||
async def test_punctuation(hass: HomeAssistant) -> None:
|
||||
"""Test punctuation is handled properly."""
|
||||
hass.states.async_set(
|
||||
"light.test_light",
|
||||
"off",
|
||||
attributes={ATTR_FRIENDLY_NAME: "Test light"},
|
||||
)
|
||||
expose_entity(hass, "light.test_light", True)
|
||||
|
||||
calls = async_mock_service(hass, "light", "turn_on")
|
||||
result = await conversation.async_converse(
|
||||
hass, "Turn?? on,, test;; light!!!", None, Context(), None
|
||||
)
|
||||
|
||||
assert len(calls) == 1
|
||||
assert calls[0].data["entity_id"][0] == "light.test_light"
|
||||
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
assert result.response.intent is not None
|
||||
assert result.response.intent.slots["name"]["value"] == "test light"
|
||||
assert result.response.intent.slots["name"]["text"] == "test light"
|
||||
|
||||
|
||||
async def test_expose_flag_automatically_set(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
|
@@ -4,7 +4,7 @@ from ipaddress import ip_address
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from aiohue.discovery import URL_NUPNP
|
||||
from aiohue.errors import LinkButtonNotPressed
|
||||
from aiohue.errors import AiohueException, LinkButtonNotPressed
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -732,3 +732,216 @@ async def test_bridge_connection_failed(
|
||||
)
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "cannot_connect"
|
||||
|
||||
|
||||
async def test_bsb003_bridge_discovery(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test a bridge being discovered."""
|
||||
entry = MockConfigEntry(
|
||||
domain=const.DOMAIN,
|
||||
data={"host": "192.168.1.217", "api_version": 2, "api_key": "abc"},
|
||||
unique_id="bsb002_00000",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
device = device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id,
|
||||
identifiers={(const.DOMAIN, "bsb002_00000")},
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, "AA:BB:CC:DD:EE:FF")},
|
||||
)
|
||||
create_mock_api_discovery(
|
||||
aioclient_mock,
|
||||
[("192.168.1.217", "bsb002_00000"), ("192.168.1.218", "bsb003_00000")],
|
||||
)
|
||||
disc_bridge = get_discovered_bridge(
|
||||
bridge_id="bsb003_00000", host="192.168.1.218", supports_v2=True
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.hue.config_flow.discover_bridge",
|
||||
return_value=disc_bridge,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.hue.config_flow.HueBridgeV2",
|
||||
autospec=True,
|
||||
) as mock_bridge,
|
||||
):
|
||||
mock_bridge.return_value.fetch_full_state.return_value = {}
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data=ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.218"),
|
||||
ip_addresses=[ip_address("192.168.1.218")],
|
||||
port=443,
|
||||
hostname="Philips-hue.local",
|
||||
type="_hue._tcp.local.",
|
||||
name="Philips Hue - ABCABC._hue._tcp.local.",
|
||||
properties={
|
||||
"bridgeid": "bsb003_00000",
|
||||
"modelid": "BSB003",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "migrated_bridge"
|
||||
|
||||
migrated_device = device_registry.async_get(device.id)
|
||||
|
||||
assert migrated_device is not None
|
||||
assert len(migrated_device.identifiers) == 1
|
||||
assert list(migrated_device.identifiers)[0] == (const.DOMAIN, "bsb003_00000")
|
||||
# The tests don't add new connection, but that will happen
|
||||
# outside of the config flow
|
||||
assert len(migrated_device.connections) == 0
|
||||
assert entry.data["host"] == "192.168.1.218"
|
||||
|
||||
|
||||
async def test_bsb003_bridge_discovery_old_version(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test a bridge being discovered."""
|
||||
entry = MockConfigEntry(
|
||||
domain=const.DOMAIN,
|
||||
data={"host": "192.168.1.217", "api_version": 1, "api_key": "abc"},
|
||||
unique_id="bsb002_00000",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
disc_bridge = get_discovered_bridge(
|
||||
bridge_id="bsb003_00000", host="192.168.1.218", supports_v2=True
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.hue.config_flow.discover_bridge",
|
||||
return_value=disc_bridge,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data=ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.218"),
|
||||
ip_addresses=[ip_address("192.168.1.218")],
|
||||
port=443,
|
||||
hostname="Philips-hue.local",
|
||||
type="_hue._tcp.local.",
|
||||
name="Philips Hue - ABCABC._hue._tcp.local.",
|
||||
properties={
|
||||
"bridgeid": "bsb003_00000",
|
||||
"modelid": "BSB003",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "link"
|
||||
|
||||
|
||||
async def test_bsb003_bridge_discovery_same_host(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test a bridge being discovered."""
|
||||
entry = MockConfigEntry(
|
||||
domain=const.DOMAIN,
|
||||
data={"host": "192.168.1.217", "api_version": 2, "api_key": "abc"},
|
||||
unique_id="bsb002_00000",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
create_mock_api_discovery(
|
||||
aioclient_mock,
|
||||
[("192.168.1.217", "bsb003_00000")],
|
||||
)
|
||||
disc_bridge = get_discovered_bridge(
|
||||
bridge_id="bsb003_00000", host="192.168.1.217", supports_v2=True
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.hue.config_flow.discover_bridge",
|
||||
return_value=disc_bridge,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.hue.config_flow.HueBridgeV2",
|
||||
autospec=True,
|
||||
),
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data=ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.217"),
|
||||
ip_addresses=[ip_address("192.168.1.217")],
|
||||
port=443,
|
||||
hostname="Philips-hue.local",
|
||||
type="_hue._tcp.local.",
|
||||
name="Philips Hue - ABCABC._hue._tcp.local.",
|
||||
properties={
|
||||
"bridgeid": "bsb003_00000",
|
||||
"modelid": "BSB003",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "link"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exception", [AiohueException, ClientError])
|
||||
async def test_bsb003_bridge_discovery_cannot_connect(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
exception: Exception,
|
||||
) -> None:
|
||||
"""Test a bridge being discovered."""
|
||||
entry = MockConfigEntry(
|
||||
domain=const.DOMAIN,
|
||||
data={"host": "192.168.1.217", "api_version": 2, "api_key": "abc"},
|
||||
unique_id="bsb002_00000",
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
create_mock_api_discovery(
|
||||
aioclient_mock,
|
||||
[("192.168.1.217", "bsb003_00000")],
|
||||
)
|
||||
disc_bridge = get_discovered_bridge(
|
||||
bridge_id="bsb003_00000", host="192.168.1.217", supports_v2=True
|
||||
)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.hue.config_flow.discover_bridge",
|
||||
return_value=disc_bridge,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.hue.config_flow.HueBridgeV2",
|
||||
autospec=True,
|
||||
) as mock_bridge,
|
||||
):
|
||||
mock_bridge.return_value.fetch_full_state.side_effect = exception
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
const.DOMAIN,
|
||||
context={"source": config_entries.SOURCE_ZEROCONF},
|
||||
data=ZeroconfServiceInfo(
|
||||
ip_address=ip_address("192.168.1.217"),
|
||||
ip_addresses=[ip_address("192.168.1.217")],
|
||||
port=443,
|
||||
hostname="Philips-hue.local",
|
||||
type="_hue._tcp.local.",
|
||||
name="Philips Hue - ABCABC._hue._tcp.local.",
|
||||
properties={
|
||||
"bridgeid": "bsb003_00000",
|
||||
"modelid": "BSB003",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "link"
|
||||
|
@@ -73,6 +73,32 @@ async def test_http_handle_intent(
|
||||
}
|
||||
|
||||
|
||||
async def test_http_handle_intent_match_failure(
|
||||
hass: HomeAssistant, hass_client: ClientSessionGenerator, hass_admin_user: MockUser
|
||||
) -> None:
|
||||
"""Test handle intent match failure via HTTP API."""
|
||||
|
||||
assert await async_setup_component(hass, "intent", {})
|
||||
|
||||
hass.states.async_set(
|
||||
"cover.garage_door_1", "closed", {ATTR_FRIENDLY_NAME: "Garage Door"}
|
||||
)
|
||||
hass.states.async_set(
|
||||
"cover.garage_door_2", "closed", {ATTR_FRIENDLY_NAME: "Garage Door"}
|
||||
)
|
||||
async_mock_service(hass, "cover", SERVICE_OPEN_COVER)
|
||||
|
||||
client = await hass_client()
|
||||
resp = await client.post(
|
||||
"/api/intent/handle",
|
||||
json={"name": "HassTurnOn", "data": {"name": "Garage Door"}},
|
||||
)
|
||||
assert resp.status == 200
|
||||
data = await resp.json()
|
||||
|
||||
assert "DUPLICATE_NAME" in data["speech"]["plain"]["speech"]
|
||||
|
||||
|
||||
async def test_cover_intents_loading(hass: HomeAssistant) -> None:
|
||||
"""Test Cover Intents Loading."""
|
||||
assert await async_setup_component(hass, "intent", {})
|
||||
|
@@ -38,9 +38,9 @@ async def platform_binary_sensor_fixture():
|
||||
[
|
||||
(0, "off"),
|
||||
(1, "low"),
|
||||
(2, "medium"),
|
||||
(3, "high"),
|
||||
(4, "max"),
|
||||
("2", "medium"),
|
||||
("3", "high"),
|
||||
("4", "max"),
|
||||
],
|
||||
)
|
||||
async def test_select_dialog_level(
|
||||
@@ -49,7 +49,7 @@ async def test_select_dialog_level(
|
||||
soco,
|
||||
entity_registry: er.EntityRegistry,
|
||||
speaker_info: dict[str, str],
|
||||
level: int,
|
||||
level: int | str,
|
||||
result: str,
|
||||
) -> None:
|
||||
"""Test dialog level select entity."""
|
||||
|
Reference in New Issue
Block a user