Add temp entities initialization

This commit is contained in:
Jean-Marc Collin
2024-02-04 20:06:44 +00:00
parent 8526b7d7ac
commit f7da58d841
3 changed files with 167 additions and 10 deletions

View File

@@ -1,4 +1,5 @@
""" Some usefull commons class """ """ Some usefull commons class """
# pylint: disable=line-too-long # pylint: disable=line-too-long
import logging import logging
@@ -182,6 +183,9 @@ class VersatileThermostatBaseEntity(Entity):
"""Returns my climate if found""" """Returns my climate if found"""
if not self._my_climate: if not self._my_climate:
self._my_climate = self.find_my_versatile_thermostat() self._my_climate = self.find_my_versatile_thermostat()
if self._my_climate:
# Only the first time
self.my_climate_is_initialized()
return self._my_climate return self._my_climate
@property @property
@@ -238,6 +242,11 @@ class VersatileThermostatBaseEntity(Entity):
await try_find_climate(None) await try_find_climate(None)
@callback
def my_climate_is_initialized(self):
"""Called when the associated climate is initialized"""
return
@callback @callback
async def async_my_climate_changed( async def async_my_climate_changed(
self, event: Event self, event: Event

View File

@@ -39,12 +39,14 @@ HIDDEN_PRESETS = [PRESET_POWER, PRESET_SECURITY]
DOMAIN = "versatile_thermostat" DOMAIN = "versatile_thermostat"
# The order is important.
# NUMBER should be after CLIMATE,
PLATFORMS: list[Platform] = [ PLATFORMS: list[Platform] = [
Platform.NUMBER,
Platform.SELECT, Platform.SELECT,
Platform.CLIMATE, Platform.CLIMATE,
Platform.SENSOR, Platform.SENSOR,
Platform.BINARY_SENSOR, Platform.BINARY_SENSOR,
Platform.NUMBER,
] ]
CONF_HEATER = "heater_entity_id" CONF_HEATER = "heater_entity_id"
@@ -361,7 +363,9 @@ CENTRAL_MODES = [
class RegulationParamSlow: class RegulationParamSlow:
"""Light parameters for slow latency regulation""" """Light parameters for slow latency regulation"""
kp: float = 0.2 # 20% of the current internal regulation offset are caused by the current difference of target temperature and room temperature kp: float = (
0.2 # 20% of the current internal regulation offset are caused by the current difference of target temperature and room temperature
)
ki: float = ( ki: float = (
0.8 / 288.0 0.8 / 288.0
) # 80% of the current internal regulation offset are caused by the average offset of the past 24 hours ) # 80% of the current internal regulation offset are caused by the average offset of the past 24 hours
@@ -369,7 +373,9 @@ class RegulationParamSlow:
1.0 / 25.0 1.0 / 25.0
) # this will add 1°C to the offset when it's 25°C colder outdoor than indoor ) # this will add 1°C to the offset when it's 25°C colder outdoor than indoor
offset_max: float = 2.0 # limit to a final offset of -2°C to +2°C offset_max: float = 2.0 # limit to a final offset of -2°C to +2°C
stabilization_threshold: float = 0.0 # this needs to be disabled as otherwise the long term accumulated error will always be reset when the temp briefly crosses from/to below/above the target stabilization_threshold: float = (
0.0 # this needs to be disabled as otherwise the long term accumulated error will always be reset when the temp briefly crosses from/to below/above the target
)
accumulated_error_threshold: float = ( accumulated_error_threshold: float = (
2.0 * 288 2.0 * 288
) # this allows up to 2°C long term offset in both directions ) # this allows up to 2°C long term offset in both directions

View File

@@ -2,11 +2,27 @@
""" Implements the VersatileThermostat select component """ """ Implements the VersatileThermostat select component """
import logging import logging
from typing import Any
# from homeassistant.const import EVENT_HOMEASSISTANT_START # from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.core import HomeAssistant, CoreState # , callback from homeassistant.core import HomeAssistant, CoreState # , callback
from homeassistant.components.number import NumberEntity, NumberMode from homeassistant.components.number import (
NumberEntity,
NumberMode,
NumberDeviceClass,
ATTR_MAX,
ATTR_MIN,
ATTR_STEP,
ATTR_MODE,
)
from homeassistant.components.climate import (
PRESET_BOOST,
PRESET_COMFORT,
PRESET_ECO,
)
from homeassistant.components.sensor import UnitOfTemperature
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType 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
@@ -14,6 +30,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
from .commons import VersatileThermostatBaseEntity
from .const import ( from .const import (
DOMAIN, DOMAIN,
DEVICE_MANUFACTURER, DEVICE_MANUFACTURER,
@@ -21,9 +39,32 @@ from .const import (
CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_CENTRAL_CONFIG, CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_ADD_CENTRAL_BOILER_CONTROL, CONF_ADD_CENTRAL_BOILER_CONTROL,
CONF_TEMP_MIN,
CONF_TEMP_MAX,
CONF_STEP_TEMPERATURE,
CONF_AC_MODE,
PRESET_FROST_PROTECTION,
PRESET_ECO_AC,
PRESET_COMFORT_AC,
PRESET_BOOST_AC,
PRESET_AC_SUFFIX,
CONF_PRESETS_VALUES,
CONF_PRESETS_WITH_AC_VALUES,
# CONF_PRESETS_AWAY_VALUES,
# CONF_PRESETS_AWAY_WITH_AC_VALUES,
overrides, overrides,
) )
PRESET_ICON_MAPPING = {
PRESET_FROST_PROTECTION + "_temp": "mdi:snowflake-thermometer",
PRESET_ECO + "_temp": "mdi:leaf",
PRESET_COMFORT + "_temp": "mdi:sofa",
PRESET_BOOST + "_temp": "mdi:rocket-launch",
PRESET_ECO_AC + "_temp": "mdi:leaf-circle-outline",
PRESET_COMFORT_AC + "_temp": "mdi:sofa-outline",
PRESET_BOOST_AC + "_temp": "mdi:rocket-launch-outline",
}
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -42,17 +83,30 @@ async def async_setup_entry(
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)
if vt_type != CONF_THERMOSTAT_CENTRAL_CONFIG or not is_central_boiler: entities = []
return
entities = [ if vt_type != CONF_THERMOSTAT_CENTRAL_CONFIG or not is_central_boiler:
ActivateBoilerThresholdNumber(hass, unique_id, name, entry.data), for preset in CONF_PRESETS_VALUES:
] entities.append(
TemperatureNumber(hass, unique_id, preset, preset, entry.data)
)
if entry.data.get(CONF_AC_MODE, False):
for preset in CONF_PRESETS_WITH_AC_VALUES:
entities.append(
TemperatureNumber(hass, unique_id, preset, preset, entry.data)
)
else:
entities.append(
ActivateBoilerThresholdNumber(hass, unique_id, name, entry.data)
)
async_add_entities(entities, True) async_add_entities(entities, True)
class ActivateBoilerThresholdNumber(NumberEntity, RestoreEntity): class ActivateBoilerThresholdNumber(
NumberEntity, RestoreEntity
): # pylint: disable=abstract-method
"""Representation of the threshold of the number of VTherm """Representation of the threshold of the number of VTherm
which should be active to activate the boiler""" which should be active to activate the boiler"""
@@ -115,3 +169,91 @@ class ActivateBoilerThresholdNumber(NumberEntity, RestoreEntity):
def __str__(self): def __str__(self):
return f"VersatileThermostat-{self.name}" return f"VersatileThermostat-{self.name}"
class TemperatureNumber( # pylint: disable=abstract-method
VersatileThermostatBaseEntity, NumberEntity, RestoreEntity
):
"""Representation of one temperature number"""
def __init__(
self, hass: HomeAssistant, unique_id, name, preset_name, entry_infos
) -> None:
"""Initialize the temperature with entry_infos if available. Else
the restoration will do the trick."""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
split = name.split("_")
self._attr_name = split[0]
if "_" + split[1] == PRESET_AC_SUFFIX:
self._attr_name = self._attr_name + " AC"
self._attr_name = self._attr_name + " temperature"
self._attr_unique_id = f"{self._device_name}_{name}"
self._attr_device_class = NumberDeviceClass.TEMPERATURE
self._attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
# Initialize the values if included into the entry_infos. This will do
# the temperature migration.
# TODO see if this should be replace by the central config if any
temp = None
if temp := entry_infos.get(preset_name, None):
self._attr_value = self._attr_native_value = temp
self._attr_mode = NumberMode.BOX
self._preset_name = preset_name
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
def icon(self) -> str | None:
return PRESET_ICON_MAPPING[self._preset_name]
@overrides
async def async_added_to_hass(self) -> None:
await super().async_added_to_hass()
old_state: CoreState = await self.async_get_last_state()
_LOGGER.debug(
"%s - Calling async_added_to_hass old_state is %s", self, old_state
)
if old_state is not None:
self._attr_value = self._attr_native_value = float(old_state.state)
@overrides
def my_climate_is_initialized(self):
"""Called when the associated climate is initialized"""
self._attr_native_step = self.my_climate.target_temperature_step
self._attr_native_min_value = self.my_climate.min_temp
self._attr_native_max_value = self.my_climate.max_temp
return
# @overrides
# @property
# def native_step(self) -> float | None:
# """The native step"""
# return self.my_climate.target_temperature_step
@overrides
def set_native_value(self, value: float) -> None:
"""Change the value"""
float_value = float(value)
old_value = float(self._attr_native_value)
if float_value == old_value:
return
self._attr_value = self._attr_native_value = float_value
def __str__(self):
return f"VersatileThermostat-{self.name}"
@property
def native_unit_of_measurement(self) -> str | None:
"""The unit of measurement"""
if not self.my_climate:
return UnitOfTemperature.CELSIUS
return self.my_climate.temperature_unit