Add ecobee indefinite away preset, remove unusable/broken presets (#108636)

* Add ecobee indefinite away preset, remove unusable/broken presets

* Revert cleanup of presets which no longer work
This commit is contained in:
alexsydell 2024-03-22 15:45:54 -06:00 committed by GitHub
parent d3c68303b0
commit 9e86f82a1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 88 additions and 17 deletions

View file

@ -12,7 +12,6 @@ from homeassistant.components.climate import (
ATTR_TARGET_TEMP_LOW,
FAN_AUTO,
FAN_ON,
PRESET_AWAY,
PRESET_NONE,
ClimateEntity,
ClimateEntityFeature,
@ -38,7 +37,7 @@ from homeassistant.util.unit_conversion import TemperatureConverter
from . import EcobeeData
from .const import _LOGGER, DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER
from .util import ecobee_date, ecobee_time
from .util import ecobee_date, ecobee_time, is_indefinite_hold
ATTR_COOL_TEMP = "cool_temp"
ATTR_END_DATE = "end_date"
@ -56,6 +55,7 @@ ATTR_AUTO_AWAY = "auto_away"
ATTR_FOLLOW_ME = "follow_me"
DEFAULT_RESUME_ALL = False
PRESET_AWAY_INDEFINITELY = "away_indefinitely"
PRESET_TEMPERATURE = "temp"
PRESET_VACATION = "vacation"
PRESET_HOLD_NEXT_TRANSITION = "next_transition"
@ -325,6 +325,7 @@ class Thermostat(ClimateEntity):
_attr_name = None
_attr_has_entity_name = True
_enable_turn_on_off_backwards_compatibility = False
_attr_translation_key = "ecobee"
def __init__(
self, data: EcobeeData, thermostat_index: int, thermostat: dict
@ -481,6 +482,11 @@ class Thermostat(ClimateEntity):
continue
if event["type"] == "hold":
if event["holdClimateRef"] == "away" and is_indefinite_hold(
event["startDate"], event["endDate"]
):
return PRESET_AWAY_INDEFINITELY
if event["holdClimateRef"] in self._preset_modes:
return self._preset_modes[event["holdClimateRef"]]
@ -577,7 +583,7 @@ class Thermostat(ClimateEntity):
if self.preset_mode == PRESET_VACATION:
self.data.ecobee.delete_vacation(self.thermostat_index, self.vacation)
if preset_mode == PRESET_AWAY:
if preset_mode == PRESET_AWAY_INDEFINITELY:
self.data.ecobee.set_climate_hold(
self.thermostat_index, "away", "indefinite", self.hold_hours()
)
@ -625,7 +631,9 @@ class Thermostat(ClimateEntity):
@property
def preset_modes(self):
"""Return available preset modes."""
return list(self._preset_modes.values())
# Return presets provided by the ecobee API, and an indefinite away
# preset which we handle separately in set_preset_mode().
return [*self._preset_modes.values(), PRESET_AWAY_INDEFINITELY]
def set_auto_temp_hold(self, heat_temp, cool_temp):
"""Set temperature hold in auto mode."""

View file

@ -20,6 +20,17 @@
}
},
"entity": {
"climate": {
"ecobee": {
"state_attributes": {
"preset_mode": {
"state": {
"away_indefinitely": "Away Indefinitely"
}
}
}
}
},
"number": {
"ventilator_min_type_home": {
"name": "Ventilator min time home"

View file

@ -1,6 +1,6 @@
"""Validation utility functions for ecobee services."""
from datetime import datetime
from datetime import date, datetime, timedelta
import voluptuous as vol
@ -23,3 +23,13 @@ def ecobee_time(time_string):
"Time does not match ecobee 24-hour time format HH:MM:SS"
) from err
return time_string
def is_indefinite_hold(start_date_string: str, end_date_string: str) -> bool:
"""Determine if the given start and end dates from the ecobee API represent an indefinite hold.
This is not documented in the API, so a rough heuristic is used where a hold over 1 year is considered indefinite.
"""
return date.fromisoformat(end_date_string) - date.fromisoformat(
start_date_string
) > timedelta(days=365)

View file

@ -39,8 +39,10 @@ GENERIC_THERMOSTAT_INFO = {
"running": True,
"type": "hold",
"holdClimateRef": "away",
"endDate": "2022-01-01 10:00:00",
"startDate": "2022-02-02 11:00:00",
"startDate": "2022-02-02",
"startTime": "11:00:00",
"endDate": "2022-01-01",
"endTime": "10:00:00",
}
],
"remoteSensors": [
@ -99,8 +101,10 @@ GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP = {
"running": True,
"type": "hold",
"holdClimateRef": "away",
"endDate": "2022-01-01 10:00:00",
"startDate": "2022-02-02 11:00:00",
"startDate": "2022-02-02",
"startTime": "11:00:00",
"endDate": "2022-01-01",
"endTime": "10:00:00",
}
],
"remoteSensors": [

View file

@ -42,8 +42,10 @@
"running": true,
"type": "hold",
"holdClimateRef": "away",
"endDate": "2022-01-01 10:00:00",
"startDate": "2022-02-02 11:00:00"
"startDate": "2022-02-02",
"startTime": "11:00:00",
"endDate": "2022-01-01",
"endTime": "10:00:00"
}
],
"remoteSensors": [
@ -110,8 +112,10 @@
"running": true,
"type": "hold",
"holdClimateRef": "away",
"endDate": "2022-01-01 10:00:00",
"startDate": "2022-02-02 11:00:00"
"startDate": "2022-02-02",
"startTime": "11:00:00",
"endDate": "2022-01-01",
"endTime": "10:00:00"
}
],
"remoteSensors": [

View file

@ -9,7 +9,11 @@ import pytest
from homeassistant.components import climate
from homeassistant.components.climate import ClimateEntityFeature
from homeassistant.components.ecobee.climate import ECOBEE_AUX_HEAT_ONLY, Thermostat
from homeassistant.components.ecobee.climate import (
ECOBEE_AUX_HEAT_ONLY,
PRESET_AWAY_INDEFINITELY,
Thermostat,
)
import homeassistant.const as const
from homeassistant.const import ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, STATE_OFF
from homeassistant.core import HomeAssistant
@ -31,6 +35,7 @@ def ecobee_fixture():
"climates": [
{"name": "Climate1", "climateRef": "c1"},
{"name": "Climate2", "climateRef": "c2"},
{"name": "Away", "climateRef": "away"},
],
"currentClimateRef": "c1",
},
@ -56,9 +61,11 @@ def ecobee_fixture():
"name": "Event1",
"running": True,
"type": "hold",
"holdClimateRef": "away",
"endDate": "2017-01-01 10:00:00",
"startDate": "2017-02-02 11:00:00",
"holdClimateRef": "c1",
"startDate": "2017-02-02",
"startTime": "11:00:00",
"endDate": "2017-01-01",
"endTime": "10:00:00",
}
],
}
@ -428,3 +435,30 @@ async def test_turn_aux_heat_off(hass: HomeAssistant, mock_ecobee: MagicMock) ->
)
assert mock_ecobee.set_hvac_mode.call_count == 1
assert mock_ecobee.set_hvac_mode.call_args == mock.call(0, "auto")
async def test_preset_indefinite_away(ecobee_fixture, thermostat) -> None:
"""Test indefinite away showing correctly, and not as temporary away."""
ecobee_fixture["program"]["currentClimateRef"] = "away"
ecobee_fixture["events"][0]["holdClimateRef"] = "away"
assert thermostat.preset_mode == "Away"
ecobee_fixture["events"][0]["endDate"] = "2999-01-01"
assert thermostat.preset_mode == PRESET_AWAY_INDEFINITELY
async def test_set_preset_mode(ecobee_fixture, thermostat, data) -> None:
"""Test set preset mode."""
# Set a preset provided by ecobee.
data.reset_mock()
thermostat.set_preset_mode("Climate2")
data.ecobee.set_climate_hold.assert_has_calls(
[mock.call(1, "c2", thermostat.hold_preference(), thermostat.hold_hours())]
)
# Set the indefinite away preset provided by this integration.
data.reset_mock()
thermostat.set_preset_mode(PRESET_AWAY_INDEFINITELY)
data.ecobee.set_climate_hold.assert_has_calls(
[mock.call(1, "away", "indefinite", thermostat.hold_hours())]
)