mirror of
https://github.com/home-assistant/core.git
synced 2025-08-30 18:01:31 +02:00
2025.8.3 (#151008)
This commit is contained in:
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -37,7 +37,7 @@ on:
|
|||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
env:
|
env:
|
||||||
CACHE_VERSION: 4
|
CACHE_VERSION: 6
|
||||||
UV_CACHE_VERSION: 1
|
UV_CACHE_VERSION: 1
|
||||||
MYPY_CACHE_VERSION: 1
|
MYPY_CACHE_VERSION: 1
|
||||||
HA_SHORT_VERSION: "2025.8"
|
HA_SHORT_VERSION: "2025.8"
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
"""Alexa Devices integration."""
|
"""Alexa Devices integration."""
|
||||||
|
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import CONF_COUNTRY, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import _LOGGER, COUNTRY_DOMAINS, DOMAIN
|
||||||
from .coordinator import AmazonConfigEntry, AmazonDevicesCoordinator
|
from .coordinator import AmazonConfigEntry, AmazonDevicesCoordinator
|
||||||
from .services import async_setup_services
|
from .services import async_setup_services
|
||||||
|
|
||||||
@@ -40,6 +40,32 @@ async def async_setup_entry(hass: HomeAssistant, entry: AmazonConfigEntry) -> bo
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_migrate_entry(hass: HomeAssistant, entry: AmazonConfigEntry) -> bool:
|
||||||
|
"""Migrate old entry."""
|
||||||
|
if entry.version == 1 and entry.minor_version == 0:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Migrating from version %s.%s", entry.version, entry.minor_version
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert country in domain
|
||||||
|
country = entry.data[CONF_COUNTRY]
|
||||||
|
domain = COUNTRY_DOMAINS.get(country, country)
|
||||||
|
|
||||||
|
# Save domain and remove country
|
||||||
|
new_data = entry.data.copy()
|
||||||
|
new_data.update({"site": f"https://www.amazon.{domain}"})
|
||||||
|
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
entry, data=new_data, version=1, minor_version=1
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER.info(
|
||||||
|
"Migration to version %s.%s successful", entry.version, entry.minor_version
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: AmazonConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: AmazonConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
|
@@ -10,16 +10,14 @@ from aioamazondevices.exceptions import (
|
|||||||
CannotAuthenticate,
|
CannotAuthenticate,
|
||||||
CannotConnect,
|
CannotConnect,
|
||||||
CannotRetrieveData,
|
CannotRetrieveData,
|
||||||
WrongCountry,
|
|
||||||
)
|
)
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||||
from homeassistant.const import CONF_CODE, CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_CODE, CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import aiohttp_client
|
from homeassistant.helpers import aiohttp_client
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.selector import CountrySelector
|
|
||||||
|
|
||||||
from .const import CONF_LOGIN_DATA, DOMAIN
|
from .const import CONF_LOGIN_DATA, DOMAIN
|
||||||
|
|
||||||
@@ -37,7 +35,6 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
|
|||||||
session = aiohttp_client.async_create_clientsession(hass)
|
session = aiohttp_client.async_create_clientsession(hass)
|
||||||
api = AmazonEchoApi(
|
api = AmazonEchoApi(
|
||||||
session,
|
session,
|
||||||
data[CONF_COUNTRY],
|
|
||||||
data[CONF_USERNAME],
|
data[CONF_USERNAME],
|
||||||
data[CONF_PASSWORD],
|
data[CONF_PASSWORD],
|
||||||
)
|
)
|
||||||
@@ -48,6 +45,9 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
|
|||||||
class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
|
class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for Alexa Devices."""
|
"""Handle a config flow for Alexa Devices."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
MINOR_VERSION = 1
|
||||||
|
|
||||||
async def async_step_user(
|
async def async_step_user(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
@@ -62,8 +62,6 @@ class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
errors["base"] = "invalid_auth"
|
errors["base"] = "invalid_auth"
|
||||||
except CannotRetrieveData:
|
except CannotRetrieveData:
|
||||||
errors["base"] = "cannot_retrieve_data"
|
errors["base"] = "cannot_retrieve_data"
|
||||||
except WrongCountry:
|
|
||||||
errors["base"] = "wrong_country"
|
|
||||||
else:
|
else:
|
||||||
await self.async_set_unique_id(data["customer_info"]["user_id"])
|
await self.async_set_unique_id(data["customer_info"]["user_id"])
|
||||||
self._abort_if_unique_id_configured()
|
self._abort_if_unique_id_configured()
|
||||||
@@ -78,9 +76,6 @@ class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
errors=errors,
|
errors=errors,
|
||||||
data_schema=vol.Schema(
|
data_schema=vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(
|
|
||||||
CONF_COUNTRY, default=self.hass.config.country
|
|
||||||
): CountrySelector(),
|
|
||||||
vol.Required(CONF_USERNAME): cv.string,
|
vol.Required(CONF_USERNAME): cv.string,
|
||||||
vol.Required(CONF_PASSWORD): cv.string,
|
vol.Required(CONF_PASSWORD): cv.string,
|
||||||
vol.Required(CONF_CODE): cv.string,
|
vol.Required(CONF_CODE): cv.string,
|
||||||
|
@@ -6,3 +6,22 @@ _LOGGER = logging.getLogger(__package__)
|
|||||||
|
|
||||||
DOMAIN = "alexa_devices"
|
DOMAIN = "alexa_devices"
|
||||||
CONF_LOGIN_DATA = "login_data"
|
CONF_LOGIN_DATA = "login_data"
|
||||||
|
|
||||||
|
DEFAULT_DOMAIN = {"domain": "com"}
|
||||||
|
COUNTRY_DOMAINS = {
|
||||||
|
"ar": DEFAULT_DOMAIN,
|
||||||
|
"at": DEFAULT_DOMAIN,
|
||||||
|
"au": {"domain": "com.au"},
|
||||||
|
"be": {"domain": "com.be"},
|
||||||
|
"br": DEFAULT_DOMAIN,
|
||||||
|
"gb": {"domain": "co.uk"},
|
||||||
|
"il": DEFAULT_DOMAIN,
|
||||||
|
"jp": {"domain": "co.jp"},
|
||||||
|
"mx": {"domain": "com.mx"},
|
||||||
|
"no": DEFAULT_DOMAIN,
|
||||||
|
"nz": {"domain": "com.au"},
|
||||||
|
"pl": DEFAULT_DOMAIN,
|
||||||
|
"tr": {"domain": "com.tr"},
|
||||||
|
"us": DEFAULT_DOMAIN,
|
||||||
|
"za": {"domain": "co.za"},
|
||||||
|
}
|
||||||
|
@@ -11,7 +11,7 @@ from aioamazondevices.exceptions import (
|
|||||||
from aiohttp import ClientSession
|
from aiohttp import ClientSession
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
@@ -44,7 +44,6 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
|
|||||||
)
|
)
|
||||||
self.api = AmazonEchoApi(
|
self.api = AmazonEchoApi(
|
||||||
session,
|
session,
|
||||||
entry.data[CONF_COUNTRY],
|
|
||||||
entry.data[CONF_USERNAME],
|
entry.data[CONF_USERNAME],
|
||||||
entry.data[CONF_PASSWORD],
|
entry.data[CONF_PASSWORD],
|
||||||
entry.data[CONF_LOGIN_DATA],
|
entry.data[CONF_LOGIN_DATA],
|
||||||
|
@@ -8,5 +8,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aioamazondevices"],
|
"loggers": ["aioamazondevices"],
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["aioamazondevices==4.0.0"]
|
"requirements": ["aioamazondevices==5.0.0"]
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"common": {
|
"common": {
|
||||||
"data_code": "One-time password (OTP code)",
|
"data_code": "One-time password (OTP code)",
|
||||||
"data_description_country": "The country where your Amazon account is registered.",
|
|
||||||
"data_description_username": "The email address of your Amazon account.",
|
"data_description_username": "The email address of your Amazon account.",
|
||||||
"data_description_password": "The password of your Amazon account.",
|
"data_description_password": "The password of your Amazon account.",
|
||||||
"data_description_code": "The one-time password to log in to your account. Currently, only tokens from OTP applications are supported.",
|
"data_description_code": "The one-time password to log in to your account. Currently, only tokens from OTP applications are supported.",
|
||||||
@@ -12,13 +11,11 @@
|
|||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"data": {
|
"data": {
|
||||||
"country": "[%key:common::config_flow::data::country%]",
|
|
||||||
"username": "[%key:common::config_flow::data::username%]",
|
"username": "[%key:common::config_flow::data::username%]",
|
||||||
"password": "[%key:common::config_flow::data::password%]",
|
"password": "[%key:common::config_flow::data::password%]",
|
||||||
"code": "[%key:component::alexa_devices::common::data_code%]"
|
"code": "[%key:component::alexa_devices::common::data_code%]"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"country": "[%key:component::alexa_devices::common::data_description_country%]",
|
|
||||||
"username": "[%key:component::alexa_devices::common::data_description_username%]",
|
"username": "[%key:component::alexa_devices::common::data_description_username%]",
|
||||||
"password": "[%key:component::alexa_devices::common::data_description_password%]",
|
"password": "[%key:component::alexa_devices::common::data_description_password%]",
|
||||||
"code": "[%key:component::alexa_devices::common::data_description_code%]"
|
"code": "[%key:component::alexa_devices::common::data_description_code%]"
|
||||||
@@ -46,7 +43,6 @@
|
|||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
"cannot_retrieve_data": "Unable to retrieve data from Amazon. Please try again later.",
|
"cannot_retrieve_data": "Unable to retrieve data from Amazon. Please try again later.",
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
"wrong_country": "Wrong country selected. Please select the country where your Amazon account is registered.",
|
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -16,7 +16,7 @@ from homeassistant.helpers.selector import (
|
|||||||
SelectSelectorMode,
|
SelectSelectorMode,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .const import CONF_SITE_ID, CONF_SITE_NAME, DOMAIN
|
from .const import CONF_SITE_ID, CONF_SITE_NAME, DOMAIN, REQUEST_TIMEOUT
|
||||||
|
|
||||||
API_URL = "https://app.amber.com.au/developers"
|
API_URL = "https://app.amber.com.au/developers"
|
||||||
|
|
||||||
@@ -64,7 +64,9 @@ class AmberElectricConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
api = amberelectric.AmberApi(api_client)
|
api = amberelectric.AmberApi(api_client)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sites: list[Site] = filter_sites(api.get_sites())
|
sites: list[Site] = filter_sites(
|
||||||
|
api.get_sites(_request_timeout=REQUEST_TIMEOUT)
|
||||||
|
)
|
||||||
except amberelectric.ApiException as api_exception:
|
except amberelectric.ApiException as api_exception:
|
||||||
if api_exception.status == 403:
|
if api_exception.status == 403:
|
||||||
self._errors[CONF_API_TOKEN] = "invalid_api_token"
|
self._errors[CONF_API_TOKEN] = "invalid_api_token"
|
||||||
|
@@ -22,3 +22,5 @@ SERVICE_GET_FORECASTS = "get_forecasts"
|
|||||||
GENERAL_CHANNEL = "general"
|
GENERAL_CHANNEL = "general"
|
||||||
CONTROLLED_LOAD_CHANNEL = "controlled_load"
|
CONTROLLED_LOAD_CHANNEL = "controlled_load"
|
||||||
FEED_IN_CHANNEL = "feed_in"
|
FEED_IN_CHANNEL = "feed_in"
|
||||||
|
|
||||||
|
REQUEST_TIMEOUT = 15
|
||||||
|
@@ -16,7 +16,7 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import LOGGER
|
from .const import LOGGER, REQUEST_TIMEOUT
|
||||||
from .helpers import normalize_descriptor
|
from .helpers import normalize_descriptor
|
||||||
|
|
||||||
type AmberConfigEntry = ConfigEntry[AmberUpdateCoordinator]
|
type AmberConfigEntry = ConfigEntry[AmberUpdateCoordinator]
|
||||||
@@ -82,7 +82,11 @@ class AmberUpdateCoordinator(DataUpdateCoordinator):
|
|||||||
"grid": {},
|
"grid": {},
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
data = self._api.get_current_prices(self.site_id, next=288)
|
data = self._api.get_current_prices(
|
||||||
|
self.site_id,
|
||||||
|
next=288,
|
||||||
|
_request_timeout=REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
intervals = [interval.actual_instance for interval in data]
|
intervals = [interval.actual_instance for interval in data]
|
||||||
except ApiException as api_exception:
|
except ApiException as api_exception:
|
||||||
raise UpdateFailed("Missing price data, skipping update") from api_exception
|
raise UpdateFailed("Missing price data, skipping update") from api_exception
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"bleak==1.0.1",
|
"bleak==1.0.1",
|
||||||
"bleak-retry-connector==4.0.1",
|
"bleak-retry-connector==4.0.2",
|
||||||
"bluetooth-adapters==2.0.0",
|
"bluetooth-adapters==2.0.0",
|
||||||
"bluetooth-auto-recovery==1.5.2",
|
"bluetooth-auto-recovery==1.5.2",
|
||||||
"bluetooth-data-tools==1.28.2",
|
"bluetooth-data-tools==1.28.2",
|
||||||
|
@@ -69,12 +69,7 @@ class SHCEntity(SHCBaseEntity):
|
|||||||
manufacturer=device.manufacturer,
|
manufacturer=device.manufacturer,
|
||||||
model=device.device_model,
|
model=device.device_model,
|
||||||
name=device.name,
|
name=device.name,
|
||||||
via_device=(
|
via_device=(DOMAIN, device.root_device_id),
|
||||||
DOMAIN,
|
|
||||||
device.parent_device_id
|
|
||||||
if device.parent_device_id is not None
|
|
||||||
else parent_id,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
super().__init__(device=device, parent_id=parent_id, entry_id=entry_id)
|
super().__init__(device=device, parent_id=parent_id, entry_id=entry_id)
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/bosch_shc",
|
"documentation": "https://www.home-assistant.io/integrations/bosch_shc",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["boschshcpy"],
|
"loggers": ["boschshcpy"],
|
||||||
"requirements": ["boschshcpy==0.2.91"],
|
"requirements": ["boschshcpy==0.2.107"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_http._tcp.local.",
|
"type": "_http._tcp.local.",
|
||||||
|
@@ -17,7 +17,7 @@ DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS = False
|
|||||||
|
|
||||||
DEFAULT_PORT: Final = 6053
|
DEFAULT_PORT: Final = 6053
|
||||||
|
|
||||||
STABLE_BLE_VERSION_STR = "2025.5.0"
|
STABLE_BLE_VERSION_STR = "2025.8.0"
|
||||||
STABLE_BLE_VERSION = AwesomeVersion(STABLE_BLE_VERSION_STR)
|
STABLE_BLE_VERSION = AwesomeVersion(STABLE_BLE_VERSION_STR)
|
||||||
PROJECT_URLS = {
|
PROJECT_URLS = {
|
||||||
"esphome.bluetooth-proxy": "https://esphome.github.io/bluetooth-proxies/",
|
"esphome.bluetooth-proxy": "https://esphome.github.io/bluetooth-proxies/",
|
||||||
|
@@ -50,7 +50,7 @@ CONF_EXTRA_JS_URL_ES5 = "extra_js_url_es5"
|
|||||||
CONF_FRONTEND_REPO = "development_repo"
|
CONF_FRONTEND_REPO = "development_repo"
|
||||||
CONF_JS_VERSION = "javascript_version"
|
CONF_JS_VERSION = "javascript_version"
|
||||||
|
|
||||||
DEFAULT_THEME_COLOR = "#03A9F4"
|
DEFAULT_THEME_COLOR = "#2980b9"
|
||||||
|
|
||||||
|
|
||||||
DATA_PANELS: HassKey[dict[str, Panel]] = HassKey("frontend_panels")
|
DATA_PANELS: HassKey[dict[str, Panel]] = HassKey("frontend_panels")
|
||||||
|
@@ -20,5 +20,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["home-assistant-frontend==20250811.0"]
|
"requirements": ["home-assistant-frontend==20250811.1"]
|
||||||
}
|
}
|
||||||
|
@@ -61,18 +61,19 @@ PLACEHOLDER_KEY_REASON = "reason"
|
|||||||
|
|
||||||
UNSUPPORTED_REASONS = {
|
UNSUPPORTED_REASONS = {
|
||||||
"apparmor",
|
"apparmor",
|
||||||
|
"cgroup_version",
|
||||||
"connectivity_check",
|
"connectivity_check",
|
||||||
"content_trust",
|
"content_trust",
|
||||||
"dbus",
|
"dbus",
|
||||||
"dns_server",
|
"dns_server",
|
||||||
"docker_configuration",
|
"docker_configuration",
|
||||||
"docker_version",
|
"docker_version",
|
||||||
"cgroup_version",
|
|
||||||
"job_conditions",
|
"job_conditions",
|
||||||
"lxc",
|
"lxc",
|
||||||
"network_manager",
|
"network_manager",
|
||||||
"os",
|
"os",
|
||||||
"os_agent",
|
"os_agent",
|
||||||
|
"os_version",
|
||||||
"restart_policy",
|
"restart_policy",
|
||||||
"software",
|
"software",
|
||||||
"source_mods",
|
"source_mods",
|
||||||
@@ -80,6 +81,7 @@ UNSUPPORTED_REASONS = {
|
|||||||
"systemd",
|
"systemd",
|
||||||
"systemd_journal",
|
"systemd_journal",
|
||||||
"systemd_resolved",
|
"systemd_resolved",
|
||||||
|
"virtualization_image",
|
||||||
}
|
}
|
||||||
# Some unsupported reasons also mark the system as unhealthy. If the unsupported reason
|
# Some unsupported reasons also mark the system as unhealthy. If the unsupported reason
|
||||||
# provides no additional information beyond the unhealthy one then skip that repair.
|
# provides no additional information beyond the unhealthy one then skip that repair.
|
||||||
|
@@ -5,5 +5,5 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["holidays==0.78", "babel==2.15.0"]
|
"requirements": ["holidays==0.79", "babel==2.15.0"]
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@ from homeassistant.core import HomeAssistant, ServiceCall, callback
|
|||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
|
||||||
from .account import IcloudAccount
|
from .account import IcloudAccount, IcloudConfigEntry
|
||||||
from .const import (
|
from .const import (
|
||||||
ATTR_ACCOUNT,
|
ATTR_ACCOUNT,
|
||||||
ATTR_DEVICE_NAME,
|
ATTR_DEVICE_NAME,
|
||||||
@@ -92,8 +92,10 @@ def lost_device(service: ServiceCall) -> None:
|
|||||||
def update_account(service: ServiceCall) -> None:
|
def update_account(service: ServiceCall) -> None:
|
||||||
"""Call the update function of an iCloud account."""
|
"""Call the update function of an iCloud account."""
|
||||||
if (account := service.data.get(ATTR_ACCOUNT)) is None:
|
if (account := service.data.get(ATTR_ACCOUNT)) is None:
|
||||||
for account in service.hass.data[DOMAIN].values():
|
# Update all accounts when no specific account is provided
|
||||||
account.keep_alive()
|
entry: IcloudConfigEntry
|
||||||
|
for entry in service.hass.config_entries.async_loaded_entries(DOMAIN):
|
||||||
|
entry.runtime_data.keep_alive()
|
||||||
else:
|
else:
|
||||||
_get_account(service.hass, account).keep_alive()
|
_get_account(service.hass, account).keep_alive()
|
||||||
|
|
||||||
@@ -102,17 +104,12 @@ def _get_account(hass: HomeAssistant, account_identifier: str) -> IcloudAccount:
|
|||||||
if account_identifier is None:
|
if account_identifier is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
icloud_account: IcloudAccount | None = hass.data[DOMAIN].get(account_identifier)
|
entry: IcloudConfigEntry
|
||||||
if icloud_account is None:
|
for entry in hass.config_entries.async_loaded_entries(DOMAIN):
|
||||||
for account in hass.data[DOMAIN].values():
|
if entry.runtime_data.username == account_identifier:
|
||||||
if account.username == account_identifier:
|
return entry.runtime_data
|
||||||
icloud_account = account
|
|
||||||
|
|
||||||
if icloud_account is None:
|
raise ValueError(f"No iCloud account with username or name {account_identifier}")
|
||||||
raise ValueError(
|
|
||||||
f"No iCloud account with username or name {account_identifier}"
|
|
||||||
)
|
|
||||||
return icloud_account
|
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
@@ -75,13 +75,11 @@ class InverterCoordinator(DataUpdateCoordinator[dict[str, str | float | int]]):
|
|||||||
data: dict[str, str | float | int] = {}
|
data: dict[str, str | float | int] = {}
|
||||||
|
|
||||||
async with timeout(TIMEOUT):
|
async with timeout(TIMEOUT):
|
||||||
await self._api.login(
|
|
||||||
self.config_entry.data[CONF_USERNAME],
|
|
||||||
self.config_entry.data[CONF_PASSWORD],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Fetch data using distant API
|
|
||||||
try:
|
try:
|
||||||
|
await self._api.login(
|
||||||
|
self.config_entry.data[CONF_USERNAME],
|
||||||
|
self.config_entry.data[CONF_PASSWORD],
|
||||||
|
)
|
||||||
await self._api.update()
|
await self._api.update()
|
||||||
except (ValueError, ClientError) as e:
|
except (ValueError, ClientError) as e:
|
||||||
raise UpdateFailed(e) from e
|
raise UpdateFailed(e) from e
|
||||||
|
@@ -14,7 +14,6 @@ from homeassistant.const import (
|
|||||||
EntityCategory,
|
EntityCategory,
|
||||||
UnitOfElectricCurrent,
|
UnitOfElectricCurrent,
|
||||||
UnitOfElectricPotential,
|
UnitOfElectricPotential,
|
||||||
UnitOfEnergy,
|
|
||||||
UnitOfFrequency,
|
UnitOfFrequency,
|
||||||
UnitOfPower,
|
UnitOfPower,
|
||||||
UnitOfTemperature,
|
UnitOfTemperature,
|
||||||
@@ -50,8 +49,8 @@ SENSOR_DESCRIPTIONS = (
|
|||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="battery_stored",
|
key="battery_stored",
|
||||||
translation_key="battery_stored",
|
translation_key="battery_stored",
|
||||||
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.ENERGY_STORAGE,
|
device_class=SensorDeviceClass.POWER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
# Grid
|
# Grid
|
||||||
@@ -238,16 +237,16 @@ SENSOR_DESCRIPTIONS = (
|
|||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="pv_consumed",
|
key="pv_consumed",
|
||||||
translation_key="pv_consumed",
|
translation_key="pv_consumed",
|
||||||
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.POWER,
|
||||||
state_class=SensorStateClass.TOTAL,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="pv_injected",
|
key="pv_injected",
|
||||||
translation_key="pv_injected",
|
translation_key="pv_injected",
|
||||||
native_unit_of_measurement=UnitOfEnergy.WATT_HOUR,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.POWER,
|
||||||
state_class=SensorStateClass.TOTAL,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="pv_power_1",
|
key="pv_power_1",
|
||||||
@@ -290,14 +289,14 @@ SENSOR_DESCRIPTIONS = (
|
|||||||
key="monitoring_self_consumption",
|
key="monitoring_self_consumption",
|
||||||
translation_key="monitoring_self_consumption",
|
translation_key="monitoring_self_consumption",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.TOTAL,
|
||||||
suggested_display_precision=2,
|
suggested_display_precision=2,
|
||||||
),
|
),
|
||||||
SensorEntityDescription(
|
SensorEntityDescription(
|
||||||
key="monitoring_self_sufficiency",
|
key="monitoring_self_sufficiency",
|
||||||
translation_key="monitoring_self_sufficiency",
|
translation_key="monitoring_self_sufficiency",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.TOTAL,
|
||||||
suggested_display_precision=2,
|
suggested_display_precision=2,
|
||||||
),
|
),
|
||||||
# Monitoring (instant minute data)
|
# Monitoring (instant minute data)
|
||||||
|
@@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
|
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["imgw_pib==1.5.3"]
|
"requirements": ["imgw_pib==1.5.4"]
|
||||||
}
|
}
|
||||||
|
@@ -37,7 +37,7 @@ class DeviceDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||||||
name=f"{DOMAIN}_{ha_bridge.device.device_id}",
|
name=f"{DOMAIN}_{ha_bridge.device.device_id}",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.data = {}
|
self.data = ha_bridge.update_status(None)
|
||||||
self.api = ha_bridge
|
self.api = ha_bridge
|
||||||
self.device_id = ha_bridge.device.device_id
|
self.device_id = ha_bridge.device.device_id
|
||||||
self.sub_id = ha_bridge.sub_id
|
self.sub_id = ha_bridge.sub_id
|
||||||
|
@@ -52,9 +52,12 @@ class MatterValve(MatterEntity, ValveEntity):
|
|||||||
|
|
||||||
async def async_set_valve_position(self, position: int) -> None:
|
async def async_set_valve_position(self, position: int) -> None:
|
||||||
"""Move the valve to a specific position."""
|
"""Move the valve to a specific position."""
|
||||||
await self.send_device_command(
|
if position > 0:
|
||||||
ValveConfigurationAndControl.Commands.Open(targetLevel=position)
|
await self.send_device_command(
|
||||||
)
|
ValveConfigurationAndControl.Commands.Open(targetLevel=position)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
await self.send_device_command(ValveConfigurationAndControl.Commands.Close())
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _update_from_device(self) -> None:
|
def _update_from_device(self) -> None:
|
||||||
|
@@ -8,6 +8,6 @@
|
|||||||
"iot_class": "calculated",
|
"iot_class": "calculated",
|
||||||
"loggers": ["yt_dlp"],
|
"loggers": ["yt_dlp"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["yt-dlp[default]==2025.07.21"],
|
"requirements": ["yt-dlp[default]==2025.08.11"],
|
||||||
"single_config_entry": true
|
"single_config_entry": true
|
||||||
}
|
}
|
||||||
|
@@ -7,5 +7,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/mystrom",
|
"documentation": "https://www.home-assistant.io/integrations/mystrom",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["pymystrom"],
|
"loggers": ["pymystrom"],
|
||||||
"requirements": ["python-mystrom==2.4.0"]
|
"requirements": ["python-mystrom==2.5.0"]
|
||||||
}
|
}
|
||||||
|
@@ -10,7 +10,12 @@ from typing import Any, Final, cast
|
|||||||
from aionanoleaf import InvalidToken, Nanoleaf, Unauthorized, Unavailable
|
from aionanoleaf import InvalidToken, Nanoleaf, Unauthorized, Unavailable
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult
|
from homeassistant.config_entries import (
|
||||||
|
SOURCE_REAUTH,
|
||||||
|
SOURCE_USER,
|
||||||
|
ConfigFlow,
|
||||||
|
ConfigFlowResult,
|
||||||
|
)
|
||||||
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.json import save_json
|
from homeassistant.helpers.json import save_json
|
||||||
@@ -200,7 +205,9 @@ class NanoleafConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
return self.async_abort(reason="unknown")
|
return self.async_abort(reason="unknown")
|
||||||
name = self.nanoleaf.name
|
name = self.nanoleaf.name
|
||||||
|
|
||||||
await self.async_set_unique_id(name)
|
await self.async_set_unique_id(
|
||||||
|
name, raise_on_progress=self.source != SOURCE_USER
|
||||||
|
)
|
||||||
self._abort_if_unique_id_configured({CONF_HOST: self.nanoleaf.host})
|
self._abort_if_unique_id_configured({CONF_HOST: self.nanoleaf.host})
|
||||||
|
|
||||||
if discovery_integration_import:
|
if discovery_integration_import:
|
||||||
|
@@ -12,5 +12,5 @@
|
|||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pyatmo"],
|
"loggers": ["pyatmo"],
|
||||||
"requirements": ["pyatmo==9.2.1"]
|
"requirements": ["pyatmo==9.2.3"]
|
||||||
}
|
}
|
||||||
|
@@ -8,5 +8,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/onvif",
|
"documentation": "https://www.home-assistant.io/integrations/onvif",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["onvif", "wsdiscovery", "zeep"],
|
"loggers": ["onvif", "wsdiscovery", "zeep"],
|
||||||
"requirements": ["onvif-zeep-async==4.0.3", "WSDiscovery==2.1.2"]
|
"requirements": ["onvif-zeep-async==4.0.4", "WSDiscovery==2.1.2"]
|
||||||
}
|
}
|
||||||
|
@@ -91,6 +91,8 @@ MAX_TOOL_ITERATIONS = 10
|
|||||||
def _adjust_schema(schema: dict[str, Any]) -> None:
|
def _adjust_schema(schema: dict[str, Any]) -> None:
|
||||||
"""Adjust the schema to be compatible with OpenAI API."""
|
"""Adjust the schema to be compatible with OpenAI API."""
|
||||||
if schema["type"] == "object":
|
if schema["type"] == "object":
|
||||||
|
schema.setdefault("strict", True)
|
||||||
|
schema.setdefault("additionalProperties", False)
|
||||||
if "properties" not in schema:
|
if "properties" not in schema:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -124,8 +126,6 @@ def _format_structured_output(
|
|||||||
|
|
||||||
_adjust_schema(result)
|
_adjust_schema(result)
|
||||||
|
|
||||||
result["strict"] = True
|
|
||||||
result["additionalProperties"] = False
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@@ -7,5 +7,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/opower",
|
"documentation": "https://www.home-assistant.io/integrations/opower",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["opower"],
|
"loggers": ["opower"],
|
||||||
"requirements": ["opower==0.15.1"]
|
"requirements": ["opower==0.15.2"]
|
||||||
}
|
}
|
||||||
|
@@ -8,5 +8,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["renault_api"],
|
"loggers": ["renault_api"],
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["renault-api==0.3.1"]
|
"requirements": ["renault-api==0.4.0"]
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,6 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aiorussound"],
|
"loggers": ["aiorussound"],
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["aiorussound==4.8.0"],
|
"requirements": ["aiorussound==4.8.1"],
|
||||||
"zeroconf": ["_rio._tcp.local."]
|
"zeroconf": ["_rio._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@@ -30,5 +30,5 @@
|
|||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["pysmartthings"],
|
"loggers": ["pysmartthings"],
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["pysmartthings==3.2.8"]
|
"requirements": ["pysmartthings==3.2.9"]
|
||||||
}
|
}
|
||||||
|
@@ -326,7 +326,7 @@ class SqueezeBoxMediaPlayerEntity(SqueezeboxEntity, MediaPlayerEntity):
|
|||||||
def volume_level(self) -> float | None:
|
def volume_level(self) -> float | None:
|
||||||
"""Volume level of the media player (0..1)."""
|
"""Volume level of the media player (0..1)."""
|
||||||
if self._player.volume is not None:
|
if self._player.volume is not None:
|
||||||
return int(float(self._player.volume)) / 100.0
|
return float(self._player.volume) / 100.0
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -435,7 +435,7 @@ class SqueezeBoxMediaPlayerEntity(SqueezeboxEntity, MediaPlayerEntity):
|
|||||||
|
|
||||||
async def async_set_volume_level(self, volume: float) -> None:
|
async def async_set_volume_level(self, volume: float) -> None:
|
||||||
"""Set volume level, range 0..1."""
|
"""Set volume level, range 0..1."""
|
||||||
volume_percent = str(int(volume * 100))
|
volume_percent = str(round(volume * 100))
|
||||||
await self._player.async_set_volume(volume_percent)
|
await self._player.async_set_volume(volume_percent)
|
||||||
await self.coordinator.async_refresh()
|
await self.coordinator.async_refresh()
|
||||||
|
|
||||||
|
@@ -260,6 +260,8 @@ class VolvoMediumIntervalCoordinator(VolvoBaseCoordinator):
|
|||||||
"Volvo medium interval coordinator",
|
"Volvo medium interval coordinator",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._supported_capabilities: list[str] = []
|
||||||
|
|
||||||
async def _async_determine_api_calls(
|
async def _async_determine_api_calls(
|
||||||
self,
|
self,
|
||||||
) -> list[Callable[[], Coroutine[Any, Any, Any]]]:
|
) -> list[Callable[[], Coroutine[Any, Any, Any]]]:
|
||||||
@@ -267,6 +269,31 @@ class VolvoMediumIntervalCoordinator(VolvoBaseCoordinator):
|
|||||||
capabilities = await self.api.async_get_energy_capabilities()
|
capabilities = await self.api.async_get_energy_capabilities()
|
||||||
|
|
||||||
if capabilities.get("isSupported", False):
|
if capabilities.get("isSupported", False):
|
||||||
return [self.api.async_get_energy_state]
|
self._supported_capabilities = [
|
||||||
|
key
|
||||||
|
for key, value in capabilities.items()
|
||||||
|
if isinstance(value, dict) and value.get("isSupported", False)
|
||||||
|
]
|
||||||
|
|
||||||
|
return [self._async_get_energy_state]
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
async def _async_get_energy_state(
|
||||||
|
self,
|
||||||
|
) -> dict[str, VolvoCarsValueStatusField | None]:
|
||||||
|
def _mark_ok(
|
||||||
|
field: VolvoCarsValueStatusField | None,
|
||||||
|
) -> VolvoCarsValueStatusField | None:
|
||||||
|
if field:
|
||||||
|
field.status = "OK"
|
||||||
|
|
||||||
|
return field
|
||||||
|
|
||||||
|
energy_state = await self.api.async_get_energy_state()
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: _mark_ok(value)
|
||||||
|
for key, value in energy_state.items()
|
||||||
|
if key in self._supported_capabilities
|
||||||
|
}
|
||||||
|
@@ -67,8 +67,8 @@ def _calculate_time_to_service(field: VolvoCarsValue) -> int:
|
|||||||
|
|
||||||
def _charging_power_value(field: VolvoCarsValue) -> int:
|
def _charging_power_value(field: VolvoCarsValue) -> int:
|
||||||
return (
|
return (
|
||||||
int(field.value)
|
field.value
|
||||||
if isinstance(field, VolvoCarsValueStatusField) and field.status == "OK"
|
if isinstance(field, VolvoCarsValueStatusField) and isinstance(field.value, int)
|
||||||
else 0
|
else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -2,16 +2,23 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import asdict
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
|
from homeassistant.components.diagnostics import async_redact_data
|
||||||
from homeassistant.components.webhook import async_generate_url as webhook_generate_url
|
from homeassistant.components.webhook import async_generate_url as webhook_generate_url
|
||||||
from homeassistant.const import CONF_WEBHOOK_ID
|
from homeassistant.const import CONF_WEBHOOK_ID
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
from . import CONF_CLOUDHOOK_URL, WithingsConfigEntry
|
from . import CONF_CLOUDHOOK_URL, WithingsConfigEntry
|
||||||
|
|
||||||
|
TO_REDACT = {
|
||||||
|
"device_id",
|
||||||
|
"hashed_device_id",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def async_get_config_entry_diagnostics(
|
async def async_get_config_entry_diagnostics(
|
||||||
hass: HomeAssistant, entry: WithingsConfigEntry
|
hass: HomeAssistant, entry: WithingsConfigEntry
|
||||||
@@ -53,4 +60,8 @@ async def async_get_config_entry_diagnostics(
|
|||||||
"received_sleep_data": withings_data.sleep_coordinator.data is not None,
|
"received_sleep_data": withings_data.sleep_coordinator.data is not None,
|
||||||
"received_workout_data": withings_data.workout_coordinator.data is not None,
|
"received_workout_data": withings_data.workout_coordinator.data is not None,
|
||||||
"received_activity_data": withings_data.activity_coordinator.data is not None,
|
"received_activity_data": withings_data.activity_coordinator.data is not None,
|
||||||
|
"devices": async_redact_data(
|
||||||
|
[asdict(v) for v in withings_data.device_coordinator.data.values()],
|
||||||
|
TO_REDACT,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
@@ -7,5 +7,5 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["holidays"],
|
"loggers": ["holidays"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["holidays==0.78"]
|
"requirements": ["holidays==0.79"]
|
||||||
}
|
}
|
||||||
|
@@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/zimi",
|
"documentation": "https://www.home-assistant.io/integrations/zimi",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["zcc-helper==3.5.2"]
|
"requirements": ["zcc-helper==3.6"]
|
||||||
}
|
}
|
||||||
|
@@ -35,6 +35,7 @@ from homeassistant.const import CONF_NAME, CONF_URL
|
|||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.data_entry_flow import AbortFlow
|
from homeassistant.data_entry_flow import AbortFlow
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers import selector
|
||||||
from homeassistant.helpers.hassio import is_hassio
|
from homeassistant.helpers.hassio import is_hassio
|
||||||
from homeassistant.helpers.service_info.hassio import HassioServiceInfo
|
from homeassistant.helpers.service_info.hassio import HassioServiceInfo
|
||||||
from homeassistant.helpers.service_info.usb import UsbServiceInfo
|
from homeassistant.helpers.service_info.usb import UsbServiceInfo
|
||||||
@@ -88,6 +89,8 @@ ADDON_USER_INPUT_MAP = {
|
|||||||
CONF_ADDON_LR_S2_AUTHENTICATED_KEY: CONF_LR_S2_AUTHENTICATED_KEY,
|
CONF_ADDON_LR_S2_AUTHENTICATED_KEY: CONF_LR_S2_AUTHENTICATED_KEY,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CONF_ADDON_RF_REGION = "rf_region"
|
||||||
|
|
||||||
EXAMPLE_SERVER_URL = "ws://localhost:3000"
|
EXAMPLE_SERVER_URL = "ws://localhost:3000"
|
||||||
ON_SUPERVISOR_SCHEMA = vol.Schema({vol.Optional(CONF_USE_ADDON, default=True): bool})
|
ON_SUPERVISOR_SCHEMA = vol.Schema({vol.Optional(CONF_USE_ADDON, default=True): bool})
|
||||||
MIN_MIGRATION_SDK_VERSION = AwesomeVersion("6.61")
|
MIN_MIGRATION_SDK_VERSION = AwesomeVersion("6.61")
|
||||||
@@ -103,6 +106,19 @@ ZWAVE_JS_UI_MIGRATION_INSTRUCTIONS = (
|
|||||||
"#how-to-migrate-from-one-adapter-to-a-new-adapter-using-z-wave-js-ui"
|
"#how-to-migrate-from-one-adapter-to-a-new-adapter-using-z-wave-js-ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
RF_REGIONS = [
|
||||||
|
"Australia/New Zealand",
|
||||||
|
"China",
|
||||||
|
"Europe",
|
||||||
|
"Hong Kong",
|
||||||
|
"India",
|
||||||
|
"Israel",
|
||||||
|
"Japan",
|
||||||
|
"Korea",
|
||||||
|
"Russia",
|
||||||
|
"USA",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_manual_schema(user_input: dict[str, Any]) -> vol.Schema:
|
def get_manual_schema(user_input: dict[str, Any]) -> vol.Schema:
|
||||||
"""Return a schema for the manual step."""
|
"""Return a schema for the manual step."""
|
||||||
@@ -195,10 +211,12 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
self.backup_data: bytes | None = None
|
self.backup_data: bytes | None = None
|
||||||
self.backup_filepath: Path | None = None
|
self.backup_filepath: Path | None = None
|
||||||
self.use_addon = False
|
self.use_addon = False
|
||||||
|
self._addon_config_updates: dict[str, Any] = {}
|
||||||
self._migrating = False
|
self._migrating = False
|
||||||
self._reconfigure_config_entry: ZwaveJSConfigEntry | None = None
|
self._reconfigure_config_entry: ZwaveJSConfigEntry | None = None
|
||||||
self._usb_discovery = False
|
self._usb_discovery = False
|
||||||
self._recommended_install = False
|
self._recommended_install = False
|
||||||
|
self._rf_region: str | None = None
|
||||||
|
|
||||||
async def async_step_install_addon(
|
async def async_step_install_addon(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
@@ -236,6 +254,21 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Start Z-Wave JS add-on."""
|
"""Start Z-Wave JS add-on."""
|
||||||
|
if self.hass.config.country is None and (
|
||||||
|
not self._rf_region or self._rf_region == "Automatic"
|
||||||
|
):
|
||||||
|
# If the country is not set, we need to check the RF region add-on config.
|
||||||
|
addon_info = await self._async_get_addon_info()
|
||||||
|
rf_region: str | None = addon_info.options.get(CONF_ADDON_RF_REGION)
|
||||||
|
self._rf_region = rf_region
|
||||||
|
if rf_region is None or rf_region == "Automatic":
|
||||||
|
# If the RF region is not set, we need to ask the user to select it.
|
||||||
|
return await self.async_step_rf_region()
|
||||||
|
if config_updates := self._addon_config_updates:
|
||||||
|
# If we have updates to the add-on config, set them before starting the add-on.
|
||||||
|
self._addon_config_updates = {}
|
||||||
|
await self._async_set_addon_config(config_updates)
|
||||||
|
|
||||||
if not self.start_task:
|
if not self.start_task:
|
||||||
self.start_task = self.hass.async_create_task(self._async_start_addon())
|
self.start_task = self.hass.async_create_task(self._async_start_addon())
|
||||||
|
|
||||||
@@ -629,6 +662,33 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
return await self.async_step_on_supervisor({CONF_USE_ADDON: True})
|
return await self.async_step_on_supervisor({CONF_USE_ADDON: True})
|
||||||
return await self.async_step_on_supervisor()
|
return await self.async_step_on_supervisor()
|
||||||
|
|
||||||
|
async def async_step_rf_region(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle RF region selection step."""
|
||||||
|
if user_input is not None:
|
||||||
|
# Store the selected RF region
|
||||||
|
self._addon_config_updates[CONF_ADDON_RF_REGION] = self._rf_region = (
|
||||||
|
user_input["rf_region"]
|
||||||
|
)
|
||||||
|
return await self.async_step_start_addon()
|
||||||
|
|
||||||
|
schema = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required("rf_region"): selector.SelectSelector(
|
||||||
|
selector.SelectSelectorConfig(
|
||||||
|
options=RF_REGIONS,
|
||||||
|
mode=selector.SelectSelectorMode.DROPDOWN,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="rf_region",
|
||||||
|
data_schema=schema,
|
||||||
|
)
|
||||||
|
|
||||||
async def async_step_on_supervisor(
|
async def async_step_on_supervisor(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
@@ -728,7 +788,7 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
CONF_ADDON_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_key,
|
CONF_ADDON_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_key,
|
||||||
}
|
}
|
||||||
|
|
||||||
await self._async_set_addon_config(addon_config_updates)
|
self._addon_config_updates = addon_config_updates
|
||||||
return await self.async_step_start_addon()
|
return await self.async_step_start_addon()
|
||||||
|
|
||||||
# Network already exists, go to security keys step
|
# Network already exists, go to security keys step
|
||||||
@@ -799,7 +859,7 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
CONF_ADDON_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_key,
|
CONF_ADDON_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_key,
|
||||||
}
|
}
|
||||||
|
|
||||||
await self._async_set_addon_config(addon_config_updates)
|
self._addon_config_updates = addon_config_updates
|
||||||
return await self.async_step_start_addon()
|
return await self.async_step_start_addon()
|
||||||
|
|
||||||
data_schema = vol.Schema(
|
data_schema = vol.Schema(
|
||||||
@@ -1004,7 +1064,7 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
if self.usb_path:
|
if self.usb_path:
|
||||||
# USB discovery was used, so the device is already known.
|
# USB discovery was used, so the device is already known.
|
||||||
await self._async_set_addon_config({CONF_ADDON_DEVICE: self.usb_path})
|
self._addon_config_updates[CONF_ADDON_DEVICE] = self.usb_path
|
||||||
return await self.async_step_start_addon()
|
return await self.async_step_start_addon()
|
||||||
# Now that the old controller is gone, we can scan for serial ports again
|
# Now that the old controller is gone, we can scan for serial ports again
|
||||||
return await self.async_step_choose_serial_port()
|
return await self.async_step_choose_serial_port()
|
||||||
@@ -1136,6 +1196,8 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
CONF_ADDON_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_key,
|
CONF_ADDON_LR_S2_AUTHENTICATED_KEY: self.lr_s2_authenticated_key,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addon_config_updates = self._addon_config_updates | addon_config_updates
|
||||||
|
self._addon_config_updates = {}
|
||||||
await self._async_set_addon_config(addon_config_updates)
|
await self._async_set_addon_config(addon_config_updates)
|
||||||
|
|
||||||
if addon_info.state == AddonState.RUNNING and not self.restart_addon:
|
if addon_info.state == AddonState.RUNNING and not self.restart_addon:
|
||||||
@@ -1207,7 +1269,7 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
"""Choose a serial port."""
|
"""Choose a serial port."""
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
self.usb_path = user_input[CONF_USB_PATH]
|
self.usb_path = user_input[CONF_USB_PATH]
|
||||||
await self._async_set_addon_config({CONF_ADDON_DEVICE: self.usb_path})
|
self._addon_config_updates[CONF_ADDON_DEVICE] = self.usb_path
|
||||||
return await self.async_step_start_addon()
|
return await self.async_step_start_addon()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@@ -4,15 +4,15 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections.abc import Callable, Mapping
|
from collections.abc import Callable, Mapping
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any, cast
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from zwave_js_server.const import CommandClass
|
from zwave_js_server.const import CommandClass, RssiError
|
||||||
from zwave_js_server.const.command_class.meter import (
|
from zwave_js_server.const.command_class.meter import (
|
||||||
RESET_METER_OPTION_TARGET_VALUE,
|
RESET_METER_OPTION_TARGET_VALUE,
|
||||||
RESET_METER_OPTION_TYPE,
|
RESET_METER_OPTION_TYPE,
|
||||||
)
|
)
|
||||||
from zwave_js_server.exceptions import BaseZwaveJSServerError
|
from zwave_js_server.exceptions import BaseZwaveJSServerError, RssiErrorReceived
|
||||||
from zwave_js_server.model.controller import Controller
|
from zwave_js_server.model.controller import Controller
|
||||||
from zwave_js_server.model.controller.statistics import ControllerStatistics
|
from zwave_js_server.model.controller.statistics import ControllerStatistics
|
||||||
from zwave_js_server.model.driver import Driver
|
from zwave_js_server.model.driver import Driver
|
||||||
@@ -1049,7 +1049,7 @@ class ZWaveStatisticsSensor(SensorEntity):
|
|||||||
self,
|
self,
|
||||||
config_entry: ZwaveJSConfigEntry,
|
config_entry: ZwaveJSConfigEntry,
|
||||||
driver: Driver,
|
driver: Driver,
|
||||||
statistics_src: ZwaveNode | Controller,
|
statistics_src: Controller | ZwaveNode,
|
||||||
description: ZWaveJSStatisticsSensorEntityDescription,
|
description: ZWaveJSStatisticsSensorEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize a Z-Wave statistics entity."""
|
"""Initialize a Z-Wave statistics entity."""
|
||||||
@@ -1080,13 +1080,31 @@ class ZWaveStatisticsSensor(SensorEntity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def statistics_updated(self, event_data: dict) -> None:
|
def _statistics_updated(self, event_data: dict) -> None:
|
||||||
"""Call when statistics updated event is received."""
|
"""Call when statistics updated event is received."""
|
||||||
self._attr_native_value = self.entity_description.convert(
|
statistics = cast(
|
||||||
event_data["statistics_updated"], self.entity_description.key
|
ControllerStatistics | NodeStatistics, event_data["statistics_updated"]
|
||||||
)
|
)
|
||||||
|
self._set_statistics(statistics)
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _set_statistics(
|
||||||
|
self, statistics: ControllerStatistics | NodeStatistics
|
||||||
|
) -> None:
|
||||||
|
"""Set updated statistics."""
|
||||||
|
try:
|
||||||
|
self._attr_native_value = self.entity_description.convert(
|
||||||
|
statistics, self.entity_description.key
|
||||||
|
)
|
||||||
|
except RssiErrorReceived as err:
|
||||||
|
if err.error is RssiError.NOT_AVAILABLE:
|
||||||
|
self._attr_available = False
|
||||||
|
return
|
||||||
|
self._attr_native_value = None
|
||||||
|
# Reset available state.
|
||||||
|
self._attr_available = True
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Call when entity is added."""
|
"""Call when entity is added."""
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
@@ -1104,10 +1122,8 @@ class ZWaveStatisticsSensor(SensorEntity):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
self.statistics_src.on("statistics updated", self.statistics_updated)
|
self.statistics_src.on("statistics updated", self._statistics_updated)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set initial state
|
# Set initial state
|
||||||
self._attr_native_value = self.entity_description.convert(
|
self._set_statistics(self.statistics_src.statistics)
|
||||||
self.statistics_src.statistics, self.entity_description.key
|
|
||||||
)
|
|
||||||
|
@@ -113,6 +113,16 @@
|
|||||||
"description": "[%key:component::zwave_js::config::step::on_supervisor::description%]",
|
"description": "[%key:component::zwave_js::config::step::on_supervisor::description%]",
|
||||||
"title": "[%key:component::zwave_js::config::step::on_supervisor::title%]"
|
"title": "[%key:component::zwave_js::config::step::on_supervisor::title%]"
|
||||||
},
|
},
|
||||||
|
"rf_region": {
|
||||||
|
"title": "Z-Wave region",
|
||||||
|
"description": "Select the RF region for your Z-Wave network.",
|
||||||
|
"data": {
|
||||||
|
"rf_region": "RF region"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"rf_region": "The radio frequency region for your Z-Wave network. This must match the region of your Z-Wave devices."
|
||||||
|
}
|
||||||
|
},
|
||||||
"start_addon": {
|
"start_addon": {
|
||||||
"title": "Configuring add-on"
|
"title": "Configuring add-on"
|
||||||
},
|
},
|
||||||
|
@@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2025
|
MAJOR_VERSION: Final = 2025
|
||||||
MINOR_VERSION: Final = 8
|
MINOR_VERSION: Final = 8
|
||||||
PATCH_VERSION: Final = "2"
|
PATCH_VERSION: Final = "3"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 2)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 13, 2)
|
||||||
|
@@ -20,7 +20,7 @@ audioop-lts==0.2.1
|
|||||||
av==13.1.0
|
av==13.1.0
|
||||||
awesomeversion==25.5.0
|
awesomeversion==25.5.0
|
||||||
bcrypt==4.3.0
|
bcrypt==4.3.0
|
||||||
bleak-retry-connector==4.0.1
|
bleak-retry-connector==4.0.2
|
||||||
bleak==1.0.1
|
bleak==1.0.1
|
||||||
bluetooth-adapters==2.0.0
|
bluetooth-adapters==2.0.0
|
||||||
bluetooth-auto-recovery==1.5.2
|
bluetooth-auto-recovery==1.5.2
|
||||||
@@ -38,7 +38,7 @@ habluetooth==4.0.2
|
|||||||
hass-nabucasa==0.111.2
|
hass-nabucasa==0.111.2
|
||||||
hassil==2.2.3
|
hassil==2.2.3
|
||||||
home-assistant-bluetooth==1.13.1
|
home-assistant-bluetooth==1.13.1
|
||||||
home-assistant-frontend==20250811.0
|
home-assistant-frontend==20250811.1
|
||||||
home-assistant-intents==2025.7.30
|
home-assistant-intents==2025.7.30
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
@@ -221,3 +221,6 @@ num2words==0.5.14
|
|||||||
# downgraded or upgraded by custom components
|
# downgraded or upgraded by custom components
|
||||||
# This ensures all use the same version
|
# This ensures all use the same version
|
||||||
pymodbus==3.9.2
|
pymodbus==3.9.2
|
||||||
|
|
||||||
|
# Some packages don't support gql 4.0.0 yet
|
||||||
|
gql<4.0.0
|
||||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2025.8.2"
|
version = "2025.8.3"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
|
30
requirements_all.txt
generated
30
requirements_all.txt
generated
@@ -185,7 +185,7 @@ aioairzone-cloud==0.7.1
|
|||||||
aioairzone==1.0.0
|
aioairzone==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.alexa_devices
|
# homeassistant.components.alexa_devices
|
||||||
aioamazondevices==4.0.0
|
aioamazondevices==5.0.0
|
||||||
|
|
||||||
# homeassistant.components.ambient_network
|
# homeassistant.components.ambient_network
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
@@ -375,7 +375,7 @@ aioridwell==2024.01.0
|
|||||||
aioruckus==0.42
|
aioruckus==0.42
|
||||||
|
|
||||||
# homeassistant.components.russound_rio
|
# homeassistant.components.russound_rio
|
||||||
aiorussound==4.8.0
|
aiorussound==4.8.1
|
||||||
|
|
||||||
# homeassistant.components.ruuvi_gateway
|
# homeassistant.components.ruuvi_gateway
|
||||||
aioruuvigateway==0.1.0
|
aioruuvigateway==0.1.0
|
||||||
@@ -625,7 +625,7 @@ bizkaibus==0.1.1
|
|||||||
bleak-esphome==3.1.0
|
bleak-esphome==3.1.0
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bleak-retry-connector==4.0.1
|
bleak-retry-connector==4.0.2
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bleak==1.0.1
|
bleak==1.0.1
|
||||||
@@ -667,7 +667,7 @@ bond-async==0.2.1
|
|||||||
bosch-alarm-mode2==0.4.6
|
bosch-alarm-mode2==0.4.6
|
||||||
|
|
||||||
# homeassistant.components.bosch_shc
|
# homeassistant.components.bosch_shc
|
||||||
boschshcpy==0.2.91
|
boschshcpy==0.2.107
|
||||||
|
|
||||||
# homeassistant.components.amazon_polly
|
# homeassistant.components.amazon_polly
|
||||||
# homeassistant.components.route53
|
# homeassistant.components.route53
|
||||||
@@ -1171,10 +1171,10 @@ hole==0.9.0
|
|||||||
|
|
||||||
# homeassistant.components.holiday
|
# homeassistant.components.holiday
|
||||||
# homeassistant.components.workday
|
# homeassistant.components.workday
|
||||||
holidays==0.78
|
holidays==0.79
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20250811.0
|
home-assistant-frontend==20250811.1
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2025.7.30
|
home-assistant-intents==2025.7.30
|
||||||
@@ -1240,7 +1240,7 @@ ihcsdk==2.8.5
|
|||||||
imeon_inverter_api==0.3.14
|
imeon_inverter_api==0.3.14
|
||||||
|
|
||||||
# homeassistant.components.imgw_pib
|
# homeassistant.components.imgw_pib
|
||||||
imgw_pib==1.5.3
|
imgw_pib==1.5.4
|
||||||
|
|
||||||
# homeassistant.components.incomfort
|
# homeassistant.components.incomfort
|
||||||
incomfort-client==0.6.9
|
incomfort-client==0.6.9
|
||||||
@@ -1594,7 +1594,7 @@ ondilo==0.5.0
|
|||||||
onedrive-personal-sdk==0.0.14
|
onedrive-personal-sdk==0.0.14
|
||||||
|
|
||||||
# homeassistant.components.onvif
|
# homeassistant.components.onvif
|
||||||
onvif-zeep-async==4.0.3
|
onvif-zeep-async==4.0.4
|
||||||
|
|
||||||
# homeassistant.components.opengarage
|
# homeassistant.components.opengarage
|
||||||
open-garage==0.2.0
|
open-garage==0.2.0
|
||||||
@@ -1628,7 +1628,7 @@ openwrt-luci-rpc==1.1.17
|
|||||||
openwrt-ubus-rpc==0.0.2
|
openwrt-ubus-rpc==0.0.2
|
||||||
|
|
||||||
# homeassistant.components.opower
|
# homeassistant.components.opower
|
||||||
opower==0.15.1
|
opower==0.15.2
|
||||||
|
|
||||||
# homeassistant.components.oralb
|
# homeassistant.components.oralb
|
||||||
oralb-ble==0.17.6
|
oralb-ble==0.17.6
|
||||||
@@ -1852,7 +1852,7 @@ pyasuswrt==0.1.21
|
|||||||
pyatag==0.3.5.3
|
pyatag==0.3.5.3
|
||||||
|
|
||||||
# homeassistant.components.netatmo
|
# homeassistant.components.netatmo
|
||||||
pyatmo==9.2.1
|
pyatmo==9.2.3
|
||||||
|
|
||||||
# homeassistant.components.apple_tv
|
# homeassistant.components.apple_tv
|
||||||
pyatv==0.16.1
|
pyatv==0.16.1
|
||||||
@@ -2352,7 +2352,7 @@ pysmappee==0.2.29
|
|||||||
pysmarlaapi==0.9.1
|
pysmarlaapi==0.9.1
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartthings==3.2.8
|
pysmartthings==3.2.9
|
||||||
|
|
||||||
# homeassistant.components.smarty
|
# homeassistant.components.smarty
|
||||||
pysmarty2==0.10.2
|
pysmarty2==0.10.2
|
||||||
@@ -2478,7 +2478,7 @@ python-miio==0.5.12
|
|||||||
python-mpd2==3.1.1
|
python-mpd2==3.1.1
|
||||||
|
|
||||||
# homeassistant.components.mystrom
|
# homeassistant.components.mystrom
|
||||||
python-mystrom==2.4.0
|
python-mystrom==2.5.0
|
||||||
|
|
||||||
# homeassistant.components.open_router
|
# homeassistant.components.open_router
|
||||||
python-open-router==0.3.1
|
python-open-router==0.3.1
|
||||||
@@ -2660,7 +2660,7 @@ refoss-ha==1.2.5
|
|||||||
regenmaschine==2024.03.0
|
regenmaschine==2024.03.0
|
||||||
|
|
||||||
# homeassistant.components.renault
|
# homeassistant.components.renault
|
||||||
renault-api==0.3.1
|
renault-api==0.4.0
|
||||||
|
|
||||||
# homeassistant.components.renson
|
# homeassistant.components.renson
|
||||||
renson-endura-delta==1.7.2
|
renson-endura-delta==1.7.2
|
||||||
@@ -3185,7 +3185,7 @@ youless-api==2.2.0
|
|||||||
youtubeaio==2.0.0
|
youtubeaio==2.0.0
|
||||||
|
|
||||||
# homeassistant.components.media_extractor
|
# homeassistant.components.media_extractor
|
||||||
yt-dlp[default]==2025.07.21
|
yt-dlp[default]==2025.08.11
|
||||||
|
|
||||||
# homeassistant.components.zabbix
|
# homeassistant.components.zabbix
|
||||||
zabbix-utils==2.0.2
|
zabbix-utils==2.0.2
|
||||||
@@ -3194,7 +3194,7 @@ zabbix-utils==2.0.2
|
|||||||
zamg==0.3.6
|
zamg==0.3.6
|
||||||
|
|
||||||
# homeassistant.components.zimi
|
# homeassistant.components.zimi
|
||||||
zcc-helper==3.5.2
|
zcc-helper==3.6
|
||||||
|
|
||||||
# homeassistant.components.zeroconf
|
# homeassistant.components.zeroconf
|
||||||
zeroconf==0.147.0
|
zeroconf==0.147.0
|
||||||
|
30
requirements_test_all.txt
generated
30
requirements_test_all.txt
generated
@@ -173,7 +173,7 @@ aioairzone-cloud==0.7.1
|
|||||||
aioairzone==1.0.0
|
aioairzone==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.alexa_devices
|
# homeassistant.components.alexa_devices
|
||||||
aioamazondevices==4.0.0
|
aioamazondevices==5.0.0
|
||||||
|
|
||||||
# homeassistant.components.ambient_network
|
# homeassistant.components.ambient_network
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
@@ -357,7 +357,7 @@ aioridwell==2024.01.0
|
|||||||
aioruckus==0.42
|
aioruckus==0.42
|
||||||
|
|
||||||
# homeassistant.components.russound_rio
|
# homeassistant.components.russound_rio
|
||||||
aiorussound==4.8.0
|
aiorussound==4.8.1
|
||||||
|
|
||||||
# homeassistant.components.ruuvi_gateway
|
# homeassistant.components.ruuvi_gateway
|
||||||
aioruuvigateway==0.1.0
|
aioruuvigateway==0.1.0
|
||||||
@@ -559,7 +559,7 @@ bimmer-connected[china]==0.17.2
|
|||||||
bleak-esphome==3.1.0
|
bleak-esphome==3.1.0
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bleak-retry-connector==4.0.1
|
bleak-retry-connector==4.0.2
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bleak==1.0.1
|
bleak==1.0.1
|
||||||
@@ -598,7 +598,7 @@ bond-async==0.2.1
|
|||||||
bosch-alarm-mode2==0.4.6
|
bosch-alarm-mode2==0.4.6
|
||||||
|
|
||||||
# homeassistant.components.bosch_shc
|
# homeassistant.components.bosch_shc
|
||||||
boschshcpy==0.2.91
|
boschshcpy==0.2.107
|
||||||
|
|
||||||
# homeassistant.components.aws
|
# homeassistant.components.aws
|
||||||
botocore==1.37.1
|
botocore==1.37.1
|
||||||
@@ -1020,10 +1020,10 @@ hole==0.9.0
|
|||||||
|
|
||||||
# homeassistant.components.holiday
|
# homeassistant.components.holiday
|
||||||
# homeassistant.components.workday
|
# homeassistant.components.workday
|
||||||
holidays==0.78
|
holidays==0.79
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20250811.0
|
home-assistant-frontend==20250811.1
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2025.7.30
|
home-assistant-intents==2025.7.30
|
||||||
@@ -1074,7 +1074,7 @@ igloohome-api==0.1.1
|
|||||||
imeon_inverter_api==0.3.14
|
imeon_inverter_api==0.3.14
|
||||||
|
|
||||||
# homeassistant.components.imgw_pib
|
# homeassistant.components.imgw_pib
|
||||||
imgw_pib==1.5.3
|
imgw_pib==1.5.4
|
||||||
|
|
||||||
# homeassistant.components.incomfort
|
# homeassistant.components.incomfort
|
||||||
incomfort-client==0.6.9
|
incomfort-client==0.6.9
|
||||||
@@ -1362,7 +1362,7 @@ ondilo==0.5.0
|
|||||||
onedrive-personal-sdk==0.0.14
|
onedrive-personal-sdk==0.0.14
|
||||||
|
|
||||||
# homeassistant.components.onvif
|
# homeassistant.components.onvif
|
||||||
onvif-zeep-async==4.0.3
|
onvif-zeep-async==4.0.4
|
||||||
|
|
||||||
# homeassistant.components.opengarage
|
# homeassistant.components.opengarage
|
||||||
open-garage==0.2.0
|
open-garage==0.2.0
|
||||||
@@ -1384,7 +1384,7 @@ openhomedevice==2.2.0
|
|||||||
openwebifpy==4.3.1
|
openwebifpy==4.3.1
|
||||||
|
|
||||||
# homeassistant.components.opower
|
# homeassistant.components.opower
|
||||||
opower==0.15.1
|
opower==0.15.2
|
||||||
|
|
||||||
# homeassistant.components.oralb
|
# homeassistant.components.oralb
|
||||||
oralb-ble==0.17.6
|
oralb-ble==0.17.6
|
||||||
@@ -1557,7 +1557,7 @@ pyasuswrt==0.1.21
|
|||||||
pyatag==0.3.5.3
|
pyatag==0.3.5.3
|
||||||
|
|
||||||
# homeassistant.components.netatmo
|
# homeassistant.components.netatmo
|
||||||
pyatmo==9.2.1
|
pyatmo==9.2.3
|
||||||
|
|
||||||
# homeassistant.components.apple_tv
|
# homeassistant.components.apple_tv
|
||||||
pyatv==0.16.1
|
pyatv==0.16.1
|
||||||
@@ -1955,7 +1955,7 @@ pysmappee==0.2.29
|
|||||||
pysmarlaapi==0.9.1
|
pysmarlaapi==0.9.1
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartthings==3.2.8
|
pysmartthings==3.2.9
|
||||||
|
|
||||||
# homeassistant.components.smarty
|
# homeassistant.components.smarty
|
||||||
pysmarty2==0.10.2
|
pysmarty2==0.10.2
|
||||||
@@ -2051,7 +2051,7 @@ python-miio==0.5.12
|
|||||||
python-mpd2==3.1.1
|
python-mpd2==3.1.1
|
||||||
|
|
||||||
# homeassistant.components.mystrom
|
# homeassistant.components.mystrom
|
||||||
python-mystrom==2.4.0
|
python-mystrom==2.5.0
|
||||||
|
|
||||||
# homeassistant.components.open_router
|
# homeassistant.components.open_router
|
||||||
python-open-router==0.3.1
|
python-open-router==0.3.1
|
||||||
@@ -2206,7 +2206,7 @@ refoss-ha==1.2.5
|
|||||||
regenmaschine==2024.03.0
|
regenmaschine==2024.03.0
|
||||||
|
|
||||||
# homeassistant.components.renault
|
# homeassistant.components.renault
|
||||||
renault-api==0.3.1
|
renault-api==0.4.0
|
||||||
|
|
||||||
# homeassistant.components.renson
|
# homeassistant.components.renson
|
||||||
renson-endura-delta==1.7.2
|
renson-endura-delta==1.7.2
|
||||||
@@ -2632,13 +2632,13 @@ youless-api==2.2.0
|
|||||||
youtubeaio==2.0.0
|
youtubeaio==2.0.0
|
||||||
|
|
||||||
# homeassistant.components.media_extractor
|
# homeassistant.components.media_extractor
|
||||||
yt-dlp[default]==2025.07.21
|
yt-dlp[default]==2025.08.11
|
||||||
|
|
||||||
# homeassistant.components.zamg
|
# homeassistant.components.zamg
|
||||||
zamg==0.3.6
|
zamg==0.3.6
|
||||||
|
|
||||||
# homeassistant.components.zimi
|
# homeassistant.components.zimi
|
||||||
zcc-helper==3.5.2
|
zcc-helper==3.6
|
||||||
|
|
||||||
# homeassistant.components.zeroconf
|
# homeassistant.components.zeroconf
|
||||||
zeroconf==0.147.0
|
zeroconf==0.147.0
|
||||||
|
@@ -247,6 +247,9 @@ num2words==0.5.14
|
|||||||
# downgraded or upgraded by custom components
|
# downgraded or upgraded by custom components
|
||||||
# This ensures all use the same version
|
# This ensures all use the same version
|
||||||
pymodbus==3.9.2
|
pymodbus==3.9.2
|
||||||
|
|
||||||
|
# Some packages don't support gql 4.0.0 yet
|
||||||
|
gql<4.0.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
GENERATED_MESSAGE = (
|
GENERATED_MESSAGE = (
|
||||||
|
@@ -83,7 +83,6 @@ FORBIDDEN_PACKAGE_EXCEPTIONS: dict[str, dict[str, set[str]]] = {
|
|||||||
# - reasonX should be the name of the invalid dependency
|
# - reasonX should be the name of the invalid dependency
|
||||||
"adax": {"adax": {"async-timeout"}, "adax-local": {"async-timeout"}},
|
"adax": {"adax": {"async-timeout"}, "adax-local": {"async-timeout"}},
|
||||||
"airthings": {"airthings-cloud": {"async-timeout"}},
|
"airthings": {"airthings-cloud": {"async-timeout"}},
|
||||||
"alexa_devices": {"marisa-trie": {"setuptools"}},
|
|
||||||
"ampio": {"asmog": {"async-timeout"}},
|
"ampio": {"asmog": {"async-timeout"}},
|
||||||
"apache_kafka": {"aiokafka": {"async-timeout"}},
|
"apache_kafka": {"aiokafka": {"async-timeout"}},
|
||||||
"apple_tv": {"pyatv": {"async-timeout"}},
|
"apple_tv": {"pyatv": {"async-timeout"}},
|
||||||
|
@@ -202,6 +202,7 @@ EXCEPTIONS = {
|
|||||||
"pysabnzbd", # https://github.com/jeradM/pysabnzbd/pull/6
|
"pysabnzbd", # https://github.com/jeradM/pysabnzbd/pull/6
|
||||||
"sharp_aquos_rc", # https://github.com/jmoore987/sharp_aquos_rc/pull/14
|
"sharp_aquos_rc", # https://github.com/jmoore987/sharp_aquos_rc/pull/14
|
||||||
"tapsaff", # https://github.com/bazwilliams/python-taps-aff/pull/5
|
"tapsaff", # https://github.com/bazwilliams/python-taps-aff/pull/5
|
||||||
|
"ujson", # https://github.com/ultrajson/ultrajson/blob/main/LICENSE.txt
|
||||||
}
|
}
|
||||||
|
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@@ -8,9 +8,9 @@ from aioamazondevices.const import DEVICE_TYPE_TO_MODEL
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.alexa_devices.const import CONF_LOGIN_DATA, DOMAIN
|
from homeassistant.components.alexa_devices.const import CONF_LOGIN_DATA, DOMAIN
|
||||||
from homeassistant.const import CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
|
|
||||||
from .const import TEST_COUNTRY, TEST_PASSWORD, TEST_SERIAL_NUMBER, TEST_USERNAME
|
from .const import TEST_PASSWORD, TEST_SERIAL_NUMBER, TEST_USERNAME
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
@@ -80,7 +80,6 @@ def mock_config_entry() -> MockConfigEntry:
|
|||||||
domain=DOMAIN,
|
domain=DOMAIN,
|
||||||
title="Amazon Test Account",
|
title="Amazon Test Account",
|
||||||
data={
|
data={
|
||||||
CONF_COUNTRY: TEST_COUNTRY,
|
|
||||||
CONF_USERNAME: TEST_USERNAME,
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
CONF_PASSWORD: TEST_PASSWORD,
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
CONF_LOGIN_DATA: {"session": "test-session"},
|
CONF_LOGIN_DATA: {"session": "test-session"},
|
||||||
|
@@ -47,7 +47,6 @@
|
|||||||
}),
|
}),
|
||||||
'entry': dict({
|
'entry': dict({
|
||||||
'data': dict({
|
'data': dict({
|
||||||
'country': 'IT',
|
|
||||||
'login_data': dict({
|
'login_data': dict({
|
||||||
'session': 'test-session',
|
'session': 'test-session',
|
||||||
}),
|
}),
|
||||||
|
@@ -6,17 +6,16 @@ from aioamazondevices.exceptions import (
|
|||||||
CannotAuthenticate,
|
CannotAuthenticate,
|
||||||
CannotConnect,
|
CannotConnect,
|
||||||
CannotRetrieveData,
|
CannotRetrieveData,
|
||||||
WrongCountry,
|
|
||||||
)
|
)
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.alexa_devices.const import CONF_LOGIN_DATA, DOMAIN
|
from homeassistant.components.alexa_devices.const import CONF_LOGIN_DATA, DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_USER
|
from homeassistant.config_entries import SOURCE_USER
|
||||||
from homeassistant.const import CONF_CODE, CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_CODE, CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResultType
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
|
|
||||||
from .const import TEST_CODE, TEST_COUNTRY, TEST_PASSWORD, TEST_USERNAME
|
from .const import TEST_CODE, TEST_PASSWORD, TEST_USERNAME
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
@@ -37,7 +36,6 @@ async def test_full_flow(
|
|||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{
|
{
|
||||||
CONF_COUNTRY: TEST_COUNTRY,
|
|
||||||
CONF_USERNAME: TEST_USERNAME,
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
CONF_PASSWORD: TEST_PASSWORD,
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
CONF_CODE: TEST_CODE,
|
CONF_CODE: TEST_CODE,
|
||||||
@@ -46,7 +44,6 @@ async def test_full_flow(
|
|||||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
assert result["title"] == TEST_USERNAME
|
assert result["title"] == TEST_USERNAME
|
||||||
assert result["data"] == {
|
assert result["data"] == {
|
||||||
CONF_COUNTRY: TEST_COUNTRY,
|
|
||||||
CONF_USERNAME: TEST_USERNAME,
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
CONF_PASSWORD: TEST_PASSWORD,
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
CONF_LOGIN_DATA: {
|
CONF_LOGIN_DATA: {
|
||||||
@@ -63,7 +60,6 @@ async def test_full_flow(
|
|||||||
(CannotConnect, "cannot_connect"),
|
(CannotConnect, "cannot_connect"),
|
||||||
(CannotAuthenticate, "invalid_auth"),
|
(CannotAuthenticate, "invalid_auth"),
|
||||||
(CannotRetrieveData, "cannot_retrieve_data"),
|
(CannotRetrieveData, "cannot_retrieve_data"),
|
||||||
(WrongCountry, "wrong_country"),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_flow_errors(
|
async def test_flow_errors(
|
||||||
@@ -87,7 +83,6 @@ async def test_flow_errors(
|
|||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{
|
{
|
||||||
CONF_COUNTRY: TEST_COUNTRY,
|
|
||||||
CONF_USERNAME: TEST_USERNAME,
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
CONF_PASSWORD: TEST_PASSWORD,
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
CONF_CODE: TEST_CODE,
|
CONF_CODE: TEST_CODE,
|
||||||
@@ -102,7 +97,6 @@ async def test_flow_errors(
|
|||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{
|
{
|
||||||
CONF_COUNTRY: TEST_COUNTRY,
|
|
||||||
CONF_USERNAME: TEST_USERNAME,
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
CONF_PASSWORD: TEST_PASSWORD,
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
CONF_CODE: TEST_CODE,
|
CONF_CODE: TEST_CODE,
|
||||||
@@ -131,7 +125,6 @@ async def test_already_configured(
|
|||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{
|
{
|
||||||
CONF_COUNTRY: TEST_COUNTRY,
|
|
||||||
CONF_USERNAME: TEST_USERNAME,
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
CONF_PASSWORD: TEST_PASSWORD,
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
CONF_CODE: TEST_CODE,
|
CONF_CODE: TEST_CODE,
|
||||||
|
@@ -4,12 +4,14 @@ from unittest.mock import AsyncMock
|
|||||||
|
|
||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.alexa_devices.const import DOMAIN
|
from homeassistant.components.alexa_devices.const import CONF_LOGIN_DATA, DOMAIN
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
from . import setup_integration
|
from . import setup_integration
|
||||||
from .const import TEST_SERIAL_NUMBER
|
from .const import TEST_COUNTRY, TEST_PASSWORD, TEST_SERIAL_NUMBER, TEST_USERNAME
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
@@ -28,3 +30,32 @@ async def test_device_info(
|
|||||||
)
|
)
|
||||||
assert device_entry is not None
|
assert device_entry is not None
|
||||||
assert device_entry == snapshot
|
assert device_entry == snapshot
|
||||||
|
|
||||||
|
|
||||||
|
async def test_migrate_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_amazon_devices_client: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test successful migration of entry data."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="Amazon Test Account",
|
||||||
|
data={
|
||||||
|
CONF_COUNTRY: TEST_COUNTRY,
|
||||||
|
CONF_USERNAME: TEST_USERNAME,
|
||||||
|
CONF_PASSWORD: TEST_PASSWORD,
|
||||||
|
CONF_LOGIN_DATA: {"session": "test-session"},
|
||||||
|
},
|
||||||
|
unique_id=TEST_USERNAME,
|
||||||
|
version=1,
|
||||||
|
minor_version=0,
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||||
|
assert config_entry.state is ConfigEntryState.LOADED
|
||||||
|
assert config_entry.minor_version == 1
|
||||||
|
assert config_entry.data["site"] == f"https://www.amazon.{TEST_COUNTRY}"
|
||||||
|
@@ -15,7 +15,11 @@ from amberelectric.models.spike_status import SpikeStatus
|
|||||||
from dateutil import parser
|
from dateutil import parser
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.amberelectric.const import CONF_SITE_ID, CONF_SITE_NAME
|
from homeassistant.components.amberelectric.const import (
|
||||||
|
CONF_SITE_ID,
|
||||||
|
CONF_SITE_NAME,
|
||||||
|
REQUEST_TIMEOUT,
|
||||||
|
)
|
||||||
from homeassistant.components.amberelectric.coordinator import AmberUpdateCoordinator
|
from homeassistant.components.amberelectric.coordinator import AmberUpdateCoordinator
|
||||||
from homeassistant.const import CONF_API_TOKEN
|
from homeassistant.const import CONF_API_TOKEN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@@ -104,7 +108,9 @@ async def test_fetch_general_site(hass: HomeAssistant, current_price_api: Mock)
|
|||||||
result = await data_service._async_update_data()
|
result = await data_service._async_update_data()
|
||||||
|
|
||||||
current_price_api.get_current_prices.assert_called_with(
|
current_price_api.get_current_prices.assert_called_with(
|
||||||
GENERAL_ONLY_SITE_ID, next=288
|
GENERAL_ONLY_SITE_ID,
|
||||||
|
next=288,
|
||||||
|
_request_timeout=REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["current"].get("general") == GENERAL_CHANNEL[0].actual_instance
|
assert result["current"].get("general") == GENERAL_CHANNEL[0].actual_instance
|
||||||
@@ -136,7 +142,9 @@ async def test_fetch_no_general_site(
|
|||||||
await data_service._async_update_data()
|
await data_service._async_update_data()
|
||||||
|
|
||||||
current_price_api.get_current_prices.assert_called_with(
|
current_price_api.get_current_prices.assert_called_with(
|
||||||
GENERAL_ONLY_SITE_ID, next=288
|
GENERAL_ONLY_SITE_ID,
|
||||||
|
next=288,
|
||||||
|
_request_timeout=REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -150,7 +158,9 @@ async def test_fetch_api_error(hass: HomeAssistant, current_price_api: Mock) ->
|
|||||||
result = await data_service._async_update_data()
|
result = await data_service._async_update_data()
|
||||||
|
|
||||||
current_price_api.get_current_prices.assert_called_with(
|
current_price_api.get_current_prices.assert_called_with(
|
||||||
GENERAL_ONLY_SITE_ID, next=288
|
GENERAL_ONLY_SITE_ID,
|
||||||
|
next=288,
|
||||||
|
_request_timeout=REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["current"].get("general") == GENERAL_CHANNEL[0].actual_instance
|
assert result["current"].get("general") == GENERAL_CHANNEL[0].actual_instance
|
||||||
@@ -201,7 +211,9 @@ async def test_fetch_general_and_controlled_load_site(
|
|||||||
result = await data_service._async_update_data()
|
result = await data_service._async_update_data()
|
||||||
|
|
||||||
current_price_api.get_current_prices.assert_called_with(
|
current_price_api.get_current_prices.assert_called_with(
|
||||||
GENERAL_AND_CONTROLLED_SITE_ID, next=288
|
GENERAL_AND_CONTROLLED_SITE_ID,
|
||||||
|
next=288,
|
||||||
|
_request_timeout=REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["current"].get("general") == GENERAL_CHANNEL[0].actual_instance
|
assert result["current"].get("general") == GENERAL_CHANNEL[0].actual_instance
|
||||||
@@ -241,7 +253,9 @@ async def test_fetch_general_and_feed_in_site(
|
|||||||
result = await data_service._async_update_data()
|
result = await data_service._async_update_data()
|
||||||
|
|
||||||
current_price_api.get_current_prices.assert_called_with(
|
current_price_api.get_current_prices.assert_called_with(
|
||||||
GENERAL_AND_FEED_IN_SITE_ID, next=288
|
GENERAL_AND_FEED_IN_SITE_ID,
|
||||||
|
next=288,
|
||||||
|
_request_timeout=REQUEST_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result["current"].get("general") == GENERAL_CHANNEL[0].actual_instance
|
assert result["current"].get("general") == GENERAL_CHANNEL[0].actual_instance
|
||||||
|
@@ -192,7 +192,7 @@
|
|||||||
'suggested_display_precision': 0,
|
'suggested_display_precision': 0,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'original_device_class': <SensorDeviceClass.ENERGY_STORAGE: 'energy_storage'>,
|
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
'original_name': 'Battery stored',
|
'original_name': 'Battery stored',
|
||||||
'platform': 'imeon_inverter',
|
'platform': 'imeon_inverter',
|
||||||
@@ -201,16 +201,16 @@
|
|||||||
'supported_features': 0,
|
'supported_features': 0,
|
||||||
'translation_key': 'battery_stored',
|
'translation_key': 'battery_stored',
|
||||||
'unique_id': '111111111111111_battery_stored',
|
'unique_id': '111111111111111_battery_stored',
|
||||||
'unit_of_measurement': <UnitOfEnergy.WATT_HOUR: 'Wh'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_sensors[sensor.imeon_inverter_battery_stored-state]
|
# name: test_sensors[sensor.imeon_inverter_battery_stored-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'energy_storage',
|
'device_class': 'power',
|
||||||
'friendly_name': 'Imeon inverter Battery stored',
|
'friendly_name': 'Imeon inverter Battery stored',
|
||||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfEnergy.WATT_HOUR: 'Wh'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
'entity_id': 'sensor.imeon_inverter_battery_stored',
|
'entity_id': 'sensor.imeon_inverter_battery_stored',
|
||||||
@@ -1290,7 +1290,7 @@
|
|||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||||
}),
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
@@ -1328,7 +1328,7 @@
|
|||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'friendly_name': 'Imeon inverter Monitoring self-consumption',
|
'friendly_name': 'Imeon inverter Monitoring self-consumption',
|
||||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||||
'unit_of_measurement': '%',
|
'unit_of_measurement': '%',
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -1345,7 +1345,7 @@
|
|||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||||
}),
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
@@ -1383,7 +1383,7 @@
|
|||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'friendly_name': 'Imeon inverter Monitoring self-sufficiency',
|
'friendly_name': 'Imeon inverter Monitoring self-sufficiency',
|
||||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||||
'unit_of_measurement': '%',
|
'unit_of_measurement': '%',
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -2072,7 +2072,7 @@
|
|||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
}),
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
@@ -2094,7 +2094,7 @@
|
|||||||
'suggested_display_precision': 0,
|
'suggested_display_precision': 0,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
'original_name': 'PV consumed',
|
'original_name': 'PV consumed',
|
||||||
'platform': 'imeon_inverter',
|
'platform': 'imeon_inverter',
|
||||||
@@ -2103,16 +2103,16 @@
|
|||||||
'supported_features': 0,
|
'supported_features': 0,
|
||||||
'translation_key': 'pv_consumed',
|
'translation_key': 'pv_consumed',
|
||||||
'unique_id': '111111111111111_pv_consumed',
|
'unique_id': '111111111111111_pv_consumed',
|
||||||
'unit_of_measurement': <UnitOfEnergy.WATT_HOUR: 'Wh'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_sensors[sensor.imeon_inverter_pv_consumed-state]
|
# name: test_sensors[sensor.imeon_inverter_pv_consumed-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'energy',
|
'device_class': 'power',
|
||||||
'friendly_name': 'Imeon inverter PV consumed',
|
'friendly_name': 'Imeon inverter PV consumed',
|
||||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfEnergy.WATT_HOUR: 'Wh'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
'entity_id': 'sensor.imeon_inverter_pv_consumed',
|
'entity_id': 'sensor.imeon_inverter_pv_consumed',
|
||||||
@@ -2128,7 +2128,7 @@
|
|||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
}),
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
@@ -2150,7 +2150,7 @@
|
|||||||
'suggested_display_precision': 0,
|
'suggested_display_precision': 0,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
'original_device_class': <SensorDeviceClass.ENERGY: 'energy'>,
|
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||||
'original_icon': None,
|
'original_icon': None,
|
||||||
'original_name': 'PV injected',
|
'original_name': 'PV injected',
|
||||||
'platform': 'imeon_inverter',
|
'platform': 'imeon_inverter',
|
||||||
@@ -2159,16 +2159,16 @@
|
|||||||
'supported_features': 0,
|
'supported_features': 0,
|
||||||
'translation_key': 'pv_injected',
|
'translation_key': 'pv_injected',
|
||||||
'unique_id': '111111111111111_pv_injected',
|
'unique_id': '111111111111111_pv_injected',
|
||||||
'unit_of_measurement': <UnitOfEnergy.WATT_HOUR: 'Wh'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_sensors[sensor.imeon_inverter_pv_injected-state]
|
# name: test_sensors[sensor.imeon_inverter_pv_injected-state]
|
||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'device_class': 'energy',
|
'device_class': 'power',
|
||||||
'friendly_name': 'Imeon inverter PV injected',
|
'friendly_name': 'Imeon inverter PV injected',
|
||||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfEnergy.WATT_HOUR: 'Wh'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
'entity_id': 'sensor.imeon_inverter_pv_injected',
|
'entity_id': 'sensor.imeon_inverter_pv_injected',
|
||||||
|
@@ -133,3 +133,22 @@ async def test_valve(
|
|||||||
command=clusters.ValveConfigurationAndControl.Commands.Open(targetLevel=100),
|
command=clusters.ValveConfigurationAndControl.Commands.Open(targetLevel=100),
|
||||||
)
|
)
|
||||||
matter_client.send_device_command.reset_mock()
|
matter_client.send_device_command.reset_mock()
|
||||||
|
|
||||||
|
# test using set_position action to close valve
|
||||||
|
await hass.services.async_call(
|
||||||
|
"valve",
|
||||||
|
"set_valve_position",
|
||||||
|
{
|
||||||
|
"entity_id": entity_id,
|
||||||
|
"position": 0,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert matter_client.send_device_command.call_count == 1
|
||||||
|
assert matter_client.send_device_command.call_args == call(
|
||||||
|
node_id=matter_node.node_id,
|
||||||
|
endpoint_id=1,
|
||||||
|
command=clusters.ValveConfigurationAndControl.Commands.Close(),
|
||||||
|
)
|
||||||
|
matter_client.send_device_command.reset_mock()
|
||||||
|
@@ -10,6 +10,7 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.nanoleaf.const import DOMAIN
|
from homeassistant.components.nanoleaf.const import DOMAIN
|
||||||
|
from homeassistant.config_entries import SOURCE_USER
|
||||||
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
from homeassistant.const import CONF_HOST, CONF_TOKEN
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResultType
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
@@ -463,3 +464,59 @@ async def test_ssdp_discovery(hass: HomeAssistant) -> None:
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_abort_discovery_flow_with_user_flow(hass: HomeAssistant) -> None:
|
||||||
|
"""Test abort discovery flow if user flow is already in progress."""
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.load_json_object",
|
||||||
|
return_value={},
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.nanoleaf.config_flow.Nanoleaf",
|
||||||
|
return_value=_mock_nanoleaf(TEST_HOST, TEST_TOKEN),
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.nanoleaf.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_SSDP},
|
||||||
|
data=SsdpServiceInfo(
|
||||||
|
ssdp_usn="mock_usn",
|
||||||
|
ssdp_st="mock_st",
|
||||||
|
upnp={},
|
||||||
|
ssdp_headers={
|
||||||
|
"_host": TEST_HOST,
|
||||||
|
"nl-devicename": TEST_NAME,
|
||||||
|
"nl-deviceid": TEST_DEVICE_ID,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["errors"] is None
|
||||||
|
assert result["step_id"] == "link"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_USER},
|
||||||
|
)
|
||||||
|
assert len(hass.config_entries.flow.async_progress(DOMAIN)) == 2
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_HOST: TEST_HOST}
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "link"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
|
||||||
|
# Verify the discovery flow was aborted
|
||||||
|
assert not hass.config_entries.flow.async_progress(DOMAIN)
|
||||||
|
@@ -63,6 +63,8 @@ async def test_format_structured_output() -> None:
|
|||||||
"item_value",
|
"item_value",
|
||||||
],
|
],
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
"additionalProperties": False,
|
||||||
|
"strict": True,
|
||||||
},
|
},
|
||||||
"type": "array",
|
"type": "array",
|
||||||
},
|
},
|
||||||
|
@@ -9,7 +9,7 @@ from volvocarsapi.auth import TOKEN_URL
|
|||||||
from volvocarsapi.models import (
|
from volvocarsapi.models import (
|
||||||
VolvoCarsAvailableCommand,
|
VolvoCarsAvailableCommand,
|
||||||
VolvoCarsLocation,
|
VolvoCarsLocation,
|
||||||
VolvoCarsValueField,
|
VolvoCarsValueStatusField,
|
||||||
VolvoCarsVehicle,
|
VolvoCarsVehicle,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -98,7 +98,7 @@ async def mock_api(hass: HomeAssistant, full_model: str) -> AsyncGenerator[Async
|
|||||||
hass, "energy_state", full_model
|
hass, "energy_state", full_model
|
||||||
)
|
)
|
||||||
energy_state = {
|
energy_state = {
|
||||||
key: VolvoCarsValueField.from_dict(value)
|
key: VolvoCarsValueStatusField.from_dict(value)
|
||||||
for key, value in energy_state_data.items()
|
for key, value in energy_state_data.items()
|
||||||
}
|
}
|
||||||
engine_status = await async_load_fixture_as_value_field(
|
engine_status = await async_load_fixture_as_value_field(
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
"chargerConnectionStatus": {
|
"chargerConnectionStatus": {
|
||||||
"isSupported": true
|
"isSupported": true
|
||||||
},
|
},
|
||||||
"chargingSystemStatus": {
|
"chargingStatus": {
|
||||||
"isSupported": true
|
"isSupported": true
|
||||||
},
|
},
|
||||||
"chargingType": {
|
"chargingType": {
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
"isSupported": true
|
"isSupported": true
|
||||||
},
|
},
|
||||||
"chargingCurrentLimit": {
|
"chargingCurrentLimit": {
|
||||||
"isSupported": true
|
"isSupported": false
|
||||||
},
|
},
|
||||||
"chargingPower": {
|
"chargingPower": {
|
||||||
"isSupported": true
|
"isSupported": true
|
||||||
|
@@ -50,7 +50,7 @@
|
|||||||
},
|
},
|
||||||
"chargingPower": {
|
"chargingPower": {
|
||||||
"status": "ERROR",
|
"status": "ERROR",
|
||||||
"code": "NOT_SUPPORTED",
|
"code": "PROPERTY_NOT_FOUND",
|
||||||
"message": "Resource is not supported for this vehicle"
|
"message": "No valid value could be found for the requested property"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
"chargerConnectionStatus": {
|
"chargerConnectionStatus": {
|
||||||
"isSupported": true
|
"isSupported": true
|
||||||
},
|
},
|
||||||
"chargingSystemStatus": {
|
"chargingStatus": {
|
||||||
"isSupported": true
|
"isSupported": true
|
||||||
},
|
},
|
||||||
"chargingType": {
|
"chargingType": {
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
"chargerConnectionStatus": {
|
"chargerConnectionStatus": {
|
||||||
"isSupported": true
|
"isSupported": true
|
||||||
},
|
},
|
||||||
"chargingSystemStatus": {
|
"chargingStatus": {
|
||||||
"isSupported": true
|
"isSupported": true
|
||||||
},
|
},
|
||||||
"chargingType": {
|
"chargingType": {
|
||||||
|
@@ -40,9 +40,10 @@
|
|||||||
"message": "Resource is not supported for this vehicle"
|
"message": "Resource is not supported for this vehicle"
|
||||||
},
|
},
|
||||||
"targetBatteryChargeLevel": {
|
"targetBatteryChargeLevel": {
|
||||||
"status": "ERROR",
|
"status": "OK",
|
||||||
"code": "NOT_SUPPORTED",
|
"value": 80,
|
||||||
"message": "Resource is not supported for this vehicle"
|
"unit": "percentage",
|
||||||
|
"updatedAt": "2024-09-22T09:40:12Z"
|
||||||
},
|
},
|
||||||
"chargingPower": {
|
"chargingPower": {
|
||||||
"status": "ERROR",
|
"status": "ERROR",
|
||||||
|
@@ -232,6 +232,62 @@
|
|||||||
'state': 'connected',
|
'state': 'connected',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_sensor[ex30_2024][sensor.volvo_ex30_charging_power-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'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.volvo_ex30_charging_power',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.POWER: 'power'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Charging power',
|
||||||
|
'platform': 'volvo',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'charging_power',
|
||||||
|
'unique_id': 'yv1abcdefg1234567_charging_power',
|
||||||
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[ex30_2024][sensor.volvo_ex30_charging_power-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'power',
|
||||||
|
'friendly_name': 'Volvo EX30 Charging power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.volvo_ex30_charging_power',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_sensor[ex30_2024][sensor.volvo_ex30_charging_power_status-entry]
|
# name: test_sensor[ex30_2024][sensor.volvo_ex30_charging_power_status-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
@@ -2164,7 +2220,7 @@
|
|||||||
'last_changed': <ANY>,
|
'last_changed': <ANY>,
|
||||||
'last_reported': <ANY>,
|
'last_reported': <ANY>,
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': '0',
|
'state': '1386',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_sensor[xc40_electric_2024][sensor.volvo_xc40_charging_power_status-entry]
|
# name: test_sensor[xc40_electric_2024][sensor.volvo_xc40_charging_power_status-entry]
|
||||||
@@ -3601,6 +3657,58 @@
|
|||||||
'state': '30000',
|
'state': '30000',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_sensor[xc60_phev_2020][sensor.volvo_xc60_target_battery_charge_level-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': None,
|
||||||
|
'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.volvo_xc60_target_battery_charge_level',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Target battery charge level',
|
||||||
|
'platform': 'volvo',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'target_battery_charge_level',
|
||||||
|
'unique_id': 'yv1abcdefg1234567_target_battery_charge_level',
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensor[xc60_phev_2020][sensor.volvo_xc60_target_battery_charge_level-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Volvo XC60 Target battery charge level',
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.volvo_xc60_target_battery_charge_level',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '80',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_sensor[xc60_phev_2020][sensor.volvo_xc60_time_to_engine_service-entry]
|
# name: test_sensor[xc60_phev_2020][sensor.volvo_xc60_time_to_engine_service-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
@@ -68,4 +68,20 @@ async def test_skip_invalid_api_fields(
|
|||||||
with patch("homeassistant.components.volvo.PLATFORMS", [Platform.SENSOR]):
|
with patch("homeassistant.components.volvo.PLATFORMS", [Platform.SENSOR]):
|
||||||
assert await setup_integration()
|
assert await setup_integration()
|
||||||
|
|
||||||
assert not hass.states.get(f"sensor.volvo_{short_model}_charging_power")
|
assert not hass.states.get(f"sensor.volvo_{short_model}_charging_current_limit")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"full_model",
|
||||||
|
["ex30_2024"],
|
||||||
|
)
|
||||||
|
async def test_charging_power_value(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
setup_integration: Callable[[], Awaitable[bool]],
|
||||||
|
) -> None:
|
||||||
|
"""Test if charging_power_value is zero if supported, but not charging."""
|
||||||
|
|
||||||
|
with patch("homeassistant.components.volvo.PLATFORMS", [Platform.SENSOR]):
|
||||||
|
assert await setup_integration()
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.volvo_ex30_charging_power").state == "0"
|
||||||
|
@@ -1,6 +1,18 @@
|
|||||||
# serializer version: 1
|
# serializer version: 1
|
||||||
# name: test_diagnostics_cloudhook_instance
|
# name: test_diagnostics_cloudhook_instance
|
||||||
dict({
|
dict({
|
||||||
|
'devices': list([
|
||||||
|
dict({
|
||||||
|
'battery': 'high',
|
||||||
|
'device_id': '**REDACTED**',
|
||||||
|
'device_type': 'Scale',
|
||||||
|
'first_session_date': None,
|
||||||
|
'hashed_device_id': '**REDACTED**',
|
||||||
|
'last_session_date': '2023-09-04T22:39:39+00:00',
|
||||||
|
'model': 5,
|
||||||
|
'raw_model': 'Body+',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
'has_cloudhooks': True,
|
'has_cloudhooks': True,
|
||||||
'has_valid_external_webhook_url': True,
|
'has_valid_external_webhook_url': True,
|
||||||
'received_activity_data': False,
|
'received_activity_data': False,
|
||||||
@@ -64,6 +76,18 @@
|
|||||||
# ---
|
# ---
|
||||||
# name: test_diagnostics_polling_instance
|
# name: test_diagnostics_polling_instance
|
||||||
dict({
|
dict({
|
||||||
|
'devices': list([
|
||||||
|
dict({
|
||||||
|
'battery': 'high',
|
||||||
|
'device_id': '**REDACTED**',
|
||||||
|
'device_type': 'Scale',
|
||||||
|
'first_session_date': None,
|
||||||
|
'hashed_device_id': '**REDACTED**',
|
||||||
|
'last_session_date': '2023-09-04T22:39:39+00:00',
|
||||||
|
'model': 5,
|
||||||
|
'raw_model': 'Body+',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
'has_cloudhooks': False,
|
'has_cloudhooks': False,
|
||||||
'has_valid_external_webhook_url': False,
|
'has_valid_external_webhook_url': False,
|
||||||
'received_activity_data': False,
|
'received_activity_data': False,
|
||||||
@@ -127,6 +151,18 @@
|
|||||||
# ---
|
# ---
|
||||||
# name: test_diagnostics_webhook_instance
|
# name: test_diagnostics_webhook_instance
|
||||||
dict({
|
dict({
|
||||||
|
'devices': list([
|
||||||
|
dict({
|
||||||
|
'battery': 'high',
|
||||||
|
'device_id': '**REDACTED**',
|
||||||
|
'device_type': 'Scale',
|
||||||
|
'first_session_date': None,
|
||||||
|
'hashed_device_id': '**REDACTED**',
|
||||||
|
'last_session_date': '2023-09-04T22:39:39+00:00',
|
||||||
|
'model': 5,
|
||||||
|
'raw_model': 'Body+',
|
||||||
|
}),
|
||||||
|
]),
|
||||||
'has_cloudhooks': False,
|
'has_cloudhooks': False,
|
||||||
'has_valid_external_webhook_url': True,
|
'has_valid_external_webhook_url': True,
|
||||||
'received_activity_data': False,
|
'received_activity_data': False,
|
||||||
|
@@ -198,6 +198,17 @@ def mock_sdk_version(client: MagicMock) -> Generator[None]:
|
|||||||
client.driver.controller.data["sdkVersion"] = original_sdk_version
|
client.driver.controller.data["sdkVersion"] = original_sdk_version
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="set_country", autouse=True)
|
||||||
|
def set_country_fixture(hass: HomeAssistant) -> Generator[None]:
|
||||||
|
"""Set the country for the test."""
|
||||||
|
original_country = hass.config.country
|
||||||
|
# Set a default country to avoid asking the user to select it.
|
||||||
|
hass.config.country = "US"
|
||||||
|
yield
|
||||||
|
# Reset the country after the test.
|
||||||
|
hass.config.country = original_country
|
||||||
|
|
||||||
|
|
||||||
async def test_manual(hass: HomeAssistant) -> None:
|
async def test_manual(hass: HomeAssistant) -> None:
|
||||||
"""Test we create an entry with manual step."""
|
"""Test we create an entry with manual step."""
|
||||||
|
|
||||||
@@ -4601,3 +4612,324 @@ async def test_recommended_usb_discovery(
|
|||||||
}
|
}
|
||||||
assert len(mock_setup.mock_calls) == 1
|
assert len(mock_setup.mock_calls) == 1
|
||||||
assert len(mock_setup_entry.mock_calls) == 1
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("supervisor", "addon_installed", "addon_info", "unload_entry")
|
||||||
|
async def test_addon_rf_region_new_network(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
setup_entry: AsyncMock,
|
||||||
|
set_addon_options: AsyncMock,
|
||||||
|
start_addon: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test RF region selection for new network when country is None."""
|
||||||
|
device = "/test"
|
||||||
|
hass.config.country = None
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.MENU
|
||||||
|
assert result["step_id"] == "installation_type"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {"next_step_id": "intent_recommended"}
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
"usb_path": device,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "rf_region"
|
||||||
|
|
||||||
|
# Check that all expected RF regions are available
|
||||||
|
|
||||||
|
data_schema = result["data_schema"]
|
||||||
|
assert data_schema is not None
|
||||||
|
schema = data_schema.schema
|
||||||
|
rf_region_field = schema["rf_region"]
|
||||||
|
selector_options = rf_region_field.config["options"]
|
||||||
|
|
||||||
|
expected_regions = [
|
||||||
|
"Australia/New Zealand",
|
||||||
|
"China",
|
||||||
|
"Europe",
|
||||||
|
"Hong Kong",
|
||||||
|
"India",
|
||||||
|
"Israel",
|
||||||
|
"Japan",
|
||||||
|
"Korea",
|
||||||
|
"Russia",
|
||||||
|
"USA",
|
||||||
|
]
|
||||||
|
|
||||||
|
assert selector_options == expected_regions
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {"rf_region": "Europe"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||||
|
assert result["step_id"] == "start_addon"
|
||||||
|
|
||||||
|
# Verify RF region was set in addon config
|
||||||
|
assert set_addon_options.call_count == 1
|
||||||
|
assert set_addon_options.call_args == call(
|
||||||
|
"core_zwave_js",
|
||||||
|
AddonsOptions(
|
||||||
|
config={
|
||||||
|
"device": device,
|
||||||
|
"s0_legacy_key": "",
|
||||||
|
"s2_access_control_key": "",
|
||||||
|
"s2_authenticated_key": "",
|
||||||
|
"s2_unauthenticated_key": "",
|
||||||
|
"lr_s2_access_control_key": "",
|
||||||
|
"lr_s2_authenticated_key": "",
|
||||||
|
"rf_region": "Europe",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
assert start_addon.call_count == 1
|
||||||
|
assert start_addon.call_args == call("core_zwave_js")
|
||||||
|
assert setup_entry.call_count == 1
|
||||||
|
|
||||||
|
# avoid unload entry in teardown
|
||||||
|
entry = result["result"]
|
||||||
|
await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("supervisor", "addon_running")
|
||||||
|
async def test_addon_rf_region_migrate_network(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
client: MagicMock,
|
||||||
|
integration: MockConfigEntry,
|
||||||
|
restart_addon: AsyncMock,
|
||||||
|
addon_options: dict[str, Any],
|
||||||
|
set_addon_options: AsyncMock,
|
||||||
|
get_server_version: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test migration flow with add-on."""
|
||||||
|
hass.config.country = None
|
||||||
|
version_info = get_server_version.return_value
|
||||||
|
entry = integration
|
||||||
|
assert client.connect.call_count == 1
|
||||||
|
assert client.driver.controller.home_id == 3245146787
|
||||||
|
assert entry.unique_id == "3245146787"
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
entry,
|
||||||
|
data={
|
||||||
|
"url": "ws://localhost:3000",
|
||||||
|
"use_addon": True,
|
||||||
|
"usb_path": "/dev/ttyUSB0",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
addon_options["device"] = "/dev/ttyUSB0"
|
||||||
|
|
||||||
|
async def mock_backup_nvm_raw():
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
client.driver.controller.emit(
|
||||||
|
"nvm backup progress", {"bytesRead": 100, "total": 200}
|
||||||
|
)
|
||||||
|
return b"test_nvm_data"
|
||||||
|
|
||||||
|
client.driver.controller.async_backup_nvm_raw = AsyncMock(
|
||||||
|
side_effect=mock_backup_nvm_raw
|
||||||
|
)
|
||||||
|
|
||||||
|
async def mock_restore_nvm(data: bytes, options: dict[str, bool] | None = None):
|
||||||
|
client.driver.controller.emit(
|
||||||
|
"nvm convert progress",
|
||||||
|
{"event": "nvm convert progress", "bytesRead": 100, "total": 200},
|
||||||
|
)
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
client.driver.controller.emit(
|
||||||
|
"nvm restore progress",
|
||||||
|
{"event": "nvm restore progress", "bytesWritten": 100, "total": 200},
|
||||||
|
)
|
||||||
|
client.driver.controller.data["homeId"] = 3245146787
|
||||||
|
client.driver.emit(
|
||||||
|
"driver ready", {"event": "driver ready", "source": "driver"}
|
||||||
|
)
|
||||||
|
|
||||||
|
client.driver.controller.async_restore_nvm = AsyncMock(side_effect=mock_restore_nvm)
|
||||||
|
|
||||||
|
events = async_capture_events(
|
||||||
|
hass, data_entry_flow.EVENT_DATA_ENTRY_FLOW_PROGRESS_UPDATE
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await entry.start_reconfigure_flow(hass)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.MENU
|
||||||
|
assert result["step_id"] == "reconfigure"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {"next_step_id": "intent_migrate"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||||
|
assert result["step_id"] == "backup_nvm"
|
||||||
|
|
||||||
|
with patch("pathlib.Path.write_bytes") as mock_file:
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert client.driver.controller.async_backup_nvm_raw.call_count == 1
|
||||||
|
assert mock_file.call_count == 1
|
||||||
|
assert len(events) == 1
|
||||||
|
assert events[0].data["progress"] == 0.5
|
||||||
|
events.clear()
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "instruct_unplug"
|
||||||
|
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "choose_serial_port"
|
||||||
|
data_schema = result["data_schema"]
|
||||||
|
assert data_schema is not None
|
||||||
|
assert data_schema.schema[CONF_USB_PATH]
|
||||||
|
# Ensure the old usb path is not in the list of options
|
||||||
|
with pytest.raises(InInvalid):
|
||||||
|
data_schema.schema[CONF_USB_PATH](addon_options["device"])
|
||||||
|
|
||||||
|
version_info.home_id = 5678
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={
|
||||||
|
CONF_USB_PATH: "/test",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "rf_region"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {"rf_region": "Europe"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||||
|
assert result["step_id"] == "start_addon"
|
||||||
|
assert set_addon_options.call_args == call(
|
||||||
|
"core_zwave_js",
|
||||||
|
AddonsOptions(
|
||||||
|
config={
|
||||||
|
"device": "/test",
|
||||||
|
"rf_region": "Europe",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert restart_addon.call_args == call("core_zwave_js")
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
|
|
||||||
|
assert entry.unique_id == "5678"
|
||||||
|
version_info.home_id = 3245146787
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||||
|
assert result["step_id"] == "restore_nvm"
|
||||||
|
assert client.connect.call_count == 2
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert client.connect.call_count == 4
|
||||||
|
assert entry.state is config_entries.ConfigEntryState.LOADED
|
||||||
|
assert client.driver.controller.async_restore_nvm.call_count == 1
|
||||||
|
assert len(events) == 2
|
||||||
|
assert events[0].data["progress"] == 0.25
|
||||||
|
assert events[1].data["progress"] == 0.75
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "migration_successful"
|
||||||
|
assert entry.data["url"] == "ws://host1:3001"
|
||||||
|
assert entry.data["usb_path"] == "/test"
|
||||||
|
assert entry.data["use_addon"] is True
|
||||||
|
assert entry.unique_id == "3245146787"
|
||||||
|
assert client.driver.controller.home_id == 3245146787
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("supervisor", "addon_installed", "unload_entry")
|
||||||
|
@pytest.mark.parametrize(("country", "rf_region"), [("US", "Automatic"), (None, "USA")])
|
||||||
|
async def test_addon_skip_rf_region(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
setup_entry: AsyncMock,
|
||||||
|
addon_options: dict[str, Any],
|
||||||
|
set_addon_options: AsyncMock,
|
||||||
|
start_addon: AsyncMock,
|
||||||
|
country: str | None,
|
||||||
|
rf_region: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test RF region selection is skipped if not needed."""
|
||||||
|
device = "/test"
|
||||||
|
addon_options["rf_region"] = rf_region
|
||||||
|
hass.config.country = country
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.MENU
|
||||||
|
assert result["step_id"] == "installation_type"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {"next_step_id": "intent_recommended"}
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
"usb_path": device,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.SHOW_PROGRESS
|
||||||
|
assert result["step_id"] == "start_addon"
|
||||||
|
|
||||||
|
# Verify RF region was set in addon config
|
||||||
|
assert set_addon_options.call_count == 1
|
||||||
|
assert set_addon_options.call_args == call(
|
||||||
|
"core_zwave_js",
|
||||||
|
AddonsOptions(
|
||||||
|
config={
|
||||||
|
"device": device,
|
||||||
|
"s0_legacy_key": "",
|
||||||
|
"s2_access_control_key": "",
|
||||||
|
"s2_authenticated_key": "",
|
||||||
|
"s2_unauthenticated_key": "",
|
||||||
|
"lr_s2_access_control_key": "",
|
||||||
|
"lr_s2_authenticated_key": "",
|
||||||
|
"rf_region": rf_region,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
assert start_addon.call_count == 1
|
||||||
|
assert start_addon.call_args == call("core_zwave_js")
|
||||||
|
assert setup_entry.call_count == 1
|
||||||
|
|
||||||
|
# avoid unload entry in teardown
|
||||||
|
entry = result["result"]
|
||||||
|
await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
assert entry.state is config_entries.ConfigEntryState.NOT_LOADED
|
||||||
|
@@ -1045,6 +1045,183 @@ async def test_last_seen_statistics_sensors(
|
|||||||
assert state.state == "2024-01-01T12:00:00+00:00"
|
assert state.state == "2024-01-01T12:00:00+00:00"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_rssi_sensor_error(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
zp3111: Node,
|
||||||
|
integration: MockConfigEntry,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Test rssi sensor error."""
|
||||||
|
entity_id = "sensor.4_in_1_sensor_signal_strength"
|
||||||
|
|
||||||
|
entity_registry.async_update_entity(entity_id, disabled_by=None)
|
||||||
|
|
||||||
|
# reload integration and check if entity is correctly there
|
||||||
|
await hass.config_entries.async_reload(integration.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
assert state.state == "unknown"
|
||||||
|
|
||||||
|
# Fire statistics updated event for node
|
||||||
|
event = Event(
|
||||||
|
"statistics updated",
|
||||||
|
{
|
||||||
|
"source": "node",
|
||||||
|
"event": "statistics updated",
|
||||||
|
"nodeId": zp3111.node_id,
|
||||||
|
"statistics": {
|
||||||
|
"commandsTX": 1,
|
||||||
|
"commandsRX": 2,
|
||||||
|
"commandsDroppedTX": 3,
|
||||||
|
"commandsDroppedRX": 4,
|
||||||
|
"timeoutResponse": 5,
|
||||||
|
"rtt": 6,
|
||||||
|
"rssi": 7, # baseline
|
||||||
|
"lwr": {
|
||||||
|
"protocolDataRate": 1,
|
||||||
|
"rssi": 1,
|
||||||
|
"repeaters": [],
|
||||||
|
"repeaterRSSI": [],
|
||||||
|
"routeFailedBetween": [],
|
||||||
|
},
|
||||||
|
"nlwr": {
|
||||||
|
"protocolDataRate": 2,
|
||||||
|
"rssi": 2,
|
||||||
|
"repeaters": [],
|
||||||
|
"repeaterRSSI": [],
|
||||||
|
"routeFailedBetween": [],
|
||||||
|
},
|
||||||
|
"lastSeen": "2024-01-01T00:00:00+0000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
zp3111.receive_event(event)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
assert state.state == "7"
|
||||||
|
|
||||||
|
event = Event(
|
||||||
|
"statistics updated",
|
||||||
|
{
|
||||||
|
"source": "node",
|
||||||
|
"event": "statistics updated",
|
||||||
|
"nodeId": zp3111.node_id,
|
||||||
|
"statistics": {
|
||||||
|
"commandsTX": 1,
|
||||||
|
"commandsRX": 2,
|
||||||
|
"commandsDroppedTX": 3,
|
||||||
|
"commandsDroppedRX": 4,
|
||||||
|
"timeoutResponse": 5,
|
||||||
|
"rtt": 6,
|
||||||
|
"rssi": 125, # no signal detected
|
||||||
|
"lwr": {
|
||||||
|
"protocolDataRate": 1,
|
||||||
|
"rssi": 1,
|
||||||
|
"repeaters": [],
|
||||||
|
"repeaterRSSI": [],
|
||||||
|
"routeFailedBetween": [],
|
||||||
|
},
|
||||||
|
"nlwr": {
|
||||||
|
"protocolDataRate": 2,
|
||||||
|
"rssi": 2,
|
||||||
|
"repeaters": [],
|
||||||
|
"repeaterRSSI": [],
|
||||||
|
"routeFailedBetween": [],
|
||||||
|
},
|
||||||
|
"lastSeen": "2024-01-01T00:00:00+0000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
zp3111.receive_event(event)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
assert state.state == "unknown"
|
||||||
|
|
||||||
|
event = Event(
|
||||||
|
"statistics updated",
|
||||||
|
{
|
||||||
|
"source": "node",
|
||||||
|
"event": "statistics updated",
|
||||||
|
"nodeId": zp3111.node_id,
|
||||||
|
"statistics": {
|
||||||
|
"commandsTX": 1,
|
||||||
|
"commandsRX": 2,
|
||||||
|
"commandsDroppedTX": 3,
|
||||||
|
"commandsDroppedRX": 4,
|
||||||
|
"timeoutResponse": 5,
|
||||||
|
"rtt": 6,
|
||||||
|
"rssi": 127, # not available
|
||||||
|
"lwr": {
|
||||||
|
"protocolDataRate": 1,
|
||||||
|
"rssi": 1,
|
||||||
|
"repeaters": [],
|
||||||
|
"repeaterRSSI": [],
|
||||||
|
"routeFailedBetween": [],
|
||||||
|
},
|
||||||
|
"nlwr": {
|
||||||
|
"protocolDataRate": 2,
|
||||||
|
"rssi": 2,
|
||||||
|
"repeaters": [],
|
||||||
|
"repeaterRSSI": [],
|
||||||
|
"routeFailedBetween": [],
|
||||||
|
},
|
||||||
|
"lastSeen": "2024-01-01T00:00:00+0000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
zp3111.receive_event(event)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
assert state.state == "unavailable"
|
||||||
|
|
||||||
|
event = Event(
|
||||||
|
"statistics updated",
|
||||||
|
{
|
||||||
|
"source": "node",
|
||||||
|
"event": "statistics updated",
|
||||||
|
"nodeId": zp3111.node_id,
|
||||||
|
"statistics": {
|
||||||
|
"commandsTX": 1,
|
||||||
|
"commandsRX": 2,
|
||||||
|
"commandsDroppedTX": 3,
|
||||||
|
"commandsDroppedRX": 4,
|
||||||
|
"timeoutResponse": 5,
|
||||||
|
"rtt": 6,
|
||||||
|
"rssi": 126, # receiver saturated
|
||||||
|
"lwr": {
|
||||||
|
"protocolDataRate": 1,
|
||||||
|
"rssi": 1,
|
||||||
|
"repeaters": [],
|
||||||
|
"repeaterRSSI": [],
|
||||||
|
"routeFailedBetween": [],
|
||||||
|
},
|
||||||
|
"nlwr": {
|
||||||
|
"protocolDataRate": 2,
|
||||||
|
"rssi": 2,
|
||||||
|
"repeaters": [],
|
||||||
|
"repeaterRSSI": [],
|
||||||
|
"routeFailedBetween": [],
|
||||||
|
},
|
||||||
|
"lastSeen": "2024-01-01T00:00:00+0000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
zp3111.receive_event(event)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
assert state.state == "unknown"
|
||||||
|
|
||||||
|
|
||||||
ENERGY_PRODUCTION_ENTITY_MAP = {
|
ENERGY_PRODUCTION_ENTITY_MAP = {
|
||||||
"energy_production_power": {
|
"energy_production_power": {
|
||||||
"state": 1.23,
|
"state": 1.23,
|
||||||
|
Reference in New Issue
Block a user