Cleaning and fixing Issues

This commit is contained in:
Jean-Marc Collin
2024-03-09 10:57:13 +00:00
parent a396c8831f
commit d8bc2fc3d3
14 changed files with 409 additions and 379 deletions

View File

@@ -111,6 +111,7 @@ from .const import (
CONF_USE_POWER_CENTRAL_CONFIG,
CONF_USE_PRESENCE_CENTRAL_CONFIG,
CONF_USE_ADVANCED_CENTRAL_CONFIG,
CONF_USE_PRESENCE_FEATURE,
CONF_TEMP_MAX,
CONF_TEMP_MIN,
HIDDEN_PRESETS,
@@ -293,6 +294,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._attr_preset_modes: list[str] | None
self._use_central_config_temperature = False
self.post_init(entry_infos)
def clean_central_config_doublon(
@@ -361,42 +364,19 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._entry_infos = entry_infos
self._use_central_config_temperature = entry_infos.get(
CONF_USE_PRESETS_CENTRAL_CONFIG
) or (
entry_infos.get(CONF_USE_PRESENCE_CENTRAL_CONFIG)
and entry_infos.get(CONF_USE_PRESENCE_FEATURE)
)
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)
if (step := entry_infos.get(CONF_STEP_TEMPERATURE)) is not None:
self._attr_target_temperature_step = step
# convert entry_infos into usable attributes
# 354 - presets are now initializesd by number entities
# presets: dict[str, Any] = {}
# items = CONF_PRESETS_WITH_AC.items() if self._ac_mode else CONF_PRESETS.items()
# for key, value in items:
# _LOGGER.debug("looking for key=%s, value=%s", key, value)
# if value in entry_infos:
# presets[key] = entry_infos.get(value)
# else:
# _LOGGER.debug("value %s not found in Entry", value)
# presets[key] = (
# self._attr_max_temp if self._ac_mode else self._attr_min_temp
# )
# presets_away: dict[str, Any] = {}
# items = (
# CONF_PRESETS_AWAY_WITH_AC.items()
# if self._ac_mode
# else CONF_PRESETS_AWAY.items()
# )
# for key, value in items:
# _LOGGER.debug("looking for key=%s, value=%s", key, value)
# if value in entry_infos:
# presets_away[key] = entry_infos.get(value)
# else:
# _LOGGER.debug("value %s not found in Entry", value)
# presets_away[key] = (
# self._attr_max_temp if self._ac_mode else self._attr_min_temp
# )
self._attr_preset_modes: list[str] | None
if self._window_call_cancel is not None:
@@ -648,41 +628,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
await self.async_startup()
# TODO remove this
def init_temperature_preset(self, preset, temperature, is_ac, is_away):
"""Initialize the internal temperature preset
from the Number entity which holds the temperature"""
if temperature is None or preset is None:
return
if is_away:
self._presets_away[preset] = temperature
else:
self._presets[preset] = temperature
_LOGGER.debug(
"%s - presets are set to: %s, away: %s",
self,
self._presets,
self._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")
def remove_thermostat(self):
"""Called when the thermostat will be removed"""
_LOGGER.info("%s - Removing thermostat", self)
@@ -1213,6 +1158,11 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
Is None if the VTherm is not controlled by central_mode"""
return self._last_central_mode
@property
def use_central_config_temperature(self):
"""True if this VTHerm uses the central configuration temperature"""
return self._use_central_config_temperature
def underlying_entity_id(self, index=0) -> str | None:
"""The climate_entity_id. Added for retrocompatibility reason"""
if index < self.nb_underlying_entities:
@@ -1231,18 +1181,22 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
"""Turn auxiliary heater on."""
raise NotImplementedError()
@overrides
async def async_turn_aux_heat_on(self) -> None:
"""Turn auxiliary heater on."""
raise NotImplementedError()
@overrides
def turn_aux_heat_off(self) -> None:
"""Turn auxiliary heater off."""
raise NotImplementedError()
@overrides
async def async_turn_aux_heat_off(self) -> None:
"""Turn auxiliary heater off."""
raise NotImplementedError()
@overrides
async def async_set_hvac_mode(self, hvac_mode: HVACMode, need_control_heating=True):
"""Set new target hvac mode."""
_LOGGER.info("%s - Set hvac mode: %s", self, hvac_mode)
@@ -1388,13 +1342,21 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
_LOGGER.info("%s - find preset temp: %s", self, preset_mode)
temp_val = self._presets.get(preset_mode, 0)
if not self._presence_on or self._presence_state in [
STATE_ON,
STATE_HOME,
]:
return self._presets.get(preset_mode, 0)
return temp_val
else:
return self._presets_away.get(self.get_preset_away_name(preset_mode), 0)
# We should return the preset_away temp val but if
# preset temp is 0, that means the user don't want to use
# the preset so we return 0, even if there is a value is preset_away
return (
self._presets_away.get(self.get_preset_away_name(preset_mode), 0)
if temp_val > 0
else temp_val
)
def get_preset_away_name(self, preset_mode: str) -> str:
"""Get the preset name in away mode (when presence is off)"""

View File

@@ -22,6 +22,7 @@ from .prop_algorithm import (
_LOGGER = logging.getLogger(__name__)
PRESET_TEMP_SUFFIX = "_temp"
PRESET_AC_SUFFIX = "_ac"
PRESET_ECO_AC = PRESET_ECO + PRESET_AC_SUFFIX
PRESET_COMFORT_AC = PRESET_COMFORT + PRESET_AC_SUFFIX
@@ -148,7 +149,7 @@ DEFAULT_SHORT_EMA_PARAMS = {
}
CONF_PRESETS = {
p: f"{p}_temp"
p: f"{p}{PRESET_TEMP_SUFFIX}"
for p in (
PRESET_FROST_PROTECTION,
PRESET_ECO,
@@ -158,7 +159,7 @@ CONF_PRESETS = {
}
CONF_PRESETS_WITH_AC = {
p: f"{p}_temp"
p: f"{p}{PRESET_TEMP_SUFFIX}"
for p in (
PRESET_FROST_PROTECTION,
PRESET_ECO,
@@ -174,7 +175,7 @@ CONF_PRESETS_WITH_AC = {
PRESET_AWAY_SUFFIX = "_away"
CONF_PRESETS_AWAY = {
p: f"{p}_temp"
p: f"{p}{PRESET_TEMP_SUFFIX}"
for p in (
PRESET_FROST_PROTECTION + PRESET_AWAY_SUFFIX,
PRESET_ECO + PRESET_AWAY_SUFFIX,
@@ -184,7 +185,7 @@ CONF_PRESETS_AWAY = {
}
CONF_PRESETS_AWAY_WITH_AC = {
p: f"{p}_temp"
p: f"{p}{PRESET_TEMP_SUFFIX}"
for p in (
PRESET_FROST_PROTECTION + PRESET_AWAY_SUFFIX,
PRESET_ECO + PRESET_AWAY_SUFFIX,

View File

@@ -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 homeassistant.util import slugify
from .vtherm_api import VersatileThermostatAPI
from .commons import VersatileThermostatBaseEntity
@@ -34,7 +34,6 @@ from .const import (
CONF_NAME,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_ADD_CENTRAL_BOILER_CONTROL,
CONF_TEMP_MIN,
CONF_TEMP_MAX,
CONF_STEP_TEMPERATURE,
@@ -43,7 +42,8 @@ from .const import (
PRESET_ECO_AC,
PRESET_COMFORT_AC,
PRESET_BOOST_AC,
PRESET_AC_SUFFIX,
PRESET_AWAY_SUFFIX,
PRESET_TEMP_SUFFIX,
CONF_PRESETS_VALUES,
CONF_PRESETS_WITH_AC_VALUES,
CONF_PRESETS_AWAY_VALUES,
@@ -55,20 +55,24 @@ from .const import (
)
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",
PRESET_FROST_PROTECTION + "_away_temp": "mdi:snowflake-thermometer",
PRESET_ECO + "_away_temp": "mdi:leaf",
PRESET_COMFORT + "_away_temp": "mdi:sofa",
PRESET_BOOST + "_away_temp": "mdi:rocket-launch",
PRESET_ECO_AC + "_away_temp": "mdi:leaf-circle-outline",
PRESET_COMFORT_AC + "_away_temp": "mdi:sofa-outline",
PRESET_BOOST_AC + "_away_temp": "mdi:rocket-launch-outline",
PRESET_FROST_PROTECTION + PRESET_TEMP_SUFFIX: "mdi:snowflake-thermometer",
PRESET_ECO + PRESET_TEMP_SUFFIX: "mdi:leaf",
PRESET_COMFORT + PRESET_TEMP_SUFFIX: "mdi:sofa",
PRESET_BOOST + PRESET_TEMP_SUFFIX: "mdi:rocket-launch",
PRESET_ECO_AC + PRESET_TEMP_SUFFIX: "mdi:leaf-circle-outline",
PRESET_COMFORT_AC + PRESET_TEMP_SUFFIX: "mdi:sofa-outline",
PRESET_BOOST_AC + PRESET_TEMP_SUFFIX: "mdi:rocket-launch-outline",
PRESET_FROST_PROTECTION
+ PRESET_AWAY_SUFFIX
+ PRESET_TEMP_SUFFIX: "mdi:snowflake-thermometer",
PRESET_ECO + PRESET_AWAY_SUFFIX + PRESET_TEMP_SUFFIX: "mdi:leaf",
PRESET_COMFORT + PRESET_AWAY_SUFFIX + PRESET_TEMP_SUFFIX: "mdi:sofa",
PRESET_BOOST + PRESET_AWAY_SUFFIX + PRESET_TEMP_SUFFIX: "mdi:rocket-launch",
PRESET_ECO_AC + PRESET_AWAY_SUFFIX + PRESET_TEMP_SUFFIX: "mdi:leaf-circle-outline",
PRESET_COMFORT_AC + PRESET_AWAY_SUFFIX + PRESET_TEMP_SUFFIX: "mdi:sofa-outline",
PRESET_BOOST_AC
+ PRESET_AWAY_SUFFIX
+ PRESET_TEMP_SUFFIX: "mdi:rocket-launch-outline",
}
_LOGGER = logging.getLogger(__name__)
@@ -252,12 +256,6 @@ class CentralConfigTemperatureNumber(
# self._attr_name = name
self._attr_translation_key = preset_name
# self._attr_translation_placeholders = {
# "preset": preset_name,
# "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}.{slugify(name)}_{preset_name}"
self._attr_unique_id = f"central_configuration_{preset_name}"
self._attr_device_class = NumberDeviceClass.TEMPERATURE
@@ -331,10 +329,8 @@ class CentralConfigTemperatureNumber(
# We have to reload all VTherm for which uses the central configuration
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self.hass)
# Update the VTherms
# TODO this reload all VTherms temp. This could be optimized by reloading only
# VTherm which have the USE_CENTRAL_CONFIG true for Preset and Presence
self.hass.create_task(api.init_vtherm_links())
# Update the VTherms which have temperature in central config
self.hass.create_task(api.init_vtherm_links(only_use_central=True))
def __str__(self):
return f"VersatileThermostat-{self.name}"
@@ -372,11 +368,6 @@ class TemperatureNumber( # pylint: disable=abstract-method
self._attr_translation_key = preset_name
self.entity_id = f"{NUMBER_DOMAIN}.{slugify(name)}_{preset_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
@@ -387,7 +378,6 @@ class TemperatureNumber( # pylint: disable=abstract-method
# 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)) is not None:
self._attr_value = self._attr_native_value = temp
@@ -399,6 +389,9 @@ class TemperatureNumber( # pylint: disable=abstract-method
self._attr_mode = NumberMode.BOX
self._preset_name = preset_name
self._canonical_preset_name = preset_name.replace(
PRESET_TEMP_SUFFIX, ""
).replace(PRESET_AWAY_SUFFIX, "")
self._is_away = is_away
self._is_ac = is_ac
@@ -430,15 +423,10 @@ class TemperatureNumber( # pylint: disable=abstract-method
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
# Initialize the internal temp value of VTherm
self.my_climate.init_temperature_preset(
self._preset_name, self._attr_native_value, self._is_ac, self._is_away
)
return
@overrides
async def async_set_native_value(self, value: float) -> None:
def set_native_value(self, value: float) -> None:
"""Change the value"""
if self.my_climate is None:
@@ -455,12 +443,12 @@ class TemperatureNumber( # pylint: disable=abstract-method
self._attr_value = self._attr_native_value = float_value
self.async_write_ha_state()
# Update the VTherm
# Update the VTherm temp
self.hass.create_task(
self.my_climate.service_set_preset_temperature(
self._preset_name.replace("_temp", ""), self._attr_native_value, None
self._canonical_preset_name,
self._attr_native_value if not self._is_away else None,
self._attr_native_value if self._is_away else None,
)
)

View File

@@ -1,197 +0,0 @@
# pylint: disable=unused-argument
""" Implements the VersatileThermostat select component """
import logging
# from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.core import HomeAssistant, CoreState # , callback
from homeassistant.components.number import (
NumberEntity,
NumberMode,
NumberDeviceClass,
)
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.config_entries import ConfigEntry
from homeassistant.helpers.restore_state import RestoreEntity
from .const import (
DOMAIN,
DEVICE_MANUFACTURER,
CONF_NAME,
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,
)
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__)
class TemperatureNumber(NumberEntity, RestoreEntity):
"""Representation of one temperature number"""
_attr_has_entity_name = True
_attr_translation_key = "temperature"
def __init__(
self,
hass: HomeAssistant,
unique_id,
name,
preset_name,
is_ac,
is_away,
entry_infos: ConfigEntry,
) -> 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))
self.my_climate = None
self._unique_id = unique_id
self._device_name = 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 = preset_name + " new 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}_{self._attr_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._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]
@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
return DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, self._unique_id)},
name=self._device_name,
manufacturer=DEVICE_MANUFACTURER,
model=DOMAIN,
)
@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
)
try:
if old_state is not None and (value := float(old_state.state) > 0):
self._attr_value = self._attr_native_value = value
except ValueError:
pass
@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
# Initialize the internal temp value of VTherm
self.my_climate.init_temperature_preset(
self._preset_name, self._attr_native_value, self._is_ac, self._is_away
)
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"""
if self.my_climate is None:
_LOGGER.warning(
"%s - cannot change temperature because VTherm is not initialized", self
)
return
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
self.async_write_ha_state()
# Update the VTherm
self.hass.create_task(
self.my_climate.service_set_preset_temperature(
self._preset_name.replace("_temp", ""), self._attr_native_value, None
)
)
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

View File

@@ -31,7 +31,7 @@ from .underlyings import UnderlyingValve
_LOGGER = logging.getLogger(__name__)
class ThermostatOverValve(BaseThermostat):
class ThermostatOverValve(BaseThermostat): # pylint: disable=abstract-method
"""Representation of a class for a Versatile Thermostat over a Valve"""
_entity_component_unrecorded_attributes = (

View File

@@ -149,7 +149,7 @@ class VersatileThermostatAPI(dict):
return entity.state
return None
async def init_vtherm_links(self):
async def init_vtherm_links(self, only_use_central=False):
"""INitialize all VTherms entities links
This method is called when HA is fully started (and all entities should be initialized)
Or when we need to reload all VTherm links (with Number temp entities, central boiler, ...)
@@ -163,7 +163,11 @@ class VersatileThermostatAPI(dict):
if component:
for entity in component.entities:
if hasattr(entity, "init_presets"):
await entity.init_presets(self.find_central_configuration())
if (
only_use_central is False
or entity.use_central_config_temperature
):
await entity.init_presets(self.find_central_configuration())
async def reload_central_boiler_binary_listener(self):
"""Reloads the BinarySensor entity which listen to the number of