mirror of
https://github.com/home-assistant/core.git
synced 2025-04-25 09:47:52 +00:00
2025.1.1 (#134940)
This commit is contained in:
commit
d59a91a905
@ -44,12 +44,12 @@
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"title": "Configure Android Apps",
|
||||
"description": "Configure application id {app_id}",
|
||||
"title": "Configure Android apps",
|
||||
"description": "Configure application ID {app_id}",
|
||||
"data": {
|
||||
"app_name": "Application Name",
|
||||
"app_name": "Application name",
|
||||
"app_id": "Application ID",
|
||||
"app_icon": "Application Icon",
|
||||
"app_icon": "Application icon",
|
||||
"app_delete": "Check to delete this application"
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,8 @@
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"invalid_unique_id": "Impossible to determine a valid unique id for the device",
|
||||
"no_unique_id": "A device without a valid unique id is already configured. Configuration of multiple instance is not possible"
|
||||
"invalid_unique_id": "Impossible to determine a valid unique ID for the device",
|
||||
"no_unique_id": "A device without a valid unique ID is already configured. Configuration of multiple instances is not possible"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
@ -42,7 +42,7 @@
|
||||
"consider_home": "Seconds to wait before considering a device away",
|
||||
"track_unknown": "Track unknown / unnamed devices",
|
||||
"interface": "The interface that you want statistics from (e.g. eth0, eth1 etc)",
|
||||
"dnsmasq": "The location in the router of the dnsmasq.leases files",
|
||||
"dnsmasq": "The location of the dnsmasq.leases file in the router",
|
||||
"require_ip": "Devices must have IP (for access point mode)"
|
||||
}
|
||||
}
|
||||
|
@ -435,6 +435,7 @@ class BackupManager:
|
||||
# no point in continuing
|
||||
raise BackupManagerError(str(result)) from result
|
||||
if isinstance(result, BackupAgentError):
|
||||
LOGGER.error("Error uploading to %s: %s", agent_ids[idx], result)
|
||||
agent_errors[agent_ids[idx]] = result
|
||||
continue
|
||||
if isinstance(result, Exception):
|
||||
|
@ -20,6 +20,6 @@
|
||||
"bluetooth-auto-recovery==1.4.2",
|
||||
"bluetooth-data-tools==1.20.0",
|
||||
"dbus-fast==2.24.3",
|
||||
"habluetooth==3.6.0"
|
||||
"habluetooth==3.7.0"
|
||||
]
|
||||
}
|
||||
|
@ -6,5 +6,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/bring",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["bring_api"],
|
||||
"requirements": ["bring-api==0.9.1"]
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
}
|
||||
},
|
||||
"discovery_confirm": {
|
||||
"description": "Do you want to setup {name}?"
|
||||
"description": "Do you want to set up {name}?"
|
||||
},
|
||||
"reconfigure": {
|
||||
"description": "Reconfigure your Cambridge Audio Streamer.",
|
||||
@ -28,7 +28,7 @@
|
||||
"cannot_connect": "Failed to connect to Cambridge Audio device. Please make sure the device is powered up and connected to the network. Try power-cycling the device if it does not connect."
|
||||
},
|
||||
"abort": {
|
||||
"wrong_device": "This Cambridge Audio device does not match the existing device id. Please make sure you entered the correct IP address.",
|
||||
"wrong_device": "This Cambridge Audio device does not match the existing device ID. Please make sure you entered the correct IP address.",
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
|
@ -516,6 +516,19 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Flag supported features."""
|
||||
return self._attr_supported_features
|
||||
|
||||
@property
|
||||
def supported_features_compat(self) -> CameraEntityFeature:
|
||||
"""Return the supported features as CameraEntityFeature.
|
||||
|
||||
Remove this compatibility shim in 2025.1 or later.
|
||||
"""
|
||||
features = self.supported_features
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = CameraEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
|
||||
@cached_property
|
||||
def is_recording(self) -> bool:
|
||||
"""Return true if the device is recording."""
|
||||
@ -569,7 +582,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
|
||||
self._deprecate_attr_frontend_stream_type_logged = True
|
||||
return self._attr_frontend_stream_type
|
||||
if CameraEntityFeature.STREAM not in self.supported_features:
|
||||
if CameraEntityFeature.STREAM not in self.supported_features_compat:
|
||||
return None
|
||||
if (
|
||||
self._webrtc_provider
|
||||
@ -798,7 +811,9 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
async def async_internal_added_to_hass(self) -> None:
|
||||
"""Run when entity about to be added to hass."""
|
||||
await super().async_internal_added_to_hass()
|
||||
self.__supports_stream = self.supported_features & CameraEntityFeature.STREAM
|
||||
self.__supports_stream = (
|
||||
self.supported_features_compat & CameraEntityFeature.STREAM
|
||||
)
|
||||
await self.async_refresh_providers(write_state=False)
|
||||
|
||||
async def async_refresh_providers(self, *, write_state: bool = True) -> None:
|
||||
@ -838,7 +853,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
self, fn: Callable[[HomeAssistant, Camera], Coroutine[None, None, _T | None]]
|
||||
) -> _T | None:
|
||||
"""Get first provider that supports this camera."""
|
||||
if CameraEntityFeature.STREAM not in self.supported_features:
|
||||
if CameraEntityFeature.STREAM not in self.supported_features_compat:
|
||||
return None
|
||||
|
||||
return await fn(self.hass, self)
|
||||
@ -896,7 +911,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def camera_capabilities(self) -> CameraCapabilities:
|
||||
"""Return the camera capabilities."""
|
||||
frontend_stream_types = set()
|
||||
if CameraEntityFeature.STREAM in self.supported_features:
|
||||
if CameraEntityFeature.STREAM in self.supported_features_compat:
|
||||
if self._supports_native_sync_webrtc or self._supports_native_async_webrtc:
|
||||
# The camera has a native WebRTC implementation
|
||||
frontend_stream_types.add(StreamType.WEB_RTC)
|
||||
@ -916,7 +931,8 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""
|
||||
super().async_write_ha_state()
|
||||
if self.__supports_stream != (
|
||||
supports_stream := self.supported_features & CameraEntityFeature.STREAM
|
||||
supports_stream := self.supported_features_compat
|
||||
& CameraEntityFeature.STREAM
|
||||
):
|
||||
self.__supports_stream = supports_stream
|
||||
self._invalidate_camera_capabilities_cache()
|
||||
|
@ -181,6 +181,11 @@ class CloudBackupAgent(BackupAgent):
|
||||
headers=details["headers"] | {"content-length": str(backup.size)},
|
||||
timeout=ClientTimeout(connect=10.0, total=43200.0), # 43200s == 12h
|
||||
)
|
||||
_LOGGER.log(
|
||||
logging.DEBUG if upload_status.status < 400 else logging.WARNING,
|
||||
"Backup upload status: %s",
|
||||
upload_status.status,
|
||||
)
|
||||
upload_status.raise_for_status()
|
||||
except (TimeoutError, ClientError) as err:
|
||||
raise BackupAgentError("Failed to upload backup") from err
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from cookidoo_api import Cookidoo, CookidooConfig, CookidooLocalizationConfig
|
||||
from cookidoo_api import Cookidoo, CookidooConfig, get_localization_options
|
||||
|
||||
from homeassistant.const import (
|
||||
CONF_COUNTRY,
|
||||
@ -22,15 +22,17 @@ PLATFORMS: list[Platform] = [Platform.TODO]
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: CookidooConfigEntry) -> bool:
|
||||
"""Set up Cookidoo from a config entry."""
|
||||
|
||||
localizations = await get_localization_options(
|
||||
country=entry.data[CONF_COUNTRY].lower(),
|
||||
language=entry.data[CONF_LANGUAGE],
|
||||
)
|
||||
|
||||
cookidoo = Cookidoo(
|
||||
async_get_clientsession(hass),
|
||||
CookidooConfig(
|
||||
email=entry.data[CONF_EMAIL],
|
||||
password=entry.data[CONF_PASSWORD],
|
||||
localization=CookidooLocalizationConfig(
|
||||
country_code=entry.data[CONF_COUNTRY].lower(),
|
||||
language=entry.data[CONF_LANGUAGE],
|
||||
),
|
||||
localization=localizations[0],
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -10,7 +10,6 @@ from cookidoo_api import (
|
||||
Cookidoo,
|
||||
CookidooAuthException,
|
||||
CookidooConfig,
|
||||
CookidooLocalizationConfig,
|
||||
CookidooRequestException,
|
||||
get_country_options,
|
||||
get_localization_options,
|
||||
@ -219,18 +218,19 @@ class CookidooConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
else:
|
||||
data_input[CONF_LANGUAGE] = (
|
||||
await get_localization_options(country=data_input[CONF_COUNTRY].lower())
|
||||
)[0] # Pick any language to test login
|
||||
)[0].language # Pick any language to test login
|
||||
|
||||
localizations = await get_localization_options(
|
||||
country=data_input[CONF_COUNTRY].lower(),
|
||||
language=data_input[CONF_LANGUAGE],
|
||||
)
|
||||
|
||||
session = async_get_clientsession(self.hass)
|
||||
cookidoo = Cookidoo(
|
||||
session,
|
||||
async_get_clientsession(self.hass),
|
||||
CookidooConfig(
|
||||
email=data_input[CONF_EMAIL],
|
||||
password=data_input[CONF_PASSWORD],
|
||||
localization=CookidooLocalizationConfig(
|
||||
country_code=data_input[CONF_COUNTRY].lower(),
|
||||
language=data_input[CONF_LANGUAGE],
|
||||
),
|
||||
localization=localizations[0],
|
||||
),
|
||||
)
|
||||
try:
|
||||
|
@ -6,6 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/cookidoo",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["cookidoo_api"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["cookidoo-api==0.10.0"]
|
||||
"requirements": ["cookidoo-api==0.11.2"]
|
||||
}
|
||||
|
@ -300,6 +300,10 @@ class CoverEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def supported_features(self) -> CoverEntityFeature:
|
||||
"""Flag supported features."""
|
||||
if (features := self._attr_supported_features) is not None:
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = CoverEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
|
||||
supported_features = (
|
||||
|
@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["openwebif"],
|
||||
"requirements": ["openwebifpy==4.3.0"]
|
||||
"requirements": ["openwebifpy==4.3.1"]
|
||||
}
|
||||
|
@ -22,5 +22,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["eq3btsmart"],
|
||||
"requirements": ["eq3btsmart==1.4.1", "bleak-esphome==1.1.0"]
|
||||
"requirements": ["eq3btsmart==1.4.1", "bleak-esphome==2.0.0"]
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from aioesphomeapi import APIClient, DeviceInfo
|
||||
from bleak_esphome import connect_scanner
|
||||
from bleak_esphome.backend.cache import ESPHomeBluetoothCache
|
||||
|
||||
from homeassistant.components.bluetooth import async_register_scanner
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
|
||||
@ -28,10 +27,9 @@ def async_connect_scanner(
|
||||
entry_data: RuntimeEntryData,
|
||||
cli: APIClient,
|
||||
device_info: DeviceInfo,
|
||||
cache: ESPHomeBluetoothCache,
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Connect scanner."""
|
||||
client_data = connect_scanner(cli, device_info, cache, entry_data.available)
|
||||
client_data = connect_scanner(cli, device_info, entry_data.available)
|
||||
entry_data.bluetooth_device = client_data.bluetooth_device
|
||||
client_data.disconnect_callbacks = entry_data.disconnect_callbacks
|
||||
scanner = client_data.scanner
|
||||
|
@ -6,8 +6,6 @@ from dataclasses import dataclass, field
|
||||
from functools import cache
|
||||
from typing import Self
|
||||
|
||||
from bleak_esphome.backend.cache import ESPHomeBluetoothCache
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.json import JSONEncoder
|
||||
|
||||
@ -22,9 +20,6 @@ class DomainData:
|
||||
"""Define a class that stores global esphome data in hass.data[DOMAIN]."""
|
||||
|
||||
_stores: dict[str, ESPHomeStorage] = field(default_factory=dict)
|
||||
bluetooth_cache: ESPHomeBluetoothCache = field(
|
||||
default_factory=ESPHomeBluetoothCache
|
||||
)
|
||||
|
||||
def get_entry_data(self, entry: ESPHomeConfigEntry) -> RuntimeEntryData:
|
||||
"""Return the runtime entry data associated with this config entry.
|
||||
|
@ -423,9 +423,7 @@ class ESPHomeManager:
|
||||
|
||||
if device_info.bluetooth_proxy_feature_flags_compat(api_version):
|
||||
entry_data.disconnect_callbacks.add(
|
||||
async_connect_scanner(
|
||||
hass, entry_data, cli, device_info, self.domain_data.bluetooth_cache
|
||||
)
|
||||
async_connect_scanner(hass, entry_data, cli, device_info)
|
||||
)
|
||||
|
||||
if device_info.voice_assistant_feature_flags_compat(api_version) and (
|
||||
|
@ -18,7 +18,7 @@
|
||||
"requirements": [
|
||||
"aioesphomeapi==28.0.0",
|
||||
"esphome-dashboard-api==1.2.3",
|
||||
"bleak-esphome==1.1.0"
|
||||
"bleak-esphome==2.0.0"
|
||||
],
|
||||
"zeroconf": ["_esphomelib._tcp.local."]
|
||||
}
|
||||
|
@ -2,10 +2,11 @@
|
||||
|
||||
from datetime import datetime as dt
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import jwt
|
||||
from pyflick import FlickAPI
|
||||
from pyflick.authentication import AbstractFlickAuth
|
||||
from pyflick.authentication import SimpleFlickAuth
|
||||
from pyflick.const import DEFAULT_CLIENT_ID, DEFAULT_CLIENT_SECRET
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@ -93,16 +94,22 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
||||
return True
|
||||
|
||||
|
||||
class HassFlickAuth(AbstractFlickAuth):
|
||||
class HassFlickAuth(SimpleFlickAuth):
|
||||
"""Implementation of AbstractFlickAuth based on a Home Assistant entity config."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
def __init__(self, hass: HomeAssistant, entry: FlickConfigEntry) -> None:
|
||||
"""Flick authentication based on a Home Assistant entity config."""
|
||||
super().__init__(aiohttp_client.async_get_clientsession(hass))
|
||||
super().__init__(
|
||||
username=entry.data[CONF_USERNAME],
|
||||
password=entry.data[CONF_PASSWORD],
|
||||
client_id=entry.data.get(CONF_CLIENT_ID, DEFAULT_CLIENT_ID),
|
||||
client_secret=entry.data.get(CONF_CLIENT_SECRET, DEFAULT_CLIENT_SECRET),
|
||||
websession=aiohttp_client.async_get_clientsession(hass),
|
||||
)
|
||||
self._entry = entry
|
||||
self._hass = hass
|
||||
|
||||
async def _get_entry_token(self):
|
||||
async def _get_entry_token(self) -> dict[str, Any]:
|
||||
# No token saved, generate one
|
||||
if (
|
||||
CONF_TOKEN_EXPIRY not in self._entry.data
|
||||
@ -119,13 +126,8 @@ class HassFlickAuth(AbstractFlickAuth):
|
||||
async def _update_token(self):
|
||||
_LOGGER.debug("Fetching new access token")
|
||||
|
||||
token = await self.get_new_token(
|
||||
username=self._entry.data[CONF_USERNAME],
|
||||
password=self._entry.data[CONF_PASSWORD],
|
||||
client_id=self._entry.data.get(CONF_CLIENT_ID, DEFAULT_CLIENT_ID),
|
||||
client_secret=self._entry.data.get(
|
||||
CONF_CLIENT_SECRET, DEFAULT_CLIENT_SECRET
|
||||
),
|
||||
token = await super().get_new_token(
|
||||
self._username, self._password, self._client_id, self._client_secret
|
||||
)
|
||||
|
||||
_LOGGER.debug("New token: %s", token)
|
||||
|
@ -214,6 +214,18 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
self._options = options
|
||||
await self.hass.async_add_executor_job(self.setup)
|
||||
|
||||
device_registry = dr.async_get(self.hass)
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=self.config_entry.entry_id,
|
||||
configuration_url=f"http://{self.host}",
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, self.mac)},
|
||||
identifiers={(DOMAIN, self.unique_id)},
|
||||
manufacturer="AVM",
|
||||
model=self.model,
|
||||
name=self.config_entry.title,
|
||||
sw_version=self.current_firmware,
|
||||
)
|
||||
|
||||
def setup(self) -> None:
|
||||
"""Set up FritzboxTools class."""
|
||||
|
||||
|
@ -68,23 +68,14 @@ class FritzBoxBaseEntity:
|
||||
"""Init device info class."""
|
||||
self._avm_wrapper = avm_wrapper
|
||||
self._device_name = device_name
|
||||
|
||||
@property
|
||||
def mac_address(self) -> str:
|
||||
"""Return the mac address of the main device."""
|
||||
return self._avm_wrapper.mac
|
||||
self.mac_address = self._avm_wrapper.mac
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device information."""
|
||||
return DeviceInfo(
|
||||
configuration_url=f"http://{self._avm_wrapper.host}",
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, self.mac_address)},
|
||||
identifiers={(DOMAIN, self._avm_wrapper.unique_id)},
|
||||
manufacturer="AVM",
|
||||
model=self._avm_wrapper.model,
|
||||
name=self._device_name,
|
||||
sw_version=self._avm_wrapper.current_firmware,
|
||||
)
|
||||
|
||||
|
||||
|
@ -21,5 +21,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20250103.0"]
|
||||
"requirements": ["home-assistant-frontend==20250106.0"]
|
||||
}
|
||||
|
@ -114,6 +114,7 @@ class HiveDeviceLight(HiveEntity, LightEntity):
|
||||
self._attr_hs_color = color_util.color_RGB_to_hs(*rgb)
|
||||
self._attr_color_mode = ColorMode.HS
|
||||
else:
|
||||
color_temp = self.device["status"].get("color_temp")
|
||||
self._attr_color_temp_kelvin = (
|
||||
None
|
||||
if color_temp is None
|
||||
|
@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["holidays==0.63", "babel==2.15.0"]
|
||||
"requirements": ["holidays==0.64", "babel==2.15.0"]
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ async def _run_appliance_service[*_Ts](
|
||||
error_translation_placeholders: dict[str, str],
|
||||
) -> None:
|
||||
try:
|
||||
await hass.async_add_executor_job(getattr(appliance, method), args)
|
||||
await hass.async_add_executor_job(getattr(appliance, method), *args)
|
||||
except api.HomeConnectError as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
|
@ -220,7 +220,7 @@ async def async_setup_entry(
|
||||
with contextlib.suppress(HomeConnectError):
|
||||
programs = device.appliance.get_programs_available()
|
||||
if programs:
|
||||
for program in programs:
|
||||
for program in programs.copy():
|
||||
if program not in PROGRAMS_TRANSLATION_KEYS_MAP:
|
||||
programs.remove(program)
|
||||
if program not in programs_not_found:
|
||||
|
@ -12,6 +12,6 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["homewizard_energy"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["python-homewizard-energy==v7.0.0"],
|
||||
"requirements": ["python-homewizard-energy==v7.0.1"],
|
||||
"zeroconf": ["_hwenergy._tcp.local."]
|
||||
}
|
||||
|
@ -188,8 +188,8 @@ PINECIL_NUMBER_DESCRIPTIONS: tuple[IronOSNumberEntityDescription, ...] = (
|
||||
characteristic=CharSetting.POWER_LIMIT,
|
||||
mode=NumberMode.BOX,
|
||||
native_min_value=0,
|
||||
native_max_value=12,
|
||||
native_step=0.1,
|
||||
native_max_value=120,
|
||||
native_step=5,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_unit_of_measurement=UnitOfPower.WATT,
|
||||
entity_registry_enabled_default=False,
|
||||
|
@ -128,8 +128,8 @@
|
||||
"temp_unit": {
|
||||
"name": "Temperature display unit",
|
||||
"state": {
|
||||
"celsius": "Celsius (C°)",
|
||||
"fahrenheit": "Fahrenheit (F°)"
|
||||
"celsius": "Celsius (°C)",
|
||||
"fahrenheit": "Fahrenheit (°F)"
|
||||
}
|
||||
},
|
||||
"desc_scroll_speed": {
|
||||
|
@ -13,7 +13,7 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["demetriek"],
|
||||
"requirements": ["demetriek==1.1.0"],
|
||||
"requirements": ["demetriek==1.1.1"],
|
||||
"ssdp": [
|
||||
{
|
||||
"deviceType": "urn:schemas-upnp-org:device:LaMetric:1"
|
||||
|
@ -50,7 +50,7 @@ NUMBERS = [
|
||||
native_step=1,
|
||||
native_min_value=0,
|
||||
native_max_value=100,
|
||||
has_fn=lambda device: bool(device.audio),
|
||||
has_fn=lambda device: bool(device.audio and device.audio.available),
|
||||
value_fn=lambda device: device.audio.volume if device.audio else 0,
|
||||
set_value_fn=lambda api, volume: api.audio(volume=int(volume)),
|
||||
),
|
||||
|
@ -53,6 +53,6 @@
|
||||
"requirements": [
|
||||
"aiolifx==1.1.2",
|
||||
"aiolifx-effects==0.3.2",
|
||||
"aiolifx-themes==0.5.5"
|
||||
"aiolifx-themes==0.6.0"
|
||||
]
|
||||
}
|
||||
|
@ -354,7 +354,7 @@ def filter_turn_off_params(
|
||||
if not params:
|
||||
return params
|
||||
|
||||
supported_features = light.supported_features
|
||||
supported_features = light.supported_features_compat
|
||||
|
||||
if LightEntityFeature.FLASH not in supported_features:
|
||||
params.pop(ATTR_FLASH, None)
|
||||
@ -366,7 +366,7 @@ def filter_turn_off_params(
|
||||
|
||||
def filter_turn_on_params(light: LightEntity, params: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Filter out params not supported by the light."""
|
||||
supported_features = light.supported_features
|
||||
supported_features = light.supported_features_compat
|
||||
|
||||
if LightEntityFeature.EFFECT not in supported_features:
|
||||
params.pop(ATTR_EFFECT, None)
|
||||
@ -1093,7 +1093,7 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def capability_attributes(self) -> dict[str, Any]:
|
||||
"""Return capability attributes."""
|
||||
data: dict[str, Any] = {}
|
||||
supported_features = self.supported_features
|
||||
supported_features = self.supported_features_compat
|
||||
supported_color_modes = self._light_internal_supported_color_modes
|
||||
|
||||
if ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
@ -1255,11 +1255,12 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return state attributes."""
|
||||
data: dict[str, Any] = {}
|
||||
supported_features = self.supported_features
|
||||
supported_features = self.supported_features_compat
|
||||
supported_color_modes = self.supported_color_modes
|
||||
legacy_supported_color_modes = (
|
||||
supported_color_modes or self._light_internal_supported_color_modes
|
||||
)
|
||||
supported_features_value = supported_features.value
|
||||
_is_on = self.is_on
|
||||
color_mode = self._light_internal_color_mode if _is_on else None
|
||||
|
||||
@ -1278,6 +1279,13 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
data[ATTR_BRIGHTNESS] = self.brightness
|
||||
else:
|
||||
data[ATTR_BRIGHTNESS] = None
|
||||
elif supported_features_value & _DEPRECATED_SUPPORT_BRIGHTNESS.value:
|
||||
# Backwards compatibility for ambiguous / incomplete states
|
||||
# Warning is printed by supported_features_compat, remove in 2025.1
|
||||
if _is_on:
|
||||
data[ATTR_BRIGHTNESS] = self.brightness
|
||||
else:
|
||||
data[ATTR_BRIGHTNESS] = None
|
||||
|
||||
if color_temp_supported(supported_color_modes):
|
||||
if color_mode == ColorMode.COLOR_TEMP:
|
||||
@ -1292,6 +1300,21 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
else:
|
||||
data[ATTR_COLOR_TEMP_KELVIN] = None
|
||||
data[_DEPRECATED_ATTR_COLOR_TEMP.value] = None
|
||||
elif supported_features_value & _DEPRECATED_SUPPORT_COLOR_TEMP.value:
|
||||
# Backwards compatibility
|
||||
# Warning is printed by supported_features_compat, remove in 2025.1
|
||||
if _is_on:
|
||||
color_temp_kelvin = self.color_temp_kelvin
|
||||
data[ATTR_COLOR_TEMP_KELVIN] = color_temp_kelvin
|
||||
if color_temp_kelvin:
|
||||
data[_DEPRECATED_ATTR_COLOR_TEMP.value] = (
|
||||
color_util.color_temperature_kelvin_to_mired(color_temp_kelvin)
|
||||
)
|
||||
else:
|
||||
data[_DEPRECATED_ATTR_COLOR_TEMP.value] = None
|
||||
else:
|
||||
data[ATTR_COLOR_TEMP_KELVIN] = None
|
||||
data[_DEPRECATED_ATTR_COLOR_TEMP.value] = None
|
||||
|
||||
if color_supported(legacy_supported_color_modes) or color_temp_supported(
|
||||
legacy_supported_color_modes
|
||||
@ -1329,7 +1352,24 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
type(self),
|
||||
report_issue,
|
||||
)
|
||||
return {ColorMode.ONOFF}
|
||||
supported_features = self.supported_features_compat
|
||||
supported_features_value = supported_features.value
|
||||
supported_color_modes: set[ColorMode] = set()
|
||||
|
||||
if supported_features_value & _DEPRECATED_SUPPORT_COLOR_TEMP.value:
|
||||
supported_color_modes.add(ColorMode.COLOR_TEMP)
|
||||
if supported_features_value & _DEPRECATED_SUPPORT_COLOR.value:
|
||||
supported_color_modes.add(ColorMode.HS)
|
||||
if (
|
||||
not supported_color_modes
|
||||
and supported_features_value & _DEPRECATED_SUPPORT_BRIGHTNESS.value
|
||||
):
|
||||
supported_color_modes = {ColorMode.BRIGHTNESS}
|
||||
|
||||
if not supported_color_modes:
|
||||
supported_color_modes = {ColorMode.ONOFF}
|
||||
|
||||
return supported_color_modes
|
||||
|
||||
@cached_property
|
||||
def supported_color_modes(self) -> set[ColorMode] | set[str] | None:
|
||||
@ -1341,6 +1381,37 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Flag supported features."""
|
||||
return self._attr_supported_features
|
||||
|
||||
@property
|
||||
def supported_features_compat(self) -> LightEntityFeature:
|
||||
"""Return the supported features as LightEntityFeature.
|
||||
|
||||
Remove this compatibility shim in 2025.1 or later.
|
||||
"""
|
||||
features = self.supported_features
|
||||
if type(features) is not int: # noqa: E721
|
||||
return features
|
||||
new_features = LightEntityFeature(features)
|
||||
if self._deprecated_supported_features_reported is True:
|
||||
return new_features
|
||||
self._deprecated_supported_features_reported = True
|
||||
report_issue = self._suggest_report_issue()
|
||||
report_issue += (
|
||||
" and reference "
|
||||
"https://developers.home-assistant.io/blog/2023/12/28/support-feature-magic-numbers-deprecation"
|
||||
)
|
||||
_LOGGER.warning(
|
||||
(
|
||||
"Entity %s (%s) is using deprecated supported features"
|
||||
" values which will be removed in HA Core 2025.1. Instead it should use"
|
||||
" %s and color modes, please %s"
|
||||
),
|
||||
self.entity_id,
|
||||
type(self),
|
||||
repr(new_features),
|
||||
report_issue,
|
||||
)
|
||||
return new_features
|
||||
|
||||
def __should_report_light_issue(self) -> bool:
|
||||
"""Return if light color mode issues should be reported."""
|
||||
if not self.platform:
|
||||
|
@ -57,6 +57,9 @@
|
||||
},
|
||||
"valve_position": {
|
||||
"default": "mdi:valve"
|
||||
},
|
||||
"battery_replacement_description": {
|
||||
"default": "mdi:battery-sync-outline"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -773,6 +773,19 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Flag media player features that are supported."""
|
||||
return self._attr_supported_features
|
||||
|
||||
@property
|
||||
def supported_features_compat(self) -> MediaPlayerEntityFeature:
|
||||
"""Return the supported features as MediaPlayerEntityFeature.
|
||||
|
||||
Remove this compatibility shim in 2025.1 or later.
|
||||
"""
|
||||
features = self.supported_features
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = MediaPlayerEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
|
||||
def turn_on(self) -> None:
|
||||
"""Turn the media player on."""
|
||||
raise NotImplementedError
|
||||
@ -912,85 +925,87 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
@property
|
||||
def support_play(self) -> bool:
|
||||
"""Boolean if play is supported."""
|
||||
return MediaPlayerEntityFeature.PLAY in self.supported_features
|
||||
return MediaPlayerEntityFeature.PLAY in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_pause(self) -> bool:
|
||||
"""Boolean if pause is supported."""
|
||||
return MediaPlayerEntityFeature.PAUSE in self.supported_features
|
||||
return MediaPlayerEntityFeature.PAUSE in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_stop(self) -> bool:
|
||||
"""Boolean if stop is supported."""
|
||||
return MediaPlayerEntityFeature.STOP in self.supported_features
|
||||
return MediaPlayerEntityFeature.STOP in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_seek(self) -> bool:
|
||||
"""Boolean if seek is supported."""
|
||||
return MediaPlayerEntityFeature.SEEK in self.supported_features
|
||||
return MediaPlayerEntityFeature.SEEK in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_volume_set(self) -> bool:
|
||||
"""Boolean if setting volume is supported."""
|
||||
return MediaPlayerEntityFeature.VOLUME_SET in self.supported_features
|
||||
return MediaPlayerEntityFeature.VOLUME_SET in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_volume_mute(self) -> bool:
|
||||
"""Boolean if muting volume is supported."""
|
||||
return MediaPlayerEntityFeature.VOLUME_MUTE in self.supported_features
|
||||
return MediaPlayerEntityFeature.VOLUME_MUTE in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_previous_track(self) -> bool:
|
||||
"""Boolean if previous track command supported."""
|
||||
return MediaPlayerEntityFeature.PREVIOUS_TRACK in self.supported_features
|
||||
return MediaPlayerEntityFeature.PREVIOUS_TRACK in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_next_track(self) -> bool:
|
||||
"""Boolean if next track command supported."""
|
||||
return MediaPlayerEntityFeature.NEXT_TRACK in self.supported_features
|
||||
return MediaPlayerEntityFeature.NEXT_TRACK in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_play_media(self) -> bool:
|
||||
"""Boolean if play media command supported."""
|
||||
return MediaPlayerEntityFeature.PLAY_MEDIA in self.supported_features
|
||||
return MediaPlayerEntityFeature.PLAY_MEDIA in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_select_source(self) -> bool:
|
||||
"""Boolean if select source command supported."""
|
||||
return MediaPlayerEntityFeature.SELECT_SOURCE in self.supported_features
|
||||
return MediaPlayerEntityFeature.SELECT_SOURCE in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_select_sound_mode(self) -> bool:
|
||||
"""Boolean if select sound mode command supported."""
|
||||
return MediaPlayerEntityFeature.SELECT_SOUND_MODE in self.supported_features
|
||||
return (
|
||||
MediaPlayerEntityFeature.SELECT_SOUND_MODE in self.supported_features_compat
|
||||
)
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_clear_playlist(self) -> bool:
|
||||
"""Boolean if clear playlist command supported."""
|
||||
return MediaPlayerEntityFeature.CLEAR_PLAYLIST in self.supported_features
|
||||
return MediaPlayerEntityFeature.CLEAR_PLAYLIST in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_shuffle_set(self) -> bool:
|
||||
"""Boolean if shuffle is supported."""
|
||||
return MediaPlayerEntityFeature.SHUFFLE_SET in self.supported_features
|
||||
return MediaPlayerEntityFeature.SHUFFLE_SET in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_grouping(self) -> bool:
|
||||
"""Boolean if player grouping is supported."""
|
||||
return MediaPlayerEntityFeature.GROUPING in self.supported_features
|
||||
return MediaPlayerEntityFeature.GROUPING in self.supported_features_compat
|
||||
|
||||
async def async_toggle(self) -> None:
|
||||
"""Toggle the power on the media player."""
|
||||
@ -1019,7 +1034,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
if (
|
||||
self.volume_level is not None
|
||||
and self.volume_level < 1
|
||||
and MediaPlayerEntityFeature.VOLUME_SET in self.supported_features
|
||||
and MediaPlayerEntityFeature.VOLUME_SET in self.supported_features_compat
|
||||
):
|
||||
await self.async_set_volume_level(
|
||||
min(1, self.volume_level + self.volume_step)
|
||||
@ -1037,7 +1052,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
if (
|
||||
self.volume_level is not None
|
||||
and self.volume_level > 0
|
||||
and MediaPlayerEntityFeature.VOLUME_SET in self.supported_features
|
||||
and MediaPlayerEntityFeature.VOLUME_SET in self.supported_features_compat
|
||||
):
|
||||
await self.async_set_volume_level(
|
||||
max(0, self.volume_level - self.volume_step)
|
||||
@ -1080,7 +1095,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def capability_attributes(self) -> dict[str, Any]:
|
||||
"""Return capability attributes."""
|
||||
data: dict[str, Any] = {}
|
||||
supported_features = self.supported_features
|
||||
supported_features = self.supported_features_compat
|
||||
|
||||
if (
|
||||
source_list := self.source_list
|
||||
@ -1286,7 +1301,7 @@ async def websocket_browse_media(
|
||||
connection.send_error(msg["id"], "entity_not_found", "Entity not found")
|
||||
return
|
||||
|
||||
if MediaPlayerEntityFeature.BROWSE_MEDIA not in player.supported_features:
|
||||
if MediaPlayerEntityFeature.BROWSE_MEDIA not in player.supported_features_compat:
|
||||
connection.send_message(
|
||||
websocket_api.error_message(
|
||||
msg["id"], ERR_NOT_SUPPORTED, "Player does not support browsing media"
|
||||
|
@ -7,6 +7,6 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["peblar==0.3.2"],
|
||||
"requirements": ["peblar==0.3.3"],
|
||||
"zeroconf": [{ "type": "_http._tcp.local.", "name": "pblr-*" }]
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ DESCRIPTIONS: tuple[PeblarUpdateEntityDescription, ...] = (
|
||||
key="firmware",
|
||||
device_class=UpdateDeviceClass.FIRMWARE,
|
||||
installed_fn=lambda x: x.current.firmware,
|
||||
has_fn=lambda x: x.current.firmware is not None,
|
||||
available_fn=lambda x: x.available.firmware,
|
||||
),
|
||||
PeblarUpdateEntityDescription(
|
||||
|
@ -7,6 +7,7 @@ from powerfox import (
|
||||
Powerfox,
|
||||
PowerfoxAuthenticationError,
|
||||
PowerfoxConnectionError,
|
||||
PowerfoxNoDataError,
|
||||
Poweropti,
|
||||
)
|
||||
|
||||
@ -45,5 +46,5 @@ class PowerfoxDataUpdateCoordinator(DataUpdateCoordinator[Poweropti]):
|
||||
return await self.client.device(device_id=self.device.id)
|
||||
except PowerfoxAuthenticationError as err:
|
||||
raise ConfigEntryAuthFailed(err) from err
|
||||
except PowerfoxConnectionError as err:
|
||||
except (PowerfoxConnectionError, PowerfoxNoDataError) as err:
|
||||
raise UpdateFailed(err) from err
|
||||
|
@ -6,7 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/powerfox",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["powerfox==1.0.0"],
|
||||
"requirements": ["powerfox==1.2.0"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_http._tcp.local.",
|
||||
|
@ -180,7 +180,7 @@ def guarded_import(
|
||||
# Allow import of _strptime needed by datetime.datetime.strptime
|
||||
if name == "_strptime":
|
||||
return __import__(name, globals, locals, fromlist, level)
|
||||
raise ScriptError(f"Not allowed to import {name}")
|
||||
raise ImportError(f"Not allowed to import {name}")
|
||||
|
||||
|
||||
def guarded_inplacevar(op: str, target: Any, operand: Any) -> Any:
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from base64 import urlsafe_b64decode, urlsafe_b64encode
|
||||
from http import HTTPStatus
|
||||
import logging
|
||||
from urllib import parse
|
||||
|
||||
from aiohttp import ClientError, ClientTimeout, web
|
||||
from reolink_aio.enums import VodRequestType
|
||||
@ -31,7 +31,7 @@ def async_generate_playback_proxy_url(
|
||||
return url_format.format(
|
||||
config_entry_id=config_entry_id,
|
||||
channel=channel,
|
||||
filename=parse.quote(filename, safe=""),
|
||||
filename=urlsafe_b64encode(filename.encode("utf-8")).decode("utf-8"),
|
||||
stream_res=stream_res,
|
||||
vod_type=vod_type,
|
||||
)
|
||||
@ -66,7 +66,7 @@ class PlaybackProxyView(HomeAssistantView):
|
||||
"""Get playback proxy video response."""
|
||||
retry = retry - 1
|
||||
|
||||
filename = parse.unquote(filename)
|
||||
filename_decoded = urlsafe_b64decode(filename.encode("utf-8")).decode("utf-8")
|
||||
ch = int(channel)
|
||||
try:
|
||||
host = get_host(self.hass, config_entry_id)
|
||||
@ -77,7 +77,7 @@ class PlaybackProxyView(HomeAssistantView):
|
||||
|
||||
try:
|
||||
mime_type, reolink_url = await host.api.get_vod_source(
|
||||
ch, filename, stream_res, VodRequestType(vod_type)
|
||||
ch, filename_decoded, stream_res, VodRequestType(vod_type)
|
||||
)
|
||||
except ReolinkError as err:
|
||||
_LOGGER.warning("Reolink playback proxy error: %s", str(err))
|
||||
|
@ -9,7 +9,13 @@ from datetime import timedelta
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from roborock import HomeDataRoom, RoborockException, RoborockInvalidCredentials
|
||||
from roborock import (
|
||||
HomeDataRoom,
|
||||
RoborockException,
|
||||
RoborockInvalidCredentials,
|
||||
RoborockInvalidUserAgreement,
|
||||
RoborockNoUserAgreement,
|
||||
)
|
||||
from roborock.containers import DeviceData, HomeDataDevice, HomeDataProduct, UserData
|
||||
from roborock.version_1_apis.roborock_mqtt_client_v1 import RoborockMqttClientV1
|
||||
from roborock.version_a01_apis import RoborockMqttClientA01
|
||||
@ -60,12 +66,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_credentials",
|
||||
) from err
|
||||
except RoborockInvalidUserAgreement as err:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_user_agreement",
|
||||
) from err
|
||||
except RoborockNoUserAgreement as err:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="no_user_agreement",
|
||||
) from err
|
||||
except RoborockException as err:
|
||||
raise ConfigEntryNotReady(
|
||||
"Failed to get Roborock home data",
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="home_data_fail",
|
||||
) from err
|
||||
|
||||
_LOGGER.debug("Got home data %s", home_data)
|
||||
all_devices: list[HomeDataDevice] = home_data.devices + home_data.received_devices
|
||||
device_map: dict[str, HomeDataDevice] = {
|
||||
|
@ -60,7 +60,7 @@ class RoborockFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
if user_input is not None:
|
||||
username = user_input[CONF_USERNAME]
|
||||
await self.async_set_unique_id(username.lower())
|
||||
self._abort_if_unique_id_configured()
|
||||
self._abort_if_unique_id_configured(error="already_configured_account")
|
||||
self._username = username
|
||||
_LOGGER.debug("Requesting code for Roborock account")
|
||||
self._client = RoborockApiClient(username)
|
||||
|
@ -28,7 +28,7 @@
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||
"already_configured_account": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
}
|
||||
},
|
||||
@ -422,6 +422,12 @@
|
||||
},
|
||||
"update_options_failed": {
|
||||
"message": "Failed to update Roborock options"
|
||||
},
|
||||
"invalid_user_agreement": {
|
||||
"message": "User agreement must be accepted again. Open your Roborock app and accept the agreement."
|
||||
},
|
||||
"no_user_agreement": {
|
||||
"message": "You have not valid user agreement. Open your Roborock app and accept the agreement."
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
@ -73,7 +73,6 @@ class SlideConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return {}
|
||||
|
||||
# API version 2 is not working, try API version 1 instead
|
||||
await slide.slide_del(user_input[CONF_HOST])
|
||||
await slide.slide_add(
|
||||
user_input[CONF_HOST],
|
||||
user_input.get(CONF_PASSWORD, ""),
|
||||
@ -185,14 +184,15 @@ class SlideConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
await self.async_set_unique_id(self._mac)
|
||||
|
||||
self._abort_if_unique_id_configured(
|
||||
{CONF_HOST: discovery_info.host}, reload_on_update=True
|
||||
)
|
||||
ip = str(discovery_info.ip_address)
|
||||
_LOGGER.debug("Slide device discovered, ip %s", ip)
|
||||
|
||||
self._abort_if_unique_id_configured({CONF_HOST: ip}, reload_on_update=True)
|
||||
|
||||
errors = {}
|
||||
if errors := await self.async_test_connection(
|
||||
{
|
||||
CONF_HOST: self._host,
|
||||
CONF_HOST: ip,
|
||||
}
|
||||
):
|
||||
return self.async_abort(
|
||||
@ -202,7 +202,7 @@ class SlideConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
},
|
||||
)
|
||||
|
||||
self._host = discovery_info.host
|
||||
self._host = ip
|
||||
|
||||
return await self.async_step_zeroconf_confirm()
|
||||
|
||||
|
@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/solax",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["solax"],
|
||||
"requirements": ["solax==3.2.1"]
|
||||
"requirements": ["solax==3.2.3"]
|
||||
}
|
||||
|
@ -115,6 +115,7 @@ async def build_item_response(
|
||||
item_type = CONTENT_TYPE_TO_CHILD_TYPE[search_type]
|
||||
|
||||
children = []
|
||||
list_playable = []
|
||||
for item in result["items"]:
|
||||
item_id = str(item["id"])
|
||||
item_thumbnail: str | None = None
|
||||
@ -131,7 +132,7 @@ async def build_item_response(
|
||||
child_media_class = CONTENT_TYPE_MEDIA_CLASS[MediaType.ALBUM]
|
||||
can_expand = True
|
||||
can_play = True
|
||||
elif item["hasitems"]:
|
||||
elif item["hasitems"] and not item["isaudio"]:
|
||||
child_item_type = "Favorites"
|
||||
child_media_class = CONTENT_TYPE_MEDIA_CLASS["Favorites"]
|
||||
can_expand = True
|
||||
@ -139,8 +140,8 @@ async def build_item_response(
|
||||
else:
|
||||
child_item_type = "Favorites"
|
||||
child_media_class = CONTENT_TYPE_MEDIA_CLASS[MediaType.TRACK]
|
||||
can_expand = False
|
||||
can_play = True
|
||||
can_expand = item["hasitems"]
|
||||
can_play = item["isaudio"] and item.get("url")
|
||||
|
||||
if artwork_track_id := item.get("artwork_track_id"):
|
||||
if internal_request:
|
||||
@ -166,6 +167,7 @@ async def build_item_response(
|
||||
thumbnail=item_thumbnail,
|
||||
)
|
||||
)
|
||||
list_playable.append(can_play)
|
||||
|
||||
if children is None:
|
||||
raise BrowseError(f"Media not found: {search_type} / {search_id}")
|
||||
@ -179,7 +181,7 @@ async def build_item_response(
|
||||
children_media_class=media_class["children"],
|
||||
media_content_id=search_id,
|
||||
media_content_type=search_type,
|
||||
can_play=search_type != "Favorites",
|
||||
can_play=any(list_playable),
|
||||
children=children,
|
||||
can_expand=True,
|
||||
)
|
||||
|
@ -7,5 +7,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pysuez", "regex"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pysuezV2==1.3.5"]
|
||||
"requirements": ["pysuezV2==2.0.1"]
|
||||
}
|
||||
|
@ -300,5 +300,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/tplink",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["kasa"],
|
||||
"requirements": ["python-kasa[speedups]==0.9.0"]
|
||||
"requirements": ["python-kasa[speedups]==0.9.1"]
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
},
|
||||
"user_auth_confirm": {
|
||||
"title": "Authenticate",
|
||||
"description": "The device requires authentication, please input your TP-Link credentials below.",
|
||||
"description": "The device requires authentication, please input your TP-Link credentials below. Note, that both e-mail and password are case-sensitive.",
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
|
@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["twentemilieu"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["twentemilieu==2.2.0"]
|
||||
"requirements": ["twentemilieu==2.2.1"]
|
||||
}
|
||||
|
@ -40,7 +40,7 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["uiprotect", "unifi_discovery"],
|
||||
"requirements": ["uiprotect==7.1.0", "unifi-discovery==1.2.0"],
|
||||
"requirements": ["uiprotect==7.4.1", "unifi-discovery==1.2.0"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Ubiquiti Networks",
|
||||
|
@ -312,7 +312,7 @@ class StateVacuumEntity(
|
||||
@property
|
||||
def capability_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return capability attributes."""
|
||||
if VacuumEntityFeature.FAN_SPEED in self.supported_features:
|
||||
if VacuumEntityFeature.FAN_SPEED in self.supported_features_compat:
|
||||
return {ATTR_FAN_SPEED_LIST: self.fan_speed_list}
|
||||
return None
|
||||
|
||||
@ -330,7 +330,7 @@ class StateVacuumEntity(
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the state attributes of the vacuum cleaner."""
|
||||
data: dict[str, Any] = {}
|
||||
supported_features = self.supported_features
|
||||
supported_features = self.supported_features_compat
|
||||
|
||||
if VacuumEntityFeature.BATTERY in supported_features:
|
||||
data[ATTR_BATTERY_LEVEL] = self.battery_level
|
||||
@ -369,6 +369,19 @@ class StateVacuumEntity(
|
||||
"""Flag vacuum cleaner features that are supported."""
|
||||
return self._attr_supported_features
|
||||
|
||||
@property
|
||||
def supported_features_compat(self) -> VacuumEntityFeature:
|
||||
"""Return the supported features as VacuumEntityFeature.
|
||||
|
||||
Remove this compatibility shim in 2025.1 or later.
|
||||
"""
|
||||
features = self.supported_features
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = VacuumEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
|
||||
def stop(self, **kwargs: Any) -> None:
|
||||
"""Stop the vacuum cleaner."""
|
||||
raise NotImplementedError
|
||||
|
@ -3,7 +3,7 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "For Origin and Destination, enter the address or the GPS coordinates of the location (GPS coordinates has to be separated by a comma). You can also enter an entity id which provides this information in its state, an entity id with latitude and longitude attributes, or zone friendly name.",
|
||||
"description": "For Origin and Destination, enter the address or the GPS coordinates of the location (GPS coordinates has to be separated by a comma). You can also enter an entity ID which provides this information in its state, an entity ID with latitude and longitude attributes, or zone friendly name.",
|
||||
"data": {
|
||||
"name": "[%key:common::config_flow::data::name%]",
|
||||
"origin": "Origin",
|
||||
@ -26,13 +26,13 @@
|
||||
"description": "Some options will allow you to force the integration to use a particular route or avoid a particular route in its time travel calculation.",
|
||||
"data": {
|
||||
"units": "Units",
|
||||
"vehicle_type": "Vehicle Type",
|
||||
"vehicle_type": "Vehicle type",
|
||||
"incl_filter": "Exact streetname which must be part of the selected route",
|
||||
"excl_filter": "Exact streetname which must NOT be part of the selected route",
|
||||
"realtime": "Realtime Travel Time?",
|
||||
"avoid_toll_roads": "Avoid Toll Roads?",
|
||||
"avoid_ferries": "Avoid Ferries?",
|
||||
"avoid_subscription_roads": "Avoid Roads Needing a Vignette / Subscription?"
|
||||
"realtime": "Realtime travel time?",
|
||||
"avoid_toll_roads": "Avoid toll roads?",
|
||||
"avoid_ferries": "Avoid ferries?",
|
||||
"avoid_subscription_roads": "Avoid roads needing a vignette / subscription?"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,8 +47,8 @@
|
||||
},
|
||||
"units": {
|
||||
"options": {
|
||||
"metric": "Metric System",
|
||||
"imperial": "Imperial System"
|
||||
"metric": "Metric system",
|
||||
"imperial": "Imperial system"
|
||||
}
|
||||
},
|
||||
"region": {
|
||||
@ -63,8 +63,8 @@
|
||||
},
|
||||
"services": {
|
||||
"get_travel_times": {
|
||||
"name": "Get Travel Times",
|
||||
"description": "Get route alternatives and travel times between two locations.",
|
||||
"name": "Get travel times",
|
||||
"description": "Retrieves route alternatives and travel times between two locations.",
|
||||
"fields": {
|
||||
"origin": {
|
||||
"name": "[%key:component::waze_travel_time::config::step::user::data::origin%]",
|
||||
@ -76,7 +76,7 @@
|
||||
},
|
||||
"region": {
|
||||
"name": "[%key:component::waze_travel_time::config::step::user::data::region%]",
|
||||
"description": "The region. Controls which waze server is used."
|
||||
"description": "The region. Controls which Waze server is used."
|
||||
},
|
||||
"units": {
|
||||
"name": "[%key:component::waze_travel_time::options::step::init::data::units%]",
|
||||
|
@ -7,5 +7,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["holidays"],
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["holidays==0.63"]
|
||||
"requirements": ["holidays==0.64"]
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
"zha",
|
||||
"universal_silabs_flasher"
|
||||
],
|
||||
"requirements": ["universal-silabs-flasher==0.0.25", "zha==0.0.44"],
|
||||
"requirements": ["universal-silabs-flasher==0.0.25", "zha==0.0.45"],
|
||||
"usb": [
|
||||
{
|
||||
"vid": "10C4",
|
||||
|
@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2025
|
||||
MINOR_VERSION: Final = 1
|
||||
PATCH_VERSION: Final = "0"
|
||||
PATCH_VERSION: Final = "1"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
||||
|
@ -7,7 +7,7 @@ import asyncio
|
||||
from collections import deque
|
||||
from collections.abc import Callable, Coroutine, Iterable, Mapping
|
||||
import dataclasses
|
||||
from enum import Enum, auto
|
||||
from enum import Enum, IntFlag, auto
|
||||
import functools as ft
|
||||
import logging
|
||||
import math
|
||||
@ -1639,6 +1639,31 @@ class Entity(
|
||||
self.hass, integration_domain=platform_name, module=type(self).__module__
|
||||
)
|
||||
|
||||
@callback
|
||||
def _report_deprecated_supported_features_values(
|
||||
self, replacement: IntFlag
|
||||
) -> None:
|
||||
"""Report deprecated supported features values."""
|
||||
if self._deprecated_supported_features_reported is True:
|
||||
return
|
||||
self._deprecated_supported_features_reported = True
|
||||
report_issue = self._suggest_report_issue()
|
||||
report_issue += (
|
||||
" and reference "
|
||||
"https://developers.home-assistant.io/blog/2023/12/28/support-feature-magic-numbers-deprecation"
|
||||
)
|
||||
_LOGGER.warning(
|
||||
(
|
||||
"Entity %s (%s) is using deprecated supported features"
|
||||
" values which will be removed in HA Core 2025.1. Instead it should use"
|
||||
" %s, please %s"
|
||||
),
|
||||
self.entity_id,
|
||||
type(self),
|
||||
repr(replacement),
|
||||
report_issue,
|
||||
)
|
||||
|
||||
|
||||
class ToggleEntityDescription(EntityDescription, frozen_or_thawed=True):
|
||||
"""A class that describes toggle entities."""
|
||||
|
@ -31,11 +31,11 @@ dbus-fast==2.24.3
|
||||
fnv-hash-fast==1.0.2
|
||||
go2rtc-client==0.1.2
|
||||
ha-ffmpeg==3.2.2
|
||||
habluetooth==3.6.0
|
||||
habluetooth==3.7.0
|
||||
hass-nabucasa==0.87.0
|
||||
hassil==2.1.0
|
||||
home-assistant-bluetooth==1.13.0
|
||||
home-assistant-frontend==20250103.0
|
||||
home-assistant-frontend==20250106.0
|
||||
home-assistant-intents==2025.1.1
|
||||
httpx==0.27.2
|
||||
ifaddr==0.2.0
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2025.1.0"
|
||||
version = "2025.1.1"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
readme = "README.rst"
|
||||
|
@ -282,7 +282,7 @@ aiokef==0.2.16
|
||||
aiolifx-effects==0.3.2
|
||||
|
||||
# homeassistant.components.lifx
|
||||
aiolifx-themes==0.5.5
|
||||
aiolifx-themes==0.6.0
|
||||
|
||||
# homeassistant.components.lifx
|
||||
aiolifx==1.1.2
|
||||
@ -585,7 +585,7 @@ bizkaibus==0.1.1
|
||||
|
||||
# homeassistant.components.eq3btsmart
|
||||
# homeassistant.components.esphome
|
||||
bleak-esphome==1.1.0
|
||||
bleak-esphome==2.0.0
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bleak-retry-connector==3.6.0
|
||||
@ -704,7 +704,7 @@ connect-box==0.3.1
|
||||
construct==2.10.68
|
||||
|
||||
# homeassistant.components.cookidoo
|
||||
cookidoo-api==0.10.0
|
||||
cookidoo-api==0.11.2
|
||||
|
||||
# homeassistant.components.backup
|
||||
# homeassistant.components.utility_meter
|
||||
@ -749,7 +749,7 @@ defusedxml==0.7.1
|
||||
deluge-client==1.10.2
|
||||
|
||||
# homeassistant.components.lametric
|
||||
demetriek==1.1.0
|
||||
demetriek==1.1.1
|
||||
|
||||
# homeassistant.components.denonavr
|
||||
denonavr==1.0.1
|
||||
@ -1091,7 +1091,7 @@ ha-philipsjs==3.2.2
|
||||
habitipy==0.3.3
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
habluetooth==3.6.0
|
||||
habluetooth==3.7.0
|
||||
|
||||
# homeassistant.components.cloud
|
||||
hass-nabucasa==0.87.0
|
||||
@ -1131,10 +1131,10 @@ hole==0.8.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.63
|
||||
holidays==0.64
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20250103.0
|
||||
home-assistant-frontend==20250106.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2025.1.1
|
||||
@ -1561,7 +1561,7 @@ openhomedevice==2.2.0
|
||||
opensensemap-api==0.2.0
|
||||
|
||||
# homeassistant.components.enigma2
|
||||
openwebifpy==4.3.0
|
||||
openwebifpy==4.3.1
|
||||
|
||||
# homeassistant.components.luci
|
||||
openwrt-luci-rpc==1.1.17
|
||||
@ -1603,7 +1603,7 @@ panasonic-viera==0.4.2
|
||||
pdunehd==1.3.2
|
||||
|
||||
# homeassistant.components.peblar
|
||||
peblar==0.3.2
|
||||
peblar==0.3.3
|
||||
|
||||
# homeassistant.components.peco
|
||||
peco==0.0.30
|
||||
@ -1650,7 +1650,7 @@ pmsensor==0.4
|
||||
poolsense==0.0.8
|
||||
|
||||
# homeassistant.components.powerfox
|
||||
powerfox==1.0.0
|
||||
powerfox==1.2.0
|
||||
|
||||
# homeassistant.components.reddit
|
||||
praw==7.5.0
|
||||
@ -2309,7 +2309,7 @@ pysqueezebox==0.10.0
|
||||
pystiebeleltron==0.0.1.dev2
|
||||
|
||||
# homeassistant.components.suez_water
|
||||
pysuezV2==1.3.5
|
||||
pysuezV2==2.0.1
|
||||
|
||||
# homeassistant.components.switchbee
|
||||
pyswitchbee==1.8.3
|
||||
@ -2363,7 +2363,7 @@ python-gitlab==1.6.0
|
||||
python-homeassistant-analytics==0.8.1
|
||||
|
||||
# homeassistant.components.homewizard
|
||||
python-homewizard-energy==v7.0.0
|
||||
python-homewizard-energy==v7.0.1
|
||||
|
||||
# homeassistant.components.hp_ilo
|
||||
python-hpilo==4.4.3
|
||||
@ -2378,7 +2378,7 @@ python-join-api==0.0.9
|
||||
python-juicenet==1.1.0
|
||||
|
||||
# homeassistant.components.tplink
|
||||
python-kasa[speedups]==0.9.0
|
||||
python-kasa[speedups]==0.9.1
|
||||
|
||||
# homeassistant.components.linkplay
|
||||
python-linkplay==0.1.1
|
||||
@ -2720,7 +2720,7 @@ solaredge-local==0.2.3
|
||||
solarlog_cli==0.4.0
|
||||
|
||||
# homeassistant.components.solax
|
||||
solax==3.2.1
|
||||
solax==3.2.3
|
||||
|
||||
# homeassistant.components.somfy_mylink
|
||||
somfy-mylink-synergy==1.0.6
|
||||
@ -2895,7 +2895,7 @@ ttn_client==1.2.0
|
||||
tuya-device-sharing-sdk==0.2.1
|
||||
|
||||
# homeassistant.components.twentemilieu
|
||||
twentemilieu==2.2.0
|
||||
twentemilieu==2.2.1
|
||||
|
||||
# homeassistant.components.twilio
|
||||
twilio==6.32.0
|
||||
@ -2910,7 +2910,7 @@ typedmonarchmoney==0.3.1
|
||||
uasiren==0.0.1
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
uiprotect==7.1.0
|
||||
uiprotect==7.4.1
|
||||
|
||||
# homeassistant.components.landisgyr_heat_meter
|
||||
ultraheat-api==0.5.7
|
||||
@ -3100,7 +3100,7 @@ zeroconf==0.136.2
|
||||
zeversolar==0.3.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==0.0.44
|
||||
zha==0.0.45
|
||||
|
||||
# homeassistant.components.zhong_hong
|
||||
zhong-hong-hvac==1.0.13
|
||||
|
@ -264,7 +264,7 @@ aiokafka==0.10.0
|
||||
aiolifx-effects==0.3.2
|
||||
|
||||
# homeassistant.components.lifx
|
||||
aiolifx-themes==0.5.5
|
||||
aiolifx-themes==0.6.0
|
||||
|
||||
# homeassistant.components.lifx
|
||||
aiolifx==1.1.2
|
||||
@ -516,7 +516,7 @@ bimmer-connected[china]==0.17.2
|
||||
|
||||
# homeassistant.components.eq3btsmart
|
||||
# homeassistant.components.esphome
|
||||
bleak-esphome==1.1.0
|
||||
bleak-esphome==2.0.0
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
bleak-retry-connector==3.6.0
|
||||
@ -600,7 +600,7 @@ colorthief==0.2.1
|
||||
construct==2.10.68
|
||||
|
||||
# homeassistant.components.cookidoo
|
||||
cookidoo-api==0.10.0
|
||||
cookidoo-api==0.11.2
|
||||
|
||||
# homeassistant.components.backup
|
||||
# homeassistant.components.utility_meter
|
||||
@ -639,7 +639,7 @@ defusedxml==0.7.1
|
||||
deluge-client==1.10.2
|
||||
|
||||
# homeassistant.components.lametric
|
||||
demetriek==1.1.0
|
||||
demetriek==1.1.1
|
||||
|
||||
# homeassistant.components.denonavr
|
||||
denonavr==1.0.1
|
||||
@ -932,7 +932,7 @@ ha-philipsjs==3.2.2
|
||||
habitipy==0.3.3
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
habluetooth==3.6.0
|
||||
habluetooth==3.7.0
|
||||
|
||||
# homeassistant.components.cloud
|
||||
hass-nabucasa==0.87.0
|
||||
@ -960,10 +960,10 @@ hole==0.8.0
|
||||
|
||||
# homeassistant.components.holiday
|
||||
# homeassistant.components.workday
|
||||
holidays==0.63
|
||||
holidays==0.64
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20250103.0
|
||||
home-assistant-frontend==20250106.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2025.1.1
|
||||
@ -1303,7 +1303,7 @@ openerz-api==0.3.0
|
||||
openhomedevice==2.2.0
|
||||
|
||||
# homeassistant.components.enigma2
|
||||
openwebifpy==4.3.0
|
||||
openwebifpy==4.3.1
|
||||
|
||||
# homeassistant.components.opower
|
||||
opower==0.8.7
|
||||
@ -1330,7 +1330,7 @@ panasonic-viera==0.4.2
|
||||
pdunehd==1.3.2
|
||||
|
||||
# homeassistant.components.peblar
|
||||
peblar==0.3.2
|
||||
peblar==0.3.3
|
||||
|
||||
# homeassistant.components.peco
|
||||
peco==0.0.30
|
||||
@ -1360,7 +1360,7 @@ plumlightpad==0.0.11
|
||||
poolsense==0.0.8
|
||||
|
||||
# homeassistant.components.powerfox
|
||||
powerfox==1.0.0
|
||||
powerfox==1.2.0
|
||||
|
||||
# homeassistant.components.reddit
|
||||
praw==7.5.0
|
||||
@ -1875,7 +1875,7 @@ pyspeex-noise==1.0.2
|
||||
pysqueezebox==0.10.0
|
||||
|
||||
# homeassistant.components.suez_water
|
||||
pysuezV2==1.3.5
|
||||
pysuezV2==2.0.1
|
||||
|
||||
# homeassistant.components.switchbee
|
||||
pyswitchbee==1.8.3
|
||||
@ -1905,7 +1905,7 @@ python-fullykiosk==0.0.14
|
||||
python-homeassistant-analytics==0.8.1
|
||||
|
||||
# homeassistant.components.homewizard
|
||||
python-homewizard-energy==v7.0.0
|
||||
python-homewizard-energy==v7.0.1
|
||||
|
||||
# homeassistant.components.izone
|
||||
python-izone==1.2.9
|
||||
@ -1914,7 +1914,7 @@ python-izone==1.2.9
|
||||
python-juicenet==1.1.0
|
||||
|
||||
# homeassistant.components.tplink
|
||||
python-kasa[speedups]==0.9.0
|
||||
python-kasa[speedups]==0.9.1
|
||||
|
||||
# homeassistant.components.linkplay
|
||||
python-linkplay==0.1.1
|
||||
@ -2181,7 +2181,7 @@ soco==0.30.6
|
||||
solarlog_cli==0.4.0
|
||||
|
||||
# homeassistant.components.solax
|
||||
solax==3.2.1
|
||||
solax==3.2.3
|
||||
|
||||
# homeassistant.components.somfy_mylink
|
||||
somfy-mylink-synergy==1.0.6
|
||||
@ -2317,7 +2317,7 @@ ttn_client==1.2.0
|
||||
tuya-device-sharing-sdk==0.2.1
|
||||
|
||||
# homeassistant.components.twentemilieu
|
||||
twentemilieu==2.2.0
|
||||
twentemilieu==2.2.1
|
||||
|
||||
# homeassistant.components.twilio
|
||||
twilio==6.32.0
|
||||
@ -2332,7 +2332,7 @@ typedmonarchmoney==0.3.1
|
||||
uasiren==0.0.1
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
uiprotect==7.1.0
|
||||
uiprotect==7.4.1
|
||||
|
||||
# homeassistant.components.landisgyr_heat_meter
|
||||
ultraheat-api==0.5.7
|
||||
@ -2489,7 +2489,7 @@ zeroconf==0.136.2
|
||||
zeversolar==0.3.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==0.0.44
|
||||
zha==0.0.45
|
||||
|
||||
# homeassistant.components.zwave_js
|
||||
zwave-js-server-python==0.60.0
|
||||
|
@ -826,6 +826,26 @@ def test_deprecated_state_constants(
|
||||
import_and_test_deprecated_constant_enum(caplog, module, enum, "STATE_", "2025.10")
|
||||
|
||||
|
||||
def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Test deprecated supported features ints."""
|
||||
|
||||
class MockCamera(camera.Camera):
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Return supported features."""
|
||||
return 1
|
||||
|
||||
entity = MockCamera()
|
||||
assert entity.supported_features_compat is camera.CameraEntityFeature(1)
|
||||
assert "MockCamera" in caplog.text
|
||||
assert "is using deprecated supported features values" in caplog.text
|
||||
assert "Instead it should use" in caplog.text
|
||||
assert "CameraEntityFeature.ON_OFF" in caplog.text
|
||||
caplog.clear()
|
||||
assert entity.supported_features_compat is camera.CameraEntityFeature(1)
|
||||
assert "is using deprecated supported features values" not in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_camera")
|
||||
async def test_entity_picture_url_changes_on_token_update(hass: HomeAssistant) -> None:
|
||||
"""Test the token is rotated and entity entity picture cache is cleared."""
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
from enum import Enum
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import cover
|
||||
from homeassistant.components.cover import CoverState
|
||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM, SERVICE_TOGGLE
|
||||
@ -153,3 +155,20 @@ def _create_tuples(enum: type[Enum], constant_prefix: str) -> list[tuple[Enum, s
|
||||
def test_all() -> None:
|
||||
"""Test module.__all__ is correctly set."""
|
||||
help_test_all(cover)
|
||||
|
||||
|
||||
def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Test deprecated supported features ints."""
|
||||
|
||||
class MockCoverEntity(cover.CoverEntity):
|
||||
_attr_supported_features = 1
|
||||
|
||||
entity = MockCoverEntity()
|
||||
assert entity.supported_features is cover.CoverEntityFeature(1)
|
||||
assert "MockCoverEntity" in caplog.text
|
||||
assert "is using deprecated supported features values" in caplog.text
|
||||
assert "Instead it should use" in caplog.text
|
||||
assert "CoverEntityFeature.OPEN" in caplog.text
|
||||
caplog.clear()
|
||||
assert entity.supported_features is cover.CoverEntityFeature(1)
|
||||
assert "is using deprecated supported features values" not in caplog.text
|
||||
|
@ -4,7 +4,6 @@ from __future__ import annotations
|
||||
|
||||
from aioesphomeapi import APIClient, APIVersion, BluetoothProxyFeature, DeviceInfo
|
||||
from bleak.exc import BleakError
|
||||
from bleak_esphome.backend.cache import ESPHomeBluetoothCache
|
||||
from bleak_esphome.backend.client import ESPHomeClient, ESPHomeClientData
|
||||
from bleak_esphome.backend.device import ESPHomeBluetoothDevice
|
||||
from bleak_esphome.backend.scanner import ESPHomeScanner
|
||||
@ -27,7 +26,6 @@ async def client_data_fixture(
|
||||
connector = HaBluetoothConnector(ESPHomeClientData, ESP_MAC_ADDRESS, lambda: True)
|
||||
return ESPHomeClientData(
|
||||
bluetooth_device=ESPHomeBluetoothDevice(ESP_NAME, ESP_MAC_ADDRESS),
|
||||
cache=ESPHomeBluetoothCache(),
|
||||
client=mock_client,
|
||||
device_info=DeviceInfo(
|
||||
mac_address=ESP_MAC_ADDRESS,
|
||||
|
@ -10,11 +10,16 @@ from homeassistant.components.home_connect.const import (
|
||||
BSH_ACTIVE_PROGRAM,
|
||||
BSH_SELECTED_PROGRAM,
|
||||
)
|
||||
from homeassistant.components.select import ATTR_OPTION, DOMAIN as SELECT_DOMAIN
|
||||
from homeassistant.components.select import (
|
||||
ATTR_OPTION,
|
||||
ATTR_OPTIONS,
|
||||
DOMAIN as SELECT_DOMAIN,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_SELECT_OPTION, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .conftest import get_all_appliances
|
||||
|
||||
@ -52,6 +57,40 @@ async def test_select(
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
async def test_filter_unknown_programs(
|
||||
bypass_throttle: Generator[None],
|
||||
hass: HomeAssistant,
|
||||
config_entry: MockConfigEntry,
|
||||
integration_setup: Callable[[], Awaitable[bool]],
|
||||
setup_credentials: None,
|
||||
get_appliances: Mock,
|
||||
appliance: Mock,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test select that programs that are not part of the official Home Connect API specification are filtered out.
|
||||
|
||||
We use two programs to ensure that programs are iterated over a copy of the list,
|
||||
and it does not raise problems when removing an element from the original list.
|
||||
"""
|
||||
appliance.status.update(SETTINGS_STATUS)
|
||||
appliance.get_programs_available.return_value = [
|
||||
PROGRAM,
|
||||
"NonOfficialProgram",
|
||||
"AntotherNonOfficialProgram",
|
||||
]
|
||||
get_appliances.return_value = [appliance]
|
||||
|
||||
assert config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
assert await integration_setup()
|
||||
assert config_entry.state is ConfigEntryState.LOADED
|
||||
|
||||
entity = entity_registry.async_get("select.washer_selected_program")
|
||||
assert entity
|
||||
assert entity.capabilities.get(ATTR_OPTIONS) == [
|
||||
"dishcare_dishwasher_program_eco_50"
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "status", "program_to_set"),
|
||||
[
|
||||
|
@ -620,10 +620,10 @@
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 12,
|
||||
'max': 120,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
'step': 5,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
@ -656,10 +656,10 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Pinecil Power limit',
|
||||
'max': 12,
|
||||
'max': 120,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
'step': 5,
|
||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
|
@ -126,7 +126,7 @@ async def test_state(
|
||||
2.0,
|
||||
2.0,
|
||||
),
|
||||
("number.pinecil_power_limit", CharSetting.POWER_LIMIT, 12.0, 12.0),
|
||||
("number.pinecil_power_limit", CharSetting.POWER_LIMIT, 120, 120),
|
||||
("number.pinecil_quick_charge_voltage", CharSetting.QC_IDEAL_VOLTAGE, 9.0, 9.0),
|
||||
(
|
||||
"number.pinecil_short_press_temperature_step",
|
||||
|
@ -2,6 +2,7 @@
|
||||
# name: test_diagnostics
|
||||
dict({
|
||||
'audio': dict({
|
||||
'available': True,
|
||||
'volume': 100,
|
||||
'volume_limit': dict({
|
||||
'range_max': 100,
|
||||
|
@ -26,7 +26,6 @@ from homeassistant.components.light import (
|
||||
DOMAIN,
|
||||
ColorMode,
|
||||
LightEntity,
|
||||
LightEntityFeature,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
@ -157,7 +156,7 @@ class MockLight(MockToggleEntity, LightEntity):
|
||||
|
||||
_attr_max_color_temp_kelvin = DEFAULT_MAX_KELVIN
|
||||
_attr_min_color_temp_kelvin = DEFAULT_MIN_KELVIN
|
||||
supported_features = LightEntityFeature(0)
|
||||
supported_features = 0
|
||||
|
||||
brightness = None
|
||||
color_temp_kelvin = None
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""The tests for the Light component."""
|
||||
|
||||
from types import ModuleType
|
||||
from typing import Literal
|
||||
from unittest.mock import MagicMock, mock_open, patch
|
||||
|
||||
import pytest
|
||||
@ -137,8 +138,13 @@ async def test_services(
|
||||
ent3.supported_color_modes = [light.ColorMode.HS]
|
||||
ent1.supported_features = light.LightEntityFeature.TRANSITION
|
||||
ent2.supported_features = (
|
||||
light.LightEntityFeature.EFFECT | light.LightEntityFeature.TRANSITION
|
||||
light.SUPPORT_COLOR
|
||||
| light.LightEntityFeature.EFFECT
|
||||
| light.LightEntityFeature.TRANSITION
|
||||
)
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
ent2.supported_color_modes = None
|
||||
ent2.color_mode = None
|
||||
ent3.supported_features = (
|
||||
light.LightEntityFeature.FLASH | light.LightEntityFeature.TRANSITION
|
||||
)
|
||||
@ -254,7 +260,10 @@ async def test_services(
|
||||
}
|
||||
|
||||
_, data = ent2.last_call("turn_on")
|
||||
assert data == {light.ATTR_EFFECT: "fun_effect"}
|
||||
assert data == {
|
||||
light.ATTR_EFFECT: "fun_effect",
|
||||
light.ATTR_HS_COLOR: (0, 0),
|
||||
}
|
||||
|
||||
_, data = ent3.last_call("turn_on")
|
||||
assert data == {light.ATTR_FLASH: "short", light.ATTR_HS_COLOR: (71.059, 100)}
|
||||
@ -338,6 +347,8 @@ async def test_services(
|
||||
|
||||
_, data = ent2.last_call("turn_on")
|
||||
assert data == {
|
||||
light.ATTR_BRIGHTNESS: 100,
|
||||
light.ATTR_HS_COLOR: profile.hs_color,
|
||||
light.ATTR_TRANSITION: 1,
|
||||
}
|
||||
|
||||
@ -915,12 +926,16 @@ async def test_light_brightness_step(hass: HomeAssistant) -> None:
|
||||
setup_test_component_platform(hass, light.DOMAIN, entities)
|
||||
|
||||
entity0 = entities[0]
|
||||
entity0.supported_color_modes = {light.ColorMode.BRIGHTNESS}
|
||||
entity0.color_mode = light.ColorMode.BRIGHTNESS
|
||||
entity0.supported_features = light.SUPPORT_BRIGHTNESS
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
entity0.supported_color_modes = None
|
||||
entity0.color_mode = None
|
||||
entity0.brightness = 100
|
||||
entity1 = entities[1]
|
||||
entity1.supported_color_modes = {light.ColorMode.BRIGHTNESS}
|
||||
entity1.color_mode = light.ColorMode.BRIGHTNESS
|
||||
entity1.supported_features = light.SUPPORT_BRIGHTNESS
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
entity1.supported_color_modes = None
|
||||
entity1.color_mode = None
|
||||
entity1.brightness = 50
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
@ -981,8 +996,10 @@ async def test_light_brightness_pct_conversion(
|
||||
setup_test_component_platform(hass, light.DOMAIN, mock_light_entities)
|
||||
|
||||
entity = mock_light_entities[0]
|
||||
entity.supported_color_modes = {light.ColorMode.BRIGHTNESS}
|
||||
entity.color_mode = light.ColorMode.BRIGHTNESS
|
||||
entity.supported_features = light.SUPPORT_BRIGHTNESS
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
entity.supported_color_modes = None
|
||||
entity.color_mode = None
|
||||
entity.brightness = 100
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
@ -1131,6 +1148,167 @@ invalid_no_brightness_no_color_no_transition,,,
|
||||
assert invalid_profile_name not in profiles.data
|
||||
|
||||
|
||||
@pytest.mark.parametrize("light_state", [STATE_ON, STATE_OFF])
|
||||
async def test_light_backwards_compatibility_supported_color_modes(
|
||||
hass: HomeAssistant, light_state: Literal["on", "off"]
|
||||
) -> None:
|
||||
"""Test supported_color_modes if not implemented by the entity."""
|
||||
entities = [
|
||||
MockLight("Test_0", light_state),
|
||||
MockLight("Test_1", light_state),
|
||||
MockLight("Test_2", light_state),
|
||||
MockLight("Test_3", light_state),
|
||||
MockLight("Test_4", light_state),
|
||||
]
|
||||
|
||||
entity0 = entities[0]
|
||||
|
||||
entity1 = entities[1]
|
||||
entity1.supported_features = light.SUPPORT_BRIGHTNESS
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
entity1.supported_color_modes = None
|
||||
entity1.color_mode = None
|
||||
|
||||
entity2 = entities[2]
|
||||
entity2.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR_TEMP
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
entity2.supported_color_modes = None
|
||||
entity2.color_mode = None
|
||||
|
||||
entity3 = entities[3]
|
||||
entity3.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
entity3.supported_color_modes = None
|
||||
entity3.color_mode = None
|
||||
|
||||
entity4 = entities[4]
|
||||
entity4.supported_features = (
|
||||
light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_COLOR_TEMP
|
||||
)
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
entity4.supported_color_modes = None
|
||||
entity4.color_mode = None
|
||||
|
||||
setup_test_component_platform(hass, light.DOMAIN, entities)
|
||||
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.ONOFF]
|
||||
if light_state == STATE_OFF:
|
||||
assert state.attributes["color_mode"] is None
|
||||
else:
|
||||
assert state.attributes["color_mode"] == light.ColorMode.ONOFF
|
||||
|
||||
state = hass.states.get(entity1.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.BRIGHTNESS]
|
||||
if light_state == STATE_OFF:
|
||||
assert state.attributes["color_mode"] is None
|
||||
else:
|
||||
assert state.attributes["color_mode"] == light.ColorMode.UNKNOWN
|
||||
|
||||
state = hass.states.get(entity2.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.COLOR_TEMP]
|
||||
if light_state == STATE_OFF:
|
||||
assert state.attributes["color_mode"] is None
|
||||
else:
|
||||
assert state.attributes["color_mode"] == light.ColorMode.UNKNOWN
|
||||
|
||||
state = hass.states.get(entity3.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.HS]
|
||||
if light_state == STATE_OFF:
|
||||
assert state.attributes["color_mode"] is None
|
||||
else:
|
||||
assert state.attributes["color_mode"] == light.ColorMode.UNKNOWN
|
||||
|
||||
state = hass.states.get(entity4.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [
|
||||
light.ColorMode.COLOR_TEMP,
|
||||
light.ColorMode.HS,
|
||||
]
|
||||
if light_state == STATE_OFF:
|
||||
assert state.attributes["color_mode"] is None
|
||||
else:
|
||||
assert state.attributes["color_mode"] == light.ColorMode.UNKNOWN
|
||||
|
||||
|
||||
async def test_light_backwards_compatibility_color_mode(hass: HomeAssistant) -> None:
|
||||
"""Test color_mode if not implemented by the entity."""
|
||||
entities = [
|
||||
MockLight("Test_0", STATE_ON),
|
||||
MockLight("Test_1", STATE_ON),
|
||||
MockLight("Test_2", STATE_ON),
|
||||
MockLight("Test_3", STATE_ON),
|
||||
MockLight("Test_4", STATE_ON),
|
||||
]
|
||||
|
||||
entity0 = entities[0]
|
||||
|
||||
entity1 = entities[1]
|
||||
entity1.supported_features = light.SUPPORT_BRIGHTNESS
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
entity1.supported_color_modes = None
|
||||
entity1.color_mode = None
|
||||
entity1.brightness = 100
|
||||
|
||||
entity2 = entities[2]
|
||||
entity2.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR_TEMP
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
entity2.supported_color_modes = None
|
||||
entity2.color_mode = None
|
||||
entity2.color_temp_kelvin = 10000
|
||||
|
||||
entity3 = entities[3]
|
||||
entity3.supported_features = light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
entity3.supported_color_modes = None
|
||||
entity3.color_mode = None
|
||||
entity3.hs_color = (240, 100)
|
||||
|
||||
entity4 = entities[4]
|
||||
entity4.supported_features = (
|
||||
light.SUPPORT_BRIGHTNESS | light.SUPPORT_COLOR | light.SUPPORT_COLOR_TEMP
|
||||
)
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
entity4.supported_color_modes = None
|
||||
entity4.color_mode = None
|
||||
entity4.hs_color = (240, 100)
|
||||
entity4.color_temp_kelvin = 10000
|
||||
|
||||
setup_test_component_platform(hass, light.DOMAIN, entities)
|
||||
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get(entity0.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.ONOFF]
|
||||
assert state.attributes["color_mode"] == light.ColorMode.ONOFF
|
||||
|
||||
state = hass.states.get(entity1.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.BRIGHTNESS]
|
||||
assert state.attributes["color_mode"] == light.ColorMode.BRIGHTNESS
|
||||
|
||||
state = hass.states.get(entity2.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.COLOR_TEMP]
|
||||
assert state.attributes["color_mode"] == light.ColorMode.COLOR_TEMP
|
||||
assert state.attributes["rgb_color"] == (202, 218, 255)
|
||||
assert state.attributes["hs_color"] == (221.575, 20.9)
|
||||
assert state.attributes["xy_color"] == (0.278, 0.287)
|
||||
|
||||
state = hass.states.get(entity3.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.HS]
|
||||
assert state.attributes["color_mode"] == light.ColorMode.HS
|
||||
|
||||
state = hass.states.get(entity4.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [
|
||||
light.ColorMode.COLOR_TEMP,
|
||||
light.ColorMode.HS,
|
||||
]
|
||||
# hs color prioritized over color_temp, light should report mode ColorMode.HS
|
||||
assert state.attributes["color_mode"] == light.ColorMode.HS
|
||||
|
||||
|
||||
async def test_light_service_call_rgbw(hass: HomeAssistant) -> None:
|
||||
"""Test rgbw functionality in service calls."""
|
||||
entity0 = MockLight("Test_rgbw", STATE_ON)
|
||||
@ -1186,7 +1364,7 @@ async def test_light_state_off(hass: HomeAssistant) -> None:
|
||||
"color_mode": None,
|
||||
"friendly_name": "Test_onoff",
|
||||
"supported_color_modes": [light.ColorMode.ONOFF],
|
||||
"supported_features": light.LightEntityFeature(0),
|
||||
"supported_features": 0,
|
||||
}
|
||||
|
||||
state = hass.states.get(entity1.entity_id)
|
||||
@ -1194,7 +1372,7 @@ async def test_light_state_off(hass: HomeAssistant) -> None:
|
||||
"color_mode": None,
|
||||
"friendly_name": "Test_brightness",
|
||||
"supported_color_modes": [light.ColorMode.BRIGHTNESS],
|
||||
"supported_features": light.LightEntityFeature(0),
|
||||
"supported_features": 0,
|
||||
"brightness": None,
|
||||
}
|
||||
|
||||
@ -1203,7 +1381,7 @@ async def test_light_state_off(hass: HomeAssistant) -> None:
|
||||
"color_mode": None,
|
||||
"friendly_name": "Test_ct",
|
||||
"supported_color_modes": [light.ColorMode.COLOR_TEMP],
|
||||
"supported_features": light.LightEntityFeature(0),
|
||||
"supported_features": 0,
|
||||
"brightness": None,
|
||||
"color_temp": None,
|
||||
"color_temp_kelvin": None,
|
||||
@ -1221,7 +1399,7 @@ async def test_light_state_off(hass: HomeAssistant) -> None:
|
||||
"color_mode": None,
|
||||
"friendly_name": "Test_rgbw",
|
||||
"supported_color_modes": [light.ColorMode.RGBW],
|
||||
"supported_features": light.LightEntityFeature(0),
|
||||
"supported_features": 0,
|
||||
"brightness": None,
|
||||
"rgbw_color": None,
|
||||
"hs_color": None,
|
||||
@ -1252,7 +1430,7 @@ async def test_light_state_rgbw(hass: HomeAssistant) -> None:
|
||||
"color_mode": light.ColorMode.RGBW,
|
||||
"friendly_name": "Test_rgbw",
|
||||
"supported_color_modes": [light.ColorMode.RGBW],
|
||||
"supported_features": light.LightEntityFeature(0),
|
||||
"supported_features": 0,
|
||||
"hs_color": (240.0, 25.0),
|
||||
"rgb_color": (3, 3, 4),
|
||||
"rgbw_color": (1, 2, 3, 4),
|
||||
@ -1283,7 +1461,7 @@ async def test_light_state_rgbww(hass: HomeAssistant) -> None:
|
||||
"color_mode": light.ColorMode.RGBWW,
|
||||
"friendly_name": "Test_rgbww",
|
||||
"supported_color_modes": [light.ColorMode.RGBWW],
|
||||
"supported_features": light.LightEntityFeature(0),
|
||||
"supported_features": 0,
|
||||
"hs_color": (60.0, 20.0),
|
||||
"rgb_color": (5, 5, 4),
|
||||
"rgbww_color": (1, 2, 3, 4, 5),
|
||||
@ -1299,6 +1477,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
MockLight("Test_rgb", STATE_ON),
|
||||
MockLight("Test_xy", STATE_ON),
|
||||
MockLight("Test_all", STATE_ON),
|
||||
MockLight("Test_legacy", STATE_ON),
|
||||
MockLight("Test_rgbw", STATE_ON),
|
||||
MockLight("Test_rgbww", STATE_ON),
|
||||
MockLight("Test_temperature", STATE_ON),
|
||||
@ -1322,13 +1501,19 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
}
|
||||
|
||||
entity4 = entities[4]
|
||||
entity4.supported_color_modes = {light.ColorMode.RGBW}
|
||||
entity4.supported_features = light.SUPPORT_COLOR
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
entity4.supported_color_modes = None
|
||||
entity4.color_mode = None
|
||||
|
||||
entity5 = entities[5]
|
||||
entity5.supported_color_modes = {light.ColorMode.RGBWW}
|
||||
entity5.supported_color_modes = {light.ColorMode.RGBW}
|
||||
|
||||
entity6 = entities[6]
|
||||
entity6.supported_color_modes = {light.ColorMode.COLOR_TEMP}
|
||||
entity6.supported_color_modes = {light.ColorMode.RGBWW}
|
||||
|
||||
entity7 = entities[7]
|
||||
entity7.supported_color_modes = {light.ColorMode.COLOR_TEMP}
|
||||
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
@ -1350,12 +1535,15 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
]
|
||||
|
||||
state = hass.states.get(entity4.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.RGBW]
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.HS]
|
||||
|
||||
state = hass.states.get(entity5.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.RGBWW]
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.RGBW]
|
||||
|
||||
state = hass.states.get(entity6.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.RGBWW]
|
||||
|
||||
state = hass.states.get(entity7.entity_id)
|
||||
assert state.attributes["supported_color_modes"] == [light.ColorMode.COLOR_TEMP]
|
||||
|
||||
await hass.services.async_call(
|
||||
@ -1370,6 +1558,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 100,
|
||||
"hs_color": (240, 100),
|
||||
@ -1385,10 +1574,12 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
_, data = entity3.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "hs_color": (240.0, 100.0)}
|
||||
_, data = entity4.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "rgbw_color": (0, 0, 255, 0)}
|
||||
assert data == {"brightness": 255, "hs_color": (240.0, 100.0)}
|
||||
_, data = entity5.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "rgbww_color": (0, 0, 255, 0, 0)}
|
||||
assert data == {"brightness": 255, "rgbw_color": (0, 0, 255, 0)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "rgbww_color": (0, 0, 255, 0, 0)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "color_temp_kelvin": 1739, "color_temp": 575}
|
||||
|
||||
await hass.services.async_call(
|
||||
@ -1403,6 +1594,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 100,
|
||||
"hs_color": (240, 0),
|
||||
@ -1418,11 +1610,13 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
_, data = entity3.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "hs_color": (240.0, 0.0)}
|
||||
_, data = entity4.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "rgbw_color": (0, 0, 0, 255)}
|
||||
assert data == {"brightness": 255, "hs_color": (240.0, 0.0)}
|
||||
_, data = entity5.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "rgbw_color": (0, 0, 0, 255)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
# The midpoint of the white channels is warm, compensated by adding green + blue
|
||||
assert data == {"brightness": 255, "rgbww_color": (0, 76, 141, 255, 255)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 255, "color_temp_kelvin": 5962, "color_temp": 167}
|
||||
|
||||
await hass.services.async_call(
|
||||
@ -1437,6 +1631,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"rgb_color": (128, 0, 0),
|
||||
@ -1451,12 +1646,13 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
assert data == {"brightness": 128, "xy_color": (0.701, 0.299)}
|
||||
_, data = entity3.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgb_color": (128, 0, 0)}
|
||||
|
||||
_, data = entity4.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbw_color": (128, 0, 0, 0)}
|
||||
assert data == {"brightness": 128, "hs_color": (0.0, 100.0)}
|
||||
_, data = entity5.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbww_color": (128, 0, 0, 0, 0)}
|
||||
assert data == {"brightness": 128, "rgbw_color": (128, 0, 0, 0)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbww_color": (128, 0, 0, 0, 0)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 6279, "color_temp": 159}
|
||||
|
||||
await hass.services.async_call(
|
||||
@ -1471,6 +1667,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"rgb_color": (255, 255, 255),
|
||||
@ -1486,11 +1683,13 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
_, data = entity3.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgb_color": (255, 255, 255)}
|
||||
_, data = entity4.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbw_color": (0, 0, 0, 255)}
|
||||
assert data == {"brightness": 128, "hs_color": (0.0, 0.0)}
|
||||
_, data = entity5.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbw_color": (0, 0, 0, 255)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
# The midpoint the white channels is warm, compensated by adding green + blue
|
||||
assert data == {"brightness": 128, "rgbww_color": (0, 76, 141, 255, 255)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167}
|
||||
|
||||
await hass.services.async_call(
|
||||
@ -1505,6 +1704,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"xy_color": (0.1, 0.8),
|
||||
@ -1520,10 +1720,12 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
_, data = entity3.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "xy_color": (0.1, 0.8)}
|
||||
_, data = entity4.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbw_color": (0, 255, 22, 0)}
|
||||
assert data == {"brightness": 128, "hs_color": (125.176, 100.0)}
|
||||
_, data = entity5.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbww_color": (0, 255, 22, 0, 0)}
|
||||
assert data == {"brightness": 128, "rgbw_color": (0, 255, 22, 0)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbww_color": (0, 255, 22, 0, 0)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 8645, "color_temp": 115}
|
||||
|
||||
await hass.services.async_call(
|
||||
@ -1538,6 +1740,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"xy_color": (0.323, 0.329),
|
||||
@ -1553,11 +1756,13 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
_, data = entity3.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "xy_color": (0.323, 0.329)}
|
||||
_, data = entity4.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbw_color": (1, 0, 0, 255)}
|
||||
assert data == {"brightness": 128, "hs_color": (0.0, 0.392)}
|
||||
_, data = entity5.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbw_color": (1, 0, 0, 255)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
# The midpoint the white channels is warm, compensated by adding green + blue
|
||||
assert data == {"brightness": 128, "rgbww_color": (0, 75, 140, 255, 255)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167}
|
||||
|
||||
await hass.services.async_call(
|
||||
@ -1572,6 +1777,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"rgbw_color": (128, 0, 0, 64),
|
||||
@ -1587,11 +1793,13 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
_, data = entity3.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgb_color": (128, 43, 43)}
|
||||
_, data = entity4.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbw_color": (128, 0, 0, 64)}
|
||||
assert data == {"brightness": 128, "hs_color": (0.0, 66.406)}
|
||||
_, data = entity5.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbw_color": (128, 0, 0, 64)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
# The midpoint the white channels is warm, compensated by adding green + blue
|
||||
assert data == {"brightness": 128, "rgbww_color": (128, 0, 30, 117, 117)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 3011, "color_temp": 332}
|
||||
|
||||
await hass.services.async_call(
|
||||
@ -1606,6 +1814,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"rgbw_color": (255, 255, 255, 255),
|
||||
@ -1621,11 +1830,13 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
_, data = entity3.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgb_color": (255, 255, 255)}
|
||||
_, data = entity4.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbw_color": (255, 255, 255, 255)}
|
||||
assert data == {"brightness": 128, "hs_color": (0.0, 0.0)}
|
||||
_, data = entity5.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbw_color": (255, 255, 255, 255)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
# The midpoint the white channels is warm, compensated by adding green + blue
|
||||
assert data == {"brightness": 128, "rgbww_color": (0, 76, 141, 255, 255)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167}
|
||||
|
||||
await hass.services.async_call(
|
||||
@ -1640,6 +1851,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"rgbww_color": (128, 0, 0, 64, 32),
|
||||
@ -1655,10 +1867,12 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
_, data = entity3.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgb_color": (128, 33, 26)}
|
||||
_, data = entity4.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbw_color": (128, 9, 0, 33)}
|
||||
assert data == {"brightness": 128, "hs_color": (4.118, 79.688)}
|
||||
_, data = entity5.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbww_color": (128, 0, 0, 64, 32)}
|
||||
assert data == {"brightness": 128, "rgbw_color": (128, 9, 0, 33)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbww_color": (128, 0, 0, 64, 32)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 3845, "color_temp": 260}
|
||||
|
||||
await hass.services.async_call(
|
||||
@ -1673,6 +1887,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
entity7.entity_id,
|
||||
],
|
||||
"brightness_pct": 50,
|
||||
"rgbww_color": (255, 255, 255, 255, 255),
|
||||
@ -1688,11 +1903,13 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
||||
_, data = entity3.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgb_color": (255, 217, 185)}
|
||||
_, data = entity4.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "hs_color": (27.429, 27.451)}
|
||||
_, data = entity5.last_call("turn_on")
|
||||
# The midpoint the white channels is warm, compensated by decreasing green + blue
|
||||
assert data == {"brightness": 128, "rgbw_color": (96, 44, 0, 255)}
|
||||
_, data = entity5.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbww_color": (255, 255, 255, 255, 255)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "rgbww_color": (255, 255, 255, 255, 255)}
|
||||
_, data = entity7.last_call("turn_on")
|
||||
assert data == {"brightness": 128, "color_temp_kelvin": 3451, "color_temp": 289}
|
||||
|
||||
|
||||
@ -1705,6 +1922,7 @@ async def test_light_service_call_color_conversion_named_tuple(
|
||||
MockLight("Test_rgb", STATE_ON),
|
||||
MockLight("Test_xy", STATE_ON),
|
||||
MockLight("Test_all", STATE_ON),
|
||||
MockLight("Test_legacy", STATE_ON),
|
||||
MockLight("Test_rgbw", STATE_ON),
|
||||
MockLight("Test_rgbww", STATE_ON),
|
||||
]
|
||||
@ -1727,10 +1945,16 @@ async def test_light_service_call_color_conversion_named_tuple(
|
||||
}
|
||||
|
||||
entity4 = entities[4]
|
||||
entity4.supported_color_modes = {light.ColorMode.RGBW}
|
||||
entity4.supported_features = light.SUPPORT_COLOR
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
entity4.supported_color_modes = None
|
||||
entity4.color_mode = None
|
||||
|
||||
entity5 = entities[5]
|
||||
entity5.supported_color_modes = {light.ColorMode.RGBWW}
|
||||
entity5.supported_color_modes = {light.ColorMode.RGBW}
|
||||
|
||||
entity6 = entities[6]
|
||||
entity6.supported_color_modes = {light.ColorMode.RGBWW}
|
||||
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
@ -1746,6 +1970,7 @@ async def test_light_service_call_color_conversion_named_tuple(
|
||||
entity3.entity_id,
|
||||
entity4.entity_id,
|
||||
entity5.entity_id,
|
||||
entity6.entity_id,
|
||||
],
|
||||
"brightness_pct": 25,
|
||||
"rgb_color": color_util.RGBColor(128, 0, 0),
|
||||
@ -1761,8 +1986,10 @@ async def test_light_service_call_color_conversion_named_tuple(
|
||||
_, data = entity3.last_call("turn_on")
|
||||
assert data == {"brightness": 64, "rgb_color": (128, 0, 0)}
|
||||
_, data = entity4.last_call("turn_on")
|
||||
assert data == {"brightness": 64, "rgbw_color": (128, 0, 0, 0)}
|
||||
assert data == {"brightness": 64, "hs_color": (0.0, 100.0)}
|
||||
_, data = entity5.last_call("turn_on")
|
||||
assert data == {"brightness": 64, "rgbw_color": (128, 0, 0, 0)}
|
||||
_, data = entity6.last_call("turn_on")
|
||||
assert data == {"brightness": 64, "rgbww_color": (128, 0, 0, 0, 0)}
|
||||
|
||||
|
||||
@ -2131,6 +2358,13 @@ async def test_light_state_color_conversion(hass: HomeAssistant) -> None:
|
||||
entity2.rgb_color = "Invalid" # Should be ignored
|
||||
entity2.xy_color = (0.1, 0.8)
|
||||
|
||||
entity3 = entities[3]
|
||||
entity3.hs_color = (240, 100)
|
||||
entity3.supported_features = light.SUPPORT_COLOR
|
||||
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||
entity3.supported_color_modes = None
|
||||
entity3.color_mode = None
|
||||
|
||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@ -2152,6 +2386,12 @@ async def test_light_state_color_conversion(hass: HomeAssistant) -> None:
|
||||
assert state.attributes["rgb_color"] == (0, 255, 22)
|
||||
assert state.attributes["xy_color"] == (0.1, 0.8)
|
||||
|
||||
state = hass.states.get(entity3.entity_id)
|
||||
assert state.attributes["color_mode"] == light.ColorMode.HS
|
||||
assert state.attributes["hs_color"] == (240, 100)
|
||||
assert state.attributes["rgb_color"] == (0, 0, 255)
|
||||
assert state.attributes["xy_color"] == (0.136, 0.04)
|
||||
|
||||
|
||||
async def test_services_filter_parameters(
|
||||
hass: HomeAssistant,
|
||||
@ -2386,6 +2626,27 @@ def test_filter_supported_color_modes() -> None:
|
||||
assert light.filter_supported_color_modes(supported) == {light.ColorMode.BRIGHTNESS}
|
||||
|
||||
|
||||
def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Test deprecated supported features ints."""
|
||||
|
||||
class MockLightEntityEntity(light.LightEntity):
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Return supported features."""
|
||||
return 1
|
||||
|
||||
entity = MockLightEntityEntity()
|
||||
assert entity.supported_features_compat is light.LightEntityFeature(1)
|
||||
assert "MockLightEntityEntity" in caplog.text
|
||||
assert "is using deprecated supported features values" in caplog.text
|
||||
assert "Instead it should use" in caplog.text
|
||||
assert "LightEntityFeature" in caplog.text
|
||||
assert "and color modes" in caplog.text
|
||||
caplog.clear()
|
||||
assert entity.supported_features_compat is light.LightEntityFeature(1)
|
||||
assert "is using deprecated supported features values" not in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("color_mode", "supported_color_modes", "warning_expected"),
|
||||
[
|
||||
|
@ -129,7 +129,7 @@ def test_support_properties(property_suffix: str) -> None:
|
||||
entity3 = MediaPlayerEntity()
|
||||
entity3._attr_supported_features = feature
|
||||
entity4 = MediaPlayerEntity()
|
||||
entity4._attr_supported_features = all_features & ~feature
|
||||
entity4._attr_supported_features = all_features - feature
|
||||
|
||||
assert getattr(entity1, f"support_{property_suffix}") is False
|
||||
assert getattr(entity2, f"support_{property_suffix}") is True
|
||||
@ -447,3 +447,23 @@ async def test_get_async_get_browse_image_quoting(
|
||||
url = player.get_browse_image_url("album", media_content_id)
|
||||
await client.get(url)
|
||||
mock_browse_image.assert_called_with("album", media_content_id, None)
|
||||
|
||||
|
||||
def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) -> None:
|
||||
"""Test deprecated supported features ints."""
|
||||
|
||||
class MockMediaPlayerEntity(MediaPlayerEntity):
|
||||
@property
|
||||
def supported_features(self) -> int:
|
||||
"""Return supported features."""
|
||||
return 1
|
||||
|
||||
entity = MockMediaPlayerEntity()
|
||||
assert entity.supported_features_compat is MediaPlayerEntityFeature(1)
|
||||
assert "MockMediaPlayerEntity" in caplog.text
|
||||
assert "is using deprecated supported features values" in caplog.text
|
||||
assert "Instead it should use" in caplog.text
|
||||
assert "MediaPlayerEntityFeature.PAUSE" in caplog.text
|
||||
caplog.clear()
|
||||
assert entity.supported_features_compat is MediaPlayerEntityFeature(1)
|
||||
assert "is using deprecated supported features values" not in caplog.text
|
||||
|
@ -711,4 +711,4 @@ async def test_no_other_imports_allowed(
|
||||
source = "import sys"
|
||||
hass.async_add_executor_job(execute, hass, "test.py", source, {})
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
assert "Error executing script: Not allowed to import sys" in caplog.text
|
||||
assert "ImportError: Not allowed to import sys" in caplog.text
|
||||
|
@ -22,7 +22,7 @@ TEST_DAY = 14
|
||||
TEST_DAY2 = 15
|
||||
TEST_HOUR = 13
|
||||
TEST_MINUTE = 12
|
||||
TEST_FILE_NAME_MP4 = f"{TEST_YEAR}{TEST_MONTH}{TEST_DAY}{TEST_HOUR}{TEST_MINUTE}00.mp4"
|
||||
TEST_FILE_NAME_MP4 = f"Mp4Record/{TEST_YEAR}-{TEST_MONTH}-{TEST_DAY}/RecS04_{TEST_YEAR}{TEST_MONTH}{TEST_DAY}{TEST_HOUR}{TEST_MINUTE}00_123456_AB123C.mp4"
|
||||
TEST_STREAM = "sub"
|
||||
TEST_CHANNEL = "0"
|
||||
TEST_VOD_TYPE = VodRequestType.PLAYBACK.value
|
||||
|
@ -161,6 +161,7 @@ def mock_roborock_entry(hass: HomeAssistant) -> MockConfigEntry:
|
||||
CONF_USER_DATA: USER_DATA.as_dict(),
|
||||
CONF_BASE_URL: BASE_URL,
|
||||
},
|
||||
unique_id=USER_EMAIL,
|
||||
)
|
||||
mock_entry.add_to_hass(hass)
|
||||
return mock_entry
|
||||
|
@ -244,3 +244,28 @@ async def test_reauth_flow(
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "reauth_successful"
|
||||
assert mock_roborock_entry.data["user_data"]["rriot"]["s"] == "new_password_hash"
|
||||
|
||||
|
||||
async def test_account_already_configured(
|
||||
hass: HomeAssistant,
|
||||
bypass_api_fixture,
|
||||
mock_roborock_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Handle the config flow and make sure it succeeds."""
|
||||
with patch(
|
||||
"homeassistant.components.roborock.async_setup_entry", return_value=True
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
with patch(
|
||||
"homeassistant.components.roborock.config_flow.RoborockApiClient.request_code"
|
||||
):
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"], {CONF_USERNAME: USER_EMAIL}
|
||||
)
|
||||
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "already_configured_account"
|
||||
|
@ -4,7 +4,12 @@ from copy import deepcopy
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from roborock import RoborockException, RoborockInvalidCredentials
|
||||
from roborock import (
|
||||
RoborockException,
|
||||
RoborockInvalidCredentials,
|
||||
RoborockInvalidUserAgreement,
|
||||
RoborockNoUserAgreement,
|
||||
)
|
||||
|
||||
from homeassistant.components.roborock.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
@ -194,3 +199,35 @@ async def test_not_supported_a01_device(
|
||||
await async_setup_component(hass, DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
assert "The device you added is not yet supported" in caplog.text
|
||||
|
||||
|
||||
async def test_invalid_user_agreement(
|
||||
hass: HomeAssistant,
|
||||
bypass_api_fixture,
|
||||
mock_roborock_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test that we fail setting up if the user agreement is out of date."""
|
||||
with patch(
|
||||
"homeassistant.components.roborock.RoborockApiClient.get_home_data_v2",
|
||||
side_effect=RoborockInvalidUserAgreement(),
|
||||
):
|
||||
await hass.config_entries.async_setup(mock_roborock_entry.entry_id)
|
||||
assert mock_roborock_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert (
|
||||
mock_roborock_entry.error_reason_translation_key == "invalid_user_agreement"
|
||||
)
|
||||
|
||||
|
||||
async def test_no_user_agreement(
|
||||
hass: HomeAssistant,
|
||||
bypass_api_fixture,
|
||||
mock_roborock_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test that we fail setting up if the user has no agreement."""
|
||||
with patch(
|
||||
"homeassistant.components.roborock.RoborockApiClient.get_home_data_v2",
|
||||
side_effect=RoborockNoUserAgreement(),
|
||||
):
|
||||
await hass.config_entries.async_setup(mock_roborock_entry.entry_id)
|
||||
assert mock_roborock_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
assert mock_roborock_entry.error_reason_translation_key == "no_user_agreement"
|
||||
|
@ -137,6 +137,7 @@ async def mock_async_browse(
|
||||
"title": "Fake Item 1",
|
||||
"id": FAKE_VALID_ITEM_ID,
|
||||
"hasitems": False,
|
||||
"isaudio": True,
|
||||
"item_type": child_types[media_type],
|
||||
"artwork_track_id": "b35bb9e9",
|
||||
"url": "file:///var/lib/squeezeboxserver/music/track_1.mp3",
|
||||
@ -145,6 +146,7 @@ async def mock_async_browse(
|
||||
"title": "Fake Item 2",
|
||||
"id": FAKE_VALID_ITEM_ID + "_2",
|
||||
"hasitems": media_type == "favorites",
|
||||
"isaudio": True,
|
||||
"item_type": child_types[media_type],
|
||||
"image_url": "http://lms.internal:9000/html/images/favorites.png",
|
||||
"url": "file:///var/lib/squeezeboxserver/music/track_2.mp3",
|
||||
@ -153,6 +155,7 @@ async def mock_async_browse(
|
||||
"title": "Fake Item 3",
|
||||
"id": FAKE_VALID_ITEM_ID + "_3",
|
||||
"hasitems": media_type == "favorites",
|
||||
"isaudio": True,
|
||||
"album_id": FAKE_VALID_ITEM_ID if media_type == "favorites" else None,
|
||||
"url": "file:///var/lib/squeezeboxserver/music/track_3.mp3",
|
||||
},
|
||||
|
@ -564,6 +564,24 @@
|
||||
"legacyUFVs": [],
|
||||
"lastUpdateId": "ebf25bac-d5a1-4f1d-a0ee-74c15981eb70",
|
||||
"displays": [],
|
||||
"ringtones": [
|
||||
{
|
||||
"id": "66a14fa502d44203e40003eb",
|
||||
"name": "Default",
|
||||
"size": 208,
|
||||
"isDefault": true,
|
||||
"nvrMac": "A1E00C826924",
|
||||
"modelKey": "ringtone"
|
||||
},
|
||||
{
|
||||
"id": "66a14fa502da4203e40003ec",
|
||||
"name": "Traditional",
|
||||
"size": 180,
|
||||
"isDefault": false,
|
||||
"nvrMac": "A1E00C826924",
|
||||
"modelKey": "ringtone"
|
||||
}
|
||||
],
|
||||
"bridges": [
|
||||
{
|
||||
"mac": "A28D0DB15AE1",
|
||||
|
@ -272,6 +272,42 @@ async def test_send_command(hass: HomeAssistant, config_flow_fixture: None) -> N
|
||||
assert "test" in strings
|
||||
|
||||
|
||||
async def test_supported_features_compat(hass: HomeAssistant) -> None:
|
||||
"""Test StateVacuumEntity using deprecated feature constants features."""
|
||||
|
||||
features = (
|
||||
VacuumEntityFeature.BATTERY
|
||||
| VacuumEntityFeature.FAN_SPEED
|
||||
| VacuumEntityFeature.START
|
||||
| VacuumEntityFeature.STOP
|
||||
| VacuumEntityFeature.PAUSE
|
||||
)
|
||||
|
||||
class _LegacyConstantsStateVacuum(StateVacuumEntity):
|
||||
_attr_supported_features = int(features)
|
||||
_attr_fan_speed_list = ["silent", "normal", "pet hair"]
|
||||
|
||||
entity = _LegacyConstantsStateVacuum()
|
||||
assert isinstance(entity.supported_features, int)
|
||||
assert entity.supported_features == int(features)
|
||||
assert entity.supported_features_compat is (
|
||||
VacuumEntityFeature.BATTERY
|
||||
| VacuumEntityFeature.FAN_SPEED
|
||||
| VacuumEntityFeature.START
|
||||
| VacuumEntityFeature.STOP
|
||||
| VacuumEntityFeature.PAUSE
|
||||
)
|
||||
assert entity.state_attributes == {
|
||||
"battery_level": None,
|
||||
"battery_icon": "mdi:battery-unknown",
|
||||
"fan_speed": None,
|
||||
}
|
||||
assert entity.capability_attributes == {
|
||||
"fan_speed_list": ["silent", "normal", "pet hair"]
|
||||
}
|
||||
assert entity._deprecated_supported_features_reported
|
||||
|
||||
|
||||
async def test_vacuum_not_log_deprecated_state_warning(
|
||||
hass: HomeAssistant,
|
||||
mock_vacuum_entity: MockVacuum,
|
||||
|
@ -4,6 +4,7 @@ import asyncio
|
||||
from collections.abc import Iterable
|
||||
import dataclasses
|
||||
from datetime import timedelta
|
||||
from enum import IntFlag
|
||||
import logging
|
||||
import threading
|
||||
from typing import Any
|
||||
@ -2485,6 +2486,31 @@ async def test_cached_entity_property_override(hass: HomeAssistant) -> None:
|
||||
return "🤡"
|
||||
|
||||
|
||||
async def test_entity_report_deprecated_supported_features_values(
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test reporting deprecated supported feature values only happens once."""
|
||||
ent = entity.Entity()
|
||||
|
||||
class MockEntityFeatures(IntFlag):
|
||||
VALUE1 = 1
|
||||
VALUE2 = 2
|
||||
|
||||
ent._report_deprecated_supported_features_values(MockEntityFeatures(2))
|
||||
assert (
|
||||
"is using deprecated supported features values which will be removed"
|
||||
in caplog.text
|
||||
)
|
||||
assert "MockEntityFeatures.VALUE2" in caplog.text
|
||||
|
||||
caplog.clear()
|
||||
ent._report_deprecated_supported_features_values(MockEntityFeatures(2))
|
||||
assert (
|
||||
"is using deprecated supported features values which will be removed"
|
||||
not in caplog.text
|
||||
)
|
||||
|
||||
|
||||
async def test_remove_entity_registry(
|
||||
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
||||
) -> None:
|
||||
|
Loading…
x
Reference in New Issue
Block a user