With calculation of VTherm temp entities + test ok

This commit is contained in:
Jean-Marc Collin
2024-03-03 19:06:59 +00:00
parent ea6f2d5579
commit 156d19666c
7 changed files with 524 additions and 83 deletions
@@ -137,8 +137,6 @@ from .prop_algorithm import PropAlgorithm
from .open_window_algorithm import WindowOpenDetectionAlgorithm from .open_window_algorithm import WindowOpenDetectionAlgorithm
from .ema import ExponentialMovingAverage from .ema import ExponentialMovingAverage
from .temp_number import TemperatureNumber
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ConfigData = MappingProxyType[str, Any] ConfigData = MappingProxyType[str, Any]
@@ -216,6 +214,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
super().__init__() super().__init__()
self._hass = hass self._hass = hass
self._entry_infos = None
self._attr_extra_state_attributes = {} self._attr_extra_state_attributes = {}
self._unique_id = unique_id self._unique_id = unique_id
@@ -288,6 +287,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._is_used_by_central_boiler = False self._is_used_by_central_boiler = False
self._support_flags = None self._support_flags = None
# Preset will be initialized from Number entities
self._presets: dict[str, Any] = {} # presets
self._presets_away: dict[str, Any] = {} # presets_away
self._attr_preset_modes: list[str] | None self._attr_preset_modes: list[str] | None
self.post_init(entry_infos) self.post_init(entry_infos)
@@ -356,6 +359,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
_LOGGER.info("%s - The merged configuration is %s", self, entry_infos) _LOGGER.info("%s - The merged configuration is %s", self, entry_infos)
self._entry_infos = entry_infos
self._ac_mode = entry_infos.get(CONF_AC_MODE) is True self._ac_mode = entry_infos.get(CONF_AC_MODE) is True
self._attr_max_temp = entry_infos.get(CONF_TEMP_MAX) self._attr_max_temp = entry_infos.get(CONF_TEMP_MAX)
self._attr_min_temp = entry_infos.get(CONF_TEMP_MIN) self._attr_min_temp = entry_infos.get(CONF_TEMP_MIN)
@@ -2649,18 +2654,68 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
"""Send an event""" """Send an event"""
send_vtherm_event(self._hass, event_type=event_type, entity=self, data=data) send_vtherm_event(self._hass, event_type=event_type, entity=self, data=data)
def get_temperature_number_entities(self, config_entry: ConfigData): async def init_presets(self, central_config):
"""Creates all TemperatureNumber depending of the configuration of the Climate""" """Init all presets of the VTherm"""
# If preset central config is used and central config is set , take the presets from central config
vtherm_api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api()
# TODO add the list of preset we want to use in the VTherm. Here we will suppose all preset will be available presets: dict[str, Any] = {}
entity = TemperatureNumber( presets_away: dict[str, Any] = {}
self._hass,
unique_id=config_entry.entry_id, def calculate_presets(items, use_central_conf_key):
name=config_entry.data.get(CONF_NAME), presets: dict[str, Any] = {}
preset_name="comfort", config_id = self._unique_id
is_ac=False, if (
is_away=False, central_config
entry_infos=config_entry.data, and self._entry_infos.get(use_central_conf_key, False) is True
):
config_id = central_config.entry_id
for key, preset_name in items:
_LOGGER.debug("looking for key=%s, preset_name=%s", key, preset_name)
value = vtherm_api.get_temperature_number_value(
config_id=config_id, preset_name=preset_name
)
if value is not None:
presets[key] = value
else:
_LOGGER.debug("preset_name %s not found in VTherm API", preset_name)
presets[key] = (
self._attr_max_temp if self._ac_mode else self._attr_min_temp
)
return presets
# Calculate all presets
presets = calculate_presets(
CONF_PRESETS_WITH_AC.items() if self._ac_mode else CONF_PRESETS.items(),
CONF_USE_PRESETS_CENTRAL_CONFIG,
) )
return entity if self._entry_infos.get(CONF_USE_PRESENCE_FEATURE) is True:
presets_away = calculate_presets(
(
CONF_PRESETS_AWAY_WITH_AC.items()
if self._ac_mode
else CONF_PRESETS_AWAY.items()
),
CONF_USE_PRESENCE_CENTRAL_CONFIG,
)
# aggregate all available presets now
self._presets: dict[str, Any] = presets
self._presets_away: dict[str, Any] = presets_away
# Calculate all possible presets
self._attr_preset_modes = [PRESET_NONE]
if len(self._presets):
self._support_flags = SUPPORT_FLAGS | ClimateEntityFeature.PRESET_MODE
for key, _ in CONF_PRESETS.items():
if self.find_preset_temp(key) > 0:
self._attr_preset_modes.append(key)
_LOGGER.debug(
"After adding presets, preset_modes to %s", self._attr_preset_modes
)
else:
_LOGGER.debug("No preset_modes")
@@ -7,11 +7,11 @@ from homeassistant.core import (
HomeAssistant, HomeAssistant,
callback, callback,
Event, Event,
CoreState, # CoreState,
HomeAssistantError, HomeAssistantError,
) )
from homeassistant.const import STATE_ON, STATE_OFF, EVENT_HOMEASSISTANT_START from homeassistant.const import STATE_ON, STATE_OFF # , EVENT_HOMEASSISTANT_START
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.event import async_track_state_change_event
@@ -386,17 +386,18 @@ class CentralBoilerBinarySensor(BinarySensorEntity):
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self._hass) api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self._hass)
api.register_central_boiler(self) api.register_central_boiler(self)
@callback # Should be not more needed and replaced by vtherm_api.init_vtherm_links
async def _async_startup_internal(*_): # @callback
_LOGGER.debug("%s - Calling async_startup_internal", self) # async def _async_startup_internal(*_):
await self.listen_nb_active_vtherm_entity() # _LOGGER.debug("%s - Calling async_startup_internal", self)
# await self.listen_nb_active_vtherm_entity()
if self.hass.state == CoreState.running: #
await _async_startup_internal() # if self.hass.state == CoreState.running:
else: # await _async_startup_internal()
self.hass.bus.async_listen_once( # else:
EVENT_HOMEASSISTANT_START, _async_startup_internal # self.hass.bus.async_listen_once(
) # EVENT_HOMEASSISTANT_START, _async_startup_internal
# )
async def listen_nb_active_vtherm_entity(self): async def listen_nb_active_vtherm_entity(self):
"""Initialize the listening of state change of VTherms""" """Initialize the listening of state change of VTherms"""
@@ -76,7 +76,7 @@ async def async_setup_entry(
elif vt_type == CONF_THERMOSTAT_VALVE: elif vt_type == CONF_THERMOSTAT_VALVE:
entity = ThermostatOverValve(hass, unique_id, name, entry.data) entity = ThermostatOverValve(hass, unique_id, name, entry.data)
async_add_entities([entity, entity.get_temperature_number_entities(entry)], True) async_add_entities([entity], True)
# Add services # Add services
platform = entity_platform.async_get_current_platform() platform = entity_platform.async_get_current_platform()
@@ -23,7 +23,7 @@ from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import ensure_unique_string, slugify
from .vtherm_api import VersatileThermostatAPI from .vtherm_api import VersatileThermostatAPI
from .commons import VersatileThermostatBaseEntity from .commons import VersatileThermostatBaseEntity
@@ -48,6 +48,9 @@ from .const import (
CONF_PRESETS_WITH_AC_VALUES, CONF_PRESETS_WITH_AC_VALUES,
CONF_PRESETS_AWAY_VALUES, CONF_PRESETS_AWAY_VALUES,
CONF_PRESETS_AWAY_WITH_AC_VALUES, CONF_PRESETS_AWAY_WITH_AC_VALUES,
CONF_USE_PRESETS_CENTRAL_CONFIG,
CONF_USE_PRESENCE_CENTRAL_CONFIG,
CONF_USE_PRESENCE_FEATURE,
overrides, overrides,
) )
@@ -84,28 +87,45 @@ async def async_setup_entry(
unique_id = entry.entry_id unique_id = entry.entry_id
name = entry.data.get(CONF_NAME) name = entry.data.get(CONF_NAME)
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE) vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
is_central_boiler = entry.data.get(CONF_ADD_CENTRAL_BOILER_CONTROL) # is_central_boiler = entry.data.get(CONF_ADD_CENTRAL_BOILER_CONTROL)
entities = [] entities = []
if vt_type != CONF_THERMOSTAT_CENTRAL_CONFIG: if vt_type != CONF_THERMOSTAT_CENTRAL_CONFIG:
if not is_central_boiler: # Creates non central temperature entities
pass if not entry.data.get(CONF_USE_PRESETS_CENTRAL_CONFIG, False):
# for preset in CONF_PRESETS_VALUES: for preset in CONF_PRESETS_VALUES:
# entities.append( entities.append(
# TemperatureNumber( TemperatureNumber(
# hass, unique_id, preset, preset, False, False, entry.data hass, unique_id, name, preset, False, False, entry.data
# ) )
# ) )
if entry.data.get(CONF_AC_MODE, False):
for preset in CONF_PRESETS_WITH_AC_VALUES:
entities.append(
TemperatureNumber(
hass, unique_id, name, preset, True, False, entry.data
)
)
# TODO if entry.data.get(
# if entry.data.get(CONF_AC_MODE, False): CONF_USE_PRESENCE_FEATURE, False
# for preset in CONF_PRESETS_WITH_AC_VALUES: ) is True and not entry.data.get(CONF_USE_PRESENCE_CENTRAL_CONFIG, False):
# entities.append( for preset in CONF_PRESETS_AWAY_VALUES:
# TemperatureNumber( entities.append(
# hass, unique_id, preset, preset, True, False, entry.data TemperatureNumber(
# ) hass, unique_id, name, preset, False, True, entry.data
# ) )
)
if entry.data.get(CONF_AC_MODE, False):
for preset in CONF_PRESETS_AWAY_WITH_AC_VALUES:
entities.append(
TemperatureNumber(
hass, unique_id, name, preset, True, True, entry.data
)
)
# For central config only
else: else:
entities.append( entities.append(
ActivateBoilerThresholdNumber(hass, unique_id, name, entry.data) ActivateBoilerThresholdNumber(hass, unique_id, name, entry.data)
@@ -113,27 +133,27 @@ async def async_setup_entry(
for preset in CONF_PRESETS_VALUES: for preset in CONF_PRESETS_VALUES:
entities.append( entities.append(
CentralConfigTemperatureNumber( CentralConfigTemperatureNumber(
hass, unique_id, preset, preset, False, False, entry.data hass, unique_id, name, preset, False, False, entry.data
) )
) )
for preset in CONF_PRESETS_WITH_AC_VALUES: for preset in CONF_PRESETS_WITH_AC_VALUES:
entities.append( entities.append(
CentralConfigTemperatureNumber( CentralConfigTemperatureNumber(
hass, unique_id, preset, preset, True, False, entry.data hass, unique_id, name, preset, True, False, entry.data
) )
) )
for preset in CONF_PRESETS_AWAY_VALUES: for preset in CONF_PRESETS_AWAY_VALUES:
entities.append( entities.append(
CentralConfigTemperatureNumber( CentralConfigTemperatureNumber(
hass, unique_id, preset, preset, False, True, entry.data hass, unique_id, name, preset, False, True, entry.data
) )
) )
for preset in CONF_PRESETS_AWAY_WITH_AC_VALUES: for preset in CONF_PRESETS_AWAY_WITH_AC_VALUES:
entities.append( entities.append(
CentralConfigTemperatureNumber( CentralConfigTemperatureNumber(
hass, unique_id, preset, preset, True, True, entry.data hass, unique_id, name, preset, True, True, entry.data
) )
) )
@@ -211,7 +231,6 @@ class CentralConfigTemperatureNumber(NumberEntity, RestoreEntity):
"""Representation of one temperature number""" """Representation of one temperature number"""
_attr_has_entity_name = True _attr_has_entity_name = True
# _attr_translation_key = "temperature"
def __init__( def __init__(
self, self,
@@ -227,7 +246,7 @@ class CentralConfigTemperatureNumber(NumberEntity, RestoreEntity):
the restoration will do the trick.""" the restoration will do the trick."""
self._config_id = unique_id self._config_id = unique_id
self._device_name = entry_infos.get(CONF_NAME) self._device_name = name
# self._attr_name = name # self._attr_name = name
self._attr_translation_key = preset_name self._attr_translation_key = preset_name
@@ -236,7 +255,8 @@ class CentralConfigTemperatureNumber(NumberEntity, RestoreEntity):
# "ac": "-AC" if is_ac else "", # "ac": "-AC" if is_ac else "",
# "away": "-AWAY" if is_away else "", # "away": "-AWAY" if is_away else "",
# } # }
self.entity_id = f"{NUMBER_DOMAIN}.central_configuration_{preset_name}" # self.entity_id = f"{NUMBER_DOMAIN}.central_configuration_{preset_name}"
self.entity_id = f"{NUMBER_DOMAIN}.{slugify(name)}_{preset_name}"
self._attr_unique_id = f"central_configuration_{preset_name}" self._attr_unique_id = f"central_configuration_{preset_name}"
self._attr_device_class = NumberDeviceClass.TEMPERATURE self._attr_device_class = NumberDeviceClass.TEMPERATURE
self._attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS self._attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
@@ -250,7 +270,7 @@ class CentralConfigTemperatureNumber(NumberEntity, RestoreEntity):
# previous value # previous value
# TODO remove this after the next major release and just keep the init min/max # TODO remove this after the next major release and just keep the init min/max
temp = None temp = None
if temp := entry_infos.get(preset_name, None): if (temp := entry_infos.get(preset_name, None)) is not None:
self._attr_value = self._attr_native_value = temp self._attr_value = self._attr_native_value = temp
else: else:
if entry_infos.get(CONF_AC_MODE) is True: if entry_infos.get(CONF_AC_MODE) is True:
@@ -346,7 +366,6 @@ class TemperatureNumber( # pylint: disable=abstract-method
"""Representation of one temperature number""" """Representation of one temperature number"""
_attr_has_entity_name = True _attr_has_entity_name = True
_attr_translation_key = "temperature"
def __init__( def __init__(
self, self,
@@ -360,40 +379,41 @@ class TemperatureNumber( # pylint: disable=abstract-method
) -> None: ) -> None:
"""Initialize the temperature with entry_infos if available. Else """Initialize the temperature with entry_infos if available. Else
the restoration will do the trick.""" the restoration will do the trick."""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME)) super().__init__(hass, unique_id, name)
split = name.split("_") self._attr_translation_key = preset_name
# self._attr_name = split[0] self.entity_id = f"{NUMBER_DOMAIN}.{slugify(name)}_{preset_name}"
# if "_" + split[1] == PRESET_AC_SUFFIX:
# self._attr_name = self._attr_name + " AC"
# self._attr_name = self._attr_name + " temperature" # self._attr_translation_placeholders = {
# "preset": preset_name,
self._attr_translation_placeholders = { # "ac": "-AC" if is_ac else "",
"preset": preset_name, # "away": "-AWAY" if is_away else "",
"ac": "-AC" if is_ac else "", # }
"away": "-AWAY" if is_away else "", self._attr_unique_id = f"{self._device_name}_{preset_name}"
}
self._attr_unique_id = f"{self._device_name}_{name}"
self._attr_device_class = NumberDeviceClass.TEMPERATURE self._attr_device_class = NumberDeviceClass.TEMPERATURE
self._attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS self._attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
self._attr_native_step = entry_infos.get(CONF_STEP_TEMPERATURE, 0.5)
self._attr_native_min_value = entry_infos.get(CONF_TEMP_MIN)
self._attr_native_max_value = entry_infos.get(CONF_TEMP_MAX)
# Initialize the values if included into the entry_infos. This will do # Initialize the values if included into the entry_infos. This will do
# the temperature migration. # the temperature migration.
# TODO see if this should be replace by the central config if any # TODO see if this should be replace by the central config if any
temp = None temp = None
if temp := entry_infos.get(preset_name, None): if (temp := entry_infos.get(preset_name, None)) is not None:
self._attr_value = self._attr_native_value = temp self._attr_value = self._attr_native_value = temp
else:
if entry_infos.get(CONF_AC_MODE) is True:
self._attr_native_value = self._attr_native_max_value
else:
self._attr_native_value = self._attr_native_min_value
self._attr_mode = NumberMode.BOX self._attr_mode = NumberMode.BOX
self._preset_name = preset_name self._preset_name = preset_name
self._is_away = is_away self._is_away = is_away
self._is_ac = is_ac self._is_ac = is_ac
self._attr_native_step = entry_infos.get(CONF_STEP_TEMPERATURE, 0.5)
self._attr_native_min_value = entry_infos.get(CONF_TEMP_MIN)
self._attr_native_max_value = entry_infos.get(CONF_TEMP_MAX)
@property @property
def icon(self) -> str | None: def icon(self) -> str | None:
return PRESET_ICON_MAPPING[self._preset_name] return PRESET_ICON_MAPPING[self._preset_name]
@@ -402,12 +422,16 @@ class TemperatureNumber( # pylint: disable=abstract-method
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:
await super().async_added_to_hass() await super().async_added_to_hass()
# register the temp entity for this device and preset
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self.hass)
api.register_temperature_number(self._config_id, self._preset_name, self)
old_state: CoreState = await self.async_get_last_state() old_state: CoreState = await self.async_get_last_state()
_LOGGER.debug( _LOGGER.debug(
"%s - Calling async_added_to_hass old_state is %s", self, old_state "%s - Calling async_added_to_hass old_state is %s", self, old_state
) )
try: try:
if old_state is not None and (value := float(old_state.state) > 0): if old_state is not None and ((value := float(old_state.state)) > 0):
self._attr_value = self._attr_native_value = value self._attr_value = self._attr_native_value = value
except ValueError: except ValueError:
pass pass
@@ -425,12 +449,6 @@ class TemperatureNumber( # pylint: disable=abstract-method
) )
return return
# @overrides
# @property
# def native_step(self) -> float | None:
# """The native step"""
# return self.my_climate.target_temperature_step
@overrides @overrides
async def async_set_native_value(self, value: float) -> None: async def async_set_native_value(self, value: float) -> None:
"""Change the value""" """Change the value"""
@@ -6,7 +6,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
from homeassistant.components.number import NumberEntity, DOMAIN as NUMBER_DOMAIN from homeassistant.components.number import NumberEntity
from .const import ( from .const import (
DOMAIN, DOMAIN,
+6 -1
View File
@@ -500,7 +500,12 @@ async def create_thermostat(
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.LOADED assert entry.state is ConfigEntryState.LOADED
return search_entity(hass, entity_id, CLIMATE_DOMAIN) # We should reload the VTherm links
vtherm_api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api()
entity = search_entity(hass, entity_id, CLIMATE_DOMAIN)
if entity:
await entity.init_presets(vtherm_api.find_central_configuration())
return entity
async def create_central_config( # pylint: disable=dangerous-default-value async def create_central_config( # pylint: disable=dangerous-default-value
+363 -1
View File
@@ -14,7 +14,10 @@ from homeassistant.components.number import NumberEntity, DOMAIN as NUMBER_DOMAI
from pytest_homeassistant_custom_component.common import MockConfigEntry from pytest_homeassistant_custom_component.common import MockConfigEntry
# from custom_components.versatile_thermostat.base_thermostat import BaseThermostat from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from custom_components.versatile_thermostat.thermostat_switch import (
ThermostatOverSwitch,
)
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
from .commons import * from .commons import *
@@ -394,3 +397,362 @@ async def test_add_number_for_central_config_without_temp_restore(
config_id=central_config_entry.entry_id, preset_name=preset_name config_id=central_config_entry.entry_id, preset_name=preset_name
) )
assert val == value assert val == value
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_add_number_for_over_switch_use_central(
hass: HomeAssistant, skip_hass_states_is_state
):
"""Test the construction of a over switch vtherm with
use central config for PRESET and PRESENCE.
It also have old temp config value which should be not used.
So it should have no Temp NumberEntity"""
vtherm_api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass)
temps = {
"frost_temp": 10,
"eco_temp": 17.1,
"comfort_temp": 18.1,
"boost_temp": 19.1,
"eco_ac_temp": 25.1,
"comfort_ac_temp": 23.1,
"boost_ac_temp": 21.1,
"frost_away_temp": 15.1,
"eco_away_temp": 15.2,
"comfort_away_temp": 15.3,
"boost_away_temp": 15.4,
"eco_ac_away_temp": 30.5,
"comfort_ac_away_temp": 30.6,
"boost_ac_away_temp": 30.7,
}
vtherm_entry = MockConfigEntry(
domain=DOMAIN,
title="TheCentralConfigMockName",
unique_id="centralConfigUniqueId",
data={
CONF_NAME: "TheOverSwitchVTherm",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_central_ext_temp_sensor",
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
CONF_AC_MODE: False,
CONF_TPI_COEF_INT: 0.5,
CONF_TPI_COEF_EXT: 0.02,
CONF_USE_PRESENCE_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_WINDOW_CENTRAL_CONFIG: True,
CONF_USE_POWER_CENTRAL_CONFIG: True,
CONF_USE_MOTION_CENTRAL_CONFIG: True,
}
| temps,
)
# The restore should not be used
with patch(
"homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state",
return_value=State(entity_id="number.mock_valve", state="23"),
) as mock_restore_state:
vtherm_entry.add_to_hass(hass)
await hass.config_entries.async_setup(vtherm_entry.entry_id)
assert mock_restore_state.call_count == 0
assert vtherm_entry.state is ConfigEntryState.LOADED
# We search for NumberEntities
for preset_name, value in temps.items():
temp_entity = search_entity(
hass,
"number.central_configuration_" + preset_name,
NUMBER_DOMAIN,
)
assert temp_entity is None
# Find temp Number into vtherm_api
val = vtherm_api.get_temperature_number_value(
config_id=vtherm_entry.entry_id, preset_name=preset_name
)
assert val is None
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_add_number_for_over_switch_use_central_presence(
hass: HomeAssistant, skip_hass_states_is_state, init_central_config
):
"""Test the construction of a over switch vtherm with
use central config for PRESET and PRESENCE.
It also have old temp config value which should be not used.
So it should have no Temp NumberEntity"""
vtherm_api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass)
temps = {
"frost_temp": 10,
"eco_temp": 17.1,
"comfort_temp": 18.1,
"boost_temp": 19.1,
"eco_ac_temp": 25.1,
"comfort_ac_temp": 23.1,
"boost_ac_temp": 21.1,
}
temps_missing = {
"frost_away_temp": 15.1,
"eco_away_temp": 15.2,
"comfort_away_temp": 15.3,
"boost_away_temp": 15.4,
"eco_ac_away_temp": 30.5,
"comfort_ac_away_temp": 30.6,
"boost_ac_away_temp": 30.7,
}
vtherm_entry = MockConfigEntry(
domain=DOMAIN,
title="TheCentralConfigMockName",
unique_id="centralConfigUniqueId",
data={
CONF_NAME: "TheOverSwitchVTherm",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_central_ext_temp_sensor",
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
CONF_AC_MODE: True,
CONF_TPI_COEF_INT: 0.5,
CONF_TPI_COEF_EXT: 0.02,
CONF_CYCLE_MIN: 5,
CONF_HEATER: "switch.mock_switch1",
CONF_USE_PRESENCE_FEATURE: True,
CONF_USE_PRESENCE_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: False,
CONF_USE_WINDOW_CENTRAL_CONFIG: True,
CONF_USE_POWER_CENTRAL_CONFIG: True,
CONF_USE_MOTION_CENTRAL_CONFIG: True,
}
| temps
| temps_missing,
)
vtherm: BaseThermostat = await create_thermostat(
hass, vtherm_entry, "climate.theoverswitchvtherm"
)
# 1. We search for NumberEntities
for preset_name, value in temps.items():
temp_entity = search_entity(
hass,
"number.theoverswitchvtherm_" + preset_name,
NUMBER_DOMAIN,
)
assert temp_entity
assert temp_entity.state == value
# This test is dependent to translation en.json. If translations change
# this may fails
assert (
temp_entity.name.lower()
== preset_name.replace("_temp", "")
.replace("_ac", " ac")
.replace("_away", " away")
.lower()
)
# Find temp Number into vtherm_api
val = vtherm_api.get_temperature_number_value(
config_id=vtherm_entry.entry_id, preset_name=preset_name
)
assert val == value
# 2. We search for NumberEntities to be missing
for preset_name, value in temps_missing.items():
temp_entity = search_entity(
hass,
"number.theoverswitchvtherm_" + preset_name,
NUMBER_DOMAIN,
)
assert temp_entity is None
# Find temp Number into vtherm_api
val = vtherm_api.get_temperature_number_value(
config_id=vtherm_entry.entry_id, preset_name=preset_name
)
assert val is None
# 3. The VTherm should be initialized with all presets and correct temperature
assert vtherm
assert isinstance(vtherm, ThermostatOverSwitch)
assert vtherm.preset_modes == [
PRESET_NONE,
PRESET_FROST_PROTECTION,
PRESET_ECO,
PRESET_COMFORT,
PRESET_BOOST,
]
assert vtherm._presets == {
PRESET_FROST_PROTECTION: temps["frost_temp"],
PRESET_ECO: temps["eco_temp"],
PRESET_COMFORT: temps["comfort_temp"],
PRESET_BOOST: temps["boost_temp"],
PRESET_ECO_AC: temps["eco_ac_temp"],
PRESET_COMFORT_AC: temps["comfort_ac_temp"],
PRESET_BOOST_AC: temps["boost_ac_temp"],
}
# Preset away should be initialized with the central config
assert vtherm._presets_away == {
PRESET_FROST_PROTECTION
+ PRESET_AWAY_SUFFIX: FULL_CENTRAL_CONFIG["frost_away_temp"],
PRESET_ECO + PRESET_AWAY_SUFFIX: FULL_CENTRAL_CONFIG["eco_away_temp"],
PRESET_COMFORT + PRESET_AWAY_SUFFIX: FULL_CENTRAL_CONFIG["comfort_away_temp"],
PRESET_BOOST + PRESET_AWAY_SUFFIX: FULL_CENTRAL_CONFIG["boost_away_temp"],
PRESET_ECO_AC + PRESET_AWAY_SUFFIX: FULL_CENTRAL_CONFIG["eco_ac_away_temp"],
PRESET_COMFORT_AC
+ PRESET_AWAY_SUFFIX: FULL_CENTRAL_CONFIG["comfort_ac_away_temp"],
PRESET_BOOST_AC + PRESET_AWAY_SUFFIX: FULL_CENTRAL_CONFIG["boost_ac_away_temp"],
}
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_add_number_for_over_switch_use_central_presets_and_restore(
hass: HomeAssistant, skip_hass_states_is_state, init_central_config
):
"""Test the construction of a over switch vtherm with
use central config for PRESET and PRESENCE.
It also have old temp config value which should be not used.
So it should have no Temp NumberEntity"""
vtherm_api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass)
temps = {
"frost_away_temp": 23,
"eco_away_temp": 23,
"comfort_away_temp": 23,
"boost_away_temp": 23,
}
temps_missing = {
"frost_temp": 10,
"eco_temp": 17.1,
"comfort_temp": 18.1,
"boost_temp": 19.1,
"eco_ac_temp": 25.1,
"comfort_ac_temp": 23.1,
"boost_ac_temp": 21.1,
"eco_ac_away_temp": 30.5,
"comfort_ac_away_temp": 30.6,
"boost_ac_away_temp": 30.7,
}
vtherm_entry = MockConfigEntry(
domain=DOMAIN,
title="TheCentralConfigMockName",
unique_id="centralConfigUniqueId",
data={
CONF_NAME: "TheOverSwitchVTherm",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_central_ext_temp_sensor",
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
CONF_AC_MODE: False,
CONF_TPI_COEF_INT: 0.5,
CONF_TPI_COEF_EXT: 0.02,
CONF_CYCLE_MIN: 5,
CONF_HEATER: "switch.mock_switch1",
CONF_USE_PRESENCE_FEATURE: True,
CONF_USE_PRESENCE_CENTRAL_CONFIG: False,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_WINDOW_CENTRAL_CONFIG: True,
CONF_USE_POWER_CENTRAL_CONFIG: True,
CONF_USE_MOTION_CENTRAL_CONFIG: True,
}
| temps
| temps_missing,
)
# The restore should not be used
with patch(
"homeassistant.helpers.restore_state.RestoreEntity.async_get_last_state",
return_value=State(entity_id="number.mock_valve", state="23"),
) as mock_restore_state:
vtherm: BaseThermostat = await create_thermostat(
hass, vtherm_entry, "climate.theoverswitchvtherm"
)
# We should try to restore all 4 temp entities
assert mock_restore_state.call_count == 4
# 1. We search for NumberEntities
for preset_name, value in temps.items():
temp_entity = search_entity(
hass,
"number.theoverswitchvtherm_" + preset_name,
NUMBER_DOMAIN,
)
assert temp_entity
assert temp_entity.state == value
# This test is dependent to translation en.json. If translations change
# this may fails
assert (
temp_entity.name.lower()
== preset_name.replace("_temp", "")
.replace("_ac", " ac")
.replace("_away", " away")
.lower()
)
# Find temp Number into vtherm_api
val = vtherm_api.get_temperature_number_value(
config_id=vtherm_entry.entry_id, preset_name=preset_name
)
assert val == value
# 2. We search for NumberEntities to be missing
for preset_name, value in temps_missing.items():
temp_entity = search_entity(
hass,
"number.theoverswitchvtherm_" + preset_name,
NUMBER_DOMAIN,
)
assert temp_entity is None
# Find temp Number into vtherm_api
val = vtherm_api.get_temperature_number_value(
config_id=vtherm_entry.entry_id, preset_name=preset_name
)
assert val is None
# 3. The VTherm should be initialized with all presets and correct temperature
assert vtherm
assert isinstance(vtherm, ThermostatOverSwitch)
assert vtherm.preset_modes == [
PRESET_NONE,
PRESET_FROST_PROTECTION,
PRESET_ECO,
# PRESET_COMFORT, because temp is 0
PRESET_BOOST,
]
# Preset away should be empty cause we use central config for presets
assert vtherm._presets == {
PRESET_FROST_PROTECTION: FULL_CENTRAL_CONFIG["frost_temp"],
PRESET_ECO: FULL_CENTRAL_CONFIG["eco_temp"],
PRESET_COMFORT: FULL_CENTRAL_CONFIG["comfort_temp"],
PRESET_BOOST: FULL_CENTRAL_CONFIG["boost_temp"],
}
assert vtherm._presets_away == {
PRESET_FROST_PROTECTION + PRESET_AWAY_SUFFIX: temps["frost_away_temp"],
PRESET_ECO + PRESET_AWAY_SUFFIX: temps["eco_away_temp"],
PRESET_COMFORT + PRESET_AWAY_SUFFIX: temps["comfort_away_temp"],
PRESET_BOOST + PRESET_AWAY_SUFFIX: temps["boost_away_temp"],
}