Skip to content

Commit

Permalink
Add AuxHeat switch to ecobee integration
Browse files Browse the repository at this point in the history
  • Loading branch information
bjpetit committed May 13, 2024
1 parent 548eb35 commit 4b4a69d
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 13 deletions.
21 changes: 13 additions & 8 deletions homeassistant/components/ecobee/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@
from homeassistant.util.unit_conversion import TemperatureConverter

from . import EcobeeData
from .const import _LOGGER, DOMAIN, ECOBEE_MODEL_TO_NAME, MANUFACTURER
from .const import (
_LOGGER,
DOMAIN,
ECOBEE_AUX_HEAT_ONLY,
ECOBEE_MODEL_TO_NAME,
MANUFACTURER,
)
from .util import ecobee_date, ecobee_time, is_indefinite_hold

ATTR_COOL_TEMP = "cool_temp"
Expand Down Expand Up @@ -69,19 +75,20 @@
DEFAULT_MAX_HUMIDITY = 50
HUMIDIFIER_MANUAL_MODE = "manual"

ECOBEE_AUX_HEAT_ONLY = "auxHeatOnly"


# Order matters, because for reverse mapping we don't want to map HEAT to AUX
ECOBEE_HVAC_TO_HASS = collections.OrderedDict(
[
("heat", HVACMode.HEAT),
("cool", HVACMode.COOL),
("auto", HVACMode.HEAT_COOL),
("off", HVACMode.OFF),
("auxHeatOnly", HVACMode.HEAT),
(ECOBEE_AUX_HEAT_ONLY, HVACMode.HEAT),
]
)
# Reverse key/value pair, drop auxHeatOnly as it doesn't map to specific HASS mode
HASS_TO_ECOBEE_HVAC = {
v: k for k, v in ECOBEE_HVAC_TO_HASS.items() if k != ECOBEE_AUX_HEAT_ONLY
}

ECOBEE_HVAC_ACTION_TO_HASS = {
# Map to None if we do not know how to represent.
Expand Down Expand Up @@ -740,9 +747,7 @@ def set_humidity(self, humidity: int) -> None:

def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set HVAC mode (auto, auxHeatOnly, cool, heat, off)."""
ecobee_value = next(
(k for k, v in ECOBEE_HVAC_TO_HASS.items() if v == hvac_mode), None
)
ecobee_value = HASS_TO_ECOBEE_HVAC.get(hvac_mode)
if ecobee_value is None:
_LOGGER.error("Invalid mode for set_hvac_mode: %s", hvac_mode)
return
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/ecobee/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@

MANUFACTURER = "ecobee"

ECOBEE_AUX_HEAT_ONLY = "auxHeatOnly"

# Translates ecobee API weatherSymbol to Home Assistant usable names
# https://www.ecobee.com/home/developer/api/documentation/v1/objects/WeatherForecast.shtml
ECOBEE_WEATHER_SYMBOL_TO_HASS = {
Expand Down
48 changes: 47 additions & 1 deletion homeassistant/components/ecobee/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
import logging
from typing import Any

from homeassistant.components.climate import HVACMode
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import dt as dt_util

from . import EcobeeData
from .const import DOMAIN
from .climate import HASS_TO_ECOBEE_HVAC
from .const import DOMAIN, ECOBEE_AUX_HEAT_ONLY
from .entity import EcobeeBaseEntity

_LOGGER = logging.getLogger(__name__)
Expand All @@ -37,6 +39,12 @@ async def async_setup_entry(
True,
)

async_add_entities(
EcobeeSwitchAuxHeatOnly(data, index)
for index, thermostat in enumerate(data.ecobee.thermostats)
if thermostat["settings"]["hasHeatPump"]
)


class EcobeeVentilator20MinSwitch(EcobeeBaseEntity, SwitchEntity):
"""A Switch class, representing 20 min timer for an ecobee thermostat with ventilator attached."""
Expand Down Expand Up @@ -88,3 +96,41 @@ async def async_turn_off(self, **kwargs: Any) -> None:
self.data.ecobee.set_ventilator_timer, self.thermostat_index, False
)
self.update_without_throttle = True


class EcobeeSwitchAuxHeatOnly(EcobeeBaseEntity, SwitchEntity):
"""Representation of a aux_heat_only ecobee switch."""

_attr_has_entity_name = True
_attr_name = "Aux Heat Only"

def __init__(
self,
data: EcobeeData,
thermostat_index: int,
) -> None:
"""Initialize ecobee ventilator platform."""
super().__init__(data, thermostat_index)
self._attr_unique_id = f"{self.base_unique_id}_aux_heat_only"

self._last_hvac_mode_before_aux_heat = HASS_TO_ECOBEE_HVAC.get(
HVACMode.HEAT_COOL
)

def turn_on(self, **kwargs: Any) -> None:
"""Set the hvacMode to auxHeatOnly."""
_LOGGER.debug("Setting HVAC mode to auxHeatOnly to turn on aux heat")
self._last_hvac_mode_before_aux_heat = self.thermostat["settings"]["hvacMode"]
self.data.ecobee.set_hvac_mode(self.thermostat_index, ECOBEE_AUX_HEAT_ONLY)

def turn_off(self, **kwargs: Any) -> None:
"""Set the hvacMode back to the prior setting."""
_LOGGER.debug("Setting HVAC mode to last mode to disable aux heat")
self.data.ecobee.set_hvac_mode(
self.thermostat_index, self._last_hvac_mode_before_aux_heat
)

@property
def is_on(self) -> bool:
"""Return true if auxHeatOnly mode is active."""
return self.thermostat["settings"]["hvacMode"] == ECOBEE_AUX_HEAT_ONLY
23 changes: 19 additions & 4 deletions tests/components/ecobee/fixtures/ecobee-data.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@
},
"program": {
"climates": [
{ "name": "Climate1", "climateRef": "c1" },
{ "name": "Climate2", "climateRef": "c2" }
{
"name": "Climate1",
"climateRef": "c1"
},
{
"name": "Climate2",
"climateRef": "c2"
}
],
"currentClimateRef": "c1"
},
Expand All @@ -39,6 +45,7 @@
"isVentilatorTimerOn": false,
"hasHumidifier": true,
"humidifierMode": "manual",
"hasHeatPump": false,
"humidity": "30"
},
"equipmentStatus": "fan",
Expand Down Expand Up @@ -82,8 +89,14 @@
"modelNumber": "athenaSmart",
"program": {
"climates": [
{ "name": "Climate1", "climateRef": "c1" },
{ "name": "Climate2", "climateRef": "c2" }
{
"name": "Climate1",
"climateRef": "c1"
},
{
"name": "Climate2",
"climateRef": "c2"
}
],
"currentClimateRef": "c1"
},
Expand All @@ -109,6 +122,7 @@
"isVentilatorTimerOn": false,
"hasHumidifier": true,
"humidifierMode": "manual",
"hasHeatPump": true,
"humidity": "30"
},
"equipmentStatus": "fan",
Expand Down Expand Up @@ -184,6 +198,7 @@
"isVentilatorTimerOn": false,
"hasHumidifier": true,
"humidifierMode": "manual",
"hasHeatPump": false,
"humidity": "30"
},
"equipmentStatus": "fan",
Expand Down
33 changes: 33 additions & 0 deletions tests/components/ecobee/test_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,36 @@ async def test_turn_off_20min_ventilator(hass: HomeAssistant) -> None:
)
await hass.async_block_till_done()
mock_set_20min_ventilator.assert_called_once_with(THERMOSTAT_ID, False)


DEVICE_ID = "switch.ecobee2_aux_heat_only"


async def test_aux_heat_only_turn_on(hass: HomeAssistant) -> None:
"""Test the switch can be turned on."""
with patch("pyecobee.Ecobee.set_hvac_mode") as mock_turn_on:
await setup_platform(hass, DOMAIN)

await hass.services.async_call(
DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: DEVICE_ID},
blocking=True,
)
await hass.async_block_till_done()
mock_turn_on.assert_called_once_with(1, "auxHeatOnly")


async def test_aux_heat_only_turn_off(hass: HomeAssistant) -> None:
"""Test the switch can be turned off."""
with patch("pyecobee.Ecobee.set_hvac_mode") as mock_turn_off:
await setup_platform(hass, DOMAIN)

await hass.services.async_call(
DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: DEVICE_ID},
blocking=True,
)
await hass.async_block_till_done()
mock_turn_off.assert_called_once_with(1, "auto")

0 comments on commit 4b4a69d

Please sign in to comment.