Skip to content

Commit

Permalink
Merge branch 'dev' into fbx_bridge
Browse files Browse the repository at this point in the history
  • Loading branch information
agrenott committed Dec 20, 2023
2 parents e02b29a + 69dcc15 commit 7183fca
Show file tree
Hide file tree
Showing 87 changed files with 3,277 additions and 469 deletions.
27 changes: 19 additions & 8 deletions homeassistant/components/alarm_control_panel/__init__.py
Expand Up @@ -2,6 +2,7 @@
from __future__ import annotations

from datetime import timedelta
from functools import partial
import logging
from typing import Any, Final, final

Expand All @@ -22,26 +23,36 @@
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import make_entity_service_schema
from homeassistant.helpers.deprecation import (
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

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,
ATTR_CHANGED_BY,
ATTR_CODE_ARM_REQUIRED,
DOMAIN,
FORMAT_NUMBER,
FORMAT_TEXT,
SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_CUSTOM_BYPASS,
SUPPORT_ALARM_ARM_HOME,
SUPPORT_ALARM_ARM_NIGHT,
SUPPORT_ALARM_ARM_VACATION,
SUPPORT_ALARM_TRIGGER,
AlarmControlPanelEntityFeature,
CodeFormat,
)

# As we import constants of the cost module here, we need to add the following
# functions to check for deprecated constants again
# Both can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(dir_with_deprecated_constants, module_globals=globals())

_LOGGER: Final = logging.getLogger(__name__)

SCAN_INTERVAL: Final = timedelta(seconds=30)
Expand Down
41 changes: 32 additions & 9 deletions homeassistant/components/alarm_control_panel/const.py
@@ -1,7 +1,14 @@
"""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,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)

DOMAIN: Final = "alarm_control_panel"

ATTR_CHANGED_BY: Final = "changed_by"
Expand All @@ -15,10 +22,10 @@ class CodeFormat(StrEnum):
NUMBER = "number"


# These constants are deprecated as of Home Assistant 2022.5
# These constants are deprecated as of Home Assistant 2022.5, can be removed in 2025.1
# Please use the CodeFormat enum instead.
FORMAT_TEXT: Final = "text"
FORMAT_NUMBER: Final = "number"
_DEPRECATED_FORMAT_TEXT: Final = DeprecatedConstantEnum(CodeFormat.TEXT, "2025.1")
_DEPRECATED_FORMAT_NUMBER: Final = DeprecatedConstantEnum(CodeFormat.NUMBER, "2025.1")


class AlarmControlPanelEntityFeature(IntFlag):
Expand All @@ -34,12 +41,28 @@ class AlarmControlPanelEntityFeature(IntFlag):

# These constants are deprecated as of Home Assistant 2022.5
# Please use the AlarmControlPanelEntityFeature enum instead.
SUPPORT_ALARM_ARM_HOME: Final = 1
SUPPORT_ALARM_ARM_AWAY: Final = 2
SUPPORT_ALARM_ARM_NIGHT: Final = 4
SUPPORT_ALARM_TRIGGER: Final = 8
SUPPORT_ALARM_ARM_CUSTOM_BYPASS: Final = 16
SUPPORT_ALARM_ARM_VACATION: Final = 32
_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"
)

# Both can be removed if no deprecated constant are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(dir_with_deprecated_constants, module_globals=globals())

CONDITION_TRIGGERED: Final = "is_triggered"
CONDITION_DISARMED: Final = "is_disarmed"
Expand Down
18 changes: 6 additions & 12 deletions homeassistant/components/alarm_control_panel/device_action.py
Expand Up @@ -28,13 +28,7 @@
from homeassistant.helpers.typing import ConfigType, TemplateVarsType

from . import ATTR_CODE_ARM_REQUIRED, DOMAIN
from .const import (
SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_HOME,
SUPPORT_ALARM_ARM_NIGHT,
SUPPORT_ALARM_ARM_VACATION,
SUPPORT_ALARM_TRIGGER,
)
from .const import AlarmControlPanelEntityFeature

ACTION_TYPES: Final[set[str]] = {
"arm_away",
Expand Down Expand Up @@ -82,16 +76,16 @@ async def async_get_actions(
}

# Add actions for each entity that belongs to this integration
if supported_features & SUPPORT_ALARM_ARM_AWAY:
if supported_features & AlarmControlPanelEntityFeature.ARM_AWAY:
actions.append({**base_action, CONF_TYPE: "arm_away"})
if supported_features & SUPPORT_ALARM_ARM_HOME:
if supported_features & AlarmControlPanelEntityFeature.ARM_HOME:
actions.append({**base_action, CONF_TYPE: "arm_home"})
if supported_features & SUPPORT_ALARM_ARM_NIGHT:
if supported_features & AlarmControlPanelEntityFeature.ARM_NIGHT:
actions.append({**base_action, CONF_TYPE: "arm_night"})
if supported_features & SUPPORT_ALARM_ARM_VACATION:
if supported_features & AlarmControlPanelEntityFeature.ARM_VACATION:
actions.append({**base_action, CONF_TYPE: "arm_vacation"})
actions.append({**base_action, CONF_TYPE: "disarm"})
if supported_features & SUPPORT_ALARM_TRIGGER:
if supported_features & AlarmControlPanelEntityFeature.TRIGGER:
actions.append({**base_action, CONF_TYPE: "trigger"})

return actions
Expand Down
16 changes: 6 additions & 10 deletions homeassistant/components/alarm_control_panel/device_condition.py
Expand Up @@ -39,11 +39,7 @@
CONDITION_ARMED_VACATION,
CONDITION_DISARMED,
CONDITION_TRIGGERED,
SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_CUSTOM_BYPASS,
SUPPORT_ALARM_ARM_HOME,
SUPPORT_ALARM_ARM_NIGHT,
SUPPORT_ALARM_ARM_VACATION,
AlarmControlPanelEntityFeature,
)

CONDITION_TYPES: Final[set[str]] = {
Expand Down Expand Up @@ -90,15 +86,15 @@ async def async_get_conditions(
{**base_condition, CONF_TYPE: CONDITION_DISARMED},
{**base_condition, CONF_TYPE: CONDITION_TRIGGERED},
]
if supported_features & SUPPORT_ALARM_ARM_HOME:
if supported_features & AlarmControlPanelEntityFeature.ARM_HOME:
conditions.append({**base_condition, CONF_TYPE: CONDITION_ARMED_HOME})
if supported_features & SUPPORT_ALARM_ARM_AWAY:
if supported_features & AlarmControlPanelEntityFeature.ARM_AWAY:
conditions.append({**base_condition, CONF_TYPE: CONDITION_ARMED_AWAY})
if supported_features & SUPPORT_ALARM_ARM_NIGHT:
if supported_features & AlarmControlPanelEntityFeature.ARM_NIGHT:
conditions.append({**base_condition, CONF_TYPE: CONDITION_ARMED_NIGHT})
if supported_features & SUPPORT_ALARM_ARM_VACATION:
if supported_features & AlarmControlPanelEntityFeature.ARM_VACATION:
conditions.append({**base_condition, CONF_TYPE: CONDITION_ARMED_VACATION})
if supported_features & SUPPORT_ALARM_ARM_CUSTOM_BYPASS:
if supported_features & AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS:
conditions.append(
{**base_condition, CONF_TYPE: CONDITION_ARMED_CUSTOM_BYPASS}
)
Expand Down
15 changes: 5 additions & 10 deletions homeassistant/components/alarm_control_panel/device_trigger.py
Expand Up @@ -29,12 +29,7 @@
from homeassistant.helpers.typing import ConfigType

from . import DOMAIN
from .const import (
SUPPORT_ALARM_ARM_AWAY,
SUPPORT_ALARM_ARM_HOME,
SUPPORT_ALARM_ARM_NIGHT,
SUPPORT_ALARM_ARM_VACATION,
)
from .const import AlarmControlPanelEntityFeature

BASIC_TRIGGER_TYPES: Final[set[str]] = {"triggered", "disarmed", "arming"}
TRIGGER_TYPES: Final[set[str]] = BASIC_TRIGGER_TYPES | {
Expand Down Expand Up @@ -82,28 +77,28 @@ async def async_get_triggers(
}
for trigger in BASIC_TRIGGER_TYPES
]
if supported_features & SUPPORT_ALARM_ARM_HOME:
if supported_features & AlarmControlPanelEntityFeature.ARM_HOME:
triggers.append(
{
**base_trigger,
CONF_TYPE: "armed_home",
}
)
if supported_features & SUPPORT_ALARM_ARM_AWAY:
if supported_features & AlarmControlPanelEntityFeature.ARM_AWAY:
triggers.append(
{
**base_trigger,
CONF_TYPE: "armed_away",
}
)
if supported_features & SUPPORT_ALARM_ARM_NIGHT:
if supported_features & AlarmControlPanelEntityFeature.ARM_NIGHT:
triggers.append(
{
**base_trigger,
CONF_TYPE: "armed_night",
}
)
if supported_features & SUPPORT_ALARM_ARM_VACATION:
if supported_features & AlarmControlPanelEntityFeature.ARM_VACATION:
triggers.append(
{
**base_trigger,
Expand Down
47 changes: 45 additions & 2 deletions homeassistant/components/alexa/capabilities.py
Expand Up @@ -19,6 +19,7 @@
number,
timer,
vacuum,
water_heater,
)
from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntityFeature,
Expand Down Expand Up @@ -435,7 +436,8 @@ def get_property(self, name: str) -> Any:
is_on = self.entity.state == vacuum.STATE_CLEANING
elif self.entity.domain == timer.DOMAIN:
is_on = self.entity.state != STATE_IDLE

elif self.entity.domain == water_heater.DOMAIN:
is_on = self.entity.state not in (STATE_OFF, STATE_UNKNOWN)
else:
is_on = self.entity.state != STATE_OFF

Expand Down Expand Up @@ -938,6 +940,9 @@ def get_property(self, name: str) -> Any:
if self.entity.domain == climate.DOMAIN:
unit = self.hass.config.units.temperature_unit
temp = self.entity.attributes.get(climate.ATTR_CURRENT_TEMPERATURE)
elif self.entity.domain == water_heater.DOMAIN:
unit = self.hass.config.units.temperature_unit
temp = self.entity.attributes.get(water_heater.ATTR_CURRENT_TEMPERATURE)

if temp is None or temp in (STATE_UNAVAILABLE, STATE_UNKNOWN):
return None
Expand Down Expand Up @@ -1108,6 +1113,8 @@ def properties_supported(self) -> list[dict[str, str]]:
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & climate.ClimateEntityFeature.TARGET_TEMPERATURE:
properties.append({"name": "targetSetpoint"})
if supported & water_heater.WaterHeaterEntityFeature.TARGET_TEMPERATURE:
properties.append({"name": "targetSetpoint"})
if supported & climate.ClimateEntityFeature.TARGET_TEMPERATURE_RANGE:
properties.append({"name": "lowerSetpoint"})
properties.append({"name": "upperSetpoint"})
Expand All @@ -1127,6 +1134,8 @@ def get_property(self, name: str) -> Any:
return None

if name == "thermostatMode":
if self.entity.domain == water_heater.DOMAIN:
return None
preset = self.entity.attributes.get(climate.ATTR_PRESET_MODE)

mode: dict[str, str] | str | None
Expand Down Expand Up @@ -1176,9 +1185,13 @@ def configuration(self) -> dict[str, Any] | None:
ThermostatMode Values.
ThermostatMode Value must be AUTO, COOL, HEAT, ECO, OFF, or CUSTOM.
Water heater devices do not return thermostat modes.
"""
if self.entity.domain == water_heater.DOMAIN:
return None

supported_modes: list[str] = []
hvac_modes = self.entity.attributes[climate.ATTR_HVAC_MODES]
hvac_modes = self.entity.attributes.get(climate.ATTR_HVAC_MODES, [])
for mode in hvac_modes:
if thermostat_mode := API_THERMOSTAT_MODES.get(mode):
supported_modes.append(thermostat_mode)
Expand Down Expand Up @@ -1408,6 +1421,16 @@ def get_property(self, name: str) -> Any:
if mode in self.entity.attributes.get(humidifier.ATTR_AVAILABLE_MODES, []):
return f"{humidifier.ATTR_MODE}.{mode}"

# Water heater operation mode
if self.instance == f"{water_heater.DOMAIN}.{water_heater.ATTR_OPERATION_MODE}":
operation_mode = self.entity.attributes.get(
water_heater.ATTR_OPERATION_MODE, None
)
if operation_mode in self.entity.attributes.get(
water_heater.ATTR_OPERATION_LIST, []
):
return f"{water_heater.ATTR_OPERATION_MODE}.{operation_mode}"

# Cover Position
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
# Return state instead of position when using ModeController.
Expand Down Expand Up @@ -1478,6 +1501,26 @@ def capability_resources(self) -> dict[str, list[dict[str, Any]]]:
)
return self._resource.serialize_capability_resources()

# Water heater operation modes
if self.instance == f"{water_heater.DOMAIN}.{water_heater.ATTR_OPERATION_MODE}":
self._resource = AlexaModeResource([AlexaGlobalCatalog.SETTING_MODE], False)
operation_modes = self.entity.attributes.get(
water_heater.ATTR_OPERATION_LIST, []
)
for operation_mode in operation_modes:
self._resource.add_mode(
f"{water_heater.ATTR_OPERATION_MODE}.{operation_mode}",
[operation_mode],
)
# Devices with a single mode completely break Alexa discovery,
# add a fake preset (see issue #53832).
if len(operation_modes) == 1:
self._resource.add_mode(
f"{water_heater.ATTR_OPERATION_MODE}.{PRESET_MODE_NA}",
[PRESET_MODE_NA],
)
return self._resource.serialize_capability_resources()

# Cover Position Resources
if self.instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
self._resource = AlexaModeResource(
Expand Down

0 comments on commit 7183fca

Please sign in to comment.