mirror of
https://github.com/home-assistant/core.git
synced 2026-05-04 11:54:35 +02:00
Compare commits
194 Commits
2024.1.0b1
...
2024.1.4
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e6a5ff52c | |||
| 43f1c0927f | |||
| 916e5de9d1 | |||
| e2a6097141 | |||
| 005af2eb4c | |||
| 59e12ad0c1 | |||
| 6ecb562a80 | |||
| da5d4fe4ae | |||
| 901b7b6278 | |||
| d2feee86b7 | |||
| 5521ab0b35 | |||
| 7fb2a8a3cd | |||
| 304b950f1a | |||
| 99f9f0205a | |||
| 497d2f5677 | |||
| 7fee6c5279 | |||
| 70492a80cc | |||
| ed31adc6db | |||
| 9c6f87dd11 | |||
| 507cccdd53 | |||
| 8a3eb149b7 | |||
| 20b88e30f5 | |||
| 9551ff31ec | |||
| 488acc3252 | |||
| 80387be061 | |||
| c0c9fb0f00 | |||
| e2ef889687 | |||
| 5c99c6e823 | |||
| a8be7c27ad | |||
| 596f19055e | |||
| 99ee57aefc | |||
| 9c6cb5347c | |||
| 51c75b020d | |||
| 061d2d3ccf | |||
| 97b596a00d | |||
| e3a44e499c | |||
| 48766c08e0 | |||
| f7ad7c4235 | |||
| 38f9fd5734 | |||
| 05964d6bad | |||
| 70d1e6a270 | |||
| 28da1ac69e | |||
| 504e4a7923 | |||
| 40547974fb | |||
| b87bbd1529 | |||
| d89659f196 | |||
| 00b899ca3c | |||
| 765c520d7a | |||
| 2a46f201cb | |||
| 7f8a157788 | |||
| f993e923a3 | |||
| 3386e0e766 | |||
| 0705be607f | |||
| 5b84e50dc0 | |||
| 26da7402a2 | |||
| f1fc5abbc2 | |||
| 644a823c45 | |||
| fcab683cc0 | |||
| 7bdabce68f | |||
| cf1a528b7a | |||
| f18ab5e1cc | |||
| bee76db1c3 | |||
| 652fa7d693 | |||
| 73ba77deb6 | |||
| 66307c5acb | |||
| 86822018d8 | |||
| b333be8945 | |||
| a58483f93c | |||
| 7c06f05108 | |||
| 175e07fe3b | |||
| 9bc0217738 | |||
| 53ab892575 | |||
| 409a254fe5 | |||
| 54e62b4095 | |||
| 4e991388fb | |||
| 1acae5a62d | |||
| 9fe351f363 | |||
| f9dacedf0f | |||
| 4eddbe7b47 | |||
| cab833160d | |||
| 003d2be477 | |||
| 5a01b55fd1 | |||
| 0dbb4105bc | |||
| 5ff6284e0f | |||
| 97674cee88 | |||
| 24a8a512d6 | |||
| 658f1cf5c5 | |||
| d012817190 | |||
| 056701d218 | |||
| c3963b26e7 | |||
| 4ade5e46d9 | |||
| c242dcd1f2 | |||
| 4e126d68b7 | |||
| d7e1a4fa20 | |||
| 3215dfee6d | |||
| c78d691d30 | |||
| 04bf569308 | |||
| b8576b8091 | |||
| a7aa5c0e52 | |||
| d600b76801 | |||
| c56d118e8b | |||
| 80b45edb2e | |||
| 5529a85a2b | |||
| e2acc70128 | |||
| 427077a4c9 | |||
| 8c9875c3cc | |||
| ce5455fefc | |||
| aef129afaf | |||
| 1c94a94ba2 | |||
| d87baba96f | |||
| 5a0997bac0 | |||
| e70af204ee | |||
| fb0cc6c5d0 | |||
| 15cecbd4a4 | |||
| 8cf47c4925 | |||
| cd8d95a04d | |||
| 015752ff11 | |||
| 9d697c5026 | |||
| 4595c3edaa | |||
| e745542431 | |||
| 2be72fd891 | |||
| 2b43f5fcda | |||
| 3295722e70 | |||
| f98bbf88b1 | |||
| 0226b3f10c | |||
| 5986967db7 | |||
| 95ef2dd7f9 | |||
| 527d9fbb6b | |||
| 5eb1073b4a | |||
| 77cdc10883 | |||
| 5100ba252f | |||
| b5b8bc3102 | |||
| 6f18a29241 | |||
| 596f855eab | |||
| 5877fe135c | |||
| 26cf30fc3a | |||
| 3419b8d082 | |||
| 35fc26457b | |||
| fc66dead64 | |||
| 056b06de13 | |||
| e604bc8c9b | |||
| 59bed57d48 | |||
| 8c25e2610e | |||
| 38b8a1f95d | |||
| 54a87cf047 | |||
| 448e98eac5 | |||
| 6ca3c7a673 | |||
| b1a55e9b19 | |||
| 16d3d88fa3 | |||
| 39960caf36 | |||
| e6d2721d1b | |||
| fedb63720c | |||
| 77286e8f59 | |||
| a7d11120fa | |||
| c06df1957f | |||
| 99d575261d | |||
| 3dca39d0f9 | |||
| a11fd2aaa6 | |||
| 05768f5fbd | |||
| 3d75603b4f | |||
| 2179d4de3d | |||
| 2255f6737c | |||
| 456cb20fcd | |||
| 8dfbe6849e | |||
| 3dd998b622 | |||
| 84da1638e8 | |||
| 362e5ca09a | |||
| 494dd2ef07 | |||
| 767c55fbac | |||
| 5f3389b8e4 | |||
| c1e37a4cc3 | |||
| 3cd5f0568a | |||
| f9150b78b3 | |||
| c54af00ce9 | |||
| bb6f3bc830 | |||
| f84d865c51 | |||
| 2147df4418 | |||
| af9f6a2b12 | |||
| 024d689b94 | |||
| 9f4790902a | |||
| 5d9177d6e6 | |||
| 70842f197e | |||
| f03bb4a2da | |||
| aa6e904e86 | |||
| 6224e630ac | |||
| 04fe8260ab | |||
| 4a98a6465e | |||
| 06f06b7595 | |||
| 16192cd7f2 | |||
| 982707afe6 | |||
| 911234ae8f | |||
| 55877b0953 | |||
| 0623972ee0 | |||
| d407b9fca8 |
@@ -49,6 +49,7 @@ homeassistant.components.aftership.*
|
||||
homeassistant.components.air_quality.*
|
||||
homeassistant.components.airly.*
|
||||
homeassistant.components.airnow.*
|
||||
homeassistant.components.airthings_ble.*
|
||||
homeassistant.components.airvisual.*
|
||||
homeassistant.components.airvisual_pro.*
|
||||
homeassistant.components.airzone.*
|
||||
|
||||
+1
-1
@@ -1550,7 +1550,7 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/zodiac/ @JulienTant
|
||||
/homeassistant/components/zone/ @home-assistant/core
|
||||
/tests/components/zone/ @home-assistant/core
|
||||
/homeassistant/components/zoneminder/ @rohankapoorcom
|
||||
/homeassistant/components/zoneminder/ @rohankapoorcom @nabbi
|
||||
/homeassistant/components/zwave_js/ @home-assistant/z-wave
|
||||
/tests/components/zwave_js/ @home-assistant/z-wave
|
||||
/homeassistant/components/zwave_me/ @lawfulchaos @Z-Wave-Me @PoltoS
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"domain": "aepohio",
|
||||
"domain": "aep_ohio",
|
||||
"name": "AEP Ohio",
|
||||
"integration_type": "virtual",
|
||||
"supported_by": "opower"
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"domain": "aeptexas",
|
||||
"domain": "aep_texas",
|
||||
"name": "AEP Texas",
|
||||
"integration_type": "virtual",
|
||||
"supported_by": "opower"
|
||||
@@ -4,7 +4,8 @@ from __future__ import annotations
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from airthings_ble import AirthingsBluetoothDeviceData
|
||||
from airthings_ble import AirthingsBluetoothDeviceData, AirthingsDevice
|
||||
from bleak_retry_connector import close_stale_connections_by_address
|
||||
|
||||
from homeassistant.components import bluetooth
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -30,6 +31,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
is_metric = hass.config.units is METRIC_SYSTEM
|
||||
assert address is not None
|
||||
|
||||
await close_stale_connections_by_address(address)
|
||||
|
||||
ble_device = bluetooth.async_ble_device_from_address(hass, address)
|
||||
|
||||
if not ble_device:
|
||||
@@ -37,13 +40,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
f"Could not find Airthings device with address {address}"
|
||||
)
|
||||
|
||||
async def _async_update_method():
|
||||
async def _async_update_method() -> AirthingsDevice:
|
||||
"""Get data from Airthings BLE."""
|
||||
ble_device = bluetooth.async_ble_device_from_address(hass, address)
|
||||
airthings = AirthingsBluetoothDeviceData(_LOGGER, elevation, is_metric)
|
||||
|
||||
try:
|
||||
data = await airthings.update_device(ble_device)
|
||||
data = await airthings.update_device(ble_device) # type: ignore[arg-type]
|
||||
except Exception as err:
|
||||
raise UpdateFailed(f"Unable to fetch data: {err}") from err
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.config_validation import make_entity_service_schema
|
||||
from homeassistant.helpers.deprecation import (
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -52,12 +53,6 @@ if TYPE_CHECKING:
|
||||
else:
|
||||
from homeassistant.backports.functools import cached_property
|
||||
|
||||
# As we import constants of the cost module here, we need to add the following
|
||||
# functions to check for deprecated constants again
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
|
||||
_LOGGER: Final = logging.getLogger(__name__)
|
||||
|
||||
SCAN_INTERVAL: Final = timedelta(seconds=30)
|
||||
@@ -233,7 +228,12 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
|
||||
@cached_property
|
||||
def supported_features(self) -> AlarmControlPanelEntityFeature:
|
||||
"""Return the list of supported features."""
|
||||
return self._attr_supported_features
|
||||
features = self._attr_supported_features
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = AlarmControlPanelEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
|
||||
@final
|
||||
@property
|
||||
@@ -244,3 +244,13 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
|
||||
ATTR_CHANGED_BY: self.changed_by,
|
||||
ATTR_CODE_ARM_REQUIRED: self.code_arm_required,
|
||||
}
|
||||
|
||||
|
||||
# As we import constants of the const module here, we need to add the following
|
||||
# functions to check for deprecated constants again
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Final
|
||||
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstantEnum,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -60,10 +61,6 @@ _DEPRECATED_SUPPORT_ALARM_ARM_VACATION: Final = DeprecatedConstantEnum(
|
||||
AlarmControlPanelEntityFeature.ARM_VACATION, "2025.1"
|
||||
)
|
||||
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
|
||||
CONDITION_TRIGGERED: Final = "is_triggered"
|
||||
CONDITION_DISARMED: Final = "is_disarmed"
|
||||
CONDITION_ARMED_HOME: Final = "is_armed_home"
|
||||
@@ -71,3 +68,10 @@ CONDITION_ARMED_AWAY: Final = "is_armed_away"
|
||||
CONDITION_ARMED_NIGHT: Final = "is_armed_night"
|
||||
CONDITION_ARMED_VACATION: Final = "is_armed_vacation"
|
||||
CONDITION_ARMED_CUSTOM_BYPASS: Final = "is_armed_custom_bypass"
|
||||
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -15,6 +15,9 @@ from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.helpers.storage import Store
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import STORAGE_ACCESS_TOKEN, STORAGE_REFRESH_TOKEN
|
||||
from .diagnostics import async_redact_lwa_params
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
LWA_TOKEN_URI = "https://api.amazon.com/auth/o2/token"
|
||||
@@ -24,8 +27,6 @@ PREEMPTIVE_REFRESH_TTL_IN_SECONDS = 300
|
||||
STORAGE_KEY = "alexa_auth"
|
||||
STORAGE_VERSION = 1
|
||||
STORAGE_EXPIRE_TIME = "expire_time"
|
||||
STORAGE_ACCESS_TOKEN = "access_token"
|
||||
STORAGE_REFRESH_TOKEN = "refresh_token"
|
||||
|
||||
|
||||
class Auth:
|
||||
@@ -56,7 +57,7 @@ class Auth:
|
||||
}
|
||||
_LOGGER.debug(
|
||||
"Calling LWA to get the access token (first time), with: %s",
|
||||
json.dumps(lwa_params),
|
||||
json.dumps(async_redact_lwa_params(lwa_params)),
|
||||
)
|
||||
|
||||
return await self._async_request_new_token(lwa_params)
|
||||
@@ -133,7 +134,7 @@ class Auth:
|
||||
return None
|
||||
|
||||
response_json = await response.json()
|
||||
_LOGGER.debug("LWA response body : %s", response_json)
|
||||
_LOGGER.debug("LWA response body : %s", async_redact_lwa_params(response_json))
|
||||
|
||||
access_token: str = response_json["access_token"]
|
||||
refresh_token: str = response_json["refresh_token"]
|
||||
|
||||
@@ -1112,13 +1112,17 @@ class AlexaThermostatController(AlexaCapability):
|
||||
"""Return what properties this entity supports."""
|
||||
properties = [{"name": "thermostatMode"}]
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & climate.ClimateEntityFeature.TARGET_TEMPERATURE:
|
||||
if self.entity.domain == climate.DOMAIN:
|
||||
if supported & climate.ClimateEntityFeature.TARGET_TEMPERATURE_RANGE:
|
||||
properties.append({"name": "lowerSetpoint"})
|
||||
properties.append({"name": "upperSetpoint"})
|
||||
if supported & climate.ClimateEntityFeature.TARGET_TEMPERATURE:
|
||||
properties.append({"name": "targetSetpoint"})
|
||||
elif (
|
||||
self.entity.domain == water_heater.DOMAIN
|
||||
and supported & water_heater.WaterHeaterEntityFeature.TARGET_TEMPERATURE
|
||||
):
|
||||
properties.append({"name": "targetSetpoint"})
|
||||
if supported & water_heater.WaterHeaterEntityFeature.TARGET_TEMPERATURE:
|
||||
properties.append({"name": "targetSetpoint"})
|
||||
if supported & climate.ClimateEntityFeature.TARGET_TEMPERATURE_RANGE:
|
||||
properties.append({"name": "lowerSetpoint"})
|
||||
properties.append({"name": "upperSetpoint"})
|
||||
return properties
|
||||
|
||||
def properties_proactively_reported(self) -> bool:
|
||||
|
||||
@@ -90,6 +90,9 @@ API_THERMOSTAT_PRESETS = {climate.PRESET_ECO: "ECO"}
|
||||
# we add PRESET_MODE_NA if a fan / humidifier has only one preset_mode
|
||||
PRESET_MODE_NA = "-"
|
||||
|
||||
STORAGE_ACCESS_TOKEN = "access_token"
|
||||
STORAGE_REFRESH_TOKEN = "refresh_token"
|
||||
|
||||
|
||||
class Cause:
|
||||
"""Possible causes for property changes.
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Diagnostics helpers for Alexa."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
|
||||
from homeassistant.core import callback
|
||||
|
||||
STORAGE_ACCESS_TOKEN = "access_token"
|
||||
STORAGE_REFRESH_TOKEN = "refresh_token"
|
||||
|
||||
TO_REDACT_LWA = {
|
||||
CONF_CLIENT_ID,
|
||||
CONF_CLIENT_SECRET,
|
||||
STORAGE_ACCESS_TOKEN,
|
||||
STORAGE_REFRESH_TOKEN,
|
||||
}
|
||||
|
||||
TO_REDACT_AUTH = {"correlationToken", "token"}
|
||||
|
||||
|
||||
@callback
|
||||
def async_redact_lwa_params(lwa_params: dict[str, str]) -> dict[str, str]:
|
||||
"""Redact lwa_params."""
|
||||
return async_redact_data(lwa_params, TO_REDACT_LWA)
|
||||
|
||||
|
||||
@callback
|
||||
def async_redact_auth_data(mapping: Mapping[Any, Any]) -> dict[str, str]:
|
||||
"""React auth data."""
|
||||
return async_redact_data(mapping, TO_REDACT_AUTH)
|
||||
@@ -144,7 +144,6 @@ async def async_api_accept_grant(
|
||||
Async friendly.
|
||||
"""
|
||||
auth_code: str = directive.payload["grant"]["code"]
|
||||
_LOGGER.debug("AcceptGrant code: %s", auth_code)
|
||||
|
||||
if config.supports_auth:
|
||||
await config.async_accept_grant(auth_code)
|
||||
|
||||
@@ -25,6 +25,7 @@ from .const import (
|
||||
CONF_LOCALE,
|
||||
EVENT_ALEXA_SMART_HOME,
|
||||
)
|
||||
from .diagnostics import async_redact_auth_data
|
||||
from .errors import AlexaBridgeUnreachableError, AlexaError
|
||||
from .handlers import HANDLERS
|
||||
from .state_report import AlexaDirective
|
||||
@@ -149,12 +150,21 @@ class SmartHomeView(HomeAssistantView):
|
||||
user: User = request["hass_user"]
|
||||
message: dict[str, Any] = await request.json()
|
||||
|
||||
_LOGGER.debug("Received Alexa Smart Home request: %s", message)
|
||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
||||
_LOGGER.debug(
|
||||
"Received Alexa Smart Home request: %s",
|
||||
async_redact_auth_data(message),
|
||||
)
|
||||
|
||||
response = await async_handle_message(
|
||||
hass, self.smart_home_config, message, context=core.Context(user_id=user.id)
|
||||
)
|
||||
_LOGGER.debug("Sending Alexa Smart Home response: %s", response)
|
||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
||||
_LOGGER.debug(
|
||||
"Sending Alexa Smart Home response: %s",
|
||||
async_redact_auth_data(response),
|
||||
)
|
||||
|
||||
return b"" if response is None else self.json(response)
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ from .const import (
|
||||
DOMAIN,
|
||||
Cause,
|
||||
)
|
||||
from .diagnostics import async_redact_auth_data
|
||||
from .entities import ENTITY_ADAPTERS, AlexaEntity, generate_alexa_id
|
||||
from .errors import AlexaInvalidEndpointError, NoTokenAvailable, RequireRelink
|
||||
|
||||
@@ -43,6 +44,8 @@ if TYPE_CHECKING:
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
DEFAULT_TIMEOUT = 10
|
||||
|
||||
TO_REDACT = {"correlationToken", "token"}
|
||||
|
||||
|
||||
class AlexaDirective:
|
||||
"""An incoming Alexa directive."""
|
||||
@@ -379,7 +382,9 @@ async def async_send_changereport_message(
|
||||
response_text = await response.text()
|
||||
|
||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
||||
_LOGGER.debug("Sent: %s", json.dumps(message_serialized))
|
||||
_LOGGER.debug(
|
||||
"Sent: %s", json.dumps(async_redact_auth_data(message_serialized))
|
||||
)
|
||||
_LOGGER.debug("Received (%s): %s", response.status, response_text)
|
||||
|
||||
if response.status == HTTPStatus.ACCEPTED:
|
||||
@@ -533,7 +538,9 @@ async def async_send_doorbell_event_message(
|
||||
response_text = await response.text()
|
||||
|
||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
||||
_LOGGER.debug("Sent: %s", json.dumps(message_serialized))
|
||||
_LOGGER.debug(
|
||||
"Sent: %s", json.dumps(async_redact_auth_data(message_serialized))
|
||||
)
|
||||
_LOGGER.debug("Received (%s): %s", response.status, response_text)
|
||||
|
||||
if response.status == HTTPStatus.ACCEPTED:
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
"""Diagnostics support for A. O. Smith."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import AOSmithData
|
||||
from .const import DOMAIN
|
||||
|
||||
TO_REDACT = {
|
||||
"address",
|
||||
"city",
|
||||
"contactId",
|
||||
"dsn",
|
||||
"email",
|
||||
"firstName",
|
||||
"heaterSsid",
|
||||
"id",
|
||||
"lastName",
|
||||
"phone",
|
||||
"postalCode",
|
||||
"registeredOwner",
|
||||
"serial",
|
||||
"ssid",
|
||||
"state",
|
||||
}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
data: AOSmithData = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
all_device_info = await data.client.get_all_device_info()
|
||||
return async_redact_data(all_device_info, TO_REDACT)
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/aosmith",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["py-aosmith==1.0.1"]
|
||||
"requirements": ["py-aosmith==1.0.4"]
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/august",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pubnub", "yalexs"],
|
||||
"requirements": ["yalexs==1.10.0", "yalexs-ble==2.3.2"]
|
||||
"requirements": ["yalexs==1.10.0", "yalexs-ble==2.4.0"]
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ from homeassistant.helpers import condition
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstant,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -147,10 +148,6 @@ _DEPRECATED_AutomationTriggerInfo = DeprecatedConstant(
|
||||
TriggerInfo, "TriggerInfo", "2025.1"
|
||||
)
|
||||
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
|
||||
|
||||
@bind_hass
|
||||
def is_on(hass: HomeAssistant, entity_id: str) -> bool:
|
||||
@@ -1108,3 +1105,11 @@ def websocket_config(
|
||||
"config": automation.raw_config,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -19,6 +19,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||
)
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstantEnum,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -218,10 +219,6 @@ _DEPRECATED_DEVICE_CLASS_WINDOW = DeprecatedConstantEnum(
|
||||
BinarySensorDeviceClass.WINDOW, "2025.1"
|
||||
)
|
||||
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
|
||||
@@ -303,3 +300,11 @@ class BinarySensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_)
|
||||
if (is_on := self.is_on) is None:
|
||||
return None
|
||||
return STATE_ON if is_on else STATE_OFF
|
||||
|
||||
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -13,7 +13,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
SCAN_INTERVAL = 30
|
||||
SCAN_INTERVAL = 300
|
||||
|
||||
|
||||
class BlinkUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/blink",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["blinkpy"],
|
||||
"requirements": ["blinkpy==0.22.4"]
|
||||
"requirements": ["blinkpy==0.22.5"]
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
},
|
||||
"exceptions": {
|
||||
"integration_not_found": {
|
||||
"message": "Integraion '{target}' not found in registry"
|
||||
"message": "Integration '{target}' not found in registry"
|
||||
},
|
||||
"no_path": {
|
||||
"message": "Can't write to directory {target}, no access to path!"
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
"quality_scale": "internal",
|
||||
"requirements": [
|
||||
"bleak==0.21.1",
|
||||
"bleak-retry-connector==3.3.0",
|
||||
"bleak-retry-connector==3.4.0",
|
||||
"bluetooth-adapters==0.16.2",
|
||||
"bluetooth-auto-recovery==1.2.3",
|
||||
"bluetooth-auto-recovery==1.3.0",
|
||||
"bluetooth-data-tools==1.19.0",
|
||||
"dbus-fast==2.21.0",
|
||||
"habluetooth==2.0.0"
|
||||
"habluetooth==2.1.0"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||
)
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstantEnum,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -123,10 +124,6 @@ _DEPRECATED_SUPPORT_STREAM: Final = DeprecatedConstantEnum(
|
||||
CameraEntityFeature.STREAM, "2025.1"
|
||||
)
|
||||
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
|
||||
RTSP_PREFIXES = {"rtsp://", "rtsps://", "rtmp://"}
|
||||
|
||||
DEFAULT_CONTENT_TYPE: Final = "image/jpeg"
|
||||
@@ -530,6 +527,19 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Flag supported features."""
|
||||
return self._attr_supported_features
|
||||
|
||||
@property
|
||||
def supported_features_compat(self) -> CameraEntityFeature:
|
||||
"""Return the supported features as CameraEntityFeature.
|
||||
|
||||
Remove this compatibility shim in 2025.1 or later.
|
||||
"""
|
||||
features = self.supported_features
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = CameraEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
|
||||
@cached_property
|
||||
def is_recording(self) -> bool:
|
||||
"""Return true if the device is recording."""
|
||||
@@ -570,7 +580,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""
|
||||
if hasattr(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
|
||||
if self._rtsp_to_webrtc:
|
||||
return StreamType.WEB_RTC
|
||||
@@ -758,7 +768,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
|
||||
async def _async_use_rtsp_to_webrtc(self) -> bool:
|
||||
"""Determine if a WebRTC provider can be used for the camera."""
|
||||
if CameraEntityFeature.STREAM not in self.supported_features:
|
||||
if CameraEntityFeature.STREAM not in self.supported_features_compat:
|
||||
return False
|
||||
if DATA_RTSP_TO_WEB_RTC not in self.hass.data:
|
||||
return False
|
||||
@@ -1069,3 +1079,11 @@ async def async_handle_record_service(
|
||||
duration=service_call.data[CONF_DURATION],
|
||||
lookback=service_call.data[CONF_LOOKBACK],
|
||||
)
|
||||
|
||||
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Final
|
||||
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstantEnum,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -47,6 +48,9 @@ _DEPRECATED_STREAM_TYPE_HLS = DeprecatedConstantEnum(StreamType.HLS, "2025.1")
|
||||
_DEPRECATED_STREAM_TYPE_WEB_RTC = DeprecatedConstantEnum(StreamType.WEB_RTC, "2025.1")
|
||||
|
||||
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
__dir__ = partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -28,6 +28,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||
make_entity_service_schema,
|
||||
)
|
||||
from homeassistant.helpers.deprecation import (
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -141,12 +142,6 @@ SET_TEMPERATURE_SCHEMA = vol.All(
|
||||
),
|
||||
)
|
||||
|
||||
# As we import deprecated constants from the const module, we need to add these two functions
|
||||
# otherwise this module will be logged for using deprecated constants and not the custom component
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = ft.partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = ft.partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
|
||||
@@ -227,6 +222,7 @@ CACHED_PROPERTIES_WITH_ATTR_ = {
|
||||
"temperature_unit",
|
||||
"current_humidity",
|
||||
"target_humidity",
|
||||
"hvac_mode",
|
||||
"hvac_modes",
|
||||
"hvac_action",
|
||||
"current_temperature",
|
||||
@@ -316,7 +312,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
@property
|
||||
def capability_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return the capability attributes."""
|
||||
supported_features = self.supported_features
|
||||
supported_features = self.supported_features_compat
|
||||
temperature_unit = self.temperature_unit
|
||||
precision = self.precision
|
||||
hass = self.hass
|
||||
@@ -349,7 +345,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
@property
|
||||
def state_attributes(self) -> dict[str, Any]:
|
||||
"""Return the optional state attributes."""
|
||||
supported_features = self.supported_features
|
||||
supported_features = self.supported_features_compat
|
||||
temperature_unit = self.temperature_unit
|
||||
precision = self.precision
|
||||
hass = self.hass
|
||||
@@ -414,7 +410,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Return the humidity we try to reach."""
|
||||
return self._attr_target_humidity
|
||||
|
||||
@property
|
||||
@cached_property
|
||||
def hvac_mode(self) -> HVACMode | None:
|
||||
"""Return hvac operation ie. heat, cool mode."""
|
||||
return self._attr_hvac_mode
|
||||
@@ -665,6 +661,19 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Return the list of supported features."""
|
||||
return self._attr_supported_features
|
||||
|
||||
@property
|
||||
def supported_features_compat(self) -> ClimateEntityFeature:
|
||||
"""Return the supported features as ClimateEntityFeature.
|
||||
|
||||
Remove this compatibility shim in 2025.1 or later.
|
||||
"""
|
||||
features = self.supported_features
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = ClimateEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
|
||||
@cached_property
|
||||
def min_temp(self) -> float:
|
||||
"""Return the minimum temperature."""
|
||||
@@ -720,3 +729,13 @@ async def async_service_temperature_set(
|
||||
kwargs[value] = temp
|
||||
|
||||
await entity.async_set_temperature(**kwargs)
|
||||
|
||||
|
||||
# As we import deprecated constants from the const module, we need to add these two functions
|
||||
# otherwise this module will be logged for using deprecated constants and not the custom component
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = ft.partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = ft.partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -5,6 +5,7 @@ from functools import partial
|
||||
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstantEnum,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -188,6 +189,9 @@ _DEPRECATED_SUPPORT_AUX_HEAT = DeprecatedConstantEnum(
|
||||
ClimateEntityFeature.AUX_HEAT, "2025.1"
|
||||
)
|
||||
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
__dir__ = partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -5,6 +5,7 @@ import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
from datetime import datetime, timedelta
|
||||
from enum import Enum
|
||||
from typing import cast
|
||||
|
||||
from hass_nabucasa import Cloud
|
||||
import voluptuous as vol
|
||||
@@ -176,6 +177,22 @@ def async_active_subscription(hass: HomeAssistant) -> bool:
|
||||
return async_is_logged_in(hass) and not hass.data[DOMAIN].subscription_expired
|
||||
|
||||
|
||||
async def async_get_or_create_cloudhook(hass: HomeAssistant, webhook_id: str) -> str:
|
||||
"""Get or create a cloudhook."""
|
||||
if not async_is_connected(hass):
|
||||
raise CloudNotConnected
|
||||
|
||||
if not async_is_logged_in(hass):
|
||||
raise CloudNotAvailable
|
||||
|
||||
cloud: Cloud[CloudClient] = hass.data[DOMAIN]
|
||||
cloudhooks = cloud.client.cloudhooks
|
||||
if hook := cloudhooks.get(webhook_id):
|
||||
return cast(str, hook["cloudhook_url"])
|
||||
|
||||
return await async_create_cloudhook(hass, webhook_id)
|
||||
|
||||
|
||||
@bind_hass
|
||||
async def async_create_cloudhook(hass: HomeAssistant, webhook_id: str) -> str:
|
||||
"""Create a cloudhook."""
|
||||
@@ -274,7 +291,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
}
|
||||
|
||||
async def _on_start() -> None:
|
||||
"""Discover platforms."""
|
||||
"""Handle cloud started after login."""
|
||||
nonlocal loaded
|
||||
|
||||
# Prevent multiple discovery
|
||||
@@ -282,14 +299,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
return
|
||||
loaded = True
|
||||
|
||||
tts_info = {"platform_loaded": tts_platform_loaded}
|
||||
|
||||
await async_load_platform(hass, Platform.TTS, DOMAIN, tts_info, config)
|
||||
await tts_platform_loaded.wait()
|
||||
|
||||
# The config entry should be loaded after the legacy tts platform is loaded
|
||||
# to make sure that the tts integration is setup before we try to migrate
|
||||
# old assist pipelines in the cloud stt entity.
|
||||
await hass.config_entries.flow.async_init(DOMAIN, context={"source": "system"})
|
||||
|
||||
async def _on_connect() -> None:
|
||||
@@ -318,6 +327,16 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
|
||||
account_link.async_setup(hass)
|
||||
|
||||
hass.async_create_task(
|
||||
async_load_platform(
|
||||
hass,
|
||||
Platform.TTS,
|
||||
DOMAIN,
|
||||
{"platform_loaded": tts_platform_loaded},
|
||||
config,
|
||||
)
|
||||
)
|
||||
|
||||
async_call_later(
|
||||
hass=hass,
|
||||
delay=timedelta(hours=STARTUP_REPAIR_DELAY),
|
||||
|
||||
@@ -72,6 +72,7 @@ class ComelitConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
_reauth_entry: ConfigEntry | None
|
||||
_reauth_host: str
|
||||
_reauth_port: int
|
||||
_reauth_type: str
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
@@ -109,6 +110,7 @@ class ComelitConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
self._reauth_host = entry_data[CONF_HOST]
|
||||
self._reauth_port = entry_data.get(CONF_PORT, DEFAULT_PORT)
|
||||
self._reauth_type = entry_data.get(CONF_TYPE, BRIDGE)
|
||||
|
||||
self.context["title_placeholders"] = {"host": self._reauth_host}
|
||||
return await self.async_step_reauth_confirm()
|
||||
@@ -127,6 +129,7 @@ class ComelitConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
{
|
||||
CONF_HOST: self._reauth_host,
|
||||
CONF_PORT: self._reauth_port,
|
||||
CONF_TYPE: self._reauth_type,
|
||||
}
|
||||
| user_input,
|
||||
)
|
||||
@@ -144,6 +147,7 @@ class ComelitConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
CONF_HOST: self._reauth_host,
|
||||
CONF_PORT: self._reauth_port,
|
||||
CONF_PIN: user_input[CONF_PIN],
|
||||
CONF_TYPE: self._reauth_type,
|
||||
},
|
||||
)
|
||||
self.hass.async_create_task(
|
||||
|
||||
@@ -81,15 +81,11 @@ class ComelitBaseCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
try:
|
||||
await self.api.login()
|
||||
return await self._async_update_system_data()
|
||||
except exceptions.CannotConnect as err:
|
||||
_LOGGER.warning("Connection error for %s", self._host)
|
||||
await self.api.close()
|
||||
raise UpdateFailed(f"Error fetching data: {repr(err)}") from err
|
||||
except (exceptions.CannotConnect, exceptions.CannotRetrieveData) as err:
|
||||
raise UpdateFailed(repr(err)) from err
|
||||
except exceptions.CannotAuthenticate:
|
||||
raise ConfigEntryAuthFailed
|
||||
|
||||
return {}
|
||||
|
||||
@abstractmethod
|
||||
async def _async_update_system_data(self) -> dict[str, Any]:
|
||||
"""Class method for updating data."""
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/comelit",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aiocomelit"],
|
||||
"requirements": ["aiocomelit==0.7.0"]
|
||||
"requirements": ["aiocomelit==0.7.3"]
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "system",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["hassil==1.5.1", "home-assistant-intents==2023.12.05"]
|
||||
"requirements": ["hassil==1.5.1", "home-assistant-intents==2024.1.2"]
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||
)
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstantEnum,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -143,10 +144,6 @@ _DEPRECATED_SUPPORT_SET_TILT_POSITION = DeprecatedConstantEnum(
|
||||
CoverEntityFeature.SET_TILT_POSITION, "2025.1"
|
||||
)
|
||||
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = ft.partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = ft.partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
|
||||
ATTR_CURRENT_POSITION = "current_position"
|
||||
ATTR_CURRENT_TILT_POSITION = "current_tilt_position"
|
||||
ATTR_POSITION = "position"
|
||||
@@ -340,8 +337,12 @@ class CoverEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
@property
|
||||
def supported_features(self) -> CoverEntityFeature:
|
||||
"""Flag supported features."""
|
||||
if self._attr_supported_features is not None:
|
||||
return self._attr_supported_features
|
||||
if (features := self._attr_supported_features) is not None:
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = CoverEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
|
||||
supported_features = (
|
||||
CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP
|
||||
@@ -480,7 +481,7 @@ class CoverEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def _get_toggle_function(
|
||||
self, fns: dict[str, Callable[_P, _R]]
|
||||
) -> Callable[_P, _R]:
|
||||
if CoverEntityFeature.STOP | self.supported_features and (
|
||||
if self.supported_features & CoverEntityFeature.STOP and (
|
||||
self.is_closing or self.is_opening
|
||||
):
|
||||
return fns["stop"]
|
||||
@@ -489,3 +490,11 @@ class CoverEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
if self._cover_is_last_toggle_direction_open:
|
||||
return fns["close"]
|
||||
return fns["open"]
|
||||
|
||||
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = ft.partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = ft.partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -6,6 +6,7 @@ from functools import partial
|
||||
from homeassistant.const import ATTR_GPS_ACCURACY, STATE_HOME # noqa: F401
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.deprecation import (
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -57,12 +58,6 @@ from .legacy import ( # noqa: F401
|
||||
see,
|
||||
)
|
||||
|
||||
# As we import deprecated constants from the const module, we need to add these two functions
|
||||
# otherwise this module will be logged for using deprecated constants and not the custom component
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
|
||||
|
||||
@bind_hass
|
||||
def is_on(hass: HomeAssistant, entity_id: str) -> bool:
|
||||
@@ -83,3 +78,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
await async_setup_legacy_integration(hass, config)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# As we import deprecated constants from the const module, we need to add these two functions
|
||||
# otherwise this module will be logged for using deprecated constants and not the custom component
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import Final
|
||||
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstantEnum,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -44,10 +45,6 @@ _DEPRECATED_SOURCE_TYPE_BLUETOOTH_LE: Final = DeprecatedConstantEnum(
|
||||
SourceType.BLUETOOTH_LE, "2025.1"
|
||||
)
|
||||
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
|
||||
CONF_SCAN_INTERVAL: Final = "interval_seconds"
|
||||
SCAN_INTERVAL: Final = timedelta(seconds=12)
|
||||
|
||||
@@ -71,3 +68,10 @@ ATTR_CONSIDER_HOME: Final = "consider_home"
|
||||
ATTR_IP: Final = "ip"
|
||||
|
||||
CONNECTED_DEVICE_REGISTERED: Final = "device_tracker_connected_device_registered"
|
||||
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/drop_connect",
|
||||
"iot_class": "local_push",
|
||||
"mqtt": ["drop_connect/discovery/#"],
|
||||
"requirements": ["dropmqttapi==1.0.1"]
|
||||
"requirements": ["dropmqttapi==1.0.2"]
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class DuotecnoClimate(DuotecnoEntity, ClimateEntity):
|
||||
_attr_translation_key = "duotecno"
|
||||
|
||||
@property
|
||||
def current_temperature(self) -> int | None:
|
||||
def current_temperature(self) -> float | None:
|
||||
"""Get the current temperature."""
|
||||
return self._unit.get_cur_temp()
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pyduotecno", "pyduotecno-node", "pyduotecno-unit"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["pyDuotecno==2023.11.1"]
|
||||
"requirements": ["pyDuotecno==2024.1.1"]
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Iterable
|
||||
from functools import lru_cache
|
||||
import hashlib
|
||||
from http import HTTPStatus
|
||||
@@ -41,6 +42,7 @@ from homeassistant.components.light import (
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_TRANSITION,
|
||||
ATTR_XY_COLOR,
|
||||
ColorMode,
|
||||
LightEntityFeature,
|
||||
)
|
||||
from homeassistant.components.media_player import (
|
||||
@@ -115,12 +117,19 @@ UNAUTHORIZED_USER = [
|
||||
{"error": {"address": "/", "description": "unauthorized user", "type": "1"}}
|
||||
]
|
||||
|
||||
DIMMABLE_SUPPORT_FEATURES = (
|
||||
CoverEntityFeature.SET_POSITION
|
||||
| FanEntityFeature.SET_SPEED
|
||||
| MediaPlayerEntityFeature.VOLUME_SET
|
||||
| ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
)
|
||||
DIMMABLE_SUPPORTED_FEATURES_BY_DOMAIN = {
|
||||
cover.DOMAIN: CoverEntityFeature.SET_POSITION,
|
||||
fan.DOMAIN: FanEntityFeature.SET_SPEED,
|
||||
media_player.DOMAIN: MediaPlayerEntityFeature.VOLUME_SET,
|
||||
climate.DOMAIN: ClimateEntityFeature.TARGET_TEMPERATURE,
|
||||
}
|
||||
|
||||
ENTITY_FEATURES_BY_DOMAIN = {
|
||||
cover.DOMAIN: CoverEntityFeature,
|
||||
fan.DOMAIN: FanEntityFeature,
|
||||
media_player.DOMAIN: MediaPlayerEntityFeature,
|
||||
climate.DOMAIN: ClimateEntityFeature,
|
||||
}
|
||||
|
||||
|
||||
@lru_cache(maxsize=32)
|
||||
@@ -756,7 +765,6 @@ def _entity_unique_id(entity_id: str) -> str:
|
||||
|
||||
def state_to_json(config: Config, state: State) -> dict[str, Any]:
|
||||
"""Convert an entity to its Hue bridge JSON representation."""
|
||||
entity_features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
color_modes = state.attributes.get(light.ATTR_SUPPORTED_COLOR_MODES, [])
|
||||
unique_id = _entity_unique_id(state.entity_id)
|
||||
state_dict = get_entity_state_dict(config, state)
|
||||
@@ -773,9 +781,9 @@ def state_to_json(config: Config, state: State) -> dict[str, Any]:
|
||||
"manufacturername": "Home Assistant",
|
||||
"swversion": "123",
|
||||
}
|
||||
|
||||
color_supported = light.color_supported(color_modes)
|
||||
color_temp_supported = light.color_temp_supported(color_modes)
|
||||
is_light = state.domain == light.DOMAIN
|
||||
color_supported = is_light and light.color_supported(color_modes)
|
||||
color_temp_supported = is_light and light.color_temp_supported(color_modes)
|
||||
if color_supported and color_temp_supported:
|
||||
# Extended Color light (Zigbee Device ID: 0x0210)
|
||||
# Same as Color light, but which supports additional setting of color temperature
|
||||
@@ -820,9 +828,7 @@ def state_to_json(config: Config, state: State) -> dict[str, Any]:
|
||||
HUE_API_STATE_BRI: state_dict[STATE_BRIGHTNESS],
|
||||
}
|
||||
)
|
||||
elif entity_features & DIMMABLE_SUPPORT_FEATURES or light.brightness_supported(
|
||||
color_modes
|
||||
):
|
||||
elif state_supports_hue_brightness(state, color_modes):
|
||||
# Dimmable light (Zigbee Device ID: 0x0100)
|
||||
# Supports groups, scenes, on/off and dimming
|
||||
retval["type"] = "Dimmable light"
|
||||
@@ -845,6 +851,21 @@ def state_to_json(config: Config, state: State) -> dict[str, Any]:
|
||||
return retval
|
||||
|
||||
|
||||
def state_supports_hue_brightness(
|
||||
state: State, color_modes: Iterable[ColorMode]
|
||||
) -> bool:
|
||||
"""Return True if the state supports brightness."""
|
||||
domain = state.domain
|
||||
if domain == light.DOMAIN:
|
||||
return light.brightness_supported(color_modes)
|
||||
if not (required_feature := DIMMABLE_SUPPORTED_FEATURES_BY_DOMAIN.get(domain)):
|
||||
return False
|
||||
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
enum = ENTITY_FEATURES_BY_DOMAIN[domain]
|
||||
features = enum(features) if type(features) is int else features # noqa: E721
|
||||
return required_feature in features
|
||||
|
||||
|
||||
def create_hue_success_response(
|
||||
entity_number: str, attr: str, value: str
|
||||
) -> dict[str, Any]:
|
||||
|
||||
@@ -5,12 +5,23 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import EnergyZeroDataUpdateCoordinator
|
||||
from .services import async_register_services
|
||||
from .services import async_setup_services
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up EnergyZero services."""
|
||||
|
||||
async_setup_services(hass)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
@@ -27,8 +38,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
async_register_services(hass, coordinator)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import Final
|
||||
from energyzero import Electricity, Gas, VatOption
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
from homeassistant.core import (
|
||||
HomeAssistant,
|
||||
ServiceCall,
|
||||
@@ -17,11 +18,13 @@ from homeassistant.core import (
|
||||
callback,
|
||||
)
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import selector
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import EnergyZeroDataUpdateCoordinator
|
||||
|
||||
ATTR_CONFIG_ENTRY: Final = "config_entry"
|
||||
ATTR_START: Final = "start"
|
||||
ATTR_END: Final = "end"
|
||||
ATTR_INCL_VAT: Final = "incl_vat"
|
||||
@@ -30,6 +33,11 @@ GAS_SERVICE_NAME: Final = "get_gas_prices"
|
||||
ENERGY_SERVICE_NAME: Final = "get_energy_prices"
|
||||
SERVICE_SCHEMA: Final = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_CONFIG_ENTRY): selector.ConfigEntrySelector(
|
||||
{
|
||||
"integration": DOMAIN,
|
||||
}
|
||||
),
|
||||
vol.Required(ATTR_INCL_VAT): bool,
|
||||
vol.Optional(ATTR_START): str,
|
||||
vol.Optional(ATTR_END): str,
|
||||
@@ -75,12 +83,43 @@ def __serialize_prices(prices: Electricity | Gas) -> ServiceResponse:
|
||||
}
|
||||
|
||||
|
||||
def __get_coordinator(
|
||||
hass: HomeAssistant, call: ServiceCall
|
||||
) -> EnergyZeroDataUpdateCoordinator:
|
||||
"""Get the coordinator from the entry."""
|
||||
entry_id: str = call.data[ATTR_CONFIG_ENTRY]
|
||||
entry: ConfigEntry | None = hass.config_entries.async_get_entry(entry_id)
|
||||
|
||||
if not entry:
|
||||
raise ServiceValidationError(
|
||||
f"Invalid config entry: {entry_id}",
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_config_entry",
|
||||
translation_placeholders={
|
||||
"config_entry": entry_id,
|
||||
},
|
||||
)
|
||||
if entry.state != ConfigEntryState.LOADED:
|
||||
raise ServiceValidationError(
|
||||
f"{entry.title} is not loaded",
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="unloaded_config_entry",
|
||||
translation_placeholders={
|
||||
"config_entry": entry.title,
|
||||
},
|
||||
)
|
||||
|
||||
return hass.data[DOMAIN][entry_id]
|
||||
|
||||
|
||||
async def __get_prices(
|
||||
call: ServiceCall,
|
||||
*,
|
||||
coordinator: EnergyZeroDataUpdateCoordinator,
|
||||
hass: HomeAssistant,
|
||||
price_type: PriceType,
|
||||
) -> ServiceResponse:
|
||||
coordinator = __get_coordinator(hass, call)
|
||||
|
||||
start = __get_date(call.data.get(ATTR_START))
|
||||
end = __get_date(call.data.get(ATTR_END))
|
||||
|
||||
@@ -108,22 +147,20 @@ async def __get_prices(
|
||||
|
||||
|
||||
@callback
|
||||
def async_register_services(
|
||||
hass: HomeAssistant, coordinator: EnergyZeroDataUpdateCoordinator
|
||||
):
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Set up EnergyZero services."""
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
GAS_SERVICE_NAME,
|
||||
partial(__get_prices, coordinator=coordinator, price_type=PriceType.GAS),
|
||||
partial(__get_prices, hass=hass, price_type=PriceType.GAS),
|
||||
schema=SERVICE_SCHEMA,
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
ENERGY_SERVICE_NAME,
|
||||
partial(__get_prices, coordinator=coordinator, price_type=PriceType.ENERGY),
|
||||
partial(__get_prices, hass=hass, price_type=PriceType.ENERGY),
|
||||
schema=SERVICE_SCHEMA,
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
get_gas_prices:
|
||||
fields:
|
||||
config_entry:
|
||||
required: true
|
||||
selector:
|
||||
config_entry:
|
||||
integration: energyzero
|
||||
incl_vat:
|
||||
required: true
|
||||
default: true
|
||||
@@ -17,6 +22,11 @@ get_gas_prices:
|
||||
datetime:
|
||||
get_energy_prices:
|
||||
fields:
|
||||
config_entry:
|
||||
required: true
|
||||
selector:
|
||||
config_entry:
|
||||
integration: energyzero
|
||||
incl_vat:
|
||||
required: true
|
||||
default: true
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
"exceptions": {
|
||||
"invalid_date": {
|
||||
"message": "Invalid date provided. Got {date}"
|
||||
},
|
||||
"invalid_config_entry": {
|
||||
"message": "Invalid config entry provided. Got {config_entry}"
|
||||
},
|
||||
"unloaded_config_entry": {
|
||||
"message": "Invalid config entry provided. {config_entry} is not loaded."
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
@@ -50,6 +56,10 @@
|
||||
"name": "Get gas prices",
|
||||
"description": "Request gas prices from EnergyZero.",
|
||||
"fields": {
|
||||
"config_entry": {
|
||||
"name": "Config Entry",
|
||||
"description": "The config entry to use for this service."
|
||||
},
|
||||
"incl_vat": {
|
||||
"name": "Including VAT",
|
||||
"description": "Include VAT in the prices."
|
||||
@@ -68,6 +78,10 @@
|
||||
"name": "Get energy prices",
|
||||
"description": "Request energy prices from EnergyZero.",
|
||||
"fields": {
|
||||
"config_entry": {
|
||||
"name": "[%key:component::energyzero::services::get_gas_prices::fields::config_entry::name%]",
|
||||
"description": "[%key:component::energyzero::services::get_gas_prices::fields::config_entry::description%]"
|
||||
},
|
||||
"incl_vat": {
|
||||
"name": "[%key:component::energyzero::services::get_gas_prices::fields::incl_vat::name%]",
|
||||
"description": "[%key:component::energyzero::services::get_gas_prices::fields::incl_vat::description%]"
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/enigma2",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["openwebif"],
|
||||
"requirements": ["openwebifpy==4.0.2"]
|
||||
"requirements": ["openwebifpy==4.2.1"]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Support for Enigma2 media players."""
|
||||
from __future__ import annotations
|
||||
|
||||
from aiohttp.client_exceptions import ClientConnectorError
|
||||
from openwebif.api import OpenWebIfDevice
|
||||
from openwebif.enums import RemoteControlCodes
|
||||
import voluptuous as vol
|
||||
@@ -20,6 +21,7 @@ from homeassistant.const import (
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
@@ -96,9 +98,13 @@ async def async_setup_platform(
|
||||
source_bouquet=config.get(CONF_SOURCE_BOUQUET),
|
||||
)
|
||||
|
||||
async_add_entities(
|
||||
[Enigma2Device(config[CONF_NAME], device, await device.get_about())]
|
||||
)
|
||||
try:
|
||||
about = await device.get_about()
|
||||
except ClientConnectorError as err:
|
||||
await device.close()
|
||||
raise PlatformNotReady from err
|
||||
|
||||
async_add_entities([Enigma2Device(config[CONF_NAME], device, about)])
|
||||
|
||||
|
||||
class Enigma2Device(MediaPlayerEntity):
|
||||
@@ -169,8 +175,8 @@ class Enigma2Device(MediaPlayerEntity):
|
||||
await self._device.send_remote_control_action(RemoteControlCodes.CHANNEL_UP)
|
||||
|
||||
async def async_media_previous_track(self) -> None:
|
||||
"""Send next track command."""
|
||||
self._device.send_remote_control_action(RemoteControlCodes.CHANNEL_DOWN)
|
||||
"""Send previous track command."""
|
||||
await self._device.send_remote_control_action(RemoteControlCodes.CHANNEL_DOWN)
|
||||
|
||||
async def async_mute_volume(self, mute: bool) -> None:
|
||||
"""Mute or unmute."""
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["pyenphase"],
|
||||
"requirements": ["pyenphase==1.15.2"],
|
||||
"requirements": ["pyenphase==1.17.0"],
|
||||
"zeroconf": [
|
||||
{
|
||||
"type": "_enphase-envoy._tcp.local."
|
||||
|
||||
@@ -479,10 +479,20 @@ class EnvoyInverterEntity(EnvoySensorBaseEntity):
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> datetime.datetime | float:
|
||||
def native_value(self) -> datetime.datetime | float | None:
|
||||
"""Return the state of the sensor."""
|
||||
inverters = self.data.inverters
|
||||
assert inverters is not None
|
||||
# Some envoy fw versions return an empty inverter array every 4 hours when
|
||||
# no production is taking place. Prevent collection failure due to this
|
||||
# as other data seems fine. Inverters will show unknown during this cycle.
|
||||
if self._serial_number not in inverters:
|
||||
_LOGGER.debug(
|
||||
"Inverter %s not in returned inverters array (size: %s)",
|
||||
self._serial_number,
|
||||
len(inverters),
|
||||
)
|
||||
return None
|
||||
return self.entity_description.value_fn(inverters[self._serial_number])
|
||||
|
||||
|
||||
|
||||
@@ -497,7 +497,6 @@ class EvoBroker:
|
||||
|
||||
session_id = get_session_id(self.client_v1)
|
||||
|
||||
self.temps = {} # these are now stale, will fall back to v2 temps
|
||||
try:
|
||||
temps = await self.client_v1.get_temperatures()
|
||||
|
||||
@@ -523,6 +522,11 @@ class EvoBroker:
|
||||
),
|
||||
err,
|
||||
)
|
||||
self.temps = {} # high-precision temps now considered stale
|
||||
|
||||
except Exception:
|
||||
self.temps = {} # high-precision temps now considered stale
|
||||
raise
|
||||
|
||||
else:
|
||||
if str(self.client_v1.location_id) != self._location.locationId:
|
||||
@@ -654,6 +658,7 @@ class EvoChild(EvoDevice):
|
||||
assert isinstance(self._evo_device, evo.HotWater | evo.Zone) # mypy check
|
||||
|
||||
if self._evo_broker.temps.get(self._evo_id) is not None:
|
||||
# use high-precision temps if available
|
||||
return self._evo_broker.temps[self._evo_id]
|
||||
return self._evo_device.temperature
|
||||
|
||||
|
||||
@@ -118,7 +118,6 @@ class FAABinarySensor(CoordinatorEntity[FAADataUpdateCoordinator], BinarySensorE
|
||||
super().__init__(coordinator)
|
||||
self.entity_description = description
|
||||
_id = coordinator.data.code
|
||||
self._attr_name = f"{_id} {description.name}"
|
||||
self._attr_unique_id = f"{_id}_{description.key}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, _id)},
|
||||
|
||||
@@ -26,6 +26,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||
)
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstantEnum,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -76,10 +77,6 @@ _DEPRECATED_SUPPORT_PRESET_MODE = DeprecatedConstantEnum(
|
||||
FanEntityFeature.PRESET_MODE, "2025.1"
|
||||
)
|
||||
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = ft.partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = ft.partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
|
||||
SERVICE_INCREASE_SPEED = "increase_speed"
|
||||
SERVICE_DECREASE_SPEED = "decrease_speed"
|
||||
SERVICE_OSCILLATE = "oscillate"
|
||||
@@ -400,7 +397,7 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def capability_attributes(self) -> dict[str, list[str] | None]:
|
||||
"""Return capability attributes."""
|
||||
attrs = {}
|
||||
supported_features = self.supported_features
|
||||
supported_features = self.supported_features_compat
|
||||
|
||||
if (
|
||||
FanEntityFeature.SET_SPEED in supported_features
|
||||
@@ -415,7 +412,7 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def state_attributes(self) -> dict[str, float | str | None]:
|
||||
"""Return optional state attributes."""
|
||||
data: dict[str, float | str | None] = {}
|
||||
supported_features = self.supported_features
|
||||
supported_features = self.supported_features_compat
|
||||
|
||||
if FanEntityFeature.DIRECTION in supported_features:
|
||||
data[ATTR_DIRECTION] = self.current_direction
|
||||
@@ -439,6 +436,19 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Flag supported features."""
|
||||
return self._attr_supported_features
|
||||
|
||||
@property
|
||||
def supported_features_compat(self) -> FanEntityFeature:
|
||||
"""Return the supported features as FanEntityFeature.
|
||||
|
||||
Remove this compatibility shim in 2025.1 or later.
|
||||
"""
|
||||
features = self.supported_features
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = FanEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
|
||||
@cached_property
|
||||
def preset_mode(self) -> str | None:
|
||||
"""Return the current preset mode, e.g., auto, smart, interval, favorite.
|
||||
@@ -458,3 +468,11 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
if hasattr(self, "_attr_preset_modes"):
|
||||
return self._attr_preset_modes
|
||||
return None
|
||||
|
||||
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = ft.partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = ft.partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -168,8 +168,8 @@ class FinTsClient:
|
||||
if not account_information:
|
||||
return False
|
||||
|
||||
if 1 <= account_information["type"] <= 9:
|
||||
return True
|
||||
if account_type := account_information.get("type"):
|
||||
return 1 <= account_type <= 9
|
||||
|
||||
if (
|
||||
account_information["iban"] in self.account_config
|
||||
@@ -188,8 +188,8 @@ class FinTsClient:
|
||||
if not account_information:
|
||||
return False
|
||||
|
||||
if 30 <= account_information["type"] <= 39:
|
||||
return True
|
||||
if account_type := account_information.get("type"):
|
||||
return 30 <= account_type <= 39
|
||||
|
||||
if (
|
||||
account_information["iban"] in self.holdings_config
|
||||
|
||||
@@ -69,6 +69,8 @@ class FitbitOAuth2Implementation(AuthImplementation):
|
||||
)
|
||||
if err.status == HTTPStatus.UNAUTHORIZED:
|
||||
raise FitbitAuthException(f"Unauthorized error: {err}") from err
|
||||
if err.status == HTTPStatus.BAD_REQUEST:
|
||||
raise FitbitAuthException(f"Bad Request error: {err}") from err
|
||||
raise FitbitApiException(f"Server error response: {err}") from err
|
||||
except aiohttp.ClientError as err:
|
||||
raise FitbitApiException(f"Client connection error: {err}") from err
|
||||
|
||||
@@ -19,7 +19,7 @@ from homeassistant.components.climate import (
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, UnitOfTemperature
|
||||
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_HALVES, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
@@ -27,6 +27,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
MAX_TEMP,
|
||||
MIN_TEMP,
|
||||
PRESET_TO_VENTILATION_MODE_MAP,
|
||||
VENTILATION_TO_PRESET_MODE_MAP,
|
||||
)
|
||||
@@ -65,8 +67,10 @@ class FlexitClimateEntity(ClimateEntity):
|
||||
ClimateEntityFeature.PRESET_MODE | ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
)
|
||||
|
||||
_attr_target_temperature_step = PRECISION_WHOLE
|
||||
_attr_target_temperature_step = PRECISION_HALVES
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_max_temp = MAX_TEMP
|
||||
_attr_min_temp = MIN_TEMP
|
||||
|
||||
def __init__(self, device: FlexitBACnet) -> None:
|
||||
"""Initialize the unit."""
|
||||
|
||||
@@ -15,6 +15,9 @@ from homeassistant.components.climate import (
|
||||
|
||||
DOMAIN = "flexit_bacnet"
|
||||
|
||||
MAX_TEMP = 30
|
||||
MIN_TEMP = 10
|
||||
|
||||
VENTILATION_TO_PRESET_MODE_MAP = {
|
||||
VENTILATION_MODE_STOP: PRESET_NONE,
|
||||
VENTILATION_MODE_AWAY: PRESET_AWAY,
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/flipr",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["flipr_api"],
|
||||
"requirements": ["flipr-api==1.5.0"]
|
||||
"requirements": ["flipr-api==1.5.1"]
|
||||
}
|
||||
|
||||
@@ -1063,6 +1063,7 @@ class SwitchInfo(TypedDict):
|
||||
type: str
|
||||
callback_update: Callable
|
||||
callback_switch: Callable
|
||||
init_state: bool
|
||||
|
||||
|
||||
class FritzBoxBaseEntity:
|
||||
|
||||
@@ -166,9 +166,7 @@ async def _async_wifi_entities_list(
|
||||
|
||||
_LOGGER.debug("WiFi networks list: %s", networks)
|
||||
return [
|
||||
FritzBoxWifiSwitch(
|
||||
avm_wrapper, device_friendly_name, index, data["switch_name"]
|
||||
)
|
||||
FritzBoxWifiSwitch(avm_wrapper, device_friendly_name, index, data)
|
||||
for index, data in networks.items()
|
||||
]
|
||||
|
||||
@@ -310,18 +308,16 @@ class FritzBoxBaseCoordinatorSwitch(CoordinatorEntity[AvmWrapper], SwitchEntity)
|
||||
await self._async_handle_turn_on_off(turn_on=False)
|
||||
|
||||
|
||||
class FritzBoxBaseSwitch(FritzBoxBaseEntity):
|
||||
class FritzBoxBaseSwitch(FritzBoxBaseEntity, SwitchEntity):
|
||||
"""Fritz switch base class."""
|
||||
|
||||
_attr_is_on: bool | None = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
avm_wrapper: AvmWrapper,
|
||||
device_friendly_name: str,
|
||||
switch_info: SwitchInfo,
|
||||
) -> None:
|
||||
"""Init Fritzbox port switch."""
|
||||
"""Init Fritzbox base switch."""
|
||||
super().__init__(avm_wrapper, device_friendly_name)
|
||||
|
||||
self._description = switch_info["description"]
|
||||
@@ -330,6 +326,7 @@ class FritzBoxBaseSwitch(FritzBoxBaseEntity):
|
||||
self._type = switch_info["type"]
|
||||
self._update = switch_info["callback_update"]
|
||||
self._switch = switch_info["callback_switch"]
|
||||
self._attr_is_on = switch_info["init_state"]
|
||||
|
||||
self._name = f"{self._friendly_name} {self._description}"
|
||||
self._unique_id = f"{self._avm_wrapper.unique_id}-{slugify(self._description)}"
|
||||
@@ -381,7 +378,7 @@ class FritzBoxBaseSwitch(FritzBoxBaseEntity):
|
||||
self._attr_is_on = turn_on
|
||||
|
||||
|
||||
class FritzBoxPortSwitch(FritzBoxBaseSwitch, SwitchEntity):
|
||||
class FritzBoxPortSwitch(FritzBoxBaseSwitch):
|
||||
"""Defines a FRITZ!Box Tools PortForward switch."""
|
||||
|
||||
def __init__(
|
||||
@@ -412,6 +409,7 @@ class FritzBoxPortSwitch(FritzBoxBaseSwitch, SwitchEntity):
|
||||
type=SWITCH_TYPE_PORTFORWARD,
|
||||
callback_update=self._async_fetch_update,
|
||||
callback_switch=self._async_switch_on_off_executor,
|
||||
init_state=port_mapping["NewEnabled"],
|
||||
)
|
||||
super().__init__(avm_wrapper, device_friendly_name, switch_info)
|
||||
|
||||
@@ -553,7 +551,7 @@ class FritzBoxProfileSwitch(FritzDeviceBase, SwitchEntity):
|
||||
return True
|
||||
|
||||
|
||||
class FritzBoxWifiSwitch(FritzBoxBaseSwitch, SwitchEntity):
|
||||
class FritzBoxWifiSwitch(FritzBoxBaseSwitch):
|
||||
"""Defines a FRITZ!Box Tools Wifi switch."""
|
||||
|
||||
def __init__(
|
||||
@@ -561,7 +559,7 @@ class FritzBoxWifiSwitch(FritzBoxBaseSwitch, SwitchEntity):
|
||||
avm_wrapper: AvmWrapper,
|
||||
device_friendly_name: str,
|
||||
network_num: int,
|
||||
network_name: str,
|
||||
network_data: dict,
|
||||
) -> None:
|
||||
"""Init Fritz Wifi switch."""
|
||||
self._avm_wrapper = avm_wrapper
|
||||
@@ -571,12 +569,13 @@ class FritzBoxWifiSwitch(FritzBoxBaseSwitch, SwitchEntity):
|
||||
self._network_num = network_num
|
||||
|
||||
switch_info = SwitchInfo(
|
||||
description=f"Wi-Fi {network_name}",
|
||||
description=f"Wi-Fi {network_data['switch_name']}",
|
||||
friendly_name=device_friendly_name,
|
||||
icon="mdi:wifi",
|
||||
type=SWITCH_TYPE_WIFINETWORK,
|
||||
callback_update=self._async_fetch_update,
|
||||
callback_switch=self._async_switch_on_off_executor,
|
||||
init_state=network_data["enabled"],
|
||||
)
|
||||
super().__init__(self._avm_wrapper, device_friendly_name, switch_info)
|
||||
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20231228.0"]
|
||||
"requirements": ["home-assistant-frontend==20240104.0"]
|
||||
}
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["aio_geojson_generic_client"],
|
||||
"requirements": ["aio-geojson-generic-client==0.3"]
|
||||
"requirements": ["aio-geojson-generic-client==0.4"]
|
||||
}
|
||||
|
||||
@@ -43,6 +43,18 @@ async def async_setup_entry(
|
||||
)
|
||||
language = lang
|
||||
break
|
||||
if (
|
||||
obj_holidays.supported_languages
|
||||
and language not in obj_holidays.supported_languages
|
||||
and (default_language := obj_holidays.default_language)
|
||||
):
|
||||
obj_holidays = country_holidays(
|
||||
country,
|
||||
subdiv=province,
|
||||
years={dt_util.now().year, dt_util.now().year + 1},
|
||||
language=default_language,
|
||||
)
|
||||
language = default_language
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["aiohomekit", "commentjson"],
|
||||
"requirements": ["aiohomekit==3.1.1"],
|
||||
"requirements": ["aiohomekit==3.1.3"],
|
||||
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||
PLATFORM_SCHEMA_BASE,
|
||||
)
|
||||
from homeassistant.helpers.deprecation import (
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -81,12 +82,6 @@ DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.Coerce(HumidifierDeviceClass))
|
||||
# use the HumidifierDeviceClass enum instead.
|
||||
DEVICE_CLASSES = [cls.value for cls in HumidifierDeviceClass]
|
||||
|
||||
# As we import deprecated constants from the const module, we need to add these two functions
|
||||
# otherwise this module will be logged for using deprecated constants and not the custom component
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
|
||||
@@ -185,7 +180,7 @@ class HumidifierEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_AT
|
||||
ATTR_MAX_HUMIDITY: self.max_humidity,
|
||||
}
|
||||
|
||||
if HumidifierEntityFeature.MODES in self.supported_features:
|
||||
if HumidifierEntityFeature.MODES in self.supported_features_compat:
|
||||
data[ATTR_AVAILABLE_MODES] = self.available_modes
|
||||
|
||||
return data
|
||||
@@ -214,7 +209,7 @@ class HumidifierEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_AT
|
||||
if self.target_humidity is not None:
|
||||
data[ATTR_HUMIDITY] = self.target_humidity
|
||||
|
||||
if HumidifierEntityFeature.MODES in self.supported_features:
|
||||
if HumidifierEntityFeature.MODES in self.supported_features_compat:
|
||||
data[ATTR_MODE] = self.mode
|
||||
|
||||
return data
|
||||
@@ -280,3 +275,26 @@ class HumidifierEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_AT
|
||||
def supported_features(self) -> HumidifierEntityFeature:
|
||||
"""Return the list of supported features."""
|
||||
return self._attr_supported_features
|
||||
|
||||
@property
|
||||
def supported_features_compat(self) -> HumidifierEntityFeature:
|
||||
"""Return the supported features as HumidifierEntityFeature.
|
||||
|
||||
Remove this compatibility shim in 2025.1 or later.
|
||||
"""
|
||||
features = self.supported_features
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = HumidifierEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
|
||||
|
||||
# As we import deprecated constants from the const module, we need to add these two functions
|
||||
# otherwise this module will be logged for using deprecated constants and not the custom component
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -5,6 +5,7 @@ from functools import partial
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstant,
|
||||
DeprecatedConstantEnum,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -66,6 +67,9 @@ _DEPRECATED_SUPPORT_MODES = DeprecatedConstantEnum(
|
||||
HumidifierEntityFeature.MODES, "2025.1"
|
||||
)
|
||||
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
__dir__ = partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -70,7 +70,7 @@ async def async_setup_entry(
|
||||
config_entry.entry_id
|
||||
]
|
||||
entities = []
|
||||
for controller in coordinator.data.controllers:
|
||||
for controller in coordinator.data.controllers.values():
|
||||
entities.append(
|
||||
HydrawiseBinarySensor(coordinator, BINARY_SENSOR_STATUS, controller)
|
||||
)
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import timedelta
|
||||
|
||||
from pydrawise import HydrawiseBase
|
||||
from pydrawise.schema import User
|
||||
from pydrawise.schema import Controller, User, Zone
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
@@ -13,9 +14,20 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
from .const import DOMAIN, LOGGER
|
||||
|
||||
|
||||
class HydrawiseDataUpdateCoordinator(DataUpdateCoordinator[User]):
|
||||
@dataclass
|
||||
class HydrawiseData:
|
||||
"""Container for data fetched from the Hydrawise API."""
|
||||
|
||||
user: User
|
||||
controllers: dict[int, Controller]
|
||||
zones: dict[int, Zone]
|
||||
|
||||
|
||||
class HydrawiseDataUpdateCoordinator(DataUpdateCoordinator[HydrawiseData]):
|
||||
"""The Hydrawise Data Update Coordinator."""
|
||||
|
||||
api: HydrawiseBase
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, api: HydrawiseBase, scan_interval: timedelta
|
||||
) -> None:
|
||||
@@ -23,6 +35,13 @@ class HydrawiseDataUpdateCoordinator(DataUpdateCoordinator[User]):
|
||||
super().__init__(hass, LOGGER, name=DOMAIN, update_interval=scan_interval)
|
||||
self.api = api
|
||||
|
||||
async def _async_update_data(self) -> User:
|
||||
async def _async_update_data(self) -> HydrawiseData:
|
||||
"""Fetch the latest data from Hydrawise."""
|
||||
return await self.api.get_user()
|
||||
user = await self.api.get_user()
|
||||
controllers = {}
|
||||
zones = {}
|
||||
for controller in user.controllers:
|
||||
controllers[controller.id] = controller
|
||||
for zone in controller.zones:
|
||||
zones[zone.id] = zone
|
||||
return HydrawiseData(user=user, controllers=controllers, zones=zones)
|
||||
|
||||
@@ -48,5 +48,8 @@ class HydrawiseEntity(CoordinatorEntity[HydrawiseDataUpdateCoordinator]):
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Get the latest data and updates the state."""
|
||||
self.controller = self.coordinator.data.controllers[self.controller.id]
|
||||
if self.zone:
|
||||
self.zone = self.coordinator.data.zones[self.zone.id]
|
||||
self._update_attrs()
|
||||
super()._handle_coordinator_update()
|
||||
|
||||
@@ -76,7 +76,7 @@ async def async_setup_entry(
|
||||
]
|
||||
async_add_entities(
|
||||
HydrawiseSensor(coordinator, description, controller, zone)
|
||||
for controller in coordinator.data.controllers
|
||||
for controller in coordinator.data.controllers.values()
|
||||
for zone in controller.zones
|
||||
for description in SENSOR_TYPES
|
||||
)
|
||||
|
||||
@@ -81,7 +81,7 @@ async def async_setup_entry(
|
||||
]
|
||||
async_add_entities(
|
||||
HydrawiseSwitch(coordinator, description, controller, zone)
|
||||
for controller in coordinator.data.controllers
|
||||
for controller in coordinator.data.controllers.values()
|
||||
for zone in controller.zones
|
||||
for description in SWITCH_TYPES
|
||||
)
|
||||
|
||||
@@ -118,7 +118,7 @@ async def async_setup_platform(
|
||||
|
||||
mode = get_ip_mode(host)
|
||||
mac = await hass.async_add_executor_job(partial(get_mac_address, **{mode: host}))
|
||||
if mac is None:
|
||||
if mac is None or mac == "00:00:00:00:00:00":
|
||||
raise PlatformNotReady("Cannot get the ip address of kef speaker.")
|
||||
|
||||
unique_id = f"kef-{mac}"
|
||||
|
||||
@@ -82,6 +82,9 @@ DATA_HASS_CONFIG: Final = "knx_hass_config"
|
||||
ATTR_COUNTER: Final = "counter"
|
||||
ATTR_SOURCE: Final = "source"
|
||||
|
||||
# dispatcher signal for KNX interface device triggers
|
||||
SIGNAL_KNX_TELEGRAM_DICT: Final = "knx_telegram_dict"
|
||||
|
||||
AsyncMessageCallbackType = Callable[[Telegram], Awaitable[None]]
|
||||
MessageCallbackType = Callable[[Telegram], None]
|
||||
|
||||
|
||||
@@ -9,11 +9,12 @@ from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEM
|
||||
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE
|
||||
from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback
|
||||
from homeassistant.helpers import selector
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from . import KNXModule
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, SIGNAL_KNX_TELEGRAM_DICT
|
||||
from .project import KNXProject
|
||||
from .schema import ga_list_validator
|
||||
from .telegrams import TelegramDict
|
||||
@@ -87,7 +88,6 @@ async def async_attach_trigger(
|
||||
trigger_data = trigger_info["trigger_data"]
|
||||
dst_addresses: list[str] = config.get(EXTRA_FIELD_DESTINATION, [])
|
||||
job = HassJob(action, f"KNX device trigger {trigger_info}")
|
||||
knx: KNXModule = hass.data[DOMAIN]
|
||||
|
||||
@callback
|
||||
def async_call_trigger_action(telegram: TelegramDict) -> None:
|
||||
@@ -99,6 +99,8 @@ async def async_attach_trigger(
|
||||
{"trigger": {**trigger_data, **telegram}},
|
||||
)
|
||||
|
||||
return knx.telegrams.async_listen_telegram(
|
||||
async_call_trigger_action, name="KNX device trigger call"
|
||||
return async_dispatcher_connect(
|
||||
hass,
|
||||
signal=SIGNAL_KNX_TELEGRAM_DICT,
|
||||
target=async_call_trigger_action,
|
||||
)
|
||||
|
||||
@@ -11,10 +11,11 @@ from xknx.telegram import Telegram
|
||||
from xknx.telegram.apci import GroupValueResponse, GroupValueWrite
|
||||
|
||||
from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.storage import Store
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, SIGNAL_KNX_TELEGRAM_DICT
|
||||
from .project import KNXProject
|
||||
|
||||
STORAGE_VERSION: Final = 1
|
||||
@@ -87,6 +88,7 @@ class Telegrams:
|
||||
"""Handle incoming and outgoing telegrams from xknx."""
|
||||
telegram_dict = self.telegram_to_dict(telegram)
|
||||
self.recent_telegrams.append(telegram_dict)
|
||||
async_dispatcher_send(self.hass, SIGNAL_KNX_TELEGRAM_DICT, telegram_dict)
|
||||
for job in self._jobs:
|
||||
self.hass.async_run_hass_job(job, telegram_dict)
|
||||
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
import logging
|
||||
|
||||
from bleak_retry_connector import BleakError, close_stale_connections, get_device
|
||||
from bleak_retry_connector import (
|
||||
BleakError,
|
||||
close_stale_connections_by_address,
|
||||
get_device,
|
||||
)
|
||||
from ld2410_ble import LD2410BLE
|
||||
|
||||
from homeassistant.components import bluetooth
|
||||
@@ -24,6 +28,9 @@ _LOGGER = logging.getLogger(__name__)
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up LD2410 BLE from a config entry."""
|
||||
address: str = entry.data[CONF_ADDRESS]
|
||||
|
||||
await close_stale_connections_by_address(address)
|
||||
|
||||
ble_device = bluetooth.async_ble_device_from_address(
|
||||
hass, address.upper(), True
|
||||
) or await get_device(address)
|
||||
@@ -32,8 +39,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
f"Could not find LD2410B device with address {address}"
|
||||
)
|
||||
|
||||
await close_stale_connections(ble_device)
|
||||
|
||||
ld2410_ble = LD2410BLE(ble_device)
|
||||
|
||||
coordinator = LD2410BLECoordinator(hass, ld2410_ble)
|
||||
|
||||
@@ -345,11 +345,11 @@ def filter_turn_off_params(
|
||||
light: LightEntity, params: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Filter out params not used in turn off or not supported by the light."""
|
||||
supported_features = light.supported_features
|
||||
supported_features = light.supported_features_compat
|
||||
|
||||
if not supported_features & LightEntityFeature.FLASH:
|
||||
if LightEntityFeature.FLASH not in supported_features:
|
||||
params.pop(ATTR_FLASH, None)
|
||||
if not supported_features & LightEntityFeature.TRANSITION:
|
||||
if LightEntityFeature.TRANSITION not in supported_features:
|
||||
params.pop(ATTR_TRANSITION, None)
|
||||
|
||||
return {k: v for k, v in params.items() if k in (ATTR_TRANSITION, ATTR_FLASH)}
|
||||
@@ -357,13 +357,13 @@ def filter_turn_off_params(
|
||||
|
||||
def filter_turn_on_params(light: LightEntity, params: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Filter out params not supported by the light."""
|
||||
supported_features = light.supported_features
|
||||
supported_features = light.supported_features_compat
|
||||
|
||||
if not supported_features & LightEntityFeature.EFFECT:
|
||||
if LightEntityFeature.EFFECT not in supported_features:
|
||||
params.pop(ATTR_EFFECT, None)
|
||||
if not supported_features & LightEntityFeature.FLASH:
|
||||
if LightEntityFeature.FLASH not in supported_features:
|
||||
params.pop(ATTR_FLASH, None)
|
||||
if not supported_features & LightEntityFeature.TRANSITION:
|
||||
if LightEntityFeature.TRANSITION not in supported_features:
|
||||
params.pop(ATTR_TRANSITION, None)
|
||||
|
||||
supported_color_modes = (
|
||||
@@ -989,7 +989,7 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def capability_attributes(self) -> dict[str, Any]:
|
||||
"""Return capability attributes."""
|
||||
data: dict[str, Any] = {}
|
||||
supported_features = self.supported_features
|
||||
supported_features = self.supported_features_compat
|
||||
supported_color_modes = self._light_internal_supported_color_modes
|
||||
|
||||
if ColorMode.COLOR_TEMP in supported_color_modes:
|
||||
@@ -1007,7 +1007,7 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
data[ATTR_MAX_MIREDS] = color_util.color_temperature_kelvin_to_mired(
|
||||
self.min_color_temp_kelvin
|
||||
)
|
||||
if supported_features & LightEntityFeature.EFFECT:
|
||||
if LightEntityFeature.EFFECT in supported_features:
|
||||
data[ATTR_EFFECT_LIST] = self.effect_list
|
||||
|
||||
data[ATTR_SUPPORTED_COLOR_MODES] = sorted(supported_color_modes)
|
||||
@@ -1061,8 +1061,9 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return state attributes."""
|
||||
data: dict[str, Any] = {}
|
||||
supported_features = self.supported_features
|
||||
supported_features = self.supported_features_compat
|
||||
supported_color_modes = self._light_internal_supported_color_modes
|
||||
supported_features_value = supported_features.value
|
||||
color_mode = self._light_internal_color_mode if self.is_on else None
|
||||
|
||||
if color_mode and color_mode not in supported_color_modes:
|
||||
@@ -1081,7 +1082,7 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
data[ATTR_BRIGHTNESS] = self.brightness
|
||||
else:
|
||||
data[ATTR_BRIGHTNESS] = None
|
||||
elif supported_features & SUPPORT_BRIGHTNESS:
|
||||
elif supported_features_value & SUPPORT_BRIGHTNESS:
|
||||
# Backwards compatibility for ambiguous / incomplete states
|
||||
# Add warning in 2021.6, remove in 2021.10
|
||||
if self.is_on:
|
||||
@@ -1103,7 +1104,7 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
else:
|
||||
data[ATTR_COLOR_TEMP_KELVIN] = None
|
||||
data[ATTR_COLOR_TEMP] = None
|
||||
elif supported_features & SUPPORT_COLOR_TEMP:
|
||||
elif supported_features_value & SUPPORT_COLOR_TEMP:
|
||||
# Backwards compatibility
|
||||
# Add warning in 2021.6, remove in 2021.10
|
||||
if self.is_on:
|
||||
@@ -1133,7 +1134,7 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
if color_mode:
|
||||
data.update(self._light_internal_convert_color(color_mode))
|
||||
|
||||
if supported_features & LightEntityFeature.EFFECT:
|
||||
if LightEntityFeature.EFFECT in supported_features:
|
||||
data[ATTR_EFFECT] = self.effect if self.is_on else None
|
||||
|
||||
return data
|
||||
@@ -1146,14 +1147,15 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
|
||||
# Backwards compatibility for supported_color_modes added in 2021.4
|
||||
# Add warning in 2021.6, remove in 2021.10
|
||||
supported_features = self.supported_features
|
||||
supported_features = self.supported_features_compat
|
||||
supported_features_value = supported_features.value
|
||||
supported_color_modes: set[ColorMode] = set()
|
||||
|
||||
if supported_features & SUPPORT_COLOR_TEMP:
|
||||
if supported_features_value & SUPPORT_COLOR_TEMP:
|
||||
supported_color_modes.add(ColorMode.COLOR_TEMP)
|
||||
if supported_features & SUPPORT_COLOR:
|
||||
if supported_features_value & SUPPORT_COLOR:
|
||||
supported_color_modes.add(ColorMode.HS)
|
||||
if supported_features & SUPPORT_BRIGHTNESS and not supported_color_modes:
|
||||
if not supported_color_modes and supported_features_value & SUPPORT_BRIGHTNESS:
|
||||
supported_color_modes = {ColorMode.BRIGHTNESS}
|
||||
|
||||
if not supported_color_modes:
|
||||
@@ -1170,3 +1172,34 @@ class LightEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def supported_features(self) -> LightEntityFeature:
|
||||
"""Flag supported features."""
|
||||
return self._attr_supported_features
|
||||
|
||||
@property
|
||||
def supported_features_compat(self) -> LightEntityFeature:
|
||||
"""Return the supported features as LightEntityFeature.
|
||||
|
||||
Remove this compatibility shim in 2025.1 or later.
|
||||
"""
|
||||
features = self.supported_features
|
||||
if type(features) is not int: # noqa: E721
|
||||
return features
|
||||
new_features = LightEntityFeature(features)
|
||||
if self._deprecated_supported_features_reported is True:
|
||||
return new_features
|
||||
self._deprecated_supported_features_reported = True
|
||||
report_issue = self._suggest_report_issue()
|
||||
report_issue += (
|
||||
" and reference "
|
||||
"https://developers.home-assistant.io/blog/2023/12/28/support-feature-magic-numbers-deprecation"
|
||||
)
|
||||
_LOGGER.warning(
|
||||
(
|
||||
"Entity %s (%s) is using deprecated supported features"
|
||||
" values which will be removed in HA Core 2025.1. Instead it should use"
|
||||
" %s and color modes, please %s"
|
||||
),
|
||||
self.entity_id,
|
||||
type(self),
|
||||
repr(new_features),
|
||||
report_issue,
|
||||
)
|
||||
return new_features
|
||||
|
||||
@@ -33,6 +33,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401
|
||||
)
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstantEnum,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -70,10 +71,6 @@ class LockEntityFeature(IntFlag):
|
||||
# Please use the LockEntityFeature enum instead.
|
||||
_DEPRECATED_SUPPORT_OPEN = DeprecatedConstantEnum(LockEntityFeature.OPEN, "2025.1")
|
||||
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = ft.partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = ft.partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
|
||||
PROP_TO_ATTR = {"changed_by": ATTR_CHANGED_BY, "code_format": ATTR_CODE_FORMAT}
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
@@ -278,7 +275,12 @@ class LockEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
@cached_property
|
||||
def supported_features(self) -> LockEntityFeature:
|
||||
"""Return the list of supported features."""
|
||||
return self._attr_supported_features
|
||||
features = self._attr_supported_features
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = LockEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
|
||||
async def async_internal_added_to_hass(self) -> None:
|
||||
"""Call when the sensor entity is added to hass."""
|
||||
@@ -310,3 +312,11 @@ class LockEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
return
|
||||
|
||||
self._lock_option_default_code = ""
|
||||
|
||||
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = ft.partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = ft.partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"station_id": "Sensor ID",
|
||||
"sensor_id": "Sensor ID",
|
||||
"show_on_map": "Show on map"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,9 +223,12 @@ class MatrixBot:
|
||||
def _load_commands(self, commands: list[ConfigCommand]) -> None:
|
||||
for command in commands:
|
||||
# Set the command for all listening_rooms, unless otherwise specified.
|
||||
command.setdefault(CONF_ROOMS, list(self._listening_rooms.values()))
|
||||
if rooms := command.get(CONF_ROOMS):
|
||||
command[CONF_ROOMS] = [self._listening_rooms[room] for room in rooms]
|
||||
else:
|
||||
command[CONF_ROOMS] = list(self._listening_rooms.values())
|
||||
|
||||
# COMMAND_SCHEMA guarantees that exactly one of CONF_WORD and CONF_expression are set.
|
||||
# COMMAND_SCHEMA guarantees that exactly one of CONF_WORD and CONF_EXPRESSION are set.
|
||||
if (word_command := command.get(CONF_WORD)) is not None:
|
||||
for room_id in command[CONF_ROOMS]:
|
||||
self._word_commands.setdefault(room_id, {})
|
||||
|
||||
@@ -89,6 +89,10 @@ class MatterLight(MatterEntity, LightEntity):
|
||||
colorY=int(matter_xy[1]),
|
||||
# It's required in TLV. We don't implement transition time yet.
|
||||
transitionTime=0,
|
||||
# allow setting the color while the light is off,
|
||||
# by setting the optionsMask to 1 (=ExecuteIfOff)
|
||||
optionsMask=1,
|
||||
optionsOverride=1,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -103,6 +107,10 @@ class MatterLight(MatterEntity, LightEntity):
|
||||
saturation=int(matter_hs[1]),
|
||||
# It's required in TLV. We don't implement transition time yet.
|
||||
transitionTime=0,
|
||||
# allow setting the color while the light is off,
|
||||
# by setting the optionsMask to 1 (=ExecuteIfOff)
|
||||
optionsMask=1,
|
||||
optionsOverride=1,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -114,6 +122,10 @@ class MatterLight(MatterEntity, LightEntity):
|
||||
colorTemperatureMireds=color_temp,
|
||||
# It's required in TLV. We don't implement transition time yet.
|
||||
transitionTime=0,
|
||||
# allow setting the color while the light is off,
|
||||
# by setting the optionsMask to 1 (=ExecuteIfOff)
|
||||
optionsMask=1,
|
||||
optionsOverride=1,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -766,6 +766,19 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Flag media player features that are supported."""
|
||||
return self._attr_supported_features
|
||||
|
||||
@property
|
||||
def supported_features_compat(self) -> MediaPlayerEntityFeature:
|
||||
"""Return the supported features as MediaPlayerEntityFeature.
|
||||
|
||||
Remove this compatibility shim in 2025.1 or later.
|
||||
"""
|
||||
features = self.supported_features
|
||||
if type(features) is int: # noqa: E721
|
||||
new_features = MediaPlayerEntityFeature(features)
|
||||
self._report_deprecated_supported_features_values(new_features)
|
||||
return new_features
|
||||
return features
|
||||
|
||||
def turn_on(self) -> None:
|
||||
"""Turn the media player on."""
|
||||
raise NotImplementedError()
|
||||
@@ -905,85 +918,87 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
@property
|
||||
def support_play(self) -> bool:
|
||||
"""Boolean if play is supported."""
|
||||
return MediaPlayerEntityFeature.PLAY in self.supported_features
|
||||
return MediaPlayerEntityFeature.PLAY in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_pause(self) -> bool:
|
||||
"""Boolean if pause is supported."""
|
||||
return MediaPlayerEntityFeature.PAUSE in self.supported_features
|
||||
return MediaPlayerEntityFeature.PAUSE in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_stop(self) -> bool:
|
||||
"""Boolean if stop is supported."""
|
||||
return MediaPlayerEntityFeature.STOP in self.supported_features
|
||||
return MediaPlayerEntityFeature.STOP in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_seek(self) -> bool:
|
||||
"""Boolean if seek is supported."""
|
||||
return MediaPlayerEntityFeature.SEEK in self.supported_features
|
||||
return MediaPlayerEntityFeature.SEEK in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_volume_set(self) -> bool:
|
||||
"""Boolean if setting volume is supported."""
|
||||
return MediaPlayerEntityFeature.VOLUME_SET in self.supported_features
|
||||
return MediaPlayerEntityFeature.VOLUME_SET in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_volume_mute(self) -> bool:
|
||||
"""Boolean if muting volume is supported."""
|
||||
return MediaPlayerEntityFeature.VOLUME_MUTE in self.supported_features
|
||||
return MediaPlayerEntityFeature.VOLUME_MUTE in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_previous_track(self) -> bool:
|
||||
"""Boolean if previous track command supported."""
|
||||
return MediaPlayerEntityFeature.PREVIOUS_TRACK in self.supported_features
|
||||
return MediaPlayerEntityFeature.PREVIOUS_TRACK in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_next_track(self) -> bool:
|
||||
"""Boolean if next track command supported."""
|
||||
return MediaPlayerEntityFeature.NEXT_TRACK in self.supported_features
|
||||
return MediaPlayerEntityFeature.NEXT_TRACK in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_play_media(self) -> bool:
|
||||
"""Boolean if play media command supported."""
|
||||
return MediaPlayerEntityFeature.PLAY_MEDIA in self.supported_features
|
||||
return MediaPlayerEntityFeature.PLAY_MEDIA in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_select_source(self) -> bool:
|
||||
"""Boolean if select source command supported."""
|
||||
return MediaPlayerEntityFeature.SELECT_SOURCE in self.supported_features
|
||||
return MediaPlayerEntityFeature.SELECT_SOURCE in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_select_sound_mode(self) -> bool:
|
||||
"""Boolean if select sound mode command supported."""
|
||||
return MediaPlayerEntityFeature.SELECT_SOUND_MODE in self.supported_features
|
||||
return (
|
||||
MediaPlayerEntityFeature.SELECT_SOUND_MODE in self.supported_features_compat
|
||||
)
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_clear_playlist(self) -> bool:
|
||||
"""Boolean if clear playlist command supported."""
|
||||
return MediaPlayerEntityFeature.CLEAR_PLAYLIST in self.supported_features
|
||||
return MediaPlayerEntityFeature.CLEAR_PLAYLIST in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_shuffle_set(self) -> bool:
|
||||
"""Boolean if shuffle is supported."""
|
||||
return MediaPlayerEntityFeature.SHUFFLE_SET in self.supported_features
|
||||
return MediaPlayerEntityFeature.SHUFFLE_SET in self.supported_features_compat
|
||||
|
||||
@final
|
||||
@property
|
||||
def support_grouping(self) -> bool:
|
||||
"""Boolean if player grouping is supported."""
|
||||
return MediaPlayerEntityFeature.GROUPING in self.supported_features
|
||||
return MediaPlayerEntityFeature.GROUPING in self.supported_features_compat
|
||||
|
||||
async def async_toggle(self) -> None:
|
||||
"""Toggle the power on the media player."""
|
||||
@@ -1012,7 +1027,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
if (
|
||||
self.volume_level is not None
|
||||
and self.volume_level < 1
|
||||
and MediaPlayerEntityFeature.VOLUME_SET in self.supported_features
|
||||
and MediaPlayerEntityFeature.VOLUME_SET in self.supported_features_compat
|
||||
):
|
||||
await self.async_set_volume_level(
|
||||
min(1, self.volume_level + self.volume_step)
|
||||
@@ -1030,7 +1045,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
if (
|
||||
self.volume_level is not None
|
||||
and self.volume_level > 0
|
||||
and MediaPlayerEntityFeature.VOLUME_SET in self.supported_features
|
||||
and MediaPlayerEntityFeature.VOLUME_SET in self.supported_features_compat
|
||||
):
|
||||
await self.async_set_volume_level(
|
||||
max(0, self.volume_level - self.volume_step)
|
||||
@@ -1073,7 +1088,7 @@ class MediaPlayerEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def capability_attributes(self) -> dict[str, Any]:
|
||||
"""Return capability attributes."""
|
||||
data: dict[str, Any] = {}
|
||||
supported_features = self.supported_features
|
||||
supported_features = self.supported_features_compat
|
||||
|
||||
if (
|
||||
source_list := self.source_list
|
||||
@@ -1280,7 +1295,7 @@ async def websocket_browse_media(
|
||||
connection.send_error(msg["id"], "entity_not_found", "Entity not found")
|
||||
return
|
||||
|
||||
if MediaPlayerEntityFeature.BROWSE_MEDIA not in player.supported_features:
|
||||
if MediaPlayerEntityFeature.BROWSE_MEDIA not in player.supported_features_compat:
|
||||
connection.send_message(
|
||||
websocket_api.error_message(
|
||||
msg["id"], ERR_NOT_SUPPORTED, "Player does not support browsing media"
|
||||
|
||||
@@ -11,7 +11,6 @@ from homeassistant.helpers.significant_change import (
|
||||
|
||||
from . import (
|
||||
ATTR_ENTITY_PICTURE_LOCAL,
|
||||
ATTR_GROUP_MEMBERS,
|
||||
ATTR_MEDIA_POSITION,
|
||||
ATTR_MEDIA_POSITION_UPDATED_AT,
|
||||
ATTR_MEDIA_VOLUME_LEVEL,
|
||||
@@ -25,9 +24,8 @@ INSIGNIFICANT_ATTRIBUTES: set[str] = {
|
||||
|
||||
SIGNIFICANT_ATTRIBUTES: set[str] = {
|
||||
ATTR_ENTITY_PICTURE_LOCAL,
|
||||
ATTR_GROUP_MEMBERS,
|
||||
*ATTR_TO_PROPERTY,
|
||||
}
|
||||
} - INSIGNIFICANT_ATTRIBUTES
|
||||
|
||||
|
||||
@callback
|
||||
@@ -44,18 +42,10 @@ def async_check_significant_change(
|
||||
return True
|
||||
|
||||
old_attrs_s = set(
|
||||
{
|
||||
k: v
|
||||
for k, v in old_attrs.items()
|
||||
if k in SIGNIFICANT_ATTRIBUTES - INSIGNIFICANT_ATTRIBUTES
|
||||
}.items()
|
||||
{k: v for k, v in old_attrs.items() if k in SIGNIFICANT_ATTRIBUTES}.items()
|
||||
)
|
||||
new_attrs_s = set(
|
||||
{
|
||||
k: v
|
||||
for k, v in new_attrs.items()
|
||||
if k in SIGNIFICANT_ATTRIBUTES - INSIGNIFICANT_ATTRIBUTES
|
||||
}.items()
|
||||
{k: v for k, v in new_attrs.items() if k in SIGNIFICANT_ATTRIBUTES}.items()
|
||||
)
|
||||
changed_attrs: set[str] = {item[0] for item in old_attrs_s ^ new_attrs_s}
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Coroutine
|
||||
import json
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
from aiohttp.hdrs import CONTENT_TYPE
|
||||
@@ -267,11 +269,11 @@ class MicrosoftFace:
|
||||
"""Store group/person data and IDs."""
|
||||
return self._store
|
||||
|
||||
async def update_store(self):
|
||||
async def update_store(self) -> None:
|
||||
"""Load all group/person data into local store."""
|
||||
groups = await self.call_api("get", "persongroups")
|
||||
|
||||
remove_tasks = []
|
||||
remove_tasks: list[Coroutine[Any, Any, None]] = []
|
||||
new_entities = []
|
||||
for group in groups:
|
||||
g_id = group["personGroupId"]
|
||||
@@ -293,7 +295,7 @@ class MicrosoftFace:
|
||||
self._store[g_id][person["name"]] = person["personId"]
|
||||
|
||||
if remove_tasks:
|
||||
await asyncio.gather(remove_tasks)
|
||||
await asyncio.gather(*remove_tasks)
|
||||
await self._component.async_add_entities(new_entities)
|
||||
|
||||
async def call_api(self, method, function, data=None, binary=False, params=None):
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["dnspython", "mcstatus"],
|
||||
"quality_scale": "gold",
|
||||
"requirements": ["mcstatus==11.0.0"]
|
||||
"requirements": ["mcstatus==11.1.1"]
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ def async_get_schema(
|
||||
|
||||
if show_name:
|
||||
schema = {
|
||||
vol.Optional(CONF_NAME, default=defaults.get(CONF_NAME)): str,
|
||||
vol.Required(CONF_NAME, default=defaults.get(CONF_NAME)): str,
|
||||
**schema,
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ from .const import (
|
||||
)
|
||||
from .helpers import savable_state
|
||||
from .http_api import RegistrationsView
|
||||
from .util import async_create_cloud_hook
|
||||
from .webhook import handle_webhook
|
||||
|
||||
PLATFORMS = [Platform.SENSOR, Platform.BINARY_SENSOR, Platform.DEVICE_TRACKER]
|
||||
@@ -103,26 +104,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
registration_name = f"Mobile App: {registration[ATTR_DEVICE_NAME]}"
|
||||
webhook_register(hass, DOMAIN, registration_name, webhook_id, handle_webhook)
|
||||
|
||||
async def create_cloud_hook() -> None:
|
||||
"""Create a cloud hook."""
|
||||
hook = await cloud.async_create_cloudhook(hass, webhook_id)
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, data={**entry.data, CONF_CLOUDHOOK_URL: hook}
|
||||
)
|
||||
|
||||
async def manage_cloudhook(state: cloud.CloudConnectionState) -> None:
|
||||
if (
|
||||
state is cloud.CloudConnectionState.CLOUD_CONNECTED
|
||||
and CONF_CLOUDHOOK_URL not in entry.data
|
||||
):
|
||||
await create_cloud_hook()
|
||||
await async_create_cloud_hook(hass, webhook_id, entry)
|
||||
|
||||
if (
|
||||
CONF_CLOUDHOOK_URL not in registration
|
||||
CONF_CLOUDHOOK_URL not in entry.data
|
||||
and cloud.async_active_subscription(hass)
|
||||
and cloud.async_is_connected(hass)
|
||||
):
|
||||
await create_cloud_hook()
|
||||
await async_create_cloud_hook(hass, webhook_id, entry)
|
||||
|
||||
entry.async_on_unload(cloud.async_listen_connection_change(hass, manage_cloudhook))
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
@@ -35,6 +35,7 @@ from .const import (
|
||||
SCHEMA_APP_DATA,
|
||||
)
|
||||
from .helpers import supports_encryption
|
||||
from .util import async_create_cloud_hook
|
||||
|
||||
|
||||
class RegistrationsView(HomeAssistantView):
|
||||
@@ -69,8 +70,8 @@ class RegistrationsView(HomeAssistantView):
|
||||
webhook_id = secrets.token_hex()
|
||||
|
||||
if cloud.async_active_subscription(hass):
|
||||
data[CONF_CLOUDHOOK_URL] = await cloud.async_create_cloudhook(
|
||||
hass, webhook_id
|
||||
data[CONF_CLOUDHOOK_URL] = await async_create_cloud_hook(
|
||||
hass, webhook_id, None
|
||||
)
|
||||
|
||||
data[CONF_WEBHOOK_ID] = webhook_id
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
"""Mobile app utility functions."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.components import cloud
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
|
||||
from .const import (
|
||||
@@ -10,6 +13,7 @@ from .const import (
|
||||
ATTR_PUSH_TOKEN,
|
||||
ATTR_PUSH_URL,
|
||||
ATTR_PUSH_WEBSOCKET_CHANNEL,
|
||||
CONF_CLOUDHOOK_URL,
|
||||
DATA_CONFIG_ENTRIES,
|
||||
DATA_DEVICES,
|
||||
DATA_NOTIFY,
|
||||
@@ -53,3 +57,19 @@ def get_notify_service(hass: HomeAssistant, webhook_id: str) -> str | None:
|
||||
return target_service
|
||||
|
||||
return None
|
||||
|
||||
|
||||
_CLOUD_HOOK_LOCK = asyncio.Lock()
|
||||
|
||||
|
||||
async def async_create_cloud_hook(
|
||||
hass: HomeAssistant, webhook_id: str, entry: ConfigEntry | None
|
||||
) -> str:
|
||||
"""Create a cloud hook."""
|
||||
async with _CLOUD_HOOK_LOCK:
|
||||
hook = await cloud.async_get_or_create_cloudhook(hass, webhook_id)
|
||||
if entry:
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, data={**entry.data, CONF_CLOUDHOOK_URL: hook}
|
||||
)
|
||||
return hook
|
||||
|
||||
@@ -190,7 +190,7 @@ BASE_STRUCT_SCHEMA = BASE_COMPONENT_SCHEMA.extend(
|
||||
vol.Optional(CONF_STRUCTURE): cv.string,
|
||||
vol.Optional(CONF_SCALE, default=1): number_validator,
|
||||
vol.Optional(CONF_OFFSET, default=0): number_validator,
|
||||
vol.Optional(CONF_PRECISION, default=0): cv.positive_int,
|
||||
vol.Optional(CONF_PRECISION): cv.positive_int,
|
||||
vol.Optional(
|
||||
CONF_SWAP,
|
||||
): vol.In(
|
||||
|
||||
@@ -185,10 +185,8 @@ class BaseStructPlatform(BasePlatform, RestoreEntity):
|
||||
self._swap = config[CONF_SWAP]
|
||||
self._data_type = config[CONF_DATA_TYPE]
|
||||
self._structure: str = config[CONF_STRUCTURE]
|
||||
self._precision = config[CONF_PRECISION]
|
||||
self._scale = config[CONF_SCALE]
|
||||
if self._scale < 1 and not self._precision:
|
||||
self._precision = 2
|
||||
self._precision = config.get(CONF_PRECISION, 2 if self._scale < 1 else 0)
|
||||
self._offset = config[CONF_OFFSET]
|
||||
self._slave_count = config.get(CONF_SLAVE_COUNT, None) or config.get(
|
||||
CONF_VIRTUAL_COUNT, 0
|
||||
|
||||
@@ -5,7 +5,7 @@ import logging
|
||||
from socket import timeout
|
||||
from typing import Any
|
||||
|
||||
from motionblinds import ParseException
|
||||
from motionblinds import DEVICE_TYPES_WIFI, ParseException
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
@@ -59,7 +59,9 @@ class DataUpdateCoordinatorMotionBlinds(DataUpdateCoordinator):
|
||||
def update_blind(self, blind):
|
||||
"""Fetch data from a blind."""
|
||||
try:
|
||||
if self._wait_for_push:
|
||||
if blind.device_type in DEVICE_TYPES_WIFI:
|
||||
blind.Update_from_cache()
|
||||
elif self._wait_for_push:
|
||||
blind.Update()
|
||||
else:
|
||||
blind.Update_trigger()
|
||||
|
||||
@@ -70,8 +70,8 @@ MQTT_TEXT_ATTRIBUTES_BLOCKED = frozenset(
|
||||
|
||||
def valid_text_size_configuration(config: ConfigType) -> ConfigType:
|
||||
"""Validate that the text length configuration is valid, throws if it isn't."""
|
||||
if config[CONF_MIN] >= config[CONF_MAX]:
|
||||
raise vol.Invalid("text length min must be >= max")
|
||||
if config[CONF_MIN] > config[CONF_MAX]:
|
||||
raise vol.Invalid("text length min must be <= max")
|
||||
if config[CONF_MAX] > MAX_LENGTH_STATE_STATE:
|
||||
raise vol.Invalid(f"max text length must be <= {MAX_LENGTH_STATE_STATE}")
|
||||
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyatmo"],
|
||||
"requirements": ["pyatmo==8.0.1"]
|
||||
"requirements": ["pyatmo==8.0.2"]
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
hass.data[DATA_HASS_CONFIG] = config
|
||||
|
||||
if lte_config := config.get(DOMAIN):
|
||||
await hass.async_create_task(import_yaml(hass, lte_config))
|
||||
hass.async_create_task(import_yaml(hass, lte_config))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstantEnum,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
@@ -70,10 +71,6 @@ _DEPRECATED_MODE_AUTO: Final = DeprecatedConstantEnum(NumberMode.AUTO, "2025.1")
|
||||
_DEPRECATED_MODE_BOX: Final = DeprecatedConstantEnum(NumberMode.BOX, "2025.1")
|
||||
_DEPRECATED_MODE_SLIDER: Final = DeprecatedConstantEnum(NumberMode.SLIDER, "2025.1")
|
||||
|
||||
# Both can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(dir_with_deprecated_constants, module_globals=globals())
|
||||
|
||||
|
||||
class NumberDeviceClass(StrEnum):
|
||||
"""Device class for numbers."""
|
||||
@@ -481,3 +478,10 @@ DEVICE_CLASS_UNITS: dict[NumberDeviceClass, set[type[StrEnum] | str | None]] = {
|
||||
UNIT_CONVERTERS: dict[str, type[BaseUnitConverter]] = {
|
||||
NumberDeviceClass.TEMPERATURE: TemperatureConverter,
|
||||
}
|
||||
|
||||
# These can be removed if no deprecated constant are in this module anymore
|
||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
||||
__dir__ = partial(
|
||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
||||
)
|
||||
__all__ = all_with_deprecated_constants(globals())
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
import socket
|
||||
from typing import Any
|
||||
|
||||
from opower import (
|
||||
@@ -38,7 +39,7 @@ async def _validate_login(
|
||||
) -> dict[str, str]:
|
||||
"""Validate login data and return any errors."""
|
||||
api = Opower(
|
||||
async_create_clientsession(hass),
|
||||
async_create_clientsession(hass, family=socket.AF_INET),
|
||||
login_data[CONF_UTILITY],
|
||||
login_data[CONF_USERNAME],
|
||||
login_data[CONF_PASSWORD],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Coordinator to handle Opower connections."""
|
||||
from datetime import datetime, timedelta
|
||||
import logging
|
||||
import socket
|
||||
from types import MappingProxyType
|
||||
from typing import Any, cast
|
||||
|
||||
@@ -51,7 +52,7 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, Forecast]]):
|
||||
update_interval=timedelta(hours=12),
|
||||
)
|
||||
self.api = Opower(
|
||||
aiohttp_client.async_get_clientsession(hass),
|
||||
aiohttp_client.async_get_clientsession(hass, family=socket.AF_INET),
|
||||
entry_data[CONF_UTILITY],
|
||||
entry_data[CONF_USERNAME],
|
||||
entry_data[CONF_PASSWORD],
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/orvibo",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["orvibo"],
|
||||
"requirements": ["orvibo==1.1.1"]
|
||||
"requirements": ["orvibo==1.1.2"]
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ async def async_setup_entry(
|
||||
class PingDeviceTracker(CoordinatorEntity[PingUpdateCoordinator], ScannerEntity):
|
||||
"""Representation of a Ping device tracker."""
|
||||
|
||||
_first_offline: datetime | None = None
|
||||
_last_seen: datetime | None = None
|
||||
|
||||
def __init__(
|
||||
self, config_entry: ConfigEntry, coordinator: PingUpdateCoordinator
|
||||
@@ -171,14 +171,12 @@ class PingDeviceTracker(CoordinatorEntity[PingUpdateCoordinator], ScannerEntity)
|
||||
def is_connected(self) -> bool:
|
||||
"""Return true if ping returns is_alive or considered home."""
|
||||
if self.coordinator.data.is_alive:
|
||||
self._first_offline = None
|
||||
return True
|
||||
self._last_seen = dt_util.utcnow()
|
||||
|
||||
now = dt_util.utcnow()
|
||||
if self._first_offline is None:
|
||||
self._first_offline = now
|
||||
|
||||
return (self._first_offline + self._consider_home_interval) > now
|
||||
return (
|
||||
self._last_seen is not None
|
||||
and (dt_util.utcnow() - self._last_seen) < self._consider_home_interval
|
||||
)
|
||||
|
||||
@property
|
||||
def entity_registry_enabled_default(self) -> bool:
|
||||
|
||||
@@ -165,6 +165,13 @@ def count_torrents_in_states(
|
||||
coordinator: QBittorrentDataCoordinator, states: list[str]
|
||||
) -> int:
|
||||
"""Count the number of torrents in specified states."""
|
||||
# When torrents are not in the returned data, there are none, return 0.
|
||||
if "torrents" not in coordinator.data:
|
||||
return 0
|
||||
|
||||
if not states:
|
||||
return len(coordinator.data["torrents"])
|
||||
|
||||
return len(
|
||||
[
|
||||
torrent
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user