mirror of
https://github.com/home-assistant/core.git
synced 2026-05-23 09:15:45 +02:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 73c9edd3e8 | |||
| 18f30bd97b | |||
| eae6e79b61 | |||
| 5bb42801d9 | |||
| 98271265d3 | |||
| 92d20477bc | |||
| 9352a0057e | |||
| 5fb874277a | |||
| d65f605398 | |||
| 7e5b448f70 | |||
| ef5da5ef36 | |||
| 410f00c4ed | |||
| 33c205dc04 | |||
| 267b3e279d | |||
| 9c1cd8093d | |||
| 201c0c2470 | |||
| 281d6e0e8b | |||
| 88746534a4 | |||
| 135f91c3c5 | |||
| 49d8dc88d9 | |||
| a7a2c1eb02 | |||
| 6596f956d2 | |||
| 9d8859833b | |||
| 65a4c10660 | |||
| 1737b50558 | |||
| 614c7006f6 | |||
| 8c901cc405 | |||
| 5d0fdfd38b |
@@ -11,7 +11,7 @@
|
||||
"service": "mdi:dialpad"
|
||||
},
|
||||
"alarm_toggle_chime": {
|
||||
"service": "mdi:abc"
|
||||
"service": "mdi:bell-ring"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,9 @@
|
||||
"entry_not_loaded": {
|
||||
"message": "Entry not loaded: {entry}"
|
||||
},
|
||||
"invalid_auth": {
|
||||
"message": "Invalid authentication credentials: {error}"
|
||||
},
|
||||
"invalid_device_id": {
|
||||
"message": "Invalid device ID specified: {device_id}"
|
||||
},
|
||||
|
||||
@@ -70,7 +70,6 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
|
||||
raise InvalidAuth(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="cannot_authenticate",
|
||||
translation_placeholders={"error": repr(err)},
|
||||
) from err
|
||||
finally:
|
||||
await api.logout()
|
||||
|
||||
@@ -63,7 +63,7 @@ class CurrencylayerSensor(SensorEntity):
|
||||
"""Implementing the Currencylayer sensor."""
|
||||
|
||||
_attr_attribution = "Data provided by currencylayer.com"
|
||||
_attr_icon = "mdi:currency"
|
||||
_attr_icon = "mdi:currency-usd"
|
||||
|
||||
def __init__(self, rest: CurrencylayerData, base: str, quote: str) -> None:
|
||||
"""Initialize the sensor."""
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/dnsip",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["aiodns==4.0.3"]
|
||||
"requirements": ["aiodns==4.0.4"]
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"service": "mdi:refresh"
|
||||
},
|
||||
"set_dhw_override": {
|
||||
"service": "mdi:water-heater"
|
||||
"service": "mdi:water-boiler"
|
||||
},
|
||||
"set_system_mode": {
|
||||
"service": "mdi:pencil"
|
||||
|
||||
@@ -16,7 +16,7 @@ class DeviceType(Enum):
|
||||
GAME_CONSOLE = "mdi:nintendo-game-boy"
|
||||
STREAMING_DONGLE = "mdi:cast"
|
||||
LOUDSPEAKER = SOUND_SYSTEM = STB = SATELLITE = MUSIC = "mdi:speaker"
|
||||
DISC_PLAYER = "mdi:disk-player"
|
||||
DISC_PLAYER = "mdi:disc-player"
|
||||
REMOTE_CONTROL = "mdi:remote-tv"
|
||||
RADIO = "mdi:radio"
|
||||
PHOTO_CAMERA = PHOTOS = "mdi:camera"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"entity": {
|
||||
"button": {
|
||||
"sync_clock": {
|
||||
"default": "mdi:clock-sync"
|
||||
"default": "mdi:clock-check"
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
|
||||
@@ -808,10 +808,10 @@ async def _create_or_update_task(call: ServiceCall) -> ServiceResponse: # noqa:
|
||||
data["daysOfMonth"] = [start_date.day]
|
||||
data["weeksOfMonth"] = []
|
||||
|
||||
if interval := call.data.get(ATTR_INTERVAL):
|
||||
if (interval := call.data.get(ATTR_INTERVAL)) is not None:
|
||||
data["everyX"] = interval
|
||||
|
||||
if streak := call.data.get(ATTR_STREAK):
|
||||
if (streak := call.data.get(ATTR_STREAK)) is not None:
|
||||
data["streak"] = streak
|
||||
|
||||
try:
|
||||
|
||||
@@ -21,7 +21,9 @@ EXPECTED_ENTRY_VERSION = (
|
||||
@callback
|
||||
def async_info(hass: HomeAssistant) -> list[HardwareInfo]:
|
||||
"""Return board info."""
|
||||
entries = hass.config_entries.async_entries(DOMAIN)
|
||||
entries = hass.config_entries.async_entries(
|
||||
DOMAIN, include_ignore=False, include_disabled=False
|
||||
)
|
||||
return [
|
||||
HardwareInfo(
|
||||
board=None,
|
||||
|
||||
@@ -89,6 +89,8 @@ def async_get_triggers(
|
||||
|
||||
# Get Hue device id from device identifier
|
||||
hue_dev_id = get_hue_device_id(device_entry)
|
||||
if hue_dev_id is None or hue_dev_id not in api.devices:
|
||||
return []
|
||||
# extract triggers from all button resources of this Hue device
|
||||
triggers: list[dict[str, Any]] = []
|
||||
model_id = api.devices[hue_dev_id].product_data.product_name
|
||||
|
||||
@@ -135,6 +135,11 @@ class PowerViewShadeBase(ShadeEntity, CoverEntity):
|
||||
"""
|
||||
return self._is_hard_wired
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if shade position data is available."""
|
||||
return super().available and self.positions.primary is not None
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, str]:
|
||||
"""Return the state attributes."""
|
||||
|
||||
@@ -118,6 +118,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
)
|
||||
device = devices.add_x10_device(housecode, unitcode, x10_type, steps)
|
||||
|
||||
create_insteon_device(hass, devices.modem, entry.entry_id)
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, INSTEON_PLATFORMS)
|
||||
|
||||
for address in devices:
|
||||
@@ -131,8 +133,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
register_new_device_callback(hass)
|
||||
async_setup_services(hass)
|
||||
|
||||
create_insteon_device(hass, devices.modem, entry.entry_id)
|
||||
|
||||
entry.async_create_background_task(
|
||||
hass, async_get_device_config(hass, entry), "insteon-get-device-config"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"erev_shabbat_hag": { "default": "mdi:candle-light" },
|
||||
"erev_shabbat_hag": { "default": "mdi:candle" },
|
||||
"issur_melacha_in_effect": { "default": "mdi:power-plug-off" },
|
||||
"motzei_shabbat_hag": { "default": "mdi:fire" }
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"service": "mdi:lock-open"
|
||||
},
|
||||
"disable": {
|
||||
"service": "mdi:fash-off"
|
||||
"service": "mdi:flash-off"
|
||||
},
|
||||
"enable": {
|
||||
"service": "mdi:flash"
|
||||
|
||||
@@ -28,25 +28,25 @@
|
||||
"ice_maker": {
|
||||
"default": "mdi:cube-outline",
|
||||
"state": {
|
||||
"off": "mdi:cube-outline-off"
|
||||
"off": "mdi:cube-off-outline"
|
||||
}
|
||||
},
|
||||
"ice_maker_bottom_zone": {
|
||||
"default": "mdi:cube-outline",
|
||||
"state": {
|
||||
"off": "mdi:cube-outline-off"
|
||||
"off": "mdi:cube-off-outline"
|
||||
}
|
||||
},
|
||||
"ice_maker_middle_zone": {
|
||||
"default": "mdi:cube-outline",
|
||||
"state": {
|
||||
"off": "mdi:cube-outline-off"
|
||||
"off": "mdi:cube-off-outline"
|
||||
}
|
||||
},
|
||||
"ice_maker_top_zone": {
|
||||
"default": "mdi:cube-outline",
|
||||
"state": {
|
||||
"off": "mdi:cube-outline-off"
|
||||
"off": "mdi:cube-off-outline"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from pylutron_caseta import OCCUPANCY_GROUP_OCCUPIED
|
||||
from pylutron_caseta import OCCUPANCY_GROUP_OCCUPIED, BridgeResponseError
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
@@ -133,7 +133,11 @@ class LutronCasetaBatterySensor(LutronCasetaEntity, BinarySensorEntity):
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Fetch the latest battery status from the bridge."""
|
||||
status = await self._smartbridge.get_battery_status(self.device_id)
|
||||
try:
|
||||
status = await self._smartbridge.get_battery_status(self.device_id)
|
||||
except BridgeResponseError:
|
||||
self._attr_is_on = None
|
||||
return
|
||||
normalized_status = status.strip().casefold() if status else None
|
||||
if normalized_status == BATTERY_STATUS_LOW:
|
||||
self._attr_is_on = True
|
||||
|
||||
@@ -5,8 +5,8 @@ from aiolyric.exceptions import LyricAuthenticationException, LyricException
|
||||
|
||||
DOMAIN = "lyric"
|
||||
|
||||
OAUTH2_AUTHORIZE = "https://api.honeywell.com/oauth2/authorize"
|
||||
OAUTH2_TOKEN = "https://api.honeywell.com/oauth2/token"
|
||||
OAUTH2_AUTHORIZE = "https://api.honeywellhome.com/oauth2/authorize"
|
||||
OAUTH2_TOKEN = "https://api.honeywellhome.com/oauth2/token"
|
||||
|
||||
PRESET_NO_HOLD = "NoHold"
|
||||
PRESET_TEMPORARY_HOLD = "TemporaryHold"
|
||||
|
||||
@@ -22,5 +22,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aiolyric"],
|
||||
"requirements": ["aiolyric==2.0.2"]
|
||||
"requirements": ["aiolyric==2.1.1"]
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
"default": "mdi:home-lightning-bolt"
|
||||
},
|
||||
"eve_weather_trend": {
|
||||
"default": "mdi:weather",
|
||||
"default": "mdi:weather-cloudy",
|
||||
"state": {
|
||||
"cloudy": "mdi:weather-cloudy",
|
||||
"rainy": "mdi:weather-rainy",
|
||||
|
||||
@@ -511,7 +511,9 @@ class DishWasherProgramId(MieleEnum, missing_to_none=True):
|
||||
tall_items = 17, 42
|
||||
glasses_warm = 19
|
||||
quick_intense = 21
|
||||
normal = 30
|
||||
normal = 23, 30
|
||||
pre_wash = 24
|
||||
pot_rests_and_filters = 25
|
||||
power_wash = 44, 204
|
||||
comfort_wash = 203
|
||||
comfort_wash_plus = 209
|
||||
|
||||
@@ -709,6 +709,7 @@
|
||||
"pork_tenderloin_medaillons_4_cm": "Pork tenderloin (medaillons, 4 cm)",
|
||||
"pork_tenderloin_medaillons_5_cm": "Pork tenderloin (medaillons, 5 cm)",
|
||||
"pork_with_crackling": "Pork with crackling",
|
||||
"pot_rests_and_filters": "Pot rests and filters",
|
||||
"potato_cheese_gratin": "Potato cheese gratin",
|
||||
"potato_dumplings_half_half_boil_in_bag": "Potato dumplings (half/half, boil-in-bag)",
|
||||
"potato_dumplings_half_half_deep_frozen": "Potato dumplings (half/half, deep-frozen)",
|
||||
@@ -751,6 +752,7 @@
|
||||
"powerfresh": "PowerFresh",
|
||||
"prawns": "Prawns",
|
||||
"pre_ironing": "Pre-ironing",
|
||||
"pre_wash": "Pre-wash",
|
||||
"proofing": "Proofing",
|
||||
"prove_15_min": "Prove for 15 min",
|
||||
"prove_30_min": "Prove for 30 min",
|
||||
|
||||
@@ -597,8 +597,8 @@ class OpenAIBaseLLMEntity(Entity):
|
||||
)
|
||||
)
|
||||
|
||||
if "reasoning" not in model_args:
|
||||
# Reasoning models handle this correctly with just a prompt
|
||||
if not model_args["model"].startswith("o"):
|
||||
# o-series models handle this correctly with just a prompt
|
||||
remove_citations = True
|
||||
|
||||
tools.append(web_search)
|
||||
|
||||
@@ -37,11 +37,15 @@ class OpenhomeConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
_LOGGER.debug("async_step_ssdp: Incomplete discovery, ignoring")
|
||||
return self.async_abort(reason="incomplete_discovery")
|
||||
|
||||
_LOGGER.debug(
|
||||
"async_step_ssdp: setting unique id %s", discovery_info.upnp[ATTR_UPNP_UDN]
|
||||
)
|
||||
udn = discovery_info.upnp[ATTR_UPNP_UDN]
|
||||
if isinstance(udn, list):
|
||||
if not udn:
|
||||
return self.async_abort(reason="incomplete_discovery")
|
||||
udn = udn[0]
|
||||
|
||||
await self.async_set_unique_id(discovery_info.upnp[ATTR_UPNP_UDN])
|
||||
_LOGGER.debug("async_step_ssdp: setting unique id %s", udn)
|
||||
|
||||
await self.async_set_unique_id(udn)
|
||||
self._abort_if_unique_id_configured({CONF_HOST: discovery_info.ssdp_location})
|
||||
|
||||
_LOGGER.debug(
|
||||
|
||||
@@ -29,29 +29,29 @@
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"translation_key_0": {
|
||||
"default": "mdi:abc"
|
||||
"flow_sensor_clicks_cubic_meter": {
|
||||
"default": "mdi:water-pump"
|
||||
},
|
||||
"translation_key_1": {
|
||||
"default": "mdi:abc"
|
||||
"flow_sensor_consumed_liters": {
|
||||
"default": "mdi:water-pump"
|
||||
},
|
||||
"translation_key_2": {
|
||||
"default": "mdi:abc"
|
||||
"flow_sensor_leak_clicks": {
|
||||
"default": "mdi:pipe-leak"
|
||||
},
|
||||
"translation_key_3": {
|
||||
"default": "mdi:abc"
|
||||
"flow_sensor_leak_volume": {
|
||||
"default": "mdi:pipe-leak"
|
||||
},
|
||||
"translation_key_4": {
|
||||
"default": "mdi:abc"
|
||||
"flow_sensor_start_index": {
|
||||
"default": "mdi:water-pump"
|
||||
},
|
||||
"translation_key_5": {
|
||||
"default": "mdi:abc"
|
||||
"flow_sensor_watering_clicks": {
|
||||
"default": "mdi:water-pump"
|
||||
},
|
||||
"translation_key_6": {
|
||||
"default": "mdi:abc"
|
||||
"last_leak_detected": {
|
||||
"default": "mdi:pipe-leak"
|
||||
},
|
||||
"translation_key_7": {
|
||||
"default": "mdi:abc"
|
||||
"rain_sensor_rain_start": {
|
||||
"default": "mdi:weather-pouring"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["renault_api"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["renault-api==0.5.8"]
|
||||
"requirements": ["renault-api==0.5.10"]
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"loggers": ["roborock"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": [
|
||||
"python-roborock==5.5.1",
|
||||
"python-roborock==5.12.0",
|
||||
"vacuum-map-parser-roborock==0.1.4"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import contextlib
|
||||
from dataclasses import dataclass
|
||||
import datetime
|
||||
import hashlib
|
||||
@@ -40,6 +41,15 @@ from .utils import get_device_entry_gen
|
||||
CONTENT_TYPE_AUDIO = "audio"
|
||||
CONTENT_TYPE_RADIO = "radio"
|
||||
|
||||
ALLOWED_IMAGE_MIME_TYPES: Final = frozenset(
|
||||
{
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
}
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@@ -104,6 +114,9 @@ class ShellyRpcMediaPlayer(ShellyRpcAttributeEntity, MediaPlayerEntity):
|
||||
_last_media_position: int | None = None
|
||||
_last_media_position_updated_at: datetime.datetime | None = None
|
||||
|
||||
_cached_thumb: str | None = None
|
||||
_cached_thumb_result: tuple[bytes, str] | None = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ShellyRpcCoordinator,
|
||||
@@ -217,9 +230,11 @@ class ShellyRpcMediaPlayer(ShellyRpcAttributeEntity, MediaPlayerEntity):
|
||||
@property
|
||||
def media_image_hash(self) -> str | None:
|
||||
"""Hash value for media image."""
|
||||
if (thumb := self._media_meta.get("thumb")) and thumb.startswith("data"):
|
||||
return hashlib.sha256(thumb.encode("utf-8")).hexdigest()[:16]
|
||||
return super().media_image_hash
|
||||
thumb = self._media_meta.get("thumb")
|
||||
if not thumb or self._decode_image_data(thumb) is None:
|
||||
return super().media_image_hash
|
||||
|
||||
return hashlib.sha256(thumb.encode("utf-8")).hexdigest()[:16]
|
||||
|
||||
def _get_updated_media_position(self) -> int | None:
|
||||
"""Return the current playback position and update its timestamp."""
|
||||
@@ -237,15 +252,11 @@ class ShellyRpcMediaPlayer(ShellyRpcAttributeEntity, MediaPlayerEntity):
|
||||
|
||||
async def async_get_media_image(self) -> tuple[bytes | None, str | None]:
|
||||
"""Fetch media image of current playing track."""
|
||||
thumb = self._media_meta["thumb"]
|
||||
try:
|
||||
prefix, image_data = thumb.split(",", 1)
|
||||
image = base64.b64decode(image_data, validate=True)
|
||||
mime = prefix.split(";", 1)[0].rsplit(":", 1)[-1]
|
||||
except binascii.Error, ValueError:
|
||||
thumb = self._media_meta.get("thumb")
|
||||
if not thumb or (result := self._decode_image_data(thumb)) is None:
|
||||
return await super().async_get_media_image()
|
||||
|
||||
return image, mime
|
||||
return result
|
||||
|
||||
@rpc_call
|
||||
async def async_media_play(self) -> None:
|
||||
@@ -436,3 +447,25 @@ class ShellyRpcMediaPlayer(ShellyRpcAttributeEntity, MediaPlayerEntity):
|
||||
translation_key="unsupported_media_type",
|
||||
translation_placeholders={"media_type": str(media_type)},
|
||||
)
|
||||
|
||||
def _decode_image_data(self, thumb: str) -> tuple[bytes, str] | None:
|
||||
"""Return image_bytes and mime_type for a valid image data or None."""
|
||||
if thumb == self._cached_thumb:
|
||||
return self._cached_thumb_result
|
||||
|
||||
result: tuple[bytes, str] | None = None
|
||||
if thumb.startswith("data"):
|
||||
try:
|
||||
prefix, image_data = thumb.split(",", 1)
|
||||
mime = prefix.split(";", 1)[0].rsplit(":", 1)[-1]
|
||||
except IndexError, ValueError:
|
||||
pass
|
||||
else:
|
||||
if mime in ALLOWED_IMAGE_MIME_TYPES:
|
||||
with contextlib.suppress(binascii.Error):
|
||||
result = base64.b64decode(image_data, validate=True), mime
|
||||
|
||||
self._cached_thumb = thumb
|
||||
self._cached_thumb_result = result
|
||||
|
||||
return result
|
||||
|
||||
@@ -396,7 +396,7 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
key=Attribute.COMPLETION_TIME,
|
||||
translation_key="completion_time",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=dt_util.parse_datetime,
|
||||
value_fn=lambda value: dt_util.parse_datetime(value) if value else None,
|
||||
)
|
||||
],
|
||||
},
|
||||
@@ -449,7 +449,7 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
key=Attribute.COMPLETION_TIME,
|
||||
translation_key="completion_time",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=dt_util.parse_datetime,
|
||||
value_fn=lambda value: dt_util.parse_datetime(value) if value else None,
|
||||
)
|
||||
],
|
||||
},
|
||||
@@ -567,7 +567,7 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
key=Attribute.GAS_METER_TIME,
|
||||
translation_key="gas_meter_time",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=dt_util.parse_datetime,
|
||||
value_fn=lambda value: dt_util.parse_datetime(value) if value else None,
|
||||
)
|
||||
],
|
||||
Attribute.GAS_METER_VOLUME: [
|
||||
@@ -728,7 +728,7 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
key=Attribute.COMPLETION_TIME,
|
||||
translation_key="completion_time",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=dt_util.parse_datetime,
|
||||
value_fn=lambda value: dt_util.parse_datetime(value) if value else None,
|
||||
component_fn=lambda component: component == "cavity-01",
|
||||
component_translation_key={
|
||||
"cavity-01": "oven_completion_time_cavity_01",
|
||||
@@ -1198,7 +1198,7 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
key=Attribute.COMPLETION_TIME,
|
||||
translation_key="completion_time",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=dt_util.parse_datetime,
|
||||
value_fn=lambda value: dt_util.parse_datetime(value) if value else None,
|
||||
component_fn=lambda component: component == "sub",
|
||||
component_translation_key={
|
||||
"sub": "washer_sub_completion_time",
|
||||
|
||||
@@ -145,7 +145,7 @@
|
||||
"config_entry_not_ready": {
|
||||
"message": "Error while loading the config entry."
|
||||
},
|
||||
"update_error": {
|
||||
"update_failed": {
|
||||
"message": "Error while updating data from the API."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,7 +425,7 @@ async def async_setup_entry(
|
||||
),
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
description_placeholders={
|
||||
"syntax_keys_documentation_url": "http://robotjs.io/docs/syntax#keys"
|
||||
"syntax_keys_documentation_url": "https://robotjs.dev/docs/syntax#keys"
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ class TedeeApiCoordinator(DataUpdateCoordinator[dict[int, TedeeLock]]):
|
||||
except TedeeLocalAuthException as ex:
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="authentification_failed",
|
||||
translation_key="authentication_failed",
|
||||
) from ex
|
||||
|
||||
except TedeeDataUpdateException as ex:
|
||||
|
||||
@@ -497,7 +497,7 @@
|
||||
"default": "mdi:battery-clock"
|
||||
},
|
||||
"forward_collision_warning": {
|
||||
"default": "mdi:car-crash",
|
||||
"default": "mdi:car-emergency",
|
||||
"state": {
|
||||
"average": "mdi:alert-circle",
|
||||
"early": "mdi:alert-octagon",
|
||||
@@ -634,7 +634,7 @@
|
||||
"default": "mdi:key"
|
||||
},
|
||||
"pedal_position": {
|
||||
"default": "mdi:pedestal"
|
||||
"default": "mdi:gauge"
|
||||
},
|
||||
"powershare_hours_left": {
|
||||
"default": "mdi:clock-time-eight-outline"
|
||||
@@ -794,7 +794,7 @@
|
||||
"service": "mdi:calendar-plus"
|
||||
},
|
||||
"add_precondition_schedule": {
|
||||
"service": "mdi:hvac-outline"
|
||||
"service": "mdi:hvac"
|
||||
},
|
||||
"navigation_gps_request": {
|
||||
"service": "mdi:crosshairs-gps"
|
||||
@@ -803,7 +803,7 @@
|
||||
"service": "mdi:calendar-minus"
|
||||
},
|
||||
"remove_precondition_schedule": {
|
||||
"service": "mdi:hvac-off-outline"
|
||||
"service": "mdi:hvac-off"
|
||||
},
|
||||
"set_scheduled_charging": {
|
||||
"service": "mdi:timeline-clock-outline"
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
"state": {
|
||||
"lightning": "mdi:weather-lightning-rainy",
|
||||
"rain": "mdi:weather-rainy",
|
||||
"rain_snow": "mdi:weather-snoy-rainy",
|
||||
"rain_snow": "mdi:weather-snowy-rainy",
|
||||
"snow": "mdi:weather-snowy"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["wled==0.22.0"],
|
||||
"requirements": ["wled==0.23.0"],
|
||||
"zeroconf": ["_wled._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ class WyomingAssistSatellite(WyomingSatelliteEntity, AssistSatelliteEntity):
|
||||
return
|
||||
|
||||
if event.type == assist_pipeline.PipelineEventType.RUN_START:
|
||||
if event.data and (tts_output := event.data["tts_output"]):
|
||||
if event.data and (tts_output := event.data.get("tts_output")):
|
||||
# Get stream token early.
|
||||
# If "tts_start_streaming" is True in INTENT_PROGRESS event, we
|
||||
# can start streaming TTS before the TTS_END event.
|
||||
|
||||
@@ -310,11 +310,8 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
||||
config_entry.minor_version,
|
||||
)
|
||||
|
||||
if (config_entry.version, config_entry.minor_version) > (
|
||||
ZhaConfigFlowHandler.VERSION,
|
||||
ZhaConfigFlowHandler.MINOR_VERSION,
|
||||
):
|
||||
# This means the user has downgraded from a future version
|
||||
if config_entry.version > ZhaConfigFlowHandler.VERSION:
|
||||
# This means the user has downgraded from a future major version
|
||||
return False
|
||||
|
||||
if config_entry.version == 1:
|
||||
|
||||
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2026
|
||||
MINOR_VERSION: Final = 5
|
||||
PATCH_VERSION: Final = "3"
|
||||
PATCH_VERSION: Final = "4"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 14, 2)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
aiodhcpwatcher==1.2.1
|
||||
aiodiscover==2.7.1
|
||||
aiodns==4.0.3
|
||||
aiodns==4.0.4
|
||||
aiogithubapi==26.0.0
|
||||
aiohttp-asyncmdnsresolver==0.1.1
|
||||
aiohttp-fast-zlib==0.3.0
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2026.5.3"
|
||||
version = "2026.5.4"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
@@ -23,7 +23,7 @@ classifiers = [
|
||||
]
|
||||
requires-python = ">=3.14.2"
|
||||
dependencies = [
|
||||
"aiodns==4.0.3",
|
||||
"aiodns==4.0.4",
|
||||
# aiogithubapi is needed by frontend; frontend is unconditionally imported at
|
||||
# module level in `bootstrap.py` and its requirements thus need to be in
|
||||
# requirements.txt to ensure they are always installed
|
||||
|
||||
Generated
+1
-1
@@ -3,7 +3,7 @@
|
||||
-c homeassistant/package_constraints.txt
|
||||
|
||||
# Home Assistant Core
|
||||
aiodns==4.0.3
|
||||
aiodns==4.0.4
|
||||
aiogithubapi==26.0.0
|
||||
aiohttp-asyncmdnsresolver==0.1.1
|
||||
aiohttp-fast-zlib==0.3.0
|
||||
|
||||
Generated
+5
-5
@@ -233,7 +233,7 @@ aiodhcpwatcher==1.2.1
|
||||
aiodiscover==2.7.1
|
||||
|
||||
# homeassistant.components.dnsip
|
||||
aiodns==4.0.3
|
||||
aiodns==4.0.4
|
||||
|
||||
# homeassistant.components.eafm
|
||||
aioeafm==0.1.2
|
||||
@@ -321,7 +321,7 @@ aiolifx==1.2.1
|
||||
aiolookin==1.0.0
|
||||
|
||||
# homeassistant.components.lyric
|
||||
aiolyric==2.0.2
|
||||
aiolyric==2.1.1
|
||||
|
||||
# homeassistant.components.mealie
|
||||
aiomealie==1.2.4
|
||||
@@ -2675,7 +2675,7 @@ python-rabbitair==0.0.8
|
||||
python-ripple-api==0.0.3
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==5.5.1
|
||||
python-roborock==5.12.0
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.47
|
||||
@@ -2835,7 +2835,7 @@ refoss-ha==1.2.5
|
||||
regenmaschine==2024.03.0
|
||||
|
||||
# homeassistant.components.renault
|
||||
renault-api==0.5.8
|
||||
renault-api==0.5.10
|
||||
|
||||
# homeassistant.components.renson
|
||||
renson-endura-delta==1.7.2
|
||||
@@ -3328,7 +3328,7 @@ wiim==0.1.2
|
||||
wirelesstagpy==0.8.1
|
||||
|
||||
# homeassistant.components.wled
|
||||
wled==0.22.0
|
||||
wled==0.23.0
|
||||
|
||||
# homeassistant.components.wolflink
|
||||
wolf-comm==0.0.48
|
||||
|
||||
Generated
+5
-5
@@ -224,7 +224,7 @@ aiodhcpwatcher==1.2.1
|
||||
aiodiscover==2.7.1
|
||||
|
||||
# homeassistant.components.dnsip
|
||||
aiodns==4.0.3
|
||||
aiodns==4.0.4
|
||||
|
||||
# homeassistant.components.eafm
|
||||
aioeafm==0.1.2
|
||||
@@ -306,7 +306,7 @@ aiolifx==1.2.1
|
||||
aiolookin==1.0.0
|
||||
|
||||
# homeassistant.components.lyric
|
||||
aiolyric==2.0.2
|
||||
aiolyric==2.1.1
|
||||
|
||||
# homeassistant.components.mealie
|
||||
aiomealie==1.2.4
|
||||
@@ -2280,7 +2280,7 @@ python-qube-heatpump==1.8.0
|
||||
python-rabbitair==0.0.8
|
||||
|
||||
# homeassistant.components.roborock
|
||||
python-roborock==5.5.1
|
||||
python-roborock==5.12.0
|
||||
|
||||
# homeassistant.components.smarttub
|
||||
python-smarttub==0.0.47
|
||||
@@ -2419,7 +2419,7 @@ refoss-ha==1.2.5
|
||||
regenmaschine==2024.03.0
|
||||
|
||||
# homeassistant.components.renault
|
||||
renault-api==0.5.8
|
||||
renault-api==0.5.10
|
||||
|
||||
# homeassistant.components.renson
|
||||
renson-endura-delta==1.7.2
|
||||
@@ -2828,7 +2828,7 @@ wiffi==1.1.2
|
||||
wiim==0.1.2
|
||||
|
||||
# homeassistant.components.wled
|
||||
wled==0.22.0
|
||||
wled==0.23.0
|
||||
|
||||
# homeassistant.components.wolflink
|
||||
wolf-comm==0.0.48
|
||||
|
||||
@@ -285,8 +285,6 @@ FORBIDDEN_PACKAGE_FILES_EXCEPTIONS = {
|
||||
"lacrosse": {"homeassistant": {"pylacrosse"}},
|
||||
# ???
|
||||
"linode": {"homeassistant": {"linode-api"}},
|
||||
# https://github.com/timmo001/aiolyric
|
||||
"lyric": {"homeassistant": {"aiolyric"}},
|
||||
# https://github.com/microBeesTech/pythonSDK/
|
||||
"microbees": {
|
||||
"homeassistant": {"microbeespy"},
|
||||
|
||||
@@ -1766,6 +1766,12 @@ async def test_create_todo(
|
||||
},
|
||||
Task(everyX=5),
|
||||
),
|
||||
(
|
||||
{
|
||||
ATTR_INTERVAL: 0,
|
||||
},
|
||||
Task(everyX=0),
|
||||
),
|
||||
(
|
||||
{
|
||||
ATTR_FREQUENCY: "weekly",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from homeassistant.components.homeassistant_connect_zbt2.const import DOMAIN
|
||||
from homeassistant.components.usb import DOMAIN as USB_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.setup import async_setup_component
|
||||
@@ -65,3 +66,66 @@ async def test_hardware_info(
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
async def test_hardware_info_ignored_entry(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator, addon_store_info
|
||||
) -> None:
|
||||
"""Test ignored discovery entries don't crash hardware info.
|
||||
|
||||
Regression test for https://github.com/home-assistant/core/issues/170270
|
||||
"""
|
||||
assert await async_setup_component(hass, USB_DOMAIN, {})
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED)
|
||||
|
||||
# Setup the normal entry so the hardware platform is loaded
|
||||
normal_entry = MockConfigEntry(
|
||||
data=CONFIG_ENTRY_DATA,
|
||||
domain=DOMAIN,
|
||||
options={},
|
||||
title="Home Assistant Connect ZBT-2",
|
||||
unique_id="normal_1",
|
||||
version=1,
|
||||
minor_version=1,
|
||||
)
|
||||
normal_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(normal_entry.entry_id)
|
||||
|
||||
# Setup an ignored config entry without USB data
|
||||
ignored_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
options={},
|
||||
title="Home Assistant Connect ZBT-2",
|
||||
unique_id="ignored_1",
|
||||
version=1,
|
||||
minor_version=2,
|
||||
source="ignore",
|
||||
)
|
||||
ignored_entry.add_to_hass(hass)
|
||||
assert ignored_entry.state is ConfigEntryState.NOT_LOADED
|
||||
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
await client.send_json({"id": 1, "type": "hardware/info"})
|
||||
msg = await client.receive_json()
|
||||
|
||||
assert msg["id"] == 1
|
||||
assert msg["success"]
|
||||
assert msg["result"] == {
|
||||
"hardware": [
|
||||
{
|
||||
"board": None,
|
||||
"config_entries": [normal_entry.entry_id],
|
||||
"dongle": {
|
||||
"vid": "303A",
|
||||
"pid": "4001",
|
||||
"serial_number": "80B54EEFAE18",
|
||||
"manufacturer": "Nabu Casa",
|
||||
"description": "ZBT-2",
|
||||
},
|
||||
"name": "Home Assistant Connect ZBT-2",
|
||||
"url": "https://support.nabucasa.com/hc/en-us/categories/24734620813469-Home-Assistant-Connect-ZBT-1",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -116,3 +116,30 @@ async def test_get_triggers(
|
||||
]
|
||||
|
||||
assert triggers == unordered(expected_triggers)
|
||||
|
||||
|
||||
async def test_get_triggers_for_removed_device(
|
||||
hass: HomeAssistant,
|
||||
mock_bridge_v2: Mock,
|
||||
v2_resources_test_data: JsonArrayType,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test triggers for a device removed from the bridge.
|
||||
|
||||
Regression test for https://github.com/home-assistant/core/issues/152937
|
||||
"""
|
||||
await mock_bridge_v2.api.load_test_data(v2_resources_test_data)
|
||||
await setup_platform(
|
||||
hass, mock_bridge_v2, [Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||
)
|
||||
|
||||
# Create a device entry with a Hue ID that doesn't exist on the bridge
|
||||
orphaned_device = device_registry.async_get_or_create(
|
||||
config_entry_id=mock_bridge_v2.config_entry.entry_id,
|
||||
identifiers={(hue.DOMAIN, "non-existent-hue-device-id")},
|
||||
)
|
||||
|
||||
triggers = await async_get_device_automations(
|
||||
hass, DeviceAutomationType.TRIGGER, orphaned_device.id
|
||||
)
|
||||
assert triggers == []
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
"""Tests for the Lutron Caseta binary sensors."""
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from pylutron_caseta import BridgeResponseError
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
from homeassistant.components.lutron_caseta.binary_sensor import SCAN_INTERVAL
|
||||
@@ -114,3 +115,32 @@ async def test_battery_sensor_updates_on_schedule(
|
||||
assert unknown_state is not None
|
||||
assert unknown_state.state == STATE_UNKNOWN
|
||||
assert instance.get_battery_status.await_count == 3
|
||||
|
||||
|
||||
async def test_battery_sensor_handles_bridge_response_error(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test battery sensor handles BridgeResponseError gracefully.
|
||||
|
||||
Regression test for https://github.com/home-assistant/core/issues/169965
|
||||
"""
|
||||
instance = MockBridge()
|
||||
|
||||
def factory(*args: Any, **kwargs: Any) -> MockBridge:
|
||||
"""Return the mock bridge instance."""
|
||||
return instance
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.Header.StatusCode = "404 NotFound"
|
||||
instance.get_battery_status = AsyncMock(
|
||||
side_effect=BridgeResponseError(mock_response)
|
||||
)
|
||||
|
||||
await async_setup_integration(hass, factory)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(
|
||||
"binary_sensor.basement_bedroom_basement_bedroom_left_shade_battery"
|
||||
)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
@@ -659,6 +659,52 @@ async def test_web_search(
|
||||
assert mock_create_stream.mock_calls[1][2]["input"][1:] == snapshot
|
||||
|
||||
|
||||
async def test_web_search_remove_citations_gpt5(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream,
|
||||
mock_chat_log: MockChatLog, # noqa: F811
|
||||
) -> None:
|
||||
"""Test that citations are stripped for GPT-5 models with inline_citations disabled."""
|
||||
subentry = next(iter(mock_config_entry.subentries.values()))
|
||||
hass.config_entries.async_update_subentry(
|
||||
mock_config_entry,
|
||||
subentry,
|
||||
data={
|
||||
**subentry.data,
|
||||
CONF_CHAT_MODEL: "gpt-5-mini",
|
||||
CONF_WEB_SEARCH: True,
|
||||
CONF_WEB_SEARCH_INLINE_CITATIONS: False,
|
||||
},
|
||||
)
|
||||
await hass.config_entries.async_reload(mock_config_entry.entry_id)
|
||||
|
||||
message = [
|
||||
"The match ended 0-2",
|
||||
" ([legaseriea.it](https://www.legaseriea.it/))",
|
||||
".",
|
||||
]
|
||||
mock_create_stream.return_value = [
|
||||
(
|
||||
*create_web_search_item(id="ws_A", output_index=0),
|
||||
*create_message_item(id="msg_A", text=message, output_index=1),
|
||||
)
|
||||
]
|
||||
|
||||
result = await conversation.async_converse(
|
||||
hass,
|
||||
"What was the score?",
|
||||
mock_chat_log.conversation_id,
|
||||
Context(),
|
||||
agent_id="conversation.openai_conversation",
|
||||
)
|
||||
|
||||
assert result.response.response_type == intent.IntentResponseType.ACTION_DONE
|
||||
# Citation should be stripped from the response
|
||||
assert result.response.speech["plain"]["speech"] == "The match ended 0-2."
|
||||
|
||||
|
||||
async def test_code_interpreter(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
|
||||
@@ -116,3 +116,54 @@ async def test_host_updated(hass: HomeAssistant) -> None:
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
assert entry.data[CONF_HOST] == MOCK_SSDP_LOCATION
|
||||
|
||||
|
||||
async def test_ssdp_udn_as_list(hass: HomeAssistant) -> None:
|
||||
"""Test SSDP discovery when UDN is a list instead of a string.
|
||||
|
||||
Regression test for https://github.com/home-assistant/core/issues/171837
|
||||
"""
|
||||
list_udn_discovery = SsdpServiceInfo(
|
||||
ssdp_usn="usn",
|
||||
ssdp_st="st",
|
||||
ssdp_location=MOCK_SSDP_LOCATION,
|
||||
upnp={
|
||||
ATTR_UPNP_FRIENDLY_NAME: MOCK_FRIENDLY_NAME,
|
||||
ATTR_UPNP_UDN: [MOCK_UDN, "uuid:other"],
|
||||
},
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={CONF_SOURCE: SOURCE_SSDP},
|
||||
data=list_udn_discovery,
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "confirm"
|
||||
assert result["description_placeholders"] == {CONF_NAME: MOCK_FRIENDLY_NAME}
|
||||
|
||||
result2 = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
assert result2["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result2["title"] == MOCK_FRIENDLY_NAME
|
||||
assert result2["data"] == {CONF_HOST: MOCK_SSDP_LOCATION}
|
||||
|
||||
|
||||
async def test_ssdp_udn_as_empty_list(hass: HomeAssistant) -> None:
|
||||
"""Test SSDP discovery when UDN is an empty list."""
|
||||
empty_udn_discovery = SsdpServiceInfo(
|
||||
ssdp_usn="usn",
|
||||
ssdp_st="st",
|
||||
ssdp_location=MOCK_SSDP_LOCATION,
|
||||
upnp={
|
||||
ATTR_UPNP_FRIENDLY_NAME: MOCK_FRIENDLY_NAME,
|
||||
ATTR_UPNP_UDN: [],
|
||||
},
|
||||
)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={CONF_SOURCE: SOURCE_SSDP},
|
||||
data=empty_udn_discovery,
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "incomplete_discovery"
|
||||
|
||||
@@ -999,106 +999,6 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[captur_fuel][button.reg_captur_fuel_flash_lights-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.reg_captur_fuel_flash_lights',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Flash lights',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Flash lights',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'flash_lights',
|
||||
'unique_id': 'vf1capturfuelvin_flash_lights',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[captur_fuel][button.reg_captur_fuel_flash_lights-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'REG-CAPTUR-FUEL Flash lights',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.reg_captur_fuel_flash_lights',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[captur_fuel][button.reg_captur_fuel_sound_horn-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.reg_captur_fuel_sound_horn',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sound horn',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sound horn',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sound_horn',
|
||||
'unique_id': 'vf1capturfuelvin_sound_horn',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[captur_fuel][button.reg_captur_fuel_sound_horn-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'REG-CAPTUR-FUEL Sound horn',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.reg_captur_fuel_sound_horn',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[captur_fuel][button.reg_captur_fuel_start_air_conditioner-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -1149,106 +1049,6 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[captur_phev][button.reg_captur_phev_flash_lights-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.reg_captur_phev_flash_lights',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Flash lights',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Flash lights',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'flash_lights',
|
||||
'unique_id': 'vf1capturphevvin_flash_lights',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[captur_phev][button.reg_captur_phev_flash_lights-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'REG-CAPTUR_PHEV Flash lights',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.reg_captur_phev_flash_lights',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[captur_phev][button.reg_captur_phev_sound_horn-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.reg_captur_phev_sound_horn',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sound horn',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sound horn',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sound_horn',
|
||||
'unique_id': 'vf1capturphevvin_sound_horn',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[captur_phev][button.reg_captur_phev_sound_horn-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'REG-CAPTUR_PHEV Sound horn',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.reg_captur_phev_sound_horn',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[captur_phev][button.reg_captur_phev_start_air_conditioner-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -1299,106 +1099,6 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[captur_phev][button.reg_captur_phev_start_charge-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.reg_captur_phev_start_charge',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Start charge',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Start charge',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'start_charge',
|
||||
'unique_id': 'vf1capturphevvin_start_charge',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[captur_phev][button.reg_captur_phev_start_charge-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'REG-CAPTUR_PHEV Start charge',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.reg_captur_phev_start_charge',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[captur_phev][button.reg_captur_phev_stop_charge-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.reg_captur_phev_stop_charge',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Stop charge',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Stop charge',
|
||||
'platform': 'renault',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'stop_charge',
|
||||
'unique_id': 'vf1capturphevvin_stop_charge',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[captur_phev][button.reg_captur_phev_stop_charge-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'REG-CAPTUR_PHEV Stop charge',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.reg_captur_phev_stop_charge',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[megane_e_tech][button.reg_meg_0_flash_lights-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
|
||||
@@ -1753,7 +1753,10 @@
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'ok',
|
||||
'no_dustbin_or_filter',
|
||||
'auto_empty_dock_fan_error',
|
||||
'duct_blockage',
|
||||
'auto_empty_dock_voltage_error',
|
||||
'water_empty',
|
||||
'waste_water_tank_full',
|
||||
'maintenance_brush_jammed',
|
||||
@@ -1799,7 +1802,10 @@
|
||||
'friendly_name': 'Roborock S7 2 Dock Dock error',
|
||||
'options': list([
|
||||
'ok',
|
||||
'no_dustbin_or_filter',
|
||||
'auto_empty_dock_fan_error',
|
||||
'duct_blockage',
|
||||
'auto_empty_dock_voltage_error',
|
||||
'water_empty',
|
||||
'waste_water_tank_full',
|
||||
'maintenance_brush_jammed',
|
||||
@@ -2958,7 +2964,10 @@
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'ok',
|
||||
'no_dustbin_or_filter',
|
||||
'auto_empty_dock_fan_error',
|
||||
'duct_blockage',
|
||||
'auto_empty_dock_voltage_error',
|
||||
'water_empty',
|
||||
'waste_water_tank_full',
|
||||
'maintenance_brush_jammed',
|
||||
@@ -3004,7 +3013,10 @@
|
||||
'friendly_name': 'Roborock S7 MaxV Dock Dock error',
|
||||
'options': list([
|
||||
'ok',
|
||||
'no_dustbin_or_filter',
|
||||
'auto_empty_dock_fan_error',
|
||||
'duct_blockage',
|
||||
'auto_empty_dock_voltage_error',
|
||||
'water_empty',
|
||||
'waste_water_tank_full',
|
||||
'maintenance_brush_jammed',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Tests for Shelly media player platform."""
|
||||
|
||||
from copy import deepcopy
|
||||
from http import HTTPStatus
|
||||
from unittest.mock import Mock
|
||||
|
||||
from aioshelly.const import MODEL_WALL_DISPLAY
|
||||
@@ -367,7 +368,7 @@ async def test_get_image_http(
|
||||
|
||||
await init_integration(hass, 2, model=MODEL_WALL_DISPLAY)
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert (state := hass.states.get(ENTITY_ID)) is not None
|
||||
assert "entity_picture_local" not in state.attributes
|
||||
|
||||
client = await hass_client_no_auth()
|
||||
@@ -378,13 +379,54 @@ async def test_get_image_http(
|
||||
assert isinstance(content, bytes)
|
||||
|
||||
|
||||
async def test_get_image_http_base64_decode_error(
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_thumb",
|
||||
[
|
||||
"data:image/webp;base64,0",
|
||||
"data invalid",
|
||||
"data:video/mpg;base64,AAAA",
|
||||
],
|
||||
)
|
||||
async def test_get_image_http_stale_url_after_thumb_invalidated(
|
||||
hass: HomeAssistant,
|
||||
mock_rpc_device: Mock,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
hass_client_no_auth: ClientSessionGenerator,
|
||||
invalid_thumb: str,
|
||||
) -> None:
|
||||
"""Test get image via http command base64 decode error."""
|
||||
"""Test image proxy with a stale URL after the thumb becomes invalid."""
|
||||
status = deepcopy(mock_rpc_device.status)
|
||||
status["media"] = STATUS_AUDIO_FILE
|
||||
monkeypatch.setattr(mock_rpc_device, "status", status)
|
||||
|
||||
await init_integration(hass, 2, model=MODEL_WALL_DISPLAY)
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID)) is not None
|
||||
entity_picture = state.attributes["entity_picture"]
|
||||
|
||||
monkeypatch.setitem(
|
||||
mock_rpc_device.status["media"]["playback"]["media_meta"],
|
||||
"thumb",
|
||||
invalid_thumb,
|
||||
)
|
||||
mock_rpc_device.mock_update()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID)) is not None
|
||||
assert "entity_picture" not in state.attributes
|
||||
|
||||
client = await hass_client_no_auth()
|
||||
resp = await client.get(entity_picture)
|
||||
assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
|
||||
|
||||
async def test_entity_picture_absent_base64_data_invalid(
|
||||
hass: HomeAssistant,
|
||||
mock_rpc_device: Mock,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
hass_client: ClientSessionGenerator,
|
||||
) -> None:
|
||||
"""Test that entity_picture is absent when base64 data is invalid."""
|
||||
status = deepcopy(mock_rpc_device.status)
|
||||
status["media"] = STATUS_AUDIO_FILE
|
||||
status["media"]["playback"]["media_meta"]["thumb"] = "data:image/webp;base64,0"
|
||||
@@ -392,15 +434,64 @@ async def test_get_image_http_base64_decode_error(
|
||||
|
||||
await init_integration(hass, 2, model=MODEL_WALL_DISPLAY)
|
||||
|
||||
state = hass.states.get(ENTITY_ID)
|
||||
assert "entity_picture_local" not in state.attributes
|
||||
assert (state := hass.states.get(ENTITY_ID)) is not None
|
||||
assert "entity_picture" not in state.attributes
|
||||
|
||||
client = await hass_client_no_auth()
|
||||
client = await hass_client()
|
||||
resp = await client.get(f"/api/media_player_proxy/{ENTITY_ID}")
|
||||
assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
|
||||
resp = await client.get(state.attributes["entity_picture"])
|
||||
content = await resp.read()
|
||||
|
||||
assert isinstance(content, bytes)
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_thumb",
|
||||
[
|
||||
"data invalid",
|
||||
"lorem ipsum",
|
||||
],
|
||||
)
|
||||
async def test_entity_picture_absent_thumb_string_invalid(
|
||||
hass: HomeAssistant,
|
||||
mock_rpc_device: Mock,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
hass_client: ClientSessionGenerator,
|
||||
invalid_thumb: str,
|
||||
) -> None:
|
||||
"""Test that entity_picture is absent when thumb string has invalid format."""
|
||||
status = deepcopy(mock_rpc_device.status)
|
||||
status["media"] = STATUS_AUDIO_FILE
|
||||
status["media"]["playback"]["media_meta"]["thumb"] = invalid_thumb
|
||||
monkeypatch.setattr(mock_rpc_device, "status", status)
|
||||
|
||||
await init_integration(hass, 2, model=MODEL_WALL_DISPLAY)
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID)) is not None
|
||||
assert "entity_picture" not in state.attributes
|
||||
|
||||
client = await hass_client()
|
||||
resp = await client.get(f"/api/media_player_proxy/{ENTITY_ID}")
|
||||
assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
|
||||
|
||||
async def test_entity_picture_absent_mime_type_not_allowed(
|
||||
hass: HomeAssistant,
|
||||
mock_rpc_device: Mock,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
hass_client: ClientSessionGenerator,
|
||||
) -> None:
|
||||
"""Test that entity_picture is absent when MIME type is not allowed."""
|
||||
status = deepcopy(mock_rpc_device.status)
|
||||
status["media"] = STATUS_AUDIO_FILE
|
||||
status["media"]["playback"]["media_meta"]["thumb"] = "data:video/mpg;base64,0"
|
||||
monkeypatch.setattr(mock_rpc_device, "status", status)
|
||||
|
||||
await init_integration(hass, 2, model=MODEL_WALL_DISPLAY)
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID)) is not None
|
||||
assert "entity_picture" not in state.attributes
|
||||
|
||||
client = await hass_client()
|
||||
resp = await client.get(f"/api/media_player_proxy/{ENTITY_ID}")
|
||||
assert resp.status == HTTPStatus.INTERNAL_SERVER_ERROR
|
||||
|
||||
|
||||
async def test_rpc_media_player_browse_media_root(
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
'111': 'Chunchun',
|
||||
'112': 'Dancing Shadows',
|
||||
'113': 'Washing Machine',
|
||||
'114': 'RSVD',
|
||||
'115': 'Blends',
|
||||
'116': 'TV Simulator',
|
||||
'117': 'Dynamic Smooth',
|
||||
@@ -51,7 +50,6 @@
|
||||
'14': 'Theater Rainbow',
|
||||
'140': 'Waterfall',
|
||||
'141': 'Freqpixels',
|
||||
'142': 'RSVD',
|
||||
'143': 'Noisefire',
|
||||
'144': 'Puddlepeak',
|
||||
'145': 'Noisemove',
|
||||
@@ -61,7 +59,6 @@
|
||||
'149': 'Firenoise',
|
||||
'15': 'Running',
|
||||
'150': 'Squared Swirl',
|
||||
'151': 'RSVD',
|
||||
'152': 'DNA',
|
||||
'153': 'Matrix',
|
||||
'154': 'Metaballs',
|
||||
@@ -72,7 +69,6 @@
|
||||
'159': 'DJ Light',
|
||||
'16': 'Saw',
|
||||
'160': 'Funky Plank',
|
||||
'161': 'RSVD',
|
||||
'162': 'Pulser',
|
||||
'163': 'Blurz',
|
||||
'164': 'Drift',
|
||||
@@ -80,10 +76,7 @@
|
||||
'166': 'Sun Radiation',
|
||||
'167': 'Colored Bursts',
|
||||
'168': 'Julia',
|
||||
'169': 'RSVD',
|
||||
'17': 'Twinkle',
|
||||
'170': 'RSVD',
|
||||
'171': 'RSVD',
|
||||
'172': 'Game Of Life',
|
||||
'173': 'Tartan',
|
||||
'174': 'Polar Lights',
|
||||
@@ -138,7 +131,6 @@
|
||||
'50': 'Two Dots',
|
||||
'51': 'Fairytwinkle',
|
||||
'52': 'Running Dual',
|
||||
'53': 'RSVD',
|
||||
'54': 'Chase 3',
|
||||
'55': 'Tri Wipe',
|
||||
'56': 'Tri Fade',
|
||||
@@ -227,6 +219,7 @@
|
||||
'product': 'FOSS',
|
||||
'str': False,
|
||||
'udpport': 21324,
|
||||
'umpalcount': 0,
|
||||
'uptime': 966,
|
||||
'ver': '0.14.4',
|
||||
'vid': '2405180',
|
||||
|
||||
@@ -60,7 +60,6 @@
|
||||
'Two Dots',
|
||||
'Fairytwinkle',
|
||||
'Running Dual',
|
||||
'RSVD',
|
||||
'Chase 3',
|
||||
'Tri Wipe',
|
||||
'Tri Fade',
|
||||
@@ -149,7 +148,6 @@
|
||||
'GEQ',
|
||||
'Waterfall',
|
||||
'Freqpixels',
|
||||
'RSVD',
|
||||
'Noisefire',
|
||||
'Puddlepeak',
|
||||
'Noisemove',
|
||||
@@ -158,7 +156,6 @@
|
||||
'Ripple Peak',
|
||||
'Firenoise',
|
||||
'Squared Swirl',
|
||||
'RSVD',
|
||||
'DNA',
|
||||
'Matrix',
|
||||
'Metaballs',
|
||||
@@ -168,7 +165,6 @@
|
||||
'Gravfreq',
|
||||
'DJ Light',
|
||||
'Funky Plank',
|
||||
'RSVD',
|
||||
'Pulser',
|
||||
'Blurz',
|
||||
'Drift',
|
||||
@@ -176,9 +172,6 @@
|
||||
'Sun Radiation',
|
||||
'Colored Bursts',
|
||||
'Julia',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'Game Of Life',
|
||||
'Tartan',
|
||||
'Polar Lights',
|
||||
@@ -293,7 +286,6 @@
|
||||
'Two Dots',
|
||||
'Fairytwinkle',
|
||||
'Running Dual',
|
||||
'RSVD',
|
||||
'Chase 3',
|
||||
'Tri Wipe',
|
||||
'Tri Fade',
|
||||
@@ -382,7 +374,6 @@
|
||||
'GEQ',
|
||||
'Waterfall',
|
||||
'Freqpixels',
|
||||
'RSVD',
|
||||
'Noisefire',
|
||||
'Puddlepeak',
|
||||
'Noisemove',
|
||||
@@ -391,7 +382,6 @@
|
||||
'Ripple Peak',
|
||||
'Firenoise',
|
||||
'Squared Swirl',
|
||||
'RSVD',
|
||||
'DNA',
|
||||
'Matrix',
|
||||
'Metaballs',
|
||||
@@ -401,7 +391,6 @@
|
||||
'Gravfreq',
|
||||
'DJ Light',
|
||||
'Funky Plank',
|
||||
'RSVD',
|
||||
'Pulser',
|
||||
'Blurz',
|
||||
'Drift',
|
||||
@@ -409,9 +398,6 @@
|
||||
'Sun Radiation',
|
||||
'Colored Bursts',
|
||||
'Julia',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'Game Of Life',
|
||||
'Tartan',
|
||||
'Polar Lights',
|
||||
@@ -520,7 +506,6 @@
|
||||
'Two Dots',
|
||||
'Fairytwinkle',
|
||||
'Running Dual',
|
||||
'RSVD',
|
||||
'Chase 3',
|
||||
'Tri Wipe',
|
||||
'Tri Fade',
|
||||
@@ -581,7 +566,6 @@
|
||||
'Chunchun',
|
||||
'Dancing Shadows',
|
||||
'Washing Machine',
|
||||
'RSVD',
|
||||
'Blends',
|
||||
'TV Simulator',
|
||||
'Dynamic Smooth',
|
||||
@@ -609,7 +593,6 @@
|
||||
'GEQ',
|
||||
'Waterfall',
|
||||
'Freqpixels',
|
||||
'RSVD',
|
||||
'Noisefire',
|
||||
'Puddlepeak',
|
||||
'Noisemove',
|
||||
@@ -618,7 +601,6 @@
|
||||
'Ripple Peak',
|
||||
'Firenoise',
|
||||
'Squared Swirl',
|
||||
'RSVD',
|
||||
'DNA',
|
||||
'Matrix',
|
||||
'Metaballs',
|
||||
@@ -628,7 +610,6 @@
|
||||
'Gravfreq',
|
||||
'DJ Light',
|
||||
'Funky Plank',
|
||||
'RSVD',
|
||||
'Pulser',
|
||||
'Blurz',
|
||||
'Drift',
|
||||
@@ -636,9 +617,6 @@
|
||||
'Sun Radiation',
|
||||
'Colored Bursts',
|
||||
'Julia',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'Game Of Life',
|
||||
'Tartan',
|
||||
'Polar Lights',
|
||||
@@ -749,7 +727,6 @@
|
||||
'Two Dots',
|
||||
'Fairytwinkle',
|
||||
'Running Dual',
|
||||
'RSVD',
|
||||
'Chase 3',
|
||||
'Tri Wipe',
|
||||
'Tri Fade',
|
||||
@@ -810,7 +787,6 @@
|
||||
'Chunchun',
|
||||
'Dancing Shadows',
|
||||
'Washing Machine',
|
||||
'RSVD',
|
||||
'Blends',
|
||||
'TV Simulator',
|
||||
'Dynamic Smooth',
|
||||
@@ -838,7 +814,6 @@
|
||||
'GEQ',
|
||||
'Waterfall',
|
||||
'Freqpixels',
|
||||
'RSVD',
|
||||
'Noisefire',
|
||||
'Puddlepeak',
|
||||
'Noisemove',
|
||||
@@ -847,7 +822,6 @@
|
||||
'Ripple Peak',
|
||||
'Firenoise',
|
||||
'Squared Swirl',
|
||||
'RSVD',
|
||||
'DNA',
|
||||
'Matrix',
|
||||
'Metaballs',
|
||||
@@ -857,7 +831,6 @@
|
||||
'Gravfreq',
|
||||
'DJ Light',
|
||||
'Funky Plank',
|
||||
'RSVD',
|
||||
'Pulser',
|
||||
'Blurz',
|
||||
'Drift',
|
||||
@@ -865,9 +838,6 @@
|
||||
'Sun Radiation',
|
||||
'Colored Bursts',
|
||||
'Julia',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'Game Of Life',
|
||||
'Tartan',
|
||||
'Polar Lights',
|
||||
@@ -1032,7 +1002,6 @@
|
||||
'Two Dots',
|
||||
'Fairytwinkle',
|
||||
'Running Dual',
|
||||
'RSVD',
|
||||
'Chase 3',
|
||||
'Tri Wipe',
|
||||
'Tri Fade',
|
||||
@@ -1093,7 +1062,6 @@
|
||||
'Chunchun',
|
||||
'Dancing Shadows',
|
||||
'Washing Machine',
|
||||
'RSVD',
|
||||
'Blends',
|
||||
'TV Simulator',
|
||||
'Dynamic Smooth',
|
||||
@@ -1121,7 +1089,6 @@
|
||||
'GEQ',
|
||||
'Waterfall',
|
||||
'Freqpixels',
|
||||
'RSVD',
|
||||
'Noisefire',
|
||||
'Puddlepeak',
|
||||
'Noisemove',
|
||||
@@ -1130,7 +1097,6 @@
|
||||
'Ripple Peak',
|
||||
'Firenoise',
|
||||
'Squared Swirl',
|
||||
'RSVD',
|
||||
'DNA',
|
||||
'Matrix',
|
||||
'Metaballs',
|
||||
@@ -1140,7 +1106,6 @@
|
||||
'Gravfreq',
|
||||
'DJ Light',
|
||||
'Funky Plank',
|
||||
'RSVD',
|
||||
'Pulser',
|
||||
'Blurz',
|
||||
'Drift',
|
||||
@@ -1148,9 +1113,6 @@
|
||||
'Sun Radiation',
|
||||
'Colored Bursts',
|
||||
'Julia',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'Game Of Life',
|
||||
'Tartan',
|
||||
'Polar Lights',
|
||||
@@ -1261,7 +1223,6 @@
|
||||
'Two Dots',
|
||||
'Fairytwinkle',
|
||||
'Running Dual',
|
||||
'RSVD',
|
||||
'Chase 3',
|
||||
'Tri Wipe',
|
||||
'Tri Fade',
|
||||
@@ -1322,7 +1283,6 @@
|
||||
'Chunchun',
|
||||
'Dancing Shadows',
|
||||
'Washing Machine',
|
||||
'RSVD',
|
||||
'Blends',
|
||||
'TV Simulator',
|
||||
'Dynamic Smooth',
|
||||
@@ -1350,7 +1310,6 @@
|
||||
'GEQ',
|
||||
'Waterfall',
|
||||
'Freqpixels',
|
||||
'RSVD',
|
||||
'Noisefire',
|
||||
'Puddlepeak',
|
||||
'Noisemove',
|
||||
@@ -1359,7 +1318,6 @@
|
||||
'Ripple Peak',
|
||||
'Firenoise',
|
||||
'Squared Swirl',
|
||||
'RSVD',
|
||||
'DNA',
|
||||
'Matrix',
|
||||
'Metaballs',
|
||||
@@ -1369,7 +1327,6 @@
|
||||
'Gravfreq',
|
||||
'DJ Light',
|
||||
'Funky Plank',
|
||||
'RSVD',
|
||||
'Pulser',
|
||||
'Blurz',
|
||||
'Drift',
|
||||
@@ -1377,9 +1334,6 @@
|
||||
'Sun Radiation',
|
||||
'Colored Bursts',
|
||||
'Julia',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'Game Of Life',
|
||||
'Tartan',
|
||||
'Polar Lights',
|
||||
@@ -1484,7 +1438,6 @@
|
||||
'Two Dots',
|
||||
'Fairytwinkle',
|
||||
'Running Dual',
|
||||
'RSVD',
|
||||
'Chase 3',
|
||||
'Tri Wipe',
|
||||
'Tri Fade',
|
||||
@@ -1545,7 +1498,6 @@
|
||||
'Chunchun',
|
||||
'Dancing Shadows',
|
||||
'Washing Machine',
|
||||
'RSVD',
|
||||
'Blends',
|
||||
'TV Simulator',
|
||||
'Dynamic Smooth',
|
||||
@@ -1573,7 +1525,6 @@
|
||||
'GEQ',
|
||||
'Waterfall',
|
||||
'Freqpixels',
|
||||
'RSVD',
|
||||
'Noisefire',
|
||||
'Puddlepeak',
|
||||
'Noisemove',
|
||||
@@ -1582,7 +1533,6 @@
|
||||
'Ripple Peak',
|
||||
'Firenoise',
|
||||
'Squared Swirl',
|
||||
'RSVD',
|
||||
'DNA',
|
||||
'Matrix',
|
||||
'Metaballs',
|
||||
@@ -1592,7 +1542,6 @@
|
||||
'Gravfreq',
|
||||
'DJ Light',
|
||||
'Funky Plank',
|
||||
'RSVD',
|
||||
'Pulser',
|
||||
'Blurz',
|
||||
'Drift',
|
||||
@@ -1600,9 +1549,6 @@
|
||||
'Sun Radiation',
|
||||
'Colored Bursts',
|
||||
'Julia',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'Game Of Life',
|
||||
'Tartan',
|
||||
'Polar Lights',
|
||||
@@ -1713,7 +1659,6 @@
|
||||
'Two Dots',
|
||||
'Fairytwinkle',
|
||||
'Running Dual',
|
||||
'RSVD',
|
||||
'Chase 3',
|
||||
'Tri Wipe',
|
||||
'Tri Fade',
|
||||
@@ -1774,7 +1719,6 @@
|
||||
'Chunchun',
|
||||
'Dancing Shadows',
|
||||
'Washing Machine',
|
||||
'RSVD',
|
||||
'Blends',
|
||||
'TV Simulator',
|
||||
'Dynamic Smooth',
|
||||
@@ -1802,7 +1746,6 @@
|
||||
'GEQ',
|
||||
'Waterfall',
|
||||
'Freqpixels',
|
||||
'RSVD',
|
||||
'Noisefire',
|
||||
'Puddlepeak',
|
||||
'Noisemove',
|
||||
@@ -1811,7 +1754,6 @@
|
||||
'Ripple Peak',
|
||||
'Firenoise',
|
||||
'Squared Swirl',
|
||||
'RSVD',
|
||||
'DNA',
|
||||
'Matrix',
|
||||
'Metaballs',
|
||||
@@ -1821,7 +1763,6 @@
|
||||
'Gravfreq',
|
||||
'DJ Light',
|
||||
'Funky Plank',
|
||||
'RSVD',
|
||||
'Pulser',
|
||||
'Blurz',
|
||||
'Drift',
|
||||
@@ -1829,9 +1770,6 @@
|
||||
'Sun Radiation',
|
||||
'Colored Bursts',
|
||||
'Julia',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'Game Of Life',
|
||||
'Tartan',
|
||||
'Polar Lights',
|
||||
@@ -1936,7 +1874,6 @@
|
||||
'Two Dots',
|
||||
'Fairytwinkle',
|
||||
'Running Dual',
|
||||
'RSVD',
|
||||
'Chase 3',
|
||||
'Tri Wipe',
|
||||
'Tri Fade',
|
||||
@@ -1997,7 +1934,6 @@
|
||||
'Chunchun',
|
||||
'Dancing Shadows',
|
||||
'Washing Machine',
|
||||
'RSVD',
|
||||
'Blends',
|
||||
'TV Simulator',
|
||||
'Dynamic Smooth',
|
||||
@@ -2025,7 +1961,6 @@
|
||||
'GEQ',
|
||||
'Waterfall',
|
||||
'Freqpixels',
|
||||
'RSVD',
|
||||
'Noisefire',
|
||||
'Puddlepeak',
|
||||
'Noisemove',
|
||||
@@ -2034,7 +1969,6 @@
|
||||
'Ripple Peak',
|
||||
'Firenoise',
|
||||
'Squared Swirl',
|
||||
'RSVD',
|
||||
'DNA',
|
||||
'Matrix',
|
||||
'Metaballs',
|
||||
@@ -2044,7 +1978,6 @@
|
||||
'Gravfreq',
|
||||
'DJ Light',
|
||||
'Funky Plank',
|
||||
'RSVD',
|
||||
'Pulser',
|
||||
'Blurz',
|
||||
'Drift',
|
||||
@@ -2052,9 +1985,6 @@
|
||||
'Sun Radiation',
|
||||
'Colored Bursts',
|
||||
'Julia',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'Game Of Life',
|
||||
'Tartan',
|
||||
'Polar Lights',
|
||||
@@ -2165,7 +2095,6 @@
|
||||
'Two Dots',
|
||||
'Fairytwinkle',
|
||||
'Running Dual',
|
||||
'RSVD',
|
||||
'Chase 3',
|
||||
'Tri Wipe',
|
||||
'Tri Fade',
|
||||
@@ -2226,7 +2155,6 @@
|
||||
'Chunchun',
|
||||
'Dancing Shadows',
|
||||
'Washing Machine',
|
||||
'RSVD',
|
||||
'Blends',
|
||||
'TV Simulator',
|
||||
'Dynamic Smooth',
|
||||
@@ -2254,7 +2182,6 @@
|
||||
'GEQ',
|
||||
'Waterfall',
|
||||
'Freqpixels',
|
||||
'RSVD',
|
||||
'Noisefire',
|
||||
'Puddlepeak',
|
||||
'Noisemove',
|
||||
@@ -2263,7 +2190,6 @@
|
||||
'Ripple Peak',
|
||||
'Firenoise',
|
||||
'Squared Swirl',
|
||||
'RSVD',
|
||||
'DNA',
|
||||
'Matrix',
|
||||
'Metaballs',
|
||||
@@ -2273,7 +2199,6 @@
|
||||
'Gravfreq',
|
||||
'DJ Light',
|
||||
'Funky Plank',
|
||||
'RSVD',
|
||||
'Pulser',
|
||||
'Blurz',
|
||||
'Drift',
|
||||
@@ -2281,9 +2206,6 @@
|
||||
'Sun Radiation',
|
||||
'Colored Bursts',
|
||||
'Julia',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'Game Of Life',
|
||||
'Tartan',
|
||||
'Polar Lights',
|
||||
@@ -2388,7 +2310,6 @@
|
||||
'Two Dots',
|
||||
'Fairytwinkle',
|
||||
'Running Dual',
|
||||
'RSVD',
|
||||
'Chase 3',
|
||||
'Tri Wipe',
|
||||
'Tri Fade',
|
||||
@@ -2449,7 +2370,6 @@
|
||||
'Chunchun',
|
||||
'Dancing Shadows',
|
||||
'Washing Machine',
|
||||
'RSVD',
|
||||
'Blends',
|
||||
'TV Simulator',
|
||||
'Dynamic Smooth',
|
||||
@@ -2477,7 +2397,6 @@
|
||||
'GEQ',
|
||||
'Waterfall',
|
||||
'Freqpixels',
|
||||
'RSVD',
|
||||
'Noisefire',
|
||||
'Puddlepeak',
|
||||
'Noisemove',
|
||||
@@ -2486,7 +2405,6 @@
|
||||
'Ripple Peak',
|
||||
'Firenoise',
|
||||
'Squared Swirl',
|
||||
'RSVD',
|
||||
'DNA',
|
||||
'Matrix',
|
||||
'Metaballs',
|
||||
@@ -2496,7 +2414,6 @@
|
||||
'Gravfreq',
|
||||
'DJ Light',
|
||||
'Funky Plank',
|
||||
'RSVD',
|
||||
'Pulser',
|
||||
'Blurz',
|
||||
'Drift',
|
||||
@@ -2504,9 +2421,6 @@
|
||||
'Sun Radiation',
|
||||
'Colored Bursts',
|
||||
'Julia',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'Game Of Life',
|
||||
'Tartan',
|
||||
'Polar Lights',
|
||||
@@ -2617,7 +2531,6 @@
|
||||
'Two Dots',
|
||||
'Fairytwinkle',
|
||||
'Running Dual',
|
||||
'RSVD',
|
||||
'Chase 3',
|
||||
'Tri Wipe',
|
||||
'Tri Fade',
|
||||
@@ -2678,7 +2591,6 @@
|
||||
'Chunchun',
|
||||
'Dancing Shadows',
|
||||
'Washing Machine',
|
||||
'RSVD',
|
||||
'Blends',
|
||||
'TV Simulator',
|
||||
'Dynamic Smooth',
|
||||
@@ -2706,7 +2618,6 @@
|
||||
'GEQ',
|
||||
'Waterfall',
|
||||
'Freqpixels',
|
||||
'RSVD',
|
||||
'Noisefire',
|
||||
'Puddlepeak',
|
||||
'Noisemove',
|
||||
@@ -2715,7 +2626,6 @@
|
||||
'Ripple Peak',
|
||||
'Firenoise',
|
||||
'Squared Swirl',
|
||||
'RSVD',
|
||||
'DNA',
|
||||
'Matrix',
|
||||
'Metaballs',
|
||||
@@ -2725,7 +2635,6 @@
|
||||
'Gravfreq',
|
||||
'DJ Light',
|
||||
'Funky Plank',
|
||||
'RSVD',
|
||||
'Pulser',
|
||||
'Blurz',
|
||||
'Drift',
|
||||
@@ -2733,9 +2642,6 @@
|
||||
'Sun Radiation',
|
||||
'Colored Bursts',
|
||||
'Julia',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'RSVD',
|
||||
'Game Of Life',
|
||||
'Tartan',
|
||||
'Polar Lights',
|
||||
|
||||
@@ -820,6 +820,57 @@ async def test_on_pipeline_event_ignores_disconnected_client(
|
||||
assert not mock_client.error_event.is_set()
|
||||
|
||||
|
||||
async def test_run_start_without_tts(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test RUN_START event without tts_output does not crash.
|
||||
|
||||
Regression test for https://github.com/home-assistant/core/issues/165734
|
||||
"""
|
||||
events: list[Event] = [
|
||||
RunPipeline(
|
||||
start_stage=PipelineStage.WAKE, end_stage=PipelineStage.TTS
|
||||
).event(),
|
||||
]
|
||||
|
||||
pipeline_event = asyncio.Event()
|
||||
|
||||
def _async_pipeline_from_audio_stream(*args: Any, **kwargs: Any) -> None:
|
||||
pipeline_event.set()
|
||||
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.wyoming.data.load_wyoming_info",
|
||||
return_value=SATELLITE_INFO,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.wyoming.assist_satellite.AsyncTcpClient",
|
||||
SatelliteAsyncTcpClient(events),
|
||||
) as mock_client,
|
||||
patch(
|
||||
"homeassistant.components.assist_satellite.entity.async_pipeline_from_audio_stream",
|
||||
wraps=_async_pipeline_from_audio_stream,
|
||||
) as mock_run_pipeline,
|
||||
):
|
||||
await setup_config_entry(hass)
|
||||
|
||||
async with asyncio.timeout(1):
|
||||
await pipeline_event.wait()
|
||||
await mock_client.connect_event.wait()
|
||||
await mock_client.run_satellite_event.wait()
|
||||
|
||||
event_callback = mock_run_pipeline.call_args.kwargs["event_callback"]
|
||||
|
||||
# Fire RUN_START without tts_output (TTS not configured)
|
||||
# must not raise KeyError
|
||||
event_callback(
|
||||
assist_pipeline.PipelineEvent(
|
||||
assist_pipeline.PipelineEventType.RUN_START,
|
||||
{"pipeline": "test", "language": "en"},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def test_announce_raises_when_client_disconnected(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
|
||||
@@ -21,6 +21,7 @@ from homeassistant.components.homeassistant_hardware.helpers import (
|
||||
async_register_firmware_update_in_progress,
|
||||
)
|
||||
from homeassistant.components.usb import USBDevice
|
||||
from homeassistant.components.zha.config_flow import ZhaConfigFlowHandler
|
||||
from homeassistant.components.zha.const import (
|
||||
CONF_BAUDRATE,
|
||||
CONF_FLOW_CONTROL,
|
||||
@@ -181,6 +182,39 @@ async def test_migration_v5_explicit_socket_port(
|
||||
assert config_entry.data[CONF_DEVICE][CONF_DEVICE_PATH] == new_path
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("version_delta", "minor_delta", "expected_state"),
|
||||
[
|
||||
pytest.param(0, 1, ConfigEntryState.LOADED, id="minor_allowed"),
|
||||
pytest.param(1, 0, ConfigEntryState.MIGRATION_ERROR, id="major_blocked"),
|
||||
],
|
||||
)
|
||||
@patch("homeassistant.components.zha.async_setup_entry", AsyncMock(return_value=True))
|
||||
async def test_migration_version_downgrade_guard(
|
||||
version_delta: int,
|
||||
minor_delta: int,
|
||||
expected_state: ConfigEntryState,
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test the config version downgrade guard."""
|
||||
future_version = ZhaConfigFlowHandler.VERSION + version_delta
|
||||
future_minor_version = ZhaConfigFlowHandler.MINOR_VERSION + minor_delta
|
||||
config_entry.add_to_hass(hass)
|
||||
hass.config_entries.async_update_entry(
|
||||
config_entry,
|
||||
version=future_version,
|
||||
minor_version=future_minor_version,
|
||||
)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry.state is expected_state
|
||||
assert config_entry.version == future_version
|
||||
assert config_entry.minor_version == future_minor_version
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"radio_type",
|
||||
|
||||
Reference in New Issue
Block a user