forked from home-assistant/core
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -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())
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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",
|
||||
@@ -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())
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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"])
|
||||
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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,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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
},
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["PyTado"],
|
||||
"requirements": ["python-tado==0.17.0"]
|
||||
"requirements": ["python-tado==0.17.3"]
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user