Ecobee aux cutover threshold (#129474)
* removing extra blank space * Adding EcobeeAuxCutoverThreshold First pass. * minor reorg and changes; testing local check-in * Adding entity, setting device class and name * Bumping max value slightly to hopefully accomodate celsius, setting numberMode=box * fixing the entity name for aux cutover threshold * Combined async_add_entities * Using a list comprehension Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * fixing stuff with listcomprehension * exchanging call to list.append() to extend with list comprehension * Updating the class name and the entity name to match the device UI. Removing abbreviations from entity names * Fixing tests to match new entity names * respecting 88 column limit * Formatting * Adding test coverage for update/set compressorMinTemp values --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>pull/130287/head
parent
433321136d
commit
a1a08f7755
|
@ -6,9 +6,14 @@ from collections.abc import Awaitable, Callable
|
|||
from dataclasses import dataclass
|
||||
import logging
|
||||
|
||||
from homeassistant.components.number import NumberEntity, NumberEntityDescription
|
||||
from homeassistant.components.number import (
|
||||
NumberDeviceClass,
|
||||
NumberEntity,
|
||||
NumberEntityDescription,
|
||||
NumberMode,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import UnitOfTime
|
||||
from homeassistant.const import UnitOfTemperature, UnitOfTime
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
|
@ -54,21 +59,30 @@ async def async_setup_entry(
|
|||
) -> None:
|
||||
"""Set up the ecobee thermostat number entity."""
|
||||
data: EcobeeData = hass.data[DOMAIN]
|
||||
_LOGGER.debug("Adding min time ventilators numbers (if present)")
|
||||
|
||||
async_add_entities(
|
||||
assert data is not None
|
||||
|
||||
entities: list[NumberEntity] = [
|
||||
EcobeeVentilatorMinTime(data, index, numbers)
|
||||
for index, thermostat in enumerate(data.ecobee.thermostats)
|
||||
if thermostat["settings"]["ventilatorType"] != "none"
|
||||
for numbers in VENTILATOR_NUMBERS
|
||||
]
|
||||
|
||||
_LOGGER.debug("Adding compressor min temp number (if present)")
|
||||
entities.extend(
|
||||
(
|
||||
EcobeeVentilatorMinTime(data, index, numbers)
|
||||
EcobeeCompressorMinTemp(data, index)
|
||||
for index, thermostat in enumerate(data.ecobee.thermostats)
|
||||
if thermostat["settings"]["ventilatorType"] != "none"
|
||||
for numbers in VENTILATOR_NUMBERS
|
||||
),
|
||||
True,
|
||||
if thermostat["settings"]["hasHeatPump"]
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class EcobeeVentilatorMinTime(EcobeeBaseEntity, NumberEntity):
|
||||
"""A number class, representing min time for an ecobee thermostat with ventilator attached."""
|
||||
"""A number class, representing min time for an ecobee thermostat with ventilator attached."""
|
||||
|
||||
entity_description: EcobeeNumberEntityDescription
|
||||
|
||||
|
@ -105,3 +119,53 @@ class EcobeeVentilatorMinTime(EcobeeBaseEntity, NumberEntity):
|
|||
"""Set new ventilator Min On Time value."""
|
||||
self.entity_description.set_fn(self.data, self.thermostat_index, int(value))
|
||||
self.update_without_throttle = True
|
||||
|
||||
|
||||
class EcobeeCompressorMinTemp(EcobeeBaseEntity, NumberEntity):
|
||||
"""Minimum outdoor temperature at which the compressor will operate.
|
||||
|
||||
This applies more to air source heat pumps than geothermal. This serves as a safety
|
||||
feature (compressors have a minimum operating temperature) as well as
|
||||
providing the ability to choose fuel in a dual-fuel system (i.e. choose between
|
||||
electrical heat pump and fossil auxiliary heat depending on Time of Use, Solar,
|
||||
etc.).
|
||||
Note that python-ecobee-api refers to this as Aux Cutover Threshold, but Ecobee
|
||||
uses Compressor Protection Min Temp.
|
||||
"""
|
||||
|
||||
_attr_device_class = NumberDeviceClass.TEMPERATURE
|
||||
_attr_has_entity_name = True
|
||||
_attr_icon = "mdi:thermometer-off"
|
||||
_attr_mode = NumberMode.BOX
|
||||
_attr_native_min_value = -25
|
||||
_attr_native_max_value = 66
|
||||
_attr_native_step = 5
|
||||
_attr_native_unit_of_measurement = UnitOfTemperature.FAHRENHEIT
|
||||
_attr_translation_key = "compressor_protection_min_temp"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: EcobeeData,
|
||||
thermostat_index: int,
|
||||
) -> None:
|
||||
"""Initialize ecobee compressor min temperature."""
|
||||
super().__init__(data, thermostat_index)
|
||||
self._attr_unique_id = f"{self.base_unique_id}_compressor_protection_min_temp"
|
||||
self.update_without_throttle = False
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Get the latest state from the thermostat."""
|
||||
if self.update_without_throttle:
|
||||
await self.data.update(no_throttle=True)
|
||||
self.update_without_throttle = False
|
||||
else:
|
||||
await self.data.update()
|
||||
|
||||
self._attr_native_value = (
|
||||
(self.thermostat["settings"]["compressorProtectionMinTemp"]) / 10
|
||||
)
|
||||
|
||||
def set_native_value(self, value: float) -> None:
|
||||
"""Set new compressor minimum temperature."""
|
||||
self.data.ecobee.set_aux_cutover_threshold(self.thermostat_index, value)
|
||||
self.update_without_throttle = True
|
||||
|
|
|
@ -33,15 +33,18 @@
|
|||
},
|
||||
"number": {
|
||||
"ventilator_min_type_home": {
|
||||
"name": "Ventilator min time home"
|
||||
"name": "Ventilator minimum time home"
|
||||
},
|
||||
"ventilator_min_type_away": {
|
||||
"name": "Ventilator min time away"
|
||||
"name": "Ventilator minimum time away"
|
||||
},
|
||||
"compressor_protection_min_temp": {
|
||||
"name": "Compressor minimum temperature"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"aux_heat_only": {
|
||||
"name": "Aux heat only"
|
||||
"name": "Auxiliary heat only"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -160,6 +160,7 @@
|
|||
"hasHumidifier": true,
|
||||
"humidifierMode": "manual",
|
||||
"hasHeatPump": true,
|
||||
"compressorProtectionMinTemp": 100,
|
||||
"humidity": "30"
|
||||
},
|
||||
"equipmentStatus": "fan",
|
||||
|
|
|
@ -12,8 +12,8 @@ from homeassistant.core import HomeAssistant
|
|||
|
||||
from .common import setup_platform
|
||||
|
||||
VENTILATOR_MIN_HOME_ID = "number.ecobee_ventilator_min_time_home"
|
||||
VENTILATOR_MIN_AWAY_ID = "number.ecobee_ventilator_min_time_away"
|
||||
VENTILATOR_MIN_HOME_ID = "number.ecobee_ventilator_minimum_time_home"
|
||||
VENTILATOR_MIN_AWAY_ID = "number.ecobee_ventilator_minimum_time_away"
|
||||
THERMOSTAT_ID = 0
|
||||
|
||||
|
||||
|
@ -26,7 +26,9 @@ async def test_ventilator_min_on_home_attributes(hass: HomeAssistant) -> None:
|
|||
assert state.attributes.get("min") == 0
|
||||
assert state.attributes.get("max") == 60
|
||||
assert state.attributes.get("step") == 5
|
||||
assert state.attributes.get("friendly_name") == "ecobee Ventilator min time home"
|
||||
assert (
|
||||
state.attributes.get("friendly_name") == "ecobee Ventilator minimum time home"
|
||||
)
|
||||
assert state.attributes.get("unit_of_measurement") == UnitOfTime.MINUTES
|
||||
|
||||
|
||||
|
@ -39,7 +41,9 @@ async def test_ventilator_min_on_away_attributes(hass: HomeAssistant) -> None:
|
|||
assert state.attributes.get("min") == 0
|
||||
assert state.attributes.get("max") == 60
|
||||
assert state.attributes.get("step") == 5
|
||||
assert state.attributes.get("friendly_name") == "ecobee Ventilator min time away"
|
||||
assert (
|
||||
state.attributes.get("friendly_name") == "ecobee Ventilator minimum time away"
|
||||
)
|
||||
assert state.attributes.get("unit_of_measurement") == UnitOfTime.MINUTES
|
||||
|
||||
|
||||
|
@ -77,3 +81,42 @@ async def test_set_min_time_away(hass: HomeAssistant) -> None:
|
|||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_set_min_away_time.assert_called_once_with(THERMOSTAT_ID, target_value)
|
||||
|
||||
|
||||
COMPRESSOR_MIN_TEMP_ID = "number.ecobee2_compressor_minimum_temperature"
|
||||
|
||||
|
||||
async def test_compressor_protection_min_temp_attributes(hass: HomeAssistant) -> None:
|
||||
"""Test the compressor min temp value is correct.
|
||||
|
||||
Ecobee runs in Fahrenheit; the test rig runs in Celsius. Conversions are necessary.
|
||||
"""
|
||||
await setup_platform(hass, NUMBER_DOMAIN)
|
||||
|
||||
state = hass.states.get(COMPRESSOR_MIN_TEMP_ID)
|
||||
assert state.state == "-12.2"
|
||||
assert (
|
||||
state.attributes.get("friendly_name")
|
||||
== "ecobee2 Compressor minimum temperature"
|
||||
)
|
||||
|
||||
|
||||
async def test_set_compressor_protection_min_temp(hass: HomeAssistant) -> None:
|
||||
"""Test the number can set minimum compressor operating temp.
|
||||
|
||||
Ecobee runs in Fahrenheit; the test rig runs in Celsius. Conversions are necessary
|
||||
"""
|
||||
target_value = 0
|
||||
with patch(
|
||||
"homeassistant.components.ecobee.Ecobee.set_aux_cutover_threshold"
|
||||
) as mock_set_compressor_min_temp:
|
||||
await setup_platform(hass, NUMBER_DOMAIN)
|
||||
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: COMPRESSOR_MIN_TEMP_ID, ATTR_VALUE: target_value},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
mock_set_compressor_min_temp.assert_called_once_with(1, 32)
|
||||
|
|
|
@ -118,7 +118,7 @@ async def test_turn_off_20min_ventilator(hass: HomeAssistant) -> None:
|
|||
mock_set_20min_ventilator.assert_called_once_with(THERMOSTAT_ID, False)
|
||||
|
||||
|
||||
DEVICE_ID = "switch.ecobee2_aux_heat_only"
|
||||
DEVICE_ID = "switch.ecobee2_auxiliary_heat_only"
|
||||
|
||||
|
||||
async def test_aux_heat_only_turn_on(hass: HomeAssistant) -> None:
|
||||
|
|
Loading…
Reference in New Issue