mirror of
https://github.com/home-assistant/core.git
synced 2025-04-26 02:07:54 +00:00
2025.1.1 (#134940)
This commit is contained in:
commit
d59a91a905
@ -44,12 +44,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"apps": {
|
"apps": {
|
||||||
"title": "Configure Android Apps",
|
"title": "Configure Android apps",
|
||||||
"description": "Configure application id {app_id}",
|
"description": "Configure application ID {app_id}",
|
||||||
"data": {
|
"data": {
|
||||||
"app_name": "Application Name",
|
"app_name": "Application name",
|
||||||
"app_id": "Application ID",
|
"app_id": "Application ID",
|
||||||
"app_icon": "Application Icon",
|
"app_icon": "Application icon",
|
||||||
"app_delete": "Check to delete this application"
|
"app_delete": "Check to delete this application"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@
|
|||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"invalid_unique_id": "Impossible to determine a valid unique id for the device",
|
"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"
|
"no_unique_id": "A device without a valid unique ID is already configured. Configuration of multiple instances is not possible"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
@ -42,7 +42,7 @@
|
|||||||
"consider_home": "Seconds to wait before considering a device away",
|
"consider_home": "Seconds to wait before considering a device away",
|
||||||
"track_unknown": "Track unknown / unnamed devices",
|
"track_unknown": "Track unknown / unnamed devices",
|
||||||
"interface": "The interface that you want statistics from (e.g. eth0, eth1 etc)",
|
"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)"
|
"require_ip": "Devices must have IP (for access point mode)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -435,6 +435,7 @@ class BackupManager:
|
|||||||
# no point in continuing
|
# no point in continuing
|
||||||
raise BackupManagerError(str(result)) from result
|
raise BackupManagerError(str(result)) from result
|
||||||
if isinstance(result, BackupAgentError):
|
if isinstance(result, BackupAgentError):
|
||||||
|
LOGGER.error("Error uploading to %s: %s", agent_ids[idx], result)
|
||||||
agent_errors[agent_ids[idx]] = result
|
agent_errors[agent_ids[idx]] = result
|
||||||
continue
|
continue
|
||||||
if isinstance(result, Exception):
|
if isinstance(result, Exception):
|
||||||
|
@ -20,6 +20,6 @@
|
|||||||
"bluetooth-auto-recovery==1.4.2",
|
"bluetooth-auto-recovery==1.4.2",
|
||||||
"bluetooth-data-tools==1.20.0",
|
"bluetooth-data-tools==1.20.0",
|
||||||
"dbus-fast==2.24.3",
|
"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",
|
"documentation": "https://www.home-assistant.io/integrations/bring",
|
||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
|
"loggers": ["bring_api"],
|
||||||
"requirements": ["bring-api==0.9.1"]
|
"requirements": ["bring-api==0.9.1"]
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"discovery_confirm": {
|
"discovery_confirm": {
|
||||||
"description": "Do you want to setup {name}?"
|
"description": "Do you want to set up {name}?"
|
||||||
},
|
},
|
||||||
"reconfigure": {
|
"reconfigure": {
|
||||||
"description": "Reconfigure your Cambridge Audio Streamer.",
|
"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."
|
"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": {
|
"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%]",
|
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
"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."""
|
"""Flag supported features."""
|
||||||
return self._attr_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
|
@cached_property
|
||||||
def is_recording(self) -> bool:
|
def is_recording(self) -> bool:
|
||||||
"""Return true if the device is recording."""
|
"""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
|
self._deprecate_attr_frontend_stream_type_logged = True
|
||||||
return self._attr_frontend_stream_type
|
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
|
return None
|
||||||
if (
|
if (
|
||||||
self._webrtc_provider
|
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:
|
async def async_internal_added_to_hass(self) -> None:
|
||||||
"""Run when entity about to be added to hass."""
|
"""Run when entity about to be added to hass."""
|
||||||
await super().async_internal_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)
|
await self.async_refresh_providers(write_state=False)
|
||||||
|
|
||||||
async def async_refresh_providers(self, *, write_state: bool = True) -> None:
|
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]]
|
self, fn: Callable[[HomeAssistant, Camera], Coroutine[None, None, _T | None]]
|
||||||
) -> _T | None:
|
) -> _T | None:
|
||||||
"""Get first provider that supports this camera."""
|
"""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 None
|
||||||
|
|
||||||
return await fn(self.hass, self)
|
return await fn(self.hass, self)
|
||||||
@ -896,7 +911,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
def camera_capabilities(self) -> CameraCapabilities:
|
def camera_capabilities(self) -> CameraCapabilities:
|
||||||
"""Return the camera capabilities."""
|
"""Return the camera capabilities."""
|
||||||
frontend_stream_types = set()
|
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:
|
if self._supports_native_sync_webrtc or self._supports_native_async_webrtc:
|
||||||
# The camera has a native WebRTC implementation
|
# The camera has a native WebRTC implementation
|
||||||
frontend_stream_types.add(StreamType.WEB_RTC)
|
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()
|
super().async_write_ha_state()
|
||||||
if self.__supports_stream != (
|
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.__supports_stream = supports_stream
|
||||||
self._invalidate_camera_capabilities_cache()
|
self._invalidate_camera_capabilities_cache()
|
||||||
|
@ -181,6 +181,11 @@ class CloudBackupAgent(BackupAgent):
|
|||||||
headers=details["headers"] | {"content-length": str(backup.size)},
|
headers=details["headers"] | {"content-length": str(backup.size)},
|
||||||
timeout=ClientTimeout(connect=10.0, total=43200.0), # 43200s == 12h
|
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()
|
upload_status.raise_for_status()
|
||||||
except (TimeoutError, ClientError) as err:
|
except (TimeoutError, ClientError) as err:
|
||||||
raise BackupAgentError("Failed to upload backup") from err
|
raise BackupAgentError("Failed to upload backup") from err
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from cookidoo_api import Cookidoo, CookidooConfig, CookidooLocalizationConfig
|
from cookidoo_api import Cookidoo, CookidooConfig, get_localization_options
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_COUNTRY,
|
CONF_COUNTRY,
|
||||||
@ -22,15 +22,17 @@ PLATFORMS: list[Platform] = [Platform.TODO]
|
|||||||
async def async_setup_entry(hass: HomeAssistant, entry: CookidooConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: CookidooConfigEntry) -> bool:
|
||||||
"""Set up Cookidoo from a config entry."""
|
"""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(
|
cookidoo = Cookidoo(
|
||||||
async_get_clientsession(hass),
|
async_get_clientsession(hass),
|
||||||
CookidooConfig(
|
CookidooConfig(
|
||||||
email=entry.data[CONF_EMAIL],
|
email=entry.data[CONF_EMAIL],
|
||||||
password=entry.data[CONF_PASSWORD],
|
password=entry.data[CONF_PASSWORD],
|
||||||
localization=CookidooLocalizationConfig(
|
localization=localizations[0],
|
||||||
country_code=entry.data[CONF_COUNTRY].lower(),
|
|
||||||
language=entry.data[CONF_LANGUAGE],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ from cookidoo_api import (
|
|||||||
Cookidoo,
|
Cookidoo,
|
||||||
CookidooAuthException,
|
CookidooAuthException,
|
||||||
CookidooConfig,
|
CookidooConfig,
|
||||||
CookidooLocalizationConfig,
|
|
||||||
CookidooRequestException,
|
CookidooRequestException,
|
||||||
get_country_options,
|
get_country_options,
|
||||||
get_localization_options,
|
get_localization_options,
|
||||||
@ -219,18 +218,19 @@ class CookidooConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
else:
|
else:
|
||||||
data_input[CONF_LANGUAGE] = (
|
data_input[CONF_LANGUAGE] = (
|
||||||
await get_localization_options(country=data_input[CONF_COUNTRY].lower())
|
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(
|
cookidoo = Cookidoo(
|
||||||
session,
|
async_get_clientsession(self.hass),
|
||||||
CookidooConfig(
|
CookidooConfig(
|
||||||
email=data_input[CONF_EMAIL],
|
email=data_input[CONF_EMAIL],
|
||||||
password=data_input[CONF_PASSWORD],
|
password=data_input[CONF_PASSWORD],
|
||||||
localization=CookidooLocalizationConfig(
|
localization=localizations[0],
|
||||||
country_code=data_input[CONF_COUNTRY].lower(),
|
|
||||||
language=data_input[CONF_LANGUAGE],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/cookidoo",
|
"documentation": "https://www.home-assistant.io/integrations/cookidoo",
|
||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
|
"loggers": ["cookidoo_api"],
|
||||||
"quality_scale": "silver",
|
"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:
|
def supported_features(self) -> CoverEntityFeature:
|
||||||
"""Flag supported features."""
|
"""Flag supported features."""
|
||||||
if (features := self._attr_supported_features) is not None:
|
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
|
return features
|
||||||
|
|
||||||
supported_features = (
|
supported_features = (
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["openwebif"],
|
"loggers": ["openwebif"],
|
||||||
"requirements": ["openwebifpy==4.3.0"]
|
"requirements": ["openwebifpy==4.3.1"]
|
||||||
}
|
}
|
||||||
|
@ -22,5 +22,5 @@
|
|||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["eq3btsmart"],
|
"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 aioesphomeapi import APIClient, DeviceInfo
|
||||||
from bleak_esphome import connect_scanner
|
from bleak_esphome import connect_scanner
|
||||||
from bleak_esphome.backend.cache import ESPHomeBluetoothCache
|
|
||||||
|
|
||||||
from homeassistant.components.bluetooth import async_register_scanner
|
from homeassistant.components.bluetooth import async_register_scanner
|
||||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
|
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback as hass_callback
|
||||||
@ -28,10 +27,9 @@ def async_connect_scanner(
|
|||||||
entry_data: RuntimeEntryData,
|
entry_data: RuntimeEntryData,
|
||||||
cli: APIClient,
|
cli: APIClient,
|
||||||
device_info: DeviceInfo,
|
device_info: DeviceInfo,
|
||||||
cache: ESPHomeBluetoothCache,
|
|
||||||
) -> CALLBACK_TYPE:
|
) -> CALLBACK_TYPE:
|
||||||
"""Connect scanner."""
|
"""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
|
entry_data.bluetooth_device = client_data.bluetooth_device
|
||||||
client_data.disconnect_callbacks = entry_data.disconnect_callbacks
|
client_data.disconnect_callbacks = entry_data.disconnect_callbacks
|
||||||
scanner = client_data.scanner
|
scanner = client_data.scanner
|
||||||
|
@ -6,8 +6,6 @@ from dataclasses import dataclass, field
|
|||||||
from functools import cache
|
from functools import cache
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
|
||||||
from bleak_esphome.backend.cache import ESPHomeBluetoothCache
|
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.json import JSONEncoder
|
from homeassistant.helpers.json import JSONEncoder
|
||||||
|
|
||||||
@ -22,9 +20,6 @@ class DomainData:
|
|||||||
"""Define a class that stores global esphome data in hass.data[DOMAIN]."""
|
"""Define a class that stores global esphome data in hass.data[DOMAIN]."""
|
||||||
|
|
||||||
_stores: dict[str, ESPHomeStorage] = field(default_factory=dict)
|
_stores: dict[str, ESPHomeStorage] = field(default_factory=dict)
|
||||||
bluetooth_cache: ESPHomeBluetoothCache = field(
|
|
||||||
default_factory=ESPHomeBluetoothCache
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_entry_data(self, entry: ESPHomeConfigEntry) -> RuntimeEntryData:
|
def get_entry_data(self, entry: ESPHomeConfigEntry) -> RuntimeEntryData:
|
||||||
"""Return the runtime entry data associated with this config entry.
|
"""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):
|
if device_info.bluetooth_proxy_feature_flags_compat(api_version):
|
||||||
entry_data.disconnect_callbacks.add(
|
entry_data.disconnect_callbacks.add(
|
||||||
async_connect_scanner(
|
async_connect_scanner(hass, entry_data, cli, device_info)
|
||||||
hass, entry_data, cli, device_info, self.domain_data.bluetooth_cache
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if device_info.voice_assistant_feature_flags_compat(api_version) and (
|
if device_info.voice_assistant_feature_flags_compat(api_version) and (
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
"requirements": [
|
"requirements": [
|
||||||
"aioesphomeapi==28.0.0",
|
"aioesphomeapi==28.0.0",
|
||||||
"esphome-dashboard-api==1.2.3",
|
"esphome-dashboard-api==1.2.3",
|
||||||
"bleak-esphome==1.1.0"
|
"bleak-esphome==2.0.0"
|
||||||
],
|
],
|
||||||
"zeroconf": ["_esphomelib._tcp.local."]
|
"zeroconf": ["_esphomelib._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
from datetime import datetime as dt
|
from datetime import datetime as dt
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
from pyflick import FlickAPI
|
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 pyflick.const import DEFAULT_CLIENT_ID, DEFAULT_CLIENT_SECRET
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
@ -93,16 +94,22 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class HassFlickAuth(AbstractFlickAuth):
|
class HassFlickAuth(SimpleFlickAuth):
|
||||||
"""Implementation of AbstractFlickAuth based on a Home Assistant entity config."""
|
"""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."""
|
"""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._entry = entry
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
|
|
||||||
async def _get_entry_token(self):
|
async def _get_entry_token(self) -> dict[str, Any]:
|
||||||
# No token saved, generate one
|
# No token saved, generate one
|
||||||
if (
|
if (
|
||||||
CONF_TOKEN_EXPIRY not in self._entry.data
|
CONF_TOKEN_EXPIRY not in self._entry.data
|
||||||
@ -119,13 +126,8 @@ class HassFlickAuth(AbstractFlickAuth):
|
|||||||
async def _update_token(self):
|
async def _update_token(self):
|
||||||
_LOGGER.debug("Fetching new access token")
|
_LOGGER.debug("Fetching new access token")
|
||||||
|
|
||||||
token = await self.get_new_token(
|
token = await super().get_new_token(
|
||||||
username=self._entry.data[CONF_USERNAME],
|
self._username, self._password, self._client_id, self._client_secret
|
||||||
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
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER.debug("New token: %s", token)
|
_LOGGER.debug("New token: %s", token)
|
||||||
|
@ -214,6 +214,18 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
|||||||
self._options = options
|
self._options = options
|
||||||
await self.hass.async_add_executor_job(self.setup)
|
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:
|
def setup(self) -> None:
|
||||||
"""Set up FritzboxTools class."""
|
"""Set up FritzboxTools class."""
|
||||||
|
|
||||||
|
@ -68,23 +68,14 @@ class FritzBoxBaseEntity:
|
|||||||
"""Init device info class."""
|
"""Init device info class."""
|
||||||
self._avm_wrapper = avm_wrapper
|
self._avm_wrapper = avm_wrapper
|
||||||
self._device_name = device_name
|
self._device_name = device_name
|
||||||
|
self.mac_address = self._avm_wrapper.mac
|
||||||
@property
|
|
||||||
def mac_address(self) -> str:
|
|
||||||
"""Return the mac address of the main device."""
|
|
||||||
return self._avm_wrapper.mac
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_info(self) -> DeviceInfo:
|
def device_info(self) -> DeviceInfo:
|
||||||
"""Return the device information."""
|
"""Return the device information."""
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
configuration_url=f"http://{self._avm_wrapper.host}",
|
|
||||||
connections={(dr.CONNECTION_NETWORK_MAC, self.mac_address)},
|
connections={(dr.CONNECTION_NETWORK_MAC, self.mac_address)},
|
||||||
identifiers={(DOMAIN, self._avm_wrapper.unique_id)},
|
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",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal",
|
"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_hs_color = color_util.color_RGB_to_hs(*rgb)
|
||||||
self._attr_color_mode = ColorMode.HS
|
self._attr_color_mode = ColorMode.HS
|
||||||
else:
|
else:
|
||||||
|
color_temp = self.device["status"].get("color_temp")
|
||||||
self._attr_color_temp_kelvin = (
|
self._attr_color_temp_kelvin = (
|
||||||
None
|
None
|
||||||
if color_temp is None
|
if color_temp is None
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
"documentation": "https://www.home-assistant.io/integrations/holiday",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["holidays==0.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],
|
error_translation_placeholders: dict[str, str],
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
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:
|
except api.HomeConnectError as err:
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
translation_domain=DOMAIN,
|
translation_domain=DOMAIN,
|
||||||
|
@ -220,7 +220,7 @@ async def async_setup_entry(
|
|||||||
with contextlib.suppress(HomeConnectError):
|
with contextlib.suppress(HomeConnectError):
|
||||||
programs = device.appliance.get_programs_available()
|
programs = device.appliance.get_programs_available()
|
||||||
if programs:
|
if programs:
|
||||||
for program in programs:
|
for program in programs.copy():
|
||||||
if program not in PROGRAMS_TRANSLATION_KEYS_MAP:
|
if program not in PROGRAMS_TRANSLATION_KEYS_MAP:
|
||||||
programs.remove(program)
|
programs.remove(program)
|
||||||
if program not in programs_not_found:
|
if program not in programs_not_found:
|
||||||
|
@ -12,6 +12,6 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["homewizard_energy"],
|
"loggers": ["homewizard_energy"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["python-homewizard-energy==v7.0.0"],
|
"requirements": ["python-homewizard-energy==v7.0.1"],
|
||||||
"zeroconf": ["_hwenergy._tcp.local."]
|
"zeroconf": ["_hwenergy._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@ -188,8 +188,8 @@ PINECIL_NUMBER_DESCRIPTIONS: tuple[IronOSNumberEntityDescription, ...] = (
|
|||||||
characteristic=CharSetting.POWER_LIMIT,
|
characteristic=CharSetting.POWER_LIMIT,
|
||||||
mode=NumberMode.BOX,
|
mode=NumberMode.BOX,
|
||||||
native_min_value=0,
|
native_min_value=0,
|
||||||
native_max_value=12,
|
native_max_value=120,
|
||||||
native_step=0.1,
|
native_step=5,
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
|
@ -128,8 +128,8 @@
|
|||||||
"temp_unit": {
|
"temp_unit": {
|
||||||
"name": "Temperature display unit",
|
"name": "Temperature display unit",
|
||||||
"state": {
|
"state": {
|
||||||
"celsius": "Celsius (C°)",
|
"celsius": "Celsius (°C)",
|
||||||
"fahrenheit": "Fahrenheit (F°)"
|
"fahrenheit": "Fahrenheit (°F)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"desc_scroll_speed": {
|
"desc_scroll_speed": {
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["demetriek"],
|
"loggers": ["demetriek"],
|
||||||
"requirements": ["demetriek==1.1.0"],
|
"requirements": ["demetriek==1.1.1"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"deviceType": "urn:schemas-upnp-org:device:LaMetric:1"
|
"deviceType": "urn:schemas-upnp-org:device:LaMetric:1"
|
||||||
|
@ -50,7 +50,7 @@ NUMBERS = [
|
|||||||
native_step=1,
|
native_step=1,
|
||||||
native_min_value=0,
|
native_min_value=0,
|
||||||
native_max_value=100,
|
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,
|
value_fn=lambda device: device.audio.volume if device.audio else 0,
|
||||||
set_value_fn=lambda api, volume: api.audio(volume=int(volume)),
|
set_value_fn=lambda api, volume: api.audio(volume=int(volume)),
|
||||||
),
|
),
|
||||||
|
@ -53,6 +53,6 @@
|
|||||||
"requirements": [
|
"requirements": [
|
||||||
"aiolifx==1.1.2",
|
"aiolifx==1.1.2",
|
||||||
"aiolifx-effects==0.3.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:
|
if not params:
|
||||||
return params
|
return params
|
||||||
|
|
||||||
supported_features = light.supported_features
|
supported_features = light.supported_features_compat
|
||||||
|
|
||||||
if LightEntityFeature.FLASH not in supported_features:
|
if LightEntityFeature.FLASH not in supported_features:
|
||||||
params.pop(ATTR_FLASH, None)
|
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]:
|
def filter_turn_on_params(light: LightEntity, params: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Filter out params not supported by the light."""
|
"""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:
|
if LightEntityFeature.EFFECT not in supported_features:
|
||||||
params.pop(ATTR_EFFECT, None)
|
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]:
|
def capability_attributes(self) -> dict[str, Any]:
|
||||||
"""Return capability attributes."""
|
"""Return capability attributes."""
|
||||||
data: dict[str, Any] = {}
|
data: dict[str, Any] = {}
|
||||||
supported_features = self.supported_features
|
supported_features = self.supported_features_compat
|
||||||
supported_color_modes = self._light_internal_supported_color_modes
|
supported_color_modes = self._light_internal_supported_color_modes
|
||||||
|
|
||||||
if ColorMode.COLOR_TEMP in 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:
|
def state_attributes(self) -> dict[str, Any] | None:
|
||||||
"""Return state attributes."""
|
"""Return state attributes."""
|
||||||
data: dict[str, Any] = {}
|
data: dict[str, Any] = {}
|
||||||
supported_features = self.supported_features
|
supported_features = self.supported_features_compat
|
||||||
supported_color_modes = self.supported_color_modes
|
supported_color_modes = self.supported_color_modes
|
||||||
legacy_supported_color_modes = (
|
legacy_supported_color_modes = (
|
||||||
supported_color_modes or self._light_internal_supported_color_modes
|
supported_color_modes or self._light_internal_supported_color_modes
|
||||||
)
|
)
|
||||||
|
supported_features_value = supported_features.value
|
||||||
_is_on = self.is_on
|
_is_on = self.is_on
|
||||||
color_mode = self._light_internal_color_mode if _is_on else None
|
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
|
data[ATTR_BRIGHTNESS] = self.brightness
|
||||||
else:
|
else:
|
||||||
data[ATTR_BRIGHTNESS] = None
|
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_temp_supported(supported_color_modes):
|
||||||
if color_mode == ColorMode.COLOR_TEMP:
|
if color_mode == ColorMode.COLOR_TEMP:
|
||||||
@ -1292,6 +1300,21 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
else:
|
else:
|
||||||
data[ATTR_COLOR_TEMP_KELVIN] = None
|
data[ATTR_COLOR_TEMP_KELVIN] = None
|
||||||
data[_DEPRECATED_ATTR_COLOR_TEMP.value] = 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(
|
if color_supported(legacy_supported_color_modes) or color_temp_supported(
|
||||||
legacy_supported_color_modes
|
legacy_supported_color_modes
|
||||||
@ -1329,7 +1352,24 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
type(self),
|
type(self),
|
||||||
report_issue,
|
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
|
@cached_property
|
||||||
def supported_color_modes(self) -> set[ColorMode] | set[str] | None:
|
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."""
|
"""Flag supported features."""
|
||||||
return self._attr_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:
|
def __should_report_light_issue(self) -> bool:
|
||||||
"""Return if light color mode issues should be reported."""
|
"""Return if light color mode issues should be reported."""
|
||||||
if not self.platform:
|
if not self.platform:
|
||||||
|
@ -57,6 +57,9 @@
|
|||||||
},
|
},
|
||||||
"valve_position": {
|
"valve_position": {
|
||||||
"default": "mdi:valve"
|
"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."""
|
"""Flag media player features that are supported."""
|
||||||
return self._attr_supported_features
|
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:
|
def turn_on(self) -> None:
|
||||||
"""Turn the media player on."""
|
"""Turn the media player on."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@ -912,85 +925,87 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
@property
|
@property
|
||||||
def support_play(self) -> bool:
|
def support_play(self) -> bool:
|
||||||
"""Boolean if play is supported."""
|
"""Boolean if play is supported."""
|
||||||
return MediaPlayerEntityFeature.PLAY in self.supported_features
|
return MediaPlayerEntityFeature.PLAY in self.supported_features_compat
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
def support_pause(self) -> bool:
|
def support_pause(self) -> bool:
|
||||||
"""Boolean if pause is supported."""
|
"""Boolean if pause is supported."""
|
||||||
return MediaPlayerEntityFeature.PAUSE in self.supported_features
|
return MediaPlayerEntityFeature.PAUSE in self.supported_features_compat
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
def support_stop(self) -> bool:
|
def support_stop(self) -> bool:
|
||||||
"""Boolean if stop is supported."""
|
"""Boolean if stop is supported."""
|
||||||
return MediaPlayerEntityFeature.STOP in self.supported_features
|
return MediaPlayerEntityFeature.STOP in self.supported_features_compat
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
def support_seek(self) -> bool:
|
def support_seek(self) -> bool:
|
||||||
"""Boolean if seek is supported."""
|
"""Boolean if seek is supported."""
|
||||||
return MediaPlayerEntityFeature.SEEK in self.supported_features
|
return MediaPlayerEntityFeature.SEEK in self.supported_features_compat
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
def support_volume_set(self) -> bool:
|
def support_volume_set(self) -> bool:
|
||||||
"""Boolean if setting volume is supported."""
|
"""Boolean if setting volume is supported."""
|
||||||
return MediaPlayerEntityFeature.VOLUME_SET in self.supported_features
|
return MediaPlayerEntityFeature.VOLUME_SET in self.supported_features_compat
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
def support_volume_mute(self) -> bool:
|
def support_volume_mute(self) -> bool:
|
||||||
"""Boolean if muting volume is supported."""
|
"""Boolean if muting volume is supported."""
|
||||||
return MediaPlayerEntityFeature.VOLUME_MUTE in self.supported_features
|
return MediaPlayerEntityFeature.VOLUME_MUTE in self.supported_features_compat
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
def support_previous_track(self) -> bool:
|
def support_previous_track(self) -> bool:
|
||||||
"""Boolean if previous track command supported."""
|
"""Boolean if previous track command supported."""
|
||||||
return MediaPlayerEntityFeature.PREVIOUS_TRACK in self.supported_features
|
return MediaPlayerEntityFeature.PREVIOUS_TRACK in self.supported_features_compat
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
def support_next_track(self) -> bool:
|
def support_next_track(self) -> bool:
|
||||||
"""Boolean if next track command supported."""
|
"""Boolean if next track command supported."""
|
||||||
return MediaPlayerEntityFeature.NEXT_TRACK in self.supported_features
|
return MediaPlayerEntityFeature.NEXT_TRACK in self.supported_features_compat
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
def support_play_media(self) -> bool:
|
def support_play_media(self) -> bool:
|
||||||
"""Boolean if play media command supported."""
|
"""Boolean if play media command supported."""
|
||||||
return MediaPlayerEntityFeature.PLAY_MEDIA in self.supported_features
|
return MediaPlayerEntityFeature.PLAY_MEDIA in self.supported_features_compat
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
def support_select_source(self) -> bool:
|
def support_select_source(self) -> bool:
|
||||||
"""Boolean if select source command supported."""
|
"""Boolean if select source command supported."""
|
||||||
return MediaPlayerEntityFeature.SELECT_SOURCE in self.supported_features
|
return MediaPlayerEntityFeature.SELECT_SOURCE in self.supported_features_compat
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
def support_select_sound_mode(self) -> bool:
|
def support_select_sound_mode(self) -> bool:
|
||||||
"""Boolean if select sound mode command supported."""
|
"""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
|
@final
|
||||||
@property
|
@property
|
||||||
def support_clear_playlist(self) -> bool:
|
def support_clear_playlist(self) -> bool:
|
||||||
"""Boolean if clear playlist command supported."""
|
"""Boolean if clear playlist command supported."""
|
||||||
return MediaPlayerEntityFeature.CLEAR_PLAYLIST in self.supported_features
|
return MediaPlayerEntityFeature.CLEAR_PLAYLIST in self.supported_features_compat
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
def support_shuffle_set(self) -> bool:
|
def support_shuffle_set(self) -> bool:
|
||||||
"""Boolean if shuffle is supported."""
|
"""Boolean if shuffle is supported."""
|
||||||
return MediaPlayerEntityFeature.SHUFFLE_SET in self.supported_features
|
return MediaPlayerEntityFeature.SHUFFLE_SET in self.supported_features_compat
|
||||||
|
|
||||||
@final
|
@final
|
||||||
@property
|
@property
|
||||||
def support_grouping(self) -> bool:
|
def support_grouping(self) -> bool:
|
||||||
"""Boolean if player grouping is supported."""
|
"""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:
|
async def async_toggle(self) -> None:
|
||||||
"""Toggle the power on the media player."""
|
"""Toggle the power on the media player."""
|
||||||
@ -1019,7 +1034,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
if (
|
if (
|
||||||
self.volume_level is not None
|
self.volume_level is not None
|
||||||
and self.volume_level < 1
|
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(
|
await self.async_set_volume_level(
|
||||||
min(1, self.volume_level + self.volume_step)
|
min(1, self.volume_level + self.volume_step)
|
||||||
@ -1037,7 +1052,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
|||||||
if (
|
if (
|
||||||
self.volume_level is not None
|
self.volume_level is not None
|
||||||
and self.volume_level > 0
|
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(
|
await self.async_set_volume_level(
|
||||||
max(0, self.volume_level - self.volume_step)
|
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]:
|
def capability_attributes(self) -> dict[str, Any]:
|
||||||
"""Return capability attributes."""
|
"""Return capability attributes."""
|
||||||
data: dict[str, Any] = {}
|
data: dict[str, Any] = {}
|
||||||
supported_features = self.supported_features
|
supported_features = self.supported_features_compat
|
||||||
|
|
||||||
if (
|
if (
|
||||||
source_list := self.source_list
|
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")
|
connection.send_error(msg["id"], "entity_not_found", "Entity not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
if MediaPlayerEntityFeature.BROWSE_MEDIA not in player.supported_features:
|
if MediaPlayerEntityFeature.BROWSE_MEDIA not in player.supported_features_compat:
|
||||||
connection.send_message(
|
connection.send_message(
|
||||||
websocket_api.error_message(
|
websocket_api.error_message(
|
||||||
msg["id"], ERR_NOT_SUPPORTED, "Player does not support browsing media"
|
msg["id"], ERR_NOT_SUPPORTED, "Player does not support browsing media"
|
||||||
|
@ -7,6 +7,6 @@
|
|||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["peblar==0.3.2"],
|
"requirements": ["peblar==0.3.3"],
|
||||||
"zeroconf": [{ "type": "_http._tcp.local.", "name": "pblr-*" }]
|
"zeroconf": [{ "type": "_http._tcp.local.", "name": "pblr-*" }]
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ DESCRIPTIONS: tuple[PeblarUpdateEntityDescription, ...] = (
|
|||||||
key="firmware",
|
key="firmware",
|
||||||
device_class=UpdateDeviceClass.FIRMWARE,
|
device_class=UpdateDeviceClass.FIRMWARE,
|
||||||
installed_fn=lambda x: x.current.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,
|
available_fn=lambda x: x.available.firmware,
|
||||||
),
|
),
|
||||||
PeblarUpdateEntityDescription(
|
PeblarUpdateEntityDescription(
|
||||||
|
@ -7,6 +7,7 @@ from powerfox import (
|
|||||||
Powerfox,
|
Powerfox,
|
||||||
PowerfoxAuthenticationError,
|
PowerfoxAuthenticationError,
|
||||||
PowerfoxConnectionError,
|
PowerfoxConnectionError,
|
||||||
|
PowerfoxNoDataError,
|
||||||
Poweropti,
|
Poweropti,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,5 +46,5 @@ class PowerfoxDataUpdateCoordinator(DataUpdateCoordinator[Poweropti]):
|
|||||||
return await self.client.device(device_id=self.device.id)
|
return await self.client.device(device_id=self.device.id)
|
||||||
except PowerfoxAuthenticationError as err:
|
except PowerfoxAuthenticationError as err:
|
||||||
raise ConfigEntryAuthFailed(err) from err
|
raise ConfigEntryAuthFailed(err) from err
|
||||||
except PowerfoxConnectionError as err:
|
except (PowerfoxConnectionError, PowerfoxNoDataError) as err:
|
||||||
raise UpdateFailed(err) from err
|
raise UpdateFailed(err) from err
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/powerfox",
|
"documentation": "https://www.home-assistant.io/integrations/powerfox",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["powerfox==1.0.0"],
|
"requirements": ["powerfox==1.2.0"],
|
||||||
"zeroconf": [
|
"zeroconf": [
|
||||||
{
|
{
|
||||||
"type": "_http._tcp.local.",
|
"type": "_http._tcp.local.",
|
||||||
|
@ -180,7 +180,7 @@ def guarded_import(
|
|||||||
# Allow import of _strptime needed by datetime.datetime.strptime
|
# Allow import of _strptime needed by datetime.datetime.strptime
|
||||||
if name == "_strptime":
|
if name == "_strptime":
|
||||||
return __import__(name, globals, locals, fromlist, level)
|
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:
|
def guarded_inplacevar(op: str, target: Any, operand: Any) -> Any:
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from base64 import urlsafe_b64decode, urlsafe_b64encode
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
import logging
|
import logging
|
||||||
from urllib import parse
|
|
||||||
|
|
||||||
from aiohttp import ClientError, ClientTimeout, web
|
from aiohttp import ClientError, ClientTimeout, web
|
||||||
from reolink_aio.enums import VodRequestType
|
from reolink_aio.enums import VodRequestType
|
||||||
@ -31,7 +31,7 @@ def async_generate_playback_proxy_url(
|
|||||||
return url_format.format(
|
return url_format.format(
|
||||||
config_entry_id=config_entry_id,
|
config_entry_id=config_entry_id,
|
||||||
channel=channel,
|
channel=channel,
|
||||||
filename=parse.quote(filename, safe=""),
|
filename=urlsafe_b64encode(filename.encode("utf-8")).decode("utf-8"),
|
||||||
stream_res=stream_res,
|
stream_res=stream_res,
|
||||||
vod_type=vod_type,
|
vod_type=vod_type,
|
||||||
)
|
)
|
||||||
@ -66,7 +66,7 @@ class PlaybackProxyView(HomeAssistantView):
|
|||||||
"""Get playback proxy video response."""
|
"""Get playback proxy video response."""
|
||||||
retry = retry - 1
|
retry = retry - 1
|
||||||
|
|
||||||
filename = parse.unquote(filename)
|
filename_decoded = urlsafe_b64decode(filename.encode("utf-8")).decode("utf-8")
|
||||||
ch = int(channel)
|
ch = int(channel)
|
||||||
try:
|
try:
|
||||||
host = get_host(self.hass, config_entry_id)
|
host = get_host(self.hass, config_entry_id)
|
||||||
@ -77,7 +77,7 @@ class PlaybackProxyView(HomeAssistantView):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
mime_type, reolink_url = await host.api.get_vod_source(
|
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:
|
except ReolinkError as err:
|
||||||
_LOGGER.warning("Reolink playback proxy error: %s", str(err))
|
_LOGGER.warning("Reolink playback proxy error: %s", str(err))
|
||||||
|
@ -9,7 +9,13 @@ from datetime import timedelta
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any
|
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.containers import DeviceData, HomeDataDevice, HomeDataProduct, UserData
|
||||||
from roborock.version_1_apis.roborock_mqtt_client_v1 import RoborockMqttClientV1
|
from roborock.version_1_apis.roborock_mqtt_client_v1 import RoborockMqttClientV1
|
||||||
from roborock.version_a01_apis import RoborockMqttClientA01
|
from roborock.version_a01_apis import RoborockMqttClientA01
|
||||||
@ -60,12 +66,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
|
|||||||
translation_domain=DOMAIN,
|
translation_domain=DOMAIN,
|
||||||
translation_key="invalid_credentials",
|
translation_key="invalid_credentials",
|
||||||
) from err
|
) 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:
|
except RoborockException as err:
|
||||||
raise ConfigEntryNotReady(
|
raise ConfigEntryNotReady(
|
||||||
"Failed to get Roborock home data",
|
"Failed to get Roborock home data",
|
||||||
translation_domain=DOMAIN,
|
translation_domain=DOMAIN,
|
||||||
translation_key="home_data_fail",
|
translation_key="home_data_fail",
|
||||||
) from err
|
) from err
|
||||||
|
|
||||||
_LOGGER.debug("Got home data %s", home_data)
|
_LOGGER.debug("Got home data %s", home_data)
|
||||||
all_devices: list[HomeDataDevice] = home_data.devices + home_data.received_devices
|
all_devices: list[HomeDataDevice] = home_data.devices + home_data.received_devices
|
||||||
device_map: dict[str, HomeDataDevice] = {
|
device_map: dict[str, HomeDataDevice] = {
|
||||||
|
@ -60,7 +60,7 @@ class RoborockFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
username = user_input[CONF_USERNAME]
|
username = user_input[CONF_USERNAME]
|
||||||
await self.async_set_unique_id(username.lower())
|
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
|
self._username = username
|
||||||
_LOGGER.debug("Requesting code for Roborock account")
|
_LOGGER.debug("Requesting code for Roborock account")
|
||||||
self._client = RoborockApiClient(username)
|
self._client = RoborockApiClient(username)
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
},
|
},
|
||||||
"abort": {
|
"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%]"
|
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -422,6 +422,12 @@
|
|||||||
},
|
},
|
||||||
"update_options_failed": {
|
"update_options_failed": {
|
||||||
"message": "Failed to update Roborock options"
|
"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": {
|
"services": {
|
||||||
|
@ -73,7 +73,6 @@ class SlideConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
# API version 2 is not working, try API version 1 instead
|
# API version 2 is not working, try API version 1 instead
|
||||||
await slide.slide_del(user_input[CONF_HOST])
|
|
||||||
await slide.slide_add(
|
await slide.slide_add(
|
||||||
user_input[CONF_HOST],
|
user_input[CONF_HOST],
|
||||||
user_input.get(CONF_PASSWORD, ""),
|
user_input.get(CONF_PASSWORD, ""),
|
||||||
@ -185,14 +184,15 @@ class SlideConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
await self.async_set_unique_id(self._mac)
|
await self.async_set_unique_id(self._mac)
|
||||||
|
|
||||||
self._abort_if_unique_id_configured(
|
ip = str(discovery_info.ip_address)
|
||||||
{CONF_HOST: discovery_info.host}, reload_on_update=True
|
_LOGGER.debug("Slide device discovered, ip %s", ip)
|
||||||
)
|
|
||||||
|
self._abort_if_unique_id_configured({CONF_HOST: ip}, reload_on_update=True)
|
||||||
|
|
||||||
errors = {}
|
errors = {}
|
||||||
if errors := await self.async_test_connection(
|
if errors := await self.async_test_connection(
|
||||||
{
|
{
|
||||||
CONF_HOST: self._host,
|
CONF_HOST: ip,
|
||||||
}
|
}
|
||||||
):
|
):
|
||||||
return self.async_abort(
|
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()
|
return await self.async_step_zeroconf_confirm()
|
||||||
|
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/solax",
|
"documentation": "https://www.home-assistant.io/integrations/solax",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["solax"],
|
"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]
|
item_type = CONTENT_TYPE_TO_CHILD_TYPE[search_type]
|
||||||
|
|
||||||
children = []
|
children = []
|
||||||
|
list_playable = []
|
||||||
for item in result["items"]:
|
for item in result["items"]:
|
||||||
item_id = str(item["id"])
|
item_id = str(item["id"])
|
||||||
item_thumbnail: str | None = None
|
item_thumbnail: str | None = None
|
||||||
@ -131,7 +132,7 @@ async def build_item_response(
|
|||||||
child_media_class = CONTENT_TYPE_MEDIA_CLASS[MediaType.ALBUM]
|
child_media_class = CONTENT_TYPE_MEDIA_CLASS[MediaType.ALBUM]
|
||||||
can_expand = True
|
can_expand = True
|
||||||
can_play = True
|
can_play = True
|
||||||
elif item["hasitems"]:
|
elif item["hasitems"] and not item["isaudio"]:
|
||||||
child_item_type = "Favorites"
|
child_item_type = "Favorites"
|
||||||
child_media_class = CONTENT_TYPE_MEDIA_CLASS["Favorites"]
|
child_media_class = CONTENT_TYPE_MEDIA_CLASS["Favorites"]
|
||||||
can_expand = True
|
can_expand = True
|
||||||
@ -139,8 +140,8 @@ async def build_item_response(
|
|||||||
else:
|
else:
|
||||||
child_item_type = "Favorites"
|
child_item_type = "Favorites"
|
||||||
child_media_class = CONTENT_TYPE_MEDIA_CLASS[MediaType.TRACK]
|
child_media_class = CONTENT_TYPE_MEDIA_CLASS[MediaType.TRACK]
|
||||||
can_expand = False
|
can_expand = item["hasitems"]
|
||||||
can_play = True
|
can_play = item["isaudio"] and item.get("url")
|
||||||
|
|
||||||
if artwork_track_id := item.get("artwork_track_id"):
|
if artwork_track_id := item.get("artwork_track_id"):
|
||||||
if internal_request:
|
if internal_request:
|
||||||
@ -166,6 +167,7 @@ async def build_item_response(
|
|||||||
thumbnail=item_thumbnail,
|
thumbnail=item_thumbnail,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
list_playable.append(can_play)
|
||||||
|
|
||||||
if children is None:
|
if children is None:
|
||||||
raise BrowseError(f"Media not found: {search_type} / {search_id}")
|
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"],
|
children_media_class=media_class["children"],
|
||||||
media_content_id=search_id,
|
media_content_id=search_id,
|
||||||
media_content_type=search_type,
|
media_content_type=search_type,
|
||||||
can_play=search_type != "Favorites",
|
can_play=any(list_playable),
|
||||||
children=children,
|
children=children,
|
||||||
can_expand=True,
|
can_expand=True,
|
||||||
)
|
)
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pysuez", "regex"],
|
"loggers": ["pysuez", "regex"],
|
||||||
"quality_scale": "bronze",
|
"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",
|
"documentation": "https://www.home-assistant.io/integrations/tplink",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["kasa"],
|
"loggers": ["kasa"],
|
||||||
"requirements": ["python-kasa[speedups]==0.9.0"]
|
"requirements": ["python-kasa[speedups]==0.9.1"]
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
},
|
},
|
||||||
"user_auth_confirm": {
|
"user_auth_confirm": {
|
||||||
"title": "Authenticate",
|
"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": {
|
"data": {
|
||||||
"username": "[%key:common::config_flow::data::username%]",
|
"username": "[%key:common::config_flow::data::username%]",
|
||||||
"password": "[%key:common::config_flow::data::password%]"
|
"password": "[%key:common::config_flow::data::password%]"
|
||||||
|
@ -8,5 +8,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["twentemilieu"],
|
"loggers": ["twentemilieu"],
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["twentemilieu==2.2.0"]
|
"requirements": ["twentemilieu==2.2.1"]
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["uiprotect", "unifi_discovery"],
|
"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": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"manufacturer": "Ubiquiti Networks",
|
"manufacturer": "Ubiquiti Networks",
|
||||||
|
@ -312,7 +312,7 @@ class StateVacuumEntity(
|
|||||||
@property
|
@property
|
||||||
def capability_attributes(self) -> dict[str, Any] | None:
|
def capability_attributes(self) -> dict[str, Any] | None:
|
||||||
"""Return capability attributes."""
|
"""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 {ATTR_FAN_SPEED_LIST: self.fan_speed_list}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -330,7 +330,7 @@ class StateVacuumEntity(
|
|||||||
def state_attributes(self) -> dict[str, Any]:
|
def state_attributes(self) -> dict[str, Any]:
|
||||||
"""Return the state attributes of the vacuum cleaner."""
|
"""Return the state attributes of the vacuum cleaner."""
|
||||||
data: dict[str, Any] = {}
|
data: dict[str, Any] = {}
|
||||||
supported_features = self.supported_features
|
supported_features = self.supported_features_compat
|
||||||
|
|
||||||
if VacuumEntityFeature.BATTERY in supported_features:
|
if VacuumEntityFeature.BATTERY in supported_features:
|
||||||
data[ATTR_BATTERY_LEVEL] = self.battery_level
|
data[ATTR_BATTERY_LEVEL] = self.battery_level
|
||||||
@ -369,6 +369,19 @@ class StateVacuumEntity(
|
|||||||
"""Flag vacuum cleaner features that are supported."""
|
"""Flag vacuum cleaner features that are supported."""
|
||||||
return self._attr_supported_features
|
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:
|
def stop(self, **kwargs: Any) -> None:
|
||||||
"""Stop the vacuum cleaner."""
|
"""Stop the vacuum cleaner."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"user": {
|
"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": {
|
"data": {
|
||||||
"name": "[%key:common::config_flow::data::name%]",
|
"name": "[%key:common::config_flow::data::name%]",
|
||||||
"origin": "Origin",
|
"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.",
|
"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": {
|
"data": {
|
||||||
"units": "Units",
|
"units": "Units",
|
||||||
"vehicle_type": "Vehicle Type",
|
"vehicle_type": "Vehicle type",
|
||||||
"incl_filter": "Exact streetname which must be part of the selected route",
|
"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",
|
"excl_filter": "Exact streetname which must NOT be part of the selected route",
|
||||||
"realtime": "Realtime Travel Time?",
|
"realtime": "Realtime travel time?",
|
||||||
"avoid_toll_roads": "Avoid Toll Roads?",
|
"avoid_toll_roads": "Avoid toll roads?",
|
||||||
"avoid_ferries": "Avoid Ferries?",
|
"avoid_ferries": "Avoid ferries?",
|
||||||
"avoid_subscription_roads": "Avoid Roads Needing a Vignette / Subscription?"
|
"avoid_subscription_roads": "Avoid roads needing a vignette / subscription?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,8 +47,8 @@
|
|||||||
},
|
},
|
||||||
"units": {
|
"units": {
|
||||||
"options": {
|
"options": {
|
||||||
"metric": "Metric System",
|
"metric": "Metric system",
|
||||||
"imperial": "Imperial System"
|
"imperial": "Imperial system"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"region": {
|
"region": {
|
||||||
@ -63,8 +63,8 @@
|
|||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"get_travel_times": {
|
"get_travel_times": {
|
||||||
"name": "Get Travel Times",
|
"name": "Get travel times",
|
||||||
"description": "Get route alternatives and travel times between two locations.",
|
"description": "Retrieves route alternatives and travel times between two locations.",
|
||||||
"fields": {
|
"fields": {
|
||||||
"origin": {
|
"origin": {
|
||||||
"name": "[%key:component::waze_travel_time::config::step::user::data::origin%]",
|
"name": "[%key:component::waze_travel_time::config::step::user::data::origin%]",
|
||||||
@ -76,7 +76,7 @@
|
|||||||
},
|
},
|
||||||
"region": {
|
"region": {
|
||||||
"name": "[%key:component::waze_travel_time::config::step::user::data::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": {
|
"units": {
|
||||||
"name": "[%key:component::waze_travel_time::options::step::init::data::units%]",
|
"name": "[%key:component::waze_travel_time::options::step::init::data::units%]",
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["holidays"],
|
"loggers": ["holidays"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["holidays==0.63"]
|
"requirements": ["holidays==0.64"]
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
"zha",
|
"zha",
|
||||||
"universal_silabs_flasher"
|
"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": [
|
"usb": [
|
||||||
{
|
{
|
||||||
"vid": "10C4",
|
"vid": "10C4",
|
||||||
|
@ -25,7 +25,7 @@ if TYPE_CHECKING:
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2025
|
MAJOR_VERSION: Final = 2025
|
||||||
MINOR_VERSION: Final = 1
|
MINOR_VERSION: Final = 1
|
||||||
PATCH_VERSION: Final = "0"
|
PATCH_VERSION: Final = "1"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
||||||
|
@ -7,7 +7,7 @@ import asyncio
|
|||||||
from collections import deque
|
from collections import deque
|
||||||
from collections.abc import Callable, Coroutine, Iterable, Mapping
|
from collections.abc import Callable, Coroutine, Iterable, Mapping
|
||||||
import dataclasses
|
import dataclasses
|
||||||
from enum import Enum, auto
|
from enum import Enum, IntFlag, auto
|
||||||
import functools as ft
|
import functools as ft
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
@ -1639,6 +1639,31 @@ class Entity(
|
|||||||
self.hass, integration_domain=platform_name, module=type(self).__module__
|
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):
|
class ToggleEntityDescription(EntityDescription, frozen_or_thawed=True):
|
||||||
"""A class that describes toggle entities."""
|
"""A class that describes toggle entities."""
|
||||||
|
@ -31,11 +31,11 @@ dbus-fast==2.24.3
|
|||||||
fnv-hash-fast==1.0.2
|
fnv-hash-fast==1.0.2
|
||||||
go2rtc-client==0.1.2
|
go2rtc-client==0.1.2
|
||||||
ha-ffmpeg==3.2.2
|
ha-ffmpeg==3.2.2
|
||||||
habluetooth==3.6.0
|
habluetooth==3.7.0
|
||||||
hass-nabucasa==0.87.0
|
hass-nabucasa==0.87.0
|
||||||
hassil==2.1.0
|
hassil==2.1.0
|
||||||
home-assistant-bluetooth==1.13.0
|
home-assistant-bluetooth==1.13.0
|
||||||
home-assistant-frontend==20250103.0
|
home-assistant-frontend==20250106.0
|
||||||
home-assistant-intents==2025.1.1
|
home-assistant-intents==2025.1.1
|
||||||
httpx==0.27.2
|
httpx==0.27.2
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2025.1.0"
|
version = "2025.1.1"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
|
@ -282,7 +282,7 @@ aiokef==0.2.16
|
|||||||
aiolifx-effects==0.3.2
|
aiolifx-effects==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.lifx
|
# homeassistant.components.lifx
|
||||||
aiolifx-themes==0.5.5
|
aiolifx-themes==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.lifx
|
# homeassistant.components.lifx
|
||||||
aiolifx==1.1.2
|
aiolifx==1.1.2
|
||||||
@ -585,7 +585,7 @@ bizkaibus==0.1.1
|
|||||||
|
|
||||||
# homeassistant.components.eq3btsmart
|
# homeassistant.components.eq3btsmart
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
bleak-esphome==1.1.0
|
bleak-esphome==2.0.0
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bleak-retry-connector==3.6.0
|
bleak-retry-connector==3.6.0
|
||||||
@ -704,7 +704,7 @@ connect-box==0.3.1
|
|||||||
construct==2.10.68
|
construct==2.10.68
|
||||||
|
|
||||||
# homeassistant.components.cookidoo
|
# homeassistant.components.cookidoo
|
||||||
cookidoo-api==0.10.0
|
cookidoo-api==0.11.2
|
||||||
|
|
||||||
# homeassistant.components.backup
|
# homeassistant.components.backup
|
||||||
# homeassistant.components.utility_meter
|
# homeassistant.components.utility_meter
|
||||||
@ -749,7 +749,7 @@ defusedxml==0.7.1
|
|||||||
deluge-client==1.10.2
|
deluge-client==1.10.2
|
||||||
|
|
||||||
# homeassistant.components.lametric
|
# homeassistant.components.lametric
|
||||||
demetriek==1.1.0
|
demetriek==1.1.1
|
||||||
|
|
||||||
# homeassistant.components.denonavr
|
# homeassistant.components.denonavr
|
||||||
denonavr==1.0.1
|
denonavr==1.0.1
|
||||||
@ -1091,7 +1091,7 @@ ha-philipsjs==3.2.2
|
|||||||
habitipy==0.3.3
|
habitipy==0.3.3
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
habluetooth==3.6.0
|
habluetooth==3.7.0
|
||||||
|
|
||||||
# homeassistant.components.cloud
|
# homeassistant.components.cloud
|
||||||
hass-nabucasa==0.87.0
|
hass-nabucasa==0.87.0
|
||||||
@ -1131,10 +1131,10 @@ hole==0.8.0
|
|||||||
|
|
||||||
# homeassistant.components.holiday
|
# homeassistant.components.holiday
|
||||||
# homeassistant.components.workday
|
# homeassistant.components.workday
|
||||||
holidays==0.63
|
holidays==0.64
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20250103.0
|
home-assistant-frontend==20250106.0
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2025.1.1
|
home-assistant-intents==2025.1.1
|
||||||
@ -1561,7 +1561,7 @@ openhomedevice==2.2.0
|
|||||||
opensensemap-api==0.2.0
|
opensensemap-api==0.2.0
|
||||||
|
|
||||||
# homeassistant.components.enigma2
|
# homeassistant.components.enigma2
|
||||||
openwebifpy==4.3.0
|
openwebifpy==4.3.1
|
||||||
|
|
||||||
# homeassistant.components.luci
|
# homeassistant.components.luci
|
||||||
openwrt-luci-rpc==1.1.17
|
openwrt-luci-rpc==1.1.17
|
||||||
@ -1603,7 +1603,7 @@ panasonic-viera==0.4.2
|
|||||||
pdunehd==1.3.2
|
pdunehd==1.3.2
|
||||||
|
|
||||||
# homeassistant.components.peblar
|
# homeassistant.components.peblar
|
||||||
peblar==0.3.2
|
peblar==0.3.3
|
||||||
|
|
||||||
# homeassistant.components.peco
|
# homeassistant.components.peco
|
||||||
peco==0.0.30
|
peco==0.0.30
|
||||||
@ -1650,7 +1650,7 @@ pmsensor==0.4
|
|||||||
poolsense==0.0.8
|
poolsense==0.0.8
|
||||||
|
|
||||||
# homeassistant.components.powerfox
|
# homeassistant.components.powerfox
|
||||||
powerfox==1.0.0
|
powerfox==1.2.0
|
||||||
|
|
||||||
# homeassistant.components.reddit
|
# homeassistant.components.reddit
|
||||||
praw==7.5.0
|
praw==7.5.0
|
||||||
@ -2309,7 +2309,7 @@ pysqueezebox==0.10.0
|
|||||||
pystiebeleltron==0.0.1.dev2
|
pystiebeleltron==0.0.1.dev2
|
||||||
|
|
||||||
# homeassistant.components.suez_water
|
# homeassistant.components.suez_water
|
||||||
pysuezV2==1.3.5
|
pysuezV2==2.0.1
|
||||||
|
|
||||||
# homeassistant.components.switchbee
|
# homeassistant.components.switchbee
|
||||||
pyswitchbee==1.8.3
|
pyswitchbee==1.8.3
|
||||||
@ -2363,7 +2363,7 @@ python-gitlab==1.6.0
|
|||||||
python-homeassistant-analytics==0.8.1
|
python-homeassistant-analytics==0.8.1
|
||||||
|
|
||||||
# homeassistant.components.homewizard
|
# homeassistant.components.homewizard
|
||||||
python-homewizard-energy==v7.0.0
|
python-homewizard-energy==v7.0.1
|
||||||
|
|
||||||
# homeassistant.components.hp_ilo
|
# homeassistant.components.hp_ilo
|
||||||
python-hpilo==4.4.3
|
python-hpilo==4.4.3
|
||||||
@ -2378,7 +2378,7 @@ python-join-api==0.0.9
|
|||||||
python-juicenet==1.1.0
|
python-juicenet==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.tplink
|
# homeassistant.components.tplink
|
||||||
python-kasa[speedups]==0.9.0
|
python-kasa[speedups]==0.9.1
|
||||||
|
|
||||||
# homeassistant.components.linkplay
|
# homeassistant.components.linkplay
|
||||||
python-linkplay==0.1.1
|
python-linkplay==0.1.1
|
||||||
@ -2720,7 +2720,7 @@ solaredge-local==0.2.3
|
|||||||
solarlog_cli==0.4.0
|
solarlog_cli==0.4.0
|
||||||
|
|
||||||
# homeassistant.components.solax
|
# homeassistant.components.solax
|
||||||
solax==3.2.1
|
solax==3.2.3
|
||||||
|
|
||||||
# homeassistant.components.somfy_mylink
|
# homeassistant.components.somfy_mylink
|
||||||
somfy-mylink-synergy==1.0.6
|
somfy-mylink-synergy==1.0.6
|
||||||
@ -2895,7 +2895,7 @@ ttn_client==1.2.0
|
|||||||
tuya-device-sharing-sdk==0.2.1
|
tuya-device-sharing-sdk==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.twentemilieu
|
# homeassistant.components.twentemilieu
|
||||||
twentemilieu==2.2.0
|
twentemilieu==2.2.1
|
||||||
|
|
||||||
# homeassistant.components.twilio
|
# homeassistant.components.twilio
|
||||||
twilio==6.32.0
|
twilio==6.32.0
|
||||||
@ -2910,7 +2910,7 @@ typedmonarchmoney==0.3.1
|
|||||||
uasiren==0.0.1
|
uasiren==0.0.1
|
||||||
|
|
||||||
# homeassistant.components.unifiprotect
|
# homeassistant.components.unifiprotect
|
||||||
uiprotect==7.1.0
|
uiprotect==7.4.1
|
||||||
|
|
||||||
# homeassistant.components.landisgyr_heat_meter
|
# homeassistant.components.landisgyr_heat_meter
|
||||||
ultraheat-api==0.5.7
|
ultraheat-api==0.5.7
|
||||||
@ -3100,7 +3100,7 @@ zeroconf==0.136.2
|
|||||||
zeversolar==0.3.2
|
zeversolar==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zha==0.0.44
|
zha==0.0.45
|
||||||
|
|
||||||
# homeassistant.components.zhong_hong
|
# homeassistant.components.zhong_hong
|
||||||
zhong-hong-hvac==1.0.13
|
zhong-hong-hvac==1.0.13
|
||||||
|
@ -264,7 +264,7 @@ aiokafka==0.10.0
|
|||||||
aiolifx-effects==0.3.2
|
aiolifx-effects==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.lifx
|
# homeassistant.components.lifx
|
||||||
aiolifx-themes==0.5.5
|
aiolifx-themes==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.lifx
|
# homeassistant.components.lifx
|
||||||
aiolifx==1.1.2
|
aiolifx==1.1.2
|
||||||
@ -516,7 +516,7 @@ bimmer-connected[china]==0.17.2
|
|||||||
|
|
||||||
# homeassistant.components.eq3btsmart
|
# homeassistant.components.eq3btsmart
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
bleak-esphome==1.1.0
|
bleak-esphome==2.0.0
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
bleak-retry-connector==3.6.0
|
bleak-retry-connector==3.6.0
|
||||||
@ -600,7 +600,7 @@ colorthief==0.2.1
|
|||||||
construct==2.10.68
|
construct==2.10.68
|
||||||
|
|
||||||
# homeassistant.components.cookidoo
|
# homeassistant.components.cookidoo
|
||||||
cookidoo-api==0.10.0
|
cookidoo-api==0.11.2
|
||||||
|
|
||||||
# homeassistant.components.backup
|
# homeassistant.components.backup
|
||||||
# homeassistant.components.utility_meter
|
# homeassistant.components.utility_meter
|
||||||
@ -639,7 +639,7 @@ defusedxml==0.7.1
|
|||||||
deluge-client==1.10.2
|
deluge-client==1.10.2
|
||||||
|
|
||||||
# homeassistant.components.lametric
|
# homeassistant.components.lametric
|
||||||
demetriek==1.1.0
|
demetriek==1.1.1
|
||||||
|
|
||||||
# homeassistant.components.denonavr
|
# homeassistant.components.denonavr
|
||||||
denonavr==1.0.1
|
denonavr==1.0.1
|
||||||
@ -932,7 +932,7 @@ ha-philipsjs==3.2.2
|
|||||||
habitipy==0.3.3
|
habitipy==0.3.3
|
||||||
|
|
||||||
# homeassistant.components.bluetooth
|
# homeassistant.components.bluetooth
|
||||||
habluetooth==3.6.0
|
habluetooth==3.7.0
|
||||||
|
|
||||||
# homeassistant.components.cloud
|
# homeassistant.components.cloud
|
||||||
hass-nabucasa==0.87.0
|
hass-nabucasa==0.87.0
|
||||||
@ -960,10 +960,10 @@ hole==0.8.0
|
|||||||
|
|
||||||
# homeassistant.components.holiday
|
# homeassistant.components.holiday
|
||||||
# homeassistant.components.workday
|
# homeassistant.components.workday
|
||||||
holidays==0.63
|
holidays==0.64
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20250103.0
|
home-assistant-frontend==20250106.0
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2025.1.1
|
home-assistant-intents==2025.1.1
|
||||||
@ -1303,7 +1303,7 @@ openerz-api==0.3.0
|
|||||||
openhomedevice==2.2.0
|
openhomedevice==2.2.0
|
||||||
|
|
||||||
# homeassistant.components.enigma2
|
# homeassistant.components.enigma2
|
||||||
openwebifpy==4.3.0
|
openwebifpy==4.3.1
|
||||||
|
|
||||||
# homeassistant.components.opower
|
# homeassistant.components.opower
|
||||||
opower==0.8.7
|
opower==0.8.7
|
||||||
@ -1330,7 +1330,7 @@ panasonic-viera==0.4.2
|
|||||||
pdunehd==1.3.2
|
pdunehd==1.3.2
|
||||||
|
|
||||||
# homeassistant.components.peblar
|
# homeassistant.components.peblar
|
||||||
peblar==0.3.2
|
peblar==0.3.3
|
||||||
|
|
||||||
# homeassistant.components.peco
|
# homeassistant.components.peco
|
||||||
peco==0.0.30
|
peco==0.0.30
|
||||||
@ -1360,7 +1360,7 @@ plumlightpad==0.0.11
|
|||||||
poolsense==0.0.8
|
poolsense==0.0.8
|
||||||
|
|
||||||
# homeassistant.components.powerfox
|
# homeassistant.components.powerfox
|
||||||
powerfox==1.0.0
|
powerfox==1.2.0
|
||||||
|
|
||||||
# homeassistant.components.reddit
|
# homeassistant.components.reddit
|
||||||
praw==7.5.0
|
praw==7.5.0
|
||||||
@ -1875,7 +1875,7 @@ pyspeex-noise==1.0.2
|
|||||||
pysqueezebox==0.10.0
|
pysqueezebox==0.10.0
|
||||||
|
|
||||||
# homeassistant.components.suez_water
|
# homeassistant.components.suez_water
|
||||||
pysuezV2==1.3.5
|
pysuezV2==2.0.1
|
||||||
|
|
||||||
# homeassistant.components.switchbee
|
# homeassistant.components.switchbee
|
||||||
pyswitchbee==1.8.3
|
pyswitchbee==1.8.3
|
||||||
@ -1905,7 +1905,7 @@ python-fullykiosk==0.0.14
|
|||||||
python-homeassistant-analytics==0.8.1
|
python-homeassistant-analytics==0.8.1
|
||||||
|
|
||||||
# homeassistant.components.homewizard
|
# homeassistant.components.homewizard
|
||||||
python-homewizard-energy==v7.0.0
|
python-homewizard-energy==v7.0.1
|
||||||
|
|
||||||
# homeassistant.components.izone
|
# homeassistant.components.izone
|
||||||
python-izone==1.2.9
|
python-izone==1.2.9
|
||||||
@ -1914,7 +1914,7 @@ python-izone==1.2.9
|
|||||||
python-juicenet==1.1.0
|
python-juicenet==1.1.0
|
||||||
|
|
||||||
# homeassistant.components.tplink
|
# homeassistant.components.tplink
|
||||||
python-kasa[speedups]==0.9.0
|
python-kasa[speedups]==0.9.1
|
||||||
|
|
||||||
# homeassistant.components.linkplay
|
# homeassistant.components.linkplay
|
||||||
python-linkplay==0.1.1
|
python-linkplay==0.1.1
|
||||||
@ -2181,7 +2181,7 @@ soco==0.30.6
|
|||||||
solarlog_cli==0.4.0
|
solarlog_cli==0.4.0
|
||||||
|
|
||||||
# homeassistant.components.solax
|
# homeassistant.components.solax
|
||||||
solax==3.2.1
|
solax==3.2.3
|
||||||
|
|
||||||
# homeassistant.components.somfy_mylink
|
# homeassistant.components.somfy_mylink
|
||||||
somfy-mylink-synergy==1.0.6
|
somfy-mylink-synergy==1.0.6
|
||||||
@ -2317,7 +2317,7 @@ ttn_client==1.2.0
|
|||||||
tuya-device-sharing-sdk==0.2.1
|
tuya-device-sharing-sdk==0.2.1
|
||||||
|
|
||||||
# homeassistant.components.twentemilieu
|
# homeassistant.components.twentemilieu
|
||||||
twentemilieu==2.2.0
|
twentemilieu==2.2.1
|
||||||
|
|
||||||
# homeassistant.components.twilio
|
# homeassistant.components.twilio
|
||||||
twilio==6.32.0
|
twilio==6.32.0
|
||||||
@ -2332,7 +2332,7 @@ typedmonarchmoney==0.3.1
|
|||||||
uasiren==0.0.1
|
uasiren==0.0.1
|
||||||
|
|
||||||
# homeassistant.components.unifiprotect
|
# homeassistant.components.unifiprotect
|
||||||
uiprotect==7.1.0
|
uiprotect==7.4.1
|
||||||
|
|
||||||
# homeassistant.components.landisgyr_heat_meter
|
# homeassistant.components.landisgyr_heat_meter
|
||||||
ultraheat-api==0.5.7
|
ultraheat-api==0.5.7
|
||||||
@ -2489,7 +2489,7 @@ zeroconf==0.136.2
|
|||||||
zeversolar==0.3.2
|
zeversolar==0.3.2
|
||||||
|
|
||||||
# homeassistant.components.zha
|
# homeassistant.components.zha
|
||||||
zha==0.0.44
|
zha==0.0.45
|
||||||
|
|
||||||
# homeassistant.components.zwave_js
|
# homeassistant.components.zwave_js
|
||||||
zwave-js-server-python==0.60.0
|
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")
|
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")
|
@pytest.mark.usefixtures("mock_camera")
|
||||||
async def test_entity_picture_url_changes_on_token_update(hass: HomeAssistant) -> None:
|
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."""
|
"""Test the token is rotated and entity entity picture cache is cleared."""
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import cover
|
from homeassistant.components import cover
|
||||||
from homeassistant.components.cover import CoverState
|
from homeassistant.components.cover import CoverState
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM, SERVICE_TOGGLE
|
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:
|
def test_all() -> None:
|
||||||
"""Test module.__all__ is correctly set."""
|
"""Test module.__all__ is correctly set."""
|
||||||
help_test_all(cover)
|
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 aioesphomeapi import APIClient, APIVersion, BluetoothProxyFeature, DeviceInfo
|
||||||
from bleak.exc import BleakError
|
from bleak.exc import BleakError
|
||||||
from bleak_esphome.backend.cache import ESPHomeBluetoothCache
|
|
||||||
from bleak_esphome.backend.client import ESPHomeClient, ESPHomeClientData
|
from bleak_esphome.backend.client import ESPHomeClient, ESPHomeClientData
|
||||||
from bleak_esphome.backend.device import ESPHomeBluetoothDevice
|
from bleak_esphome.backend.device import ESPHomeBluetoothDevice
|
||||||
from bleak_esphome.backend.scanner import ESPHomeScanner
|
from bleak_esphome.backend.scanner import ESPHomeScanner
|
||||||
@ -27,7 +26,6 @@ async def client_data_fixture(
|
|||||||
connector = HaBluetoothConnector(ESPHomeClientData, ESP_MAC_ADDRESS, lambda: True)
|
connector = HaBluetoothConnector(ESPHomeClientData, ESP_MAC_ADDRESS, lambda: True)
|
||||||
return ESPHomeClientData(
|
return ESPHomeClientData(
|
||||||
bluetooth_device=ESPHomeBluetoothDevice(ESP_NAME, ESP_MAC_ADDRESS),
|
bluetooth_device=ESPHomeBluetoothDevice(ESP_NAME, ESP_MAC_ADDRESS),
|
||||||
cache=ESPHomeBluetoothCache(),
|
|
||||||
client=mock_client,
|
client=mock_client,
|
||||||
device_info=DeviceInfo(
|
device_info=DeviceInfo(
|
||||||
mac_address=ESP_MAC_ADDRESS,
|
mac_address=ESP_MAC_ADDRESS,
|
||||||
|
@ -10,11 +10,16 @@ from homeassistant.components.home_connect.const import (
|
|||||||
BSH_ACTIVE_PROGRAM,
|
BSH_ACTIVE_PROGRAM,
|
||||||
BSH_SELECTED_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.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_SELECT_OPTION, Platform
|
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_SELECT_OPTION, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
from .conftest import get_all_appliances
|
from .conftest import get_all_appliances
|
||||||
|
|
||||||
@ -52,6 +57,40 @@ async def test_select(
|
|||||||
assert config_entry.state is ConfigEntryState.LOADED
|
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(
|
@pytest.mark.parametrize(
|
||||||
("entity_id", "status", "program_to_set"),
|
("entity_id", "status", "program_to_set"),
|
||||||
[
|
[
|
||||||
|
@ -620,10 +620,10 @@
|
|||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'max': 12,
|
'max': 120,
|
||||||
'min': 0,
|
'min': 0,
|
||||||
'mode': <NumberMode.BOX: 'box'>,
|
'mode': <NumberMode.BOX: 'box'>,
|
||||||
'step': 0.1,
|
'step': 5,
|
||||||
}),
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@ -656,10 +656,10 @@
|
|||||||
StateSnapshot({
|
StateSnapshot({
|
||||||
'attributes': ReadOnlyDict({
|
'attributes': ReadOnlyDict({
|
||||||
'friendly_name': 'Pinecil Power limit',
|
'friendly_name': 'Pinecil Power limit',
|
||||||
'max': 12,
|
'max': 120,
|
||||||
'min': 0,
|
'min': 0,
|
||||||
'mode': <NumberMode.BOX: 'box'>,
|
'mode': <NumberMode.BOX: 'box'>,
|
||||||
'step': 0.1,
|
'step': 5,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
|
@ -126,7 +126,7 @@ async def test_state(
|
|||||||
2.0,
|
2.0,
|
||||||
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_quick_charge_voltage", CharSetting.QC_IDEAL_VOLTAGE, 9.0, 9.0),
|
||||||
(
|
(
|
||||||
"number.pinecil_short_press_temperature_step",
|
"number.pinecil_short_press_temperature_step",
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
# name: test_diagnostics
|
# name: test_diagnostics
|
||||||
dict({
|
dict({
|
||||||
'audio': dict({
|
'audio': dict({
|
||||||
|
'available': True,
|
||||||
'volume': 100,
|
'volume': 100,
|
||||||
'volume_limit': dict({
|
'volume_limit': dict({
|
||||||
'range_max': 100,
|
'range_max': 100,
|
||||||
|
@ -26,7 +26,6 @@ from homeassistant.components.light import (
|
|||||||
DOMAIN,
|
DOMAIN,
|
||||||
ColorMode,
|
ColorMode,
|
||||||
LightEntity,
|
LightEntity,
|
||||||
LightEntityFeature,
|
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
@ -157,7 +156,7 @@ class MockLight(MockToggleEntity, LightEntity):
|
|||||||
|
|
||||||
_attr_max_color_temp_kelvin = DEFAULT_MAX_KELVIN
|
_attr_max_color_temp_kelvin = DEFAULT_MAX_KELVIN
|
||||||
_attr_min_color_temp_kelvin = DEFAULT_MIN_KELVIN
|
_attr_min_color_temp_kelvin = DEFAULT_MIN_KELVIN
|
||||||
supported_features = LightEntityFeature(0)
|
supported_features = 0
|
||||||
|
|
||||||
brightness = None
|
brightness = None
|
||||||
color_temp_kelvin = None
|
color_temp_kelvin = None
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
"""The tests for the Light component."""
|
"""The tests for the Light component."""
|
||||||
|
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
|
from typing import Literal
|
||||||
from unittest.mock import MagicMock, mock_open, patch
|
from unittest.mock import MagicMock, mock_open, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -137,8 +138,13 @@ async def test_services(
|
|||||||
ent3.supported_color_modes = [light.ColorMode.HS]
|
ent3.supported_color_modes = [light.ColorMode.HS]
|
||||||
ent1.supported_features = light.LightEntityFeature.TRANSITION
|
ent1.supported_features = light.LightEntityFeature.TRANSITION
|
||||||
ent2.supported_features = (
|
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 = (
|
ent3.supported_features = (
|
||||||
light.LightEntityFeature.FLASH | light.LightEntityFeature.TRANSITION
|
light.LightEntityFeature.FLASH | light.LightEntityFeature.TRANSITION
|
||||||
)
|
)
|
||||||
@ -254,7 +260,10 @@ async def test_services(
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, data = ent2.last_call("turn_on")
|
_, 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")
|
_, data = ent3.last_call("turn_on")
|
||||||
assert data == {light.ATTR_FLASH: "short", light.ATTR_HS_COLOR: (71.059, 100)}
|
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")
|
_, data = ent2.last_call("turn_on")
|
||||||
assert data == {
|
assert data == {
|
||||||
|
light.ATTR_BRIGHTNESS: 100,
|
||||||
|
light.ATTR_HS_COLOR: profile.hs_color,
|
||||||
light.ATTR_TRANSITION: 1,
|
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)
|
setup_test_component_platform(hass, light.DOMAIN, entities)
|
||||||
|
|
||||||
entity0 = entities[0]
|
entity0 = entities[0]
|
||||||
entity0.supported_color_modes = {light.ColorMode.BRIGHTNESS}
|
entity0.supported_features = light.SUPPORT_BRIGHTNESS
|
||||||
entity0.color_mode = light.ColorMode.BRIGHTNESS
|
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||||
|
entity0.supported_color_modes = None
|
||||||
|
entity0.color_mode = None
|
||||||
entity0.brightness = 100
|
entity0.brightness = 100
|
||||||
entity1 = entities[1]
|
entity1 = entities[1]
|
||||||
entity1.supported_color_modes = {light.ColorMode.BRIGHTNESS}
|
entity1.supported_features = light.SUPPORT_BRIGHTNESS
|
||||||
entity1.color_mode = light.ColorMode.BRIGHTNESS
|
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||||
|
entity1.supported_color_modes = None
|
||||||
|
entity1.color_mode = None
|
||||||
entity1.brightness = 50
|
entity1.brightness = 50
|
||||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||||
await hass.async_block_till_done()
|
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)
|
setup_test_component_platform(hass, light.DOMAIN, mock_light_entities)
|
||||||
|
|
||||||
entity = mock_light_entities[0]
|
entity = mock_light_entities[0]
|
||||||
entity.supported_color_modes = {light.ColorMode.BRIGHTNESS}
|
entity.supported_features = light.SUPPORT_BRIGHTNESS
|
||||||
entity.color_mode = light.ColorMode.BRIGHTNESS
|
# Set color modes to none to trigger backwards compatibility in LightEntity
|
||||||
|
entity.supported_color_modes = None
|
||||||
|
entity.color_mode = None
|
||||||
entity.brightness = 100
|
entity.brightness = 100
|
||||||
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||||
await hass.async_block_till_done()
|
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
|
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:
|
async def test_light_service_call_rgbw(hass: HomeAssistant) -> None:
|
||||||
"""Test rgbw functionality in service calls."""
|
"""Test rgbw functionality in service calls."""
|
||||||
entity0 = MockLight("Test_rgbw", STATE_ON)
|
entity0 = MockLight("Test_rgbw", STATE_ON)
|
||||||
@ -1186,7 +1364,7 @@ async def test_light_state_off(hass: HomeAssistant) -> None:
|
|||||||
"color_mode": None,
|
"color_mode": None,
|
||||||
"friendly_name": "Test_onoff",
|
"friendly_name": "Test_onoff",
|
||||||
"supported_color_modes": [light.ColorMode.ONOFF],
|
"supported_color_modes": [light.ColorMode.ONOFF],
|
||||||
"supported_features": light.LightEntityFeature(0),
|
"supported_features": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
state = hass.states.get(entity1.entity_id)
|
state = hass.states.get(entity1.entity_id)
|
||||||
@ -1194,7 +1372,7 @@ async def test_light_state_off(hass: HomeAssistant) -> None:
|
|||||||
"color_mode": None,
|
"color_mode": None,
|
||||||
"friendly_name": "Test_brightness",
|
"friendly_name": "Test_brightness",
|
||||||
"supported_color_modes": [light.ColorMode.BRIGHTNESS],
|
"supported_color_modes": [light.ColorMode.BRIGHTNESS],
|
||||||
"supported_features": light.LightEntityFeature(0),
|
"supported_features": 0,
|
||||||
"brightness": None,
|
"brightness": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1203,7 +1381,7 @@ async def test_light_state_off(hass: HomeAssistant) -> None:
|
|||||||
"color_mode": None,
|
"color_mode": None,
|
||||||
"friendly_name": "Test_ct",
|
"friendly_name": "Test_ct",
|
||||||
"supported_color_modes": [light.ColorMode.COLOR_TEMP],
|
"supported_color_modes": [light.ColorMode.COLOR_TEMP],
|
||||||
"supported_features": light.LightEntityFeature(0),
|
"supported_features": 0,
|
||||||
"brightness": None,
|
"brightness": None,
|
||||||
"color_temp": None,
|
"color_temp": None,
|
||||||
"color_temp_kelvin": None,
|
"color_temp_kelvin": None,
|
||||||
@ -1221,7 +1399,7 @@ async def test_light_state_off(hass: HomeAssistant) -> None:
|
|||||||
"color_mode": None,
|
"color_mode": None,
|
||||||
"friendly_name": "Test_rgbw",
|
"friendly_name": "Test_rgbw",
|
||||||
"supported_color_modes": [light.ColorMode.RGBW],
|
"supported_color_modes": [light.ColorMode.RGBW],
|
||||||
"supported_features": light.LightEntityFeature(0),
|
"supported_features": 0,
|
||||||
"brightness": None,
|
"brightness": None,
|
||||||
"rgbw_color": None,
|
"rgbw_color": None,
|
||||||
"hs_color": None,
|
"hs_color": None,
|
||||||
@ -1252,7 +1430,7 @@ async def test_light_state_rgbw(hass: HomeAssistant) -> None:
|
|||||||
"color_mode": light.ColorMode.RGBW,
|
"color_mode": light.ColorMode.RGBW,
|
||||||
"friendly_name": "Test_rgbw",
|
"friendly_name": "Test_rgbw",
|
||||||
"supported_color_modes": [light.ColorMode.RGBW],
|
"supported_color_modes": [light.ColorMode.RGBW],
|
||||||
"supported_features": light.LightEntityFeature(0),
|
"supported_features": 0,
|
||||||
"hs_color": (240.0, 25.0),
|
"hs_color": (240.0, 25.0),
|
||||||
"rgb_color": (3, 3, 4),
|
"rgb_color": (3, 3, 4),
|
||||||
"rgbw_color": (1, 2, 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,
|
"color_mode": light.ColorMode.RGBWW,
|
||||||
"friendly_name": "Test_rgbww",
|
"friendly_name": "Test_rgbww",
|
||||||
"supported_color_modes": [light.ColorMode.RGBWW],
|
"supported_color_modes": [light.ColorMode.RGBWW],
|
||||||
"supported_features": light.LightEntityFeature(0),
|
"supported_features": 0,
|
||||||
"hs_color": (60.0, 20.0),
|
"hs_color": (60.0, 20.0),
|
||||||
"rgb_color": (5, 5, 4),
|
"rgb_color": (5, 5, 4),
|
||||||
"rgbww_color": (1, 2, 3, 4, 5),
|
"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_rgb", STATE_ON),
|
||||||
MockLight("Test_xy", STATE_ON),
|
MockLight("Test_xy", STATE_ON),
|
||||||
MockLight("Test_all", STATE_ON),
|
MockLight("Test_all", STATE_ON),
|
||||||
|
MockLight("Test_legacy", STATE_ON),
|
||||||
MockLight("Test_rgbw", STATE_ON),
|
MockLight("Test_rgbw", STATE_ON),
|
||||||
MockLight("Test_rgbww", STATE_ON),
|
MockLight("Test_rgbww", STATE_ON),
|
||||||
MockLight("Test_temperature", 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 = 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 = entities[5]
|
||||||
entity5.supported_color_modes = {light.ColorMode.RGBWW}
|
entity5.supported_color_modes = {light.ColorMode.RGBW}
|
||||||
|
|
||||||
entity6 = entities[6]
|
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"}})
|
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||||
await hass.async_block_till_done()
|
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)
|
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)
|
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)
|
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]
|
assert state.attributes["supported_color_modes"] == [light.ColorMode.COLOR_TEMP]
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -1370,6 +1558,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
|||||||
entity4.entity_id,
|
entity4.entity_id,
|
||||||
entity5.entity_id,
|
entity5.entity_id,
|
||||||
entity6.entity_id,
|
entity6.entity_id,
|
||||||
|
entity7.entity_id,
|
||||||
],
|
],
|
||||||
"brightness_pct": 100,
|
"brightness_pct": 100,
|
||||||
"hs_color": (240, 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")
|
_, data = entity3.last_call("turn_on")
|
||||||
assert data == {"brightness": 255, "hs_color": (240.0, 100.0)}
|
assert data == {"brightness": 255, "hs_color": (240.0, 100.0)}
|
||||||
_, data = entity4.last_call("turn_on")
|
_, 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")
|
_, 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")
|
_, 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}
|
assert data == {"brightness": 255, "color_temp_kelvin": 1739, "color_temp": 575}
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -1403,6 +1594,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
|||||||
entity4.entity_id,
|
entity4.entity_id,
|
||||||
entity5.entity_id,
|
entity5.entity_id,
|
||||||
entity6.entity_id,
|
entity6.entity_id,
|
||||||
|
entity7.entity_id,
|
||||||
],
|
],
|
||||||
"brightness_pct": 100,
|
"brightness_pct": 100,
|
||||||
"hs_color": (240, 0),
|
"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")
|
_, data = entity3.last_call("turn_on")
|
||||||
assert data == {"brightness": 255, "hs_color": (240.0, 0.0)}
|
assert data == {"brightness": 255, "hs_color": (240.0, 0.0)}
|
||||||
_, data = entity4.last_call("turn_on")
|
_, 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")
|
_, 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
|
# The midpoint of the white channels is warm, compensated by adding green + blue
|
||||||
assert data == {"brightness": 255, "rgbww_color": (0, 76, 141, 255, 255)}
|
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}
|
assert data == {"brightness": 255, "color_temp_kelvin": 5962, "color_temp": 167}
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -1437,6 +1631,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
|||||||
entity4.entity_id,
|
entity4.entity_id,
|
||||||
entity5.entity_id,
|
entity5.entity_id,
|
||||||
entity6.entity_id,
|
entity6.entity_id,
|
||||||
|
entity7.entity_id,
|
||||||
],
|
],
|
||||||
"brightness_pct": 50,
|
"brightness_pct": 50,
|
||||||
"rgb_color": (128, 0, 0),
|
"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)}
|
assert data == {"brightness": 128, "xy_color": (0.701, 0.299)}
|
||||||
_, data = entity3.last_call("turn_on")
|
_, data = entity3.last_call("turn_on")
|
||||||
assert data == {"brightness": 128, "rgb_color": (128, 0, 0)}
|
assert data == {"brightness": 128, "rgb_color": (128, 0, 0)}
|
||||||
|
|
||||||
_, data = entity4.last_call("turn_on")
|
_, 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")
|
_, 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")
|
_, 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}
|
assert data == {"brightness": 128, "color_temp_kelvin": 6279, "color_temp": 159}
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -1471,6 +1667,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
|||||||
entity4.entity_id,
|
entity4.entity_id,
|
||||||
entity5.entity_id,
|
entity5.entity_id,
|
||||||
entity6.entity_id,
|
entity6.entity_id,
|
||||||
|
entity7.entity_id,
|
||||||
],
|
],
|
||||||
"brightness_pct": 50,
|
"brightness_pct": 50,
|
||||||
"rgb_color": (255, 255, 255),
|
"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")
|
_, data = entity3.last_call("turn_on")
|
||||||
assert data == {"brightness": 128, "rgb_color": (255, 255, 255)}
|
assert data == {"brightness": 128, "rgb_color": (255, 255, 255)}
|
||||||
_, data = entity4.last_call("turn_on")
|
_, 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")
|
_, 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
|
# The midpoint the white channels is warm, compensated by adding green + blue
|
||||||
assert data == {"brightness": 128, "rgbww_color": (0, 76, 141, 255, 255)}
|
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}
|
assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167}
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -1505,6 +1704,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
|||||||
entity4.entity_id,
|
entity4.entity_id,
|
||||||
entity5.entity_id,
|
entity5.entity_id,
|
||||||
entity6.entity_id,
|
entity6.entity_id,
|
||||||
|
entity7.entity_id,
|
||||||
],
|
],
|
||||||
"brightness_pct": 50,
|
"brightness_pct": 50,
|
||||||
"xy_color": (0.1, 0.8),
|
"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")
|
_, data = entity3.last_call("turn_on")
|
||||||
assert data == {"brightness": 128, "xy_color": (0.1, 0.8)}
|
assert data == {"brightness": 128, "xy_color": (0.1, 0.8)}
|
||||||
_, data = entity4.last_call("turn_on")
|
_, 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")
|
_, 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")
|
_, 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}
|
assert data == {"brightness": 128, "color_temp_kelvin": 8645, "color_temp": 115}
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -1538,6 +1740,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
|||||||
entity4.entity_id,
|
entity4.entity_id,
|
||||||
entity5.entity_id,
|
entity5.entity_id,
|
||||||
entity6.entity_id,
|
entity6.entity_id,
|
||||||
|
entity7.entity_id,
|
||||||
],
|
],
|
||||||
"brightness_pct": 50,
|
"brightness_pct": 50,
|
||||||
"xy_color": (0.323, 0.329),
|
"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")
|
_, data = entity3.last_call("turn_on")
|
||||||
assert data == {"brightness": 128, "xy_color": (0.323, 0.329)}
|
assert data == {"brightness": 128, "xy_color": (0.323, 0.329)}
|
||||||
_, data = entity4.last_call("turn_on")
|
_, 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")
|
_, 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
|
# The midpoint the white channels is warm, compensated by adding green + blue
|
||||||
assert data == {"brightness": 128, "rgbww_color": (0, 75, 140, 255, 255)}
|
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}
|
assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167}
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -1572,6 +1777,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
|||||||
entity4.entity_id,
|
entity4.entity_id,
|
||||||
entity5.entity_id,
|
entity5.entity_id,
|
||||||
entity6.entity_id,
|
entity6.entity_id,
|
||||||
|
entity7.entity_id,
|
||||||
],
|
],
|
||||||
"brightness_pct": 50,
|
"brightness_pct": 50,
|
||||||
"rgbw_color": (128, 0, 0, 64),
|
"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")
|
_, data = entity3.last_call("turn_on")
|
||||||
assert data == {"brightness": 128, "rgb_color": (128, 43, 43)}
|
assert data == {"brightness": 128, "rgb_color": (128, 43, 43)}
|
||||||
_, data = entity4.last_call("turn_on")
|
_, 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")
|
_, 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
|
# The midpoint the white channels is warm, compensated by adding green + blue
|
||||||
assert data == {"brightness": 128, "rgbww_color": (128, 0, 30, 117, 117)}
|
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}
|
assert data == {"brightness": 128, "color_temp_kelvin": 3011, "color_temp": 332}
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -1606,6 +1814,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
|||||||
entity4.entity_id,
|
entity4.entity_id,
|
||||||
entity5.entity_id,
|
entity5.entity_id,
|
||||||
entity6.entity_id,
|
entity6.entity_id,
|
||||||
|
entity7.entity_id,
|
||||||
],
|
],
|
||||||
"brightness_pct": 50,
|
"brightness_pct": 50,
|
||||||
"rgbw_color": (255, 255, 255, 255),
|
"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")
|
_, data = entity3.last_call("turn_on")
|
||||||
assert data == {"brightness": 128, "rgb_color": (255, 255, 255)}
|
assert data == {"brightness": 128, "rgb_color": (255, 255, 255)}
|
||||||
_, data = entity4.last_call("turn_on")
|
_, 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")
|
_, 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
|
# The midpoint the white channels is warm, compensated by adding green + blue
|
||||||
assert data == {"brightness": 128, "rgbww_color": (0, 76, 141, 255, 255)}
|
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}
|
assert data == {"brightness": 128, "color_temp_kelvin": 5962, "color_temp": 167}
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -1640,6 +1851,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
|||||||
entity4.entity_id,
|
entity4.entity_id,
|
||||||
entity5.entity_id,
|
entity5.entity_id,
|
||||||
entity6.entity_id,
|
entity6.entity_id,
|
||||||
|
entity7.entity_id,
|
||||||
],
|
],
|
||||||
"brightness_pct": 50,
|
"brightness_pct": 50,
|
||||||
"rgbww_color": (128, 0, 0, 64, 32),
|
"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")
|
_, data = entity3.last_call("turn_on")
|
||||||
assert data == {"brightness": 128, "rgb_color": (128, 33, 26)}
|
assert data == {"brightness": 128, "rgb_color": (128, 33, 26)}
|
||||||
_, data = entity4.last_call("turn_on")
|
_, 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")
|
_, 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")
|
_, 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}
|
assert data == {"brightness": 128, "color_temp_kelvin": 3845, "color_temp": 260}
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -1673,6 +1887,7 @@ async def test_light_service_call_color_conversion(hass: HomeAssistant) -> None:
|
|||||||
entity4.entity_id,
|
entity4.entity_id,
|
||||||
entity5.entity_id,
|
entity5.entity_id,
|
||||||
entity6.entity_id,
|
entity6.entity_id,
|
||||||
|
entity7.entity_id,
|
||||||
],
|
],
|
||||||
"brightness_pct": 50,
|
"brightness_pct": 50,
|
||||||
"rgbww_color": (255, 255, 255, 255, 255),
|
"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")
|
_, data = entity3.last_call("turn_on")
|
||||||
assert data == {"brightness": 128, "rgb_color": (255, 217, 185)}
|
assert data == {"brightness": 128, "rgb_color": (255, 217, 185)}
|
||||||
_, data = entity4.last_call("turn_on")
|
_, 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
|
# The midpoint the white channels is warm, compensated by decreasing green + blue
|
||||||
assert data == {"brightness": 128, "rgbw_color": (96, 44, 0, 255)}
|
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")
|
_, 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}
|
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_rgb", STATE_ON),
|
||||||
MockLight("Test_xy", STATE_ON),
|
MockLight("Test_xy", STATE_ON),
|
||||||
MockLight("Test_all", STATE_ON),
|
MockLight("Test_all", STATE_ON),
|
||||||
|
MockLight("Test_legacy", STATE_ON),
|
||||||
MockLight("Test_rgbw", STATE_ON),
|
MockLight("Test_rgbw", STATE_ON),
|
||||||
MockLight("Test_rgbww", 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 = 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 = 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"}})
|
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -1746,6 +1970,7 @@ async def test_light_service_call_color_conversion_named_tuple(
|
|||||||
entity3.entity_id,
|
entity3.entity_id,
|
||||||
entity4.entity_id,
|
entity4.entity_id,
|
||||||
entity5.entity_id,
|
entity5.entity_id,
|
||||||
|
entity6.entity_id,
|
||||||
],
|
],
|
||||||
"brightness_pct": 25,
|
"brightness_pct": 25,
|
||||||
"rgb_color": color_util.RGBColor(128, 0, 0),
|
"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")
|
_, data = entity3.last_call("turn_on")
|
||||||
assert data == {"brightness": 64, "rgb_color": (128, 0, 0)}
|
assert data == {"brightness": 64, "rgb_color": (128, 0, 0)}
|
||||||
_, data = entity4.last_call("turn_on")
|
_, 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")
|
_, 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)}
|
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.rgb_color = "Invalid" # Should be ignored
|
||||||
entity2.xy_color = (0.1, 0.8)
|
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"}})
|
assert await async_setup_component(hass, "light", {"light": {"platform": "test"}})
|
||||||
await hass.async_block_till_done()
|
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["rgb_color"] == (0, 255, 22)
|
||||||
assert state.attributes["xy_color"] == (0.1, 0.8)
|
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(
|
async def test_services_filter_parameters(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -2386,6 +2626,27 @@ def test_filter_supported_color_modes() -> None:
|
|||||||
assert light.filter_supported_color_modes(supported) == {light.ColorMode.BRIGHTNESS}
|
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(
|
@pytest.mark.parametrize(
|
||||||
("color_mode", "supported_color_modes", "warning_expected"),
|
("color_mode", "supported_color_modes", "warning_expected"),
|
||||||
[
|
[
|
||||||
|
@ -129,7 +129,7 @@ def test_support_properties(property_suffix: str) -> None:
|
|||||||
entity3 = MediaPlayerEntity()
|
entity3 = MediaPlayerEntity()
|
||||||
entity3._attr_supported_features = feature
|
entity3._attr_supported_features = feature
|
||||||
entity4 = MediaPlayerEntity()
|
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(entity1, f"support_{property_suffix}") is False
|
||||||
assert getattr(entity2, f"support_{property_suffix}") is True
|
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)
|
url = player.get_browse_image_url("album", media_content_id)
|
||||||
await client.get(url)
|
await client.get(url)
|
||||||
mock_browse_image.assert_called_with("album", media_content_id, None)
|
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"
|
source = "import sys"
|
||||||
hass.async_add_executor_job(execute, hass, "test.py", source, {})
|
hass.async_add_executor_job(execute, hass, "test.py", source, {})
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
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_DAY2 = 15
|
||||||
TEST_HOUR = 13
|
TEST_HOUR = 13
|
||||||
TEST_MINUTE = 12
|
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_STREAM = "sub"
|
||||||
TEST_CHANNEL = "0"
|
TEST_CHANNEL = "0"
|
||||||
TEST_VOD_TYPE = VodRequestType.PLAYBACK.value
|
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_USER_DATA: USER_DATA.as_dict(),
|
||||||
CONF_BASE_URL: BASE_URL,
|
CONF_BASE_URL: BASE_URL,
|
||||||
},
|
},
|
||||||
|
unique_id=USER_EMAIL,
|
||||||
)
|
)
|
||||||
mock_entry.add_to_hass(hass)
|
mock_entry.add_to_hass(hass)
|
||||||
return mock_entry
|
return mock_entry
|
||||||
|
@ -244,3 +244,28 @@ async def test_reauth_flow(
|
|||||||
assert result["type"] is FlowResultType.ABORT
|
assert result["type"] is FlowResultType.ABORT
|
||||||
assert result["reason"] == "reauth_successful"
|
assert result["reason"] == "reauth_successful"
|
||||||
assert mock_roborock_entry.data["user_data"]["rriot"]["s"] == "new_password_hash"
|
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
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from roborock import RoborockException, RoborockInvalidCredentials
|
from roborock import (
|
||||||
|
RoborockException,
|
||||||
|
RoborockInvalidCredentials,
|
||||||
|
RoborockInvalidUserAgreement,
|
||||||
|
RoborockNoUserAgreement,
|
||||||
|
)
|
||||||
|
|
||||||
from homeassistant.components.roborock.const import DOMAIN
|
from homeassistant.components.roborock.const import DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
@ -194,3 +199,35 @@ async def test_not_supported_a01_device(
|
|||||||
await async_setup_component(hass, DOMAIN, {})
|
await async_setup_component(hass, DOMAIN, {})
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert "The device you added is not yet supported" in caplog.text
|
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",
|
"title": "Fake Item 1",
|
||||||
"id": FAKE_VALID_ITEM_ID,
|
"id": FAKE_VALID_ITEM_ID,
|
||||||
"hasitems": False,
|
"hasitems": False,
|
||||||
|
"isaudio": True,
|
||||||
"item_type": child_types[media_type],
|
"item_type": child_types[media_type],
|
||||||
"artwork_track_id": "b35bb9e9",
|
"artwork_track_id": "b35bb9e9",
|
||||||
"url": "file:///var/lib/squeezeboxserver/music/track_1.mp3",
|
"url": "file:///var/lib/squeezeboxserver/music/track_1.mp3",
|
||||||
@ -145,6 +146,7 @@ async def mock_async_browse(
|
|||||||
"title": "Fake Item 2",
|
"title": "Fake Item 2",
|
||||||
"id": FAKE_VALID_ITEM_ID + "_2",
|
"id": FAKE_VALID_ITEM_ID + "_2",
|
||||||
"hasitems": media_type == "favorites",
|
"hasitems": media_type == "favorites",
|
||||||
|
"isaudio": True,
|
||||||
"item_type": child_types[media_type],
|
"item_type": child_types[media_type],
|
||||||
"image_url": "http://lms.internal:9000/html/images/favorites.png",
|
"image_url": "http://lms.internal:9000/html/images/favorites.png",
|
||||||
"url": "file:///var/lib/squeezeboxserver/music/track_2.mp3",
|
"url": "file:///var/lib/squeezeboxserver/music/track_2.mp3",
|
||||||
@ -153,6 +155,7 @@ async def mock_async_browse(
|
|||||||
"title": "Fake Item 3",
|
"title": "Fake Item 3",
|
||||||
"id": FAKE_VALID_ITEM_ID + "_3",
|
"id": FAKE_VALID_ITEM_ID + "_3",
|
||||||
"hasitems": media_type == "favorites",
|
"hasitems": media_type == "favorites",
|
||||||
|
"isaudio": True,
|
||||||
"album_id": FAKE_VALID_ITEM_ID if media_type == "favorites" else None,
|
"album_id": FAKE_VALID_ITEM_ID if media_type == "favorites" else None,
|
||||||
"url": "file:///var/lib/squeezeboxserver/music/track_3.mp3",
|
"url": "file:///var/lib/squeezeboxserver/music/track_3.mp3",
|
||||||
},
|
},
|
||||||
|
@ -564,6 +564,24 @@
|
|||||||
"legacyUFVs": [],
|
"legacyUFVs": [],
|
||||||
"lastUpdateId": "ebf25bac-d5a1-4f1d-a0ee-74c15981eb70",
|
"lastUpdateId": "ebf25bac-d5a1-4f1d-a0ee-74c15981eb70",
|
||||||
"displays": [],
|
"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": [
|
"bridges": [
|
||||||
{
|
{
|
||||||
"mac": "A28D0DB15AE1",
|
"mac": "A28D0DB15AE1",
|
||||||
|
@ -272,6 +272,42 @@ async def test_send_command(hass: HomeAssistant, config_flow_fixture: None) -> N
|
|||||||
assert "test" in strings
|
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(
|
async def test_vacuum_not_log_deprecated_state_warning(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_vacuum_entity: MockVacuum,
|
mock_vacuum_entity: MockVacuum,
|
||||||
|
@ -4,6 +4,7 @@ import asyncio
|
|||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
import dataclasses
|
import dataclasses
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from enum import IntFlag
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@ -2485,6 +2486,31 @@ async def test_cached_entity_property_override(hass: HomeAssistant) -> None:
|
|||||||
return "🤡"
|
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(
|
async def test_remove_entity_registry(
|
||||||
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
hass: HomeAssistant, entity_registry: er.EntityRegistry
|
||||||
) -> None:
|
) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user