mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 01:07:10 +00:00
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>
This commit is contained in:
parent
433321136d
commit
a1a08f7755
@ -6,9 +6,14 @@ from collections.abc import Awaitable, Callable
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import logging
|
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.config_entries import ConfigEntry
|
||||||
from homeassistant.const import UnitOfTime
|
from homeassistant.const import UnitOfTemperature, UnitOfTime
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
@ -54,21 +59,30 @@ async def async_setup_entry(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the ecobee thermostat number entity."""
|
"""Set up the ecobee thermostat number entity."""
|
||||||
data: EcobeeData = hass.data[DOMAIN]
|
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)
|
for index, thermostat in enumerate(data.ecobee.thermostats)
|
||||||
if thermostat["settings"]["ventilatorType"] != "none"
|
if thermostat["settings"]["hasHeatPump"]
|
||||||
for numbers in VENTILATOR_NUMBERS
|
)
|
||||||
),
|
|
||||||
True,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
class EcobeeVentilatorMinTime(EcobeeBaseEntity, NumberEntity):
|
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
|
entity_description: EcobeeNumberEntityDescription
|
||||||
|
|
||||||
@ -105,3 +119,53 @@ class EcobeeVentilatorMinTime(EcobeeBaseEntity, NumberEntity):
|
|||||||
"""Set new ventilator Min On Time value."""
|
"""Set new ventilator Min On Time value."""
|
||||||
self.entity_description.set_fn(self.data, self.thermostat_index, int(value))
|
self.entity_description.set_fn(self.data, self.thermostat_index, int(value))
|
||||||
self.update_without_throttle = True
|
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": {
|
"number": {
|
||||||
"ventilator_min_type_home": {
|
"ventilator_min_type_home": {
|
||||||
"name": "Ventilator min time home"
|
"name": "Ventilator minimum time home"
|
||||||
},
|
},
|
||||||
"ventilator_min_type_away": {
|
"ventilator_min_type_away": {
|
||||||
"name": "Ventilator min time away"
|
"name": "Ventilator minimum time away"
|
||||||
|
},
|
||||||
|
"compressor_protection_min_temp": {
|
||||||
|
"name": "Compressor minimum temperature"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"switch": {
|
"switch": {
|
||||||
"aux_heat_only": {
|
"aux_heat_only": {
|
||||||
"name": "Aux heat only"
|
"name": "Auxiliary heat only"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -160,6 +160,7 @@
|
|||||||
"hasHumidifier": true,
|
"hasHumidifier": true,
|
||||||
"humidifierMode": "manual",
|
"humidifierMode": "manual",
|
||||||
"hasHeatPump": true,
|
"hasHeatPump": true,
|
||||||
|
"compressorProtectionMinTemp": 100,
|
||||||
"humidity": "30"
|
"humidity": "30"
|
||||||
},
|
},
|
||||||
"equipmentStatus": "fan",
|
"equipmentStatus": "fan",
|
||||||
|
@ -12,8 +12,8 @@ from homeassistant.core import HomeAssistant
|
|||||||
|
|
||||||
from .common import setup_platform
|
from .common import setup_platform
|
||||||
|
|
||||||
VENTILATOR_MIN_HOME_ID = "number.ecobee_ventilator_min_time_home"
|
VENTILATOR_MIN_HOME_ID = "number.ecobee_ventilator_minimum_time_home"
|
||||||
VENTILATOR_MIN_AWAY_ID = "number.ecobee_ventilator_min_time_away"
|
VENTILATOR_MIN_AWAY_ID = "number.ecobee_ventilator_minimum_time_away"
|
||||||
THERMOSTAT_ID = 0
|
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("min") == 0
|
||||||
assert state.attributes.get("max") == 60
|
assert state.attributes.get("max") == 60
|
||||||
assert state.attributes.get("step") == 5
|
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
|
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("min") == 0
|
||||||
assert state.attributes.get("max") == 60
|
assert state.attributes.get("max") == 60
|
||||||
assert state.attributes.get("step") == 5
|
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
|
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()
|
await hass.async_block_till_done()
|
||||||
mock_set_min_away_time.assert_called_once_with(THERMOSTAT_ID, target_value)
|
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)
|
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:
|
async def test_aux_heat_only_turn_on(hass: HomeAssistant) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user