mirror of
https://github.com/home-assistant/core.git
synced 2026-05-20 15:55:17 +02:00
Compare commits
15 Commits
frenck-2026-0608
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| abd8d85225 | |||
| 626a1a5c87 | |||
| 1b2e8ccc0f | |||
| 4da2cd465a | |||
| e3c31a3482 | |||
| a0b52e0f58 | |||
| 75dd509c7b | |||
| 6540ccd52a | |||
| a35ad41495 | |||
| cedf5a5861 | |||
| 16f4dc74bf | |||
| c5f22936e4 | |||
| aa23b3176c | |||
| a144bbab2b | |||
| 6a20b99252 |
@@ -18,6 +18,13 @@ description: Reviews GitHub pull requests and provides feedback comments. This i
|
||||
4. Ensure any existing review comments have been addressed.
|
||||
5. Generate constructive review comments in the CONSOLE. DO NOT POST TO GITHUB YOURSELF.
|
||||
|
||||
## Verification:
|
||||
|
||||
- After the review, run parallel subagents for each finding to double check it.
|
||||
- Spawn up to a maximum of 10 parallel subagents at a time.
|
||||
- Gather the results from the subagents and summarize them in the final review comments.
|
||||
|
||||
|
||||
## IMPORTANT:
|
||||
- Just review. DO NOT make any changes
|
||||
- Be constructive and specific in your comments
|
||||
|
||||
@@ -14,7 +14,7 @@ env:
|
||||
UV_HTTP_TIMEOUT: 60
|
||||
UV_SYSTEM_PYTHON: "true"
|
||||
# Base image version from https://github.com/home-assistant/docker
|
||||
BASE_IMAGE_VERSION: "2026.04.0"
|
||||
BASE_IMAGE_VERSION: "2026.05.0"
|
||||
ARCHITECTURES: '["amd64", "aarch64"]'
|
||||
|
||||
permissions: {}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
3.14.4
|
||||
3.14.5
|
||||
|
||||
@@ -17,6 +17,8 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
|
||||
from .const import TIMEOUT
|
||||
|
||||
type CalDavConfigEntry = ConfigEntry[caldav.DAVClient]
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -32,7 +34,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: CalDavConfigEntry) -> bo
|
||||
username=entry.data[CONF_USERNAME],
|
||||
password=entry.data[CONF_PASSWORD],
|
||||
ssl_verify_cert=entry.data[CONF_VERIFY_SSL],
|
||||
timeout=30,
|
||||
timeout=TIMEOUT,
|
||||
)
|
||||
try:
|
||||
await hass.async_add_executor_job(client.principal)
|
||||
|
||||
@@ -13,7 +13,7 @@ from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME, CONF_VERIFY_SSL
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, TIMEOUT
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -65,6 +65,7 @@ class CalDavConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
username=user_input[CONF_USERNAME],
|
||||
password=user_input[CONF_PASSWORD],
|
||||
ssl_verify_cert=user_input[CONF_VERIFY_SSL],
|
||||
timeout=TIMEOUT,
|
||||
)
|
||||
try:
|
||||
await self.hass.async_add_executor_job(client.principal)
|
||||
@@ -75,6 +76,9 @@ class CalDavConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
# AuthorizationError can be raised if the url is incorrect or
|
||||
# on some other unexpected server response.
|
||||
return "cannot_connect"
|
||||
except requests.Timeout as err:
|
||||
_LOGGER.warning("Timeout connecting to CalDAV server: %s", err)
|
||||
return "cannot_connect"
|
||||
except requests.ConnectionError as err:
|
||||
_LOGGER.warning("Connection Error connecting to CalDAV server: %s", err)
|
||||
return "cannot_connect"
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
from typing import Final
|
||||
|
||||
DOMAIN: Final = "caldav"
|
||||
TIMEOUT: Final = 30
|
||||
|
||||
@@ -7,6 +7,7 @@ from homeassistant.const import CONF_ADDRESS, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import CasperGlowConfigEntry, CasperGlowCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [
|
||||
@@ -24,7 +25,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: CasperGlowConfigEntry) -
|
||||
ble_device = bluetooth.async_ble_device_from_address(hass, address.upper(), True)
|
||||
if not ble_device:
|
||||
raise ConfigEntryNotReady(
|
||||
f"Could not find Casper Glow device with address {address}"
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="device_not_found",
|
||||
translation_placeholders={"address": address},
|
||||
)
|
||||
|
||||
glow = CasperGlow(ble_device)
|
||||
|
||||
@@ -54,6 +54,9 @@
|
||||
"exceptions": {
|
||||
"communication_error": {
|
||||
"message": "An error occurred while communicating with the Casper Glow: {error}"
|
||||
},
|
||||
"device_not_found": {
|
||||
"message": "Could not find Casper Glow device with address {address}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ from homeassistant.components.sensor import (
|
||||
)
|
||||
from homeassistant.const import (
|
||||
APPLICATION_NAME,
|
||||
ATTR_LATITUDE,
|
||||
ATTR_LONGITUDE,
|
||||
CONF_LATITUDE,
|
||||
CONF_LONGITUDE,
|
||||
CONF_NAME,
|
||||
@@ -45,10 +47,6 @@ HA_USER_AGENT = (
|
||||
)
|
||||
|
||||
ATTR_UID = "uid"
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
ATTR_LATITUDE = "latitude"
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
ATTR_LONGITUDE = "longitude"
|
||||
ATTR_EMPTY_SLOTS = "empty_slots"
|
||||
ATTR_FREE_EBIKES = "free_ebikes"
|
||||
ATTR_TIMESTAMP = "timestamp"
|
||||
|
||||
@@ -60,7 +60,11 @@ class DucoCoordinator(DataUpdateCoordinator[DucoData]):
|
||||
translation_placeholders={"error": repr(err)},
|
||||
) from err
|
||||
except DucoError as err:
|
||||
raise ConfigEntryError(f"Duco API error: {err}") from err
|
||||
raise ConfigEntryError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="api_error",
|
||||
translation_placeholders={"error": repr(err)},
|
||||
) from err
|
||||
|
||||
async def _async_update_data(self) -> DucoData:
|
||||
"""Fetch node data from the Duco box."""
|
||||
|
||||
@@ -5,7 +5,11 @@ from typing import Any
|
||||
|
||||
from google_air_quality_api.api import GoogleAirQualityApi
|
||||
from google_air_quality_api.auth import Auth
|
||||
from google_air_quality_api.exceptions import GoogleAirQualityApiError
|
||||
from google_air_quality_api.exceptions import (
|
||||
GoogleAirQualityApiError,
|
||||
InvalidCustomLAQIConfigurationError,
|
||||
)
|
||||
from google_air_quality_api.mapping import AQICategoryMapping
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import (
|
||||
@@ -18,6 +22,7 @@ from homeassistant.config_entries import (
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_API_KEY,
|
||||
CONF_COUNTRY,
|
||||
CONF_LATITUDE,
|
||||
CONF_LOCATION,
|
||||
CONF_LONGITUDE,
|
||||
@@ -26,11 +31,28 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.data_entry_flow import SectionConfig, section
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.selector import LocationSelector, LocationSelectorConfig
|
||||
from homeassistant.helpers.selector import (
|
||||
CountrySelector,
|
||||
LocationSelector,
|
||||
LocationSelectorConfig,
|
||||
SelectSelector,
|
||||
SelectSelectorConfig,
|
||||
SelectSelectorMode,
|
||||
)
|
||||
|
||||
from .const import CONF_REFERRER, DOMAIN, SECTION_API_KEY_OPTIONS
|
||||
from .const import (
|
||||
CONF_ENABLE_CUSTOM_LAQI,
|
||||
CONF_REFERRER,
|
||||
CUSTOM_LAQI,
|
||||
CUSTOM_LOCAL_AQI_OPTIONS,
|
||||
DOMAIN,
|
||||
SECTION_API_KEY_OPTIONS,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
AIR_QUALITY_COVERAGE_URL = (
|
||||
"https://developers.google.com/maps/documentation/air-quality/coverage"
|
||||
)
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
@@ -50,10 +72,31 @@ async def _validate_input(
|
||||
description_placeholders: dict[str, str],
|
||||
) -> bool:
|
||||
try:
|
||||
await api.async_get_current_conditions(
|
||||
lat=user_input[CONF_LOCATION][CONF_LATITUDE],
|
||||
lon=user_input[CONF_LOCATION][CONF_LONGITUDE],
|
||||
)
|
||||
custom_options = user_input.get(CUSTOM_LOCAL_AQI_OPTIONS) or {}
|
||||
enable_custom_laqi = custom_options.get(CONF_ENABLE_CUSTOM_LAQI)
|
||||
|
||||
if enable_custom_laqi:
|
||||
country = custom_options.get(CONF_COUNTRY)
|
||||
custom_laqi = custom_options.get(CUSTOM_LAQI)
|
||||
|
||||
# When custom LAQI is enabled, both country and custom_laqi must be provided
|
||||
if not country or not custom_laqi:
|
||||
errors[CUSTOM_LOCAL_AQI_OPTIONS] = "missing_custom_laqi_options"
|
||||
return False
|
||||
|
||||
await api.async_get_current_conditions(
|
||||
lat=user_input[CONF_LOCATION][CONF_LATITUDE],
|
||||
lon=user_input[CONF_LOCATION][CONF_LONGITUDE],
|
||||
region_code=country,
|
||||
custom_local_aqi=custom_laqi,
|
||||
)
|
||||
else:
|
||||
await api.async_get_current_conditions(
|
||||
lat=user_input[CONF_LOCATION][CONF_LATITUDE],
|
||||
lon=user_input[CONF_LOCATION][CONF_LONGITUDE],
|
||||
)
|
||||
except InvalidCustomLAQIConfigurationError:
|
||||
errors["base"] = "mismatch_country_and_laqi"
|
||||
except GoogleAirQualityApiError as err:
|
||||
errors["base"] = "cannot_connect"
|
||||
description_placeholders["error_message"] = str(err)
|
||||
@@ -79,6 +122,25 @@ def _get_location_schema(hass: HomeAssistant) -> vol.Schema:
|
||||
CONF_LONGITUDE: hass.config.longitude,
|
||||
},
|
||||
): LocationSelector(LocationSelectorConfig(radius=False)),
|
||||
vol.Optional(CUSTOM_LOCAL_AQI_OPTIONS): section(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_ENABLE_CUSTOM_LAQI, default=False): bool,
|
||||
vol.Optional(
|
||||
CONF_COUNTRY, default=hass.config.country
|
||||
): CountrySelector(),
|
||||
vol.Optional(CUSTOM_LAQI): SelectSelector(
|
||||
SelectSelectorConfig(
|
||||
options=sorted(
|
||||
AQICategoryMapping.get_all_laq_indices()
|
||||
),
|
||||
mode=SelectSelectorMode.DROPDOWN,
|
||||
)
|
||||
),
|
||||
}
|
||||
),
|
||||
SectionConfig(collapsed=True),
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -123,6 +185,7 @@ class GoogleAirQualityConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors: dict[str, str] = {}
|
||||
description_placeholders: dict[str, str] = {
|
||||
"api_key_url": "https://developers.google.com/maps/documentation/air-quality/get-api-key",
|
||||
"air_quality_coverage_url": AIR_QUALITY_COVERAGE_URL,
|
||||
"restricting_api_keys_url": "https://developers.google.com/maps/api-security-best-practices#restricting-api-keys",
|
||||
}
|
||||
if user_input is not None:
|
||||
@@ -132,10 +195,13 @@ class GoogleAirQualityConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
if _is_location_already_configured(self.hass, user_input[CONF_LOCATION]):
|
||||
return self.async_abort(reason="already_configured")
|
||||
session = async_get_clientsession(self.hass)
|
||||
referrer = user_input.get(SECTION_API_KEY_OPTIONS, {}).get(CONF_REFERRER)
|
||||
auth = Auth(session, user_input[CONF_API_KEY], referrer=referrer)
|
||||
api = GoogleAirQualityApi(auth)
|
||||
if await _validate_input(user_input, api, errors, description_placeholders):
|
||||
subentry_data = dict(user_input[CONF_LOCATION])
|
||||
custom_opts = user_input.get(CUSTOM_LOCAL_AQI_OPTIONS)
|
||||
if custom_opts and custom_opts.get(CONF_ENABLE_CUSTOM_LAQI):
|
||||
subentry_data[CUSTOM_LOCAL_AQI_OPTIONS] = custom_opts
|
||||
return self.async_create_entry(
|
||||
title="Google Air Quality",
|
||||
data={
|
||||
@@ -145,7 +211,7 @@ class GoogleAirQualityConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
subentries=[
|
||||
{
|
||||
"subentry_type": "location",
|
||||
"data": user_input[CONF_LOCATION],
|
||||
"data": subentry_data,
|
||||
"title": user_input[CONF_NAME],
|
||||
"unique_id": None,
|
||||
},
|
||||
@@ -185,7 +251,9 @@ class LocationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
return self.async_abort(reason="entry_not_loaded")
|
||||
|
||||
errors: dict[str, str] = {}
|
||||
description_placeholders: dict[str, str] = {}
|
||||
description_placeholders: dict[str, str] = {
|
||||
"air_quality_coverage_url": AIR_QUALITY_COVERAGE_URL
|
||||
}
|
||||
if user_input is not None:
|
||||
if _is_location_already_configured(self.hass, user_input[CONF_LOCATION]):
|
||||
errors["base"] = "location_already_configured"
|
||||
@@ -202,9 +270,13 @@ class LocationSubentryFlowHandler(ConfigSubentryFlow):
|
||||
description_placeholders=description_placeholders,
|
||||
)
|
||||
if await _validate_input(user_input, api, errors, description_placeholders):
|
||||
data = dict(user_input[CONF_LOCATION])
|
||||
custom_options = user_input.get(CUSTOM_LOCAL_AQI_OPTIONS)
|
||||
if custom_options and custom_options.get(CONF_ENABLE_CUSTOM_LAQI):
|
||||
data[CUSTOM_LOCAL_AQI_OPTIONS] = custom_options
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_NAME],
|
||||
data=user_input[CONF_LOCATION],
|
||||
data=data,
|
||||
)
|
||||
else:
|
||||
user_input = {}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
from typing import Final
|
||||
|
||||
DOMAIN = "google_air_quality"
|
||||
SECTION_API_KEY_OPTIONS: Final = "api_key_options"
|
||||
CONF_ENABLE_CUSTOM_LAQI: Final = "enable_custom_laqi"
|
||||
CONF_REFERRER: Final = "referrer"
|
||||
CUSTOM_LAQI: Final = "custom_laqi"
|
||||
CUSTOM_LOCAL_AQI_OPTIONS: Final = "custom_local_aqi_options"
|
||||
DOMAIN: Final = "google_air_quality"
|
||||
SECTION_API_KEY_OPTIONS: Final = "api_key_options"
|
||||
|
||||
@@ -10,11 +10,16 @@ from google_air_quality_api.exceptions import GoogleAirQualityApiError
|
||||
from google_air_quality_api.model import AirQualityCurrentConditionsData
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.const import CONF_COUNTRY, CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import (
|
||||
CONF_ENABLE_CUSTOM_LAQI,
|
||||
CUSTOM_LAQI,
|
||||
CUSTOM_LOCAL_AQI_OPTIONS,
|
||||
DOMAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -49,11 +54,27 @@ class GoogleAirQualityUpdateCoordinator(
|
||||
subentry = config_entry.subentries[subentry_id]
|
||||
self.lat = subentry.data[CONF_LATITUDE]
|
||||
self.long = subentry.data[CONF_LONGITUDE]
|
||||
self.custom_local_aqi: str | None = None
|
||||
self.region_code: str | None = None
|
||||
options = subentry.data.get(CUSTOM_LOCAL_AQI_OPTIONS)
|
||||
|
||||
if isinstance(options, dict) and options.get(CONF_ENABLE_CUSTOM_LAQI):
|
||||
custom_laqi = options.get(CUSTOM_LAQI)
|
||||
region_code = options.get(CONF_COUNTRY)
|
||||
|
||||
if custom_laqi is not None and region_code is not None:
|
||||
self.custom_local_aqi = custom_laqi
|
||||
self.region_code = region_code
|
||||
|
||||
async def _async_update_data(self) -> AirQualityCurrentConditionsData:
|
||||
"""Fetch air quality data for this coordinate."""
|
||||
try:
|
||||
return await self.client.async_get_current_conditions(self.lat, self.long)
|
||||
return await self.client.async_get_current_conditions(
|
||||
lat=self.lat,
|
||||
lon=self.long,
|
||||
region_code=self.region_code,
|
||||
custom_local_aqi=self.custom_local_aqi,
|
||||
)
|
||||
except GoogleAirQualityApiError as ex:
|
||||
_LOGGER.debug("Cannot fetch air quality data: %s", str(ex))
|
||||
raise UpdateFailed(
|
||||
|
||||
@@ -204,7 +204,6 @@ async def async_setup_entry(
|
||||
|
||||
for subentry_id, subentry in entry.subentries.items():
|
||||
coordinator = coordinators[subentry_id]
|
||||
_LOGGER.debug("subentry.data: %s", subentry.data)
|
||||
async_add_entities(
|
||||
(
|
||||
AirQualitySensorEntity(coordinator, description, subentry_id, subentry)
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "Unable to connect to the Google Air Quality API:\n\n{error_message}",
|
||||
"mismatch_country_and_laqi": "This local AQI is not available for the selected country. Please select an available combination.",
|
||||
"missing_custom_laqi_options": "Please provide both country and custom local AQI when custom local AQI is enabled.",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"step": {
|
||||
@@ -39,6 +41,20 @@
|
||||
"referrer": "Specify this only if the API key has a [website application restriction]({restricting_api_keys_url})."
|
||||
},
|
||||
"name": "Optional API key options"
|
||||
},
|
||||
"custom_local_aqi_options": {
|
||||
"data": {
|
||||
"country": "[%key:common::config_flow::data::country%]",
|
||||
"custom_laqi": "Custom local AQI",
|
||||
"enable_custom_laqi": "Enable custom local AQI"
|
||||
},
|
||||
"data_description": {
|
||||
"country": "Country of the location",
|
||||
"custom_laqi": "The target air quality index",
|
||||
"enable_custom_laqi": "Select to enable a custom local air quality index"
|
||||
},
|
||||
"description": "Country and custom local AQI must match. You can find the available combinations here: {air_quality_coverage_url}",
|
||||
"name": "Custom local AQI options"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,8 +67,11 @@
|
||||
},
|
||||
"entry_type": "Air quality location",
|
||||
"error": {
|
||||
"cannot_connect": "[%key:component::google_air_quality::config::error::cannot_connect%]",
|
||||
"location_already_configured": "[%key:common::config_flow::abort::already_configured_location%]",
|
||||
"location_name_already_configured": "Location name already configured.",
|
||||
"mismatch_country_and_laqi": "[%key:component::google_air_quality::config::error::mismatch_country_and_laqi%]",
|
||||
"missing_custom_laqi_options": "[%key:component::google_air_quality::config::error::missing_custom_laqi_options%]",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"initiate_flow": {
|
||||
@@ -69,6 +88,22 @@
|
||||
"name": "[%key:component::google_air_quality::config::step::user::data_description::name%]"
|
||||
},
|
||||
"description": "Select the coordinates for which you want to create an entry.",
|
||||
"sections": {
|
||||
"custom_local_aqi_options": {
|
||||
"data": {
|
||||
"country": "[%key:common::config_flow::data::country%]",
|
||||
"custom_laqi": "[%key:component::google_air_quality::config::step::user::sections::custom_local_aqi_options::data::custom_laqi%]",
|
||||
"enable_custom_laqi": "[%key:component::google_air_quality::config::step::user::sections::custom_local_aqi_options::data::enable_custom_laqi%]"
|
||||
},
|
||||
"data_description": {
|
||||
"country": "[%key:component::google_air_quality::config::step::user::sections::custom_local_aqi_options::data_description::country%]",
|
||||
"custom_laqi": "[%key:component::google_air_quality::config::step::user::sections::custom_local_aqi_options::data_description::custom_laqi%]",
|
||||
"enable_custom_laqi": "[%key:component::google_air_quality::config::step::user::sections::custom_local_aqi_options::data_description::enable_custom_laqi%]"
|
||||
},
|
||||
"description": "[%key:component::google_air_quality::config::step::user::sections::custom_local_aqi_options::description%]",
|
||||
"name": "[%key:component::google_air_quality::config::step::user::sections::custom_local_aqi_options::name%]"
|
||||
}
|
||||
},
|
||||
"title": "Air quality data location"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_MODEL,
|
||||
CONF_DISKS,
|
||||
CONF_HOST,
|
||||
CONF_NAME,
|
||||
@@ -28,8 +29,6 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_DEVICE = "device"
|
||||
# pylint: disable-next=home-assistant-duplicate-const
|
||||
ATTR_MODEL = "model"
|
||||
|
||||
DEFAULT_HOST = "localhost"
|
||||
DEFAULT_PORT = 7634
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["indevolt-api==1.8.0"]
|
||||
"requirements": ["indevolt-api==1.8.1"]
|
||||
}
|
||||
|
||||
@@ -53,7 +53,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: MastodonConfigEntry) ->
|
||||
translation_key="auth_failed",
|
||||
) from error
|
||||
except MastodonError as ex:
|
||||
raise ConfigEntryNotReady("Failed to connect") from ex
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="failed_to_connect",
|
||||
) from ex
|
||||
|
||||
assert entry.unique_id
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ class MastodonCoordinator(DataUpdateCoordinator[Account]):
|
||||
translation_key="auth_failed",
|
||||
) from error
|
||||
except MastodonError as ex:
|
||||
raise UpdateFailed(ex) from ex
|
||||
raise UpdateFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="update_failed",
|
||||
) from ex
|
||||
|
||||
return account
|
||||
|
||||
@@ -101,6 +101,9 @@
|
||||
"auth_failed": {
|
||||
"message": "Authentication failed, please reauthenticate with Mastodon."
|
||||
},
|
||||
"failed_to_connect": {
|
||||
"message": "Failed to connect."
|
||||
},
|
||||
"idempotency_key_too_short": {
|
||||
"message": "Idempotency key must be at least 4 characters long."
|
||||
},
|
||||
@@ -130,6 +133,9 @@
|
||||
},
|
||||
"unable_to_upload_image": {
|
||||
"message": "Unable to upload image {media_path}."
|
||||
},
|
||||
"update_failed": {
|
||||
"message": "Update failed."
|
||||
}
|
||||
},
|
||||
"selector": {
|
||||
|
||||
@@ -53,7 +53,8 @@ class NintendoUpdateCoordinator(DataUpdateCoordinator[None]):
|
||||
return await self.api.update()
|
||||
except InvalidOAuthConfigurationException as err:
|
||||
raise ConfigEntryError(
|
||||
err, translation_domain=DOMAIN, translation_key="invalid_auth"
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_auth",
|
||||
) from err
|
||||
except NoDevicesFoundException as err:
|
||||
raise ConfigEntryError(
|
||||
|
||||
@@ -34,6 +34,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.helpers.start import async_at_start
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -123,7 +124,7 @@ async def async_setup_platform(
|
||||
# Sensors with live values like "temperature" or "current_power"
|
||||
# will also be reset to None.
|
||||
if not success and (
|
||||
(sensor.per_day_basis and date.today() > sensor.date_updated) # noqa: DTZ011
|
||||
(sensor.per_day_basis and dt_util.now().date() > sensor.date_updated)
|
||||
or (not sensor.per_day_basis and not sensor.per_total_basis)
|
||||
):
|
||||
state_unknown = True
|
||||
|
||||
@@ -97,29 +97,35 @@ class WLEDDataUpdateCoordinator(DataUpdateCoordinator[WLEDDevice]):
|
||||
async def listen() -> None:
|
||||
"""Listen for state changes via WebSocket."""
|
||||
try:
|
||||
await self.wled.connect()
|
||||
except WLEDError as err:
|
||||
self.logger.info(err)
|
||||
try:
|
||||
await self.wled.connect()
|
||||
except WLEDError as err:
|
||||
self.logger.info(err)
|
||||
return
|
||||
|
||||
try:
|
||||
# Stop polling as long as we have a websocket. WS will push
|
||||
# updates to us
|
||||
self.update_interval = None
|
||||
await self.wled.listen(callback=self.async_set_updated_data)
|
||||
except WLEDConnectionClosedError as err:
|
||||
self.last_update_success = False
|
||||
self.logger.info(err)
|
||||
except WLEDError as err:
|
||||
self.last_update_success = False
|
||||
self.async_update_listeners()
|
||||
self.logger.error(err)
|
||||
finally:
|
||||
# Pull data immediately and restart polling
|
||||
self.update_interval = SCAN_INTERVAL
|
||||
self.hass.async_create_task(self.async_request_refresh())
|
||||
|
||||
# Ensure we are disconnected
|
||||
await self.wled.disconnect()
|
||||
finally:
|
||||
if self.unsub:
|
||||
self.unsub()
|
||||
self.unsub = None
|
||||
return
|
||||
|
||||
try:
|
||||
await self.wled.listen(callback=self.async_set_updated_data)
|
||||
except WLEDConnectionClosedError as err:
|
||||
self.last_update_success = False
|
||||
self.logger.info(err)
|
||||
except WLEDError as err:
|
||||
self.last_update_success = False
|
||||
self.async_update_listeners()
|
||||
self.logger.error(err)
|
||||
|
||||
# Ensure we are disconnected
|
||||
await self.wled.disconnect()
|
||||
if self.unsub:
|
||||
self.unsub()
|
||||
self.unsub = None
|
||||
|
||||
async def close_websocket(_: Event) -> None:
|
||||
"""Close WebSocket connection."""
|
||||
|
||||
@@ -193,7 +193,7 @@ class WyomingAssistSatellite(WyomingSatelliteEntity, AssistSatelliteEntity):
|
||||
return
|
||||
|
||||
if event.type == assist_pipeline.PipelineEventType.RUN_START:
|
||||
if event.data and (tts_output := event.data["tts_output"]):
|
||||
if event.data and (tts_output := event.data.get("tts_output")):
|
||||
# Get stream token early.
|
||||
# If "tts_start_streaming" is True in INTENT_PROGRESS event, we
|
||||
# can start streaming TTS before the TTS_END event.
|
||||
|
||||
@@ -35,7 +35,6 @@ from .deprecation import deprecated_function
|
||||
from .frame import ReportBehavior, report_usage
|
||||
from .json import JSON_DUMP, find_paths_unserializable_data, json_bytes, json_fragment
|
||||
from .registry import BaseRegistry, BaseRegistryItems, RegistryIndexType
|
||||
from .singleton import singleton
|
||||
from .typing import UNDEFINED, UndefinedType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -818,11 +817,11 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
|
||||
devices: ActiveDeviceRegistryItems
|
||||
deleted_devices: DeviceRegistryItems[DeletedDeviceEntry]
|
||||
_device_data: dict[str, DeviceEntry]
|
||||
_loaded_event: asyncio.Event | None = None
|
||||
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
"""Initialize the device registry."""
|
||||
self.hass = hass
|
||||
self._loaded_event = asyncio.Event()
|
||||
self._store = DeviceRegistryStore(
|
||||
hass,
|
||||
STORAGE_VERSION_MAJOR,
|
||||
@@ -832,11 +831,6 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
|
||||
serialize_in_event_loop=False,
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_setup(self) -> None:
|
||||
"""Set up the registry."""
|
||||
self._loaded_event = asyncio.Event()
|
||||
|
||||
@callback
|
||||
def async_get(self, device_id: str) -> DeviceEntry | None:
|
||||
"""Get device.
|
||||
@@ -1522,8 +1516,8 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
|
||||
|
||||
async def _async_load(self) -> None:
|
||||
"""Load the device registry."""
|
||||
assert self._loaded_event is not None
|
||||
assert not self._loaded_event.is_set()
|
||||
if self._loaded_event.is_set():
|
||||
raise RuntimeError("Device registry is already loaded")
|
||||
|
||||
async_setup_cleanup(self.hass, self)
|
||||
|
||||
@@ -1625,12 +1619,8 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
|
||||
self._loaded_event.set()
|
||||
|
||||
async def async_wait_loaded(self) -> None:
|
||||
"""Wait until the device registry is fully loaded.
|
||||
|
||||
Will only wait if the registry had already been set up.
|
||||
"""
|
||||
if self._loaded_event is not None:
|
||||
await self._loaded_event.wait()
|
||||
"""Wait until the device registry is fully loaded."""
|
||||
await self._loaded_event.wait()
|
||||
|
||||
@callback
|
||||
def _data_to_save(self) -> dict[str, Any]:
|
||||
@@ -1772,16 +1762,19 @@ class DeviceRegistry(BaseRegistry[dict[str, list[dict[str, Any]]]]):
|
||||
|
||||
|
||||
@callback
|
||||
@singleton(DATA_REGISTRY)
|
||||
def async_get(hass: HomeAssistant) -> DeviceRegistry:
|
||||
"""Get device registry."""
|
||||
return DeviceRegistry(hass)
|
||||
try:
|
||||
return hass.data[DATA_REGISTRY]
|
||||
except KeyError as ex:
|
||||
raise RuntimeError("Device registry not set up") from ex
|
||||
|
||||
|
||||
def async_setup(hass: HomeAssistant) -> None:
|
||||
"""Set up device registry."""
|
||||
assert DATA_REGISTRY not in hass.data
|
||||
async_get(hass).async_setup()
|
||||
if DATA_REGISTRY in hass.data:
|
||||
raise RuntimeError("Device registry is already set up")
|
||||
hass.data[DATA_REGISTRY] = DeviceRegistry(hass)
|
||||
|
||||
|
||||
async def async_load(hass: HomeAssistant, *, load_empty: bool = False) -> None:
|
||||
|
||||
Generated
+1
-1
@@ -1347,7 +1347,7 @@ imgw_pib==2.2.0
|
||||
incomfort-client==0.7.0
|
||||
|
||||
# homeassistant.components.indevolt
|
||||
indevolt-api==1.8.0
|
||||
indevolt-api==1.8.1
|
||||
|
||||
# homeassistant.components.influxdb
|
||||
influxdb-client==1.50.0
|
||||
|
||||
Generated
+1
-1
@@ -1202,7 +1202,7 @@ imgw_pib==2.2.0
|
||||
incomfort-client==0.7.0
|
||||
|
||||
# homeassistant.components.indevolt
|
||||
indevolt-api==1.8.0
|
||||
indevolt-api==1.8.1
|
||||
|
||||
# homeassistant.components.influxdb
|
||||
influxdb-client==1.50.0
|
||||
|
||||
Generated
+1
-1
@@ -2,7 +2,7 @@
|
||||
# Automatically generated by hassfest.
|
||||
#
|
||||
# To update, run python3 -m script.hassfest -p docker
|
||||
FROM python:3.14.4-alpine
|
||||
FROM python:3.14.5-alpine
|
||||
|
||||
ENV \
|
||||
UV_SYSTEM_PYTHON=true \
|
||||
|
||||
@@ -766,7 +766,6 @@ def mock_device_registry(
|
||||
registry.deleted_devices = dr.DeviceRegistryItems()
|
||||
|
||||
hass.data[dr.DATA_REGISTRY] = registry
|
||||
dr.async_get.cache_clear()
|
||||
return registry
|
||||
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ async def test_form(
|
||||
("side_effect", "expected_error"),
|
||||
[
|
||||
(Exception(), "unknown"),
|
||||
(requests.Timeout(), "cannot_connect"),
|
||||
(requests.ConnectionError(), "cannot_connect"),
|
||||
(DAVError(), "cannot_connect"),
|
||||
(AuthorizationError(reason="Unauthorized"), "invalid_auth"),
|
||||
|
||||
@@ -58,6 +58,15 @@ async def test_setup_entry_error(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state is expected_state
|
||||
if (
|
||||
method == "async_get_board_info"
|
||||
and isinstance(exception, DucoError)
|
||||
and expected_state is ConfigEntryState.SETUP_ERROR
|
||||
):
|
||||
assert mock_config_entry.error_reason_translation_key == "api_error"
|
||||
assert mock_config_entry.error_reason_translation_placeholders == {
|
||||
"error": repr(exception)
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_duco_client")
|
||||
|
||||
@@ -8,9 +8,19 @@ from google_air_quality_api.model import AirQualityCurrentConditionsData
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.google_air_quality import CONF_REFERRER
|
||||
from homeassistant.components.google_air_quality.const import DOMAIN
|
||||
from homeassistant.components.google_air_quality.const import (
|
||||
CONF_ENABLE_CUSTOM_LAQI,
|
||||
CUSTOM_LAQI,
|
||||
CUSTOM_LOCAL_AQI_OPTIONS,
|
||||
DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigSubentryDataWithId
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.const import (
|
||||
CONF_API_KEY,
|
||||
CONF_COUNTRY,
|
||||
CONF_LATITUDE,
|
||||
CONF_LONGITUDE,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry, load_json_object_fixture
|
||||
@@ -62,14 +72,53 @@ def mock_config_entry(
|
||||
title=DOMAIN,
|
||||
data={CONF_API_KEY: "test-api-key", CONF_REFERRER: None},
|
||||
entry_id="123456789",
|
||||
subentries_data=[*mock_subentries],
|
||||
subentries_data=mock_subentries,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_subentries_with_custom_laqi() -> list[ConfigSubentryDataWithId]:
|
||||
"""Fixture for subentries with custom LAQI enabled."""
|
||||
return [
|
||||
ConfigSubentryDataWithId(
|
||||
data={
|
||||
CONF_LATITUDE: 10.1,
|
||||
CONF_LONGITUDE: 20.1,
|
||||
CUSTOM_LOCAL_AQI_OPTIONS: {
|
||||
CONF_ENABLE_CUSTOM_LAQI: True,
|
||||
CUSTOM_LAQI: "deu_uba",
|
||||
CONF_COUNTRY: "DE",
|
||||
},
|
||||
},
|
||||
subentry_type="location",
|
||||
title="Home",
|
||||
subentry_id="home-subentry-id",
|
||||
unique_id=None,
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry_with_custom_laqi(
|
||||
hass: HomeAssistant,
|
||||
mock_subentries_with_custom_laqi: list[ConfigSubentryDataWithId],
|
||||
) -> MockConfigEntry:
|
||||
"""Fixture for config entry with custom LAQI."""
|
||||
return MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
title=DOMAIN,
|
||||
data={CONF_API_KEY: "test-api-key", CONF_REFERRER: None},
|
||||
entry_id="123456789",
|
||||
subentries_data=mock_subentries_with_custom_laqi,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_api")
|
||||
def mock_client_api() -> Generator[Mock]:
|
||||
def mock_client_api(request: pytest.FixtureRequest) -> Generator[Mock]:
|
||||
"""Set up fake Google Air Quality API responses from fixtures."""
|
||||
responses = load_json_object_fixture("air_quality_data.json", DOMAIN)
|
||||
filename = getattr(request, "param", "air_quality_data.json")
|
||||
responses = load_json_object_fixture(filename, DOMAIN)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.google_air_quality.GoogleAirQualityApi",
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"dateTime": "2026-02-01T11:00:00Z",
|
||||
"regionCode": "de",
|
||||
"indexes": [
|
||||
{
|
||||
"code": "deu_lubw",
|
||||
"displayName": "LuQx (DE)",
|
||||
"aqi": 3,
|
||||
"aqiDisplay": "3",
|
||||
"color": {
|
||||
"red": 0.6,
|
||||
"green": 1,
|
||||
"blue": 1
|
||||
},
|
||||
"category": "Satisfactory air quality",
|
||||
"dominantPollutant": "pm10"
|
||||
},
|
||||
{
|
||||
"code": "uaqi",
|
||||
"displayName": "Universal AQI",
|
||||
"aqi": 57,
|
||||
"aqiDisplay": "57",
|
||||
"color": {
|
||||
"red": 0.827451,
|
||||
"green": 0.93333334,
|
||||
"blue": 0.06666667
|
||||
},
|
||||
"category": "Moderate air quality",
|
||||
"dominantPollutant": "pm25"
|
||||
}
|
||||
],
|
||||
"pollutants": [
|
||||
{
|
||||
"code": "no2",
|
||||
"displayName": "NO2",
|
||||
"fullName": "Nitrogen dioxide",
|
||||
"concentration": {
|
||||
"value": 14.25,
|
||||
"units": "PARTS_PER_BILLION"
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "o3",
|
||||
"displayName": "O3",
|
||||
"fullName": "Ozone",
|
||||
"concentration": {
|
||||
"value": 8.4,
|
||||
"units": "PARTS_PER_BILLION"
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "pm10",
|
||||
"displayName": "PM10",
|
||||
"fullName": "Inhalable particulate matter (\u003c10µm)",
|
||||
"concentration": {
|
||||
"value": 32.04,
|
||||
"units": "MICROGRAMS_PER_CUBIC_METER"
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "pm25",
|
||||
"displayName": "PM2.5",
|
||||
"fullName": "Fine particulate matter (\u003c2.5µm)",
|
||||
"concentration": {
|
||||
"value": 27.23,
|
||||
"units": "MICROGRAMS_PER_CUBIC_METER"
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "co",
|
||||
"displayName": "CO",
|
||||
"fullName": "Carbon monoxide",
|
||||
"concentration": {
|
||||
"value": 349.3,
|
||||
"units": "PARTS_PER_BILLION"
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "so2",
|
||||
"displayName": "SO2",
|
||||
"fullName": "Sulfur dioxide",
|
||||
"concentration": {
|
||||
"value": 1.13,
|
||||
"units": "PARTS_PER_BILLION"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
# serializer version: 1
|
||||
# name: test_sensor_snapshot[sensor.home_ammonia-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_ammonia-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -38,7 +38,7 @@
|
||||
'unit_of_measurement': 'ppb',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_ammonia-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_ammonia-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -54,7 +54,7 @@
|
||||
'state': '81.41',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_benzene-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_benzene-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -93,7 +93,7 @@
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_benzene-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_benzene-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -109,7 +109,7 @@
|
||||
'state': '0.24',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_carbon_monoxide-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_carbon_monoxide-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -151,7 +151,7 @@
|
||||
'unit_of_measurement': 'ppm',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_carbon_monoxide-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_carbon_monoxide-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -168,7 +168,7 @@
|
||||
'state': '0.26902',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_lqi_de_category-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_lqi_de_category-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -213,7 +213,7 @@
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_lqi_de_category-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_lqi_de_category-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -235,7 +235,7 @@
|
||||
'state': 'good_air_quality',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_lqi_de_dominant_pollutant-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_lqi_de_dominant_pollutant-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -279,7 +279,7 @@
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_lqi_de_dominant_pollutant-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_lqi_de_dominant_pollutant-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -300,7 +300,7 @@
|
||||
'state': 'no2',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_nitrogen_dioxide-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_nitrogen_dioxide-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -339,7 +339,7 @@
|
||||
'unit_of_measurement': 'ppb',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_nitrogen_dioxide-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_nitrogen_dioxide-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -356,7 +356,7 @@
|
||||
'state': '14.18',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_nitrogen_monoxide-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_nitrogen_monoxide-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -395,7 +395,7 @@
|
||||
'unit_of_measurement': 'ppb',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_nitrogen_monoxide-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_nitrogen_monoxide-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -412,7 +412,7 @@
|
||||
'state': '0.62',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_non_methane_hydrocarbons-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_non_methane_hydrocarbons-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -451,7 +451,7 @@
|
||||
'unit_of_measurement': 'ppb',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_non_methane_hydrocarbons-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_non_methane_hydrocarbons-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -467,7 +467,7 @@
|
||||
'state': '52.66',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_ozone-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_ozone-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -506,7 +506,7 @@
|
||||
'unit_of_measurement': 'ppb',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_ozone-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_ozone-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -523,7 +523,7 @@
|
||||
'state': '24.94',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_pm10-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_pm10-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -562,7 +562,7 @@
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_pm10-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_pm10-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -579,7 +579,7 @@
|
||||
'state': '21.95',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_pm2_5-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_pm2_5-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -618,7 +618,7 @@
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_pm2_5-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_pm2_5-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -635,7 +635,7 @@
|
||||
'state': '10.6',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_sulphur_dioxide-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_sulphur_dioxide-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -674,7 +674,7 @@
|
||||
'unit_of_measurement': 'ppb',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_sulphur_dioxide-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_sulphur_dioxide-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -691,7 +691,7 @@
|
||||
'state': '1.2',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_uaqi_category-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_uaqi_category-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -736,7 +736,7 @@
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_uaqi_category-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_uaqi_category-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -758,7 +758,7 @@
|
||||
'state': 'excellent_air_quality',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_uaqi_dominant_pollutant-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_uaqi_dominant_pollutant-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -804,7 +804,7 @@
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_uaqi_dominant_pollutant-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_uaqi_dominant_pollutant-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -827,7 +827,7 @@
|
||||
'state': 'o3',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_universal_air_quality_index-entry]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_universal_air_quality_index-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -866,7 +866,7 @@
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[sensor.home_universal_air_quality_index-state]
|
||||
# name: test_sensor_snapshot[air_quality_data.json-mock_config_entry][sensor.home_universal_air_quality_index-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
@@ -882,3 +882,724 @@
|
||||
'state': '80',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_carbon_monoxide-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.home_carbon_monoxide',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Carbon monoxide',
|
||||
'options': dict({
|
||||
'sensor.private': dict({
|
||||
'suggested_unit_of_measurement': 'ppm',
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.CO: 'carbon_monoxide'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Carbon monoxide',
|
||||
'platform': 'google_air_quality',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'co_10.1_20.1',
|
||||
'unit_of_measurement': 'ppm',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_carbon_monoxide-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
'device_class': 'carbon_monoxide',
|
||||
'friendly_name': 'Home Carbon monoxide',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'ppm',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_carbon_monoxide',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.3493',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_luqx_de_aqi-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.home_luqx_de_aqi',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'LuQx (DE) AQI',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.AQI: 'aqi'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'LuQx (DE) AQI',
|
||||
'platform': 'google_air_quality',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'local_aqi',
|
||||
'unique_id': 'local_aqi_10.1_20.1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_luqx_de_aqi-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
'device_class': 'aqi',
|
||||
'friendly_name': 'Home LuQx (DE) AQI',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_luqx_de_aqi',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '3',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_luqx_de_category-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'very_good_air_quality',
|
||||
'good_air_quality',
|
||||
'satisfactory_air_quality',
|
||||
'sufficient_air_quality',
|
||||
'bad_air_quality',
|
||||
'very_bad_air_quality',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.home_luqx_de_category',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'LuQx (DE) category',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'LuQx (DE) category',
|
||||
'platform': 'google_air_quality',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'local_category',
|
||||
'unique_id': 'local_category_10.1_20.1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_luqx_de_category-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Home LuQx (DE) category',
|
||||
'options': list([
|
||||
'very_good_air_quality',
|
||||
'good_air_quality',
|
||||
'satisfactory_air_quality',
|
||||
'sufficient_air_quality',
|
||||
'bad_air_quality',
|
||||
'very_bad_air_quality',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_luqx_de_category',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'satisfactory_air_quality',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_luqx_de_dominant_pollutant-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'no2',
|
||||
'so2',
|
||||
'co',
|
||||
'o3',
|
||||
'pm10',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.home_luqx_de_dominant_pollutant',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'LuQx (DE) dominant pollutant',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'LuQx (DE) dominant pollutant',
|
||||
'platform': 'google_air_quality',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'local_dominant_pollutant',
|
||||
'unique_id': 'local_dominant_pollutant_10.1_20.1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_luqx_de_dominant_pollutant-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Home LuQx (DE) dominant pollutant',
|
||||
'options': list([
|
||||
'no2',
|
||||
'so2',
|
||||
'co',
|
||||
'o3',
|
||||
'pm10',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_luqx_de_dominant_pollutant',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'pm10',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_nitrogen_dioxide-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.home_nitrogen_dioxide',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Nitrogen dioxide',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.NITROGEN_DIOXIDE: 'nitrogen_dioxide'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Nitrogen dioxide',
|
||||
'platform': 'google_air_quality',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'no2_10.1_20.1',
|
||||
'unit_of_measurement': 'ppb',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_nitrogen_dioxide-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
'device_class': 'nitrogen_dioxide',
|
||||
'friendly_name': 'Home Nitrogen dioxide',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'ppb',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_nitrogen_dioxide',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '14.25',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_ozone-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.home_ozone',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Ozone',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.OZONE: 'ozone'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Ozone',
|
||||
'platform': 'google_air_quality',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'o3_10.1_20.1',
|
||||
'unit_of_measurement': 'ppb',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_ozone-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
'device_class': 'ozone',
|
||||
'friendly_name': 'Home Ozone',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'ppb',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_ozone',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '8.4',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_pm10-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.home_pm10',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'PM10',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.PM10: 'pm10'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'PM10',
|
||||
'platform': 'google_air_quality',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'pm10_10.1_20.1',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_pm10-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
'device_class': 'pm10',
|
||||
'friendly_name': 'Home PM10',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_pm10',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '32.04',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_pm2_5-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.home_pm2_5',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'PM2.5',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.PM25: 'pm25'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'PM2.5',
|
||||
'platform': 'google_air_quality',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'pm25_10.1_20.1',
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_pm2_5-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
'device_class': 'pm25',
|
||||
'friendly_name': 'Home PM2.5',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'μg/m³',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_pm2_5',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '27.23',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_sulphur_dioxide-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.home_sulphur_dioxide',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sulphur dioxide',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.SULPHUR_DIOXIDE: 'sulphur_dioxide'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sulphur dioxide',
|
||||
'platform': 'google_air_quality',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'so2_10.1_20.1',
|
||||
'unit_of_measurement': 'ppb',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_sulphur_dioxide-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
'device_class': 'sulphur_dioxide',
|
||||
'friendly_name': 'Home Sulphur dioxide',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'ppb',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_sulphur_dioxide',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1.13',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_uaqi_category-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'excellent_air_quality',
|
||||
'good_air_quality',
|
||||
'moderate_air_quality',
|
||||
'low_air_quality',
|
||||
'poor_air_quality',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.home_uaqi_category',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'UAQI category',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'UAQI category',
|
||||
'platform': 'google_air_quality',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'uaqi_category',
|
||||
'unique_id': 'uaqi_category_10.1_20.1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_uaqi_category-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Home UAQI category',
|
||||
'options': list([
|
||||
'excellent_air_quality',
|
||||
'good_air_quality',
|
||||
'moderate_air_quality',
|
||||
'low_air_quality',
|
||||
'poor_air_quality',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_uaqi_category',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'moderate_air_quality',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_uaqi_dominant_pollutant-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'co',
|
||||
'no2',
|
||||
'o3',
|
||||
'pm10',
|
||||
'pm25',
|
||||
'so2',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.home_uaqi_dominant_pollutant',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'UAQI dominant pollutant',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'UAQI dominant pollutant',
|
||||
'platform': 'google_air_quality',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'uaqi_dominant_pollutant',
|
||||
'unique_id': 'uaqi_dominant_pollutant_10.1_20.1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_uaqi_dominant_pollutant-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
'device_class': 'enum',
|
||||
'friendly_name': 'Home UAQI dominant pollutant',
|
||||
'options': list([
|
||||
'co',
|
||||
'no2',
|
||||
'o3',
|
||||
'pm10',
|
||||
'pm25',
|
||||
'so2',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_uaqi_dominant_pollutant',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'pm25',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_universal_air_quality_index-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.home_universal_air_quality_index',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Universal Air Quality Index',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.AQI: 'aqi'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Universal Air Quality Index',
|
||||
'platform': 'google_air_quality',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'uaqi',
|
||||
'unique_id': 'uaqi_10.1_20.1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_snapshot[air_quality_data_custom_laqi.json-mock_config_entry_with_custom_laqi][sensor.home_universal_air_quality_index-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'attribution': 'Data provided by Google Air Quality',
|
||||
'device_class': 'aqi',
|
||||
'friendly_name': 'Home Universal Air Quality Index',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.home_universal_air_quality_index',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '57',
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -2,17 +2,24 @@
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from google_air_quality_api.exceptions import GoogleAirQualityApiError
|
||||
from google_air_quality_api.exceptions import (
|
||||
GoogleAirQualityApiError,
|
||||
InvalidCustomLAQIConfigurationError,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.google_air_quality.const import (
|
||||
CONF_ENABLE_CUSTOM_LAQI,
|
||||
CONF_REFERRER,
|
||||
CUSTOM_LAQI,
|
||||
CUSTOM_LOCAL_AQI_OPTIONS,
|
||||
DOMAIN,
|
||||
SECTION_API_KEY_OPTIONS,
|
||||
)
|
||||
from homeassistant.config_entries import SOURCE_USER
|
||||
from homeassistant.const import (
|
||||
CONF_API_KEY,
|
||||
CONF_COUNTRY,
|
||||
CONF_LATITUDE,
|
||||
CONF_LOCATION,
|
||||
CONF_LONGITUDE,
|
||||
@@ -110,6 +117,7 @@ async def test_form_with_referrer(
|
||||
@pytest.mark.parametrize(
|
||||
("api_exception", "expected_error"),
|
||||
[
|
||||
(InvalidCustomLAQIConfigurationError(), "mismatch_country_and_laqi"),
|
||||
(GoogleAirQualityApiError(), "cannot_connect"),
|
||||
(ValueError(), "unknown"),
|
||||
],
|
||||
@@ -301,6 +309,11 @@ async def test_subentry_flow(
|
||||
CONF_LATITUDE: 30.1,
|
||||
CONF_LONGITUDE: 40.1,
|
||||
},
|
||||
CUSTOM_LOCAL_AQI_OPTIONS: {
|
||||
CONF_COUNTRY: "DE",
|
||||
CUSTOM_LAQI: "deu_uba",
|
||||
CONF_ENABLE_CUSTOM_LAQI: False,
|
||||
},
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
@@ -404,3 +417,126 @@ async def test_subentry_flow_entry_not_loaded(
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "entry_not_loaded"
|
||||
|
||||
|
||||
async def test_create_entry_with_custom_laqi(
|
||||
hass: HomeAssistant,
|
||||
mock_setup_entry: AsyncMock,
|
||||
mock_api: AsyncMock,
|
||||
) -> None:
|
||||
"""Test creating a config entry with custom laqi."""
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] == {}
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_NAME: "test-name",
|
||||
CONF_API_KEY: "test-api-key",
|
||||
CONF_LOCATION: {CONF_LATITUDE: 10.1, CONF_LONGITUDE: 20.1},
|
||||
CUSTOM_LOCAL_AQI_OPTIONS: {
|
||||
CONF_COUNTRY: "DE",
|
||||
CONF_ENABLE_CUSTOM_LAQI: True,
|
||||
},
|
||||
},
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"][CUSTOM_LOCAL_AQI_OPTIONS] == "missing_custom_laqi_options"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_NAME: "test-name",
|
||||
CONF_API_KEY: "test-api-key",
|
||||
CONF_LOCATION: {CONF_LATITUDE: 10.1, CONF_LONGITUDE: 20.1},
|
||||
CUSTOM_LOCAL_AQI_OPTIONS: {
|
||||
CONF_COUNTRY: "DE",
|
||||
CUSTOM_LAQI: "deu_lubw",
|
||||
CONF_ENABLE_CUSTOM_LAQI: True,
|
||||
},
|
||||
},
|
||||
)
|
||||
mock_api.async_get_current_conditions.assert_called_once_with(
|
||||
lat=10.1, lon=20.1, region_code="DE", custom_local_aqi="deu_lubw"
|
||||
)
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Google Air Quality"
|
||||
assert result["data"] == {
|
||||
CONF_API_KEY: "test-api-key",
|
||||
CONF_REFERRER: None,
|
||||
}
|
||||
assert len(result["subentries"]) == 1
|
||||
subentry = result["subentries"][0]
|
||||
assert subentry["subentry_type"] == "location"
|
||||
assert subentry["title"] == "test-name"
|
||||
assert subentry["data"] == {
|
||||
CONF_LATITUDE: 10.1,
|
||||
CONF_LONGITUDE: 20.1,
|
||||
CUSTOM_LOCAL_AQI_OPTIONS: {
|
||||
CONF_COUNTRY: "DE",
|
||||
CUSTOM_LAQI: "deu_lubw",
|
||||
CONF_ENABLE_CUSTOM_LAQI: True,
|
||||
},
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_subentry_flow_with_custom_laqi(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_api: AsyncMock,
|
||||
) -> None:
|
||||
"""Test creating a location subentry with a custom local AQI."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_api.async_get_current_conditions.call_count == 1
|
||||
|
||||
result = await hass.config_entries.subentries.async_init(
|
||||
(mock_config_entry.entry_id, "location"),
|
||||
context={"source": "user"},
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "location"
|
||||
|
||||
result = await hass.config_entries.subentries.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_NAME: "Work",
|
||||
CONF_LOCATION: {
|
||||
CONF_LATITUDE: 30.1,
|
||||
CONF_LONGITUDE: 40.1,
|
||||
},
|
||||
CUSTOM_LOCAL_AQI_OPTIONS: {
|
||||
CONF_COUNTRY: "DE",
|
||||
CUSTOM_LAQI: "deu_lubw",
|
||||
CONF_ENABLE_CUSTOM_LAQI: True,
|
||||
},
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "Work"
|
||||
assert result["data"] == {
|
||||
CONF_LATITUDE: 30.1,
|
||||
CONF_LONGITUDE: 40.1,
|
||||
CUSTOM_LOCAL_AQI_OPTIONS: {
|
||||
CONF_COUNTRY: "DE",
|
||||
CUSTOM_LAQI: "deu_lubw",
|
||||
CONF_ENABLE_CUSTOM_LAQI: True,
|
||||
},
|
||||
}
|
||||
|
||||
# Initial setup: 1 of each API call
|
||||
# Subentry flow validation: 1 current conditions call
|
||||
# Reload with 2 subentries: 2 of each API call
|
||||
assert mock_api.async_get_current_conditions.call_count == 1 + 1 + 2
|
||||
|
||||
entry = hass.config_entries.async_get_entry(mock_config_entry.entry_id)
|
||||
assert len(entry.subentries) == 2
|
||||
|
||||
@@ -26,6 +26,22 @@ async def test_setup(
|
||||
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
async def test_setup_with_custom_laqi(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry_with_custom_laqi: MockConfigEntry,
|
||||
mock_api: AsyncMock,
|
||||
) -> None:
|
||||
"""Test successful setup with custom LAQI and unload."""
|
||||
mock_config_entry_with_custom_laqi.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry_with_custom_laqi.entry_id)
|
||||
assert mock_config_entry_with_custom_laqi.state is ConfigEntryState.LOADED
|
||||
|
||||
await hass.config_entries.async_unload(mock_config_entry_with_custom_laqi.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry_with_custom_laqi.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
|
||||
async def test_config_not_ready(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
@@ -9,19 +10,32 @@ from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
from tests.common import snapshot_platform
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("mock_api", "config_fixture_name"),
|
||||
[
|
||||
("air_quality_data.json", "mock_config_entry"),
|
||||
("air_quality_data_custom_laqi.json", "mock_config_entry_with_custom_laqi"),
|
||||
],
|
||||
indirect=("mock_api",),
|
||||
)
|
||||
async def test_sensor_snapshot(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
request: pytest.FixtureRequest,
|
||||
mock_api: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
config_fixture_name: str,
|
||||
) -> None:
|
||||
"""Snapshot test of the sensors."""
|
||||
|
||||
mock_config_entry = request.getfixturevalue(config_fixture_name)
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
with patch(
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
"9101": 32.8,
|
||||
"9117": 31.9,
|
||||
"9133": 33.0,
|
||||
"9270": 31.2,
|
||||
"9003": 150,
|
||||
"11005": 35.5,
|
||||
"7171": 1,
|
||||
|
||||
@@ -66,7 +66,6 @@
|
||||
'11011': 85,
|
||||
'11016': 0,
|
||||
'11034': 100,
|
||||
'11042': 32.1,
|
||||
'142': 1.79,
|
||||
'1501': 0,
|
||||
'1502': 0,
|
||||
@@ -148,6 +147,7 @@
|
||||
'9206': 50.9,
|
||||
'9216': 24.9,
|
||||
'9218': '**REDACTED**',
|
||||
'9270': 31.2,
|
||||
'9279': 1,
|
||||
}),
|
||||
'device': dict({
|
||||
|
||||
@@ -2481,7 +2481,7 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'battery_pack_1_mos_temperature',
|
||||
'unique_id': 'SolidFlex2000-87654321_11042',
|
||||
'unique_id': 'SolidFlex2000-87654321_9085',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
})
|
||||
# ---
|
||||
@@ -2498,7 +2498,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '32.1',
|
||||
'state': '31.5',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[2][sensor.cms_sf2000_battery_pack_1_sn-entry]
|
||||
@@ -2818,7 +2818,7 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'battery_pack_2_mos_temperature',
|
||||
'unique_id': 'SolidFlex2000-87654321_9085',
|
||||
'unique_id': 'SolidFlex2000-87654321_9101',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
})
|
||||
# ---
|
||||
@@ -2835,7 +2835,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '31.5',
|
||||
'state': '32.8',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[2][sensor.cms_sf2000_battery_pack_2_sn-entry]
|
||||
@@ -3155,7 +3155,7 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'battery_pack_3_mos_temperature',
|
||||
'unique_id': 'SolidFlex2000-87654321_9101',
|
||||
'unique_id': 'SolidFlex2000-87654321_9117',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
})
|
||||
# ---
|
||||
@@ -3172,7 +3172,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '32.8',
|
||||
'state': '31.9',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[2][sensor.cms_sf2000_battery_pack_3_sn-entry]
|
||||
@@ -3492,7 +3492,7 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'battery_pack_4_mos_temperature',
|
||||
'unique_id': 'SolidFlex2000-87654321_9117',
|
||||
'unique_id': 'SolidFlex2000-87654321_9133',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
})
|
||||
# ---
|
||||
@@ -3509,7 +3509,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '31.9',
|
||||
'state': '33.0',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[2][sensor.cms_sf2000_battery_pack_4_sn-entry]
|
||||
@@ -3829,7 +3829,7 @@
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'battery_pack_5_mos_temperature',
|
||||
'unique_id': 'SolidFlex2000-87654321_9133',
|
||||
'unique_id': 'SolidFlex2000-87654321_9270',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
})
|
||||
# ---
|
||||
@@ -3846,7 +3846,7 @@
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '33.0',
|
||||
'state': '31.2',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor[2][sensor.cms_sf2000_battery_pack_5_sn-entry]
|
||||
|
||||
@@ -125,8 +125,10 @@ async def test_websocket(
|
||||
assert mock_bus.async_listen_once.return_value.call_count == 0
|
||||
|
||||
# Send update from WebSocket
|
||||
# Note we may still update() during websocket setup
|
||||
# so update it too.
|
||||
mock_wled.update.return_value.state.on = False
|
||||
updated_device = deepcopy(mock_wled.update.return_value)
|
||||
updated_device.state.on = False
|
||||
callback(updated_device)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -135,7 +137,15 @@ async def test_websocket(
|
||||
assert state
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
# Resolve Future with a connection losed.
|
||||
# Listening for changes on websocket, polling is suspended
|
||||
num_updates_before_websocket = mock_wled.update.call_count
|
||||
for _scans in range(4):
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
assert mock_wled.update.call_count == num_updates_before_websocket
|
||||
|
||||
# Resolve Future with a closed connection.
|
||||
connection_finished.set_exception(WLEDConnectionClosedError)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -173,15 +183,29 @@ async def test_websocket_error(
|
||||
async_fire_time_changed(hass)
|
||||
await connection_connected
|
||||
|
||||
# Resolve Future with an error.
|
||||
# Resolve listen() with an error. This causes polling
|
||||
# to take over so fail polling update() too
|
||||
mock_wled.update.side_effect = WLEDError
|
||||
mock_wled.listen.side_effect = WLEDError
|
||||
connection_finished.set_exception(WLEDError)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Light no longer available as an error occurred
|
||||
# and polling couldn't take over.
|
||||
state = hass.states.get("light.wled_websocket")
|
||||
assert state
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
# Light becomes available after polling takes over
|
||||
mock_wled.update.side_effect = None
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("light.wled_websocket")
|
||||
assert state
|
||||
assert state.state == STATE_ON
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["rgb_websocket"])
|
||||
async def test_websocket_disconnect_on_home_assistant_stop(
|
||||
|
||||
@@ -817,6 +817,57 @@ async def test_on_pipeline_event_ignores_disconnected_client(
|
||||
assert not mock_client.error_event.is_set()
|
||||
|
||||
|
||||
async def test_run_start_without_tts(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test RUN_START event without tts_output does not crash.
|
||||
|
||||
Regression test for https://github.com/home-assistant/core/issues/165734
|
||||
"""
|
||||
events: list[Event] = [
|
||||
RunPipeline(
|
||||
start_stage=PipelineStage.WAKE, end_stage=PipelineStage.TTS
|
||||
).event(),
|
||||
]
|
||||
|
||||
pipeline_event = asyncio.Event()
|
||||
|
||||
def _async_pipeline_from_audio_stream(*args: Any, **kwargs: Any) -> None:
|
||||
pipeline_event.set()
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wyoming.data.load_wyoming_info",
|
||||
return_value=SATELLITE_INFO,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wyoming.assist_satellite.AsyncTcpClient",
|
||||
SatelliteAsyncTcpClient(events),
|
||||
) as mock_client,
|
||||
patch(
|
||||
"homeassistant.components.assist_satellite.entity.async_pipeline_from_audio_stream",
|
||||
wraps=_async_pipeline_from_audio_stream,
|
||||
) as mock_run_pipeline,
|
||||
):
|
||||
await setup_config_entry(hass)
|
||||
|
||||
async with asyncio.timeout(1):
|
||||
await pipeline_event.wait()
|
||||
await mock_client.connect_event.wait()
|
||||
await mock_client.run_satellite_event.wait()
|
||||
|
||||
event_callback = mock_run_pipeline.call_args.kwargs["event_callback"]
|
||||
|
||||
# Fire RUN_START without tts_output (TTS not configured)
|
||||
# must not raise KeyError
|
||||
event_callback(
|
||||
assist_pipeline.PipelineEvent(
|
||||
assist_pipeline.PipelineEventType.RUN_START,
|
||||
{"pipeline": "test", "language": "en"},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def test_announce_raises_when_client_disconnected(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
|
||||
@@ -303,6 +303,23 @@ async def test_multiple_config_subentries(
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("load_registries", [False])
|
||||
async def test_async_get_before_setup_raises(hass: HomeAssistant) -> None:
|
||||
"""Test async_get raises when the registry has not been set up."""
|
||||
with pytest.raises(RuntimeError, match="Device registry not set up"):
|
||||
dr.async_get(hass)
|
||||
|
||||
dr.async_setup(hass)
|
||||
assert isinstance(dr.async_get(hass), dr.DeviceRegistry)
|
||||
|
||||
|
||||
async def test_async_load_twice_raises(hass: HomeAssistant) -> None:
|
||||
"""Test loading the device registry twice raises."""
|
||||
registry = dr.async_get(hass)
|
||||
with pytest.raises(RuntimeError, match="Device registry is already loaded"):
|
||||
await registry.async_load()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("load_registries", [False])
|
||||
@pytest.mark.usefixtures("freezer")
|
||||
async def test_loading_from_storage(
|
||||
@@ -2637,7 +2654,6 @@ async def test_loading_saving_data(
|
||||
# Now load written data in new registry
|
||||
registry2 = dr.DeviceRegistry(hass)
|
||||
await flush_store(device_registry._store)
|
||||
registry2.async_setup()
|
||||
await registry2.async_load()
|
||||
|
||||
# Ensure same order
|
||||
|
||||
@@ -789,6 +789,9 @@ async def test_filter_on_load(
|
||||
},
|
||||
}
|
||||
|
||||
dr.async_setup(hass)
|
||||
await dr.async_load(hass)
|
||||
|
||||
await er.async_load(hass)
|
||||
registry = er.async_get(hass)
|
||||
|
||||
@@ -960,6 +963,9 @@ async def test_load_bad_data(
|
||||
},
|
||||
}
|
||||
|
||||
dr.async_setup(hass)
|
||||
await dr.async_load(hass)
|
||||
|
||||
await er.async_load(hass)
|
||||
registry = er.async_get(hass)
|
||||
|
||||
@@ -1267,6 +1273,9 @@ async def test_migration_1_1(hass: HomeAssistant, hass_storage: dict[str, Any])
|
||||
},
|
||||
}
|
||||
|
||||
dr.async_setup(hass)
|
||||
await dr.async_load(hass)
|
||||
|
||||
await er.async_load(hass)
|
||||
registry = er.async_get(hass)
|
||||
|
||||
@@ -1383,6 +1392,9 @@ async def test_migration_1_7(hass: HomeAssistant, hass_storage: dict[str, Any])
|
||||
},
|
||||
}
|
||||
|
||||
dr.async_setup(hass)
|
||||
await dr.async_load(hass)
|
||||
|
||||
await er.async_load(hass)
|
||||
registry = er.async_get(hass)
|
||||
|
||||
@@ -1455,6 +1467,9 @@ async def test_migration_1_11(
|
||||
},
|
||||
}
|
||||
|
||||
dr.async_setup(hass)
|
||||
await dr.async_load(hass)
|
||||
|
||||
await er.async_load(hass)
|
||||
registry = er.async_get(hass)
|
||||
|
||||
@@ -1622,6 +1637,9 @@ async def test_migration_1_18(
|
||||
},
|
||||
}
|
||||
|
||||
dr.async_setup(hass)
|
||||
await dr.async_load(hass)
|
||||
|
||||
await er.async_load(hass)
|
||||
registry = er.async_get(hass)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user