mirror of
https://github.com/home-assistant/core.git
synced 2025-09-03 03:41:40 +02:00
2025.8.1 (#150412)
This commit is contained in:
@@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/airos",
|
"documentation": "https://www.home-assistant.io/integrations/airos",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["airos==0.2.4"]
|
"requirements": ["airos==0.2.7"]
|
||||||
}
|
}
|
||||||
|
@@ -25,7 +25,7 @@
|
|||||||
"services": {
|
"services": {
|
||||||
"press": {
|
"press": {
|
||||||
"name": "Press",
|
"name": "Press",
|
||||||
"description": "Press the button entity."
|
"description": "Presses a button entity."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,6 @@
|
|||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["acme", "hass_nabucasa", "snitun"],
|
"loggers": ["acme", "hass_nabucasa", "snitun"],
|
||||||
"requirements": ["hass-nabucasa==0.111.1"],
|
"requirements": ["hass-nabucasa==0.111.2"],
|
||||||
"single_config_entry": true
|
"single_config_entry": true
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
"""Data update coordinator for the Enigma2 integration."""
|
"""Data update coordinator for the Enigma2 integration."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from openwebif.api import OpenWebIfDevice, OpenWebIfStatus
|
from openwebif.api import OpenWebIfDevice, OpenWebIfStatus
|
||||||
@@ -30,6 +31,8 @@ from .const import CONF_SOURCE_BOUQUET, DOMAIN
|
|||||||
|
|
||||||
LOGGER = logging.getLogger(__package__)
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
|
SETUP_TIMEOUT = 10
|
||||||
|
|
||||||
type Enigma2ConfigEntry = ConfigEntry[Enigma2UpdateCoordinator]
|
type Enigma2ConfigEntry = ConfigEntry[Enigma2UpdateCoordinator]
|
||||||
|
|
||||||
|
|
||||||
@@ -79,7 +82,7 @@ class Enigma2UpdateCoordinator(DataUpdateCoordinator[OpenWebIfStatus]):
|
|||||||
async def _async_setup(self) -> None:
|
async def _async_setup(self) -> None:
|
||||||
"""Provide needed data to the device info."""
|
"""Provide needed data to the device info."""
|
||||||
|
|
||||||
about = await self.device.get_about()
|
about = await asyncio.wait_for(self.device.get_about(), timeout=SETUP_TIMEOUT)
|
||||||
self.device.mac_address = about["info"]["ifaces"][0]["mac"]
|
self.device.mac_address = about["info"]["ifaces"][0]["mac"]
|
||||||
self.device_info["model"] = about["info"]["model"]
|
self.device_info["model"] = about["info"]["model"]
|
||||||
self.device_info["manufacturer"] = about["info"]["brand"]
|
self.device_info["manufacturer"] = about["info"]["brand"]
|
||||||
|
@@ -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==20250806.0"]
|
"requirements": ["home-assistant-frontend==20250811.0"]
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,7 @@ from aioautomower.exceptions import (
|
|||||||
ApiError,
|
ApiError,
|
||||||
AuthError,
|
AuthError,
|
||||||
HusqvarnaTimeoutError,
|
HusqvarnaTimeoutError,
|
||||||
|
HusqvarnaWSClientError,
|
||||||
HusqvarnaWSServerHandshakeError,
|
HusqvarnaWSServerHandshakeError,
|
||||||
)
|
)
|
||||||
from aioautomower.model import MowerDictionary
|
from aioautomower.model import MowerDictionary
|
||||||
@@ -142,7 +143,7 @@ class AutomowerDataUpdateCoordinator(DataUpdateCoordinator[MowerDictionary]):
|
|||||||
# Reset reconnect time after successful connection
|
# Reset reconnect time after successful connection
|
||||||
self.reconnect_time = DEFAULT_RECONNECT_TIME
|
self.reconnect_time = DEFAULT_RECONNECT_TIME
|
||||||
await automower_client.start_listening()
|
await automower_client.start_listening()
|
||||||
except HusqvarnaWSServerHandshakeError as err:
|
except (HusqvarnaWSServerHandshakeError, HusqvarnaWSClientError) as err:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Failed to connect to websocket. Trying to reconnect: %s",
|
"Failed to connect to websocket. Trying to reconnect: %s",
|
||||||
err,
|
err,
|
||||||
|
@@ -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.2"]
|
"requirements": ["imgw_pib==1.5.3"]
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
"requirements": [
|
"requirements": [
|
||||||
"xknx==3.8.0",
|
"xknx==3.8.0",
|
||||||
"xknxproject==3.8.2",
|
"xknxproject==3.8.2",
|
||||||
"knx-frontend==2025.8.6.52906"
|
"knx-frontend==2025.8.9.63154"
|
||||||
],
|
],
|
||||||
"single_config_entry": true
|
"single_config_entry": true
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
"description": "Do you want to configure the Launch Library?"
|
"description": "Do you want to configure Launch Library?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -49,6 +49,7 @@ from .const import (
|
|||||||
CONF_RECOMMENDED,
|
CONF_RECOMMENDED,
|
||||||
CONF_TEMPERATURE,
|
CONF_TEMPERATURE,
|
||||||
CONF_TOP_P,
|
CONF_TOP_P,
|
||||||
|
CONF_VERBOSITY,
|
||||||
CONF_WEB_SEARCH,
|
CONF_WEB_SEARCH,
|
||||||
CONF_WEB_SEARCH_CITY,
|
CONF_WEB_SEARCH_CITY,
|
||||||
CONF_WEB_SEARCH_CONTEXT_SIZE,
|
CONF_WEB_SEARCH_CONTEXT_SIZE,
|
||||||
@@ -67,6 +68,7 @@ from .const import (
|
|||||||
RECOMMENDED_REASONING_EFFORT,
|
RECOMMENDED_REASONING_EFFORT,
|
||||||
RECOMMENDED_TEMPERATURE,
|
RECOMMENDED_TEMPERATURE,
|
||||||
RECOMMENDED_TOP_P,
|
RECOMMENDED_TOP_P,
|
||||||
|
RECOMMENDED_VERBOSITY,
|
||||||
RECOMMENDED_WEB_SEARCH,
|
RECOMMENDED_WEB_SEARCH,
|
||||||
RECOMMENDED_WEB_SEARCH_CONTEXT_SIZE,
|
RECOMMENDED_WEB_SEARCH_CONTEXT_SIZE,
|
||||||
RECOMMENDED_WEB_SEARCH_USER_LOCATION,
|
RECOMMENDED_WEB_SEARCH_USER_LOCATION,
|
||||||
@@ -323,7 +325,7 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
|||||||
|
|
||||||
model = options[CONF_CHAT_MODEL]
|
model = options[CONF_CHAT_MODEL]
|
||||||
|
|
||||||
if model.startswith("o"):
|
if model.startswith(("o", "gpt-5")):
|
||||||
step_schema.update(
|
step_schema.update(
|
||||||
{
|
{
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
@@ -331,7 +333,9 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
|||||||
default=RECOMMENDED_REASONING_EFFORT,
|
default=RECOMMENDED_REASONING_EFFORT,
|
||||||
): SelectSelector(
|
): SelectSelector(
|
||||||
SelectSelectorConfig(
|
SelectSelectorConfig(
|
||||||
options=["low", "medium", "high"],
|
options=["low", "medium", "high"]
|
||||||
|
if model.startswith("o")
|
||||||
|
else ["minimal", "low", "medium", "high"],
|
||||||
translation_key=CONF_REASONING_EFFORT,
|
translation_key=CONF_REASONING_EFFORT,
|
||||||
mode=SelectSelectorMode.DROPDOWN,
|
mode=SelectSelectorMode.DROPDOWN,
|
||||||
)
|
)
|
||||||
@@ -341,6 +345,24 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
|||||||
elif CONF_REASONING_EFFORT in options:
|
elif CONF_REASONING_EFFORT in options:
|
||||||
options.pop(CONF_REASONING_EFFORT)
|
options.pop(CONF_REASONING_EFFORT)
|
||||||
|
|
||||||
|
if model.startswith("gpt-5"):
|
||||||
|
step_schema.update(
|
||||||
|
{
|
||||||
|
vol.Optional(
|
||||||
|
CONF_VERBOSITY,
|
||||||
|
default=RECOMMENDED_VERBOSITY,
|
||||||
|
): SelectSelector(
|
||||||
|
SelectSelectorConfig(
|
||||||
|
options=["low", "medium", "high"],
|
||||||
|
translation_key=CONF_VERBOSITY,
|
||||||
|
mode=SelectSelectorMode.DROPDOWN,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
elif CONF_VERBOSITY in options:
|
||||||
|
options.pop(CONF_VERBOSITY)
|
||||||
|
|
||||||
if self._subentry_type == "conversation" and not model.startswith(
|
if self._subentry_type == "conversation" and not model.startswith(
|
||||||
tuple(UNSUPPORTED_WEB_SEARCH_MODELS)
|
tuple(UNSUPPORTED_WEB_SEARCH_MODELS)
|
||||||
):
|
):
|
||||||
|
@@ -21,6 +21,7 @@ CONF_REASONING_EFFORT = "reasoning_effort"
|
|||||||
CONF_RECOMMENDED = "recommended"
|
CONF_RECOMMENDED = "recommended"
|
||||||
CONF_TEMPERATURE = "temperature"
|
CONF_TEMPERATURE = "temperature"
|
||||||
CONF_TOP_P = "top_p"
|
CONF_TOP_P = "top_p"
|
||||||
|
CONF_VERBOSITY = "verbosity"
|
||||||
CONF_WEB_SEARCH = "web_search"
|
CONF_WEB_SEARCH = "web_search"
|
||||||
CONF_WEB_SEARCH_USER_LOCATION = "user_location"
|
CONF_WEB_SEARCH_USER_LOCATION = "user_location"
|
||||||
CONF_WEB_SEARCH_CONTEXT_SIZE = "search_context_size"
|
CONF_WEB_SEARCH_CONTEXT_SIZE = "search_context_size"
|
||||||
@@ -34,6 +35,7 @@ RECOMMENDED_MAX_TOKENS = 3000
|
|||||||
RECOMMENDED_REASONING_EFFORT = "low"
|
RECOMMENDED_REASONING_EFFORT = "low"
|
||||||
RECOMMENDED_TEMPERATURE = 1.0
|
RECOMMENDED_TEMPERATURE = 1.0
|
||||||
RECOMMENDED_TOP_P = 1.0
|
RECOMMENDED_TOP_P = 1.0
|
||||||
|
RECOMMENDED_VERBOSITY = "medium"
|
||||||
RECOMMENDED_WEB_SEARCH = False
|
RECOMMENDED_WEB_SEARCH = False
|
||||||
RECOMMENDED_WEB_SEARCH_CONTEXT_SIZE = "medium"
|
RECOMMENDED_WEB_SEARCH_CONTEXT_SIZE = "medium"
|
||||||
RECOMMENDED_WEB_SEARCH_USER_LOCATION = False
|
RECOMMENDED_WEB_SEARCH_USER_LOCATION = False
|
||||||
|
@@ -61,6 +61,7 @@ from .const import (
|
|||||||
CONF_REASONING_EFFORT,
|
CONF_REASONING_EFFORT,
|
||||||
CONF_TEMPERATURE,
|
CONF_TEMPERATURE,
|
||||||
CONF_TOP_P,
|
CONF_TOP_P,
|
||||||
|
CONF_VERBOSITY,
|
||||||
CONF_WEB_SEARCH,
|
CONF_WEB_SEARCH,
|
||||||
CONF_WEB_SEARCH_CITY,
|
CONF_WEB_SEARCH_CITY,
|
||||||
CONF_WEB_SEARCH_CONTEXT_SIZE,
|
CONF_WEB_SEARCH_CONTEXT_SIZE,
|
||||||
@@ -75,6 +76,7 @@ from .const import (
|
|||||||
RECOMMENDED_REASONING_EFFORT,
|
RECOMMENDED_REASONING_EFFORT,
|
||||||
RECOMMENDED_TEMPERATURE,
|
RECOMMENDED_TEMPERATURE,
|
||||||
RECOMMENDED_TOP_P,
|
RECOMMENDED_TOP_P,
|
||||||
|
RECOMMENDED_VERBOSITY,
|
||||||
RECOMMENDED_WEB_SEARCH_CONTEXT_SIZE,
|
RECOMMENDED_WEB_SEARCH_CONTEXT_SIZE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -346,14 +348,18 @@ class OpenAIBaseLLMEntity(Entity):
|
|||||||
if tools:
|
if tools:
|
||||||
model_args["tools"] = tools
|
model_args["tools"] = tools
|
||||||
|
|
||||||
if model_args["model"].startswith("o"):
|
if model_args["model"].startswith(("o", "gpt-5")):
|
||||||
model_args["reasoning"] = {
|
model_args["reasoning"] = {
|
||||||
"effort": options.get(
|
"effort": options.get(
|
||||||
CONF_REASONING_EFFORT, RECOMMENDED_REASONING_EFFORT
|
CONF_REASONING_EFFORT, RECOMMENDED_REASONING_EFFORT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else:
|
model_args["include"] = ["reasoning.encrypted_content"]
|
||||||
model_args["store"] = False
|
|
||||||
|
if model_args["model"].startswith("gpt-5"):
|
||||||
|
model_args["text"] = {
|
||||||
|
"verbosity": options.get(CONF_VERBOSITY, RECOMMENDED_VERBOSITY)
|
||||||
|
}
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
m
|
m
|
||||||
|
@@ -121,6 +121,7 @@
|
|||||||
"selector": {
|
"selector": {
|
||||||
"reasoning_effort": {
|
"reasoning_effort": {
|
||||||
"options": {
|
"options": {
|
||||||
|
"minimal": "Minimal",
|
||||||
"low": "[%key:common::state::low%]",
|
"low": "[%key:common::state::low%]",
|
||||||
"medium": "[%key:common::state::medium%]",
|
"medium": "[%key:common::state::medium%]",
|
||||||
"high": "[%key:common::state::high%]"
|
"high": "[%key:common::state::high%]"
|
||||||
@@ -132,6 +133,13 @@
|
|||||||
"medium": "[%key:common::state::medium%]",
|
"medium": "[%key:common::state::medium%]",
|
||||||
"high": "[%key:common::state::high%]"
|
"high": "[%key:common::state::high%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"verbosity": {
|
||||||
|
"options": {
|
||||||
|
"low": "[%key:common::state::low%]",
|
||||||
|
"medium": "[%key:common::state::medium%]",
|
||||||
|
"high": "[%key:common::state::high%]"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
@@ -30,9 +30,9 @@ async def validate_input(hass: HomeAssistant, data):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
"title": is_valid["title"],
|
"title": is_valid["title"],
|
||||||
"relay_count": is_valid["relay_count"],
|
"relay_count": is_valid["relays"],
|
||||||
"input_count": is_valid["input_count"],
|
"input_count": is_valid["inputs"],
|
||||||
"is_old": is_valid["is_old"],
|
"is_old": is_valid["temps"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -186,6 +186,9 @@ MODELS_TV_ONLY = (
|
|||||||
"ULTRA",
|
"ULTRA",
|
||||||
)
|
)
|
||||||
MODELS_LINEIN_AND_TV = ("AMP",)
|
MODELS_LINEIN_AND_TV = ("AMP",)
|
||||||
|
MODEL_SONOS_ARC_ULTRA = "SONOS ARC ULTRA"
|
||||||
|
|
||||||
|
ATTR_SPEECH_ENHANCEMENT_ENABLED = "speech_enhance_enabled"
|
||||||
|
|
||||||
AVAILABILITY_CHECK_INTERVAL = datetime.timedelta(minutes=1)
|
AVAILABILITY_CHECK_INTERVAL = datetime.timedelta(minutes=1)
|
||||||
AVAILABILITY_TIMEOUT = AVAILABILITY_CHECK_INTERVAL.total_seconds() * 4.5
|
AVAILABILITY_TIMEOUT = AVAILABILITY_CHECK_INTERVAL.total_seconds() * 4.5
|
||||||
|
@@ -35,6 +35,7 @@ from homeassistant.util import dt as dt_util
|
|||||||
|
|
||||||
from .alarms import SonosAlarms
|
from .alarms import SonosAlarms
|
||||||
from .const import (
|
from .const import (
|
||||||
|
ATTR_SPEECH_ENHANCEMENT_ENABLED,
|
||||||
AVAILABILITY_TIMEOUT,
|
AVAILABILITY_TIMEOUT,
|
||||||
BATTERY_SCAN_INTERVAL,
|
BATTERY_SCAN_INTERVAL,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@@ -157,6 +158,7 @@ class SonosSpeaker:
|
|||||||
# Home theater
|
# Home theater
|
||||||
self.audio_delay: int | None = None
|
self.audio_delay: int | None = None
|
||||||
self.dialog_level: bool | None = None
|
self.dialog_level: bool | None = None
|
||||||
|
self.speech_enhance_enabled: bool | None = None
|
||||||
self.night_mode: bool | None = None
|
self.night_mode: bool | None = None
|
||||||
self.sub_enabled: bool | None = None
|
self.sub_enabled: bool | None = None
|
||||||
self.sub_crossover: int | None = None
|
self.sub_crossover: int | None = None
|
||||||
@@ -548,6 +550,11 @@ class SonosSpeaker:
|
|||||||
@callback
|
@callback
|
||||||
def async_update_volume(self, event: SonosEvent) -> None:
|
def async_update_volume(self, event: SonosEvent) -> None:
|
||||||
"""Update information about currently volume settings."""
|
"""Update information about currently volume settings."""
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Updating volume for %s with event variables: %s",
|
||||||
|
self.zone_name,
|
||||||
|
event.variables,
|
||||||
|
)
|
||||||
self.event_stats.process(event)
|
self.event_stats.process(event)
|
||||||
variables = event.variables
|
variables = event.variables
|
||||||
|
|
||||||
@@ -565,6 +572,7 @@ class SonosSpeaker:
|
|||||||
|
|
||||||
for bool_var in (
|
for bool_var in (
|
||||||
"dialog_level",
|
"dialog_level",
|
||||||
|
ATTR_SPEECH_ENHANCEMENT_ENABLED,
|
||||||
"night_mode",
|
"night_mode",
|
||||||
"sub_enabled",
|
"sub_enabled",
|
||||||
"surround_enabled",
|
"surround_enabled",
|
||||||
|
@@ -19,7 +19,9 @@ from homeassistant.helpers.event import async_track_time_change
|
|||||||
|
|
||||||
from .alarms import SonosAlarms
|
from .alarms import SonosAlarms
|
||||||
from .const import (
|
from .const import (
|
||||||
|
ATTR_SPEECH_ENHANCEMENT_ENABLED,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
MODEL_SONOS_ARC_ULTRA,
|
||||||
SONOS_ALARMS_UPDATED,
|
SONOS_ALARMS_UPDATED,
|
||||||
SONOS_CREATE_ALARM,
|
SONOS_CREATE_ALARM,
|
||||||
SONOS_CREATE_SWITCHES,
|
SONOS_CREATE_SWITCHES,
|
||||||
@@ -59,6 +61,7 @@ ALL_FEATURES = (
|
|||||||
ATTR_SURROUND_ENABLED,
|
ATTR_SURROUND_ENABLED,
|
||||||
ATTR_STATUS_LIGHT,
|
ATTR_STATUS_LIGHT,
|
||||||
)
|
)
|
||||||
|
ALL_SUBST_FEATURES = (ATTR_SPEECH_ENHANCEMENT_ENABLED,)
|
||||||
|
|
||||||
COORDINATOR_FEATURES = ATTR_CROSSFADE
|
COORDINATOR_FEATURES = ATTR_CROSSFADE
|
||||||
|
|
||||||
@@ -69,6 +72,14 @@ POLL_REQUIRED = (
|
|||||||
|
|
||||||
WEEKEND_DAYS = (0, 6)
|
WEEKEND_DAYS = (0, 6)
|
||||||
|
|
||||||
|
# Mapping of model names to feature attributes that need to be substituted.
|
||||||
|
# This is used to handle differences in attributes across Sonos models.
|
||||||
|
MODEL_FEATURE_SUBSTITUTIONS: dict[str, dict[str, str]] = {
|
||||||
|
MODEL_SONOS_ARC_ULTRA: {
|
||||||
|
ATTR_SPEECH_ENHANCEMENT: ATTR_SPEECH_ENHANCEMENT_ENABLED,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@@ -92,6 +103,13 @@ async def async_setup_entry(
|
|||||||
|
|
||||||
def available_soco_attributes(speaker: SonosSpeaker) -> list[str]:
|
def available_soco_attributes(speaker: SonosSpeaker) -> list[str]:
|
||||||
features = []
|
features = []
|
||||||
|
for feature_type in ALL_SUBST_FEATURES:
|
||||||
|
try:
|
||||||
|
if (state := getattr(speaker.soco, feature_type, None)) is not None:
|
||||||
|
setattr(speaker, feature_type, state)
|
||||||
|
except SoCoSlaveException:
|
||||||
|
pass
|
||||||
|
|
||||||
for feature_type in ALL_FEATURES:
|
for feature_type in ALL_FEATURES:
|
||||||
try:
|
try:
|
||||||
if (state := getattr(speaker.soco, feature_type, None)) is not None:
|
if (state := getattr(speaker.soco, feature_type, None)) is not None:
|
||||||
@@ -107,12 +125,23 @@ async def async_setup_entry(
|
|||||||
available_soco_attributes, speaker
|
available_soco_attributes, speaker
|
||||||
)
|
)
|
||||||
for feature_type in available_features:
|
for feature_type in available_features:
|
||||||
|
attribute_key = MODEL_FEATURE_SUBSTITUTIONS.get(
|
||||||
|
speaker.model_name.upper(), {}
|
||||||
|
).get(feature_type, feature_type)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Creating %s switch on %s",
|
"Creating %s switch on %s attribute %s",
|
||||||
feature_type,
|
feature_type,
|
||||||
speaker.zone_name,
|
speaker.zone_name,
|
||||||
|
attribute_key,
|
||||||
|
)
|
||||||
|
entities.append(
|
||||||
|
SonosSwitchEntity(
|
||||||
|
feature_type=feature_type,
|
||||||
|
attribute_key=attribute_key,
|
||||||
|
speaker=speaker,
|
||||||
|
config_entry=config_entry,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
entities.append(SonosSwitchEntity(feature_type, speaker, config_entry))
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
config_entry.async_on_unload(
|
config_entry.async_on_unload(
|
||||||
@@ -127,11 +156,15 @@ class SonosSwitchEntity(SonosPollingEntity, SwitchEntity):
|
|||||||
"""Representation of a Sonos feature switch."""
|
"""Representation of a Sonos feature switch."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, feature_type: str, speaker: SonosSpeaker, config_entry: SonosConfigEntry
|
self,
|
||||||
|
feature_type: str,
|
||||||
|
attribute_key: str,
|
||||||
|
speaker: SonosSpeaker,
|
||||||
|
config_entry: SonosConfigEntry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the switch."""
|
"""Initialize the switch."""
|
||||||
super().__init__(speaker, config_entry)
|
super().__init__(speaker, config_entry)
|
||||||
self.feature_type = feature_type
|
self.attribute_key = attribute_key
|
||||||
self.needs_coordinator = feature_type in COORDINATOR_FEATURES
|
self.needs_coordinator = feature_type in COORDINATOR_FEATURES
|
||||||
self._attr_entity_category = EntityCategory.CONFIG
|
self._attr_entity_category = EntityCategory.CONFIG
|
||||||
self._attr_translation_key = feature_type
|
self._attr_translation_key = feature_type
|
||||||
@@ -149,15 +182,15 @@ class SonosSwitchEntity(SonosPollingEntity, SwitchEntity):
|
|||||||
@soco_error()
|
@soco_error()
|
||||||
def poll_state(self) -> None:
|
def poll_state(self) -> None:
|
||||||
"""Poll the current state of the switch."""
|
"""Poll the current state of the switch."""
|
||||||
state = getattr(self.soco, self.feature_type)
|
state = getattr(self.soco, self.attribute_key)
|
||||||
setattr(self.speaker, self.feature_type, state)
|
setattr(self.speaker, self.attribute_key, state)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return True if entity is on."""
|
"""Return True if entity is on."""
|
||||||
if self.needs_coordinator and not self.speaker.is_coordinator:
|
if self.needs_coordinator and not self.speaker.is_coordinator:
|
||||||
return cast(bool, getattr(self.speaker.coordinator, self.feature_type))
|
return cast(bool, getattr(self.speaker.coordinator, self.attribute_key))
|
||||||
return cast(bool, getattr(self.speaker, self.feature_type))
|
return cast(bool, getattr(self.speaker, self.attribute_key))
|
||||||
|
|
||||||
def turn_on(self, **kwargs: Any) -> None:
|
def turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the entity on."""
|
"""Turn the entity on."""
|
||||||
@@ -175,7 +208,7 @@ class SonosSwitchEntity(SonosPollingEntity, SwitchEntity):
|
|||||||
else:
|
else:
|
||||||
soco = self.soco
|
soco = self.soco
|
||||||
try:
|
try:
|
||||||
setattr(soco, self.feature_type, enable)
|
setattr(soco, self.attribute_key, enable)
|
||||||
except SoCoUPnPException as exc:
|
except SoCoUPnPException as exc:
|
||||||
_LOGGER.warning("Could not toggle %s: %s", self.entity_id, exc)
|
_LOGGER.warning("Could not toggle %s: %s", self.entity_id, exc)
|
||||||
|
|
||||||
|
@@ -157,26 +157,28 @@ class BrowseData:
|
|||||||
|
|
||||||
cmd = ["apps", 0, browse_limit]
|
cmd = ["apps", 0, browse_limit]
|
||||||
result = await player.async_query(*cmd)
|
result = await player.async_query(*cmd)
|
||||||
for app in result["appss_loop"]:
|
if result["appss_loop"]:
|
||||||
app_cmd = "app-" + app["cmd"]
|
for app in result["appss_loop"]:
|
||||||
if app_cmd not in self.known_apps_radios:
|
app_cmd = "app-" + app["cmd"]
|
||||||
self.add_new_command(app_cmd, "item_id")
|
if app_cmd not in self.known_apps_radios:
|
||||||
_LOGGER.debug(
|
self.add_new_command(app_cmd, "item_id")
|
||||||
"Adding new command %s to browse data for player %s",
|
_LOGGER.debug(
|
||||||
app_cmd,
|
"Adding new command %s to browse data for player %s",
|
||||||
player.player_id,
|
app_cmd,
|
||||||
)
|
player.player_id,
|
||||||
|
)
|
||||||
cmd = ["radios", 0, browse_limit]
|
cmd = ["radios", 0, browse_limit]
|
||||||
result = await player.async_query(*cmd)
|
result = await player.async_query(*cmd)
|
||||||
for app in result["radioss_loop"]:
|
if result["radioss_loop"]:
|
||||||
app_cmd = "app-" + app["cmd"]
|
for app in result["radioss_loop"]:
|
||||||
if app_cmd not in self.known_apps_radios:
|
app_cmd = "app-" + app["cmd"]
|
||||||
self.add_new_command(app_cmd, "item_id")
|
if app_cmd not in self.known_apps_radios:
|
||||||
_LOGGER.debug(
|
self.add_new_command(app_cmd, "item_id")
|
||||||
"Adding new command %s to browse data for player %s",
|
_LOGGER.debug(
|
||||||
app_cmd,
|
"Adding new command %s to browse data for player %s",
|
||||||
player.player_id,
|
app_cmd,
|
||||||
)
|
player.player_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _build_response_apps_radios_category(
|
def _build_response_apps_radios_category(
|
||||||
|
@@ -325,7 +325,7 @@ class SqueezeBoxMediaPlayerEntity(SqueezeboxEntity, MediaPlayerEntity):
|
|||||||
@property
|
@property
|
||||||
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:
|
if self._player.volume is not None:
|
||||||
return int(float(self._player.volume)) / 100.0
|
return int(float(self._player.volume)) / 100.0
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
@@ -299,7 +299,10 @@ async def async_setup_entry(
|
|||||||
)
|
)
|
||||||
await home.rt_subscribe(
|
await home.rt_subscribe(
|
||||||
TibberRtDataCoordinator(
|
TibberRtDataCoordinator(
|
||||||
entity_creator.add_sensors, home, hass
|
hass,
|
||||||
|
entry,
|
||||||
|
entity_creator.add_sensors,
|
||||||
|
home,
|
||||||
).async_set_updated_data
|
).async_set_updated_data
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -613,15 +616,17 @@ class TibberRtDataCoordinator(DataUpdateCoordinator): # pylint: disable=hass-en
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
add_sensor_callback: Callable[[TibberRtDataCoordinator, Any], None],
|
add_sensor_callback: Callable[[TibberRtDataCoordinator, Any], None],
|
||||||
tibber_home: tibber.TibberHome,
|
tibber_home: tibber.TibberHome,
|
||||||
hass: HomeAssistant,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the data handler."""
|
"""Initialize the data handler."""
|
||||||
self._add_sensor_callback = add_sensor_callback
|
self._add_sensor_callback = add_sensor_callback
|
||||||
super().__init__(
|
super().__init__(
|
||||||
hass,
|
hass,
|
||||||
_LOGGER,
|
_LOGGER,
|
||||||
|
config_entry=config_entry,
|
||||||
name=tibber_home.info["viewer"]["home"]["address"].get(
|
name=tibber_home.info["viewer"]["home"]["address"].get(
|
||||||
"address1", "Tibber"
|
"address1", "Tibber"
|
||||||
),
|
),
|
||||||
|
@@ -99,8 +99,22 @@ class EnumTypeData:
|
|||||||
return cls(dpcode, **parsed)
|
return cls(dpcode, **parsed)
|
||||||
|
|
||||||
|
|
||||||
|
class ComplexTypeData:
|
||||||
|
"""Complex Type Data (for JSON/RAW parsing)."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_json(cls, data: str) -> Self:
|
||||||
|
"""Load JSON string and return a ComplexTypeData object."""
|
||||||
|
raise NotImplementedError("from_json is not implemented for this type")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_raw(cls, data: str) -> Self | None:
|
||||||
|
"""Decode base64 string and return a ComplexTypeData object."""
|
||||||
|
raise NotImplementedError("from_raw is not implemented for this type")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ElectricityTypeData:
|
class ElectricityTypeData(ComplexTypeData):
|
||||||
"""Electricity Type Data."""
|
"""Electricity Type Data."""
|
||||||
|
|
||||||
electriccurrent: str | None = None
|
electriccurrent: str | None = None
|
||||||
@@ -113,9 +127,11 @@ class ElectricityTypeData:
|
|||||||
return cls(**json.loads(data.lower()))
|
return cls(**json.loads(data.lower()))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_raw(cls, data: str) -> Self:
|
def from_raw(cls, data: str) -> Self | None:
|
||||||
"""Decode base64 string and return a ElectricityTypeData object."""
|
"""Decode base64 string and return a ElectricityTypeData object."""
|
||||||
raw = base64.b64decode(data)
|
raw = base64.b64decode(data)
|
||||||
|
if len(raw) == 0:
|
||||||
|
return None
|
||||||
voltage = struct.unpack(">H", raw[0:2])[0] / 10.0
|
voltage = struct.unpack(">H", raw[0:2])[0] / 10.0
|
||||||
electriccurrent = struct.unpack(">L", b"\x00" + raw[2:5])[0] / 1000.0
|
electriccurrent = struct.unpack(">L", b"\x00" + raw[2:5])[0] / 1000.0
|
||||||
power = struct.unpack(">L", b"\x00" + raw[5:8])[0] / 1000.0
|
power = struct.unpack(">L", b"\x00" + raw[5:8])[0] / 1000.0
|
||||||
|
@@ -40,13 +40,14 @@ from .const import (
|
|||||||
UnitOfMeasurement,
|
UnitOfMeasurement,
|
||||||
)
|
)
|
||||||
from .entity import TuyaEntity
|
from .entity import TuyaEntity
|
||||||
from .models import ElectricityTypeData, EnumTypeData, IntegerTypeData
|
from .models import ComplexTypeData, ElectricityTypeData, EnumTypeData, IntegerTypeData
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class TuyaSensorEntityDescription(SensorEntityDescription):
|
class TuyaSensorEntityDescription(SensorEntityDescription):
|
||||||
"""Describes Tuya sensor entity."""
|
"""Describes Tuya sensor entity."""
|
||||||
|
|
||||||
|
complex_type: type[ComplexTypeData] | None = None
|
||||||
subkey: str | None = None
|
subkey: str | None = None
|
||||||
|
|
||||||
|
|
||||||
@@ -368,6 +369,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.CURRENT,
|
device_class=SensorDeviceClass.CURRENT,
|
||||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="electriccurrent",
|
subkey="electriccurrent",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -376,6 +378,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="power",
|
subkey="power",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -384,6 +387,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.VOLTAGE,
|
device_class=SensorDeviceClass.VOLTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="voltage",
|
subkey="voltage",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -392,6 +396,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.CURRENT,
|
device_class=SensorDeviceClass.CURRENT,
|
||||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="electriccurrent",
|
subkey="electriccurrent",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -400,6 +405,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="power",
|
subkey="power",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -408,6 +414,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.VOLTAGE,
|
device_class=SensorDeviceClass.VOLTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="voltage",
|
subkey="voltage",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -416,6 +423,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.CURRENT,
|
device_class=SensorDeviceClass.CURRENT,
|
||||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="electriccurrent",
|
subkey="electriccurrent",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -424,6 +432,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="power",
|
subkey="power",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -432,6 +441,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.VOLTAGE,
|
device_class=SensorDeviceClass.VOLTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="voltage",
|
subkey="voltage",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -1254,6 +1264,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
translation_key="total_power",
|
translation_key="total_power",
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="power",
|
subkey="power",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -1262,6 +1273,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.CURRENT,
|
device_class=SensorDeviceClass.CURRENT,
|
||||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="electriccurrent",
|
subkey="electriccurrent",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -1270,6 +1282,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="power",
|
subkey="power",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -1278,6 +1291,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.VOLTAGE,
|
device_class=SensorDeviceClass.VOLTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="voltage",
|
subkey="voltage",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -1286,6 +1300,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.CURRENT,
|
device_class=SensorDeviceClass.CURRENT,
|
||||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="electriccurrent",
|
subkey="electriccurrent",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -1294,6 +1309,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="power",
|
subkey="power",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -1302,6 +1318,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.VOLTAGE,
|
device_class=SensorDeviceClass.VOLTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="voltage",
|
subkey="voltage",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -1310,6 +1327,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.CURRENT,
|
device_class=SensorDeviceClass.CURRENT,
|
||||||
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="electriccurrent",
|
subkey="electriccurrent",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -1318,6 +1336,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="power",
|
subkey="power",
|
||||||
),
|
),
|
||||||
TuyaSensorEntityDescription(
|
TuyaSensorEntityDescription(
|
||||||
@@ -1326,6 +1345,7 @@ SENSORS: dict[str, tuple[TuyaSensorEntityDescription, ...]] = {
|
|||||||
device_class=SensorDeviceClass.VOLTAGE,
|
device_class=SensorDeviceClass.VOLTAGE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
|
||||||
|
complex_type=ElectricityTypeData,
|
||||||
subkey="voltage",
|
subkey="voltage",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1424,7 +1444,7 @@ class TuyaSensorEntity(TuyaEntity, SensorEntity):
|
|||||||
|
|
||||||
_status_range: DeviceStatusRange | None = None
|
_status_range: DeviceStatusRange | None = None
|
||||||
_type: DPType | None = None
|
_type: DPType | None = None
|
||||||
_type_data: IntegerTypeData | EnumTypeData | None = None
|
_type_data: IntegerTypeData | EnumTypeData | ComplexTypeData | None = None
|
||||||
_uom: UnitOfMeasurement | None = None
|
_uom: UnitOfMeasurement | None = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -1476,6 +1496,7 @@ class TuyaSensorEntity(TuyaEntity, SensorEntity):
|
|||||||
self.unique_id,
|
self.unique_id,
|
||||||
)
|
)
|
||||||
self._attr_device_class = None
|
self._attr_device_class = None
|
||||||
|
self._attr_suggested_unit_of_measurement = None
|
||||||
return
|
return
|
||||||
|
|
||||||
uoms = DEVICE_CLASS_UNITS[self.device_class]
|
uoms = DEVICE_CLASS_UNITS[self.device_class]
|
||||||
@@ -1486,6 +1507,7 @@ class TuyaSensorEntity(TuyaEntity, SensorEntity):
|
|||||||
# Unknown unit of measurement, device class should not be used.
|
# Unknown unit of measurement, device class should not be used.
|
||||||
if uom is None:
|
if uom is None:
|
||||||
self._attr_device_class = None
|
self._attr_device_class = None
|
||||||
|
self._attr_suggested_unit_of_measurement = None
|
||||||
return
|
return
|
||||||
|
|
||||||
# Found unit of measurement, use the standardized Unit
|
# Found unit of measurement, use the standardized Unit
|
||||||
@@ -1523,16 +1545,23 @@ class TuyaSensorEntity(TuyaEntity, SensorEntity):
|
|||||||
|
|
||||||
# Get subkey value from Json string.
|
# Get subkey value from Json string.
|
||||||
if self._type is DPType.JSON:
|
if self._type is DPType.JSON:
|
||||||
if self.entity_description.subkey is None:
|
if (
|
||||||
|
self.entity_description.complex_type is None
|
||||||
|
or self.entity_description.subkey is None
|
||||||
|
):
|
||||||
return None
|
return None
|
||||||
values = ElectricityTypeData.from_json(value)
|
values = self.entity_description.complex_type.from_json(value)
|
||||||
return getattr(values, self.entity_description.subkey)
|
return getattr(values, self.entity_description.subkey)
|
||||||
|
|
||||||
if self._type is DPType.RAW:
|
if self._type is DPType.RAW:
|
||||||
if self.entity_description.subkey is None:
|
if (
|
||||||
|
self.entity_description.complex_type is None
|
||||||
|
or self.entity_description.subkey is None
|
||||||
|
or (raw_values := self.entity_description.complex_type.from_raw(value))
|
||||||
|
is None
|
||||||
|
):
|
||||||
return None
|
return None
|
||||||
values = ElectricityTypeData.from_raw(value)
|
return getattr(raw_values, self.entity_description.subkey)
|
||||||
return getattr(values, self.entity_description.subkey)
|
|
||||||
|
|
||||||
# Valid string or enum value
|
# Valid string or enum value
|
||||||
return value
|
return value
|
||||||
|
@@ -8,7 +8,7 @@ import logging
|
|||||||
from aiohttp.client_exceptions import ServerDisconnectedError
|
from aiohttp.client_exceptions import ServerDisconnectedError
|
||||||
from uiprotect.api import DEVICE_UPDATE_INTERVAL
|
from uiprotect.api import DEVICE_UPDATE_INTERVAL
|
||||||
from uiprotect.data import Bootstrap
|
from uiprotect.data import Bootstrap
|
||||||
from uiprotect.exceptions import ClientError, NotAuthorized
|
from uiprotect.exceptions import BadRequest, ClientError, NotAuthorized
|
||||||
|
|
||||||
# Import the test_util.anonymize module from the uiprotect package
|
# Import the test_util.anonymize module from the uiprotect package
|
||||||
# in __init__ to ensure it gets imported in the executor since the
|
# in __init__ to ensure it gets imported in the executor since the
|
||||||
@@ -100,7 +100,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: UFPConfigEntry) -> bool:
|
|||||||
new_api_key = await protect.create_api_key(
|
new_api_key = await protect.create_api_key(
|
||||||
name=f"Home Assistant ({hass.config.location_name})"
|
name=f"Home Assistant ({hass.config.location_name})"
|
||||||
)
|
)
|
||||||
except NotAuthorized as err:
|
except (NotAuthorized, BadRequest) as err:
|
||||||
_LOGGER.error("Failed to create API key: %s", err)
|
_LOGGER.error("Failed to create API key: %s", err)
|
||||||
else:
|
else:
|
||||||
protect.set_api_key(new_api_key)
|
protect.set_api_key(new_api_key)
|
||||||
|
@@ -104,7 +104,12 @@ def async_migrate_entities_unique_ids(
|
|||||||
f"{registry_entry.config_entry_id}_"
|
f"{registry_entry.config_entry_id}_"
|
||||||
).removesuffix(f"_{registry_entry.translation_key}")
|
).removesuffix(f"_{registry_entry.translation_key}")
|
||||||
if monitor := next(
|
if monitor := next(
|
||||||
(m for m in metrics.values() if m.monitor_name == name), None
|
(
|
||||||
|
m
|
||||||
|
for m in metrics.values()
|
||||||
|
if m.monitor_name == name and m.monitor_id is not None
|
||||||
|
),
|
||||||
|
None,
|
||||||
):
|
):
|
||||||
entity_registry.async_update_entity(
|
entity_registry.async_update_entity(
|
||||||
registry_entry.entity_id,
|
registry_entry.entity_id,
|
||||||
|
@@ -79,7 +79,10 @@ DEFAULT_NAME = "Vacuum cleaner robot"
|
|||||||
_DEPRECATED_STATE_IDLE = DeprecatedConstantEnum(VacuumActivity.IDLE, "2026.1")
|
_DEPRECATED_STATE_IDLE = DeprecatedConstantEnum(VacuumActivity.IDLE, "2026.1")
|
||||||
_DEPRECATED_STATE_PAUSED = DeprecatedConstantEnum(VacuumActivity.PAUSED, "2026.1")
|
_DEPRECATED_STATE_PAUSED = DeprecatedConstantEnum(VacuumActivity.PAUSED, "2026.1")
|
||||||
|
|
||||||
_BATTERY_DEPRECATION_IGNORED_PLATFORMS = ("template",)
|
_BATTERY_DEPRECATION_IGNORED_PLATFORMS = (
|
||||||
|
"mqtt",
|
||||||
|
"template",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class VacuumEntityFeature(IntFlag):
|
class VacuumEntityFeature(IntFlag):
|
||||||
@@ -333,7 +336,7 @@ class StateVacuumEntity(
|
|||||||
f"is setting the {property} which has been deprecated."
|
f"is setting the {property} which has been deprecated."
|
||||||
f" Integration {self.platform.platform_name} should implement a sensor"
|
f" Integration {self.platform.platform_name} should implement a sensor"
|
||||||
" instead with a correct device class and link it to the same device",
|
" instead with a correct device class and link it to the same device",
|
||||||
core_integration_behavior=ReportBehavior.LOG,
|
core_integration_behavior=ReportBehavior.IGNORE,
|
||||||
custom_integration_behavior=ReportBehavior.LOG,
|
custom_integration_behavior=ReportBehavior.LOG,
|
||||||
breaks_in_ha_version="2026.8",
|
breaks_in_ha_version="2026.8",
|
||||||
integration_domain=self.platform.platform_name,
|
integration_domain=self.platform.platform_name,
|
||||||
@@ -358,7 +361,7 @@ class StateVacuumEntity(
|
|||||||
f" Integration {self.platform.platform_name} should remove this as part of migrating"
|
f" Integration {self.platform.platform_name} should remove this as part of migrating"
|
||||||
" the battery level and icon to a sensor",
|
" the battery level and icon to a sensor",
|
||||||
core_behavior=ReportBehavior.LOG,
|
core_behavior=ReportBehavior.LOG,
|
||||||
core_integration_behavior=ReportBehavior.LOG,
|
core_integration_behavior=ReportBehavior.IGNORE,
|
||||||
custom_integration_behavior=ReportBehavior.LOG,
|
custom_integration_behavior=ReportBehavior.LOG,
|
||||||
breaks_in_ha_version="2026.8",
|
breaks_in_ha_version="2026.8",
|
||||||
integration_domain=self.platform.platform_name,
|
integration_domain=self.platform.platform_name,
|
||||||
|
@@ -15,6 +15,7 @@ from volvocarsapi.models import (
|
|||||||
VolvoAuthException,
|
VolvoAuthException,
|
||||||
VolvoCarsApiBaseModel,
|
VolvoCarsApiBaseModel,
|
||||||
VolvoCarsValue,
|
VolvoCarsValue,
|
||||||
|
VolvoCarsValueStatusField,
|
||||||
VolvoCarsVehicle,
|
VolvoCarsVehicle,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,6 +37,16 @@ type VolvoConfigEntry = ConfigEntry[tuple[VolvoBaseCoordinator, ...]]
|
|||||||
type CoordinatorData = dict[str, VolvoCarsApiBaseModel | None]
|
type CoordinatorData = dict[str, VolvoCarsApiBaseModel | None]
|
||||||
|
|
||||||
|
|
||||||
|
def _is_invalid_api_field(field: VolvoCarsApiBaseModel | None) -> bool:
|
||||||
|
if not field:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if isinstance(field, VolvoCarsValueStatusField) and field.status == "ERROR":
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class VolvoBaseCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
class VolvoBaseCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
||||||
"""Volvo base coordinator."""
|
"""Volvo base coordinator."""
|
||||||
|
|
||||||
@@ -121,7 +132,13 @@ class VolvoBaseCoordinator(DataUpdateCoordinator[CoordinatorData]):
|
|||||||
translation_key="update_failed",
|
translation_key="update_failed",
|
||||||
) from result
|
) from result
|
||||||
|
|
||||||
data |= cast(CoordinatorData, result)
|
api_data = cast(CoordinatorData, result)
|
||||||
|
data |= {
|
||||||
|
key: field
|
||||||
|
for key, field in api_data.items()
|
||||||
|
if not _is_invalid_api_field(field)
|
||||||
|
}
|
||||||
|
|
||||||
valid = True
|
valid = True
|
||||||
|
|
||||||
# Raise an error if not a single API call succeeded
|
# Raise an error if not a single API call succeeded
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass, replace
|
from dataclasses import dataclass
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
@@ -47,7 +47,6 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
class VolvoSensorDescription(VolvoEntityDescription, SensorEntityDescription):
|
class VolvoSensorDescription(VolvoEntityDescription, SensorEntityDescription):
|
||||||
"""Describes a Volvo sensor entity."""
|
"""Describes a Volvo sensor entity."""
|
||||||
|
|
||||||
source_fields: list[str] | None = None
|
|
||||||
value_fn: Callable[[VolvoCarsValue], Any] | None = None
|
value_fn: Callable[[VolvoCarsValue], Any] | None = None
|
||||||
|
|
||||||
|
|
||||||
@@ -87,7 +86,12 @@ def _charging_power_status_value(field: VolvoCarsValue) -> str | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
_CHARGING_POWER_STATUS_OPTIONS = ["providing_power", "no_power_available"]
|
_CHARGING_POWER_STATUS_OPTIONS = [
|
||||||
|
"fault",
|
||||||
|
"power_available_but_not_activated",
|
||||||
|
"providing_power",
|
||||||
|
"no_power_available",
|
||||||
|
]
|
||||||
|
|
||||||
_DESCRIPTIONS: tuple[VolvoSensorDescription, ...] = (
|
_DESCRIPTIONS: tuple[VolvoSensorDescription, ...] = (
|
||||||
# command-accessibility endpoint
|
# command-accessibility endpoint
|
||||||
@@ -110,6 +114,7 @@ _DESCRIPTIONS: tuple[VolvoSensorDescription, ...] = (
|
|||||||
api_field="averageEnergyConsumption",
|
api_field="averageEnergyConsumption",
|
||||||
native_unit_of_measurement=UnitOfEnergyDistance.KILO_WATT_HOUR_PER_100_KM,
|
native_unit_of_measurement=UnitOfEnergyDistance.KILO_WATT_HOUR_PER_100_KM,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
),
|
),
|
||||||
# statistics endpoint
|
# statistics endpoint
|
||||||
VolvoSensorDescription(
|
VolvoSensorDescription(
|
||||||
@@ -117,6 +122,7 @@ _DESCRIPTIONS: tuple[VolvoSensorDescription, ...] = (
|
|||||||
api_field="averageEnergyConsumptionAutomatic",
|
api_field="averageEnergyConsumptionAutomatic",
|
||||||
native_unit_of_measurement=UnitOfEnergyDistance.KILO_WATT_HOUR_PER_100_KM,
|
native_unit_of_measurement=UnitOfEnergyDistance.KILO_WATT_HOUR_PER_100_KM,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
),
|
),
|
||||||
# statistics endpoint
|
# statistics endpoint
|
||||||
VolvoSensorDescription(
|
VolvoSensorDescription(
|
||||||
@@ -124,6 +130,7 @@ _DESCRIPTIONS: tuple[VolvoSensorDescription, ...] = (
|
|||||||
api_field="averageEnergyConsumptionSinceCharge",
|
api_field="averageEnergyConsumptionSinceCharge",
|
||||||
native_unit_of_measurement=UnitOfEnergyDistance.KILO_WATT_HOUR_PER_100_KM,
|
native_unit_of_measurement=UnitOfEnergyDistance.KILO_WATT_HOUR_PER_100_KM,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
),
|
),
|
||||||
# statistics endpoint
|
# statistics endpoint
|
||||||
VolvoSensorDescription(
|
VolvoSensorDescription(
|
||||||
@@ -131,6 +138,7 @@ _DESCRIPTIONS: tuple[VolvoSensorDescription, ...] = (
|
|||||||
api_field="averageFuelConsumption",
|
api_field="averageFuelConsumption",
|
||||||
native_unit_of_measurement="L/100 km",
|
native_unit_of_measurement="L/100 km",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
),
|
),
|
||||||
# statistics endpoint
|
# statistics endpoint
|
||||||
VolvoSensorDescription(
|
VolvoSensorDescription(
|
||||||
@@ -138,6 +146,7 @@ _DESCRIPTIONS: tuple[VolvoSensorDescription, ...] = (
|
|||||||
api_field="averageFuelConsumptionAutomatic",
|
api_field="averageFuelConsumptionAutomatic",
|
||||||
native_unit_of_measurement="L/100 km",
|
native_unit_of_measurement="L/100 km",
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
suggested_display_precision=1,
|
||||||
),
|
),
|
||||||
# statistics endpoint
|
# statistics endpoint
|
||||||
VolvoSensorDescription(
|
VolvoSensorDescription(
|
||||||
@@ -235,11 +244,15 @@ _DESCRIPTIONS: tuple[VolvoSensorDescription, ...] = (
|
|||||||
"none",
|
"none",
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
# statistics & energy state endpoint
|
# statistics endpoint
|
||||||
|
# We're not using `electricRange` from the energy state endpoint because
|
||||||
|
# the official app seems to use `distanceToEmptyBattery`.
|
||||||
|
# In issue #150213, a user described to behavior as follows:
|
||||||
|
# - For a `distanceToEmptyBattery` of 250km, the `electricRange` was 150mi
|
||||||
|
# - For a `distanceToEmptyBattery` of 260km, the `electricRange` was 160mi
|
||||||
VolvoSensorDescription(
|
VolvoSensorDescription(
|
||||||
key="distance_to_empty_battery",
|
key="distance_to_empty_battery",
|
||||||
api_field="",
|
api_field="distanceToEmptyBattery",
|
||||||
source_fields=["distanceToEmptyBattery", "electricRange"],
|
|
||||||
native_unit_of_measurement=UnitOfLength.KILOMETERS,
|
native_unit_of_measurement=UnitOfLength.KILOMETERS,
|
||||||
device_class=SensorDeviceClass.DISTANCE,
|
device_class=SensorDeviceClass.DISTANCE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -357,12 +370,7 @@ async def async_setup_entry(
|
|||||||
if description.key in added_keys:
|
if description.key in added_keys:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if description.source_fields:
|
if description.api_field in coordinator.data:
|
||||||
for field in description.source_fields:
|
|
||||||
if field in coordinator.data:
|
|
||||||
description = replace(description, api_field=field)
|
|
||||||
_add_entity(coordinator, description)
|
|
||||||
elif description.api_field in coordinator.data:
|
|
||||||
_add_entity(coordinator, description)
|
_add_entity(coordinator, description)
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
@@ -94,7 +94,7 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"connected": "[%key:common::state::connected%]",
|
"connected": "[%key:common::state::connected%]",
|
||||||
"disconnected": "[%key:common::state::disconnected%]",
|
"disconnected": "[%key:common::state::disconnected%]",
|
||||||
"fault": "[%key:common::state::error%]"
|
"fault": "[%key:common::state::fault%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"charging_current_limit": {
|
"charging_current_limit": {
|
||||||
@@ -106,6 +106,8 @@
|
|||||||
"charging_power_status": {
|
"charging_power_status": {
|
||||||
"name": "Charging power status",
|
"name": "Charging power status",
|
||||||
"state": {
|
"state": {
|
||||||
|
"fault": "[%key:common::state::fault%]",
|
||||||
|
"power_available_but_not_activated": "Power available",
|
||||||
"providing_power": "Providing power",
|
"providing_power": "Providing power",
|
||||||
"no_power_available": "No power"
|
"no_power_available": "No power"
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,7 @@ from typing import Any
|
|||||||
|
|
||||||
from zha.application.const import ATTR_IEEE
|
from zha.application.const import ATTR_IEEE
|
||||||
from zha.application.gateway import Gateway
|
from zha.application.gateway import Gateway
|
||||||
|
from zigpy.application import ControllerApplication
|
||||||
from zigpy.config import CONF_NWK_EXTENDED_PAN_ID
|
from zigpy.config import CONF_NWK_EXTENDED_PAN_ID
|
||||||
from zigpy.types import Channels
|
from zigpy.types import Channels
|
||||||
|
|
||||||
@@ -63,6 +64,19 @@ def shallow_asdict(obj: Any) -> dict:
|
|||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def get_application_state_diagnostics(app: ControllerApplication) -> dict:
|
||||||
|
"""Dump the application state as a dictionary."""
|
||||||
|
data = shallow_asdict(app.state)
|
||||||
|
|
||||||
|
# EUI64 objects in zigpy are not subclasses of any JSON-serializable key type and
|
||||||
|
# must be converted to strings.
|
||||||
|
data["network_info"]["nwk_addresses"] = {
|
||||||
|
str(k): v for k, v in data["network_info"]["nwk_addresses"].items()
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
async def async_get_config_entry_diagnostics(
|
async def async_get_config_entry_diagnostics(
|
||||||
hass: HomeAssistant, config_entry: ConfigEntry
|
hass: HomeAssistant, config_entry: ConfigEntry
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
@@ -79,7 +93,7 @@ async def async_get_config_entry_diagnostics(
|
|||||||
{
|
{
|
||||||
"config": zha_data.yaml_config,
|
"config": zha_data.yaml_config,
|
||||||
"config_entry": config_entry.as_dict(),
|
"config_entry": config_entry.as_dict(),
|
||||||
"application_state": shallow_asdict(app.state),
|
"application_state": get_application_state_diagnostics(app),
|
||||||
"energy_scan": {
|
"energy_scan": {
|
||||||
channel: 100 * energy / 255 for channel, energy in energy_scan.items()
|
channel: 100 * energy / 255 for channel, energy in energy_scan.items()
|
||||||
},
|
},
|
||||||
|
@@ -21,7 +21,7 @@
|
|||||||
"zha",
|
"zha",
|
||||||
"universal_silabs_flasher"
|
"universal_silabs_flasher"
|
||||||
],
|
],
|
||||||
"requirements": ["zha==0.0.66"],
|
"requirements": ["zha==0.0.68"],
|
||||||
"usb": [
|
"usb": [
|
||||||
{
|
{
|
||||||
"vid": "10C4",
|
"vid": "10C4",
|
||||||
|
@@ -43,7 +43,7 @@ from .models import ZwaveJSConfigEntry
|
|||||||
PARALLEL_UPDATES = 1
|
PARALLEL_UPDATES = 1
|
||||||
|
|
||||||
UPDATE_DELAY_STRING = "delay"
|
UPDATE_DELAY_STRING = "delay"
|
||||||
UPDATE_DELAY_INTERVAL = 5 # In minutes
|
UPDATE_DELAY_INTERVAL = 15 # In seconds
|
||||||
ATTR_LATEST_VERSION_FIRMWARE = "latest_version_firmware"
|
ATTR_LATEST_VERSION_FIRMWARE = "latest_version_firmware"
|
||||||
|
|
||||||
|
|
||||||
@@ -130,11 +130,11 @@ async def async_setup_entry(
|
|||||||
@callback
|
@callback
|
||||||
def async_add_firmware_update_entity(node: ZwaveNode) -> None:
|
def async_add_firmware_update_entity(node: ZwaveNode) -> None:
|
||||||
"""Add firmware update entity."""
|
"""Add firmware update entity."""
|
||||||
# We need to delay the first update of each entity to avoid flooding the network
|
# Delay the first update of each entity to avoid spamming the firmware server.
|
||||||
# so we maintain a counter to schedule first update in UPDATE_DELAY_INTERVAL
|
# Maintain a counter to schedule first update in UPDATE_DELAY_INTERVAL
|
||||||
# minute increments.
|
# second increments.
|
||||||
cnt[UPDATE_DELAY_STRING] += 1
|
cnt[UPDATE_DELAY_STRING] += 1
|
||||||
delay = timedelta(minutes=(cnt[UPDATE_DELAY_STRING] * UPDATE_DELAY_INTERVAL))
|
delay = timedelta(seconds=(cnt[UPDATE_DELAY_STRING] * UPDATE_DELAY_INTERVAL))
|
||||||
driver = client.driver
|
driver = client.driver
|
||||||
assert driver is not None # Driver is ready before platforms are loaded.
|
assert driver is not None # Driver is ready before platforms are loaded.
|
||||||
if node.is_controller_node:
|
if node.is_controller_node:
|
||||||
@@ -429,7 +429,8 @@ class ZWaveFirmwareUpdateEntity(UpdateEntity):
|
|||||||
):
|
):
|
||||||
self._attr_latest_version = self._attr_installed_version
|
self._attr_latest_version = self._attr_installed_version
|
||||||
|
|
||||||
# Spread updates out in 5 minute increments to avoid flooding the network
|
# Spread updates out in 15 second increments
|
||||||
|
# to avoid spamming the firmware server
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
async_call_later(self.hass, self._delay, self._async_update)
|
async_call_later(self.hass, self._delay, self._async_update)
|
||||||
)
|
)
|
||||||
|
@@ -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 = "0"
|
PATCH_VERSION: Final = "1"
|
||||||
__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)
|
||||||
|
@@ -35,10 +35,10 @@ fnv-hash-fast==1.5.0
|
|||||||
go2rtc-client==0.2.1
|
go2rtc-client==0.2.1
|
||||||
ha-ffmpeg==3.2.2
|
ha-ffmpeg==3.2.2
|
||||||
habluetooth==4.0.2
|
habluetooth==4.0.2
|
||||||
hass-nabucasa==0.111.1
|
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==20250806.0
|
home-assistant-frontend==20250811.0
|
||||||
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
|
||||||
@@ -213,3 +213,6 @@ multidict>=6.4.2
|
|||||||
# Stable Alpine current only ships cargo 1.83.0
|
# Stable Alpine current only ships cargo 1.83.0
|
||||||
# No wheels upstream available for armhf & armv7
|
# No wheels upstream available for armhf & armv7
|
||||||
rpds-py==0.24.0
|
rpds-py==0.24.0
|
||||||
|
|
||||||
|
# Constraint num2words to 0.5.14 as 0.5.15 and 0.5.16 were removed from PyPI
|
||||||
|
num2words==0.5.14
|
||||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2025.8.0"
|
version = "2025.8.1"
|
||||||
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."
|
||||||
@@ -47,7 +47,7 @@ dependencies = [
|
|||||||
"fnv-hash-fast==1.5.0",
|
"fnv-hash-fast==1.5.0",
|
||||||
# hass-nabucasa is imported by helpers which don't depend on the cloud
|
# hass-nabucasa is imported by helpers which don't depend on the cloud
|
||||||
# integration
|
# integration
|
||||||
"hass-nabucasa==0.111.1",
|
"hass-nabucasa==0.111.2",
|
||||||
# When bumping httpx, please check the version pins of
|
# When bumping httpx, please check the version pins of
|
||||||
# httpcore, anyio, and h11 in gen_requirements_all
|
# httpcore, anyio, and h11 in gen_requirements_all
|
||||||
"httpx==0.28.1",
|
"httpx==0.28.1",
|
||||||
|
2
requirements.txt
generated
2
requirements.txt
generated
@@ -22,7 +22,7 @@ certifi>=2021.5.30
|
|||||||
ciso8601==2.3.2
|
ciso8601==2.3.2
|
||||||
cronsim==2.6
|
cronsim==2.6
|
||||||
fnv-hash-fast==1.5.0
|
fnv-hash-fast==1.5.0
|
||||||
hass-nabucasa==0.111.1
|
hass-nabucasa==0.111.2
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
home-assistant-bluetooth==1.13.1
|
home-assistant-bluetooth==1.13.1
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
|
12
requirements_all.txt
generated
12
requirements_all.txt
generated
@@ -453,7 +453,7 @@ airgradient==0.9.2
|
|||||||
airly==1.1.0
|
airly==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.airos
|
# homeassistant.components.airos
|
||||||
airos==0.2.4
|
airos==0.2.7
|
||||||
|
|
||||||
# homeassistant.components.airthings_ble
|
# homeassistant.components.airthings_ble
|
||||||
airthings-ble==0.9.2
|
airthings-ble==0.9.2
|
||||||
@@ -1133,7 +1133,7 @@ habiticalib==0.4.1
|
|||||||
habluetooth==4.0.2
|
habluetooth==4.0.2
|
||||||
|
|
||||||
# homeassistant.components.cloud
|
# homeassistant.components.cloud
|
||||||
hass-nabucasa==0.111.1
|
hass-nabucasa==0.111.2
|
||||||
|
|
||||||
# homeassistant.components.splunk
|
# homeassistant.components.splunk
|
||||||
hass-splunk==0.1.1
|
hass-splunk==0.1.1
|
||||||
@@ -1174,7 +1174,7 @@ hole==0.9.0
|
|||||||
holidays==0.78
|
holidays==0.78
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20250806.0
|
home-assistant-frontend==20250811.0
|
||||||
|
|
||||||
# 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.2
|
imgw_pib==1.5.3
|
||||||
|
|
||||||
# homeassistant.components.incomfort
|
# homeassistant.components.incomfort
|
||||||
incomfort-client==0.6.9
|
incomfort-client==0.6.9
|
||||||
@@ -1307,7 +1307,7 @@ kiwiki-client==0.1.1
|
|||||||
knocki==0.4.2
|
knocki==0.4.2
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
knx-frontend==2025.8.6.52906
|
knx-frontend==2025.8.9.63154
|
||||||
|
|
||||||
# homeassistant.components.konnected
|
# homeassistant.components.konnected
|
||||||
konnected==1.2.0
|
konnected==1.2.0
|
||||||
@@ -3203,7 +3203,7 @@ zeroconf==0.147.0
|
|||||||
zeversolar==0.3.2
|
zeversolar==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zha==0.0.66
|
zha==0.0.68
|
||||||
|
|
||||||
# homeassistant.components.zhong_hong
|
# homeassistant.components.zhong_hong
|
||||||
zhong-hong-hvac==1.0.13
|
zhong-hong-hvac==1.0.13
|
||||||
|
12
requirements_test_all.txt
generated
12
requirements_test_all.txt
generated
@@ -435,7 +435,7 @@ airgradient==0.9.2
|
|||||||
airly==1.1.0
|
airly==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.airos
|
# homeassistant.components.airos
|
||||||
airos==0.2.4
|
airos==0.2.7
|
||||||
|
|
||||||
# homeassistant.components.airthings_ble
|
# homeassistant.components.airthings_ble
|
||||||
airthings-ble==0.9.2
|
airthings-ble==0.9.2
|
||||||
@@ -994,7 +994,7 @@ habiticalib==0.4.1
|
|||||||
habluetooth==4.0.2
|
habluetooth==4.0.2
|
||||||
|
|
||||||
# homeassistant.components.cloud
|
# homeassistant.components.cloud
|
||||||
hass-nabucasa==0.111.1
|
hass-nabucasa==0.111.2
|
||||||
|
|
||||||
# homeassistant.components.assist_satellite
|
# homeassistant.components.assist_satellite
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
@@ -1023,7 +1023,7 @@ hole==0.9.0
|
|||||||
holidays==0.78
|
holidays==0.78
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20250806.0
|
home-assistant-frontend==20250811.0
|
||||||
|
|
||||||
# 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.2
|
imgw_pib==1.5.3
|
||||||
|
|
||||||
# homeassistant.components.incomfort
|
# homeassistant.components.incomfort
|
||||||
incomfort-client==0.6.9
|
incomfort-client==0.6.9
|
||||||
@@ -1129,7 +1129,7 @@ kegtron-ble==0.4.0
|
|||||||
knocki==0.4.2
|
knocki==0.4.2
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
knx-frontend==2025.8.6.52906
|
knx-frontend==2025.8.9.63154
|
||||||
|
|
||||||
# homeassistant.components.konnected
|
# homeassistant.components.konnected
|
||||||
konnected==1.2.0
|
konnected==1.2.0
|
||||||
@@ -2647,7 +2647,7 @@ zeroconf==0.147.0
|
|||||||
zeversolar==0.3.2
|
zeversolar==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zha==0.0.66
|
zha==0.0.68
|
||||||
|
|
||||||
# homeassistant.components.zwave_js
|
# homeassistant.components.zwave_js
|
||||||
zwave-js-server-python==0.67.1
|
zwave-js-server-python==0.67.1
|
||||||
|
@@ -239,6 +239,9 @@ multidict>=6.4.2
|
|||||||
# Stable Alpine current only ships cargo 1.83.0
|
# Stable Alpine current only ships cargo 1.83.0
|
||||||
# No wheels upstream available for armhf & armv7
|
# No wheels upstream available for armhf & armv7
|
||||||
rpds-py==0.24.0
|
rpds-py==0.24.0
|
||||||
|
|
||||||
|
# Constraint num2words to 0.5.14 as 0.5.15 and 0.5.16 were removed from PyPI
|
||||||
|
num2words==0.5.14
|
||||||
"""
|
"""
|
||||||
|
|
||||||
GENERATED_MESSAGE = (
|
GENERATED_MESSAGE = (
|
||||||
|
@@ -15,7 +15,7 @@ from tests.common import MockConfigEntry, load_json_object_fixture
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def ap_fixture():
|
def ap_fixture():
|
||||||
"""Load fixture data for AP mode."""
|
"""Load fixture data for AP mode."""
|
||||||
json_data = load_json_object_fixture("airos_ap-ptp.json", DOMAIN)
|
json_data = load_json_object_fixture("airos_loco5ac_ap-ptp.json", DOMAIN)
|
||||||
return AirOSData.from_dict(json_data)
|
return AirOSData.from_dict(json_data)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,132 +1,194 @@
|
|||||||
{
|
{
|
||||||
"chain_names": [
|
"chain_names": [
|
||||||
{ "number": 1, "name": "Chain 0" },
|
{
|
||||||
{ "number": 2, "name": "Chain 1" }
|
"name": "Chain 0",
|
||||||
|
"number": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chain 1",
|
||||||
|
"number": 2
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"host": {
|
"derived": {
|
||||||
"hostname": "NanoStation 5AC ap name",
|
"access_point": true,
|
||||||
"device_id": "03aa0d0b40fed0a47088293584ef5432",
|
"mac": "01:23:45:67:89:AB",
|
||||||
"uptime": 264888,
|
"mac_interface": "br0",
|
||||||
"power_time": 268683,
|
"ptmp": false,
|
||||||
"time": "2025-06-23 23:06:42",
|
"ptp": true,
|
||||||
"timestamp": 2668313184,
|
"station": false
|
||||||
"fwversion": "v8.7.17",
|
|
||||||
"devmodel": "NanoStation 5AC loco",
|
|
||||||
"netrole": "bridge",
|
|
||||||
"loadavg": 0.412598,
|
|
||||||
"totalram": 63447040,
|
|
||||||
"freeram": 16564224,
|
|
||||||
"temperature": 0,
|
|
||||||
"cpuload": 10.10101,
|
|
||||||
"height": 3
|
|
||||||
},
|
|
||||||
"genuine": "/images/genuine.png",
|
|
||||||
"services": {
|
|
||||||
"dhcpc": false,
|
|
||||||
"dhcpd": false,
|
|
||||||
"dhcp6d_stateful": false,
|
|
||||||
"pppoe": false,
|
|
||||||
"airview": 2
|
|
||||||
},
|
},
|
||||||
"firewall": {
|
"firewall": {
|
||||||
"iptables": false,
|
"eb6tables": false,
|
||||||
"ebtables": false,
|
"ebtables": false,
|
||||||
"ip6tables": false,
|
"ip6tables": false,
|
||||||
"eb6tables": false
|
"iptables": false
|
||||||
},
|
},
|
||||||
|
"genuine": "/images/genuine.png",
|
||||||
|
"gps": {
|
||||||
|
"fix": 0,
|
||||||
|
"lat": 52.379894,
|
||||||
|
"lon": 4.901608
|
||||||
|
},
|
||||||
|
"host": {
|
||||||
|
"cpuload": 10.10101,
|
||||||
|
"device_id": "03aa0d0b40fed0a47088293584ef5432",
|
||||||
|
"devmodel": "NanoStation 5AC loco",
|
||||||
|
"freeram": 16564224,
|
||||||
|
"fwversion": "v8.7.17",
|
||||||
|
"height": 3,
|
||||||
|
"hostname": "NanoStation 5AC ap name",
|
||||||
|
"loadavg": 0.412598,
|
||||||
|
"netrole": "bridge",
|
||||||
|
"power_time": 268683,
|
||||||
|
"temperature": 0,
|
||||||
|
"time": "2025-06-23 23:06:42",
|
||||||
|
"timestamp": 2668313184,
|
||||||
|
"totalram": 63447040,
|
||||||
|
"uptime": 264888
|
||||||
|
},
|
||||||
|
"interfaces": [
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"hwaddr": "01:23:45:67:89:AB",
|
||||||
|
"ifname": "eth0",
|
||||||
|
"mtu": 1500,
|
||||||
|
"status": {
|
||||||
|
"cable_len": 18,
|
||||||
|
"duplex": true,
|
||||||
|
"ip6addr": null,
|
||||||
|
"ipaddr": "0.0.0.0",
|
||||||
|
"plugged": true,
|
||||||
|
"rx_bytes": 3984971949,
|
||||||
|
"rx_dropped": 0,
|
||||||
|
"rx_errors": 4,
|
||||||
|
"rx_packets": 73564835,
|
||||||
|
"snr": [30, 30, 30, 30],
|
||||||
|
"speed": 1000,
|
||||||
|
"tx_bytes": 209900085624,
|
||||||
|
"tx_dropped": 10,
|
||||||
|
"tx_errors": 0,
|
||||||
|
"tx_packets": 185866883
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"hwaddr": "01:23:45:67:89:AB",
|
||||||
|
"ifname": "ath0",
|
||||||
|
"mtu": 1500,
|
||||||
|
"status": {
|
||||||
|
"cable_len": null,
|
||||||
|
"duplex": false,
|
||||||
|
"ip6addr": null,
|
||||||
|
"ipaddr": "0.0.0.0",
|
||||||
|
"plugged": false,
|
||||||
|
"rx_bytes": 206938324766,
|
||||||
|
"rx_dropped": 0,
|
||||||
|
"rx_errors": 0,
|
||||||
|
"rx_packets": 149767200,
|
||||||
|
"snr": null,
|
||||||
|
"speed": 0,
|
||||||
|
"tx_bytes": 5265602738,
|
||||||
|
"tx_dropped": 2005,
|
||||||
|
"tx_errors": 0,
|
||||||
|
"tx_packets": 52980390
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"hwaddr": "01:23:45:67:89:AB",
|
||||||
|
"ifname": "br0",
|
||||||
|
"mtu": 1500,
|
||||||
|
"status": {
|
||||||
|
"cable_len": null,
|
||||||
|
"duplex": false,
|
||||||
|
"ip6addr": [
|
||||||
|
{
|
||||||
|
"addr": "fe80::eea:14ff:fea4:89cd",
|
||||||
|
"plen": 64
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ipaddr": "192.168.1.2",
|
||||||
|
"plugged": true,
|
||||||
|
"rx_bytes": 204802727,
|
||||||
|
"rx_dropped": 0,
|
||||||
|
"rx_errors": 0,
|
||||||
|
"rx_packets": 1791592,
|
||||||
|
"snr": null,
|
||||||
|
"speed": 0,
|
||||||
|
"tx_bytes": 236295176,
|
||||||
|
"tx_dropped": 0,
|
||||||
|
"tx_errors": 0,
|
||||||
|
"tx_packets": 298119
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ntpclient": {},
|
||||||
"portfw": false,
|
"portfw": false,
|
||||||
|
"provmode": {},
|
||||||
|
"services": {
|
||||||
|
"airview": 2,
|
||||||
|
"dhcp6d_stateful": false,
|
||||||
|
"dhcpc": false,
|
||||||
|
"dhcpd": false,
|
||||||
|
"pppoe": false
|
||||||
|
},
|
||||||
|
"unms": {
|
||||||
|
"status": 0,
|
||||||
|
"timestamp": null
|
||||||
|
},
|
||||||
"wireless": {
|
"wireless": {
|
||||||
"essid": "DemoSSID",
|
|
||||||
"mode": "ap-ptp",
|
|
||||||
"ieeemode": "11ACVHT80",
|
|
||||||
"band": 2,
|
|
||||||
"compat_11n": 0,
|
|
||||||
"hide_essid": 0,
|
|
||||||
"apmac": "01:23:45:67:89:AB",
|
|
||||||
"antenna_gain": 13,
|
"antenna_gain": 13,
|
||||||
"frequency": 5500,
|
"apmac": "01:23:45:67:89:AB",
|
||||||
"center1_freq": 5530,
|
|
||||||
"dfs": 1,
|
|
||||||
"distance": 0,
|
|
||||||
"security": "WPA2",
|
|
||||||
"noisef": -89,
|
|
||||||
"txpower": -3,
|
|
||||||
"aprepeater": false,
|
"aprepeater": false,
|
||||||
"rstatus": 5,
|
"band": 2,
|
||||||
"chanbw": 80,
|
|
||||||
"rx_chainmask": 3,
|
|
||||||
"tx_chainmask": 3,
|
|
||||||
"nol_state": 0,
|
|
||||||
"nol_timeout": 0,
|
|
||||||
"cac_state": 0,
|
"cac_state": 0,
|
||||||
"cac_timeout": 0,
|
"cac_timeout": 0,
|
||||||
"rx_idx": 8,
|
"center1_freq": 5530,
|
||||||
"rx_nss": 2,
|
"chanbw": 80,
|
||||||
"tx_idx": 9,
|
"compat_11n": 0,
|
||||||
"tx_nss": 2,
|
"count": 1,
|
||||||
"throughput": { "tx": 222, "rx": 9907 },
|
"dfs": 1,
|
||||||
"service": { "time": 267181, "link": 266003 },
|
"distance": 0,
|
||||||
|
"essid": "DemoSSID",
|
||||||
|
"frequency": 5500,
|
||||||
|
"hide_essid": 0,
|
||||||
|
"ieeemode": "11ACVHT80",
|
||||||
|
"mode": "ap-ptp",
|
||||||
|
"noisef": -89,
|
||||||
|
"nol_state": 0,
|
||||||
|
"nol_timeout": 0,
|
||||||
"polling": {
|
"polling": {
|
||||||
|
"atpc_status": 2,
|
||||||
"cb_capacity": 593970,
|
"cb_capacity": 593970,
|
||||||
"dl_capacity": 647400,
|
"dl_capacity": 647400,
|
||||||
"ul_capacity": 540540,
|
"ff_cap_rep": false,
|
||||||
"use": 48,
|
|
||||||
"tx_use": 6,
|
|
||||||
"rx_use": 42,
|
|
||||||
"atpc_status": 2,
|
|
||||||
"fixed_frame": false,
|
"fixed_frame": false,
|
||||||
|
"flex_mode": null,
|
||||||
"gps_sync": false,
|
"gps_sync": false,
|
||||||
"ff_cap_rep": false
|
"rx_use": 42,
|
||||||
|
"tx_use": 6,
|
||||||
|
"ul_capacity": 540540,
|
||||||
|
"use": 48
|
||||||
|
},
|
||||||
|
"rstatus": 5,
|
||||||
|
"rx_chainmask": 3,
|
||||||
|
"rx_idx": 8,
|
||||||
|
"rx_nss": 2,
|
||||||
|
"security": "WPA2",
|
||||||
|
"service": {
|
||||||
|
"link": 266003,
|
||||||
|
"time": 267181
|
||||||
},
|
},
|
||||||
"count": 1,
|
|
||||||
"sta": [
|
"sta": [
|
||||||
{
|
{
|
||||||
"mac": "01:23:45:67:89:AB",
|
|
||||||
"lastip": "192.168.1.2",
|
|
||||||
"signal": -59,
|
|
||||||
"rssi": 37,
|
|
||||||
"noisefloor": -89,
|
|
||||||
"chainrssi": [35, 32, 0],
|
|
||||||
"tx_idx": 9,
|
|
||||||
"rx_idx": 8,
|
|
||||||
"tx_nss": 2,
|
|
||||||
"rx_nss": 2,
|
|
||||||
"tx_latency": 0,
|
|
||||||
"distance": 1,
|
|
||||||
"tx_packets": 0,
|
|
||||||
"tx_lretries": 0,
|
|
||||||
"tx_sretries": 0,
|
|
||||||
"uptime": 170281,
|
|
||||||
"dl_signal_expect": -80,
|
|
||||||
"ul_signal_expect": -55,
|
|
||||||
"cb_capacity_expect": 416000,
|
|
||||||
"dl_capacity_expect": 208000,
|
|
||||||
"ul_capacity_expect": 624000,
|
|
||||||
"dl_rate_expect": 3,
|
|
||||||
"ul_rate_expect": 8,
|
|
||||||
"dl_linkscore": 100,
|
|
||||||
"ul_linkscore": 86,
|
|
||||||
"dl_avg_linkscore": 100,
|
|
||||||
"ul_avg_linkscore": 88,
|
|
||||||
"tx_ratedata": [175, 4, 47, 200, 673, 158, 163, 138, 68895, 19577430],
|
|
||||||
"stats": {
|
|
||||||
"rx_bytes": 206938324814,
|
|
||||||
"rx_packets": 149767200,
|
|
||||||
"rx_pps": 846,
|
|
||||||
"tx_bytes": 5265602739,
|
|
||||||
"tx_packets": 52980390,
|
|
||||||
"tx_pps": 0
|
|
||||||
},
|
|
||||||
"airmax": {
|
"airmax": {
|
||||||
"actual_priority": 0,
|
"actual_priority": 0,
|
||||||
"beam": 0,
|
|
||||||
"desired_priority": 0,
|
|
||||||
"cb_capacity": 593970,
|
|
||||||
"dl_capacity": 647400,
|
|
||||||
"ul_capacity": 540540,
|
|
||||||
"atpc_status": 2,
|
"atpc_status": 2,
|
||||||
|
"beam": 0,
|
||||||
|
"cb_capacity": 593970,
|
||||||
|
"desired_priority": 0,
|
||||||
|
"dl_capacity": 647400,
|
||||||
"rx": {
|
"rx": {
|
||||||
"usage": 42,
|
|
||||||
"cinr": 31,
|
"cinr": 31,
|
||||||
"evm": [
|
"evm": [
|
||||||
[
|
[
|
||||||
@@ -141,10 +203,10 @@
|
|||||||
34, 33, 34, 34, 34, 34, 34, 35, 35, 35, 34, 35, 33, 34, 34, 34,
|
34, 33, 34, 34, 34, 34, 34, 35, 35, 35, 34, 35, 33, 34, 34, 34,
|
||||||
34, 35, 35, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 35
|
34, 35, 35, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 35, 35
|
||||||
]
|
]
|
||||||
]
|
],
|
||||||
|
"usage": 42
|
||||||
},
|
},
|
||||||
"tx": {
|
"tx": {
|
||||||
"usage": 6,
|
|
||||||
"cinr": 31,
|
"cinr": 31,
|
||||||
"evm": [
|
"evm": [
|
||||||
[
|
[
|
||||||
@@ -159,142 +221,127 @@
|
|||||||
38, 37, 37, 37, 38, 37, 38, 37, 37, 37, 37, 37, 36, 37, 37, 37,
|
38, 37, 37, 37, 38, 37, 38, 37, 37, 37, 37, 37, 36, 37, 37, 37,
|
||||||
37, 37, 37, 38, 37, 37, 38, 37, 36, 37, 37, 37, 37, 37, 37, 37
|
37, 37, 37, 38, 37, 37, 38, 37, 36, 37, 37, 37, 37, 37, 37, 37
|
||||||
]
|
]
|
||||||
]
|
],
|
||||||
}
|
"usage": 6
|
||||||
|
},
|
||||||
|
"ul_capacity": 540540
|
||||||
},
|
},
|
||||||
|
"airos_connected": true,
|
||||||
|
"cb_capacity_expect": 416000,
|
||||||
|
"chainrssi": [35, 32, 0],
|
||||||
|
"distance": 1,
|
||||||
|
"dl_avg_linkscore": 100,
|
||||||
|
"dl_capacity_expect": 208000,
|
||||||
|
"dl_linkscore": 100,
|
||||||
|
"dl_rate_expect": 3,
|
||||||
|
"dl_signal_expect": -80,
|
||||||
"last_disc": 1,
|
"last_disc": 1,
|
||||||
|
"lastip": "192.168.1.2",
|
||||||
|
"mac": "01:23:45:67:89:AB",
|
||||||
|
"noisefloor": -89,
|
||||||
"remote": {
|
"remote": {
|
||||||
"age": 1,
|
"age": 1,
|
||||||
"device_id": "d4f4cdf82961e619328a8f72f8d7653b",
|
"airview": 2,
|
||||||
"hostname": "NanoStation 5AC sta name",
|
"antenna_gain": 13,
|
||||||
"platform": "NanoStation 5AC loco",
|
"cable_loss": 0,
|
||||||
"version": "WA.ar934x.v8.7.17.48152.250620.2132",
|
|
||||||
"time": "2025-06-23 23:13:54",
|
|
||||||
"cpuload": 43.564301,
|
|
||||||
"temperature": 0,
|
|
||||||
"totalram": 63447040,
|
|
||||||
"freeram": 14290944,
|
|
||||||
"netrole": "bridge",
|
|
||||||
"mode": "sta-ptp",
|
|
||||||
"sys_id": "0xe7fa",
|
|
||||||
"tx_throughput": 16023,
|
|
||||||
"rx_throughput": 251,
|
|
||||||
"uptime": 265320,
|
|
||||||
"power_time": 268512,
|
|
||||||
"compat_11n": 0,
|
|
||||||
"signal": -58,
|
|
||||||
"rssi": 38,
|
|
||||||
"noisefloor": -90,
|
|
||||||
"tx_power": -4,
|
|
||||||
"distance": 1,
|
|
||||||
"rx_chainmask": 3,
|
|
||||||
"chainrssi": [33, 37, 0],
|
"chainrssi": [33, 37, 0],
|
||||||
|
"compat_11n": 0,
|
||||||
|
"cpuload": 43.564301,
|
||||||
|
"device_id": "d4f4cdf82961e619328a8f72f8d7653b",
|
||||||
|
"distance": 1,
|
||||||
|
"ethlist": [
|
||||||
|
{
|
||||||
|
"cable_len": 14,
|
||||||
|
"duplex": true,
|
||||||
|
"enabled": true,
|
||||||
|
"ifname": "eth0",
|
||||||
|
"plugged": true,
|
||||||
|
"snr": [30, 30, 29, 30],
|
||||||
|
"speed": 1000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"freeram": 14290944,
|
||||||
|
"gps": {
|
||||||
|
"alt": null,
|
||||||
|
"dim": null,
|
||||||
|
"dop": null,
|
||||||
|
"fix": 0,
|
||||||
|
"lat": 52.379894,
|
||||||
|
"lon": 4.901608,
|
||||||
|
"sats": null,
|
||||||
|
"time_synced": null
|
||||||
|
},
|
||||||
|
"height": 2,
|
||||||
|
"hostname": "NanoStation 5AC sta name",
|
||||||
|
"ip6addr": ["fe80::eea:14ff:fea4:89ab"],
|
||||||
|
"ipaddr": ["192.168.1.2"],
|
||||||
|
"mode": "sta-ptp",
|
||||||
|
"netrole": "bridge",
|
||||||
|
"noisefloor": -90,
|
||||||
|
"oob": false,
|
||||||
|
"platform": "NanoStation 5AC loco",
|
||||||
|
"power_time": 268512,
|
||||||
|
"rssi": 38,
|
||||||
|
"rx_bytes": 3624206478,
|
||||||
|
"rx_chainmask": 3,
|
||||||
|
"rx_throughput": 251,
|
||||||
|
"service": {
|
||||||
|
"link": 265996,
|
||||||
|
"time": 267195
|
||||||
|
},
|
||||||
|
"signal": -58,
|
||||||
|
"sys_id": "0xe7fa",
|
||||||
|
"temperature": 0,
|
||||||
|
"time": "2025-06-23 23:13:54",
|
||||||
|
"totalram": 63447040,
|
||||||
|
"tx_bytes": 212308148210,
|
||||||
|
"tx_power": -4,
|
||||||
"tx_ratedata": [
|
"tx_ratedata": [
|
||||||
14, 4, 372, 2223, 4708, 4037, 8142, 485763, 29420892, 24748154
|
14, 4, 372, 2223, 4708, 4037, 8142, 485763, 29420892, 24748154
|
||||||
],
|
],
|
||||||
"tx_bytes": 212308148210,
|
"tx_throughput": 16023,
|
||||||
"rx_bytes": 3624206478,
|
"unms": {
|
||||||
"antenna_gain": 13,
|
"status": 0,
|
||||||
"cable_loss": 0,
|
"timestamp": null
|
||||||
"height": 2,
|
},
|
||||||
"ethlist": [
|
"uptime": 265320,
|
||||||
{
|
"version": "WA.ar934x.v8.7.17.48152.250620.2132"
|
||||||
"ifname": "eth0",
|
|
||||||
"enabled": true,
|
|
||||||
"plugged": true,
|
|
||||||
"duplex": true,
|
|
||||||
"speed": 1000,
|
|
||||||
"snr": [30, 30, 29, 30],
|
|
||||||
"cable_len": 14
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ipaddr": ["192.168.1.2"],
|
|
||||||
"ip6addr": ["fe80::eea:14ff:fea4:89ab"],
|
|
||||||
"gps": { "lat": "52.379894", "lon": "4.901608", "fix": 0 },
|
|
||||||
"oob": false,
|
|
||||||
"unms": { "status": 0, "timestamp": null },
|
|
||||||
"airview": 2,
|
|
||||||
"service": { "time": 267195, "link": 265996 }
|
|
||||||
},
|
},
|
||||||
"airos_connected": true
|
"rssi": 37,
|
||||||
|
"rx_idx": 8,
|
||||||
|
"rx_nss": 2,
|
||||||
|
"signal": -59,
|
||||||
|
"stats": {
|
||||||
|
"rx_bytes": 206938324814,
|
||||||
|
"rx_packets": 149767200,
|
||||||
|
"rx_pps": 846,
|
||||||
|
"tx_bytes": 5265602739,
|
||||||
|
"tx_packets": 52980390,
|
||||||
|
"tx_pps": 0
|
||||||
|
},
|
||||||
|
"tx_idx": 9,
|
||||||
|
"tx_latency": 0,
|
||||||
|
"tx_lretries": 0,
|
||||||
|
"tx_nss": 2,
|
||||||
|
"tx_packets": 0,
|
||||||
|
"tx_ratedata": [175, 4, 47, 200, 673, 158, 163, 138, 68895, 19577430],
|
||||||
|
"tx_sretries": 0,
|
||||||
|
"ul_avg_linkscore": 88,
|
||||||
|
"ul_capacity_expect": 624000,
|
||||||
|
"ul_linkscore": 86,
|
||||||
|
"ul_rate_expect": 8,
|
||||||
|
"ul_signal_expect": -55,
|
||||||
|
"uptime": 170281
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sta_disconnected": []
|
"sta_disconnected": [],
|
||||||
},
|
"throughput": {
|
||||||
"interfaces": [
|
"rx": 9907,
|
||||||
{
|
"tx": 222
|
||||||
"ifname": "eth0",
|
|
||||||
"hwaddr": "01:23:45:67:89:AB",
|
|
||||||
"enabled": true,
|
|
||||||
"mtu": 1500,
|
|
||||||
"status": {
|
|
||||||
"plugged": true,
|
|
||||||
"tx_bytes": 209900085624,
|
|
||||||
"rx_bytes": 3984971949,
|
|
||||||
"tx_packets": 185866883,
|
|
||||||
"rx_packets": 73564835,
|
|
||||||
"tx_errors": 0,
|
|
||||||
"rx_errors": 4,
|
|
||||||
"tx_dropped": 10,
|
|
||||||
"rx_dropped": 0,
|
|
||||||
"ipaddr": "0.0.0.0",
|
|
||||||
"speed": 1000,
|
|
||||||
"duplex": true,
|
|
||||||
"snr": [30, 30, 30, 30],
|
|
||||||
"cable_len": 18,
|
|
||||||
"ip6addr": null
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
"tx_chainmask": 3,
|
||||||
"ifname": "ath0",
|
"tx_idx": 9,
|
||||||
"hwaddr": "01:23:45:67:89:AB",
|
"tx_nss": 2,
|
||||||
"enabled": true,
|
"txpower": -3
|
||||||
"mtu": 1500,
|
}
|
||||||
"status": {
|
|
||||||
"plugged": false,
|
|
||||||
"tx_bytes": 5265602738,
|
|
||||||
"rx_bytes": 206938324766,
|
|
||||||
"tx_packets": 52980390,
|
|
||||||
"rx_packets": 149767200,
|
|
||||||
"tx_errors": 0,
|
|
||||||
"rx_errors": 0,
|
|
||||||
"tx_dropped": 2005,
|
|
||||||
"rx_dropped": 0,
|
|
||||||
"ipaddr": "0.0.0.0",
|
|
||||||
"speed": 0,
|
|
||||||
"duplex": false,
|
|
||||||
"snr": null,
|
|
||||||
"cable_len": null,
|
|
||||||
"ip6addr": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ifname": "br0",
|
|
||||||
"hwaddr": "01:23:45:67:89:AB",
|
|
||||||
"enabled": true,
|
|
||||||
"mtu": 1500,
|
|
||||||
"status": {
|
|
||||||
"plugged": true,
|
|
||||||
"tx_bytes": 236295176,
|
|
||||||
"rx_bytes": 204802727,
|
|
||||||
"tx_packets": 298119,
|
|
||||||
"rx_packets": 1791592,
|
|
||||||
"tx_errors": 0,
|
|
||||||
"rx_errors": 0,
|
|
||||||
"tx_dropped": 0,
|
|
||||||
"rx_dropped": 0,
|
|
||||||
"ipaddr": "192.168.1.2",
|
|
||||||
"speed": 0,
|
|
||||||
"duplex": false,
|
|
||||||
"snr": null,
|
|
||||||
"cable_len": null,
|
|
||||||
"ip6addr": [{ "addr": "fe80::eea:14ff:fea4:89cd", "plen": 64 }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"provmode": {},
|
|
||||||
"ntpclient": {},
|
|
||||||
"unms": { "status": 0, "timestamp": null },
|
|
||||||
"gps": { "lat": 52.379894, "lon": 4.901608, "fix": 0 },
|
|
||||||
"derived": { "mac": "01:23:45:67:89:AB", "mac_interface": "br0" }
|
|
||||||
}
|
}
|
@@ -13,8 +13,12 @@
|
|||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
'derived': dict({
|
'derived': dict({
|
||||||
|
'access_point': True,
|
||||||
'mac': '**REDACTED**',
|
'mac': '**REDACTED**',
|
||||||
'mac_interface': 'br0',
|
'mac_interface': 'br0',
|
||||||
|
'ptmp': False,
|
||||||
|
'ptp': True,
|
||||||
|
'station': False,
|
||||||
}),
|
}),
|
||||||
'firewall': dict({
|
'firewall': dict({
|
||||||
'eb6tables': False,
|
'eb6tables': False,
|
||||||
@@ -164,6 +168,7 @@
|
|||||||
'dl_capacity': 647400,
|
'dl_capacity': 647400,
|
||||||
'ff_cap_rep': False,
|
'ff_cap_rep': False,
|
||||||
'fixed_frame': False,
|
'fixed_frame': False,
|
||||||
|
'flex_mode': None,
|
||||||
'gps_sync': False,
|
'gps_sync': False,
|
||||||
'rx_use': 42,
|
'rx_use': 42,
|
||||||
'tx_use': 6,
|
'tx_use': 6,
|
||||||
@@ -515,9 +520,14 @@
|
|||||||
]),
|
]),
|
||||||
'freeram': 14290944,
|
'freeram': 14290944,
|
||||||
'gps': dict({
|
'gps': dict({
|
||||||
|
'alt': None,
|
||||||
|
'dim': None,
|
||||||
|
'dop': None,
|
||||||
'fix': 0,
|
'fix': 0,
|
||||||
'lat': '**REDACTED**',
|
'lat': '**REDACTED**',
|
||||||
'lon': '**REDACTED**',
|
'lon': '**REDACTED**',
|
||||||
|
'sats': None,
|
||||||
|
'time_synced': None,
|
||||||
}),
|
}),
|
||||||
'height': 2,
|
'height': 2,
|
||||||
'hostname': '**REDACTED**',
|
'hostname': '**REDACTED**',
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
@@ -395,6 +396,15 @@ async def test_status_with_deprecated_battery_feature(
|
|||||||
assert issue.issue_domain == "vacuum"
|
assert issue.issue_domain == "vacuum"
|
||||||
assert issue.translation_key == "deprecated_vacuum_battery_feature"
|
assert issue.translation_key == "deprecated_vacuum_battery_feature"
|
||||||
assert issue.translation_placeholders == {"entity_id": "vacuum.mqtttest"}
|
assert issue.translation_placeholders == {"entity_id": "vacuum.mqtttest"}
|
||||||
|
assert not [
|
||||||
|
record
|
||||||
|
for record in caplog.records
|
||||||
|
if record.name == "homeassistant.helpers.frame"
|
||||||
|
and record.levelno >= logging.WARNING
|
||||||
|
]
|
||||||
|
assert (
|
||||||
|
"mqtt' is setting the battery_level which has been deprecated"
|
||||||
|
) not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@@ -94,7 +94,7 @@ def mock_config_entry_with_reasoning_model(
|
|||||||
hass.config_entries.async_update_subentry(
|
hass.config_entries.async_update_subentry(
|
||||||
mock_config_entry,
|
mock_config_entry,
|
||||||
next(iter(mock_config_entry.subentries.values())),
|
next(iter(mock_config_entry.subentries.values())),
|
||||||
data={CONF_LLM_HASS_API: llm.LLM_API_ASSIST, CONF_CHAT_MODEL: "o4-mini"},
|
data={CONF_LLM_HASS_API: llm.LLM_API_ASSIST, CONF_CHAT_MODEL: "gpt-5-mini"},
|
||||||
)
|
)
|
||||||
return mock_config_entry
|
return mock_config_entry
|
||||||
|
|
||||||
|
@@ -20,6 +20,7 @@ from homeassistant.components.openai_conversation.const import (
|
|||||||
CONF_RECOMMENDED,
|
CONF_RECOMMENDED,
|
||||||
CONF_TEMPERATURE,
|
CONF_TEMPERATURE,
|
||||||
CONF_TOP_P,
|
CONF_TOP_P,
|
||||||
|
CONF_VERBOSITY,
|
||||||
CONF_WEB_SEARCH,
|
CONF_WEB_SEARCH,
|
||||||
CONF_WEB_SEARCH_CITY,
|
CONF_WEB_SEARCH_CITY,
|
||||||
CONF_WEB_SEARCH_CONTEXT_SIZE,
|
CONF_WEB_SEARCH_CONTEXT_SIZE,
|
||||||
@@ -302,7 +303,7 @@ async def test_form_invalid_auth(hass: HomeAssistant, side_effect, error) -> Non
|
|||||||
(
|
(
|
||||||
{
|
{
|
||||||
CONF_RECOMMENDED: False,
|
CONF_RECOMMENDED: False,
|
||||||
CONF_PROMPT: "Speak like a pirate",
|
CONF_PROMPT: "Speak like a pro",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
CONF_TEMPERATURE: 1.0,
|
CONF_TEMPERATURE: 1.0,
|
||||||
@@ -317,7 +318,7 @@ async def test_form_invalid_auth(hass: HomeAssistant, side_effect, error) -> Non
|
|||||||
),
|
),
|
||||||
{
|
{
|
||||||
CONF_RECOMMENDED: False,
|
CONF_RECOMMENDED: False,
|
||||||
CONF_PROMPT: "Speak like a pirate",
|
CONF_PROMPT: "Speak like a pro",
|
||||||
CONF_TEMPERATURE: 1.0,
|
CONF_TEMPERATURE: 1.0,
|
||||||
CONF_CHAT_MODEL: "o1-pro",
|
CONF_CHAT_MODEL: "o1-pro",
|
||||||
CONF_TOP_P: RECOMMENDED_TOP_P,
|
CONF_TOP_P: RECOMMENDED_TOP_P,
|
||||||
@@ -414,35 +415,51 @@ async def test_form_invalid_auth(hass: HomeAssistant, side_effect, error) -> Non
|
|||||||
( # Case 2: reasoning model
|
( # Case 2: reasoning model
|
||||||
{
|
{
|
||||||
CONF_RECOMMENDED: False,
|
CONF_RECOMMENDED: False,
|
||||||
CONF_PROMPT: "Speak like a pro",
|
CONF_PROMPT: "Speak like a pirate",
|
||||||
CONF_TEMPERATURE: 0.8,
|
CONF_TEMPERATURE: 0.8,
|
||||||
CONF_CHAT_MODEL: "o1-pro",
|
CONF_CHAT_MODEL: "gpt-5",
|
||||||
CONF_TOP_P: 0.9,
|
CONF_TOP_P: 0.9,
|
||||||
CONF_MAX_TOKENS: 1000,
|
CONF_MAX_TOKENS: 1000,
|
||||||
CONF_REASONING_EFFORT: "high",
|
CONF_REASONING_EFFORT: "low",
|
||||||
|
CONF_VERBOSITY: "high",
|
||||||
|
CONF_CODE_INTERPRETER: False,
|
||||||
|
CONF_WEB_SEARCH: False,
|
||||||
|
CONF_WEB_SEARCH_CONTEXT_SIZE: "low",
|
||||||
|
CONF_WEB_SEARCH_USER_LOCATION: False,
|
||||||
},
|
},
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
CONF_RECOMMENDED: False,
|
CONF_RECOMMENDED: False,
|
||||||
CONF_PROMPT: "Speak like a pro",
|
CONF_PROMPT: "Speak like a pirate",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
CONF_TEMPERATURE: 0.8,
|
CONF_TEMPERATURE: 0.8,
|
||||||
CONF_CHAT_MODEL: "o1-pro",
|
CONF_CHAT_MODEL: "gpt-5",
|
||||||
CONF_TOP_P: 0.9,
|
CONF_TOP_P: 0.9,
|
||||||
CONF_MAX_TOKENS: 1000,
|
CONF_MAX_TOKENS: 1000,
|
||||||
},
|
},
|
||||||
{CONF_REASONING_EFFORT: "high", CONF_CODE_INTERPRETER: False},
|
{
|
||||||
|
CONF_REASONING_EFFORT: "minimal",
|
||||||
|
CONF_CODE_INTERPRETER: False,
|
||||||
|
CONF_VERBOSITY: "high",
|
||||||
|
CONF_WEB_SEARCH: False,
|
||||||
|
CONF_WEB_SEARCH_CONTEXT_SIZE: "low",
|
||||||
|
CONF_WEB_SEARCH_USER_LOCATION: False,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
CONF_RECOMMENDED: False,
|
CONF_RECOMMENDED: False,
|
||||||
CONF_PROMPT: "Speak like a pro",
|
CONF_PROMPT: "Speak like a pirate",
|
||||||
CONF_TEMPERATURE: 0.8,
|
CONF_TEMPERATURE: 0.8,
|
||||||
CONF_CHAT_MODEL: "o1-pro",
|
CONF_CHAT_MODEL: "gpt-5",
|
||||||
CONF_TOP_P: 0.9,
|
CONF_TOP_P: 0.9,
|
||||||
CONF_MAX_TOKENS: 1000,
|
CONF_MAX_TOKENS: 1000,
|
||||||
CONF_REASONING_EFFORT: "high",
|
CONF_REASONING_EFFORT: "minimal",
|
||||||
CONF_CODE_INTERPRETER: False,
|
CONF_CODE_INTERPRETER: False,
|
||||||
|
CONF_VERBOSITY: "high",
|
||||||
|
CONF_WEB_SEARCH: False,
|
||||||
|
CONF_WEB_SEARCH_CONTEXT_SIZE: "low",
|
||||||
|
CONF_WEB_SEARCH_USER_LOCATION: False,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
# Test that old options are removed after reconfiguration
|
# Test that old options are removed after reconfiguration
|
||||||
@@ -482,11 +499,13 @@ async def test_form_invalid_auth(hass: HomeAssistant, side_effect, error) -> Non
|
|||||||
CONF_PROMPT: "Speak like a pirate",
|
CONF_PROMPT: "Speak like a pirate",
|
||||||
CONF_LLM_HASS_API: ["assist"],
|
CONF_LLM_HASS_API: ["assist"],
|
||||||
CONF_TEMPERATURE: 0.8,
|
CONF_TEMPERATURE: 0.8,
|
||||||
CONF_CHAT_MODEL: "gpt-4o",
|
CONF_CHAT_MODEL: "gpt-5",
|
||||||
CONF_TOP_P: 0.9,
|
CONF_TOP_P: 0.9,
|
||||||
CONF_MAX_TOKENS: 1000,
|
CONF_MAX_TOKENS: 1000,
|
||||||
CONF_REASONING_EFFORT: "high",
|
CONF_REASONING_EFFORT: "high",
|
||||||
CONF_CODE_INTERPRETER: True,
|
CONF_CODE_INTERPRETER: True,
|
||||||
|
CONF_VERBOSITY: "low",
|
||||||
|
CONF_WEB_SEARCH: False,
|
||||||
},
|
},
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@@ -550,11 +569,12 @@ async def test_form_invalid_auth(hass: HomeAssistant, side_effect, error) -> Non
|
|||||||
CONF_PROMPT: "Speak like a pirate",
|
CONF_PROMPT: "Speak like a pirate",
|
||||||
CONF_LLM_HASS_API: ["assist"],
|
CONF_LLM_HASS_API: ["assist"],
|
||||||
CONF_TEMPERATURE: 0.8,
|
CONF_TEMPERATURE: 0.8,
|
||||||
CONF_CHAT_MODEL: "o3-mini",
|
CONF_CHAT_MODEL: "o5",
|
||||||
CONF_TOP_P: 0.9,
|
CONF_TOP_P: 0.9,
|
||||||
CONF_MAX_TOKENS: 1000,
|
CONF_MAX_TOKENS: 1000,
|
||||||
CONF_REASONING_EFFORT: "low",
|
CONF_REASONING_EFFORT: "low",
|
||||||
CONF_CODE_INTERPRETER: True,
|
CONF_CODE_INTERPRETER: True,
|
||||||
|
CONF_VERBOSITY: "medium",
|
||||||
},
|
},
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
|
@@ -12,9 +12,9 @@ from tests.common import MockConfigEntry
|
|||||||
|
|
||||||
mock_value_step_user = {
|
mock_value_step_user = {
|
||||||
"title": "1R & 1IN Board",
|
"title": "1R & 1IN Board",
|
||||||
"relay_count": 1,
|
"relays": 1,
|
||||||
"input_count": 1,
|
"inputs": 1,
|
||||||
"is_old": False,
|
"temps": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@@ -882,3 +882,23 @@ def ungroup_speakers(coordinator: MockSoCo, group_member: MockSoCo) -> None:
|
|||||||
)
|
)
|
||||||
coordinator.zoneGroupTopology.subscribe.return_value._callback(event)
|
coordinator.zoneGroupTopology.subscribe.return_value._callback(event)
|
||||||
group_member.zoneGroupTopology.subscribe.return_value._callback(event)
|
group_member.zoneGroupTopology.subscribe.return_value._callback(event)
|
||||||
|
|
||||||
|
|
||||||
|
def create_rendering_control_event(
|
||||||
|
soco: MockSoCo,
|
||||||
|
) -> SonosMockEvent:
|
||||||
|
"""Create a Sonos Event for speaker rendering control."""
|
||||||
|
variables = {
|
||||||
|
"dialog_level": 1,
|
||||||
|
"speech_enhance_enable": 1,
|
||||||
|
"surround_level": 6,
|
||||||
|
"music_surround_level": 4,
|
||||||
|
"audio_delay": 0,
|
||||||
|
"audio_delay_left_rear": 0,
|
||||||
|
"audio_delay_right_rear": 0,
|
||||||
|
"night_mode": 0,
|
||||||
|
"surround_enabled": 1,
|
||||||
|
"surround_mode": 1,
|
||||||
|
"height_channel_level": 1,
|
||||||
|
}
|
||||||
|
return SonosMockEvent(soco, soco.renderingControl, variables)
|
||||||
|
@@ -6,13 +6,18 @@ from unittest.mock import patch
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.sonos.const import DATA_SONOS_DISCOVERY_MANAGER
|
from homeassistant.components.sonos.const import (
|
||||||
|
DATA_SONOS_DISCOVERY_MANAGER,
|
||||||
|
MODEL_SONOS_ARC_ULTRA,
|
||||||
|
)
|
||||||
from homeassistant.components.sonos.switch import (
|
from homeassistant.components.sonos.switch import (
|
||||||
ATTR_DURATION,
|
ATTR_DURATION,
|
||||||
ATTR_ID,
|
ATTR_ID,
|
||||||
ATTR_INCLUDE_LINKED_ZONES,
|
ATTR_INCLUDE_LINKED_ZONES,
|
||||||
ATTR_PLAY_MODE,
|
ATTR_PLAY_MODE,
|
||||||
ATTR_RECURRENCE,
|
ATTR_RECURRENCE,
|
||||||
|
ATTR_SPEECH_ENHANCEMENT,
|
||||||
|
ATTR_SPEECH_ENHANCEMENT_ENABLED,
|
||||||
ATTR_VOLUME,
|
ATTR_VOLUME,
|
||||||
)
|
)
|
||||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||||
@@ -29,7 +34,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from .conftest import MockSoCo, SonosMockEvent
|
from .conftest import MockSoCo, SonosMockEvent, create_rendering_control_event
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
@@ -142,6 +147,49 @@ async def test_switch_attributes(
|
|||||||
assert touch_controls_state.state == STATE_ON
|
assert touch_controls_state.state == STATE_ON
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("model", "attribute"),
|
||||||
|
[
|
||||||
|
("Sonos One SL", ATTR_SPEECH_ENHANCEMENT),
|
||||||
|
(MODEL_SONOS_ARC_ULTRA.lower(), ATTR_SPEECH_ENHANCEMENT_ENABLED),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_switch_speech_enhancement(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
async_setup_sonos,
|
||||||
|
soco: MockSoCo,
|
||||||
|
speaker_info: dict[str, str],
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
model: str,
|
||||||
|
attribute: str,
|
||||||
|
) -> None:
|
||||||
|
"""Tests the speech enhancement switch and attribute substitution for different models."""
|
||||||
|
entity_id = "switch.zone_a_speech_enhancement"
|
||||||
|
speaker_info["model_name"] = model
|
||||||
|
soco.get_speaker_info.return_value = speaker_info
|
||||||
|
setattr(soco, attribute, True)
|
||||||
|
await async_setup_sonos()
|
||||||
|
switch = entity_registry.entities[entity_id]
|
||||||
|
state = hass.states.get(switch.entity_id)
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
event = create_rendering_control_event(soco)
|
||||||
|
event.variables[attribute] = False
|
||||||
|
soco.renderingControl.subscribe.return_value._callback(event)
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
|
state = hass.states.get(switch.entity_id)
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
SWITCH_DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{ATTR_ENTITY_ID: entity_id},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
assert getattr(soco, attribute) is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("service", "expected_result"),
|
("service", "expected_result"),
|
||||||
[
|
[
|
||||||
|
@@ -114,6 +114,7 @@ DEVICE_MOCKS = {
|
|||||||
"kj_CAjWAxBUZt7QZHfz": [
|
"kj_CAjWAxBUZt7QZHfz": [
|
||||||
# https://github.com/home-assistant/core/issues/146023
|
# https://github.com/home-assistant/core/issues/146023
|
||||||
Platform.FAN,
|
Platform.FAN,
|
||||||
|
Platform.SENSOR,
|
||||||
Platform.SWITCH,
|
Platform.SWITCH,
|
||||||
],
|
],
|
||||||
"kj_yrzylxax1qspdgpp": [
|
"kj_yrzylxax1qspdgpp": [
|
||||||
|
@@ -1649,6 +1649,58 @@
|
|||||||
'state': '220.4',
|
'state': '220.4',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[kj_CAjWAxBUZt7QZHfz][sensor.hl400_pm2_5-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.hl400_pm2_5',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'PM2.5',
|
||||||
|
'platform': 'tuya',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'pm25',
|
||||||
|
'unique_id': 'tuya.152027113c6105cce49cpm25',
|
||||||
|
'unit_of_measurement': '',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_platform_setup_and_discovery[kj_CAjWAxBUZt7QZHfz][sensor.hl400_pm2_5-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'HL400 PM2.5',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': '',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.hl400_pm2_5',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '45.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_platform_setup_and_discovery[mcs_7jIGJAymiH8OsFFb][sensor.door_garage_battery-entry]
|
# name: test_platform_setup_and_discovery[mcs_7jIGJAymiH8OsFFb][sensor.door_garage_battery-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
@@ -5,9 +5,10 @@ from __future__ import annotations
|
|||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from uiprotect import NotAuthorized, NvrError, ProtectApiClient
|
from uiprotect import NvrError, ProtectApiClient
|
||||||
from uiprotect.api import DEVICE_UPDATE_INTERVAL
|
from uiprotect.api import DEVICE_UPDATE_INTERVAL
|
||||||
from uiprotect.data import NVR, Bootstrap, CloudAccount, Light
|
from uiprotect.data import NVR, Bootstrap, CloudAccount, Light
|
||||||
|
from uiprotect.exceptions import BadRequest, NotAuthorized
|
||||||
|
|
||||||
from homeassistant.components.unifiprotect.const import (
|
from homeassistant.components.unifiprotect.const import (
|
||||||
AUTH_RETRIES,
|
AUTH_RETRIES,
|
||||||
@@ -414,6 +415,28 @@ async def test_setup_handles_api_key_creation_failure(
|
|||||||
ufp.api.set_api_key.assert_not_called()
|
ufp.api.set_api_key.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("mock_user_can_write_nvr", [True], indirect=True)
|
||||||
|
async def test_setup_handles_api_key_creation_bad_request(
|
||||||
|
hass: HomeAssistant, ufp: MockUFPFixture, mock_user_can_write_nvr: Mock
|
||||||
|
) -> None:
|
||||||
|
"""Test handling of API key creation BadRequest error."""
|
||||||
|
# Setup: API key is not set, user has write permissions, but creation fails with BadRequest
|
||||||
|
ufp.api.is_api_key_set.return_value = False
|
||||||
|
ufp.api.create_api_key = AsyncMock(
|
||||||
|
side_effect=BadRequest("Invalid API key creation request")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should fail with auth error due to API key creation failure
|
||||||
|
await hass.config_entries.async_setup(ufp.entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert ufp.entry.state is ConfigEntryState.SETUP_ERROR
|
||||||
|
|
||||||
|
# Verify API key creation was attempted but set_api_key was not called
|
||||||
|
ufp.api.create_api_key.assert_called_once_with(name="Home Assistant (test home)")
|
||||||
|
ufp.api.set_api_key.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_with_existing_api_key(
|
async def test_setup_with_existing_api_key(
|
||||||
hass: HomeAssistant, ufp: MockUFPFixture
|
hass: HomeAssistant, ufp: MockUFPFixture
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
import logging
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -437,11 +438,13 @@ async def test_vacuum_deprecated_state_does_not_break_state(
|
|||||||
assert state.state == "cleaning"
|
assert state.state == "cleaning"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_as_custom_component")
|
@pytest.mark.parametrize(("is_built_in", "log_warnings"), [(True, 0), (False, 3)])
|
||||||
async def test_vacuum_log_deprecated_battery_properties(
|
async def test_vacuum_log_deprecated_battery_using_properties(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_flow_fixture: None,
|
config_flow_fixture: None,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
is_built_in: bool,
|
||||||
|
log_warnings: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test incorrectly using battery properties logs warning."""
|
"""Test incorrectly using battery properties logs warning."""
|
||||||
|
|
||||||
@@ -449,7 +452,7 @@ async def test_vacuum_log_deprecated_battery_properties(
|
|||||||
"""Mocked vacuum entity."""
|
"""Mocked vacuum entity."""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def activity(self) -> str:
|
def activity(self) -> VacuumActivity:
|
||||||
"""Return the state of the entity."""
|
"""Return the state of the entity."""
|
||||||
return VacuumActivity.CLEANING
|
return VacuumActivity.CLEANING
|
||||||
|
|
||||||
@@ -477,7 +480,7 @@ async def test_vacuum_log_deprecated_battery_properties(
|
|||||||
async_setup_entry=help_async_setup_entry_init,
|
async_setup_entry=help_async_setup_entry_init,
|
||||||
async_unload_entry=help_async_unload_entry,
|
async_unload_entry=help_async_unload_entry,
|
||||||
),
|
),
|
||||||
built_in=False,
|
built_in=is_built_in,
|
||||||
)
|
)
|
||||||
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
|
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
|
||||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
@@ -486,26 +489,27 @@ async def test_vacuum_log_deprecated_battery_properties(
|
|||||||
assert state is not None
|
assert state is not None
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
"Detected that custom integration 'test' is setting the battery_icon which has been deprecated."
|
len([record for record in caplog.records if record.levelno >= logging.WARNING])
|
||||||
" Integration test should implement a sensor instead with a correct device class and link it"
|
== log_warnings
|
||||||
" to the same device. This will stop working in Home Assistant 2026.8,"
|
|
||||||
" please report it to the author of the 'test' custom integration"
|
|
||||||
in caplog.text
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
"Detected that custom integration 'test' is setting the battery_level which has been deprecated."
|
"integration 'test' is setting the battery_icon which has been deprecated."
|
||||||
" Integration test should implement a sensor instead with a correct device class and link it"
|
|
||||||
" to the same device. This will stop working in Home Assistant 2026.8,"
|
|
||||||
" please report it to the author of the 'test' custom integration"
|
|
||||||
in caplog.text
|
in caplog.text
|
||||||
)
|
) != is_built_in
|
||||||
|
assert (
|
||||||
|
"integration 'test' is setting the battery_level which has been deprecated."
|
||||||
|
in caplog.text
|
||||||
|
) != is_built_in
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_as_custom_component")
|
@pytest.mark.parametrize(("is_built_in", "log_warnings"), [(True, 0), (False, 3)])
|
||||||
async def test_vacuum_log_deprecated_battery_properties_using_attr(
|
async def test_vacuum_log_deprecated_battery_using_attr(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_flow_fixture: None,
|
config_flow_fixture: None,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
is_built_in: bool,
|
||||||
|
log_warnings: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test incorrectly using _attr_battery_* attribute does log issue and raise repair."""
|
"""Test incorrectly using _attr_battery_* attribute does log issue and raise repair."""
|
||||||
|
|
||||||
@@ -531,7 +535,7 @@ async def test_vacuum_log_deprecated_battery_properties_using_attr(
|
|||||||
async_setup_entry=help_async_setup_entry_init,
|
async_setup_entry=help_async_setup_entry_init,
|
||||||
async_unload_entry=help_async_unload_entry,
|
async_unload_entry=help_async_unload_entry,
|
||||||
),
|
),
|
||||||
built_in=False,
|
built_in=is_built_in,
|
||||||
)
|
)
|
||||||
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
|
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
|
||||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
@@ -541,47 +545,51 @@ async def test_vacuum_log_deprecated_battery_properties_using_attr(
|
|||||||
entity.start()
|
entity.start()
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
"Detected that custom integration 'test' is setting the battery_level which has been deprecated."
|
len([record for record in caplog.records if record.levelno >= logging.WARNING])
|
||||||
" Integration test should implement a sensor instead with a correct device class and link it to"
|
== log_warnings
|
||||||
" the same device. This will stop working in Home Assistant 2026.8,"
|
|
||||||
" please report it to the author of the 'test' custom integration"
|
|
||||||
in caplog.text
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
"Detected that custom integration 'test' is setting the battery_icon which has been deprecated."
|
"integration 'test' is setting the battery_level which has been deprecated."
|
||||||
" Integration test should implement a sensor instead with a correct device class and link it to"
|
|
||||||
" the same device. This will stop working in Home Assistant 2026.8,"
|
|
||||||
" please report it to the author of the 'test' custom integration"
|
|
||||||
in caplog.text
|
in caplog.text
|
||||||
)
|
) != is_built_in
|
||||||
|
assert (
|
||||||
|
"integration 'test' is setting the battery_icon which has been deprecated."
|
||||||
|
in caplog.text
|
||||||
|
) != is_built_in
|
||||||
|
|
||||||
await async_start(hass, entity.entity_id)
|
await async_start(hass, entity.entity_id)
|
||||||
|
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
|
||||||
await async_start(hass, entity.entity_id)
|
await async_start(hass, entity.entity_id)
|
||||||
|
|
||||||
# Test we only log once
|
# Test we only log once
|
||||||
assert (
|
assert (
|
||||||
"Detected that custom integration 'test' is setting the battery_level which has been deprecated."
|
len([record for record in caplog.records if record.levelno >= logging.WARNING])
|
||||||
not in caplog.text
|
== 0
|
||||||
)
|
|
||||||
assert (
|
|
||||||
"Detected that custom integration 'test' is setting the battery_icon which has been deprecated."
|
|
||||||
not in caplog.text
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_as_custom_component")
|
@pytest.mark.parametrize(("is_built_in", "log_warnings"), [(True, 0), (False, 1)])
|
||||||
async def test_vacuum_log_deprecated_battery_supported_feature(
|
async def test_vacuum_log_deprecated_battery_supported_feature(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_flow_fixture: None,
|
config_flow_fixture: None,
|
||||||
caplog: pytest.LogCaptureFixture,
|
caplog: pytest.LogCaptureFixture,
|
||||||
|
is_built_in: bool,
|
||||||
|
log_warnings: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test incorrectly setting battery supported feature logs warning."""
|
"""Test incorrectly setting battery supported feature logs warning."""
|
||||||
|
|
||||||
entity = MockVacuum(
|
class MockVacuum(StateVacuumEntity):
|
||||||
name="Testing",
|
"""Mock vacuum class."""
|
||||||
entity_id="vacuum.test",
|
|
||||||
)
|
_attr_supported_features = (
|
||||||
|
VacuumEntityFeature.STATE | VacuumEntityFeature.BATTERY
|
||||||
|
)
|
||||||
|
_attr_name = "Testing"
|
||||||
|
|
||||||
|
entity = MockVacuum()
|
||||||
config_entry = MockConfigEntry(domain="test")
|
config_entry = MockConfigEntry(domain="test")
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
@@ -592,7 +600,7 @@ async def test_vacuum_log_deprecated_battery_supported_feature(
|
|||||||
async_setup_entry=help_async_setup_entry_init,
|
async_setup_entry=help_async_setup_entry_init,
|
||||||
async_unload_entry=help_async_unload_entry,
|
async_unload_entry=help_async_unload_entry,
|
||||||
),
|
),
|
||||||
built_in=False,
|
built_in=is_built_in,
|
||||||
)
|
)
|
||||||
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
|
setup_test_component_platform(hass, DOMAIN, [entity], from_config_entry=True)
|
||||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
@@ -601,13 +609,14 @@ async def test_vacuum_log_deprecated_battery_supported_feature(
|
|||||||
assert state is not None
|
assert state is not None
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
"Detected that custom integration 'test' is setting the battery supported feature"
|
len([record for record in caplog.records if record.levelno >= logging.WARNING])
|
||||||
" which has been deprecated. Integration test should remove this as part of migrating"
|
== log_warnings
|
||||||
" the battery level and icon to a sensor. This will stop working in Home Assistant 2026.8"
|
|
||||||
", please report it to the author of the 'test' custom integration"
|
|
||||||
in caplog.text
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
"integration 'test' is setting the battery supported feature" in caplog.text
|
||||||
|
) != is_built_in
|
||||||
|
|
||||||
|
|
||||||
async def test_vacuum_not_log_deprecated_battery_properties_during_init(
|
async def test_vacuum_not_log_deprecated_battery_properties_during_init(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@@ -624,7 +633,7 @@ async def test_vacuum_not_log_deprecated_battery_properties_during_init(
|
|||||||
self._attr_battery_level = 50
|
self._attr_battery_level = 50
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def activity(self) -> str:
|
def activity(self) -> VacuumActivity:
|
||||||
"""Return the state of the entity."""
|
"""Return the state of the entity."""
|
||||||
return VacuumActivity.CLEANING
|
return VacuumActivity.CLEANING
|
||||||
|
|
||||||
@@ -635,6 +644,6 @@ async def test_vacuum_not_log_deprecated_battery_properties_during_init(
|
|||||||
assert entity.battery_level == 50
|
assert entity.battery_level == 50
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
"Detected that custom integration 'test' is setting the battery_level which has been deprecated."
|
len([record for record in caplog.records if record.levelno >= logging.WARNING])
|
||||||
not in caplog.text
|
== 0
|
||||||
)
|
)
|
||||||
|
@@ -20,6 +20,12 @@ _MODEL_SPECIFIC_RESPONSES = {
|
|||||||
"statistics",
|
"statistics",
|
||||||
"vehicle",
|
"vehicle",
|
||||||
],
|
],
|
||||||
|
"xc60_phev_2020": [
|
||||||
|
"energy_capabilities",
|
||||||
|
"energy_state",
|
||||||
|
"statistics",
|
||||||
|
"vehicle",
|
||||||
|
],
|
||||||
"xc90_petrol_2019": ["commands", "statistics", "vehicle"],
|
"xc90_petrol_2019": ["commands", "statistics", "vehicle"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,57 +1,56 @@
|
|||||||
{
|
{
|
||||||
"batteryChargeLevel": {
|
"batteryChargeLevel": {
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"value": 38,
|
"value": 90.0,
|
||||||
"unit": "percentage",
|
"unit": "percentage",
|
||||||
"updatedAt": "2025-07-02T08:51:23Z"
|
"updatedAt": "2025-08-07T14:30:32Z"
|
||||||
},
|
},
|
||||||
"electricRange": {
|
"electricRange": {
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"value": 90,
|
"value": 327,
|
||||||
"unit": "km",
|
"unit": "km",
|
||||||
"updatedAt": "2025-07-02T08:51:23Z"
|
"updatedAt": "2025-08-07T14:30:32Z"
|
||||||
},
|
},
|
||||||
"chargerConnectionStatus": {
|
"chargerConnectionStatus": {
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"value": "DISCONNECTED",
|
"value": "CONNECTED",
|
||||||
"updatedAt": "2025-07-02T08:51:23Z"
|
"updatedAt": "2025-08-07T14:30:32Z"
|
||||||
},
|
},
|
||||||
"chargingStatus": {
|
"chargingStatus": {
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"value": "IDLE",
|
"value": "DONE",
|
||||||
"updatedAt": "2025-07-02T08:51:23Z"
|
"updatedAt": "2025-08-07T14:30:32Z"
|
||||||
},
|
},
|
||||||
"chargingType": {
|
"chargingType": {
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"value": "NONE",
|
"value": "AC",
|
||||||
"updatedAt": "2025-07-02T08:51:23Z"
|
"updatedAt": "2025-08-07T14:30:32Z"
|
||||||
},
|
},
|
||||||
"chargerPowerStatus": {
|
"chargerPowerStatus": {
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"value": "NO_POWER_AVAILABLE",
|
"value": "FAULT",
|
||||||
"updatedAt": "2025-07-02T08:51:23Z"
|
"updatedAt": "2025-08-07T14:30:32Z"
|
||||||
},
|
},
|
||||||
"estimatedChargingTimeToTargetBatteryChargeLevel": {
|
"estimatedChargingTimeToTargetBatteryChargeLevel": {
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"value": 0,
|
"value": 2,
|
||||||
"unit": "minutes",
|
"unit": "minutes",
|
||||||
"updatedAt": "2025-07-02T08:51:23Z"
|
"updatedAt": "2025-08-07T14:30:32Z"
|
||||||
},
|
},
|
||||||
"chargingCurrentLimit": {
|
"chargingCurrentLimit": {
|
||||||
"status": "OK",
|
"status": "ERROR",
|
||||||
"value": 32,
|
"code": "NOT_SUPPORTED",
|
||||||
"unit": "ampere",
|
"message": "Resource is not supported for this vehicle"
|
||||||
"updatedAt": "2024-03-05T08:38:44Z"
|
|
||||||
},
|
},
|
||||||
"targetBatteryChargeLevel": {
|
"targetBatteryChargeLevel": {
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"value": 90,
|
"value": 90,
|
||||||
"unit": "percentage",
|
"unit": "percentage",
|
||||||
"updatedAt": "2024-09-22T09:40:12Z"
|
"updatedAt": "2025-08-07T14:49:50Z"
|
||||||
},
|
},
|
||||||
"chargingPower": {
|
"chargingPower": {
|
||||||
"status": "ERROR",
|
"status": "ERROR",
|
||||||
"code": "PROPERTY_NOT_FOUND",
|
"code": "NOT_SUPPORTED",
|
||||||
"message": "No valid value could be found for the requested property"
|
"message": "Resource is not supported for this vehicle"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,8 +7,8 @@
|
|||||||
},
|
},
|
||||||
"electricRange": {
|
"electricRange": {
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
"value": 220,
|
"value": 150,
|
||||||
"unit": "km",
|
"unit": "mi",
|
||||||
"updatedAt": "2025-07-02T08:51:23Z"
|
"updatedAt": "2025-07-02T08:51:23Z"
|
||||||
},
|
},
|
||||||
"chargerConnectionStatus": {
|
"chargerConnectionStatus": {
|
||||||
|
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"isSupported": true,
|
||||||
|
"batteryChargeLevel": {
|
||||||
|
"isSupported": false
|
||||||
|
},
|
||||||
|
"electricRange": {
|
||||||
|
"isSupported": false
|
||||||
|
},
|
||||||
|
"chargerConnectionStatus": {
|
||||||
|
"isSupported": true
|
||||||
|
},
|
||||||
|
"chargingSystemStatus": {
|
||||||
|
"isSupported": true
|
||||||
|
},
|
||||||
|
"chargingType": {
|
||||||
|
"isSupported": false
|
||||||
|
},
|
||||||
|
"chargerPowerStatus": {
|
||||||
|
"isSupported": false
|
||||||
|
},
|
||||||
|
"estimatedChargingTimeToTargetBatteryChargeLevel": {
|
||||||
|
"isSupported": false
|
||||||
|
},
|
||||||
|
"targetBatteryChargeLevel": {
|
||||||
|
"isSupported": true
|
||||||
|
},
|
||||||
|
"chargingCurrentLimit": {
|
||||||
|
"isSupported": false
|
||||||
|
},
|
||||||
|
"chargingPower": {
|
||||||
|
"isSupported": false
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"batteryChargeLevel": {
|
||||||
|
"status": "ERROR",
|
||||||
|
"code": "NOT_SUPPORTED",
|
||||||
|
"message": "Resource is not supported for this vehicle"
|
||||||
|
},
|
||||||
|
"electricRange": {
|
||||||
|
"status": "ERROR",
|
||||||
|
"code": "NOT_SUPPORTED",
|
||||||
|
"message": "Resource is not supported for this vehicle"
|
||||||
|
},
|
||||||
|
"chargerConnectionStatus": {
|
||||||
|
"status": "OK",
|
||||||
|
"value": "DISCONNECTED",
|
||||||
|
"updatedAt": "2025-08-07T20:29:18Z"
|
||||||
|
},
|
||||||
|
"chargingStatus": {
|
||||||
|
"status": "OK",
|
||||||
|
"value": "IDLE",
|
||||||
|
"updatedAt": "2025-08-07T20:29:18Z"
|
||||||
|
},
|
||||||
|
"chargingType": {
|
||||||
|
"status": "ERROR",
|
||||||
|
"code": "NOT_SUPPORTED",
|
||||||
|
"message": "Resource is not supported for this vehicle"
|
||||||
|
},
|
||||||
|
"chargerPowerStatus": {
|
||||||
|
"status": "ERROR",
|
||||||
|
"code": "NOT_SUPPORTED",
|
||||||
|
"message": "Resource is not supported for this vehicle"
|
||||||
|
},
|
||||||
|
"estimatedChargingTimeToTargetBatteryChargeLevel": {
|
||||||
|
"status": "ERROR",
|
||||||
|
"code": "NOT_SUPPORTED",
|
||||||
|
"message": "Resource is not supported for this vehicle"
|
||||||
|
},
|
||||||
|
"chargingCurrentLimit": {
|
||||||
|
"status": "ERROR",
|
||||||
|
"code": "NOT_SUPPORTED",
|
||||||
|
"message": "Resource is not supported for this vehicle"
|
||||||
|
},
|
||||||
|
"targetBatteryChargeLevel": {
|
||||||
|
"status": "ERROR",
|
||||||
|
"code": "NOT_SUPPORTED",
|
||||||
|
"message": "Resource is not supported for this vehicle"
|
||||||
|
},
|
||||||
|
"chargingPower": {
|
||||||
|
"status": "ERROR",
|
||||||
|
"code": "NOT_SUPPORTED",
|
||||||
|
"message": "Resource is not supported for this vehicle"
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"averageFuelConsumption": {
|
||||||
|
"value": 4.0,
|
||||||
|
"unit": "l/100km",
|
||||||
|
"timestamp": "2025-08-07T20:29:18.343Z"
|
||||||
|
},
|
||||||
|
"averageSpeed": {
|
||||||
|
"value": 65,
|
||||||
|
"unit": "km/h",
|
||||||
|
"timestamp": "2025-08-07T20:29:18.343Z"
|
||||||
|
},
|
||||||
|
"tripMeterManual": {
|
||||||
|
"value": 219.7,
|
||||||
|
"unit": "km",
|
||||||
|
"timestamp": "2025-08-07T20:29:18.343Z"
|
||||||
|
},
|
||||||
|
"tripMeterAutomatic": {
|
||||||
|
"value": 0.0,
|
||||||
|
"unit": "km",
|
||||||
|
"timestamp": "2025-08-07T20:29:18.343Z"
|
||||||
|
},
|
||||||
|
"distanceToEmptyTank": {
|
||||||
|
"value": 920,
|
||||||
|
"unit": "km",
|
||||||
|
"timestamp": "2025-08-07T20:29:18.343Z"
|
||||||
|
},
|
||||||
|
"distanceToEmptyBattery": {
|
||||||
|
"value": 29,
|
||||||
|
"unit": "km",
|
||||||
|
"timestamp": "2025-08-07T20:29:18.343Z"
|
||||||
|
}
|
||||||
|
}
|
17
tests/components/volvo/fixtures/xc60_phev_2020/vehicle.json
Normal file
17
tests/components/volvo/fixtures/xc60_phev_2020/vehicle.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"vin": "YV1ABCDEFG1234567",
|
||||||
|
"modelYear": 2020,
|
||||||
|
"gearbox": "AUTOMATIC",
|
||||||
|
"fuelType": "PETROL/ELECTRIC",
|
||||||
|
"externalColour": "Bright Silver",
|
||||||
|
"batteryCapacityKWH": 11.832,
|
||||||
|
"images": {
|
||||||
|
"exteriorImageUrl": "https://cas.volvocars.com/image/dynamic/MY20_0000/123/exterior-v1/_/default.png?market=se&client=public-api-engineering&angle=1&bg=00000000&w=1920",
|
||||||
|
"internalImageUrl": "https://cas.volvocars.com/image/dynamic/MY20_0000/123/interior-v1/_/default.jpg?market=se&client=public-api-engineering&angle=0&w=1920"
|
||||||
|
},
|
||||||
|
"descriptions": {
|
||||||
|
"model": "XC60",
|
||||||
|
"upholstery": "CHARCOAL/LEABR3/CHARC/SPO",
|
||||||
|
"steering": "LEFT"
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,13 @@ from tests.common import MockConfigEntry, snapshot_platform
|
|||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"full_model",
|
"full_model",
|
||||||
["ex30_2024", "s90_diesel_2018", "xc40_electric_2024", "xc90_petrol_2019"],
|
[
|
||||||
|
"ex30_2024",
|
||||||
|
"s90_diesel_2018",
|
||||||
|
"xc40_electric_2024",
|
||||||
|
"xc60_phev_2020",
|
||||||
|
"xc90_petrol_2019",
|
||||||
|
],
|
||||||
)
|
)
|
||||||
async def test_sensor(
|
async def test_sensor(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@@ -30,3 +36,36 @@ async def test_sensor(
|
|||||||
assert await setup_integration()
|
assert await setup_integration()
|
||||||
|
|
||||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"full_model",
|
||||||
|
["xc40_electric_2024"],
|
||||||
|
)
|
||||||
|
async def test_distance_to_empty_battery(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
setup_integration: Callable[[], Awaitable[bool]],
|
||||||
|
) -> None:
|
||||||
|
"""Test using `distanceToEmptyBattery` instead of `electricRange`."""
|
||||||
|
|
||||||
|
with patch("homeassistant.components.volvo.PLATFORMS", [Platform.SENSOR]):
|
||||||
|
assert await setup_integration()
|
||||||
|
|
||||||
|
assert hass.states.get("sensor.volvo_xc40_distance_to_empty_battery").state == "250"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("full_model", "short_model"),
|
||||||
|
[("ex30_2024", "ex30"), ("xc60_phev_2020", "xc60")],
|
||||||
|
)
|
||||||
|
async def test_skip_invalid_api_fields(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
setup_integration: Callable[[], Awaitable[bool]],
|
||||||
|
short_model: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test if invalid values are not creating a sensor."""
|
||||||
|
|
||||||
|
with patch("homeassistant.components.volvo.PLATFORMS", [Platform.SENSOR]):
|
||||||
|
assert await setup_integration()
|
||||||
|
|
||||||
|
assert not hass.states.get(f"sensor.volvo_{short_model}_charging_power")
|
||||||
|
@@ -36,6 +36,7 @@
|
|||||||
}),
|
}),
|
||||||
'network_key': '**REDACTED**',
|
'network_key': '**REDACTED**',
|
||||||
'nwk_addresses': dict({
|
'nwk_addresses': dict({
|
||||||
|
'11:22:33:44:55:66:77:88': 4660,
|
||||||
}),
|
}),
|
||||||
'nwk_manager_id': 0,
|
'nwk_manager_id': 0,
|
||||||
'nwk_update_id': 0,
|
'nwk_update_id': 0,
|
||||||
|
@@ -6,6 +6,7 @@ import pytest
|
|||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
from syrupy.filters import props
|
from syrupy.filters import props
|
||||||
from zigpy.profiles import zha
|
from zigpy.profiles import zha
|
||||||
|
from zigpy.types import EUI64, NWK
|
||||||
from zigpy.zcl.clusters import security
|
from zigpy.zcl.clusters import security
|
||||||
|
|
||||||
from homeassistant.components.zha.helpers import (
|
from homeassistant.components.zha.helpers import (
|
||||||
@@ -71,6 +72,10 @@ async def test_diagnostics_for_config_entry(
|
|||||||
|
|
||||||
gateway.application_controller.energy_scan.side_effect = None
|
gateway.application_controller.energy_scan.side_effect = None
|
||||||
gateway.application_controller.energy_scan.return_value = scan
|
gateway.application_controller.energy_scan.return_value = scan
|
||||||
|
gateway.application_controller.state.network_info.nwk_addresses = {
|
||||||
|
EUI64.convert("11:22:33:44:55:66:77:88"): NWK(0x1234)
|
||||||
|
}
|
||||||
|
|
||||||
diagnostics_data = await get_diagnostics_for_config_entry(
|
diagnostics_data = await get_diagnostics_for_config_entry(
|
||||||
hass, hass_client, config_entry
|
hass, hass_client, config_entry
|
||||||
)
|
)
|
||||||
|
@@ -28,7 +28,13 @@ from homeassistant.components.zwave_js.discovery_data_template import (
|
|||||||
DynamicCurrentTempClimateDataTemplate,
|
DynamicCurrentTempClimateDataTemplate,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY
|
from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_UNKNOWN, EntityCategory
|
from homeassistant.const import (
|
||||||
|
ATTR_ENTITY_ID,
|
||||||
|
STATE_OFF,
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
EntityCategory,
|
||||||
|
Platform,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
@@ -253,6 +259,7 @@ async def test_merten_507801_disabled_enitites(
|
|||||||
assert updated_entry.disabled is False
|
assert updated_entry.disabled is False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("platforms", [[Platform.BUTTON, Platform.NUMBER]])
|
||||||
async def test_zooz_zen72(
|
async def test_zooz_zen72(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
@@ -324,6 +331,9 @@ async def test_zooz_zen72(
|
|||||||
assert args["value"] is True
|
assert args["value"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"platforms", [[Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH]]
|
||||||
|
)
|
||||||
async def test_indicator_test(
|
async def test_indicator_test(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
device_registry: dr.DeviceRegistry,
|
device_registry: dr.DeviceRegistry,
|
||||||
|
@@ -167,7 +167,7 @@ async def test_update_entity_states(
|
|||||||
|
|
||||||
client.async_send_command.return_value = {"updates": []}
|
client.async_send_command.return_value = {"updates": []}
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15, days=1))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
@@ -186,7 +186,7 @@ async def test_update_entity_states(
|
|||||||
|
|
||||||
client.async_send_command.return_value = FIRMWARE_UPDATES
|
client.async_send_command.return_value = FIRMWARE_UPDATES
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=2))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15, days=2))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
@@ -224,7 +224,7 @@ async def test_update_entity_states(
|
|||||||
|
|
||||||
client.async_send_command.return_value = {"updates": []}
|
client.async_send_command.return_value = {"updates": []}
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=3))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15, days=3))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
@@ -246,7 +246,7 @@ async def test_update_entity_install_raises(
|
|||||||
"""Test update entity install raises exception."""
|
"""Test update entity install raises exception."""
|
||||||
client.async_send_command.return_value = FIRMWARE_UPDATES
|
client.async_send_command.return_value = FIRMWARE_UPDATES
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15, days=1))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# Test failed installation by driver
|
# Test failed installation by driver
|
||||||
@@ -279,7 +279,7 @@ async def test_update_entity_sleep(
|
|||||||
|
|
||||||
client.async_send_command.return_value = FIRMWARE_UPDATES
|
client.async_send_command.return_value = FIRMWARE_UPDATES
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15, days=1))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# Two nodes in total, the controller node and the zen_31 node.
|
# Two nodes in total, the controller node and the zen_31 node.
|
||||||
@@ -324,7 +324,7 @@ async def test_update_entity_dead(
|
|||||||
|
|
||||||
client.async_send_command.return_value = FIRMWARE_UPDATES
|
client.async_send_command.return_value = FIRMWARE_UPDATES
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15, days=1))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# Two nodes in total, the controller node and the zen_31 node.
|
# Two nodes in total, the controller node and the zen_31 node.
|
||||||
@@ -368,14 +368,14 @@ async def test_update_entity_ha_not_running(
|
|||||||
# Update should be delayed by a day because Home Assistant is not running
|
# Update should be delayed by a day because Home Assistant is not running
|
||||||
hass.set_state(CoreState.starting)
|
hass.set_state(CoreState.starting)
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert client.async_send_command.call_count == 0
|
assert client.async_send_command.call_count == 0
|
||||||
|
|
||||||
hass.set_state(CoreState.running)
|
hass.set_state(CoreState.running)
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15, days=1))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# Two nodes in total, the controller node and the zen_31 node.
|
# Two nodes in total, the controller node and the zen_31 node.
|
||||||
@@ -401,7 +401,7 @@ async def test_update_entity_update_failure(
|
|||||||
assert client.async_send_command.call_count == 0
|
assert client.async_send_command.call_count == 0
|
||||||
client.async_send_command.side_effect = FailedZWaveCommand("test", 260, "test")
|
client.async_send_command.side_effect = FailedZWaveCommand("test", 260, "test")
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15, days=1))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
entity_ids = (CONTROLLER_UPDATE_ENTITY, NODE_UPDATE_ENTITY)
|
entity_ids = (CONTROLLER_UPDATE_ENTITY, NODE_UPDATE_ENTITY)
|
||||||
@@ -509,7 +509,7 @@ async def test_update_entity_progress(
|
|||||||
client.async_send_command.return_value = FIRMWARE_UPDATES
|
client.async_send_command.return_value = FIRMWARE_UPDATES
|
||||||
driver = client.driver
|
driver = client.driver
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15, days=1))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
@@ -657,7 +657,7 @@ async def test_update_entity_install_failed(
|
|||||||
driver = client.driver
|
driver = client.driver
|
||||||
client.async_send_command.return_value = FIRMWARE_UPDATES
|
client.async_send_command.return_value = FIRMWARE_UPDATES
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15, days=1))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
@@ -733,7 +733,7 @@ async def test_update_entity_reload(
|
|||||||
|
|
||||||
client.async_send_command.return_value = {"updates": []}
|
client.async_send_command.return_value = {"updates": []}
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15, days=1))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
@@ -742,7 +742,7 @@ async def test_update_entity_reload(
|
|||||||
|
|
||||||
client.async_send_command.return_value = FIRMWARE_UPDATES
|
client.async_send_command.return_value = FIRMWARE_UPDATES
|
||||||
|
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=2))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15, days=2))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
@@ -774,7 +774,7 @@ async def test_update_entity_reload(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# Trigger another update and make sure the skipped version is still skipped
|
# Trigger another update and make sure the skipped version is still skipped
|
||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=4))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=15, days=4))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
@@ -809,7 +809,7 @@ async def test_update_entity_delay(
|
|||||||
|
|
||||||
assert client.async_send_command.call_count == 0
|
assert client.async_send_command.call_count == 0
|
||||||
|
|
||||||
update_interval = timedelta(minutes=5)
|
update_interval = timedelta(seconds=15)
|
||||||
freezer.tick(update_interval)
|
freezer.tick(update_interval)
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
Reference in New Issue
Block a user