Compare commits

...

18 Commits

Author SHA1 Message Date
Franck Nijhof
0cda883b56 2025.9.1 (#151766) 2025-09-05 13:13:34 +02:00
Franck Nijhof
ae58e633f0 Bump version to 2025.9.1 2025-09-05 10:33:36 +00:00
jan iversen
06480bfd9d Fix enable/disable entity in modbus (#151626) 2025-09-05 10:33:04 +00:00
Artur Pragacz
625f586945 Fix recognition of entity names in default agent with interpunction (#151759) 2025-09-05 10:30:27 +00:00
Richard Kroegel
7dbeaa475d Bump bimmer_connected to 0.17.3 (#151756) 2025-09-05 10:30:24 +00:00
David Knowles
dff3d5f8af Bump pyschlage to 2025.9.0 (#151731) 2025-09-05 10:30:21 +00:00
Michael Hansen
89c335919a Handle match failures in intent HTTP API (#151726) 2025-09-05 10:30:16 +00:00
Daniel Hjelseth Høyer
2bb4573357 Update Mill library 0.13.1 (#151712) 2025-09-05 10:30:01 +00:00
Dan Raper
7037ce989c Bump ohmepy version to 1.5.2 (#151707) 2025-09-05 10:29:58 +00:00
Dan Raper
bfdd2053ba Require OhmeAdvancedSettingsCoordinator to run regardless of entities (#151701) 2025-09-05 10:29:55 +00:00
Bram Kragten
fcc3f92f8c Update frontend to 20250903.3 (#151694) 2025-09-05 10:29:51 +00:00
Marcel van der Veldt
8710267d53 Bump aiohue to 4.7.5 (#151684) 2025-09-05 10:29:48 +00:00
Imeon-Energy
85b6adcc9a Fix, entities stay unavailable after timeout error, Imeon inverter integration (#151671)
Co-authored-by: TheBushBoy <theodavid@icloud.com>
2025-09-05 10:29:46 +00:00
Felipe Santos
beec6e86e0 Fix WebSocket proxy for add-ons not forwarding ping/pong frame data (#151654) 2025-09-05 10:29:45 +00:00
Pete Sage
3dacffaaf9 Fix Sonos Dialog Select type conversion (#151649) 2025-09-05 10:29:41 +00:00
Manu
d90f2a1de1 Correct capitalization of "FRITZ!Box" in FRITZ!Box Tools integration (#151637) 2025-09-05 10:29:39 +00:00
karwosts
b6c9217429 Add missing device trigger duration localizations (#151578)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-09-05 10:29:37 +00:00
Marcel van der Veldt
7fc8da6769 Add support for migrated Hue bridge (#151411)
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-09-05 10:29:34 +00:00
31 changed files with 500 additions and 56 deletions

View File

@@ -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"]
}

View File

@@ -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 = {

View File

@@ -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": {

View File

@@ -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%]",

View File

@@ -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"]
}

View File

@@ -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:

View File

@@ -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."""

View File

@@ -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."]
}

View File

@@ -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",

View File

@@ -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))

View File

@@ -57,7 +57,8 @@
},
"extra_fields": {
"brightness_pct": "Brightness",
"flash": "Flash"
"flash": "Flash",
"for": "[%key:common::device_automation::extra_fields::for%]"
}
},
"entity_component": {

View File

@@ -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"]
}

View File

@@ -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):

View File

@@ -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()

View File

@@ -7,5 +7,5 @@
"integration_type": "device",
"iot_class": "cloud_polling",
"quality_scale": "platinum",
"requirements": ["ohme==1.5.1"]
"requirements": ["ohme==1.5.2"]
}

View File

@@ -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": {

View File

@@ -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"]
}

View File

@@ -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:

View File

@@ -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()

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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)

View File

@@ -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

View File

@@ -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
View File

@@ -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

View File

@@ -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

View File

@@ -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({

View File

@@ -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,

View File

@@ -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"

View File

@@ -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", {})

View File

@@ -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."""