forked from home-assistant/core
Compare commits
21 Commits
2024.3.0b6
...
2024.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1aa5a07501 | ||
|
|
efe9938b33 | ||
|
|
1b64989909 | ||
|
|
b480b68e3e | ||
|
|
5294b492fc | ||
|
|
080fe4cf5f | ||
|
|
8b2f40390b | ||
|
|
3b63719fad | ||
|
|
061ae756ac | ||
|
|
862bd8ff07 | ||
|
|
742710443a | ||
|
|
015aeadf88 | ||
|
|
b8b654a160 | ||
|
|
3c5b5ca49b | ||
|
|
fb789d95ed | ||
|
|
2e6906c8d4 | ||
|
|
cc8d44bbd1 | ||
|
|
0ad56de6fc | ||
|
|
bc47c80bbf | ||
|
|
aabaa30fa7 | ||
|
|
1ee39275fc |
@@ -11,5 +11,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/airzone",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aioairzone"],
|
||||
"requirements": ["aioairzone==0.7.5"]
|
||||
"requirements": ["aioairzone==0.7.6"]
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["axis"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["axis==49"],
|
||||
"requirements": ["axis==50"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "AXIS"
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/bring",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["bring-api==0.5.4"]
|
||||
"requirements": ["bring-api==0.5.5"]
|
||||
}
|
||||
|
||||
@@ -165,6 +165,7 @@ class DeconzBaseLight(DeconzDevice[_LightDeviceT], LightEntity):
|
||||
"""Representation of a deCONZ light."""
|
||||
|
||||
TYPE = DOMAIN
|
||||
_attr_color_mode = ColorMode.UNKNOWN
|
||||
|
||||
def __init__(self, device: _LightDeviceT, gateway: DeconzGateway) -> None:
|
||||
"""Set up light."""
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20240304.0"]
|
||||
"requirements": ["home-assistant-frontend==20240306.0"]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["holidays==0.43", "babel==2.13.1"]
|
||||
"requirements": ["holidays==0.44", "babel==2.13.1"]
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ from aioautomower.session import AutomowerSession
|
||||
from aiohttp import ClientError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, Platform
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow
|
||||
@@ -17,7 +17,6 @@ from .coordinator import AutomowerDataUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.LAWN_MOWER, Platform.SENSOR, Platform.SWITCH]
|
||||
|
||||
|
||||
@@ -38,13 +37,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
await api_api.async_get_access_token()
|
||||
except ClientError as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
coordinator = AutomowerDataUpdateCoordinator(hass, automower_api)
|
||||
coordinator = AutomowerDataUpdateCoordinator(hass, automower_api, entry)
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
entry.async_create_background_task(
|
||||
hass,
|
||||
coordinator.client_listen(hass, entry, automower_api),
|
||||
"websocket_task",
|
||||
)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, coordinator.shutdown)
|
||||
)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
return True
|
||||
@@ -52,8 +53,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Handle unload of an entry."""
|
||||
coordinator: AutomowerDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
await coordinator.shutdown()
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
hass.data[DOMAIN].pop(entry.entry_id)
|
||||
|
||||
@@ -1,23 +1,28 @@
|
||||
"""Data UpdateCoordinator for the Husqvarna Automower integration."""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aioautomower.exceptions import ApiException, HusqvarnaWSServerHandshakeError
|
||||
from aioautomower.model import MowerAttributes
|
||||
from aioautomower.session import AutomowerSession
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .api import AsyncConfigEntryAuth
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
MAX_WS_RECONNECT_TIME = 600
|
||||
|
||||
|
||||
class AutomowerDataUpdateCoordinator(DataUpdateCoordinator[dict[str, MowerAttributes]]):
|
||||
"""Class to manage fetching Husqvarna data."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, api: AsyncConfigEntryAuth) -> None:
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, api: AutomowerSession, entry: ConfigEntry
|
||||
) -> None:
|
||||
"""Initialize data updater."""
|
||||
super().__init__(
|
||||
hass,
|
||||
@@ -35,13 +40,39 @@ class AutomowerDataUpdateCoordinator(DataUpdateCoordinator[dict[str, MowerAttrib
|
||||
await self.api.connect()
|
||||
self.api.register_data_callback(self.callback)
|
||||
self.ws_connected = True
|
||||
return await self.api.get_status()
|
||||
|
||||
async def shutdown(self, *_: Any) -> None:
|
||||
"""Close resources."""
|
||||
await self.api.close()
|
||||
try:
|
||||
return await self.api.get_status()
|
||||
except ApiException as err:
|
||||
raise UpdateFailed(err) from err
|
||||
|
||||
@callback
|
||||
def callback(self, ws_data: dict[str, MowerAttributes]) -> None:
|
||||
"""Process websocket callbacks and write them to the DataUpdateCoordinator."""
|
||||
self.async_set_updated_data(ws_data)
|
||||
|
||||
async def client_listen(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
automower_client: AutomowerSession,
|
||||
reconnect_time: int = 2,
|
||||
) -> None:
|
||||
"""Listen with the client."""
|
||||
try:
|
||||
await automower_client.auth.websocket_connect()
|
||||
reconnect_time = 2
|
||||
await automower_client.start_listening()
|
||||
except HusqvarnaWSServerHandshakeError as err:
|
||||
_LOGGER.debug(
|
||||
"Failed to connect to websocket. Trying to reconnect: %s", err
|
||||
)
|
||||
|
||||
if not hass.is_stopping:
|
||||
await asyncio.sleep(reconnect_time)
|
||||
reconnect_time = min(reconnect_time * 2, MAX_WS_RECONNECT_TIME)
|
||||
await self.client_listen(
|
||||
hass=hass,
|
||||
entry=entry,
|
||||
automower_client=automower_client,
|
||||
reconnect_time=reconnect_time,
|
||||
)
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"dependencies": ["application_credentials"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/husqvarna_automower",
|
||||
"iot_class": "cloud_push",
|
||||
"requirements": ["aioautomower==2024.2.7"]
|
||||
"requirements": ["aioautomower==2024.2.10"]
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"loggers": ["xknx", "xknxproject"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": [
|
||||
"xknx==2.12.1",
|
||||
"xknx==2.12.2",
|
||||
"xknxproject==3.7.0",
|
||||
"knx-frontend==2024.1.20.105944"
|
||||
]
|
||||
|
||||
@@ -308,7 +308,7 @@ def check_config(config: dict) -> dict:
|
||||
) -> bool:
|
||||
"""Validate entity."""
|
||||
name = entity[CONF_NAME]
|
||||
addr = str(entity[CONF_ADDRESS])
|
||||
addr = f"{hub_name}{entity[CONF_ADDRESS]}"
|
||||
scan_interval = entity.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL)
|
||||
if scan_interval < 5:
|
||||
_LOGGER.warning(
|
||||
@@ -335,11 +335,15 @@ def check_config(config: dict) -> dict:
|
||||
loc_addr: set[str] = {addr}
|
||||
|
||||
if CONF_TARGET_TEMP in entity:
|
||||
loc_addr.add(f"{entity[CONF_TARGET_TEMP]}_{inx}")
|
||||
loc_addr.add(f"{hub_name}{entity[CONF_TARGET_TEMP]}_{inx}")
|
||||
if CONF_HVAC_MODE_REGISTER in entity:
|
||||
loc_addr.add(f"{entity[CONF_HVAC_MODE_REGISTER][CONF_ADDRESS]}_{inx}")
|
||||
loc_addr.add(
|
||||
f"{hub_name}{entity[CONF_HVAC_MODE_REGISTER][CONF_ADDRESS]}_{inx}"
|
||||
)
|
||||
if CONF_FAN_MODE_REGISTER in entity:
|
||||
loc_addr.add(f"{entity[CONF_FAN_MODE_REGISTER][CONF_ADDRESS]}_{inx}")
|
||||
loc_addr.add(
|
||||
f"{hub_name}{entity[CONF_FAN_MODE_REGISTER][CONF_ADDRESS]}_{inx}"
|
||||
)
|
||||
|
||||
dup_addrs = ent_addr.intersection(loc_addr)
|
||||
if len(dup_addrs) > 0:
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["roborock"],
|
||||
"requirements": [
|
||||
"python-roborock==0.39.2",
|
||||
"python-roborock==0.40.0",
|
||||
"vacuum-map-parser-roborock==0.1.1"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"dependencies": ["http"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/tedee",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["pytedee-async==0.2.13"]
|
||||
"requirements": ["pytedee-async==0.2.15"]
|
||||
}
|
||||
|
||||
@@ -196,12 +196,10 @@ class UnifiHub:
|
||||
def async_add_unifi_entities() -> None:
|
||||
"""Add UniFi entity."""
|
||||
async_add_entities(
|
||||
[
|
||||
unifi_platform_entity(obj_id, self, description)
|
||||
for description in descriptions
|
||||
for obj_id in description.api_handler_fn(self.api)
|
||||
if self._async_should_add_entity(description, obj_id)
|
||||
]
|
||||
unifi_platform_entity(obj_id, self, description)
|
||||
for description in descriptions
|
||||
for obj_id in description.api_handler_fn(self.api)
|
||||
if self._async_should_add_entity(description, obj_id)
|
||||
)
|
||||
|
||||
async_add_unifi_entities()
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["holidays"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["holidays==0.43"]
|
||||
"requirements": ["holidays==0.44"]
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ from .helpers.deprecation import (
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2024
|
||||
MINOR_VERSION: Final = 3
|
||||
PATCH_VERSION: Final = "0b6"
|
||||
PATCH_VERSION: Final = "0"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 11, 0)
|
||||
|
||||
@@ -52,6 +52,20 @@ _CallableT = TypeVar("_CallableT", bound=Callable[..., Any])
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BlockedIntegration:
|
||||
"""Blocked custom integration details."""
|
||||
|
||||
lowest_good_version: AwesomeVersion | None
|
||||
reason: str
|
||||
|
||||
|
||||
BLOCKED_CUSTOM_INTEGRATIONS: dict[str, BlockedIntegration] = {
|
||||
# Added in 2024.3.0 because of https://github.com/home-assistant/core/issues/112464
|
||||
"start_time": BlockedIntegration(AwesomeVersion("1.1.7"), "breaks Home Assistant")
|
||||
}
|
||||
|
||||
DATA_COMPONENTS = "components"
|
||||
DATA_INTEGRATIONS = "integrations"
|
||||
DATA_MISSING_PLATFORMS = "missing_platforms"
|
||||
@@ -599,6 +613,7 @@ class Integration:
|
||||
return integration
|
||||
|
||||
_LOGGER.warning(CUSTOM_WARNING, integration.domain)
|
||||
|
||||
if integration.version is None:
|
||||
_LOGGER.error(
|
||||
(
|
||||
@@ -635,6 +650,21 @@ class Integration:
|
||||
integration.version,
|
||||
)
|
||||
return None
|
||||
|
||||
if blocked := BLOCKED_CUSTOM_INTEGRATIONS.get(integration.domain):
|
||||
if _version_blocked(integration.version, blocked):
|
||||
_LOGGER.error(
|
||||
(
|
||||
"Version %s of custom integration '%s' %s and was blocked "
|
||||
"from loading, please %s"
|
||||
),
|
||||
integration.version,
|
||||
integration.domain,
|
||||
blocked.reason,
|
||||
async_suggest_report_issue(None, integration=integration),
|
||||
)
|
||||
return None
|
||||
|
||||
return integration
|
||||
|
||||
return None
|
||||
@@ -1032,6 +1062,20 @@ class Integration:
|
||||
return f"<Integration {self.domain}: {self.pkg_path}>"
|
||||
|
||||
|
||||
def _version_blocked(
|
||||
integration_version: AwesomeVersion,
|
||||
blocked_integration: BlockedIntegration,
|
||||
) -> bool:
|
||||
"""Return True if the integration version is blocked."""
|
||||
if blocked_integration.lowest_good_version is None:
|
||||
return True
|
||||
|
||||
if integration_version >= blocked_integration.lowest_good_version:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _resolve_integrations_from_root(
|
||||
hass: HomeAssistant, root_module: ModuleType, domains: Iterable[str]
|
||||
) -> dict[str, Integration]:
|
||||
@@ -1387,6 +1431,7 @@ def is_component_module_loaded(hass: HomeAssistant, module: str) -> bool:
|
||||
def async_get_issue_tracker(
|
||||
hass: HomeAssistant | None,
|
||||
*,
|
||||
integration: Integration | None = None,
|
||||
integration_domain: str | None = None,
|
||||
module: str | None = None,
|
||||
) -> str | None:
|
||||
@@ -1394,19 +1439,23 @@ def async_get_issue_tracker(
|
||||
issue_tracker = (
|
||||
"https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue"
|
||||
)
|
||||
if not integration_domain and not module:
|
||||
if not integration and not integration_domain and not module:
|
||||
# If we know nothing about the entity, suggest opening an issue on HA core
|
||||
return issue_tracker
|
||||
|
||||
if hass and integration_domain:
|
||||
if not integration and (hass and integration_domain):
|
||||
with suppress(IntegrationNotLoaded):
|
||||
integration = async_get_loaded_integration(hass, integration_domain)
|
||||
if not integration.is_built_in:
|
||||
return integration.issue_tracker
|
||||
|
||||
if integration and not integration.is_built_in:
|
||||
return integration.issue_tracker
|
||||
|
||||
if module and "custom_components" in module:
|
||||
return None
|
||||
|
||||
if integration:
|
||||
integration_domain = integration.domain
|
||||
|
||||
if integration_domain:
|
||||
issue_tracker += f"+label%3A%22integration%3A+{integration_domain}%22"
|
||||
return issue_tracker
|
||||
@@ -1416,15 +1465,21 @@ def async_get_issue_tracker(
|
||||
def async_suggest_report_issue(
|
||||
hass: HomeAssistant | None,
|
||||
*,
|
||||
integration: Integration | None = None,
|
||||
integration_domain: str | None = None,
|
||||
module: str | None = None,
|
||||
) -> str:
|
||||
"""Generate a blurb asking the user to file a bug report."""
|
||||
issue_tracker = async_get_issue_tracker(
|
||||
hass, integration_domain=integration_domain, module=module
|
||||
hass,
|
||||
integration=integration,
|
||||
integration_domain=integration_domain,
|
||||
module=module,
|
||||
)
|
||||
|
||||
if not issue_tracker:
|
||||
if integration:
|
||||
integration_domain = integration.domain
|
||||
if not integration_domain:
|
||||
return "report it to the custom integration author"
|
||||
return (
|
||||
|
||||
@@ -30,7 +30,7 @@ habluetooth==2.4.2
|
||||
hass-nabucasa==0.78.0
|
||||
hassil==1.6.1
|
||||
home-assistant-bluetooth==1.12.0
|
||||
home-assistant-frontend==20240304.0
|
||||
home-assistant-frontend==20240306.0
|
||||
home-assistant-intents==2024.2.28
|
||||
httpx==0.27.0
|
||||
ifaddr==0.2.0
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2024.3.0b6"
|
||||
version = "2024.3.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
||||
@@ -191,7 +191,7 @@ aioairq==0.3.2
|
||||
aioairzone-cloud==0.4.5
|
||||
|
||||
# homeassistant.components.airzone
|
||||
aioairzone==0.7.5
|
||||
aioairzone==0.7.6
|
||||
|
||||
# homeassistant.components.ambient_station
|
||||
aioambient==2024.01.0
|
||||
@@ -206,7 +206,7 @@ aioaseko==0.0.2
|
||||
aioasuswrt==1.4.0
|
||||
|
||||
# homeassistant.components.husqvarna_automower
|
||||
aioautomower==2024.2.7
|
||||
aioautomower==2024.2.10
|
||||
|
||||
# homeassistant.components.azure_devops
|
||||
aioazuredevops==1.3.5
|
||||
@@ -514,7 +514,7 @@ aurorapy==0.2.7
|
||||
# avion==0.10
|
||||
|
||||
# homeassistant.components.axis
|
||||
axis==49
|
||||
axis==50
|
||||
|
||||
# homeassistant.components.azure_event_hub
|
||||
azure-eventhub==5.11.1
|
||||
@@ -603,7 +603,7 @@ boschshcpy==0.2.75
|
||||
boto3==1.33.13
|
||||
|
||||
# homeassistant.components.bring
|
||||
bring-api==0.5.4
|
||||
bring-api==0.5.5
|
||||
|
||||
# homeassistant.components.broadlink
|
||||
broadlink==0.18.3
|
||||
@@ -1071,10 +1071,10 @@ hole==0.8.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.43
|
||||
holidays==0.44
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20240304.0
|
||||
home-assistant-frontend==20240306.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2024.2.28
|
||||
@@ -2182,7 +2182,7 @@ pyswitchbee==1.8.0
|
||||
pytautulli==23.1.1
|
||||
|
||||
# homeassistant.components.tedee
|
||||
pytedee-async==0.2.13
|
||||
pytedee-async==0.2.15
|
||||
|
||||
# homeassistant.components.tfiac
|
||||
pytfiac==0.4
|
||||
@@ -2285,7 +2285,7 @@ python-rabbitair==0.0.8
|
||||
python-ripple-api==0.0.3
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==0.39.2
|
||||
python-roborock==0.40.0
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.36
|
||||
@@ -2872,7 +2872,7 @@ xbox-webapi==2.0.11
|
||||
xiaomi-ble==0.25.2
|
||||
|
||||
# homeassistant.components.knx
|
||||
xknx==2.12.1
|
||||
xknx==2.12.2
|
||||
|
||||
# homeassistant.components.knx
|
||||
xknxproject==3.7.0
|
||||
|
||||
@@ -170,7 +170,7 @@ aioairq==0.3.2
|
||||
aioairzone-cloud==0.4.5
|
||||
|
||||
# homeassistant.components.airzone
|
||||
aioairzone==0.7.5
|
||||
aioairzone==0.7.6
|
||||
|
||||
# homeassistant.components.ambient_station
|
||||
aioambient==2024.01.0
|
||||
@@ -185,7 +185,7 @@ aioaseko==0.0.2
|
||||
aioasuswrt==1.4.0
|
||||
|
||||
# homeassistant.components.husqvarna_automower
|
||||
aioautomower==2024.2.7
|
||||
aioautomower==2024.2.10
|
||||
|
||||
# homeassistant.components.azure_devops
|
||||
aioazuredevops==1.3.5
|
||||
@@ -454,7 +454,7 @@ auroranoaa==0.0.3
|
||||
aurorapy==0.2.7
|
||||
|
||||
# homeassistant.components.axis
|
||||
axis==49
|
||||
axis==50
|
||||
|
||||
# homeassistant.components.azure_event_hub
|
||||
azure-eventhub==5.11.1
|
||||
@@ -514,7 +514,7 @@ bond-async==0.2.1
|
||||
boschshcpy==0.2.75
|
||||
|
||||
# homeassistant.components.bring
|
||||
bring-api==0.5.4
|
||||
bring-api==0.5.5
|
||||
|
||||
# homeassistant.components.broadlink
|
||||
broadlink==0.18.3
|
||||
@@ -870,10 +870,10 @@ hole==0.8.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.43
|
||||
holidays==0.44
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20240304.0
|
||||
home-assistant-frontend==20240306.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2024.2.28
|
||||
@@ -1697,7 +1697,7 @@ pyswitchbee==1.8.0
|
||||
pytautulli==23.1.1
|
||||
|
||||
# homeassistant.components.tedee
|
||||
pytedee-async==0.2.13
|
||||
pytedee-async==0.2.15
|
||||
|
||||
# homeassistant.components.motionmount
|
||||
python-MotionMount==0.3.1
|
||||
@@ -1755,7 +1755,7 @@ python-qbittorrent==0.4.3
|
||||
python-rabbitair==0.0.8
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==0.39.2
|
||||
python-roborock==0.40.0
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.36
|
||||
@@ -2207,7 +2207,7 @@ xbox-webapi==2.0.11
|
||||
xiaomi-ble==0.25.2
|
||||
|
||||
# homeassistant.components.knx
|
||||
xknx==2.12.1
|
||||
xknx==2.12.2
|
||||
|
||||
# homeassistant.components.knx
|
||||
xknxproject==3.7.0
|
||||
|
||||
@@ -1380,10 +1380,147 @@ async def test_verify_group_supported_features(
|
||||
|
||||
assert len(hass.states.async_all()) == 4
|
||||
|
||||
assert hass.states.get("light.group").state == STATE_ON
|
||||
group_state = hass.states.get("light.group")
|
||||
assert group_state.state == STATE_ON
|
||||
assert group_state.attributes[ATTR_COLOR_MODE] == ColorMode.COLOR_TEMP
|
||||
assert (
|
||||
hass.states.get("light.group").attributes[ATTR_SUPPORTED_FEATURES]
|
||||
group_state.attributes[ATTR_SUPPORTED_FEATURES]
|
||||
== LightEntityFeature.TRANSITION
|
||||
| LightEntityFeature.FLASH
|
||||
| LightEntityFeature.EFFECT
|
||||
)
|
||||
|
||||
|
||||
async def test_verify_group_color_mode_fallback(
|
||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, mock_deconz_websocket
|
||||
) -> None:
|
||||
"""Test that group supported features reflect what included lights support."""
|
||||
data = {
|
||||
"groups": {
|
||||
"43": {
|
||||
"action": {
|
||||
"alert": "none",
|
||||
"bri": 127,
|
||||
"colormode": "hs",
|
||||
"ct": 0,
|
||||
"effect": "none",
|
||||
"hue": 0,
|
||||
"on": True,
|
||||
"sat": 127,
|
||||
"scene": "4",
|
||||
"xy": [0, 0],
|
||||
},
|
||||
"devicemembership": [],
|
||||
"etag": "4548e982c4cfff942f7af80958abb2a0",
|
||||
"id": "43",
|
||||
"lights": ["13"],
|
||||
"name": "Opbergruimte",
|
||||
"scenes": [
|
||||
{
|
||||
"id": "1",
|
||||
"lightcount": 1,
|
||||
"name": "Scene Normaal deCONZ",
|
||||
"transitiontime": 10,
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"lightcount": 1,
|
||||
"name": "Scene Fel deCONZ",
|
||||
"transitiontime": 10,
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"lightcount": 1,
|
||||
"name": "Scene Gedimd deCONZ",
|
||||
"transitiontime": 10,
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"lightcount": 1,
|
||||
"name": "Scene Uit deCONZ",
|
||||
"transitiontime": 10,
|
||||
},
|
||||
],
|
||||
"state": {"all_on": False, "any_on": False},
|
||||
"type": "LightGroup",
|
||||
},
|
||||
},
|
||||
"lights": {
|
||||
"13": {
|
||||
"capabilities": {
|
||||
"alerts": [
|
||||
"none",
|
||||
"select",
|
||||
"lselect",
|
||||
"blink",
|
||||
"breathe",
|
||||
"okay",
|
||||
"channelchange",
|
||||
"finish",
|
||||
"stop",
|
||||
],
|
||||
"bri": {"min_dim_level": 5},
|
||||
},
|
||||
"config": {
|
||||
"bri": {"execute_if_off": True, "startup": "previous"},
|
||||
"groups": ["43"],
|
||||
"on": {"startup": "previous"},
|
||||
},
|
||||
"etag": "ca0ed7763eca37f5e6b24f6d46f8a518",
|
||||
"hascolor": False,
|
||||
"lastannounced": None,
|
||||
"lastseen": "2024-03-02T20:08Z",
|
||||
"manufacturername": "Signify Netherlands B.V.",
|
||||
"modelid": "LWA001",
|
||||
"name": "Opbergruimte Lamp Plafond",
|
||||
"productid": "Philips-LWA001-1-A19DLv5",
|
||||
"productname": "Hue white lamp",
|
||||
"state": {
|
||||
"alert": "none",
|
||||
"bri": 76,
|
||||
"effect": "none",
|
||||
"on": False,
|
||||
"reachable": True,
|
||||
},
|
||||
"swconfigid": "87169548",
|
||||
"swversion": "1.104.2",
|
||||
"type": "Dimmable light",
|
||||
"uniqueid": "00:17:88:01:08:11:22:33-01",
|
||||
},
|
||||
},
|
||||
}
|
||||
with patch.dict(DECONZ_WEB_REQUEST, data):
|
||||
await setup_deconz_integration(hass, aioclient_mock)
|
||||
|
||||
group_state = hass.states.get("light.opbergruimte")
|
||||
assert group_state.state == STATE_OFF
|
||||
assert group_state.attributes[ATTR_COLOR_MODE] is None
|
||||
|
||||
await mock_deconz_websocket(
|
||||
data={
|
||||
"e": "changed",
|
||||
"id": "13",
|
||||
"r": "lights",
|
||||
"state": {
|
||||
"alert": "none",
|
||||
"bri": 76,
|
||||
"effect": "none",
|
||||
"on": True,
|
||||
"reachable": True,
|
||||
},
|
||||
"t": "event",
|
||||
"uniqueid": "00:17:88:01:08:11:22:33-01",
|
||||
}
|
||||
)
|
||||
await mock_deconz_websocket(
|
||||
data={
|
||||
"e": "changed",
|
||||
"id": "43",
|
||||
"r": "groups",
|
||||
"state": {"all_on": True, "any_on": True},
|
||||
"t": "event",
|
||||
}
|
||||
)
|
||||
group_state = hass.states.get("light.opbergruimte")
|
||||
assert group_state.state == STATE_ON
|
||||
assert group_state.attributes[ATTR_COLOR_MODE] is ColorMode.UNKNOWN
|
||||
|
||||
@@ -4,6 +4,7 @@ import time
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from aioautomower.utils import mower_list_to_dictionary_dataclass
|
||||
from aiohttp import ClientWebSocketResponse
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.application_credentials import (
|
||||
@@ -82,4 +83,11 @@ def mock_automower_client() -> Generator[AsyncMock, None, None]:
|
||||
client.get_status.return_value = mower_list_to_dictionary_dataclass(
|
||||
load_json_value_fixture("mower.json", DOMAIN)
|
||||
)
|
||||
|
||||
async def websocket_connect() -> ClientWebSocketResponse:
|
||||
"""Mock listen."""
|
||||
return ClientWebSocketResponse
|
||||
|
||||
client.auth = AsyncMock(side_effect=websocket_connect)
|
||||
|
||||
yield client
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
"""Tests for init module."""
|
||||
from datetime import timedelta
|
||||
import http
|
||||
import time
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from aioautomower.exceptions import ApiException, HusqvarnaWSServerHandshakeError
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.husqvarna_automower.const import DOMAIN, OAUTH2_TOKEN
|
||||
@@ -11,7 +14,7 @@ from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import setup_integration
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
|
||||
@@ -66,3 +69,42 @@ async def test_expired_token_refresh_failure(
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert mock_config_entry.state is expected_state
|
||||
|
||||
|
||||
async def test_update_failed(
|
||||
hass: HomeAssistant,
|
||||
mock_automower_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test load and unload entry."""
|
||||
getattr(mock_automower_client, "get_status").side_effect = ApiException(
|
||||
"Test error"
|
||||
)
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
|
||||
assert entry.state == ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
async def test_websocket_not_available(
|
||||
hass: HomeAssistant,
|
||||
mock_automower_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test trying reload the websocket."""
|
||||
mock_automower_client.start_listening.side_effect = HusqvarnaWSServerHandshakeError(
|
||||
"Boom"
|
||||
)
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
assert "Failed to connect to websocket. Trying to reconnect: Boom" in caplog.text
|
||||
assert mock_automower_client.auth.websocket_connect.call_count == 1
|
||||
assert mock_automower_client.start_listening.call_count == 1
|
||||
assert mock_config_entry.state == ConfigEntryState.LOADED
|
||||
freezer.tick(timedelta(seconds=2))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert mock_automower_client.auth.websocket_connect.call_count == 2
|
||||
assert mock_automower_client.start_listening.call_count == 2
|
||||
assert mock_config_entry.state == ConfigEntryState.LOADED
|
||||
|
||||
@@ -740,6 +740,133 @@ async def test_duplicate_fan_mode_validator(do_config) -> None:
|
||||
assert len(do_config[CONF_FAN_MODE_VALUES]) == 2
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("do_config", "sensor_cnt"),
|
||||
[
|
||||
(
|
||||
[
|
||||
{
|
||||
CONF_NAME: TEST_MODBUS_NAME,
|
||||
CONF_TYPE: TCP,
|
||||
CONF_HOST: TEST_MODBUS_HOST,
|
||||
CONF_PORT: TEST_PORT_TCP,
|
||||
CONF_TIMEOUT: 3,
|
||||
CONF_SENSORS: [
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME,
|
||||
CONF_ADDRESS: 117,
|
||||
CONF_SLAVE: 0,
|
||||
},
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME + "1",
|
||||
CONF_ADDRESS: 119,
|
||||
CONF_SLAVE: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
2,
|
||||
),
|
||||
(
|
||||
[
|
||||
{
|
||||
CONF_NAME: TEST_MODBUS_NAME,
|
||||
CONF_TYPE: TCP,
|
||||
CONF_HOST: TEST_MODBUS_HOST,
|
||||
CONF_PORT: TEST_PORT_TCP,
|
||||
CONF_TIMEOUT: 3,
|
||||
CONF_SENSORS: [
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME,
|
||||
CONF_ADDRESS: 117,
|
||||
CONF_SLAVE: 0,
|
||||
},
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME + "1",
|
||||
CONF_ADDRESS: 117,
|
||||
CONF_SLAVE: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
2,
|
||||
),
|
||||
(
|
||||
[
|
||||
{
|
||||
CONF_NAME: TEST_MODBUS_NAME,
|
||||
CONF_TYPE: TCP,
|
||||
CONF_HOST: TEST_MODBUS_HOST,
|
||||
CONF_PORT: TEST_PORT_TCP,
|
||||
CONF_TIMEOUT: 3,
|
||||
CONF_SENSORS: [
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME,
|
||||
CONF_ADDRESS: 117,
|
||||
CONF_SLAVE: 0,
|
||||
},
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME + "1",
|
||||
CONF_ADDRESS: 117,
|
||||
CONF_SLAVE: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
1,
|
||||
),
|
||||
(
|
||||
[
|
||||
{
|
||||
CONF_NAME: TEST_MODBUS_NAME,
|
||||
CONF_TYPE: TCP,
|
||||
CONF_HOST: TEST_MODBUS_HOST,
|
||||
CONF_PORT: TEST_PORT_TCP,
|
||||
CONF_TIMEOUT: 3,
|
||||
CONF_SENSORS: [
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME,
|
||||
CONF_ADDRESS: 117,
|
||||
CONF_SLAVE: 0,
|
||||
},
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME + "1",
|
||||
CONF_ADDRESS: 119,
|
||||
CONF_SLAVE: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
CONF_NAME: TEST_MODBUS_NAME + "1",
|
||||
CONF_TYPE: TCP,
|
||||
CONF_HOST: TEST_MODBUS_HOST,
|
||||
CONF_PORT: TEST_PORT_TCP,
|
||||
CONF_TIMEOUT: 3,
|
||||
CONF_SENSORS: [
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME,
|
||||
CONF_ADDRESS: 117,
|
||||
CONF_SLAVE: 0,
|
||||
},
|
||||
{
|
||||
CONF_NAME: TEST_ENTITY_NAME,
|
||||
CONF_ADDRESS: 119,
|
||||
CONF_SLAVE: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
2,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_duplicate_addresses(do_config, sensor_cnt) -> None:
|
||||
"""Test duplicate entity validator."""
|
||||
check_config(do_config)
|
||||
use_inx = len(do_config) - 1
|
||||
assert len(do_config[use_inx][CONF_SENSORS]) == sensor_cnt
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"do_config",
|
||||
[
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
'coordinators': dict({
|
||||
'**REDACTED-0**': dict({
|
||||
'api': dict({
|
||||
'misc_info': dict({
|
||||
}),
|
||||
}),
|
||||
'roborock_device_info': dict({
|
||||
'device': dict({
|
||||
@@ -309,6 +311,8 @@
|
||||
}),
|
||||
'**REDACTED-1**': dict({
|
||||
'api': dict({
|
||||
'misc_info': dict({
|
||||
}),
|
||||
}),
|
||||
'roborock_device_info': dict({
|
||||
'device': dict({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Tests for the diagnostics data provided by the Roborock integration."""
|
||||
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
from syrupy.filters import props
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@@ -20,4 +21,4 @@ async def test_diagnostics(
|
||||
result = await get_diagnostics_for_config_entry(hass, hass_client, setup_entry)
|
||||
|
||||
assert isinstance(result, dict)
|
||||
assert result == snapshot
|
||||
assert result == snapshot(exclude=props("Nonce"))
|
||||
|
||||
@@ -4,6 +4,7 @@ import sys
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
from awesomeversion import AwesomeVersion
|
||||
import pytest
|
||||
|
||||
from homeassistant import loader
|
||||
@@ -163,6 +164,57 @@ async def test_custom_integration_version_not_valid(
|
||||
) in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"blocked_versions",
|
||||
[
|
||||
loader.BlockedIntegration(None, "breaks Home Assistant"),
|
||||
loader.BlockedIntegration(AwesomeVersion("2.0.0"), "breaks Home Assistant"),
|
||||
],
|
||||
)
|
||||
async def test_custom_integration_version_blocked(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
enable_custom_integrations: None,
|
||||
blocked_versions,
|
||||
) -> None:
|
||||
"""Test that we log a warning when custom integrations have a blocked version."""
|
||||
with patch.dict(
|
||||
loader.BLOCKED_CUSTOM_INTEGRATIONS, {"test_blocked_version": blocked_versions}
|
||||
):
|
||||
with pytest.raises(loader.IntegrationNotFound):
|
||||
await loader.async_get_integration(hass, "test_blocked_version")
|
||||
|
||||
assert (
|
||||
"Version 1.0.0 of custom integration 'test_blocked_version' breaks"
|
||||
" Home Assistant and was blocked from loading, please report it to the"
|
||||
" author of the 'test_blocked_version' custom integration"
|
||||
) in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"blocked_versions",
|
||||
[
|
||||
loader.BlockedIntegration(AwesomeVersion("0.9.9"), "breaks Home Assistant"),
|
||||
loader.BlockedIntegration(AwesomeVersion("1.0.0"), "breaks Home Assistant"),
|
||||
],
|
||||
)
|
||||
async def test_custom_integration_version_not_blocked(
|
||||
hass: HomeAssistant,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
enable_custom_integrations: None,
|
||||
blocked_versions,
|
||||
) -> None:
|
||||
"""Test that we log a warning when custom integrations have a blocked version."""
|
||||
with patch.dict(
|
||||
loader.BLOCKED_CUSTOM_INTEGRATIONS, {"test_blocked_version": blocked_versions}
|
||||
):
|
||||
await loader.async_get_integration(hass, "test_blocked_version")
|
||||
|
||||
assert (
|
||||
"Version 1.0.0 of custom integration 'test_blocked_version'"
|
||||
) not in caplog.text
|
||||
|
||||
|
||||
async def test_get_integration(hass: HomeAssistant) -> None:
|
||||
"""Test resolving integration."""
|
||||
with pytest.raises(loader.IntegrationNotLoaded):
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"domain": "test_blocked_version",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
Reference in New Issue
Block a user