Compare commits

..

88 Commits

Author SHA1 Message Date
Franck Nijhof 24a8a512d6 2024.1.1 (#107239) 2024-01-05 16:25:49 +01:00
Franck Nijhof 658f1cf5c5 Bump version to 2024.1.1 2024-01-05 13:01:35 +01:00
Raman Gupta d012817190 Bump zwave-js-server-python to 0.55.3 (#107225)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2024-01-05 13:01:06 +01:00
Joakim Sørensen 056701d218 Use supported_features_compat in update.install service (#107224) 2024-01-05 13:01:03 +01:00
Petru Paler c3963b26e7 Fix entity property cache creation arguments (#107221) 2024-01-05 13:00:59 +01:00
tronikos 4ade5e46d9 Disable IPv6 in the opower integration to fix AEP utilities (#107203) 2024-01-05 13:00:55 +01:00
Brett Adams c242dcd1f2 Hotfix cache logic bug in Tessie (#107187) 2024-01-05 13:00:52 +01:00
Michael 4e126d68b7 Fix switch states in AVM FRITZ!Box Tools (#107183) 2024-01-05 13:00:49 +01:00
Erwin Douna d7e1a4fa20 Bump to PyTado 0.17.3 (#107181) 2024-01-05 13:00:46 +01:00
J. Nick Koston 3215dfee6d Bump aiohomekit to 3.1.2 (#107177) 2024-01-05 13:00:43 +01:00
Matt Emerick-Law c78d691d30 Bump Orvibo to 1.1.2 (#107162)
* Bump python-orvibo version

Fixes https://github.com/home-assistant/core/issues/106923

* Add version number

* Remove version

* Bump python-orvibo version
2024-01-05 13:00:39 +01:00
Bram Kragten 04bf569308 Update frontend to 20240104.0 (#107155) 2024-01-05 13:00:36 +01:00
Erik Montnemery b8576b8091 Include deprecated constants in wildcard imports (#107114) 2024-01-05 13:00:33 +01:00
J. Nick Koston a7aa5c0e52 Bump habluetooth to 2.0.2 (#107097) 2024-01-05 13:00:29 +01:00
J. Nick Koston d600b76801 Fix missing backwards compatibility layer for water_heater supported_features (#107091) 2024-01-05 13:00:26 +01:00
Erik Montnemery c56d118e8b Deduplicate handling of duplicated constants (#107074)
* Deduplicate handling of duplicated constants

* Use DeprecatedConstant + DeprecatedConstantEnum

* Fixup

* Remove test cases with unnamed tuples
2024-01-05 13:00:23 +01:00
Robert Resch 80b45edb2e Fix mobile_app cloudhook creation (#107068) 2024-01-05 13:00:20 +01:00
Joost Lekkerkerker 5529a85a2b Fix data access in streamlabs water (#107062)
* Fix data access in streamlabs water

* Fix data access in streamlabs water
2024-01-05 13:00:17 +01:00
Joost Lekkerkerker e2acc70128 Use async_register in streamlabswater (#107060) 2024-01-05 13:00:14 +01:00
J. Nick Koston 427077a4c9 Fix missing backwards compatiblity layer for humidifier supported_features (#107026)
fixes #107018
2024-01-05 13:00:10 +01:00
Maciej Bieniek 8c9875c3cc Get Shelly RPC device gen from config entry data (#107019)
Use gen from config entry data
2024-01-05 13:00:07 +01:00
Sid ce5455fefc Bump openwebifpy to 4.0.4 (#107000) 2024-01-05 13:00:04 +01:00
Ståle Storø Hauknes aef129afaf Close stale connections (Airthings BLE) (#106748)
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-01-05 13:00:01 +01:00
Sid 1c94a94ba2 bump openwebifpy to 4.0.3 (#106593) 2024-01-05 12:59:56 +01:00
Patrick Frazer d87baba96f Bump dropmqttapi to 1.0.2 (#106978) 2024-01-05 12:57:58 +01:00
Joe Neuman 5a0997bac0 Fix qBittorrent torrent count when empty (#106903)
* Fix qbittorrent torrent cound when empty

* lint fix

* Change based on comment
2024-01-05 12:57:55 +01:00
Marc Mueller e70af204ee Enable strict typing for airthings_ble (#106815) 2024-01-05 12:57:50 +01:00
Franck Nijhof fb0cc6c5d0 2024.1.0 (#106970) 2024-01-03 18:15:49 +01:00
Franck Nijhof 15cecbd4a4 Bump version to 2024.1.0 2024-01-03 17:13:22 +01:00
Franck Nijhof 8cf47c4925 Bump version to 2024.1.0b8 2024-01-03 15:29:59 +01:00
Bram Kragten cd8d95a04d Update frontend to 20240103.3 (#106963) 2024-01-03 15:29:51 +01:00
Jonas Fors Lellky 015752ff11 Set precision to halves in flexit_bacnet (#106959)
flexit_bacnet: set precision to halves for target temperature
2024-01-03 15:29:48 +01:00
jan iversen 9d697c5026 Only set precision in modbus if not configured. (#106952)
Only set precision if not configured.
2024-01-03 15:29:42 +01:00
Bram Kragten 4595c3edaa Update frontend to 20240103.1 (#106948) 2024-01-03 15:29:38 +01:00
Robert Resch e745542431 Fix creating cloud hook twice for mobile_app (#106945) 2024-01-03 15:29:33 +01:00
Franck Nijhof 2be72fd891 Bump version to 2024.1.0b7 2024-01-03 11:35:43 +01:00
Bram Kragten 2b43f5fcda Update frontend to 20240103.0 (#106942) 2024-01-03 11:35:35 +01:00
Erwin Douna 3295722e70 Change Tado deprecation version to 2024.7.0 (#106938)
Change version to 2024.7.0
2024-01-03 11:35:30 +01:00
Franck Nijhof f98bbf88b1 Bump version to 2024.1.0b6 2024-01-03 09:56:28 +01:00
Michael 0226b3f10c Remove group_members from significant attributes in media player (#106916) 2024-01-03 09:55:55 +01:00
Jan-Philipp Benecke 5986967db7 Avoid triggering ping device tracker home after restore (#106913) 2024-01-03 09:55:52 +01:00
Michael Hansen 95ef2dd7f9 Bump intents to 2024.1.2 (#106909) 2024-01-03 09:55:49 +01:00
Erwin Douna 527d9fbb6b Add try-catch for invalid auth to Tado (#106774)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2024-01-03 09:55:45 +01:00
Michael 5eb1073b4a Apply late review comments on media player (#106727) 2024-01-03 09:55:41 +01:00
Franck Nijhof 77cdc10883 Bump version to 2024.1.0b5 2024-01-02 20:59:49 +01:00
Bram Kragten 5100ba252f Update frontend to 20240102.0 (#106898) 2024-01-02 20:59:40 +01:00
Allen Porter b5b8bc3102 Improve To-do service error handling (#106886) 2024-01-02 20:59:37 +01:00
Allen Porter 6f18a29241 Improve fitbit authentication error handling (#106885) 2024-01-02 20:59:34 +01:00
David F. Mulcahey 596f855eab Bump Zigpy to 0.60.4 (#106870) 2024-01-02 20:59:31 +01:00
J. Nick Koston 5877fe135c Close stale connections in yalexs_ble to ensure setup can proceed (#106842) 2024-01-02 20:59:28 +01:00
J. Nick Koston 26cf30fc3a Update switchbot to use close_stale_connections_by_address (#106835) 2024-01-02 20:59:25 +01:00
Sid 3419b8d082 Move urllib3 constraint to pyproject.toml (#106768) 2024-01-02 20:59:22 +01:00
Robert Groot 35fc26457b Changed setup of EnergyZero services (#106224)
* Changed setup of energyzero services

* PR review updates

* Dict access instead of get

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Added tests for unloaded state

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2024-01-02 20:59:18 +01:00
Franck Nijhof fc66dead64 Bump version to 2024.1.0b4 2024-01-02 12:59:23 +01:00
Erik Montnemery 056b06de13 Don't use entity_id in __repr__ of not added entity (#106861) 2024-01-02 12:59:11 +01:00
Matt Zimmerman e604bc8c9b Map missing preset mapping for heat mode "ready" in smarttub (#106856) 2024-01-02 12:59:08 +01:00
J. Nick Koston 59bed57d48 Fix incorrect state in Yale Access Bluetooth when lock status is unknown (#106851) 2024-01-02 12:59:05 +01:00
J. Nick Koston 8c25e2610e Bump yalexs-ble to 2.4.0 (#106834) 2024-01-02 12:59:01 +01:00
J. Nick Koston 38b8a1f95d Bump pySwitchbot to 0.43.0 (#106833) 2024-01-02 12:58:58 +01:00
J. Nick Koston 54a87cf047 Bump bleak-retry-connector to 3.4.0 (#106831) 2024-01-02 12:58:55 +01:00
Bram Kragten 448e98eac5 Update frontend to 20240101.0 (#106808) 2024-01-02 12:58:52 +01:00
Maikel Punie 6ca3c7a673 Bump pyduotecno to 2024.1.1 (#106801)
* Bump pyduotecno to 2024.0.1

* Bump pyduotecno to 2024.1.0

* small update
2024-01-02 12:58:49 +01:00
J. Nick Koston b1a55e9b19 Fix emulated_hue brightness check (#106783) 2024-01-02 12:58:46 +01:00
David Knowles 16d3d88fa3 Bump pyschlage to 2023.12.1 (#106782) 2024-01-02 12:58:43 +01:00
Christopher Bailey 39960caf36 Bump pyunifiprotect to v4.22.5 (#106781) 2024-01-02 12:58:40 +01:00
Benjamin Richter e6d2721d1b Fix fints account type check (#106082) 2024-01-02 12:58:37 +01:00
David Knowles fedb63720c Fix Hydrawise data not refreshing (#105923)
Co-authored-by: Robert Resch <robert@resch.dev>
2024-01-02 12:58:34 +01:00
Luke Lashley 77286e8f59 Constrain dacite to at least 1.7.0 (#105709) 2024-01-02 12:58:31 +01:00
Franck Nijhof a7d11120fa Bump version to 2024.1.0b3 2023-12-31 18:57:11 +01:00
Tobias Sauerwein c06df1957f Bump pyatmo to v8.0.2 (#106758) 2023-12-31 18:56:59 +01:00
puddly 99d575261d Bump ZHA dependencies (#106756)
* Bump ZHA dependencies

* Revert "Remove bellows thread, as it has been removed upstream"

This reverts commit c28053f4bf2539eb6150d35af19687610aaeac5e.
2023-12-31 18:56:57 +01:00
J. Nick Koston 3dca39d0f9 Bump habluetooth to 2.0.1 (#106750)
fixes switching scanners to quickly since the manager failed
to account for jitter in the auto discovered advertising interval

replaces and closes #96531

changelog: https://github.com/Bluetooth-Devices/habluetooth/compare/v2.0.0...v2.0.1
2023-12-31 18:56:53 +01:00
J. Nick Koston a11fd2aaa6 Bump pyunifiprotect to 4.22.4 (#106749)
changelog: https://github.com/AngellusMortis/pyunifiprotect/compare/v4.22.3...v4.22.4
2023-12-31 18:56:51 +01:00
starkillerOG 05768f5fbd Bump reolink_aio to 0.8.5 (#106747) 2023-12-31 18:56:48 +01:00
David F. Mulcahey 3d75603b4f Fix Zlinky energy polling in ZHA (#106738) 2023-12-31 18:56:44 +01:00
Franck Nijhof 2179d4de3d Add missing vacuum toggle service description (#106729) 2023-12-31 18:56:41 +01:00
J. Nick Koston 2255f6737c Pin lxml to 4.9.4 (#106694) 2023-12-31 18:56:38 +01:00
J. Nick Koston 456cb20fcd Fix missed cached_property for hvac_mode in climate (#106692) 2023-12-31 18:56:35 +01:00
Keilin Bickar 8dfbe6849e Bump asyncsleepiq to v1.4.1 (#106682)
Update asyncsleepiq to v1.4.1
2023-12-31 18:56:32 +01:00
J. Nick Koston 3dd998b622 Bump roombapy to 1.6.10 (#106678)
changelog: https://github.com/pschmitt/roombapy/compare/1.6.8...1.6.10

fixes #105323
2023-12-31 18:56:29 +01:00
J. Nick Koston 84da1638e8 Bump thermobeacon-ble to 0.6.2 (#106676)
changelog: https://github.com/Bluetooth-Devices/thermobeacon-ble/compare/v0.6.0...v0.6.2
2023-12-31 18:56:26 +01:00
Erik Montnemery 362e5ca09a Fix changed_variables in automation traces (#106665) 2023-12-31 18:56:23 +01:00
G Johansson 494dd2ef07 Handle no permission for disks in Systemmonitor (#106653) 2023-12-31 18:56:20 +01:00
G Johansson 767c55fbac Use set instead of list in Systemmonitor (#106650) 2023-12-31 18:56:17 +01:00
Erik Montnemery 5f3389b8e4 Fix yolink entity descriptions (#106649) 2023-12-31 18:56:14 +01:00
Jaroslav Hanslík c1e37a4cc3 Fixed native apparent temperature in WeatherEntity (#106645) 2023-12-31 18:56:11 +01:00
Jirka 3cd5f0568a Fix typo in Blink strings (#106641)
Update strings.json

Fixed typo.
2023-12-31 18:56:08 +01:00
Erik Montnemery f9150b78b3 Ensure it's safe to call Entity.__repr__ on non added entity (#106032) 2023-12-31 18:56:04 +01:00
174 changed files with 1879 additions and 745 deletions
+1
View File
@@ -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.*
@@ -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)
@@ -249,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())
@@ -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())
+1 -1
View File
@@ -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-data-tools==1.19.0",
"dbus-fast==2.21.0",
"habluetooth==2.0.0"
"habluetooth==2.0.2"
]
}
+9 -4
View File
@@ -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"
@@ -1082,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())
+6 -2
View File
@@ -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())
+13 -7
View File
@@ -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",
@@ -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
@@ -733,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())
+6 -2
View File
@@ -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."""
@@ -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"]
}
+9 -4
View File
@@ -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"
@@ -493,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"]
}
+1 -1
View File
@@ -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.0.4"]
}
+9 -4
View File
@@ -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"
@@ -471,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())
+4 -4
View File
@@ -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
@@ -65,7 +65,7 @@ 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
def __init__(self, device: FlexitBACnet) -> None:
+1
View File
@@ -1063,6 +1063,7 @@ class SwitchInfo(TypedDict):
type: str
callback_update: Callable
callback_switch: Callable
init_state: bool
class FritzBoxBaseEntity:
+10 -11
View File
@@ -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"]
}
@@ -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.2"],
"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
@@ -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
@@ -293,3 +288,13 @@ class HumidifierEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_AT
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())
+6 -2
View File
@@ -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()
+1 -1
View File
@@ -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
)
+1 -1
View File
@@ -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
)
+9 -4
View File
@@ -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
@@ -315,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())
@@ -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}
@@ -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
+1 -1
View File
@@ -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
@@ -12,5 +12,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["pyatmo"],
"requirements": ["pyatmo==8.0.1"]
"requirements": ["pyatmo==8.0.2"]
}
+8 -4
View File
@@ -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,10 @@ 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"])
+9 -4
View File
@@ -27,6 +27,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,
)
@@ -92,10 +93,6 @@ _DEPRECATED_SUPPORT_ACTIVITY = DeprecatedConstantEnum(
)
# 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())
REMOTE_SERVICE_ACTIVITY_SCHEMA = make_entity_service_schema(
{vol.Optional(ATTR_ACTIVITY): cv.string}
)
@@ -262,3 +259,11 @@ class RemoteEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_)
await self.hass.async_add_executor_job(
ft.partial(self.delete_command, **kwargs)
)
# 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())
@@ -18,5 +18,5 @@
"documentation": "https://www.home-assistant.io/integrations/reolink",
"iot_class": "local_push",
"loggers": ["reolink_aio"],
"requirements": ["reolink-aio==0.8.4"]
"requirements": ["reolink-aio==0.8.5"]
}
@@ -5,6 +5,8 @@ from __future__ import annotations
import datetime as dt
import logging
from reolink_aio.enums import VodRequestType
from homeassistant.components.camera import DOMAIN as CAM_DOMAIN, DynamicStreamSettings
from homeassistant.components.media_player import MediaClass, MediaType
from homeassistant.components.media_source.error import Unresolvable
@@ -56,7 +58,14 @@ class ReolinkVODMediaSource(MediaSource):
channel = int(channel_str)
host = self.data[config_entry_id].host
mime_type, url = await host.api.get_vod_source(channel, filename, stream_res)
vod_type = VodRequestType.RTMP
if host.api.is_nvr:
vod_type = VodRequestType.FLV
mime_type, url = await host.api.get_vod_source(
channel, filename, stream_res, vod_type
)
if _LOGGER.isEnabledFor(logging.DEBUG):
url_log = f"{url.split('&user=')[0]}&user=xxxxx&password=xxxxx"
_LOGGER.debug(
@@ -24,7 +24,7 @@
"documentation": "https://www.home-assistant.io/integrations/roomba",
"iot_class": "local_push",
"loggers": ["paho_mqtt", "roombapy"],
"requirements": ["roombapy==1.6.8"],
"requirements": ["roombapy==1.6.10"],
"zeroconf": [
{
"type": "_amzn-alexa._tcp.local.",
@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/schlage",
"iot_class": "cloud_polling",
"requirements": ["pyschlage==2023.12.0"]
"requirements": ["pyschlage==2023.12.1"]
}
@@ -6,5 +6,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/scrape",
"iot_class": "cloud_polling",
"requirements": ["beautifulsoup4==4.12.2", "lxml==4.9.3"]
"requirements": ["beautifulsoup4==4.12.2", "lxml==4.9.4"]
}
+11 -17
View File
@@ -59,6 +59,7 @@ from homeassistant.helpers.config_validation import (
PLATFORM_SCHEMA_BASE,
)
from homeassistant.helpers.deprecation import (
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
@@ -120,12 +121,6 @@ __all__ = [
"SensorStateClass",
]
# 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
@@ -734,17 +729,6 @@ class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
return value
def __repr__(self) -> str:
"""Return the representation.
Entity.__repr__ includes the state in the generated string, this fails if we're
called before self.hass is set.
"""
if not self.hass:
return f"<Entity {self.name}>"
return super().__repr__()
def _suggested_precision_or_none(self) -> int | None:
"""Return suggested display precision, or None if not set."""
assert self.registry_entry
@@ -966,3 +950,13 @@ def async_rounded_state(hass: HomeAssistant, entity_id: str, state: State) -> st
value = f"{numerical_value:z.{precision}f}"
return value
# 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())
+8 -4
View File
@@ -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,
)
@@ -468,10 +469,6 @@ _DEPRECATED_STATE_CLASS_TOTAL_INCREASING: Final = DeprecatedConstantEnum(
)
STATE_CLASSES: Final[list[str]] = [cls.value for cls in SensorStateClass]
# 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())
UNIT_CONVERTERS: dict[SensorDeviceClass | str | None, type[BaseUnitConverter]] = {
SensorDeviceClass.ATMOSPHERIC_PRESSURE: PressureConverter,
SensorDeviceClass.CURRENT: ElectricCurrentConverter,
@@ -631,3 +628,10 @@ DEVICE_CLASS_STATE_CLASSES: dict[SensorDeviceClass, set[SensorStateClass]] = {
SensorDeviceClass.WEIGHT: {SensorStateClass.MEASUREMENT},
SensorDeviceClass.WIND_SPEED: {SensorStateClass.MEASUREMENT},
}
# 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())
@@ -25,6 +25,7 @@ from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig
from .const import (
CONF_BLE_SCANNER_MODE,
CONF_GEN,
CONF_SLEEP_PERIOD,
DOMAIN,
LOGGER,
@@ -84,7 +85,7 @@ async def validate_input(
"title": rpc_device.name,
CONF_SLEEP_PERIOD: sleep_period,
"model": rpc_device.shelly.get("model"),
"gen": gen,
CONF_GEN: gen,
}
# Gen1
@@ -99,7 +100,7 @@ async def validate_input(
"title": block_device.name,
CONF_SLEEP_PERIOD: get_block_device_sleep_period(block_device.settings),
"model": block_device.model,
"gen": gen,
CONF_GEN: gen,
}
@@ -153,7 +154,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
**user_input,
CONF_SLEEP_PERIOD: device_info[CONF_SLEEP_PERIOD],
"model": device_info["model"],
"gen": device_info["gen"],
CONF_GEN: device_info[CONF_GEN],
},
)
errors["base"] = "firmware_not_fully_provisioned"
@@ -190,7 +191,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
CONF_HOST: self.host,
CONF_SLEEP_PERIOD: device_info[CONF_SLEEP_PERIOD],
"model": device_info["model"],
"gen": device_info["gen"],
CONF_GEN: device_info[CONF_GEN],
},
)
errors["base"] = "firmware_not_fully_provisioned"
@@ -288,7 +289,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
"host": self.host,
CONF_SLEEP_PERIOD: self.device_info[CONF_SLEEP_PERIOD],
"model": self.device_info["model"],
"gen": self.device_info["gen"],
CONF_GEN: self.device_info[CONF_GEN],
},
)
self._set_confirm_only()
@@ -321,7 +322,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
except (DeviceConnectionError, InvalidAuthError, FirmwareUnsupported):
return self.async_abort(reason="reauth_unsuccessful")
if self.entry.data.get("gen", 1) != 1:
if self.entry.data.get(CONF_GEN, 1) != 1:
user_input[CONF_USERNAME] = "admin"
try:
await validate_input(self.hass, host, info, user_input)
@@ -334,7 +335,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
await self.hass.config_entries.async_reload(self.entry.entry_id)
return self.async_abort(reason="reauth_successful")
if self.entry.data.get("gen", 1) in BLOCK_GENERATIONS:
if self.entry.data.get(CONF_GEN, 1) in BLOCK_GENERATIONS:
schema = {
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
@@ -363,7 +364,7 @@ class ShellyConfigFlow(ConfigFlow, domain=DOMAIN):
def async_supports_options_flow(cls, config_entry: ConfigEntry) -> bool:
"""Return options flow support for this handler."""
return (
config_entry.data.get("gen") in RPC_GENERATIONS
config_entry.data.get(CONF_GEN) in RPC_GENERATIONS
and not config_entry.data.get(CONF_SLEEP_PERIOD)
and config_entry.data.get("model") != MODEL_WALL_DISPLAY
)
+2
View File
@@ -214,3 +214,5 @@ DEVICES_WITHOUT_FIRMWARE_CHANGELOG = (
MODEL_MOTION_2,
MODEL_VALVE,
)
CONF_GEN = "gen"
@@ -33,6 +33,7 @@ from .const import (
ATTR_GENERATION,
BATTERY_DEVICES_WITH_PERMANENT_CONNECTION,
CONF_BLE_SCANNER_MODE,
CONF_GEN,
CONF_SLEEP_PERIOD,
DATA_CONFIG_ENTRY,
DOMAIN,
@@ -135,7 +136,7 @@ class ShellyCoordinatorBase(DataUpdateCoordinator[None], Generic[_DeviceT]):
manufacturer="Shelly",
model=aioshelly.const.MODEL_NAMES.get(self.model, self.model),
sw_version=self.sw_version,
hw_version=f"gen{self.device.gen} ({self.model})",
hw_version=f"gen{self.entry.data[CONF_GEN]} ({self.model})",
configuration_url=f"http://{self.entry.data[CONF_HOST]}",
)
self.device_id = device_entry.id
+3 -2
View File
@@ -34,6 +34,7 @@ from homeassistant.util.dt import utcnow
from .const import (
BASIC_INPUTS_EVENTS_TYPES,
CONF_COAP_PORT,
CONF_GEN,
DEFAULT_COAP_PORT,
DEVICES_WITHOUT_FIRMWARE_CHANGELOG,
DOMAIN,
@@ -281,7 +282,7 @@ def get_info_auth(info: dict[str, Any]) -> bool:
def get_info_gen(info: dict[str, Any]) -> int:
"""Return the device generation from shelly info."""
return int(info.get("gen", 1))
return int(info.get(CONF_GEN, 1))
def get_model_name(info: dict[str, Any]) -> str:
@@ -325,7 +326,7 @@ def get_rpc_entity_name(
def get_device_entry_gen(entry: ConfigEntry) -> int:
"""Return the device generation from config entry."""
return entry.data.get("gen", 1)
return entry.data.get(CONF_GEN, 1)
def get_rpc_key_instances(keys_dict: dict[str, Any], key: str) -> list[str]:
+11 -6
View File
@@ -17,6 +17,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,
)
@@ -53,12 +54,6 @@ TURN_ON_SCHEMA = {
vol.Optional(ATTR_VOLUME_LEVEL): cv.small_float,
}
# 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())
class SirenTurnOnServiceParameters(TypedDict, total=False):
"""Represent possible parameters to siren.turn_on service data dict type."""
@@ -218,3 +213,13 @@ class SirenEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
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())
+6 -2
View File
@@ -6,6 +6,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_SUPPORT_DURATION: Final = DeprecatedConstantEnum(
SirenEntityFeature.DURATION, "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())
@@ -11,5 +11,5 @@
"documentation": "https://www.home-assistant.io/integrations/sleepiq",
"iot_class": "cloud_polling",
"loggers": ["asyncsleepiq"],
"requirements": ["asyncsleepiq==1.4.0"]
"requirements": ["asyncsleepiq==1.4.1"]
}
@@ -23,11 +23,13 @@ from .const import DEFAULT_MAX_TEMP, DEFAULT_MIN_TEMP, DOMAIN, SMARTTUB_CONTROLL
from .entity import SmartTubEntity
PRESET_DAY = "day"
PRESET_READY = "ready"
PRESET_MODES = {
Spa.HeatMode.AUTO: PRESET_NONE,
Spa.HeatMode.ECONOMY: PRESET_ECO,
Spa.HeatMode.DAY: PRESET_DAY,
Spa.HeatMode.READY: PRESET_READY,
}
HEAT_MODES = {v: k for k, v in PRESET_MODES.items()}
@@ -112,7 +112,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
client.update_location(location_id, away_mode)
hass.services.register(
hass.services.async_register(
DOMAIN, SERVICE_SET_AWAY_MODE, set_away_mode, schema=SET_AWAY_MODE_SCHEMA
)
@@ -44,7 +44,7 @@ class StreamlabsCoordinator(DataUpdateCoordinator[dict[str, StreamlabsData]]):
def _update_data(self) -> dict[str, StreamlabsData]:
locations = self.client.get_locations()
res = {}
for location in locations:
for location in locations["locations"]:
location_id = location["locationId"]
water_usage = self.client.get_water_usage_summary(location_id)
res[location_id] = StreamlabsData(
@@ -27,7 +27,7 @@ async def async_setup_entry(
entities = []
for location_id in coordinator.data.values():
for location_id in coordinator.data:
entities.extend(
[
StreamLabsDailyUsage(coordinator, location_id),
+9 -4
View File
@@ -23,6 +23,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,
)
@@ -66,10 +67,6 @@ _DEPRECATED_DEVICE_CLASS_SWITCH = DeprecatedConstantEnum(
SwitchDeviceClass.SWITCH, "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
@@ -133,3 +130,11 @@ class SwitchEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_)
if hasattr(self, "entity_description"):
return self.entity_description.device_class
return None
# 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())
@@ -98,6 +98,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# connectable means we can make connections to the device
connectable = switchbot_model in CONNECTABLE_SUPPORTED_MODEL_TYPES
address: str = entry.data[CONF_ADDRESS]
await switchbot.close_stale_connections_by_address(address)
ble_device = bluetooth.async_ble_device_from_address(
hass, address.upper(), connectable
)
@@ -106,7 +109,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
f"Could not find Switchbot {sensor_type} with address {address}"
)
await switchbot.close_stale_connections(ble_device)
cls = CLASS_BY_DEVICE.get(sensor_type, switchbot.SwitchbotDevice)
if cls is switchbot.SwitchbotLock:
try:
@@ -39,5 +39,5 @@
"documentation": "https://www.home-assistant.io/integrations/switchbot",
"iot_class": "local_push",
"loggers": ["switchbot"],
"requirements": ["PySwitchbot==0.40.1"]
"requirements": ["PySwitchbot==0.43.0"]
}
@@ -86,7 +86,7 @@ async def validate_import_sensor_setup(
async def get_sensor_setup_schema(handler: SchemaCommonFlowHandler) -> vol.Schema:
"""Return process sensor setup schema."""
hass = handler.parent_handler.hass
processes = await hass.async_add_executor_job(get_all_running_processes)
processes = list(await hass.async_add_executor_job(get_all_running_processes))
return vol.Schema(
{
vol.Required(CONF_PROCESS): SelectSelector(
@@ -267,7 +267,7 @@ def check_required_arg(value: Any) -> Any:
return value
def check_legacy_resource(resource: str, resources: list[str]) -> bool:
def check_legacy_resource(resource: str, resources: set[str]) -> bool:
"""Return True if legacy resource was configured."""
# This function to check legacy resources can be removed
# once we are removing the import from YAML
@@ -388,8 +388,8 @@ async def async_setup_entry(
"""Set up System Montor sensors based on a config entry."""
entities = []
sensor_registry: dict[tuple[str, str], SensorData] = {}
legacy_resources: list[str] = entry.options.get("resources", [])
loaded_resources: list[str] = []
legacy_resources: set[str] = set(entry.options.get("resources", []))
loaded_resources: set[str] = set()
disk_arguments = await hass.async_add_executor_job(get_all_disk_mounts)
network_arguments = await hass.async_add_executor_job(get_all_network_interfaces)
cpu_temperature = await hass.async_add_executor_job(_read_cpu_temperature)
@@ -405,7 +405,7 @@ async def async_setup_entry(
is_enabled = check_legacy_resource(
f"{_type}_{argument}", legacy_resources
)
loaded_resources.append(f"{_type}_{argument}")
loaded_resources.add(f"{_type}_{argument}")
entities.append(
SystemMonitorSensor(
sensor_registry,
@@ -425,7 +425,7 @@ async def async_setup_entry(
is_enabled = check_legacy_resource(
f"{_type}_{argument}", legacy_resources
)
loaded_resources.append(f"{_type}_{argument}")
loaded_resources.add(f"{_type}_{argument}")
entities.append(
SystemMonitorSensor(
sensor_registry,
@@ -449,7 +449,7 @@ async def async_setup_entry(
sensor_registry[(_type, argument)] = SensorData(
argument, None, None, None, None
)
loaded_resources.append(f"{_type}_{argument}")
loaded_resources.add(f"{_type}_{argument}")
entities.append(
SystemMonitorSensor(
sensor_registry,
@@ -463,7 +463,7 @@ async def async_setup_entry(
sensor_registry[(_type, "")] = SensorData("", None, None, None, None)
is_enabled = check_legacy_resource(f"{_type}_", legacy_resources)
loaded_resources.append(f"{_type}_")
loaded_resources.add(f"{_type}_")
entities.append(
SystemMonitorSensor(
sensor_registry,
+16 -10
View File
@@ -8,9 +8,9 @@ import psutil
_LOGGER = logging.getLogger(__name__)
def get_all_disk_mounts() -> list[str]:
def get_all_disk_mounts() -> set[str]:
"""Return all disk mount points on system."""
disks: list[str] = []
disks: set[str] = set()
for part in psutil.disk_partitions(all=True):
if os.name == "nt":
if "cdrom" in part.opts or part.fstype == "":
@@ -18,27 +18,33 @@ def get_all_disk_mounts() -> list[str]:
# ENOENT, pop-up a Windows GUI error for a non-ready
# partition or just hang.
continue
usage = psutil.disk_usage(part.mountpoint)
try:
usage = psutil.disk_usage(part.mountpoint)
except PermissionError:
_LOGGER.debug(
"No permission for running user to access %s", part.mountpoint
)
continue
if usage.total > 0 and part.device != "":
disks.append(part.mountpoint)
disks.add(part.mountpoint)
_LOGGER.debug("Adding disks: %s", ", ".join(disks))
return disks
def get_all_network_interfaces() -> list[str]:
def get_all_network_interfaces() -> set[str]:
"""Return all network interfaces on system."""
interfaces: list[str] = []
interfaces: set[str] = set()
for interface, _ in psutil.net_if_addrs().items():
interfaces.append(interface)
interfaces.add(interface)
_LOGGER.debug("Adding interfaces: %s", ", ".join(interfaces))
return interfaces
def get_all_running_processes() -> list[str]:
def get_all_running_processes() -> set[str]:
"""Return all running processes on system."""
processes: list[str] = []
processes: set[str] = set()
for proc in psutil.process_iter(["name"]):
if proc.name() not in processes:
processes.append(proc.name())
processes.add(proc.name())
_LOGGER.debug("Running processes: %s", ", ".join(processes))
return processes
@@ -4,6 +4,7 @@ from __future__ import annotations
import logging
from typing import Any
import PyTado
from PyTado.interface import Tado
import requests.exceptions
import voluptuous as vol
@@ -136,6 +137,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
)
except exceptions.HomeAssistantError:
return self.async_abort(reason="import_failed")
except PyTado.exceptions.TadoWrongCredentialsException:
return self.async_abort(reason="import_failed_invalid_auth")
home_id = validate_result[UNIQUE_ID]
await self.async_set_unique_id(home_id)
@@ -55,12 +55,14 @@ async def async_get_scanner(
translation_key = "import_aborted"
if import_result.get("reason") == "import_failed":
translation_key = "import_failed"
if import_result.get("reason") == "import_failed_invalid_auth":
translation_key = "import_failed_invalid_auth"
async_create_issue(
hass,
DOMAIN,
"deprecated_yaml_import_device_tracker",
breaks_in_ha_version="2024.6.0",
breaks_in_ha_version="2024.7.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key=translation_key,
+1 -1
View File
@@ -14,5 +14,5 @@
},
"iot_class": "cloud_polling",
"loggers": ["PyTado"],
"requirements": ["python-tado==0.17.0"]
"requirements": ["python-tado==0.17.3"]
}
+5 -1
View File
@@ -133,9 +133,13 @@
"title": "Import aborted",
"description": "Configuring the Tado Device Tracker using YAML is being removed.\n The import was aborted, due to an existing config entry being the same as the data being imported in the YAML. Remove the YAML device tracker configuration and restart Home Assistant. Please use the UI to configure Tado."
},
"failed_to_import": {
"import_failed": {
"title": "Failed to import",
"description": "Failed to import the configuration for the Tado Device Tracker. Please use the UI to configure Tado. Don't forget to delete the YAML configuration."
},
"import_failed_invalid_auth": {
"title": "Failed to import, invalid credentials",
"description": "Failed to import the configuration for the Tado Device Tracker, due to invalid credentials. Please fix the YAML configuration and restart Home Assistant. Alternatively you can use the UI to configure Tado. Don't forget to delete the YAML configuration, once the import is successful."
}
}
}
@@ -41,7 +41,6 @@ class TessieStateUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
self.vin = vin
self.session = async_get_clientsession(hass)
self.data = self._flatten(data)
self.did_first_update = False
async def _async_update_data(self) -> dict[str, Any]:
"""Update vehicle data using Tessie API."""
@@ -50,7 +49,7 @@ class TessieStateUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
session=self.session,
api_key=self.api_key,
vin=self.vin,
use_cache=self.did_first_update,
use_cache=False,
)
except ClientResponseError as e:
if e.status == HTTPStatus.UNAUTHORIZED:
@@ -58,7 +57,6 @@ class TessieStateUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
raise ConfigEntryAuthFailed from e
raise e
self.did_first_update = True
if vehicle["state"] == TessieStatus.ONLINE:
# Vehicle is online, all data is fresh
return self._flatten(vehicle)
@@ -42,5 +42,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/thermobeacon",
"iot_class": "local_push",
"requirements": ["thermobeacon-ble==0.6.0"]
"requirements": ["thermobeacon-ble==0.6.2"]
}
+18 -5
View File
@@ -19,7 +19,7 @@ from homeassistant.core import (
SupportsResponse,
callback,
)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import ( # noqa: F401
PLATFORM_SCHEMA,
@@ -106,8 +106,11 @@ def _validate_supported_features(
if desc.service_field not in call_data:
continue
if not supported_features or not supported_features & desc.required_feature:
raise ValueError(
f"Entity does not support setting field '{desc.service_field}'"
raise ServiceValidationError(
f"Entity does not support setting field '{desc.service_field}'",
translation_domain=DOMAIN,
translation_key="update_field_not_supported",
translation_placeholders={"service_field": desc.service_field},
)
@@ -481,7 +484,12 @@ async def _async_update_todo_item(entity: TodoListEntity, call: ServiceCall) ->
item = call.data["item"]
found = _find_by_uid_or_summary(item, entity.todo_items)
if not found:
raise ValueError(f"Unable to find To-do item '{item}'")
raise ServiceValidationError(
f"Unable to find To-do item '{item}'",
translation_domain=DOMAIN,
translation_key="item_not_found",
translation_placeholders={"item": item},
)
_validate_supported_features(entity.supported_features, call.data)
@@ -509,7 +517,12 @@ async def _async_remove_todo_items(entity: TodoListEntity, call: ServiceCall) ->
for item in call.data.get("item", []):
found = _find_by_uid_or_summary(item, entity.todo_items)
if not found or not found.uid:
raise ValueError(f"Unable to find To-do item '{item}")
raise ServiceValidationError(
f"Unable to find To-do item '{item}'",
translation_domain=DOMAIN,
translation_key="item_not_found",
translation_placeholders={"item": item},
)
uids.append(found.uid)
await entity.async_delete_todo_items(uids=uids)
@@ -90,5 +90,13 @@
"completed": "Completed"
}
}
},
"exceptions": {
"item_not_found": {
"message": "Unable to find To-do item: {item}"
},
"update_field_not_supported": {
"message": "Entity does not support setting field: {service_field}"
}
}
}
@@ -643,4 +643,15 @@ class ProtectEventBinarySensor(EventEntityMixin, BinarySensorEntity):
or self._attr_extra_state_attributes != previous_extra_state_attributes
or self._attr_available != previous_available
):
_LOGGER.debug(
"Updating state [%s (%s)] %s (%s, %s) -> %s (%s, %s)",
device.name,
device.mac,
previous_is_on,
previous_available,
previous_extra_state_attributes,
self._attr_is_on,
self._attr_available,
self._attr_extra_state_attributes,
)
self.async_write_ha_state()
@@ -206,4 +206,11 @@ class ProtectButton(ProtectDeviceEntity, ButtonEntity):
previous_available = self._attr_available
self._async_update_device_from_protect(device)
if self._attr_available != previous_available:
_LOGGER.debug(
"Updating state [%s (%s)] %s -> %s",
device.name,
device.mac,
previous_available,
self._attr_available,
)
self.async_write_ha_state()
@@ -228,6 +228,8 @@ class ProtectData:
# trigger updates for camera that the event references
elif isinstance(obj, Event): # type: ignore[unreachable]
if _LOGGER.isEnabledFor(logging.DEBUG):
_LOGGER.debug("event WS msg: %s", obj.dict())
if obj.type in SMART_EVENTS:
if obj.camera is not None:
if obj.end is None:
@@ -41,7 +41,7 @@
"iot_class": "local_push",
"loggers": ["pyunifiprotect", "unifi_discovery"],
"quality_scale": "platinum",
"requirements": ["pyunifiprotect==4.22.3", "unifi-discovery==1.1.7"],
"requirements": ["pyunifiprotect==4.22.5", "unifi-discovery==1.1.7"],
"ssdp": [
{
"manufacturer": "Ubiquiti Networks",
@@ -133,6 +133,17 @@ class ProtectMediaPlayer(ProtectDeviceEntity, MediaPlayerEntity):
or self._attr_volume_level != previous_volume_level
or self._attr_available != previous_available
):
_LOGGER.debug(
"Updating state [%s (%s)] %s (%s, %s) -> %s (%s, %s)",
device.name,
device.mac,
previous_state,
previous_available,
previous_volume_level,
self._attr_state,
self._attr_available,
self._attr_volume_level,
)
self.async_write_ha_state()
async def async_set_volume_level(self, volume: float) -> None:
@@ -3,6 +3,7 @@ from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta
import logging
from pyunifiprotect.data import (
Camera,
@@ -25,6 +26,8 @@ from .entity import ProtectDeviceEntity, async_all_device_entities
from .models import PermRequired, ProtectSetableKeysMixin, T
from .utils import async_dispatch_id as _ufpd
_LOGGER = logging.getLogger(__name__)
@dataclass(frozen=True)
class NumberKeysMixin:
@@ -285,4 +288,13 @@ class ProtectNumbers(ProtectDeviceEntity, NumberEntity):
self._attr_native_value != previous_value
or self._attr_available != previous_available
):
_LOGGER.debug(
"Updating state [%s (%s)] %s (%s) -> %s (%s)",
device.name,
device.mac,
previous_value,
previous_available,
self._attr_native_value,
self._attr_available,
)
self.async_write_ha_state()
@@ -420,4 +420,15 @@ class ProtectSelects(ProtectDeviceEntity, SelectEntity):
or self._attr_options != previous_options
or self._attr_available != previous_available
):
_LOGGER.debug(
"Updating state [%s (%s)] %s (%s, %s) -> %s (%s, %s)",
device.name,
device.mac,
previous_option,
previous_available,
previous_options,
self._attr_current_option,
self._attr_available,
self._attr_options,
)
self.async_write_ha_state()
@@ -730,6 +730,15 @@ class ProtectDeviceSensor(ProtectDeviceEntity, SensorEntity):
self._attr_native_value != previous_value
or self._attr_available != previous_available
):
_LOGGER.debug(
"Updating state [%s (%s)] %s (%s) -> %s (%s)",
device.name,
device.mac,
previous_value,
previous_available,
self._attr_native_value,
self._attr_available,
)
self.async_write_ha_state()
@@ -2,6 +2,7 @@
from __future__ import annotations
from dataclasses import dataclass
import logging
from typing import Any
from pyunifiprotect.data import (
@@ -27,6 +28,7 @@ from .entity import ProtectDeviceEntity, ProtectNVREntity, async_all_device_enti
from .models import PermRequired, ProtectSetableKeysMixin, T
from .utils import async_dispatch_id as _ufpd
_LOGGER = logging.getLogger(__name__)
ATTR_PREV_MIC = "prev_mic_level"
ATTR_PREV_RECORD = "prev_record_mode"
@@ -458,6 +460,15 @@ class ProtectSwitch(ProtectDeviceEntity, SwitchEntity):
self._attr_is_on != previous_is_on
or self._attr_available != previous_available
):
_LOGGER.debug(
"Updating state [%s (%s)] %s (%s) -> %s (%s)",
device.name,
device.mac,
previous_is_on,
previous_available,
self._attr_is_on,
self._attr_available,
)
self.async_write_ha_state()
+2 -2
View File
@@ -140,7 +140,7 @@ async def async_install(entity: UpdateEntity, service_call: ServiceCall) -> None
# If version is specified, but not supported by the entity.
if (
version is not None
and UpdateEntityFeature.SPECIFIC_VERSION not in entity.supported_features
and UpdateEntityFeature.SPECIFIC_VERSION not in entity.supported_features_compat
):
raise HomeAssistantError(
f"Installing a specific version is not supported for {entity.entity_id}"
@@ -149,7 +149,7 @@ async def async_install(entity: UpdateEntity, service_call: ServiceCall) -> None
# If backup is requested, but not supported by the entity.
if (
backup := service_call.data[ATTR_BACKUP]
) and UpdateEntityFeature.BACKUP not in entity.supported_features:
) and UpdateEntityFeature.BACKUP not in entity.supported_features_compat:
raise HomeAssistantError(f"Backup is not supported for {entity.entity_id}")
# Update is already in progress.
@@ -14,6 +14,14 @@ turn_off:
supported_features:
- vacuum.VacuumEntityFeature.TURN_OFF
toggle:
target:
entity:
domain: vacuum
supported_features:
- vacuum.VacuumEntityFeature.TURN_OFF
- vacuum.VacuumEntityFeature.TURN_ON
stop:
target:
entity:

Some files were not shown because too many files have changed in this diff Show More