mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 10:17:09 +00:00
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:
parent
d3c68303b0
commit
9e86f82a1b
@ -12,7 +12,6 @@ from homeassistant.components.climate import (
|
|||||||
ATTR_TARGET_TEMP_LOW,
|
ATTR_TARGET_TEMP_LOW,
|
||||||
FAN_AUTO,
|
FAN_AUTO,
|
||||||
FAN_ON,
|
FAN_ON,
|
||||||
PRESET_AWAY,
|
|
||||||
PRESET_NONE,
|
PRESET_NONE,
|
||||||
ClimateEntity,
|
ClimateEntity,
|
||||||
ClimateEntityFeature,
|
ClimateEntityFeature,
|
||||||
@ -38,7 +37,7 @@ from homeassistant.util.unit_conversion import TemperatureConverter
|
|||||||
|
|
||||||
from . import EcobeeData
|
from . import EcobeeData
|
||||||
from .const import _LOGGER, DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER
|
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_COOL_TEMP = "cool_temp"
|
||||||
ATTR_END_DATE = "end_date"
|
ATTR_END_DATE = "end_date"
|
||||||
@ -56,6 +55,7 @@ ATTR_AUTO_AWAY = "auto_away"
|
|||||||
ATTR_FOLLOW_ME = "follow_me"
|
ATTR_FOLLOW_ME = "follow_me"
|
||||||
|
|
||||||
DEFAULT_RESUME_ALL = False
|
DEFAULT_RESUME_ALL = False
|
||||||
|
PRESET_AWAY_INDEFINITELY = "away_indefinitely"
|
||||||
PRESET_TEMPERATURE = "temp"
|
PRESET_TEMPERATURE = "temp"
|
||||||
PRESET_VACATION = "vacation"
|
PRESET_VACATION = "vacation"
|
||||||
PRESET_HOLD_NEXT_TRANSITION = "next_transition"
|
PRESET_HOLD_NEXT_TRANSITION = "next_transition"
|
||||||
@ -325,6 +325,7 @@ class Thermostat(ClimateEntity):
|
|||||||
_attr_name = None
|
_attr_name = None
|
||||||
_attr_has_entity_name = True
|
_attr_has_entity_name = True
|
||||||
_enable_turn_on_off_backwards_compatibility = False
|
_enable_turn_on_off_backwards_compatibility = False
|
||||||
|
_attr_translation_key = "ecobee"
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, data: EcobeeData, thermostat_index: int, thermostat: dict
|
self, data: EcobeeData, thermostat_index: int, thermostat: dict
|
||||||
@ -481,6 +482,11 @@ class Thermostat(ClimateEntity):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if event["type"] == "hold":
|
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:
|
if event["holdClimateRef"] in self._preset_modes:
|
||||||
return self._preset_modes[event["holdClimateRef"]]
|
return self._preset_modes[event["holdClimateRef"]]
|
||||||
|
|
||||||
@ -577,7 +583,7 @@ class Thermostat(ClimateEntity):
|
|||||||
if self.preset_mode == PRESET_VACATION:
|
if self.preset_mode == PRESET_VACATION:
|
||||||
self.data.ecobee.delete_vacation(self.thermostat_index, self.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.data.ecobee.set_climate_hold(
|
||||||
self.thermostat_index, "away", "indefinite", self.hold_hours()
|
self.thermostat_index, "away", "indefinite", self.hold_hours()
|
||||||
)
|
)
|
||||||
@ -625,7 +631,9 @@ class Thermostat(ClimateEntity):
|
|||||||
@property
|
@property
|
||||||
def preset_modes(self):
|
def preset_modes(self):
|
||||||
"""Return available preset modes."""
|
"""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):
|
def set_auto_temp_hold(self, heat_temp, cool_temp):
|
||||||
"""Set temperature hold in auto mode."""
|
"""Set temperature hold in auto mode."""
|
||||||
|
@ -20,6 +20,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
|
"climate": {
|
||||||
|
"ecobee": {
|
||||||
|
"state_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"state": {
|
||||||
|
"away_indefinitely": "Away Indefinitely"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"number": {
|
"number": {
|
||||||
"ventilator_min_type_home": {
|
"ventilator_min_type_home": {
|
||||||
"name": "Ventilator min time home"
|
"name": "Ventilator min time home"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Validation utility functions for ecobee services."""
|
"""Validation utility functions for ecobee services."""
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import date, datetime, timedelta
|
||||||
|
|
||||||
import voluptuous as vol
|
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"
|
"Time does not match ecobee 24-hour time format HH:MM:SS"
|
||||||
) from err
|
) from err
|
||||||
return time_string
|
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)
|
||||||
|
@ -39,8 +39,10 @@ GENERIC_THERMOSTAT_INFO = {
|
|||||||
"running": True,
|
"running": True,
|
||||||
"type": "hold",
|
"type": "hold",
|
||||||
"holdClimateRef": "away",
|
"holdClimateRef": "away",
|
||||||
"endDate": "2022-01-01 10:00:00",
|
"startDate": "2022-02-02",
|
||||||
"startDate": "2022-02-02 11:00:00",
|
"startTime": "11:00:00",
|
||||||
|
"endDate": "2022-01-01",
|
||||||
|
"endTime": "10:00:00",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"remoteSensors": [
|
"remoteSensors": [
|
||||||
@ -99,8 +101,10 @@ GENERIC_THERMOSTAT_INFO_WITH_HEATPUMP = {
|
|||||||
"running": True,
|
"running": True,
|
||||||
"type": "hold",
|
"type": "hold",
|
||||||
"holdClimateRef": "away",
|
"holdClimateRef": "away",
|
||||||
"endDate": "2022-01-01 10:00:00",
|
"startDate": "2022-02-02",
|
||||||
"startDate": "2022-02-02 11:00:00",
|
"startTime": "11:00:00",
|
||||||
|
"endDate": "2022-01-01",
|
||||||
|
"endTime": "10:00:00",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"remoteSensors": [
|
"remoteSensors": [
|
||||||
|
@ -42,8 +42,10 @@
|
|||||||
"running": true,
|
"running": true,
|
||||||
"type": "hold",
|
"type": "hold",
|
||||||
"holdClimateRef": "away",
|
"holdClimateRef": "away",
|
||||||
"endDate": "2022-01-01 10:00:00",
|
"startDate": "2022-02-02",
|
||||||
"startDate": "2022-02-02 11:00:00"
|
"startTime": "11:00:00",
|
||||||
|
"endDate": "2022-01-01",
|
||||||
|
"endTime": "10:00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"remoteSensors": [
|
"remoteSensors": [
|
||||||
@ -110,8 +112,10 @@
|
|||||||
"running": true,
|
"running": true,
|
||||||
"type": "hold",
|
"type": "hold",
|
||||||
"holdClimateRef": "away",
|
"holdClimateRef": "away",
|
||||||
"endDate": "2022-01-01 10:00:00",
|
"startDate": "2022-02-02",
|
||||||
"startDate": "2022-02-02 11:00:00"
|
"startTime": "11:00:00",
|
||||||
|
"endDate": "2022-01-01",
|
||||||
|
"endTime": "10:00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"remoteSensors": [
|
"remoteSensors": [
|
||||||
|
@ -9,7 +9,11 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant.components import climate
|
from homeassistant.components import climate
|
||||||
from homeassistant.components.climate import ClimateEntityFeature
|
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
|
import homeassistant.const as const
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, STATE_OFF
|
from homeassistant.const import ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, STATE_OFF
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
@ -31,6 +35,7 @@ def ecobee_fixture():
|
|||||||
"climates": [
|
"climates": [
|
||||||
{"name": "Climate1", "climateRef": "c1"},
|
{"name": "Climate1", "climateRef": "c1"},
|
||||||
{"name": "Climate2", "climateRef": "c2"},
|
{"name": "Climate2", "climateRef": "c2"},
|
||||||
|
{"name": "Away", "climateRef": "away"},
|
||||||
],
|
],
|
||||||
"currentClimateRef": "c1",
|
"currentClimateRef": "c1",
|
||||||
},
|
},
|
||||||
@ -56,9 +61,11 @@ def ecobee_fixture():
|
|||||||
"name": "Event1",
|
"name": "Event1",
|
||||||
"running": True,
|
"running": True,
|
||||||
"type": "hold",
|
"type": "hold",
|
||||||
"holdClimateRef": "away",
|
"holdClimateRef": "c1",
|
||||||
"endDate": "2017-01-01 10:00:00",
|
"startDate": "2017-02-02",
|
||||||
"startDate": "2017-02-02 11:00:00",
|
"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_count == 1
|
||||||
assert mock_ecobee.set_hvac_mode.call_args == mock.call(0, "auto")
|
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())]
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user