Compare commits

...

89 Commits

Author SHA1 Message Date
Paulus Schoutsen
cf259c2278 Expose nevermind intent to LLMs 2024-11-30 04:02:43 +00:00
Josef Zweck
e8ced4fa12 Bump aioacaia to 0.1.10 (#131906) 2024-11-29 22:32:20 -05:00
Josef Zweck
d9cef1e708 Guard against hostname change in lamarzocco discovery (#131873)
* Guard against hostname change in lamarzocco discovery

* switch to abort_entries_match
2024-11-29 22:31:56 -05:00
Marcel van der Veldt
a760786faf Fix media player join action for Music Assistant integration (#131910)
* Fix media player join action for Music Assistant integration

* Add tests for join/unjoin

* add one more test
2024-11-29 22:11:57 -05:00
J. Diego Rodríguez Royo
8c6a24c368 Use HomeAssistant error in the right cases (#131923)
* Use the correct exceptions

* Improved exception strings
2024-11-29 22:11:15 -05:00
Manu
24bd61be3b Add missing state_class in IronOS (#131928)
Add missing state class in IronOS
2024-11-29 22:10:12 -05:00
Matthias Alphart
1abd2209b3 Fix KNX IP Secure tunnelling endpoint selection with keyfile (#131941) 2024-11-30 01:13:52 +01:00
epenet
aa206c7608 Use typed ConfigEntry in discovergy (#131891) 2024-11-29 20:28:18 +01:00
Sid
87020e8945 Bump ruff to 0.8.1 (#131927) 2024-11-29 20:23:57 +01:00
Manu
dd62fb387e Bump pynecil to v1.0.1 (#131935) 2024-11-29 20:23:10 +01:00
Raphael Hehl
c19038ced6 Bump uiprotect to 6.6.4 (#131931) 2024-11-29 12:47:33 -06:00
Jc2k
6144cc26ba Bump aiohomekit to 3.2.7 (#131924) 2024-11-29 11:29:10 -06:00
Allen Porter
920c958ec7 Add runtime_data rule to quality_scale hassfest validation (#131857)
* Add quality scale check for runtime_data

* Linter fixes

* Add developer documentation link

* Update script/hassfest/quality_scale_validation/runtime_data.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Update validation to check explicitly for ConfigEntry.runtime_data

* Update script/hassfest/quality_scale_validation/runtime_data.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Refine check for setting attributes

* Patch with changes from epenet

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-11-29 17:56:26 +01:00
epenet
0fc365a114 Add discovery rule to quality_scale hassfest validation (#131890) 2024-11-29 07:06:38 -08:00
David Knowles
954ac0d288 Ensure Schlage exceptions are translated (#131733) 2024-11-28 20:34:20 -08:00
epenet
28cfa37248 Add unique_config_entry rule to quality_scale hassfest validation (#131878)
* Add unique_config_entry rule to quality_scale hassfest validation

* Improve message
2024-11-28 20:08:43 -08:00
epenet
24f7bae5f2 Add documentation URL to quality_scale hassfest validation (#131879)
* Add documentation URL to quality_scale hassfest validation

* Adjust
2024-11-28 18:32:01 -08:00
Manu
8e12fbff88 Refactor calendars in Habitica (#131020)
* Refactor calendars

* changes
2024-11-28 18:31:38 -08:00
Robert Resch
5c8fb5ec2c Remove deprecated climate constants (#131798)
* Remove deprecated climate constants

* Fix

* Fix

* Fix

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-11-29 00:38:05 +01:00
Robert Resch
a68cf21179 Remove deprecated data entry flow constants (#131800)
* Remove deprecated data entry flow constants

* Fix

* Fix

* Fix

* Fix

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-11-29 00:37:26 +01:00
epenet
d596b4169d Add strict_typing rule to quality_scale hassfest validation (#131877)
* Add strict_typing rule to quality_scale hassfest validation

* Add acaia to .strict-typing
2024-11-28 22:05:34 +01:00
IceBotYT
8b467268df Add data descriptions to Nice G.O. config flow (#131865)
* Add data descriptions to Nice G.O. config flow

* Reference other strings instead
2024-11-28 12:09:01 -08:00
Richard Kroegel
6dd93253c6 Add captcha to BMW ConfigFlow (#131351)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-11-28 21:01:00 +01:00
Joost Lekkerkerker
9db6f0ffc4 Only download translation strings we have defined (#131864) 2024-11-28 20:52:51 +01:00
karwosts
889ac1552b Fix flaky test in history stats (#131869) 2024-11-28 20:51:23 +01:00
Bram Kragten
18db16b82c Update frontend to 20241127.1 (#131855) 2024-11-28 20:50:53 +01:00
Robert Resch
1f9ecfe839 Remove deprecated sensor constants (#131843) 2024-11-28 20:49:49 +01:00
Allen Porter
4d32fe97c3 Use ConfigEntry.runtime_data in Nest (#131871) 2024-11-28 20:45:27 +01:00
rd-blue
8feb6c7e06 Correction of prices update time in Tibber integration (with CLA now) (#131861)
correction of prices update time
2024-11-28 19:58:38 +01:00
Madhan
0b36a6d7f3 Bump PyMetEireann to 2024.11.0 (#131860)
Co-authored-by: Joostlek <joostlek@outlook.com>
2024-11-28 19:48:38 +01:00
epenet
837716b69e Add diagnostics rule to quality_scale hassfest validation (#131859) 2024-11-28 19:42:31 +01:00
Michael
1a9ab07742 Allow empty trigger sentence responses in conversations (#131849)
allow empty trigger sentence responses
2024-11-28 18:30:05 +01:00
Allen Porter
8862c5c4d8 Remove unnecessary hass.data defaults from Rainbird (#131858) 2024-11-28 09:16:58 -08:00
Joost Lekkerkerker
87320609dc Bump pyatv to 0.16.0 (#131852) 2024-11-28 11:04:00 -06:00
epenet
62e788c7da Add config flow rules to quality_scale hassfest validation (#131791)
* Add config flow rules to quality_scale hassfest validation

* Use integration.config_flow property
2024-11-28 17:58:56 +01:00
Erik Montnemery
bbce183faf Deprecate dt_util.utc_to_timestamp (#131787) 2024-11-28 17:00:20 +01:00
Robert Resch
0389800e2a Remove deprecated humidifier constants (#131844) 2024-11-28 16:59:11 +01:00
Robert Resch
0c5c09390c Remove deprecated fan constants (#131845) 2024-11-28 16:56:04 +01:00
Manu
57b099c2aa Add unit translations to Ista EcoTrend integration (#131768) 2024-11-28 16:55:07 +01:00
Robert Resch
ed408eb1a1 Remove deprecated device tracker constants (#131846) 2024-11-28 16:54:23 +01:00
Erik Montnemery
f7d2d06c9b Add comments in homeassistant/components/recorder/migration.py (#131820)
* Add comments in homeassistant/components/recorder/migration.py

* Update homeassistant/components/recorder/migration.py
2024-11-28 16:22:56 +01:00
Manu
3071aa2da1 Use common string for items unit in Bring (#131834) 2024-11-28 14:59:16 +01:00
Joost Lekkerkerker
474544abd8 Make wake word selection part of configuration (#131832) 2024-11-28 13:45:51 +01:00
Joost Lekkerkerker
dc064237ca Bump spotifyaio to 0.8.10 (#131827) 2024-11-28 13:45:10 +01:00
Robert Resch
a0584a0516 Remove deprecated switch constants (#131806)
* Remove deprecated switch constants

* Fix
2024-11-28 13:45:00 +01:00
Norbert Rittel
96dfa0e0cf Remove wrong plural "s" in 'todo.remove_item' action (#131814) 2024-11-28 13:44:40 +01:00
epenet
00d82363fe Delay "Split tests for full run" in CI (#131813)
Adjust split tests requirements in CI
2024-11-28 13:44:02 +01:00
epenet
c4e5b59326 Fix more flaky translation checks (#131824) 2024-11-28 13:41:30 +01:00
Erik Montnemery
d9832f8c3a Rename constant in tests/components/recorder/test_migration_from_schema_32.py (#131819) 2024-11-28 13:26:58 +01:00
epenet
f41bc98fe2 Cleanup deprecated exception in websocket tests (#131808) 2024-11-28 12:40:34 +01:00
Joost Lekkerkerker
3a76bfb857 Remove Spotify featured playlists and categories from media browser (#131758) 2024-11-28 12:34:06 +01:00
epenet
6ce5c89711 Fix group flaky test (#131815) 2024-11-28 12:29:38 +01:00
Franck Nijhof
9d387acb97 Ensure custom integrations are assigned the custom IQS scale (#131795) 2024-11-28 12:25:16 +01:00
Robert Resch
1d09a5bf89 Remove deprecated lock constants (#131812) 2024-11-28 12:21:13 +01:00
Robert Resch
a01e7cd6cf Remove deprecated number constants (#131810) 2024-11-28 12:20:43 +01:00
Robert Resch
3e0326dd66 Remove deprecated siren constants (#131807) 2024-11-28 12:14:43 +01:00
Robert Resch
4d27a32905 Remove deprecated cover constants (#131797) 2024-11-28 12:14:25 +01:00
Robert Resch
c5f68bcc58 Remove deprecated remote constants (#131809) 2024-11-28 12:14:06 +01:00
Robert Resch
3866176e1d Remove deprecated water heater constants (#131805) 2024-11-28 12:13:03 +01:00
Robert Resch
a67045ee6c Remove deprecated home assistant const constants (#131799) 2024-11-28 12:12:37 +01:00
Robert Resch
54ff6feadc Remove deprecated alarm control panel constants (#131790) 2024-11-28 12:11:08 +01:00
Robert Resch
fd14add67b Remove deprecated device registry constants (#131802) 2024-11-28 11:20:44 +01:00
Robert Resch
b28f352902 Remove deprecated binary sensor constants (#131793) 2024-11-28 11:08:18 +01:00
Robert Resch
fb152c7d22 Remove deprecated automation constants (#131792) 2024-11-28 11:07:00 +01:00
Robert Resch
be81fd86d3 Remvove deprecated core constants (#131803) 2024-11-28 11:06:04 +01:00
Robert Resch
28ec8272ee Remove deprecated camera constants (#131796) 2024-11-28 11:05:45 +01:00
Richard Kroegel
717f2ee206 Bump bimmer_connected to 0.17.0 (#131352) 2024-11-28 09:58:16 +01:00
epenet
5972da495a Bump samsungtvws to 2.7.1 (#131784) 2024-11-28 09:18:00 +01:00
Manu
2fcd9be3f2 Set parallel updates in IronOS integration (#131721) 2024-11-28 08:48:15 +01:00
David Knowles
a0ea9a1e83 Store Schlage runtime data in entry.runtime_data (#131731) 2024-11-28 08:29:29 +01:00
David Knowles
a831c37511 Enable strict typing for Schlage (#131734) 2024-11-28 08:29:15 +01:00
Jan Bouwhuis
d26c7a0536 Log warning if via_device reference not exists when creating or updating a device registry entry (#131746) 2024-11-28 08:27:24 +01:00
Manu
4257277086 Add units of measurement to Bring integration (#131763) 2024-11-28 08:13:15 +01:00
Manu
fe2bca51a4 Add translations for units of measurement to Habitica integration (#131761) 2024-11-28 08:12:52 +01:00
Manu
17236a5698 Remove unreachable code in Habitica (#131778) 2024-11-28 08:08:00 +01:00
Joost Lekkerkerker
39c2a529d1 Remove Spotify audio feature sensors (#131754) 2024-11-28 08:07:19 +01:00
TheJulianJES
0f5e0dd4bf Fix Home Connect microwave programs (#131782) 2024-11-28 08:06:31 +01:00
J. Nick Koston
eac6673c2b Bump orjson to 3.10.12 (#131752)
changelog: https://github.com/ijl/orjson/compare/3.10.11...3.10.12
2024-11-28 01:35:49 +01:00
Manu
bf4d6d2029 Fix rounding of attributes in Habitica integration (#131772) 2024-11-28 01:35:23 +01:00
puddly
f61a5b78cc Bump ZHA to 0.0.41 (#131776) 2024-11-28 01:34:57 +01:00
Marcel van der Veldt
cc9a97a5cf Bump music assistant client 1.0.8 (#131739) 2024-11-28 01:34:36 +01:00
J. Nick Koston
cf7acb5ae8 Bump aioesphomeapi to 27.0.3 (#131773) 2024-11-27 15:29:29 -08:00
J. Nick Koston
6edb2c0252 Bump uiprotect to 6.6.3 (#131764) 2024-11-27 15:55:51 -06:00
Josef Zweck
fb4d86196e Bump pylamarzocco to 1.2.12 (#131765) 2024-11-27 15:55:33 -06:00
Josef Zweck
44fc5c7871 Add missing data_description for lamarzocco OptionsFlow (#131708) 2024-11-27 22:37:15 +01:00
Allen Porter
c82e408138 Add a missing rainbird data description (#131740) 2024-11-27 22:36:17 +01:00
Marc Mueller
7110df04e6 Bump version to 2025.1.0dev0 (#131751) 2024-11-27 22:32:56 +01:00
J. Nick Koston
1635074aae Bump aiohttp to 3.11.8 (#131744) 2024-11-27 14:15:44 -06:00
Erik Montnemery
381d5453b1 Improve recorder history queries (#131702)
* Improve recorder history queries

* Remove some comments

* Update StatesManager._oldest_ts when adding pending state

* Update after review

* Improve tests

* Improve post-purge logic

* Avoid calling dt_util.utc_to_timestamp in new code

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2024-11-27 21:12:42 +01:00
221 changed files with 2049 additions and 4361 deletions

View File

@@ -40,7 +40,7 @@ env:
CACHE_VERSION: 11
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 9
HA_SHORT_VERSION: "2024.12"
HA_SHORT_VERSION: "2025.1"
DEFAULT_PYTHON: "3.12"
ALL_PYTHON_VERSIONS: "['3.12', '3.13']"
# 10.3 is the oldest supported version
@@ -819,6 +819,12 @@ jobs:
needs:
- info
- base
- gen-requirements-all
- hassfest
- lint-other
- lint-ruff
- lint-ruff-format
- mypy
name: Split tests for full run
steps:
- name: Install additional OS dependencies

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0
rev: v0.8.1
hooks:
- id: ruff
args:

View File

@@ -41,6 +41,7 @@ homeassistant.util.unit_system
# --- Add components below this line ---
homeassistant.components
homeassistant.components.abode.*
homeassistant.components.acaia.*
homeassistant.components.accuweather.*
homeassistant.components.acer_projector.*
homeassistant.components.acmeda.*
@@ -405,6 +406,7 @@ homeassistant.components.ruuvitag_ble.*
homeassistant.components.samsungtv.*
homeassistant.components.scene.*
homeassistant.components.schedule.*
homeassistant.components.schlage.*
homeassistant.components.scrape.*
homeassistant.components.script.*
homeassistant.components.search.*

View File

@@ -25,5 +25,5 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["aioacaia"],
"requirements": ["aioacaia==0.1.9"]
"requirements": ["aioacaia==0.1.10"]
}

View File

@@ -4,7 +4,6 @@ from __future__ import annotations
import asyncio
from datetime import timedelta
from functools import partial
import logging
from typing import TYPE_CHECKING, Any, Final, final
@@ -27,11 +26,6 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
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,
)
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_platform import EntityPlatform
@@ -39,15 +33,7 @@ from homeassistant.helpers.frame import ReportBehavior, report_usage
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey
from .const import ( # noqa: F401
_DEPRECATED_FORMAT_NUMBER,
_DEPRECATED_FORMAT_TEXT,
_DEPRECATED_SUPPORT_ALARM_ARM_AWAY,
_DEPRECATED_SUPPORT_ALARM_ARM_CUSTOM_BYPASS,
_DEPRECATED_SUPPORT_ALARM_ARM_HOME,
_DEPRECATED_SUPPORT_ALARM_ARM_NIGHT,
_DEPRECATED_SUPPORT_ALARM_ARM_VACATION,
_DEPRECATED_SUPPORT_ALARM_TRIGGER,
from .const import (
ATTR_CHANGED_BY,
ATTR_CODE_ARM_REQUIRED,
DOMAIN,
@@ -412,13 +398,3 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
self._alarm_control_panel_option_default_code = default_code
return
self._alarm_control_panel_option_default_code = None
# 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())

View File

@@ -1,16 +1,8 @@
"""Provides the constants needed for component."""
from enum import IntFlag, StrEnum
from functools import partial
from typing import Final
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
DOMAIN: Final = "alarm_control_panel"
ATTR_CHANGED_BY: Final = "changed_by"
@@ -39,12 +31,6 @@ class CodeFormat(StrEnum):
NUMBER = "number"
# These constants are deprecated as of Home Assistant 2022.5, can be removed in 2025.1
# Please use the CodeFormat enum instead.
_DEPRECATED_FORMAT_TEXT: Final = DeprecatedConstantEnum(CodeFormat.TEXT, "2025.1")
_DEPRECATED_FORMAT_NUMBER: Final = DeprecatedConstantEnum(CodeFormat.NUMBER, "2025.1")
class AlarmControlPanelEntityFeature(IntFlag):
"""Supported features of the alarm control panel entity."""
@@ -56,27 +42,6 @@ class AlarmControlPanelEntityFeature(IntFlag):
ARM_VACATION = 32
# These constants are deprecated as of Home Assistant 2022.5
# Please use the AlarmControlPanelEntityFeature enum instead.
_DEPRECATED_SUPPORT_ALARM_ARM_HOME: Final = DeprecatedConstantEnum(
AlarmControlPanelEntityFeature.ARM_HOME, "2025.1"
)
_DEPRECATED_SUPPORT_ALARM_ARM_AWAY: Final = DeprecatedConstantEnum(
AlarmControlPanelEntityFeature.ARM_AWAY, "2025.1"
)
_DEPRECATED_SUPPORT_ALARM_ARM_NIGHT: Final = DeprecatedConstantEnum(
AlarmControlPanelEntityFeature.ARM_NIGHT, "2025.1"
)
_DEPRECATED_SUPPORT_ALARM_TRIGGER: Final = DeprecatedConstantEnum(
AlarmControlPanelEntityFeature.TRIGGER, "2025.1"
)
_DEPRECATED_SUPPORT_ALARM_ARM_CUSTOM_BYPASS: Final = DeprecatedConstantEnum(
AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS, "2025.1"
)
_DEPRECATED_SUPPORT_ALARM_ARM_VACATION: Final = DeprecatedConstantEnum(
AlarmControlPanelEntityFeature.ARM_VACATION, "2025.1"
)
CONDITION_TRIGGERED: Final = "is_triggered"
CONDITION_DISARMED: Final = "is_disarmed"
CONDITION_ARMED_HOME: Final = "is_armed_home"
@@ -84,10 +49,3 @@ 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())

View File

@@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/apple_tv",
"iot_class": "local_push",
"loggers": ["pyatv", "srptools"],
"requirements": ["pyatv==0.15.1"],
"requirements": ["pyatv==0.16.0"],
"zeroconf": [
"_mediaremotetv._tcp.local.",
"_companion-link._tcp.local.",

View File

@@ -1040,7 +1040,7 @@ class PipelineRun:
:= await conversation.async_handle_sentence_triggers(
self.hass, user_input
)
):
) is not None:
# Sentence trigger matched
trigger_response = intent.IntentResponse(
self.pipeline.conversation_language

View File

@@ -6,7 +6,6 @@ from abc import ABC, abstractmethod
import asyncio
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from functools import partial
import logging
from typing import Any, Protocol, cast
@@ -51,12 +50,6 @@ from homeassistant.core import (
from homeassistant.exceptions import HomeAssistantError, ServiceNotFound, TemplateError
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,
)
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.issue_registry import (
@@ -86,12 +79,7 @@ from homeassistant.helpers.trace import (
trace_get,
trace_path,
)
from homeassistant.helpers.trigger import (
TriggerActionType,
TriggerData,
TriggerInfo,
async_initialize_triggers,
)
from homeassistant.helpers.trigger import async_initialize_triggers
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import bind_hass
from homeassistant.util.dt import parse_datetime
@@ -137,20 +125,6 @@ class IfAction(Protocol):
"""AND all conditions."""
# AutomationActionType, AutomationTriggerData,
# and AutomationTriggerInfo are deprecated as of 2022.9.
# Can be removed in 2025.1
_DEPRECATED_AutomationActionType = DeprecatedConstant(
TriggerActionType, "TriggerActionType", "2025.1"
)
_DEPRECATED_AutomationTriggerData = DeprecatedConstant(
TriggerData, "TriggerData", "2025.1"
)
_DEPRECATED_AutomationTriggerInfo = DeprecatedConstant(
TriggerInfo, "TriggerInfo", "2025.1"
)
@bind_hass
def is_on(hass: HomeAssistant, entity_id: str) -> bool:
"""Return true if specified automation entity_id is on.
@@ -477,6 +451,7 @@ class UnavailableAutomationEntity(BaseAutomationEntity):
)
async def async_will_remove_from_hass(self) -> None:
"""Run when entity will be removed from hass."""
await super().async_will_remove_from_hass()
async_delete_issue(
self.hass, DOMAIN, f"{self.entity_id}_validation_{self._validation_status}"
@@ -1219,11 +1194,3 @@ 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())

View File

@@ -4,7 +4,6 @@ from __future__ import annotations
from datetime import timedelta
from enum import StrEnum
from functools import partial
import logging
from typing import Literal, final
@@ -16,12 +15,6 @@ from homeassistant.const import STATE_OFF, STATE_ON, EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType
@@ -126,94 +119,7 @@ class BinarySensorDeviceClass(StrEnum):
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.Coerce(BinarySensorDeviceClass))
# DEVICE_CLASS* below are deprecated as of 2021.12
# use the BinarySensorDeviceClass enum instead.
DEVICE_CLASSES = [cls.value for cls in BinarySensorDeviceClass]
_DEPRECATED_DEVICE_CLASS_BATTERY = DeprecatedConstantEnum(
BinarySensorDeviceClass.BATTERY, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_BATTERY_CHARGING = DeprecatedConstantEnum(
BinarySensorDeviceClass.BATTERY_CHARGING, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_CO = DeprecatedConstantEnum(
BinarySensorDeviceClass.CO, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_COLD = DeprecatedConstantEnum(
BinarySensorDeviceClass.COLD, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_CONNECTIVITY = DeprecatedConstantEnum(
BinarySensorDeviceClass.CONNECTIVITY, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_DOOR = DeprecatedConstantEnum(
BinarySensorDeviceClass.DOOR, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_GARAGE_DOOR = DeprecatedConstantEnum(
BinarySensorDeviceClass.GARAGE_DOOR, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_GAS = DeprecatedConstantEnum(
BinarySensorDeviceClass.GAS, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_HEAT = DeprecatedConstantEnum(
BinarySensorDeviceClass.HEAT, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_LIGHT = DeprecatedConstantEnum(
BinarySensorDeviceClass.LIGHT, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_LOCK = DeprecatedConstantEnum(
BinarySensorDeviceClass.LOCK, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_MOISTURE = DeprecatedConstantEnum(
BinarySensorDeviceClass.MOISTURE, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_MOTION = DeprecatedConstantEnum(
BinarySensorDeviceClass.MOTION, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_MOVING = DeprecatedConstantEnum(
BinarySensorDeviceClass.MOVING, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_OCCUPANCY = DeprecatedConstantEnum(
BinarySensorDeviceClass.OCCUPANCY, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_OPENING = DeprecatedConstantEnum(
BinarySensorDeviceClass.OPENING, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_PLUG = DeprecatedConstantEnum(
BinarySensorDeviceClass.PLUG, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_POWER = DeprecatedConstantEnum(
BinarySensorDeviceClass.POWER, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_PRESENCE = DeprecatedConstantEnum(
BinarySensorDeviceClass.PRESENCE, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_PROBLEM = DeprecatedConstantEnum(
BinarySensorDeviceClass.PROBLEM, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_RUNNING = DeprecatedConstantEnum(
BinarySensorDeviceClass.RUNNING, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_SAFETY = DeprecatedConstantEnum(
BinarySensorDeviceClass.SAFETY, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_SMOKE = DeprecatedConstantEnum(
BinarySensorDeviceClass.SMOKE, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_SOUND = DeprecatedConstantEnum(
BinarySensorDeviceClass.SOUND, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_TAMPER = DeprecatedConstantEnum(
BinarySensorDeviceClass.TAMPER, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_UPDATE = DeprecatedConstantEnum(
BinarySensorDeviceClass.UPDATE, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_VIBRATION = DeprecatedConstantEnum(
BinarySensorDeviceClass.VIBRATION, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_WINDOW = DeprecatedConstantEnum(
BinarySensorDeviceClass.WINDOW, "2025.1"
)
# mypy: disallow-any-generics
@@ -294,11 +200,3 @@ 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())

View File

@@ -27,9 +27,18 @@ from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_SOURCE, CONF_US
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig
from homeassistant.util.ssl import get_default_context
from . import DOMAIN
from .const import CONF_ALLOWED_REGIONS, CONF_GCID, CONF_READ_ONLY, CONF_REFRESH_TOKEN
from .const import (
CONF_ALLOWED_REGIONS,
CONF_CAPTCHA_REGIONS,
CONF_CAPTCHA_TOKEN,
CONF_CAPTCHA_URL,
CONF_GCID,
CONF_READ_ONLY,
CONF_REFRESH_TOKEN,
)
DATA_SCHEMA = vol.Schema(
{
@@ -41,7 +50,14 @@ DATA_SCHEMA = vol.Schema(
translation_key="regions",
)
),
}
},
extra=vol.REMOVE_EXTRA,
)
CAPTCHA_SCHEMA = vol.Schema(
{
vol.Required(CONF_CAPTCHA_TOKEN): str,
},
extra=vol.REMOVE_EXTRA,
)
@@ -54,6 +70,8 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
data[CONF_USERNAME],
data[CONF_PASSWORD],
get_region_from_name(data[CONF_REGION]),
hcaptcha_token=data.get(CONF_CAPTCHA_TOKEN),
verify=get_default_context(),
)
try:
@@ -79,15 +97,17 @@ class BMWConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
data: dict[str, Any] = {}
_existing_entry_data: Mapping[str, Any] | None = None
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
errors: dict[str, str] = self.data.pop("errors", {})
if user_input is not None:
if user_input is not None and not errors:
unique_id = f"{user_input[CONF_REGION]}-{user_input[CONF_USERNAME]}"
await self.async_set_unique_id(unique_id)
@@ -96,22 +116,35 @@ class BMWConfigFlow(ConfigFlow, domain=DOMAIN):
else:
self._abort_if_unique_id_configured()
# Store user input for later use
self.data.update(user_input)
# North America and Rest of World require captcha token
if (
self.data.get(CONF_REGION) in CONF_CAPTCHA_REGIONS
and CONF_CAPTCHA_TOKEN not in self.data
):
return await self.async_step_captcha()
info = None
try:
info = await validate_input(self.hass, user_input)
entry_data = {
**user_input,
CONF_REFRESH_TOKEN: info.get(CONF_REFRESH_TOKEN),
CONF_GCID: info.get(CONF_GCID),
}
info = await validate_input(self.hass, self.data)
except MissingCaptcha:
errors["base"] = "missing_captcha"
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
finally:
self.data.pop(CONF_CAPTCHA_TOKEN, None)
if info:
entry_data = {
**self.data,
CONF_REFRESH_TOKEN: info.get(CONF_REFRESH_TOKEN),
CONF_GCID: info.get(CONF_GCID),
}
if self.source == SOURCE_REAUTH:
return self.async_update_reload_and_abort(
self._get_reauth_entry(), data=entry_data
@@ -128,7 +161,7 @@ class BMWConfigFlow(ConfigFlow, domain=DOMAIN):
schema = self.add_suggested_values_to_schema(
DATA_SCHEMA,
self._existing_entry_data,
self._existing_entry_data or self.data,
)
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
@@ -147,6 +180,22 @@ class BMWConfigFlow(ConfigFlow, domain=DOMAIN):
self._existing_entry_data = self._get_reconfigure_entry().data
return await self.async_step_user()
async def async_step_captcha(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Show captcha form."""
if user_input and user_input.get(CONF_CAPTCHA_TOKEN):
self.data[CONF_CAPTCHA_TOKEN] = user_input[CONF_CAPTCHA_TOKEN].strip()
return await self.async_step_user(self.data)
return self.async_show_form(
step_id="captcha",
data_schema=CAPTCHA_SCHEMA,
description_placeholders={
"captcha_url": CONF_CAPTCHA_URL.format(region=self.data[CONF_REGION])
},
)
@staticmethod
@callback
def async_get_options_flow(

View File

@@ -8,10 +8,15 @@ ATTR_DIRECTION = "direction"
ATTR_VIN = "vin"
CONF_ALLOWED_REGIONS = ["china", "north_america", "rest_of_world"]
CONF_CAPTCHA_REGIONS = ["north_america", "rest_of_world"]
CONF_READ_ONLY = "read_only"
CONF_ACCOUNT = "account"
CONF_REFRESH_TOKEN = "refresh_token"
CONF_GCID = "gcid"
CONF_CAPTCHA_TOKEN = "captcha_token"
CONF_CAPTCHA_URL = (
"https://bimmer-connected.readthedocs.io/en/stable/captcha/{region}.html"
)
DATA_HASS_CONFIG = "hass_config"

View File

@@ -84,11 +84,6 @@ class BMWDataUpdateCoordinator(DataUpdateCoordinator[None]):
if self.account.refresh_token != old_refresh_token:
self._update_config_entry_refresh_token(self.account.refresh_token)
_LOGGER.debug(
"bimmer_connected: refresh token %s > %s",
old_refresh_token,
self.account.refresh_token,
)
def _update_config_entry_refresh_token(self, refresh_token: str | None) -> None:
"""Update or delete the refresh_token in the Config Entry."""

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
"iot_class": "cloud_polling",
"loggers": ["bimmer_connected"],
"requirements": ["bimmer-connected[china]==0.16.4"]
"requirements": ["bimmer-connected[china]==0.17.0"]
}

View File

@@ -7,6 +7,16 @@
"password": "[%key:common::config_flow::data::password%]",
"region": "ConnectedDrive Region"
}
},
"captcha": {
"title": "Are you a robot?",
"description": "A captcha is required for BMW login. Visit the external website to complete the challenge and submit the form. Copy the resulting token into the field below.\n\n{captcha_url}\n\nNo data will be exposed outside of your Home Assistant instance.",
"data": {
"captcha_token": "Captcha token"
},
"data_description": {
"captcha_token": "One-time token retrieved from the captcha challenge."
}
}
},
"error": {

View File

@@ -9,4 +9,3 @@ ATTR_ITEM_NAME: Final = "item"
ATTR_NOTIFICATION_TYPE: Final = "message"
SERVICE_PUSH_NOTIFICATION = "send_message"
UNIT_ITEMS = "items"

View File

@@ -20,7 +20,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from . import BringConfigEntry
from .const import UNIT_ITEMS
from .coordinator import BringData, BringDataUpdateCoordinator
from .entity import BringBaseEntity
from .util import list_language, sum_attributes
@@ -48,19 +47,16 @@ SENSOR_DESCRIPTIONS: tuple[BringSensorEntityDescription, ...] = (
key=BringSensor.URGENT,
translation_key=BringSensor.URGENT,
value_fn=lambda lst, _: sum_attributes(lst, "urgent"),
native_unit_of_measurement=UNIT_ITEMS,
),
BringSensorEntityDescription(
key=BringSensor.CONVENIENT,
translation_key=BringSensor.CONVENIENT,
value_fn=lambda lst, _: sum_attributes(lst, "convenient"),
native_unit_of_measurement=UNIT_ITEMS,
),
BringSensorEntityDescription(
key=BringSensor.DISCOUNTED,
translation_key=BringSensor.DISCOUNTED,
value_fn=lambda lst, _: sum_attributes(lst, "discounted"),
native_unit_of_measurement=UNIT_ITEMS,
),
BringSensorEntityDescription(
key=BringSensor.LIST_LANGUAGE,

View File

@@ -1,4 +1,7 @@
{
"common": {
"shopping_list_items": "items"
},
"config": {
"step": {
"user": {
@@ -29,13 +32,16 @@
"entity": {
"sensor": {
"urgent": {
"name": "Urgent"
"name": "Urgent",
"unit_of_measurement": "[%key:component::bring::common::shopping_list_items%]"
},
"convenient": {
"name": "On occasion"
"name": "On occasion",
"unit_of_measurement": "[%key:component::bring::common::shopping_list_items%]"
},
"discounted": {
"name": "Discount only"
"name": "Discount only",
"unit_of_measurement": "[%key:component::bring::common::shopping_list_items%]"
},
"list_language": {
"name": "Region & language",

View File

@@ -67,9 +67,7 @@ from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType, VolDictType
from homeassistant.loader import bind_hass
from .const import ( # noqa: F401
_DEPRECATED_STREAM_TYPE_HLS,
_DEPRECATED_STREAM_TYPE_WEB_RTC,
from .const import (
CAMERA_IMAGE_TIMEOUT,
CAMERA_STREAM_SOURCE_TIMEOUT,
CONF_DURATION,
@@ -135,16 +133,6 @@ class CameraEntityFeature(IntFlag):
STREAM = 2
# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
# Pleease use the CameraEntityFeature enum instead.
_DEPRECATED_SUPPORT_ON_OFF: Final = DeprecatedConstantEnum(
CameraEntityFeature.ON_OFF, "2025.1"
)
_DEPRECATED_SUPPORT_STREAM: Final = DeprecatedConstantEnum(
CameraEntityFeature.STREAM, "2025.1"
)
DEFAULT_CONTENT_TYPE: Final = "image/jpeg"
ENTITY_IMAGE_URL: Final = "/api/camera_proxy/{0}?token={1}"

View File

@@ -3,15 +3,8 @@
from __future__ import annotations
from enum import StrEnum
from functools import partial
from typing import TYPE_CHECKING, Final
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.util.hass_dict import HassKey
if TYPE_CHECKING:
@@ -58,17 +51,3 @@ class StreamType(StrEnum):
HLS = "hls"
WEB_RTC = "web_rtc"
# These constants are deprecated as of Home Assistant 2022.5
# Please use the StreamType enum instead.
_DEPRECATED_STREAM_TYPE_HLS = DeprecatedConstantEnum(StreamType.HLS, "2025.1")
_DEPRECATED_STREAM_TYPE_WEB_RTC = DeprecatedConstantEnum(StreamType.WEB_RTC, "2025.1")
# 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())

View File

@@ -26,11 +26,6 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv, issue_registry as ir
from homeassistant.helpers.deprecation import (
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_platform import EntityPlatform
@@ -41,20 +36,6 @@ from homeassistant.util.hass_dict import HassKey
from homeassistant.util.unit_conversion import TemperatureConverter
from .const import ( # noqa: F401
_DEPRECATED_HVAC_MODE_AUTO,
_DEPRECATED_HVAC_MODE_COOL,
_DEPRECATED_HVAC_MODE_DRY,
_DEPRECATED_HVAC_MODE_FAN_ONLY,
_DEPRECATED_HVAC_MODE_HEAT,
_DEPRECATED_HVAC_MODE_HEAT_COOL,
_DEPRECATED_HVAC_MODE_OFF,
_DEPRECATED_SUPPORT_AUX_HEAT,
_DEPRECATED_SUPPORT_FAN_MODE,
_DEPRECATED_SUPPORT_PRESET_MODE,
_DEPRECATED_SUPPORT_SWING_MODE,
_DEPRECATED_SUPPORT_TARGET_HUMIDITY,
_DEPRECATED_SUPPORT_TARGET_TEMPERATURE,
_DEPRECATED_SUPPORT_TARGET_TEMPERATURE_RANGE,
ATTR_AUX_HEAT,
ATTR_CURRENT_HUMIDITY,
ATTR_CURRENT_TEMPERATURE,
@@ -1082,13 +1063,3 @@ 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())

View File

@@ -1,14 +1,6 @@
"""Provides the constants needed for component."""
from enum import IntFlag, StrEnum
from functools import partial
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
class HVACMode(StrEnum):
@@ -37,15 +29,6 @@ class HVACMode(StrEnum):
FAN_ONLY = "fan_only"
# These HVAC_MODE_* constants are deprecated as of Home Assistant 2022.5.
# Please use the HVACMode enum instead.
_DEPRECATED_HVAC_MODE_OFF = DeprecatedConstantEnum(HVACMode.OFF, "2025.1")
_DEPRECATED_HVAC_MODE_HEAT = DeprecatedConstantEnum(HVACMode.HEAT, "2025.1")
_DEPRECATED_HVAC_MODE_COOL = DeprecatedConstantEnum(HVACMode.COOL, "2025.1")
_DEPRECATED_HVAC_MODE_HEAT_COOL = DeprecatedConstantEnum(HVACMode.HEAT_COOL, "2025.1")
_DEPRECATED_HVAC_MODE_AUTO = DeprecatedConstantEnum(HVACMode.AUTO, "2025.1")
_DEPRECATED_HVAC_MODE_DRY = DeprecatedConstantEnum(HVACMode.DRY, "2025.1")
_DEPRECATED_HVAC_MODE_FAN_ONLY = DeprecatedConstantEnum(HVACMode.FAN_ONLY, "2025.1")
HVAC_MODES = [cls.value for cls in HVACMode]
# No preset is active
@@ -110,14 +93,6 @@ class HVACAction(StrEnum):
PREHEATING = "preheating"
# These CURRENT_HVAC_* constants are deprecated as of Home Assistant 2022.5.
# Please use the HVACAction enum instead.
_DEPRECATED_CURRENT_HVAC_OFF = DeprecatedConstantEnum(HVACAction.OFF, "2025.1")
_DEPRECATED_CURRENT_HVAC_HEAT = DeprecatedConstantEnum(HVACAction.HEATING, "2025.1")
_DEPRECATED_CURRENT_HVAC_COOL = DeprecatedConstantEnum(HVACAction.COOLING, "2025.1")
_DEPRECATED_CURRENT_HVAC_DRY = DeprecatedConstantEnum(HVACAction.DRYING, "2025.1")
_DEPRECATED_CURRENT_HVAC_IDLE = DeprecatedConstantEnum(HVACAction.IDLE, "2025.1")
_DEPRECATED_CURRENT_HVAC_FAN = DeprecatedConstantEnum(HVACAction.FAN, "2025.1")
CURRENT_HVAC_ACTIONS = [cls.value for cls in HVACAction]
@@ -176,35 +151,3 @@ class ClimateEntityFeature(IntFlag):
TURN_OFF = 128
TURN_ON = 256
SWING_HORIZONTAL_MODE = 512
# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
# Please use the ClimateEntityFeature enum instead.
_DEPRECATED_SUPPORT_TARGET_TEMPERATURE = DeprecatedConstantEnum(
ClimateEntityFeature.TARGET_TEMPERATURE, "2025.1"
)
_DEPRECATED_SUPPORT_TARGET_TEMPERATURE_RANGE = DeprecatedConstantEnum(
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE, "2025.1"
)
_DEPRECATED_SUPPORT_TARGET_HUMIDITY = DeprecatedConstantEnum(
ClimateEntityFeature.TARGET_HUMIDITY, "2025.1"
)
_DEPRECATED_SUPPORT_FAN_MODE = DeprecatedConstantEnum(
ClimateEntityFeature.FAN_MODE, "2025.1"
)
_DEPRECATED_SUPPORT_PRESET_MODE = DeprecatedConstantEnum(
ClimateEntityFeature.PRESET_MODE, "2025.1"
)
_DEPRECATED_SUPPORT_SWING_MODE = DeprecatedConstantEnum(
ClimateEntityFeature.SWING_MODE, "2025.1"
)
_DEPRECATED_SUPPORT_AUX_HEAT = DeprecatedConstantEnum(
ClimateEntityFeature.AUX_HEAT, "2025.1"
)
# 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())

View File

@@ -89,36 +89,8 @@ class CoverDeviceClass(StrEnum):
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.Coerce(CoverDeviceClass))
# DEVICE_CLASS* below are deprecated as of 2021.12
# use the CoverDeviceClass enum instead.
DEVICE_CLASSES = [cls.value for cls in CoverDeviceClass]
_DEPRECATED_DEVICE_CLASS_AWNING = DeprecatedConstantEnum(
CoverDeviceClass.AWNING, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_BLIND = DeprecatedConstantEnum(
CoverDeviceClass.BLIND, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_CURTAIN = DeprecatedConstantEnum(
CoverDeviceClass.CURTAIN, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_DAMPER = DeprecatedConstantEnum(
CoverDeviceClass.DAMPER, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_DOOR = DeprecatedConstantEnum(CoverDeviceClass.DOOR, "2025.1")
_DEPRECATED_DEVICE_CLASS_GARAGE = DeprecatedConstantEnum(
CoverDeviceClass.GARAGE, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_GATE = DeprecatedConstantEnum(CoverDeviceClass.GATE, "2025.1")
_DEPRECATED_DEVICE_CLASS_SHADE = DeprecatedConstantEnum(
CoverDeviceClass.SHADE, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_SHUTTER = DeprecatedConstantEnum(
CoverDeviceClass.SHUTTER, "2025.1"
)
_DEPRECATED_DEVICE_CLASS_WINDOW = DeprecatedConstantEnum(
CoverDeviceClass.WINDOW, "2025.1"
)
# mypy: disallow-any-generics
@@ -136,27 +108,6 @@ class CoverEntityFeature(IntFlag):
SET_TILT_POSITION = 128
# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
# Please use the CoverEntityFeature enum instead.
_DEPRECATED_SUPPORT_OPEN = DeprecatedConstantEnum(CoverEntityFeature.OPEN, "2025.1")
_DEPRECATED_SUPPORT_CLOSE = DeprecatedConstantEnum(CoverEntityFeature.CLOSE, "2025.1")
_DEPRECATED_SUPPORT_SET_POSITION = DeprecatedConstantEnum(
CoverEntityFeature.SET_POSITION, "2025.1"
)
_DEPRECATED_SUPPORT_STOP = DeprecatedConstantEnum(CoverEntityFeature.STOP, "2025.1")
_DEPRECATED_SUPPORT_OPEN_TILT = DeprecatedConstantEnum(
CoverEntityFeature.OPEN_TILT, "2025.1"
)
_DEPRECATED_SUPPORT_CLOSE_TILT = DeprecatedConstantEnum(
CoverEntityFeature.CLOSE_TILT, "2025.1"
)
_DEPRECATED_SUPPORT_STOP_TILT = DeprecatedConstantEnum(
CoverEntityFeature.STOP_TILT, "2025.1"
)
_DEPRECATED_SUPPORT_SET_TILT_POSITION = DeprecatedConstantEnum(
CoverEntityFeature.SET_TILT_POSITION, "2025.1"
)
ATTR_CURRENT_POSITION = "current_position"
ATTR_CURRENT_TILT_POSITION = "current_tilt_position"
ATTR_POSITION = "position"

View File

@@ -2,15 +2,8 @@
from __future__ import annotations
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,
)
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import bind_hass
@@ -23,10 +16,6 @@ from .config_entry import ( # noqa: F401
async_unload_entry,
)
from .const import ( # noqa: F401
_DEPRECATED_SOURCE_TYPE_BLUETOOTH,
_DEPRECATED_SOURCE_TYPE_BLUETOOTH_LE,
_DEPRECATED_SOURCE_TYPE_GPS,
_DEPRECATED_SOURCE_TYPE_ROUTER,
ATTR_ATTRIBUTES,
ATTR_BATTERY,
ATTR_DEV_ID,
@@ -72,13 +61,3 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the device tracker."""
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())

View File

@@ -4,16 +4,9 @@ from __future__ import annotations
from datetime import timedelta
from enum import StrEnum
from functools import partial
import logging
from typing import Final
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.util.signal_type import SignalType
LOGGER: Final = logging.getLogger(__package__)
@@ -34,19 +27,6 @@ class SourceType(StrEnum):
BLUETOOTH_LE = "bluetooth_le"
# SOURCE_TYPE_* below are deprecated as of 2022.9
# use the SourceType enum instead.
_DEPRECATED_SOURCE_TYPE_GPS: Final = DeprecatedConstantEnum(SourceType.GPS, "2025.1")
_DEPRECATED_SOURCE_TYPE_ROUTER: Final = DeprecatedConstantEnum(
SourceType.ROUTER, "2025.1"
)
_DEPRECATED_SOURCE_TYPE_BLUETOOTH: Final = DeprecatedConstantEnum(
SourceType.BLUETOOTH, "2025.1"
)
_DEPRECATED_SOURCE_TYPE_BLUETOOTH_LE: Final = DeprecatedConstantEnum(
SourceType.BLUETOOTH_LE, "2025.1"
)
CONF_SCAN_INTERVAL: Final = "interval_seconds"
SCAN_INTERVAL: Final = timedelta(seconds=12)
@@ -72,10 +52,3 @@ ATTR_IP: Final = "ip"
CONNECTED_DEVICE_REGISTERED = SignalType[dict[str, str | None]](
"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())

View File

@@ -60,11 +60,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: DiscovergyConfigEntry) -
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: DiscovergyConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def async_reload_entry(hass: HomeAssistant, entry: DiscovergyConfigEntry) -> None:
"""Handle an options update."""
await hass.config_entries.async_reload(entry.entry_id)

View File

@@ -16,7 +16,7 @@
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
"mqtt": ["esphome/discover/#"],
"requirements": [
"aioesphomeapi==27.0.2",
"aioesphomeapi==27.0.3",
"esphome-dashboard-api==1.2.3",
"bleak-esphome==1.1.0"
],

View File

@@ -10,6 +10,7 @@ from homeassistant.components.assist_pipeline.select import (
)
from homeassistant.components.assist_satellite import AssistSatelliteConfiguration
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import restore_state
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@@ -100,7 +101,9 @@ class EsphomeAssistSatelliteWakeWordSelect(
"""Wake word selector for esphome devices."""
entity_description = SelectEntityDescription(
key="wake_word", translation_key="wake_word"
key="wake_word",
translation_key="wake_word",
entity_category=EntityCategory.CONFIG,
)
_attr_should_poll = False
_attr_current_option: str | None = None

View File

@@ -23,12 +23,6 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_platform import EntityPlatform
@@ -61,21 +55,6 @@ class FanEntityFeature(IntFlag):
TURN_ON = 32
# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
# Please use the FanEntityFeature enum instead.
_DEPRECATED_SUPPORT_SET_SPEED = DeprecatedConstantEnum(
FanEntityFeature.SET_SPEED, "2025.1"
)
_DEPRECATED_SUPPORT_OSCILLATE = DeprecatedConstantEnum(
FanEntityFeature.OSCILLATE, "2025.1"
)
_DEPRECATED_SUPPORT_DIRECTION = DeprecatedConstantEnum(
FanEntityFeature.DIRECTION, "2025.1"
)
_DEPRECATED_SUPPORT_PRESET_MODE = DeprecatedConstantEnum(
FanEntityFeature.PRESET_MODE, "2025.1"
)
SERVICE_INCREASE_SPEED = "increase_speed"
SERVICE_DECREASE_SPEED = "decrease_speed"
SERVICE_OSCILLATE = "oscillate"
@@ -543,11 +522,3 @@ class FanEntity(ToggleEntity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
Requires FanEntityFeature.SET_SPEED.
"""
return self._attr_preset_modes
# 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())

View File

@@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20241127.0"]
"requirements": ["home-assistant-frontend==20241127.1"]
}

View File

@@ -33,7 +33,7 @@ class HabiticaButtonEntityDescription(ButtonEntityDescription):
"""Describes Habitica button entity."""
press_fn: Callable[[HabiticaDataUpdateCoordinator], Any]
available_fn: Callable[[HabiticaData], bool] | None = None
available_fn: Callable[[HabiticaData], bool]
class_needed: str | None = None
entity_picture: str | None = None
@@ -343,11 +343,10 @@ class HabiticaButton(HabiticaBase, ButtonEntity):
@property
def available(self) -> bool:
"""Is entity available."""
if not super().available:
return False
if self.entity_description.available_fn:
return self.entity_description.available_fn(self.coordinator.data)
return True
return super().available and self.entity_description.available_fn(
self.coordinator.data
)
@property
def entity_picture(self) -> str | None:

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
from abc import abstractmethod
from datetime import date, datetime, timedelta
from enum import StrEnum
@@ -60,6 +61,43 @@ class HabiticaCalendarEntity(HabiticaBase, CalendarEntity):
"""Initialize calendar entity."""
super().__init__(coordinator, self.entity_description)
@abstractmethod
def get_events(
self, start_date: datetime, end_date: datetime | None = None
) -> list[CalendarEvent]:
"""Return events."""
@property
def event(self) -> CalendarEvent | None:
"""Return the current or next upcoming event."""
return next(iter(self.get_events(dt_util.now())), None)
async def async_get_events(
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]:
"""Return calendar events within a datetime range."""
return self.get_events(start_date, end_date)
@property
def start_of_today(self) -> datetime:
"""Habitica daystart."""
return dt_util.start_of_local_day(
datetime.fromisoformat(self.coordinator.data.user["lastCron"])
)
def get_recurrence_dates(
self, recurrences: rrule, start_date: datetime, end_date: datetime | None = None
) -> list[datetime]:
"""Calculate recurrence dates based on start_date and end_date."""
if end_date:
return recurrences.between(
start_date, end_date - timedelta(days=1), inc=True
)
# if no end_date is given, return only the next recurrence
return [recurrences.after(start_date, inc=True)]
class HabiticaTodosCalendarEntity(HabiticaCalendarEntity):
"""Habitica todos calendar entity."""
@@ -69,7 +107,7 @@ class HabiticaTodosCalendarEntity(HabiticaCalendarEntity):
translation_key=HabiticaCalendar.TODOS,
)
def dated_todos(
def get_events(
self, start_date: datetime, end_date: datetime | None = None
) -> list[CalendarEvent]:
"""Get all dated todos."""
@@ -112,18 +150,6 @@ class HabiticaTodosCalendarEntity(HabiticaCalendarEntity):
),
)
@property
def event(self) -> CalendarEvent | None:
"""Return the current or next upcoming event."""
return next(iter(self.dated_todos(dt_util.now())), None)
async def async_get_events(
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]:
"""Return calendar events within a datetime range."""
return self.dated_todos(start_date, end_date)
class HabiticaDailiesCalendarEntity(HabiticaCalendarEntity):
"""Habitica dailies calendar entity."""
@@ -133,13 +159,6 @@ class HabiticaDailiesCalendarEntity(HabiticaCalendarEntity):
translation_key=HabiticaCalendar.DAILIES,
)
@property
def today(self) -> datetime:
"""Habitica daystart."""
return dt_util.start_of_local_day(
datetime.fromisoformat(self.coordinator.data.user["lastCron"])
)
def end_date(self, recurrence: datetime, end: datetime | None = None) -> date:
"""Calculate the end date for a yesterdaily.
@@ -152,29 +171,20 @@ class HabiticaDailiesCalendarEntity(HabiticaCalendarEntity):
if end:
return recurrence.date() + timedelta(days=1)
return (
dt_util.start_of_local_day() if recurrence == self.today else recurrence
dt_util.start_of_local_day()
if recurrence == self.start_of_today
else recurrence
).date() + timedelta(days=1)
def get_recurrence_dates(
self, recurrences: rrule, start_date: datetime, end_date: datetime | None = None
) -> list[datetime]:
"""Calculate recurrence dates based on start_date and end_date."""
if end_date:
return recurrences.between(
start_date, end_date - timedelta(days=1), inc=True
)
# if no end_date is given, return only the next recurrence
return [recurrences.after(self.today, inc=True)]
def due_dailies(
def get_events(
self, start_date: datetime, end_date: datetime | None = None
) -> list[CalendarEvent]:
"""Get dailies and recurrences for a given period or the next upcoming."""
# we only have dailies for today and future recurrences
if end_date and end_date < self.today:
if end_date and end_date < self.start_of_today:
return []
start_date = max(start_date, self.today)
start_date = max(start_date, self.start_of_today)
events = []
for task in self.coordinator.data.tasks:
@@ -187,10 +197,12 @@ class HabiticaDailiesCalendarEntity(HabiticaCalendarEntity):
recurrences, start_date, end_date
)
for recurrence in recurrence_dates:
is_future_event = recurrence > self.today
is_current_event = recurrence <= self.today and not task["completed"]
is_future_event = recurrence > self.start_of_today
is_current_event = (
recurrence <= self.start_of_today and not task["completed"]
)
if not (is_future_event or is_current_event):
if not is_future_event and not is_current_event:
continue
events.append(
@@ -214,20 +226,15 @@ class HabiticaDailiesCalendarEntity(HabiticaCalendarEntity):
@property
def event(self) -> CalendarEvent | None:
"""Return the next upcoming event."""
return next(iter(self.due_dailies(self.today)), None)
async def async_get_events(
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]:
"""Return calendar events within a datetime range."""
return self.due_dailies(start_date, end_date)
return next(iter(self.get_events(self.start_of_today)), None)
@property
def extra_state_attributes(self) -> dict[str, bool | None] | None:
"""Return entity specific state attributes."""
return {
"yesterdaily": self.event.start < self.today.date() if self.event else None
"yesterdaily": self.event.start < self.start_of_today.date()
if self.event
else None
}
@@ -239,7 +246,7 @@ class HabiticaTodoRemindersCalendarEntity(HabiticaCalendarEntity):
translation_key=HabiticaCalendar.TODO_REMINDERS,
)
def reminders(
def get_events(
self, start_date: datetime, end_date: datetime | None = None
) -> list[CalendarEvent]:
"""Reminders for todos."""
@@ -282,18 +289,6 @@ class HabiticaTodoRemindersCalendarEntity(HabiticaCalendarEntity):
key=lambda event: event.start,
)
@property
def event(self) -> CalendarEvent | None:
"""Return the next upcoming event."""
return next(iter(self.reminders(dt_util.now())), None)
async def async_get_events(
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]:
"""Return calendar events within a datetime range."""
return self.reminders(start_date, end_date)
class HabiticaDailyRemindersCalendarEntity(HabiticaCalendarEntity):
"""Habitica daily reminders calendar entity."""
@@ -321,47 +316,31 @@ class HabiticaDailyRemindersCalendarEntity(HabiticaCalendarEntity):
tzinfo=dt_util.DEFAULT_TIME_ZONE,
)
@property
def today(self) -> datetime:
"""Habitica daystart."""
return dt_util.start_of_local_day(
datetime.fromisoformat(self.coordinator.data.user["lastCron"])
)
def get_recurrence_dates(
self, recurrences: rrule, start_date: datetime, end_date: datetime | None = None
) -> list[datetime]:
"""Calculate recurrence dates based on start_date and end_date."""
if end_date:
return recurrences.between(
start_date, end_date - timedelta(days=1), inc=True
)
# if no end_date is given, return only the next recurrence
return [recurrences.after(self.today, inc=True)]
def reminders(
def get_events(
self, start_date: datetime, end_date: datetime | None = None
) -> list[CalendarEvent]:
"""Reminders for dailies."""
events = []
if end_date and end_date < self.today:
if end_date and end_date < self.start_of_today:
return []
start_date = max(start_date, self.today)
start_date = max(start_date, self.start_of_today)
for task in self.coordinator.data.tasks:
if not (task["type"] == HabiticaTaskType.DAILY and task["everyX"]):
continue
recurrences = build_rrule(task)
recurrences_start = self.today
recurrences_start = self.start_of_today
recurrence_dates = self.get_recurrence_dates(
recurrences, recurrences_start, end_date
)
for recurrence in recurrence_dates:
is_future_event = recurrence > self.today
is_current_event = recurrence <= self.today and not task["completed"]
is_future_event = recurrence > self.start_of_today
is_current_event = (
recurrence <= self.start_of_today and not task["completed"]
)
if not is_future_event and not is_current_event:
continue
@@ -374,9 +353,6 @@ class HabiticaDailyRemindersCalendarEntity(HabiticaCalendarEntity):
# Event ends before date range
continue
if end_date and start > end_date:
# Event starts after date range
continue
events.append(
CalendarEvent(
start=start,
@@ -391,15 +367,3 @@ class HabiticaDailyRemindersCalendarEntity(HabiticaCalendarEntity):
events,
key=lambda event: event.start,
)
@property
def event(self) -> CalendarEvent | None:
"""Return the next upcoming event."""
return next(iter(self.reminders(dt_util.now())), None)
async def async_get_events(
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]:
"""Return calendar events within a datetime range."""
return self.reminders(start_date, end_date)

View File

@@ -25,8 +25,6 @@ ATTR_DATA = "data"
MANUFACTURER = "HabitRPG, Inc."
NAME = "Habitica"
UNIT_TASKS = "tasks"
ATTR_CONFIG_ENTRY = "config_entry"
ATTR_SKILL = "skill"
ATTR_TASK = "task"

View File

@@ -24,7 +24,7 @@ from homeassistant.helpers.issue_registry import (
)
from homeassistant.helpers.typing import StateType
from .const import ASSETS_URL, DOMAIN, UNIT_TASKS
from .const import ASSETS_URL, DOMAIN
from .entity import HabiticaBase
from .types import HabiticaConfigEntry
from .util import entity_used_in, get_attribute_points, get_attributes_total
@@ -84,40 +84,34 @@ SENSOR_DESCRIPTIONS: tuple[HabitipySensorEntityDescription, ...] = (
HabitipySensorEntityDescription(
key=HabitipySensorEntity.HEALTH,
translation_key=HabitipySensorEntity.HEALTH,
native_unit_of_measurement="HP",
suggested_display_precision=0,
value_fn=lambda user, _: user.get("stats", {}).get("hp"),
),
HabitipySensorEntityDescription(
key=HabitipySensorEntity.HEALTH_MAX,
translation_key=HabitipySensorEntity.HEALTH_MAX,
native_unit_of_measurement="HP",
entity_registry_enabled_default=False,
value_fn=lambda user, _: user.get("stats", {}).get("maxHealth"),
),
HabitipySensorEntityDescription(
key=HabitipySensorEntity.MANA,
translation_key=HabitipySensorEntity.MANA,
native_unit_of_measurement="MP",
suggested_display_precision=0,
value_fn=lambda user, _: user.get("stats", {}).get("mp"),
),
HabitipySensorEntityDescription(
key=HabitipySensorEntity.MANA_MAX,
translation_key=HabitipySensorEntity.MANA_MAX,
native_unit_of_measurement="MP",
value_fn=lambda user, _: user.get("stats", {}).get("maxMP"),
),
HabitipySensorEntityDescription(
key=HabitipySensorEntity.EXPERIENCE,
translation_key=HabitipySensorEntity.EXPERIENCE,
native_unit_of_measurement="XP",
value_fn=lambda user, _: user.get("stats", {}).get("exp"),
),
HabitipySensorEntityDescription(
key=HabitipySensorEntity.EXPERIENCE_MAX,
translation_key=HabitipySensorEntity.EXPERIENCE_MAX,
native_unit_of_measurement="XP",
value_fn=lambda user, _: user.get("stats", {}).get("toNextLevel"),
),
HabitipySensorEntityDescription(
@@ -128,7 +122,6 @@ SENSOR_DESCRIPTIONS: tuple[HabitipySensorEntityDescription, ...] = (
HabitipySensorEntityDescription(
key=HabitipySensorEntity.GOLD,
translation_key=HabitipySensorEntity.GOLD,
native_unit_of_measurement="GP",
suggested_display_precision=2,
value_fn=lambda user, _: user.get("stats", {}).get("gp"),
),
@@ -144,7 +137,6 @@ SENSOR_DESCRIPTIONS: tuple[HabitipySensorEntityDescription, ...] = (
translation_key=HabitipySensorEntity.GEMS,
value_fn=lambda user, _: user.get("balance", 0) * 4,
suggested_display_precision=0,
native_unit_of_measurement="gems",
entity_picture="shop_gem.png",
),
HabitipySensorEntityDescription(
@@ -229,20 +221,17 @@ TASK_SENSOR_DESCRIPTION: tuple[HabitipyTaskSensorEntityDescription, ...] = (
HabitipyTaskSensorEntityDescription(
key=HabitipySensorEntity.HABITS,
translation_key=HabitipySensorEntity.HABITS,
native_unit_of_measurement=UNIT_TASKS,
value_fn=lambda tasks: [r for r in tasks if r.get("type") == "habit"],
),
HabitipyTaskSensorEntityDescription(
key=HabitipySensorEntity.DAILIES,
translation_key=HabitipySensorEntity.DAILIES,
native_unit_of_measurement=UNIT_TASKS,
value_fn=lambda tasks: [r for r in tasks if r.get("type") == "daily"],
entity_registry_enabled_default=False,
),
HabitipyTaskSensorEntityDescription(
key=HabitipySensorEntity.TODOS,
translation_key=HabitipySensorEntity.TODOS,
native_unit_of_measurement=UNIT_TASKS,
value_fn=lambda tasks: [
r for r in tasks if r.get("type") == "todo" and not r.get("completed")
],
@@ -251,7 +240,6 @@ TASK_SENSOR_DESCRIPTION: tuple[HabitipyTaskSensorEntityDescription, ...] = (
HabitipyTaskSensorEntityDescription(
key=HabitipySensorEntity.REWARDS,
translation_key=HabitipySensorEntity.REWARDS,
native_unit_of_measurement=UNIT_TASKS,
value_fn=lambda tasks: [r for r in tasks if r.get("type") == "reward"],
),
)

View File

@@ -2,7 +2,11 @@
"common": {
"todos": "To-Do's",
"dailies": "Dailies",
"config_entry_name": "Select character"
"config_entry_name": "Select character",
"unit_tasks": "tasks",
"unit_health_points": "HP",
"unit_mana_points": "MP",
"unit_experience_points": "XP"
},
"config": {
"abort": {
@@ -135,31 +139,39 @@
"name": "Display name"
},
"health": {
"name": "Health"
"name": "Health",
"unit_of_measurement": "[%key:component::habitica::common::unit_health_points%]"
},
"health_max": {
"name": "Max. health"
"name": "Max. health",
"unit_of_measurement": "[%key:component::habitica::common::unit_health_points%]"
},
"mana": {
"name": "Mana"
"name": "Mana",
"unit_of_measurement": "[%key:component::habitica::common::unit_mana_points%]"
},
"mana_max": {
"name": "Max. mana"
"name": "Max. mana",
"unit_of_measurement": "[%key:component::habitica::common::unit_mana_points%]"
},
"experience": {
"name": "Experience"
"name": "Experience",
"unit_of_measurement": "[%key:component::habitica::common::unit_experience_points%]"
},
"experience_max": {
"name": "Next level"
"name": "Next level",
"unit_of_measurement": "[%key:component::habitica::common::unit_experience_points%]"
},
"level": {
"name": "Level"
},
"gold": {
"name": "Gold"
"name": "Gold",
"unit_of_measurement": "GP"
},
"gems": {
"name": "Gems"
"name": "Gems",
"unit_of_measurement": "gems"
},
"trinkets": {
"name": "Mystic hourglasses"
@@ -174,16 +186,20 @@
}
},
"todos": {
"name": "[%key:component::habitica::common::todos%]"
"name": "[%key:component::habitica::common::todos%]",
"unit_of_measurement": "[%key:component::habitica::common::unit_tasks%]"
},
"dailys": {
"name": "[%key:component::habitica::common::dailies%]"
"name": "[%key:component::habitica::common::dailies%]",
"unit_of_measurement": "[%key:component::habitica::common::unit_tasks%]"
},
"habits": {
"name": "Habits"
"name": "Habits",
"unit_of_measurement": "[%key:component::habitica::common::unit_tasks%]"
},
"rewards": {
"name": "Rewards"
"name": "Rewards",
"unit_of_measurement": "[%key:component::habitica::common::unit_tasks%]"
},
"strength": {
"name": "Strength",

View File

@@ -174,7 +174,7 @@ def get_attribute_points(
)
return {
"level": min(round(user["stats"]["lvl"] / 2), 50),
"level": min(floor(user["stats"]["lvl"] / 2), 50),
"equipment": equipment,
"class": class_bonus,
"allocated": user["stats"][attribute],

View File

@@ -22,7 +22,7 @@ import homeassistant.util.dt as dt_util
from . import websocket_api
from .const import DOMAIN
from .helpers import entities_may_have_state_changes_after, has_recorder_run_after
from .helpers import entities_may_have_state_changes_after, has_states_before
CONF_ORDER = "use_include_order"
@@ -107,7 +107,10 @@ class HistoryPeriodView(HomeAssistantView):
no_attributes = "no_attributes" in request.query
if (
(end_time and not has_recorder_run_after(hass, end_time))
# has_states_before will return True if there are states older than
# end_time. If it's false, we know there are no states in the
# database up until end_time.
(end_time and not has_states_before(hass, end_time))
or not include_start_time_state
and entity_ids
and not entities_may_have_state_changes_after(

View File

@@ -6,7 +6,6 @@ from collections.abc import Iterable
from datetime import datetime as dt
from homeassistant.components.recorder import get_instance
from homeassistant.components.recorder.models import process_timestamp
from homeassistant.core import HomeAssistant
@@ -26,8 +25,10 @@ def entities_may_have_state_changes_after(
return False
def has_recorder_run_after(hass: HomeAssistant, run_time: dt) -> bool:
"""Check if the recorder has any runs after a specific time."""
return run_time >= process_timestamp(
get_instance(hass).recorder_runs_manager.first.start
)
def has_states_before(hass: HomeAssistant, run_time: dt) -> bool:
"""Check if the recorder has states as old or older than run_time.
Returns True if there may be such states.
"""
oldest_ts = get_instance(hass).states_manager.oldest_ts
return oldest_ts is not None and run_time.timestamp() >= oldest_ts

View File

@@ -39,7 +39,7 @@ from homeassistant.util.async_ import create_eager_task
import homeassistant.util.dt as dt_util
from .const import EVENT_COALESCE_TIME, MAX_PENDING_HISTORY_STATES
from .helpers import entities_may_have_state_changes_after, has_recorder_run_after
from .helpers import entities_may_have_state_changes_after, has_states_before
_LOGGER = logging.getLogger(__name__)
@@ -142,7 +142,10 @@ async def ws_get_history_during_period(
no_attributes = msg["no_attributes"]
if (
(end_time and not has_recorder_run_after(hass, end_time))
# has_states_before will return True if there are states older than
# end_time. If it's false, we know there are no states in the
# database up until end_time.
(end_time and not has_states_before(hass, end_time))
or not include_start_time_state
and entity_ids
and not entities_may_have_state_changes_after(

View File

@@ -16,7 +16,7 @@ from homeassistant.components.light import (
LightEntityDescription,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
import homeassistant.util.color as color_util
@@ -150,7 +150,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
self.device.appliance.set_setting, self.bsh_key, True
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_on_light",
translation_placeholders={
@@ -169,7 +169,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
self._enable_custom_color_value_key,
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="select_light_custom_color",
translation_placeholders={
@@ -187,7 +187,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
f"#{hex_val}",
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_light_color",
translation_placeholders={
@@ -219,7 +219,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
f"#{hex_val}",
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_light_color",
translation_placeholders={
@@ -244,7 +244,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
self.device.appliance.set_setting, self._brightness_key, brightness
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_light_brightness",
translation_placeholders={
@@ -263,7 +263,7 @@ class HomeConnectLight(HomeConnectEntity, LightEntity):
self.device.appliance.set_setting, self.bsh_key, False
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_off_light",
translation_placeholders={

View File

@@ -12,7 +12,7 @@ from homeassistant.components.number import (
NumberEntityDescription,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import HomeConnectConfigEntry, get_dict_from_home_connect_error
@@ -117,7 +117,7 @@ class HomeConnectNumberEntity(HomeConnectEntity, NumberEntity):
value,
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_setting",
translation_placeholders={

View File

@@ -7,7 +7,7 @@ from homeconnect.api import HomeConnectError
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import (
@@ -140,12 +140,12 @@ TRANSLATION_KEYS_PROGRAMS_MAP = {
"Cooking.Oven.Program.HeatingMode.HotAir80Steam",
"Cooking.Oven.Program.HeatingMode.HotAir100Steam",
"Cooking.Oven.Program.HeatingMode.SabbathProgramme",
"Cooking.Oven.Program.Microwave90Watt",
"Cooking.Oven.Program.Microwave180Watt",
"Cooking.Oven.Program.Microwave360Watt",
"Cooking.Oven.Program.Microwave600Watt",
"Cooking.Oven.Program.Microwave900Watt",
"Cooking.Oven.Program.Microwave1000Watt",
"Cooking.Oven.Program.Microwave.90Watt",
"Cooking.Oven.Program.Microwave.180Watt",
"Cooking.Oven.Program.Microwave.360Watt",
"Cooking.Oven.Program.Microwave.600Watt",
"Cooking.Oven.Program.Microwave.900Watt",
"Cooking.Oven.Program.Microwave.1000Watt",
"Cooking.Oven.Program.Microwave.Max",
"Cooking.Oven.Program.HeatingMode.WarmingDrawer",
"LaundryCare.Washer.Program.Cotton",
@@ -289,7 +289,7 @@ class HomeConnectProgramSelectEntity(HomeConnectEntity, SelectEntity):
translation_key = "start_program"
else:
translation_key = "select_program"
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key=translation_key,
translation_placeholders={

View File

@@ -23,43 +23,43 @@
},
"exceptions": {
"turn_on_light": {
"message": "Error while trying to turn on {entity_id}: {description}"
"message": "Error turning on {entity_id}: {description}"
},
"turn_off_light": {
"message": "Error while trying to turn off {entity_id}: {description}"
"message": "Error turning off {entity_id}: {description}"
},
"set_light_brightness": {
"message": "Error while trying to set brightness of {entity_id}: {description}"
"message": "Error setting brightness of {entity_id}: {description}"
},
"select_light_custom_color": {
"message": "Error while trying to select custom color of {entity_id}: {description}"
"message": "Error selecting custom color of {entity_id}: {description}"
},
"set_light_color": {
"message": "Error while trying to set color of {entity_id}: {description}"
"message": "Error setting color of {entity_id}: {description}"
},
"set_setting": {
"message": "Error while trying to assign the value \"{value}\" to the setting \"{setting_key}\" for {entity_id}: {description}"
"message": "Error assigning the value \"{value}\" to the setting \"{setting_key}\" for {entity_id}: {description}"
},
"turn_on": {
"message": "Error while trying to turn on {entity_id} ({setting_key}): {description}"
"message": "Error turning on {entity_id} ({setting_key}): {description}"
},
"turn_off": {
"message": "Error while trying to turn off {entity_id} ({setting_key}): {description}"
"message": "Error turning off {entity_id} ({setting_key}): {description}"
},
"select_program": {
"message": "Error while trying to select program {program}: {description}"
"message": "Error selecting program {program}: {description}"
},
"start_program": {
"message": "Error while trying to start program {program}: {description}"
"message": "Error starting program {program}: {description}"
},
"stop_program": {
"message": "Error while trying to stop program {program}: {description}"
"message": "Error stopping program {program}: {description}"
},
"power_on": {
"message": "Error while trying to turn on {appliance_name}: {description}"
"message": "Error turning on {appliance_name}: {description}"
},
"power_off": {
"message": "Error while trying to turn off {appliance_name} with value \"{value}\": {description}"
"message": "Error turning off {appliance_name} with value \"{value}\": {description}"
},
"turn_off_not_supported": {
"message": "{appliance_name} does not support turning off or entering standby mode."

View File

@@ -8,7 +8,7 @@ from homeconnect.api import HomeConnectError
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import HomeConnectConfigEntry, get_dict_from_home_connect_error
@@ -134,7 +134,7 @@ class HomeConnectSwitch(HomeConnectEntity, SwitchEntity):
)
except HomeConnectError as err:
self._attr_available = False
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_on",
translation_placeholders={
@@ -158,7 +158,7 @@ class HomeConnectSwitch(HomeConnectEntity, SwitchEntity):
except HomeConnectError as err:
_LOGGER.error("Error while trying to turn off: %s", err)
self._attr_available = False
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_off",
translation_placeholders={
@@ -209,7 +209,7 @@ class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
self.device.appliance.start_program, self.program_name
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="start_program",
translation_placeholders={
@@ -225,7 +225,7 @@ class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
try:
await self.hass.async_add_executor_job(self.device.appliance.stop_program)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="stop_program",
translation_placeholders={
@@ -278,7 +278,7 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
)
except HomeConnectError as err:
self._attr_is_on = False
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="power_on",
translation_placeholders={
@@ -291,7 +291,7 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
async def async_turn_off(self, **kwargs: Any) -> None:
"""Switch the device off."""
if not hasattr(self, "power_off_state"):
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="unable_to_retrieve_turn_off",
translation_placeholders={
@@ -300,7 +300,7 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
)
if self.power_off_state is None:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="turn_off_not_supported",
translation_placeholders={
@@ -316,7 +316,7 @@ class HomeConnectPowerSwitch(HomeConnectEntity, SwitchEntity):
)
except HomeConnectError as err:
self._attr_is_on = True
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="power_off",
translation_placeholders={

View File

@@ -7,7 +7,7 @@ from homeconnect.api import HomeConnectError
from homeassistant.components.time import TimeEntity, TimeEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import HomeConnectConfigEntry, get_dict_from_home_connect_error
@@ -80,7 +80,7 @@ class HomeConnectTimeEntity(HomeConnectEntity, TimeEntity):
time_to_seconds(value),
)
except HomeConnectError as err:
raise ServiceValidationError(
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="set_setting",
translation_placeholders={

View File

@@ -14,6 +14,6 @@
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
"iot_class": "local_push",
"loggers": ["aiohomekit", "commentjson"],
"requirements": ["aiohomekit==3.2.6"],
"requirements": ["aiohomekit==3.2.7"],
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
}

View File

@@ -326,7 +326,8 @@ class HomeAssistantApplication(web.Application):
protocol,
writer,
task,
loop=self._loop,
# loop will never be None when called from aiohttp
loop=self._loop, # type: ignore[arg-type]
client_max_size=self._client_max_size,
)

View File

@@ -4,7 +4,6 @@ from __future__ import annotations
from datetime import timedelta
from enum import StrEnum
from functools import partial
import logging
from typing import Any, final
@@ -22,11 +21,6 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.deprecation import (
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType
@@ -34,9 +28,6 @@ from homeassistant.loader import bind_hass
from homeassistant.util.hass_dict import HassKey
from .const import ( # noqa: F401
_DEPRECATED_DEVICE_CLASS_DEHUMIDIFIER,
_DEPRECATED_DEVICE_CLASS_HUMIDIFIER,
_DEPRECATED_SUPPORT_MODES,
ATTR_ACTION,
ATTR_AVAILABLE_MODES,
ATTR_CURRENT_HUMIDITY,
@@ -314,13 +305,3 @@ async def async_service_humidity_set(
)
await entity.async_set_humidity(humidity)
# 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())

View File

@@ -1,15 +1,6 @@
"""Provides the constants needed for component."""
from enum import IntFlag, StrEnum
from functools import partial
from homeassistant.helpers.deprecation import (
DeprecatedConstant,
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
MODE_NORMAL = "normal"
MODE_ECO = "eco"
@@ -43,15 +34,6 @@ DEFAULT_MAX_HUMIDITY = 100
DOMAIN = "humidifier"
# DEVICE_CLASS_* below are deprecated as of 2021.12
# use the HumidifierDeviceClass enum instead.
_DEPRECATED_DEVICE_CLASS_HUMIDIFIER = DeprecatedConstant(
"humidifier", "HumidifierDeviceClass.HUMIDIFIER", "2025.1"
)
_DEPRECATED_DEVICE_CLASS_DEHUMIDIFIER = DeprecatedConstant(
"dehumidifier", "HumidifierDeviceClass.DEHUMIDIFIER", "2025.1"
)
SERVICE_SET_MODE = "set_mode"
SERVICE_SET_HUMIDITY = "set_humidity"
@@ -60,17 +42,3 @@ class HumidifierEntityFeature(IntFlag):
"""Supported features of the humidifier entity."""
MODES = 1
# The SUPPORT_MODES constant is deprecated as of Home Assistant 2022.5.
# Please use the HumidifierEntityFeature enum instead.
_DEPRECATED_SUPPORT_MODES = DeprecatedConstantEnum(
HumidifierEntityFeature.MODES, "2025.1"
)
# 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())

View File

@@ -364,7 +364,7 @@ class NevermindIntentHandler(intent.IntentHandler):
"""Takes no action."""
intent_type = intent.INTENT_NEVERMIND
description = "Cancels the current request and does nothing"
description = "Cancel the current conversation if it was started by mistake or the user wants it to stop."
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
"""Do nothing and produces an empty response."""

View File

@@ -13,5 +13,5 @@
"documentation": "https://www.home-assistant.io/integrations/iron_os",
"iot_class": "local_polling",
"loggers": ["pynecil", "aiogithubapi"],
"requirements": ["pynecil==0.2.1", "aiogithubapi==24.6.0"]
"requirements": ["pynecil==1.0.1", "aiogithubapi==24.6.0"]
}

View File

@@ -23,6 +23,8 @@ from . import IronOSConfigEntry
from .const import DOMAIN, MAX_TEMP, MIN_TEMP
from .entity import IronOSBaseEntity
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class IronOSNumberEntityDescription(NumberEntityDescription):

View File

@@ -107,6 +107,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
native_unit_of_measurement=OHM,
value_fn=lambda data: data.tip_resistance,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
),
IronOSSensorEntityDescription(
key=PinecilSensor.UPTIME,
@@ -137,7 +138,7 @@ PINECIL_SENSOR_DESCRIPTIONS: tuple[IronOSSensorEntityDescription, ...] = (
IronOSSensorEntityDescription(
key=PinecilSensor.TIP_VOLTAGE,
translation_key=PinecilSensor.TIP_VOLTAGE,
native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT,
native_unit_of_measurement=UnitOfElectricPotential.MICROVOLT,
device_class=SensorDeviceClass.VOLTAGE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=3,

View File

@@ -15,6 +15,8 @@ from . import IRON_OS_KEY, IronOSConfigEntry, IronOSLiveDataCoordinator
from .coordinator import IronOSFirmwareUpdateCoordinator
from .entity import IronOSBaseEntity
PARALLEL_UPDATES = 0
UPDATE_DESCRIPTION = UpdateEntityDescription(
key="firmware",
device_class=UpdateDeviceClass.FIRMWARE,

View File

@@ -71,7 +71,6 @@ SENSOR_DESCRIPTIONS: tuple[IstaSensorEntityDescription, ...] = (
translation_key=IstaSensorEntity.HEATING,
suggested_display_precision=0,
consumption_type=IstaConsumptionType.HEATING,
native_unit_of_measurement="units",
state_class=SensorStateClass.TOTAL,
),
IstaSensorEntityDescription(

View File

@@ -38,7 +38,8 @@
"entity": {
"sensor": {
"heating": {
"name": "Heating"
"name": "Heating",
"unit_of_measurement": "units"
},
"heating_cost": {
"name": "Heating cost"

View File

@@ -54,6 +54,7 @@ from .const import (
CONF_KNX_SECURE_USER_PASSWORD,
CONF_KNX_STATE_UPDATER,
CONF_KNX_TELEGRAM_LOG_SIZE,
CONF_KNX_TUNNEL_ENDPOINT_IA,
CONF_KNX_TUNNELING,
CONF_KNX_TUNNELING_TCP,
CONF_KNX_TUNNELING_TCP_SECURE,
@@ -352,6 +353,7 @@ class KNXModule:
if _conn_type == CONF_KNX_TUNNELING_TCP:
return ConnectionConfig(
connection_type=ConnectionType.TUNNELING_TCP,
individual_address=self.entry.data.get(CONF_KNX_TUNNEL_ENDPOINT_IA),
gateway_ip=self.entry.data[CONF_HOST],
gateway_port=self.entry.data[CONF_PORT],
auto_reconnect=True,
@@ -364,6 +366,7 @@ class KNXModule:
if _conn_type == CONF_KNX_TUNNELING_TCP_SECURE:
return ConnectionConfig(
connection_type=ConnectionType.TUNNELING_TCP_SECURE,
individual_address=self.entry.data.get(CONF_KNX_TUNNEL_ENDPOINT_IA),
gateway_ip=self.entry.data[CONF_HOST],
gateway_port=self.entry.data[CONF_PORT],
secure_config=SecureConfig(

View File

@@ -104,7 +104,7 @@ class KNXConfigEntryData(TypedDict, total=False):
route_back: bool # not required
host: str # only required for tunnelling
port: int # only required for tunnelling
tunnel_endpoint_ia: str | None
tunnel_endpoint_ia: str | None # tunnelling only - not required (use get())
# KNX secure
user_id: int | None # not required
user_password: str | None # not required

View File

@@ -291,6 +291,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
CONF_ADDRESS: discovery_info.macaddress,
}
)
self._async_abort_entries_match({CONF_ADDRESS: discovery_info.macaddress})
_LOGGER.debug(
"Discovered La Marzocco machine %s through DHCP at address %s",

View File

@@ -36,5 +36,5 @@
"integration_type": "device",
"iot_class": "cloud_polling",
"loggers": ["pylamarzocco"],
"requirements": ["pylamarzocco==1.2.11"]
"requirements": ["pylamarzocco==1.2.12"]
}

View File

@@ -67,8 +67,10 @@
"step": {
"init": {
"data": {
"title": "Update Configuration",
"use_bluetooth": "Use Bluetooth"
},
"data_description": {
"use_bluetooth": "Should the integration try to use Bluetooth to control the machine?"
}
}
}

View File

@@ -31,7 +31,6 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
@@ -67,10 +66,6 @@ class LockEntityFeature(IntFlag):
OPEN = 1
# The SUPPORT_OPEN constant is deprecated as of Home Assistant 2022.5.
# Please use the LockEntityFeature enum instead.
_DEPRECATED_SUPPORT_OPEN = DeprecatedConstantEnum(LockEntityFeature.OPEN, "2025.1")
PROP_TO_ATTR = {"changed_by": ATTR_CHANGED_BY, "code_format": ATTR_CODE_FORMAT}
# mypy: disallow-any-generics

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/met_eireann",
"iot_class": "cloud_polling",
"loggers": ["meteireann"],
"requirements": ["PyMetEireann==2021.8.0"]
"requirements": ["PyMetEireann==2024.11.0"]
}

View File

@@ -86,7 +86,10 @@ rules:
comment: >
This is not possible because the integrations generates entities
based on a user supplied config or discovery.
reconfiguration-flow: done
reconfiguration-flow:
status: exempt
comment: >
This integration is reconfigured via options flow.
dynamic-devices:
status: done
comment: |

View File

@@ -7,6 +7,6 @@
"documentation": "https://www.home-assistant.io/integrations/music_assistant",
"iot_class": "local_push",
"loggers": ["music_assistant"],
"requirements": ["music-assistant-client==1.0.5"],
"requirements": ["music-assistant-client==1.0.8"],
"zeroconf": ["_mass._tcp.local."]
}

View File

@@ -193,7 +193,7 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
super().__init__(mass, player_id)
self._attr_icon = self.player.icon.replace("mdi-", "mdi:")
self._attr_supported_features = SUPPORTED_FEATURES
if PlayerFeature.SYNC in self.player.supported_features:
if PlayerFeature.SET_MEMBERS in self.player.supported_features:
self._attr_supported_features |= MediaPlayerEntityFeature.GROUPING
if PlayerFeature.VOLUME_MUTE in self.player.supported_features:
self._attr_supported_features |= MediaPlayerEntityFeature.VOLUME_MUTE
@@ -400,19 +400,19 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
async def async_join_players(self, group_members: list[str]) -> None:
"""Join `group_members` as a player group with the current player."""
player_ids: list[str] = []
entity_registry = er.async_get(self.hass)
for child_entity_id in group_members:
# resolve HA entity_id to MA player_id
if (hass_state := self.hass.states.get(child_entity_id)) is None:
continue
if (mass_player_id := hass_state.attributes.get("mass_player_id")) is None:
continue
player_ids.append(mass_player_id)
await self.mass.players.player_command_sync_many(self.player_id, player_ids)
if not (entity_reg_entry := entity_registry.async_get(child_entity_id)):
raise HomeAssistantError(f"Entity {child_entity_id} not found")
# unique id is the MA player_id
player_ids.append(entity_reg_entry.unique_id)
await self.mass.players.player_command_group_many(self.player_id, player_ids)
@catch_musicassistant_error
async def async_unjoin_player(self) -> None:
"""Remove this player from any group."""
await self.mass.players.player_command_unsync(self.player_id)
await self.mass.players.player_command_ungroup(self.player_id)
@catch_musicassistant_error
async def _async_handle_play_media(

View File

@@ -59,9 +59,7 @@ from .const import (
CONF_SUBSCRIBER_ID,
CONF_SUBSCRIBER_ID_IMPORTED,
CONF_SUBSCRIPTION_NAME,
DATA_DEVICE_MANAGER,
DATA_SDM,
DATA_SUBSCRIBER,
DOMAIN,
)
from .events import EVENT_NAME_MAP, NEST_EVENT
@@ -72,6 +70,7 @@ from .media_source import (
async_get_media_source_devices,
async_get_transcoder,
)
from .types import NestConfigEntry, NestData
_LOGGER = logging.getLogger(__name__)
@@ -113,11 +112,8 @@ THUMBNAIL_SIZE_PX = 175
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Nest components with dispatch between old/new flows."""
hass.data[DOMAIN] = {}
hass.http.register_view(NestEventMediaView(hass))
hass.http.register_view(NestEventMediaThumbnailView(hass))
return True
@@ -128,12 +124,12 @@ class SignalUpdateCallback:
self,
hass: HomeAssistant,
config_reload_cb: Callable[[], Awaitable[None]],
config_entry_id: str,
config_entry: NestConfigEntry,
) -> None:
"""Initialize EventCallback."""
self._hass = hass
self._config_reload_cb = config_reload_cb
self._config_entry_id = config_entry_id
self._config_entry = config_entry
async def async_handle_event(self, event_message: EventMessage) -> None:
"""Process an incoming EventMessage."""
@@ -181,17 +177,17 @@ class SignalUpdateCallback:
message["zones"] = image_event.zones
self._hass.bus.async_fire(NEST_EVENT, message)
def _supported_traits(self, device_id: str) -> list[TraitType]:
if not (
device_manager := self._hass.data[DOMAIN]
.get(self._config_entry_id, {})
.get(DATA_DEVICE_MANAGER)
) or not (device := device_manager.devices.get(device_id)):
def _supported_traits(self, device_id: str) -> list[str]:
if (
not self._config_entry.runtime_data
or not (device_manager := self._config_entry.runtime_data.device_manager)
or not (device := device_manager.devices.get(device_id))
):
return []
return list(device.traits)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: NestConfigEntry) -> bool:
"""Set up Nest from a config entry with dispatch between old/new flows."""
if DATA_SDM not in entry.data:
hass.async_create_task(hass.config_entries.async_remove(entry.entry_id))
@@ -215,7 +211,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_config_reload() -> None:
await hass.config_entries.async_reload(entry.entry_id)
update_callback = SignalUpdateCallback(hass, async_config_reload, entry.entry_id)
update_callback = SignalUpdateCallback(hass, async_config_reload, entry)
subscriber.set_update_callback(update_callback.async_handle_event)
try:
await subscriber.start_async()
@@ -245,11 +241,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)
)
hass.data[DOMAIN][entry.entry_id] = {
DATA_SUBSCRIBER: subscriber,
DATA_DEVICE_MANAGER: device_manager,
}
entry.runtime_data = NestData(
subscriber=subscriber,
device_manager=device_manager,
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -262,13 +257,9 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Legacy API
return True
_LOGGER.debug("Stopping nest subscriber")
subscriber = hass.data[DOMAIN][entry.entry_id][DATA_SUBSCRIBER]
subscriber = entry.runtime_data.subscriber
subscriber.stop_async()
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:

View File

@@ -17,7 +17,6 @@ from google_nest_sdm.camera_traits import (
WebRtcStream,
)
from google_nest_sdm.device import Device
from google_nest_sdm.device_manager import DeviceManager
from google_nest_sdm.exceptions import ApiException
from webrtc_models import RTCIceCandidateInit
@@ -29,15 +28,14 @@ from homeassistant.components.camera import (
WebRTCSendMessage,
)
from homeassistant.components.stream import CONF_EXTRA_PART_WAIT_TIME
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow
from .const import DATA_DEVICE_MANAGER, DOMAIN
from .device_info import NestDeviceInfo
from .types import NestConfigEntry
_LOGGER = logging.getLogger(__name__)
@@ -53,15 +51,12 @@ BACKOFF_MULTIPLIER = 1.5
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant, entry: NestConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the cameras."""
device_manager: DeviceManager = hass.data[DOMAIN][entry.entry_id][
DATA_DEVICE_MANAGER
]
entities: list[NestCameraBaseEntity] = []
for device in device_manager.devices.values():
for device in entry.runtime_data.device_manager.devices.values():
if (live_stream := device.traits.get(CameraLiveStreamTrait.NAME)) is None:
continue
if StreamingProtocol.WEB_RTC in live_stream.supported_protocols:

View File

@@ -5,7 +5,6 @@ from __future__ import annotations
from typing import Any, cast
from google_nest_sdm.device import Device
from google_nest_sdm.device_manager import DeviceManager
from google_nest_sdm.device_traits import FanTrait, TemperatureTrait
from google_nest_sdm.exceptions import ApiException
from google_nest_sdm.thermostat_traits import (
@@ -28,14 +27,13 @@ from homeassistant.components.climate import (
HVACAction,
HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_DEVICE_MANAGER, DOMAIN
from .device_info import NestDeviceInfo
from .types import NestConfigEntry
# Mapping for sdm.devices.traits.ThermostatMode mode field
THERMOSTAT_MODE_MAP: dict[str, HVACMode] = {
@@ -78,17 +76,13 @@ MIN_TEMP_RANGE = 1.66667
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant, entry: NestConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the client entities."""
device_manager: DeviceManager = hass.data[DOMAIN][entry.entry_id][
DATA_DEVICE_MANAGER
]
async_add_entities(
ThermostatEntity(device)
for device in device_manager.devices.values()
for device in entry.runtime_data.device_manager.devices.values()
if ThermostatHvacTrait.NAME in device.traits
)

View File

@@ -2,8 +2,6 @@
DOMAIN = "nest"
DATA_SDM = "sdm"
DATA_SUBSCRIBER = "subscriber"
DATA_DEVICE_MANAGER = "device_manager"
WEB_AUTH_DOMAIN = DOMAIN
INSTALLED_AUTH_DOMAIN = f"{DOMAIN}.installed"

View File

@@ -7,11 +7,12 @@ from collections.abc import Mapping
from google_nest_sdm.device import Device
from google_nest_sdm.device_traits import ConnectivityTrait, InfoTrait
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from .const import CONNECTIVITY_TRAIT_OFFLINE, DATA_DEVICE_MANAGER, DOMAIN
from .const import CONNECTIVITY_TRAIT_OFFLINE, DOMAIN
DEVICE_TYPE_MAP: dict[str, str] = {
"sdm.devices.types.CAMERA": "Camera",
@@ -81,14 +82,12 @@ class NestDeviceInfo:
@callback
def async_nest_devices(hass: HomeAssistant) -> Mapping[str, Device]:
"""Return a mapping of all nest devices for all config entries."""
devices = {}
for entry_id in hass.data[DOMAIN]:
if not (device_manager := hass.data[DOMAIN][entry_id].get(DATA_DEVICE_MANAGER)):
continue
devices.update(
{device.name: device for device in device_manager.devices.values()}
)
return devices
return {
device.name: device
for config_entry in hass.config_entries.async_entries(DOMAIN)
if config_entry.state == ConfigEntryState.LOADED
for device in config_entry.runtime_data.device_manager.devices.values()
}
@callback

View File

@@ -5,46 +5,26 @@ from __future__ import annotations
from typing import Any
from google_nest_sdm import diagnostics
from google_nest_sdm.device import Device
from google_nest_sdm.device_manager import DeviceManager
from google_nest_sdm.device_traits import InfoTrait
from homeassistant.components.camera import diagnostics as camera_diagnostics
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntry
from .const import DATA_DEVICE_MANAGER, DATA_SDM, DOMAIN
from .types import NestConfigEntry
REDACT_DEVICE_TRAITS = {InfoTrait.NAME}
@callback
def _async_get_nest_devices(
hass: HomeAssistant, config_entry: ConfigEntry
) -> dict[str, Device]:
"""Return dict of available devices."""
if DATA_SDM not in config_entry.data:
return {}
if (
config_entry.entry_id not in hass.data[DOMAIN]
or DATA_DEVICE_MANAGER not in hass.data[DOMAIN][config_entry.entry_id]
):
return {}
device_manager: DeviceManager = hass.data[DOMAIN][config_entry.entry_id][
DATA_DEVICE_MANAGER
]
return device_manager.devices
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry
hass: HomeAssistant, config_entry: NestConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
nest_devices = _async_get_nest_devices(hass, config_entry)
if not nest_devices:
if (
not hasattr(config_entry, "runtime_data")
or not config_entry.runtime_data
or not (nest_devices := config_entry.runtime_data.device_manager.devices)
):
return {}
data: dict[str, Any] = {
**diagnostics.get_diagnostics(),
@@ -62,11 +42,11 @@ async def async_get_config_entry_diagnostics(
async def async_get_device_diagnostics(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: NestConfigEntry,
device: DeviceEntry,
) -> dict[str, Any]:
"""Return diagnostics for a device."""
nest_devices = _async_get_nest_devices(hass, config_entry)
nest_devices = config_entry.runtime_data.device_manager.devices
nest_device_id = next(iter(device.identifiers))[1]
nest_device = nest_devices.get(nest_device_id)
return nest_device.get_diagnostics() if nest_device else {}

View File

@@ -4,7 +4,6 @@ from dataclasses import dataclass
import logging
from google_nest_sdm.device import Device
from google_nest_sdm.device_manager import DeviceManager
from google_nest_sdm.event import EventMessage, EventType
from google_nest_sdm.traits import TraitType
@@ -13,11 +12,9 @@ from homeassistant.components.event import (
EventEntity,
EventEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_DEVICE_MANAGER, DOMAIN
from .device_info import NestDeviceInfo
from .events import (
EVENT_CAMERA_MOTION,
@@ -26,6 +23,7 @@ from .events import (
EVENT_DOORBELL_CHIME,
EVENT_NAME_MAP,
)
from .types import NestConfigEntry
_LOGGER = logging.getLogger(__name__)
@@ -68,16 +66,12 @@ ENTITY_DESCRIPTIONS = [
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant, entry: NestConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the sensors."""
device_manager: DeviceManager = hass.data[DOMAIN][entry.entry_id][
DATA_DEVICE_MANAGER
]
async_add_entities(
NestTraitEventEntity(desc, device)
for device in device_manager.devices.values()
for device in entry.runtime_data.device_manager.devices.values()
for desc in ENTITY_DESCRIPTIONS
if any(trait in device.traits for trait in desc.trait_types)
)

View File

@@ -5,7 +5,6 @@ from __future__ import annotations
import logging
from google_nest_sdm.device import Device
from google_nest_sdm.device_manager import DeviceManager
from google_nest_sdm.device_traits import HumidityTrait, TemperatureTrait
from homeassistant.components.sensor import (
@@ -13,13 +12,12 @@ from homeassistant.components.sensor import (
SensorEntity,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DATA_DEVICE_MANAGER, DOMAIN
from .device_info import NestDeviceInfo
from .types import NestConfigEntry
_LOGGER = logging.getLogger(__name__)
@@ -33,15 +31,12 @@ DEVICE_TYPE_MAP = {
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant, entry: NestConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up the sensors."""
device_manager: DeviceManager = hass.data[DOMAIN][entry.entry_id][
DATA_DEVICE_MANAGER
]
entities: list[SensorEntity] = []
for device in device_manager.devices.values():
for device in entry.runtime_data.device_manager.devices.values():
if TemperatureTrait.NAME in device.traits:
entities.append(TemperatureSensor(device))
if HumidityTrait.NAME in device.traits:

View File

@@ -0,0 +1,19 @@
"""Type definitions for Nest."""
from dataclasses import dataclass
from google_nest_sdm.device_manager import DeviceManager
from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber
from homeassistant.config_entries import ConfigEntry
@dataclass
class NestData:
"""Data for the Nest integration."""
subscriber: GoogleNestSubscriber
device_manager: DeviceManager
type NestConfigEntry = ConfigEntry[NestData]

View File

@@ -6,12 +6,20 @@
"data": {
"email": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"email": "[%key:component::nice_go::config::step::user::data_description::email%]",
"password": "[%key:component::nice_go::config::step::user::data_description::password%]"
}
},
"user": {
"data": {
"email": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"email": "The email address used to log in to the Nice G.O. app",
"password": "The password used to log in to the Nice G.O. app"
}
}
},

View File

@@ -3,7 +3,6 @@
from __future__ import annotations
from enum import StrEnum
from functools import partial
from typing import Final
import voluptuous as vol
@@ -41,12 +40,6 @@ from homeassistant.const import (
UnitOfVolumeFlowRate,
UnitOfVolumetricFlux,
)
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.util.unit_conversion import (
BaseUnitConverter,
TemperatureConverter,
@@ -76,12 +69,6 @@ class NumberMode(StrEnum):
SLIDER = "slider"
# MODE_* are deprecated as of 2021.12, use the NumberMode enum instead.
_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")
class NumberDeviceClass(StrEnum):
"""Device class for numbers."""
@@ -519,10 +506,3 @@ UNIT_CONVERTERS: dict[NumberDeviceClass, type[BaseUnitConverter]] = {
NumberDeviceClass.TEMPERATURE: TemperatureConverter,
NumberDeviceClass.VOLUME_FLOW_RATE: VolumeFlowRateConverter,
}
# 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())

View File

@@ -30,6 +30,7 @@ from homeassistant.exceptions import HomeAssistantError, TemplateError
from homeassistant.helpers import device_registry as dr, intent, llm, template
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import ulid
from homeassistant.util.json import JsonObjectType
from . import OpenAIConfigEntry
from .const import (
@@ -292,6 +293,8 @@ class OpenAIConversationEntity(
if not tool_calls or not llm_api:
break
aborting = False
for tool_call in tool_calls:
tool_input = llm.ToolInput(
tool_name=tool_call.function.name,
@@ -301,12 +304,25 @@ class OpenAIConversationEntity(
"Tool call: %s(%s)", tool_input.tool_name, tool_input.tool_args
)
try:
tool_response = await llm_api.async_call_tool(tool_input)
except (HomeAssistantError, vol.Invalid) as e:
tool_response = {"error": type(e).__name__}
if str(e):
tool_response["error_text"] = str(e)
# OpenAI requires a tool response for every tool call in history
if aborting:
tool_response: JsonObjectType = {
"error": "Aborted",
"error_text": "Abort conversation requested",
}
if not aborting:
try:
tool_response = await llm_api.async_call_tool(tool_input)
except llm.AbortConversation as e:
aborting = True
tool_response = {
"error": "Aborted",
"error_text": str(e) or "Abort conversation requested",
}
except (HomeAssistantError, vol.Invalid) as e:
tool_response = {"error": type(e).__name__}
if str(e):
tool_response["error_text"] = str(e)
LOGGER.debug("Tool response: %s", tool_response)
messages.append(
@@ -317,6 +333,9 @@ class OpenAIConversationEntity(
)
)
if aborting:
break
self.history[conversation_id] = messages
intent_response = intent.IntentResponse(language=user_input.language)

View File

@@ -65,8 +65,6 @@ def _async_register_clientsession_shutdown(
async def async_setup_entry(hass: HomeAssistant, entry: RainbirdConfigEntry) -> bool:
"""Set up the config entry for Rain Bird."""
hass.data.setdefault(DOMAIN, {})
clientsession = async_create_clientsession()
_async_register_clientsession_shutdown(hass, entry, clientsession)

View File

@@ -40,6 +40,9 @@
"title": "[%key:component::rainbird::config::step::user::title%]",
"data": {
"duration": "Default irrigation time in minutes"
},
"data_description": {
"duration": "The default duration the sprinkler will run when turned on."
}
}
}

View File

@@ -1424,6 +1424,7 @@ class Recorder(threading.Thread):
with session_scope(session=self.get_session()) as session:
end_incomplete_runs(session, self.recorder_runs_manager.recording_start)
self.recorder_runs_manager.start(session)
self.states_manager.load_from_db(session)
self._open_event_session()

View File

@@ -22,9 +22,9 @@ from homeassistant.core import HomeAssistant, State, split_entity_id
from homeassistant.helpers.recorder import get_instance
import homeassistant.util.dt as dt_util
from ..db_schema import RecorderRuns, StateAttributes, States
from ..db_schema import StateAttributes, States
from ..filters import Filters
from ..models import process_timestamp, process_timestamp_to_utc_isoformat
from ..models import process_timestamp_to_utc_isoformat
from ..models.legacy import LegacyLazyState, legacy_row_to_compressed_state
from ..util import execute_stmt_lambda_element, session_scope
from .const import (
@@ -436,7 +436,7 @@ def get_last_state_changes(
def _get_states_for_entities_stmt(
run_start: datetime,
run_start_ts: float,
utc_point_in_time: datetime,
entity_ids: list[str],
no_attributes: bool,
@@ -447,8 +447,7 @@ def _get_states_for_entities_stmt(
)
# We got an include-list of entities, accelerate the query by filtering already
# in the inner query.
run_start_ts = process_timestamp(run_start).timestamp()
utc_point_in_time_ts = dt_util.utc_to_timestamp(utc_point_in_time)
utc_point_in_time_ts = utc_point_in_time.timestamp()
stmt += lambda q: q.join(
(
most_recent_states_for_entities_by_date := (
@@ -483,7 +482,7 @@ def _get_rows_with_session(
session: Session,
utc_point_in_time: datetime,
entity_ids: list[str],
run: RecorderRuns | None = None,
*,
no_attributes: bool = False,
) -> Iterable[Row]:
"""Return the states at a specific point in time."""
@@ -495,17 +494,16 @@ def _get_rows_with_session(
),
)
if run is None:
run = get_instance(hass).recorder_runs_manager.get(utc_point_in_time)
oldest_ts = get_instance(hass).states_manager.oldest_ts
if run is None or process_timestamp(run.start) > utc_point_in_time:
# History did not run before utc_point_in_time
if oldest_ts is None or oldest_ts > utc_point_in_time.timestamp():
# We don't have any states for the requested time
return []
# We have more than one entity to look at so we need to do a query on states
# since the last recorder run started.
stmt = _get_states_for_entities_stmt(
run.start, utc_point_in_time, entity_ids, no_attributes
oldest_ts, utc_point_in_time, entity_ids, no_attributes
)
return execute_stmt_lambda_element(session, stmt)
@@ -520,7 +518,7 @@ def _get_single_entity_states_stmt(
stmt, join_attributes = _lambda_stmt_and_join_attributes(
no_attributes, include_last_changed=True
)
utc_point_in_time_ts = dt_util.utc_to_timestamp(utc_point_in_time)
utc_point_in_time_ts = utc_point_in_time.timestamp()
stmt += (
lambda q: q.filter(
States.last_updated_ts < utc_point_in_time_ts,

View File

@@ -34,7 +34,6 @@ from ..models import (
LazyState,
datetime_to_timestamp_or_none,
extract_metadata_ids,
process_timestamp,
row_to_compressed_state,
)
from ..util import execute_stmt_lambda_element, session_scope
@@ -246,12 +245,12 @@ def get_significant_states_with_session(
if metadata_id is not None
and split_entity_id(entity_id)[0] in SIGNIFICANT_DOMAINS
]
run_start_ts: float | None = None
oldest_ts: float | None = None
if include_start_time_state and not (
run_start_ts := _get_run_start_ts_for_utc_point_in_time(hass, start_time)
oldest_ts := _get_oldest_possible_ts(hass, start_time)
):
include_start_time_state = False
start_time_ts = dt_util.utc_to_timestamp(start_time)
start_time_ts = start_time.timestamp()
end_time_ts = datetime_to_timestamp_or_none(end_time)
single_metadata_id = metadata_ids[0] if len(metadata_ids) == 1 else None
stmt = lambda_stmt(
@@ -264,7 +263,7 @@ def get_significant_states_with_session(
significant_changes_only,
no_attributes,
include_start_time_state,
run_start_ts,
oldest_ts,
),
track_on=[
bool(single_metadata_id),
@@ -411,12 +410,12 @@ def state_changes_during_period(
entity_id_to_metadata_id: dict[str, int | None] = {
entity_id: single_metadata_id
}
run_start_ts: float | None = None
oldest_ts: float | None = None
if include_start_time_state and not (
run_start_ts := _get_run_start_ts_for_utc_point_in_time(hass, start_time)
oldest_ts := _get_oldest_possible_ts(hass, start_time)
):
include_start_time_state = False
start_time_ts = dt_util.utc_to_timestamp(start_time)
start_time_ts = start_time.timestamp()
end_time_ts = datetime_to_timestamp_or_none(end_time)
stmt = lambda_stmt(
lambda: _state_changed_during_period_stmt(
@@ -426,7 +425,7 @@ def state_changes_during_period(
no_attributes,
limit,
include_start_time_state,
run_start_ts,
oldest_ts,
has_last_reported,
),
track_on=[
@@ -600,17 +599,17 @@ def _get_start_time_state_for_entities_stmt(
)
def _get_run_start_ts_for_utc_point_in_time(
def _get_oldest_possible_ts(
hass: HomeAssistant, utc_point_in_time: datetime
) -> float | None:
"""Return the start time of a run."""
run = get_instance(hass).recorder_runs_manager.get(utc_point_in_time)
if (
run is not None
and (run_start := process_timestamp(run.start)) < utc_point_in_time
):
return run_start.timestamp()
# History did not run before utc_point_in_time but we still
"""Return the oldest possible timestamp.
Returns None if there are no states as old as utc_point_in_time.
"""
oldest_ts = get_instance(hass).states_manager.oldest_ts
if oldest_ts is not None and oldest_ts < utc_point_in_time.timestamp():
return oldest_ts
return None

View File

@@ -2742,7 +2742,10 @@ class EventIDPostMigration(BaseRunTimeMigration):
class EntityIDPostMigration(BaseMigrationWithQuery, BaseRunTimeMigration):
"""Migration to remove old entity_id strings from states."""
"""Migration to remove old entity_id strings from states.
Introduced in HA Core 2023.4 by PR #89557.
"""
migration_id = "entity_id_post_migration"
task = MigrationTask
@@ -2764,9 +2767,9 @@ NON_LIVE_DATA_MIGRATORS = (
)
LIVE_DATA_MIGRATORS = (
EventTypeIDMigration,
EntityIDMigration,
EventIDPostMigration,
EventTypeIDMigration, # Introduced in HA Core 2023.4 by PR #89465
EntityIDMigration, # Introduced in HA Core 2023.4 by PR #89557
EventIDPostMigration, # Introduced in HA Core 2023.4 by PR #89901
)

View File

@@ -46,7 +46,7 @@ class LegacyLazyState(State):
self.state = self._row.state or ""
self._attributes: dict[str, Any] | None = None
self._last_updated_ts: float | None = self._row.last_updated_ts or (
dt_util.utc_to_timestamp(start_time) if start_time else None
start_time.timestamp() if start_time else None
)
self._last_changed_ts: float | None = (
self._row.last_changed_ts or self._last_updated_ts
@@ -146,7 +146,7 @@ def legacy_row_to_compressed_state(
COMPRESSED_STATE_ATTRIBUTES: decode_attributes_from_row_legacy(row, attr_cache),
}
if start_time:
comp_state[COMPRESSED_STATE_LAST_UPDATED] = dt_util.utc_to_timestamp(start_time)
comp_state[COMPRESSED_STATE_LAST_UPDATED] = start_time.timestamp()
else:
row_last_updated_ts: float = row.last_updated_ts
comp_state[COMPRESSED_STATE_LAST_UPDATED] = row_last_updated_ts

View File

@@ -123,6 +123,9 @@ def purge_old_data(
_purge_old_entity_ids(instance, session)
_purge_old_recorder_runs(instance, session, purge_before)
with session_scope(session=instance.get_session(), read_only=True) as session:
instance.recorder_runs_manager.load_from_db(session)
instance.states_manager.load_from_db(session)
if repack:
repack_database(instance)
return True

View File

@@ -637,6 +637,15 @@ def find_states_to_purge(
)
def find_oldest_state() -> StatementLambdaElement:
"""Find the last_updated_ts of the oldest state."""
return lambda_stmt(
lambda: select(States.last_updated_ts).where(
States.state_id.in_(select(func.min(States.state_id)))
)
)
def find_short_term_statistics_to_purge(
purge_before: datetime, max_bind_vars: int
) -> StatementLambdaElement:

View File

@@ -2,7 +2,15 @@
from __future__ import annotations
from collections.abc import Sequence
from typing import Any, cast
from sqlalchemy.engine.row import Row
from sqlalchemy.orm.session import Session
from ..db_schema import States
from ..queries import find_oldest_state
from ..util import execute_stmt_lambda_element
class StatesManager:
@@ -13,6 +21,12 @@ class StatesManager:
self._pending: dict[str, States] = {}
self._last_committed_id: dict[str, int] = {}
self._last_reported: dict[int, float] = {}
self._oldest_ts: float | None = None
@property
def oldest_ts(self) -> float | None:
"""Return the oldest timestamp."""
return self._oldest_ts
def pop_pending(self, entity_id: str) -> States | None:
"""Pop a pending state.
@@ -44,6 +58,8 @@ class StatesManager:
recorder thread.
"""
self._pending[entity_id] = state
if self._oldest_ts is None:
self._oldest_ts = state.last_updated_ts
def update_pending_last_reported(
self, state_id: int, last_reported_timestamp: float
@@ -74,6 +90,22 @@ class StatesManager:
"""
self._last_committed_id.clear()
self._pending.clear()
self._oldest_ts = None
def load_from_db(self, session: Session) -> None:
"""Update the cache.
Must run in the recorder thread.
"""
result = cast(
Sequence[Row[Any]],
execute_stmt_lambda_element(session, find_oldest_state()),
)
if not result:
ts = None
else:
ts = result[0].last_updated_ts
self._oldest_ts = ts
def evict_purged_state_ids(self, purged_state_ids: set[int]) -> None:
"""Evict purged states from the committed states.

View File

@@ -120,8 +120,6 @@ class PurgeTask(RecorderTask):
if purge.purge_old_data(
instance, self.purge_before, self.repack, self.apply_filter
):
with instance.get_session() as session:
instance.recorder_runs_manager.load_from_db(session)
# We always need to do the db cleanups after a purge
# is finished to ensure the WAL checkpoint and other
# tasks happen after a vacuum.

View File

@@ -22,12 +22,6 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
from homeassistant.helpers.entity import ToggleEntity, ToggleEntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType
@@ -74,19 +68,6 @@ class RemoteEntityFeature(IntFlag):
ACTIVITY = 4
# These SUPPORT_* constants are deprecated as of Home Assistant 2022.5.
# Please use the RemoteEntityFeature enum instead.
_DEPRECATED_SUPPORT_LEARN_COMMAND = DeprecatedConstantEnum(
RemoteEntityFeature.LEARN_COMMAND, "2025.1"
)
_DEPRECATED_SUPPORT_DELETE_COMMAND = DeprecatedConstantEnum(
RemoteEntityFeature.DELETE_COMMAND, "2025.1"
)
_DEPRECATED_SUPPORT_ACTIVITY = DeprecatedConstantEnum(
RemoteEntityFeature.ACTIVITY, "2025.1"
)
REMOTE_SERVICE_ACTIVITY_SCHEMA = cv.make_entity_service_schema(
{vol.Optional(ATTR_ACTIVITY): cv.string}
)
@@ -251,11 +232,3 @@ 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())

View File

@@ -37,7 +37,7 @@
"requirements": [
"getmac==0.9.4",
"samsungctl[websocket]==0.7.1",
"samsungtvws[async,encrypted]==2.7.0",
"samsungtvws[async,encrypted]==2.7.1",
"wakeonlan==2.1.0",
"async-upnp-client==0.41.0"
],

View File

@@ -10,7 +10,6 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from .const import DOMAIN
from .coordinator import SchlageDataUpdateCoordinator
PLATFORMS: list[Platform] = [
@@ -21,8 +20,10 @@ PLATFORMS: list[Platform] = [
Platform.SWITCH,
]
type SchlageConfigEntry = ConfigEntry[SchlageDataUpdateCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: SchlageConfigEntry) -> bool:
"""Set up Schlage from a config entry."""
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
@@ -32,15 +33,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
raise ConfigEntryAuthFailed from ex
coordinator = SchlageDataUpdateCoordinator(hass, username, pyschlage.Schlage(auth))
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
await coordinator.async_config_entry_first_refresh()
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: SchlageConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -10,12 +10,11 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from . import SchlageConfigEntry
from .coordinator import LockData, SchlageDataUpdateCoordinator
from .entity import SchlageEntity
@@ -40,11 +39,11 @@ _DESCRIPTIONS: tuple[SchlageBinarySensorEntityDescription] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: SchlageConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up binary_sensors based on a config entry."""
coordinator: SchlageDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
def _add_new_locks(locks: dict[str, LockData]) -> None:
async_add_entities(

View File

@@ -44,6 +44,7 @@ class SchlageDataUpdateCoordinator(DataUpdateCoordinator[SchlageData]):
super().__init__(
hass, LOGGER, name=f"{DOMAIN} ({username})", update_interval=UPDATE_INTERVAL
)
self.data = SchlageData(locks={})
self.api = api
self.new_locks_callbacks: list[Callable[[dict[str, LockData]], None]] = []
self.async_add_listener(self._add_remove_locks)
@@ -55,7 +56,9 @@ class SchlageDataUpdateCoordinator(DataUpdateCoordinator[SchlageData]):
except NotAuthorizedError as ex:
raise ConfigEntryAuthFailed from ex
except SchlageError as ex:
raise UpdateFailed("Failed to refresh Schlage data") from ex
raise UpdateFailed(
translation_domain=DOMAIN, translation_key="schlage_refresh_failed"
) from ex
lock_data = await asyncio.gather(
*(
self.hass.async_add_executor_job(self._get_lock_data, lock)
@@ -83,9 +86,6 @@ class SchlageDataUpdateCoordinator(DataUpdateCoordinator[SchlageData]):
@callback
def _add_remove_locks(self) -> None:
"""Add newly discovered locks and remove nonexistent locks."""
if self.data is None:
return
device_registry = dr.async_get(self.hass)
devices = dr.async_entries_for_config_entry(
device_registry, self.config_entry.entry_id

View File

@@ -4,19 +4,17 @@ from __future__ import annotations
from typing import Any
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import SchlageDataUpdateCoordinator
from . import SchlageConfigEntry
async def async_get_config_entry_diagnostics(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: SchlageConfigEntry,
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator: SchlageDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
# NOTE: Schlage diagnostics are already redacted.
return {
"locks": [ld.lock.get_diagnostics() for ld in coordinator.data.locks.values()]

View File

@@ -5,22 +5,21 @@ from __future__ import annotations
from typing import Any
from homeassistant.components.lock import LockEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from . import SchlageConfigEntry
from .coordinator import LockData, SchlageDataUpdateCoordinator
from .entity import SchlageEntity
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: SchlageConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Schlage WiFi locks based on a config entry."""
coordinator: SchlageDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
def _add_new_locks(locks: dict[str, LockData]) -> None:
async_add_entities(

View File

@@ -3,12 +3,11 @@
from __future__ import annotations
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from . import SchlageConfigEntry
from .coordinator import LockData, SchlageDataUpdateCoordinator
from .entity import SchlageEntity
@@ -33,11 +32,11 @@ _DESCRIPTIONS = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: SchlageConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up selects based on a config entry."""
coordinator: SchlageDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
def _add_new_locks(locks: dict[str, LockData]) -> None:
async_add_entities(

View File

@@ -13,7 +13,6 @@ from homeassistant.const import PERCENTAGE, EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import LockData, SchlageDataUpdateCoordinator
from .entity import SchlageEntity
@@ -34,7 +33,7 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up sensors based on a config entry."""
coordinator: SchlageDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
def _add_new_locks(locks: dict[str, LockData]) -> None:
async_add_entities(

View File

@@ -53,5 +53,10 @@
"name": "1-Touch Locking"
}
}
},
"exceptions": {
"schlage_refresh_failed": {
"message": "Failed to refresh Schlage data"
}
}
}

View File

@@ -19,7 +19,6 @@ from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import LockData, SchlageDataUpdateCoordinator
from .entity import SchlageEntity
@@ -61,7 +60,7 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up switches based on a config entry."""
coordinator: SchlageDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
def _add_new_locks(locks: dict[str, LockData]) -> None:
async_add_entities(

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