With calculation of VTherm temp entities + test ok
This commit is contained in:
@@ -137,8 +137,6 @@ from .prop_algorithm import PropAlgorithm
|
||||
from .open_window_algorithm import WindowOpenDetectionAlgorithm
|
||||
from .ema import ExponentialMovingAverage
|
||||
|
||||
from .temp_number import TemperatureNumber
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
ConfigData = MappingProxyType[str, Any]
|
||||
|
||||
@@ -216,6 +214,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
super().__init__()
|
||||
|
||||
self._hass = hass
|
||||
self._entry_infos = None
|
||||
self._attr_extra_state_attributes = {}
|
||||
|
||||
self._unique_id = unique_id
|
||||
@@ -288,6 +287,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
self._is_used_by_central_boiler = False
|
||||
|
||||
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.post_init(entry_infos)
|
||||
@@ -356,6 +359,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
|
||||
_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._attr_max_temp = entry_infos.get(CONF_TEMP_MAX)
|
||||
self._attr_min_temp = entry_infos.get(CONF_TEMP_MIN)
|
||||
@@ -2649,18 +2654,68 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
"""Send an event"""
|
||||
send_vtherm_event(self._hass, event_type=event_type, entity=self, data=data)
|
||||
|
||||
def get_temperature_number_entities(self, config_entry: ConfigData):
|
||||
"""Creates all TemperatureNumber depending of the configuration of the Climate"""
|
||||
async def init_presets(self, central_config):
|
||||
"""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
|
||||
entity = TemperatureNumber(
|
||||
self._hass,
|
||||
unique_id=config_entry.entry_id,
|
||||
name=config_entry.data.get(CONF_NAME),
|
||||
preset_name="comfort",
|
||||
is_ac=False,
|
||||
is_away=False,
|
||||
entry_infos=config_entry.data,
|
||||
presets: dict[str, Any] = {}
|
||||
presets_away: dict[str, Any] = {}
|
||||
|
||||
def calculate_presets(items, use_central_conf_key):
|
||||
presets: dict[str, Any] = {}
|
||||
config_id = self._unique_id
|
||||
if (
|
||||
central_config
|
||||
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,
|
||||
callback,
|
||||
Event,
|
||||
CoreState,
|
||||
# CoreState,
|
||||
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.event import async_track_state_change_event
|
||||
@@ -386,17 +386,18 @@ class CentralBoilerBinarySensor(BinarySensorEntity):
|
||||
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self._hass)
|
||||
api.register_central_boiler(self)
|
||||
|
||||
@callback
|
||||
async def _async_startup_internal(*_):
|
||||
_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()
|
||||
else:
|
||||
self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_START, _async_startup_internal
|
||||
)
|
||||
# Should be not more needed and replaced by vtherm_api.init_vtherm_links
|
||||
# @callback
|
||||
# async def _async_startup_internal(*_):
|
||||
# _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()
|
||||
# else:
|
||||
# self.hass.bus.async_listen_once(
|
||||
# EVENT_HOMEASSISTANT_START, _async_startup_internal
|
||||
# )
|
||||
|
||||
async def listen_nb_active_vtherm_entity(self):
|
||||
"""Initialize the listening of state change of VTherms"""
|
||||
|
||||
@@ -76,7 +76,7 @@ async def async_setup_entry(
|
||||
elif vt_type == CONF_THERMOSTAT_VALVE:
|
||||
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
|
||||
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.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from homeassistant.util import ensure_unique_string, slugify
|
||||
|
||||
from .vtherm_api import VersatileThermostatAPI
|
||||
from .commons import VersatileThermostatBaseEntity
|
||||
@@ -48,6 +48,9 @@ from .const import (
|
||||
CONF_PRESETS_WITH_AC_VALUES,
|
||||
CONF_PRESETS_AWAY_VALUES,
|
||||
CONF_PRESETS_AWAY_WITH_AC_VALUES,
|
||||
CONF_USE_PRESETS_CENTRAL_CONFIG,
|
||||
CONF_USE_PRESENCE_CENTRAL_CONFIG,
|
||||
CONF_USE_PRESENCE_FEATURE,
|
||||
overrides,
|
||||
)
|
||||
|
||||
@@ -84,28 +87,45 @@ async def async_setup_entry(
|
||||
unique_id = entry.entry_id
|
||||
name = entry.data.get(CONF_NAME)
|
||||
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 = []
|
||||
|
||||
if vt_type != CONF_THERMOSTAT_CENTRAL_CONFIG:
|
||||
if not is_central_boiler:
|
||||
pass
|
||||
# for preset in CONF_PRESETS_VALUES:
|
||||
# entities.append(
|
||||
# TemperatureNumber(
|
||||
# hass, unique_id, preset, preset, False, False, entry.data
|
||||
# )
|
||||
# )
|
||||
# Creates non central temperature entities
|
||||
if not entry.data.get(CONF_USE_PRESETS_CENTRAL_CONFIG, False):
|
||||
for preset in CONF_PRESETS_VALUES:
|
||||
entities.append(
|
||||
TemperatureNumber(
|
||||
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(CONF_AC_MODE, False):
|
||||
# for preset in CONF_PRESETS_WITH_AC_VALUES:
|
||||
# entities.append(
|
||||
# TemperatureNumber(
|
||||
# hass, unique_id, preset, preset, True, False, entry.data
|
||||
# )
|
||||
# )
|
||||
if entry.data.get(
|
||||
CONF_USE_PRESENCE_FEATURE, False
|
||||
) is True and not entry.data.get(CONF_USE_PRESENCE_CENTRAL_CONFIG, False):
|
||||
for preset in CONF_PRESETS_AWAY_VALUES:
|
||||
entities.append(
|
||||
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:
|
||||
entities.append(
|
||||
ActivateBoilerThresholdNumber(hass, unique_id, name, entry.data)
|
||||
@@ -113,27 +133,27 @@ async def async_setup_entry(
|
||||
for preset in CONF_PRESETS_VALUES:
|
||||
entities.append(
|
||||
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:
|
||||
entities.append(
|
||||
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:
|
||||
entities.append(
|
||||
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:
|
||||
entities.append(
|
||||
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"""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
# _attr_translation_key = "temperature"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -227,7 +246,7 @@ class CentralConfigTemperatureNumber(NumberEntity, RestoreEntity):
|
||||
the restoration will do the trick."""
|
||||
|
||||
self._config_id = unique_id
|
||||
self._device_name = entry_infos.get(CONF_NAME)
|
||||
self._device_name = name
|
||||
# self._attr_name = name
|
||||
|
||||
self._attr_translation_key = preset_name
|
||||
@@ -236,7 +255,8 @@ class CentralConfigTemperatureNumber(NumberEntity, RestoreEntity):
|
||||
# "ac": "-AC" if is_ac 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_device_class = NumberDeviceClass.TEMPERATURE
|
||||
self._attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS
|
||||
@@ -250,7 +270,7 @@ class CentralConfigTemperatureNumber(NumberEntity, RestoreEntity):
|
||||
# previous value
|
||||
# TODO remove this after the next major release and just keep the init min/max
|
||||
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
|
||||
else:
|
||||
if entry_infos.get(CONF_AC_MODE) is True:
|
||||
@@ -346,7 +366,6 @@ class TemperatureNumber( # pylint: disable=abstract-method
|
||||
"""Representation of one temperature number"""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_translation_key = "temperature"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -360,40 +379,41 @@ class TemperatureNumber( # pylint: disable=abstract-method
|
||||
) -> 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))
|
||||
super().__init__(hass, unique_id, name)
|
||||
|
||||
split = name.split("_")
|
||||
# self._attr_name = split[0]
|
||||
# if "_" + split[1] == PRESET_AC_SUFFIX:
|
||||
# self._attr_name = self._attr_name + " AC"
|
||||
self._attr_translation_key = preset_name
|
||||
self.entity_id = f"{NUMBER_DOMAIN}.{slugify(name)}_{preset_name}"
|
||||
|
||||
# self._attr_name = self._attr_name + " temperature"
|
||||
|
||||
self._attr_translation_placeholders = {
|
||||
"preset": preset_name,
|
||||
"ac": "-AC" if is_ac else "",
|
||||
"away": "-AWAY" if is_away else "",
|
||||
}
|
||||
self._attr_unique_id = f"{self._device_name}_{name}"
|
||||
# self._attr_translation_placeholders = {
|
||||
# "preset": preset_name,
|
||||
# "ac": "-AC" if is_ac else "",
|
||||
# "away": "-AWAY" if is_away else "",
|
||||
# }
|
||||
self._attr_unique_id = f"{self._device_name}_{preset_name}"
|
||||
self._attr_device_class = NumberDeviceClass.TEMPERATURE
|
||||
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
|
||||
# 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):
|
||||
if (temp := entry_infos.get(preset_name, None)) is not None:
|
||||
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._preset_name = preset_name
|
||||
self._is_away = is_away
|
||||
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
|
||||
def icon(self) -> str | None:
|
||||
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:
|
||||
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()
|
||||
_LOGGER.debug(
|
||||
"%s - Calling async_added_to_hass old_state is %s", self, old_state
|
||||
)
|
||||
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
|
||||
except ValueError:
|
||||
pass
|
||||
@@ -425,12 +449,6 @@ class TemperatureNumber( # pylint: disable=abstract-method
|
||||
)
|
||||
return
|
||||
|
||||
# @overrides
|
||||
# @property
|
||||
# def native_step(self) -> float | None:
|
||||
# """The native step"""
|
||||
# return self.my_climate.target_temperature_step
|
||||
|
||||
@overrides
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Change the value"""
|
||||
|
||||
@@ -6,7 +6,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
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 (
|
||||
DOMAIN,
|
||||
|
||||
Reference in New Issue
Block a user