mirror of
				https://github.com/home-assistant/core.git
				synced 2025-11-04 08:29:37 +00:00 
			
		
		
		
	Compare commits
	
		
			89 Commits
		
	
	
		
			2024.12.0b
			...
			llm-neverm
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					cf259c2278 | ||
| 
						 | 
					e8ced4fa12 | ||
| 
						 | 
					d9cef1e708 | ||
| 
						 | 
					a760786faf | ||
| 
						 | 
					8c6a24c368 | ||
| 
						 | 
					24bd61be3b | ||
| 
						 | 
					1abd2209b3 | ||
| 
						 | 
					aa206c7608 | ||
| 
						 | 
					87020e8945 | ||
| 
						 | 
					dd62fb387e | ||
| 
						 | 
					c19038ced6 | ||
| 
						 | 
					6144cc26ba | ||
| 
						 | 
					920c958ec7 | ||
| 
						 | 
					0fc365a114 | ||
| 
						 | 
					954ac0d288 | ||
| 
						 | 
					28cfa37248 | ||
| 
						 | 
					24f7bae5f2 | ||
| 
						 | 
					8e12fbff88 | ||
| 
						 | 
					5c8fb5ec2c | ||
| 
						 | 
					a68cf21179 | ||
| 
						 | 
					d596b4169d | ||
| 
						 | 
					8b467268df | ||
| 
						 | 
					6dd93253c6 | ||
| 
						 | 
					9db6f0ffc4 | ||
| 
						 | 
					889ac1552b | ||
| 
						 | 
					18db16b82c | ||
| 
						 | 
					1f9ecfe839 | ||
| 
						 | 
					4d32fe97c3 | ||
| 
						 | 
					8feb6c7e06 | ||
| 
						 | 
					0b36a6d7f3 | ||
| 
						 | 
					837716b69e | ||
| 
						 | 
					1a9ab07742 | ||
| 
						 | 
					8862c5c4d8 | ||
| 
						 | 
					87320609dc | ||
| 
						 | 
					62e788c7da | ||
| 
						 | 
					bbce183faf | ||
| 
						 | 
					0389800e2a | ||
| 
						 | 
					0c5c09390c | ||
| 
						 | 
					57b099c2aa | ||
| 
						 | 
					ed408eb1a1 | ||
| 
						 | 
					f7d2d06c9b | ||
| 
						 | 
					3071aa2da1 | ||
| 
						 | 
					474544abd8 | ||
| 
						 | 
					dc064237ca | ||
| 
						 | 
					a0584a0516 | ||
| 
						 | 
					96dfa0e0cf | ||
| 
						 | 
					00d82363fe | ||
| 
						 | 
					c4e5b59326 | ||
| 
						 | 
					d9832f8c3a | ||
| 
						 | 
					f41bc98fe2 | ||
| 
						 | 
					3a76bfb857 | ||
| 
						 | 
					6ce5c89711 | ||
| 
						 | 
					9d387acb97 | ||
| 
						 | 
					1d09a5bf89 | ||
| 
						 | 
					a01e7cd6cf | ||
| 
						 | 
					3e0326dd66 | ||
| 
						 | 
					4d27a32905 | ||
| 
						 | 
					c5f68bcc58 | ||
| 
						 | 
					3866176e1d | ||
| 
						 | 
					a67045ee6c | ||
| 
						 | 
					54ff6feadc | ||
| 
						 | 
					fd14add67b | ||
| 
						 | 
					b28f352902 | ||
| 
						 | 
					fb152c7d22 | ||
| 
						 | 
					be81fd86d3 | ||
| 
						 | 
					28ec8272ee | ||
| 
						 | 
					717f2ee206 | ||
| 
						 | 
					5972da495a | ||
| 
						 | 
					2fcd9be3f2 | ||
| 
						 | 
					a0ea9a1e83 | ||
| 
						 | 
					a831c37511 | ||
| 
						 | 
					d26c7a0536 | ||
| 
						 | 
					4257277086 | ||
| 
						 | 
					fe2bca51a4 | ||
| 
						 | 
					17236a5698 | ||
| 
						 | 
					39c2a529d1 | ||
| 
						 | 
					0f5e0dd4bf | ||
| 
						 | 
					eac6673c2b | ||
| 
						 | 
					bf4d6d2029 | ||
| 
						 | 
					f61a5b78cc | ||
| 
						 | 
					cc9a97a5cf | ||
| 
						 | 
					cf7acb5ae8 | ||
| 
						 | 
					6edb2c0252 | ||
| 
						 | 
					fb4d86196e | ||
| 
						 | 
					44fc5c7871 | ||
| 
						 | 
					c82e408138 | ||
| 
						 | 
					7110df04e6 | ||
| 
						 | 
					1635074aae | ||
| 
						 | 
					381d5453b1 | 
							
								
								
									
										8
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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.*
 | 
			
		||||
 
 | 
			
		||||
@@ -25,5 +25,5 @@
 | 
			
		||||
  "integration_type": "device",
 | 
			
		||||
  "iot_class": "local_push",
 | 
			
		||||
  "loggers": ["aioacaia"],
 | 
			
		||||
  "requirements": ["aioacaia==0.1.9"]
 | 
			
		||||
  "requirements": ["aioacaia==0.1.10"]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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.",
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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."""
 | 
			
		||||
 
 | 
			
		||||
@@ -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"]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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": {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,4 +9,3 @@ ATTR_ITEM_NAME: Final = "item"
 | 
			
		||||
ATTR_NOTIFICATION_TYPE: Final = "message"
 | 
			
		||||
 | 
			
		||||
SERVICE_PUSH_NOTIFICATION = "send_message"
 | 
			
		||||
UNIT_ITEMS = "items"
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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}"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
  ],
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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"]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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"],
 | 
			
		||||
    ),
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -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],
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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={
 | 
			
		||||
 
 | 
			
		||||
@@ -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={
 | 
			
		||||
 
 | 
			
		||||
@@ -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={
 | 
			
		||||
 
 | 
			
		||||
@@ -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."
 | 
			
		||||
 
 | 
			
		||||
@@ -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={
 | 
			
		||||
 
 | 
			
		||||
@@ -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={
 | 
			
		||||
 
 | 
			
		||||
@@ -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."]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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."""
 | 
			
		||||
 
 | 
			
		||||
@@ -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"]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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):
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,8 @@
 | 
			
		||||
  "entity": {
 | 
			
		||||
    "sensor": {
 | 
			
		||||
      "heating": {
 | 
			
		||||
        "name": "Heating"
 | 
			
		||||
        "name": "Heating",
 | 
			
		||||
        "unit_of_measurement": "units"
 | 
			
		||||
      },
 | 
			
		||||
      "heating_cost": {
 | 
			
		||||
        "name": "Heating cost"
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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",
 | 
			
		||||
 
 | 
			
		||||
@@ -36,5 +36,5 @@
 | 
			
		||||
  "integration_type": "device",
 | 
			
		||||
  "iot_class": "cloud_polling",
 | 
			
		||||
  "loggers": ["pylamarzocco"],
 | 
			
		||||
  "requirements": ["pylamarzocco==1.2.11"]
 | 
			
		||||
  "requirements": ["pylamarzocco==1.2.12"]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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?"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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"]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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: |
 | 
			
		||||
 
 | 
			
		||||
@@ -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."]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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 {}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
    )
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								homeassistant/components/nest/types.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								homeassistant/components/nest/types.py
									
									
									
									
									
										Normal 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]
 | 
			
		||||
@@ -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"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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."
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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:
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
  ],
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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()]
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -53,5 +53,10 @@
 | 
			
		||||
        "name": "1-Touch Locking"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "exceptions": {
 | 
			
		||||
    "schlage_refresh_failed": {
 | 
			
		||||
      "message": "Failed to refresh Schlage data"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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
		Reference in New Issue
	
	Block a user