Issue #13 - After reconfiguration of the thermostat we need to reload the integration

Issue #22 - Change preset to "security" when the Thermostat goes in security mode
Issue #30 - When shutting of a thermostat after it has been in security mode, the thermostat can start up alone
This commit is contained in:
Jean-Marc Collin
2023-01-21 13:38:37 +01:00
parent 638e007c21
commit 952d6d5e97
3 changed files with 219 additions and 162 deletions

View File

@@ -1,8 +1,10 @@
""" Implements the VersatileThermostat climate component """
import math import math
import logging import logging
from datetime import timedelta, datetime from datetime import timedelta, datetime
from typing import Any, Mapping
# from typing import Any
import voluptuous as vol import voluptuous as vol
@@ -11,7 +13,6 @@ from homeassistant.core import (
callback, callback,
CoreState, CoreState,
DOMAIN as HA_DOMAIN, DOMAIN as HA_DOMAIN,
CALLBACK_TYPE,
) )
from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate import ClimateEntity
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
@@ -20,12 +21,14 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import ( from homeassistant.helpers.event import (
async_track_state_change_event, async_track_state_change_event,
async_track_time_interval,
async_call_later, async_call_later,
) )
from homeassistant.exceptions import ConditionError from homeassistant.exceptions import ConditionError
from homeassistant.helpers import condition, entity_platform, config_validation as cv from homeassistant.helpers import (
condition,
entity_platform,
) # , config_validation as cv
from homeassistant.components.climate.const import ( from homeassistant.components.climate.const import (
ATTR_PRESET_MODE, ATTR_PRESET_MODE,
@@ -38,7 +41,7 @@ from homeassistant.components.climate.const import (
HVAC_MODE_HEAT, HVAC_MODE_HEAT,
HVAC_MODE_OFF, HVAC_MODE_OFF,
PRESET_ACTIVITY, PRESET_ACTIVITY,
PRESET_AWAY, # PRESET_AWAY,
PRESET_BOOST, PRESET_BOOST,
PRESET_COMFORT, PRESET_COMFORT,
PRESET_ECO, PRESET_ECO,
@@ -92,6 +95,7 @@ from .const import (
CONF_PRESET_POWER, CONF_PRESET_POWER,
SUPPORT_FLAGS, SUPPORT_FLAGS,
PRESET_POWER, PRESET_POWER,
PRESET_SECURITY,
PROPORTIONAL_FUNCTION_TPI, PROPORTIONAL_FUNCTION_TPI,
SERVICE_SET_PRESENCE, SERVICE_SET_PRESENCE,
SERVICE_SET_PRESET_TEMPERATURE, SERVICE_SET_PRESET_TEMPERATURE,
@@ -100,6 +104,7 @@ from .const import (
CONF_MINIMAL_ACTIVATION_DELAY, CONF_MINIMAL_ACTIVATION_DELAY,
CONF_TEMP_MAX, CONF_TEMP_MAX,
CONF_TEMP_MIN, CONF_TEMP_MIN,
HIDDEN_PRESETS,
) )
from .prop_algorithm import PropAlgorithm from .prop_algorithm import PropAlgorithm
@@ -119,79 +124,11 @@ 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)
heater_entity_id = entry.data.get(CONF_HEATER)
cycle_min = entry.data.get(CONF_CYCLE_MIN)
proportional_function = entry.data.get(CONF_PROP_FUNCTION)
temp_sensor_entity_id = entry.data.get(CONF_TEMP_SENSOR)
ext_temp_sensor_entity_id = entry.data.get(CONF_EXTERNAL_TEMP_SENSOR)
power_sensor_entity_id = entry.data.get(CONF_POWER_SENSOR)
max_power_sensor_entity_id = entry.data.get(CONF_MAX_POWER_SENSOR)
window_sensor_entity_id = entry.data.get(CONF_WINDOW_SENSOR)
window_delay_sec = entry.data.get(CONF_WINDOW_DELAY)
motion_sensor_entity_id = entry.data.get(CONF_MOTION_SENSOR)
motion_delay_sec = entry.data.get(CONF_MOTION_DELAY)
motion_preset = entry.data.get(CONF_MOTION_PRESET)
no_motion_preset = entry.data.get(CONF_NO_MOTION_PRESET)
device_power = entry.data.get(CONF_DEVICE_POWER)
tpi_coef_int = entry.data.get(CONF_TPI_COEF_INT)
tpi_coef_ext = entry.data.get(CONF_TPI_COEF_EXT)
presence_sensor_entity_id = entry.data.get(CONF_PRESENCE_SENSOR)
power_temp = entry.data.get(CONF_PRESET_POWER)
temp_min = entry.data.get(CONF_TEMP_MIN)
temp_max = entry.data.get(CONF_TEMP_MAX)
security_delay_min = entry.data.get(CONF_SECURITY_DELAY_MIN)
minimal_activation_delay = entry.data.get(CONF_MINIMAL_ACTIVATION_DELAY)
presets = {} entity = VersatileThermostat(hass, unique_id, name, entry.data)
for (key, value) in CONF_PRESETS.items():
_LOGGER.debug("looking for key=%s, value=%s", key, value)
if value in entry.data:
presets[key] = entry.data.get(value)
else:
_LOGGER.debug("value %s not found in Entry", value)
presets_away = {} async_add_entities([entity], True)
for (key, value) in CONF_PRESETS_AWAY.items(): VersatileThermostat.add_entity(entry.entry_id, entity)
_LOGGER.debug("looking for key=%s, value=%s", key, value)
if value in entry.data:
presets_away[key] = entry.data.get(value)
else:
_LOGGER.debug("value %s not found in Entry", value)
async_add_entities(
[
VersatileThermostat(
hass,
unique_id,
name,
heater_entity_id,
cycle_min,
proportional_function,
temp_sensor_entity_id,
ext_temp_sensor_entity_id,
temp_min,
temp_max,
power_sensor_entity_id,
max_power_sensor_entity_id,
window_sensor_entity_id,
window_delay_sec,
motion_sensor_entity_id,
motion_delay_sec,
motion_preset,
no_motion_preset,
presets,
presets_away,
device_power,
tpi_coef_int,
tpi_coef_ext,
presence_sensor_entity_id,
power_temp,
security_delay_min,
minimal_activation_delay,
)
],
True,
)
# Add services # Add services
platform = entity_platform.async_get_current_platform() platform = entity_platform.async_get_current_platform()
@@ -219,42 +156,10 @@ async def async_setup_entry(
class VersatileThermostat(ClimateEntity, RestoreEntity): class VersatileThermostat(ClimateEntity, RestoreEntity):
"""Representation of a Versatile Thermostat device.""" """Representation of a Versatile Thermostat device."""
_name: str # The list of VersatileThermostat entities
_heater_entity_id: str _registry: dict[str, object] = {}
_prop_algorithm: PropAlgorithm
_async_cancel_cycle: CALLBACK_TYPE
_attr_preset_modes: list[str] | None
def __init__( def __init__(self, hass, unique_id, name, entry_infos) -> None:
self,
hass,
unique_id,
name,
heater_entity_id,
cycle_min,
proportional_function,
temp_sensor_entity_id,
ext_temp_sensor_entity_id,
temp_min,
temp_max,
power_sensor_entity_id,
max_power_sensor_entity_id,
window_sensor_entity_id,
window_delay_sec,
motion_sensor_entity_id,
motion_delay_sec,
motion_preset,
no_motion_preset,
presets,
presets_away,
device_power,
tpi_coef_int,
tpi_coef_ext,
presence_sensor_entity_id,
power_temp,
security_delay_min,
minimal_activation_delay,
) -> None:
"""Initialize the thermostat.""" """Initialize the thermostat."""
super().__init__() super().__init__()
@@ -264,32 +169,98 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._unique_id = unique_id self._unique_id = unique_id
self._name = name self._name = name
self._heater_entity_id = heater_entity_id self._prop_algorithm = None
self._cycle_min = cycle_min self._async_cancel_cycle = None
self._proportional_function = proportional_function self._hvac_mode = None
self._temp_sensor_entity_id = temp_sensor_entity_id self._target_temp = None
self._ext_temp_sensor_entity_id = ext_temp_sensor_entity_id self._saved_target_temp = None
self._attr_max_temp = temp_max self._saved_preset_mode = None
self._attr_min_temp = temp_min self._fan_mode = None
self._power_sensor_entity_id = power_sensor_entity_id self._humidity = None
self._max_power_sensor_entity_id = max_power_sensor_entity_id self._swing_mode = None
self._window_sensor_entity_id = window_sensor_entity_id self._current_power = None
self._window_delay_sec = window_delay_sec self._current_power_max = None
self._window_delay_sec = window_delay_sec self._window_state = None
self._motion_sensor_entity_id = motion_sensor_entity_id self._motion_state = None
self._motion_delay_sec = motion_delay_sec self._saved_hvac_mode = None
self._motion_preset = motion_preset self._window_call_cancel = None
self._no_motion_preset = no_motion_preset self._motion_call_cancel = None
self._cur_ext_temp = None
self._cur_temp = None
self._ac_mode = None
self._last_ext_temperature_mesure = None
self._last_temperature_mesure = None
self._cur_ext_temp = None
self._presence_state = None
self._overpowering_state = None
self._should_relaunch_control_heating = None
self._security_delay_min = None
self._security_state = None
self.post_init(entry_infos)
def post_init(self, entry_infos):
"""Finish the initialization of the thermostast"""
_LOGGER.info(
"%s - Updating VersatileThermostat with infos %s",
self,
entry_infos,
)
# convert entry_infos into usable attributes
presets = {}
for (key, value) in CONF_PRESETS.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_away = {}
for (key, value) in CONF_PRESETS_AWAY.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)
# Stop eventual cycle running
if self._async_cancel_cycle is not None:
self._async_cancel_cycle()
self._async_cancel_cycle = None
if self._window_call_cancel is not None:
self._window_call_cancel()
self._window_call_cancel = None
if self._motion_call_cancel is not None:
self._motion_call_cancel()
self._motion_call_cancel = None
# Exploit usable attributs
self._heater_entity_id = entry_infos.get(CONF_HEATER)
self._cycle_min = entry_infos.get(CONF_CYCLE_MIN)
self._proportional_function = entry_infos.get(CONF_PROP_FUNCTION)
self._temp_sensor_entity_id = entry_infos.get(CONF_TEMP_SENSOR)
self._ext_temp_sensor_entity_id = entry_infos.get(CONF_EXTERNAL_TEMP_SENSOR)
self._attr_max_temp = entry_infos.get(CONF_TEMP_MAX)
self._attr_min_temp = entry_infos.get(CONF_TEMP_MIN)
self._power_sensor_entity_id = entry_infos.get(CONF_POWER_SENSOR)
self._max_power_sensor_entity_id = entry_infos.get(CONF_MAX_POWER_SENSOR)
self._window_sensor_entity_id = entry_infos.get(CONF_WINDOW_SENSOR)
self._window_delay_sec = entry_infos.get(CONF_WINDOW_DELAY)
self._motion_sensor_entity_id = entry_infos.get(CONF_MOTION_SENSOR)
self._motion_delay_sec = entry_infos.get(CONF_MOTION_DELAY)
self._motion_preset = entry_infos.get(CONF_MOTION_PRESET)
self._no_motion_preset = entry_infos.get(CONF_NO_MOTION_PRESET)
self._motion_on = ( self._motion_on = (
self._motion_sensor_entity_id is not None self._motion_sensor_entity_id is not None
and self._motion_preset is not None and self._motion_preset is not None
and self._no_motion_preset is not None and self._no_motion_preset is not None
) )
self._tpi_coef_int = tpi_coef_int self._tpi_coef_int = entry_infos.get(CONF_TPI_COEF_INT)
self._tpi_coef_ext = tpi_coef_ext self._tpi_coef_ext = entry_infos.get(CONF_TPI_COEF_EXT)
self._presence_sensor_entity_id = presence_sensor_entity_id self._presence_sensor_entity_id = entry_infos.get(CONF_PRESENCE_SENSOR)
self._power_temp = power_temp self._power_temp = entry_infos.get(CONF_PRESET_POWER)
self._presence_on = self._presence_sensor_entity_id is not None self._presence_on = self._presence_sensor_entity_id is not None
@@ -318,7 +289,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._saved_preset_mode = None self._saved_preset_mode = None
# Power management # Power management
self._device_power = device_power self._device_power = entry_infos.get(CONF_DEVICE_POWER)
self._pmax_on = False self._pmax_on = False
self._current_power = None self._current_power = None
self._current_power_max = None self._current_power_max = None
@@ -353,14 +324,16 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
) )
self._tpi_coef_ext = 0 self._tpi_coef_ext = 0
self._security_delay_min = security_delay_min self._security_delay_min = entry_infos.get(CONF_SECURITY_DELAY_MIN)
self._minimal_activation_delay = minimal_activation_delay self._minimal_activation_delay = entry_infos.get(CONF_MINIMAL_ACTIVATION_DELAY)
self._last_temperature_mesure = datetime.now() self._last_temperature_mesure = datetime.now()
self._last_ext_temperature_mesure = datetime.now() self._last_ext_temperature_mesure = datetime.now()
self._security_state = False self._security_state = False
self._saved_hvac_mode = None self._saved_hvac_mode = None
# Initiate the ProportionalAlgorithm # Initiate the ProportionalAlgorithm
if self._prop_algorithm is not None:
del self._prop_algorithm
self._prop_algorithm = PropAlgorithm( self._prop_algorithm = PropAlgorithm(
self._proportional_function, self._proportional_function,
self._tpi_coef_int, self._tpi_coef_int,
@@ -368,10 +341,6 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._cycle_min, self._cycle_min,
self._minimal_activation_delay, self._minimal_activation_delay,
) )
self._async_cancel_cycle = None
self._window_call_cancel = None
self._motion_call_cancel = None
self._should_relaunch_control_heating = False self._should_relaunch_control_heating = False
# Memory synthesis state # Memory synthesis state
@@ -385,9 +354,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if len(presets): if len(presets):
self._support_flags = SUPPORT_FLAGS | SUPPORT_PRESET_MODE self._support_flags = SUPPORT_FLAGS | SUPPORT_PRESET_MODE
for k, v in presets.items(): for key, val in presets.items():
if v != 0.0: if val != 0.0:
self._attr_preset_modes.append(k) self._attr_preset_modes.append(key)
# self._attr_preset_modes = ( # self._attr_preset_modes = (
# [PRESET_NONE] + list(presets.keys()) + [PRESET_ACTIVITY] # [PRESET_NONE] + list(presets.keys()) + [PRESET_ACTIVITY]
@@ -405,7 +374,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
"%s - Creation of a new VersatileThermostat entity: unique_id=%s heater_entity_id=%s", "%s - Creation of a new VersatileThermostat entity: unique_id=%s heater_entity_id=%s",
self, self,
self.unique_id, self.unique_id,
heater_entity_id, self._heater_entity_id,
) )
def __str__(self): def __str__(self):
@@ -505,7 +474,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
_LOGGER.info("%s - Set preset_mode: %s force=%s", self, preset_mode, force) _LOGGER.info("%s - Set preset_mode: %s force=%s", self, preset_mode, force)
if ( if (
preset_mode not in (self._attr_preset_modes or []) preset_mode not in (self._attr_preset_modes or [])
and preset_mode != PRESET_POWER and preset_mode not in HIDDEN_PRESETS
): ):
raise ValueError( raise ValueError(
f"Got unsupported preset_mode {preset_mode}. Must be one of {self._attr_preset_modes}" # pylint: disable=line-too-long f"Got unsupported preset_mode {preset_mode}. Must be one of {self._attr_preset_modes}" # pylint: disable=line-too-long
@@ -525,20 +494,24 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if self._attr_preset_mode == PRESET_NONE: if self._attr_preset_mode == PRESET_NONE:
self._saved_target_temp = self._target_temp self._saved_target_temp = self._target_temp
self._attr_preset_mode = preset_mode self._attr_preset_mode = preset_mode
preset_temp = self.find_preset_temp(preset_mode) self._target_temp = self.find_preset_temp(preset_mode)
self._target_temp = (
preset_temp if preset_mode != PRESET_POWER else self._power_temp
)
# Don't saved preset_mode if we are in POWER mode or in Away mode and presence detection is on self.save_preset_mode()
if preset_mode != PRESET_POWER:
self._saved_preset_mode = self._attr_preset_mode
self.recalculate() self.recalculate()
def find_preset_temp(self, preset_mode): def find_preset_temp(self, preset_mode):
"""Find the right temperature of a preset considering the presence if configured""" """Find the right temperature of a preset considering the presence if configured"""
if self._presence_on is False or self._presence_state in [STATE_ON, STATE_HOME]: if preset_mode == PRESET_SECURITY:
return (
self._target_temp
) # in security just keep the current target temperature, the thermostat should be off
if preset_mode == PRESET_POWER:
return self._power_temp
elif self._presence_on is False or self._presence_state in [
STATE_ON,
STATE_HOME,
]:
return self._presets[preset_mode] return self._presets[preset_mode]
else: else:
return self._presets_away[self.get_preset_away_name(preset_mode)] return self._presets_away[self.get_preset_away_name(preset_mode)]
@@ -847,7 +820,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if old_state.attributes.get(ATTR_PRESET_MODE) in self._attr_preset_modes: if old_state.attributes.get(ATTR_PRESET_MODE) in self._attr_preset_modes:
self._attr_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE) self._attr_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE)
self._saved_preset_mode = self._attr_preset_mode self.save_preset_mode()
if not self._hvac_mode and old_state.state: if not self._hvac_mode and old_state.state:
self._hvac_mode = old_state.state self._hvac_mode = old_state.state
@@ -1064,6 +1037,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
# try to restart if we were in security mode # try to restart if we were in security mode
if self._security_state: if self._security_state:
await self.async_set_hvac_mode(self._saved_hvac_mode) await self.async_set_hvac_mode(self._saved_hvac_mode)
await self.restore_preset_mode()
except ValueError as ex: except ValueError as ex:
_LOGGER.error("Unable to update temperature from sensor: %s", ex) _LOGGER.error("Unable to update temperature from sensor: %s", ex)
@@ -1079,6 +1054,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
# try to restart if we were in security mode # try to restart if we were in security mode
if self._security_state: if self._security_state:
await self.async_set_hvac_mode(self._saved_hvac_mode) await self.async_set_hvac_mode(self._saved_hvac_mode)
await self.restore_preset_mode()
except ValueError as ex: except ValueError as ex:
_LOGGER.error("Unable to update external temperature from sensor: %s", ex) _LOGGER.error("Unable to update external temperature from sensor: %s", ex)
@@ -1101,6 +1077,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if math.isnan(current_power) or math.isinf(current_power): if math.isnan(current_power) or math.isinf(current_power):
raise ValueError(f"Sensor has illegal state {new_state.state}") raise ValueError(f"Sensor has illegal state {new_state.state}")
self._current_power = current_power self._current_power = current_power
await self._async_control_heating()
except ValueError as ex: except ValueError as ex:
_LOGGER.error("Unable to update current_power from sensor: %s", ex) _LOGGER.error("Unable to update current_power from sensor: %s", ex)
@@ -1124,6 +1101,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if math.isnan(current_power_max) or math.isinf(current_power_max): if math.isnan(current_power_max) or math.isinf(current_power_max):
raise ValueError(f"Sensor has illegal state {new_state.state}") raise ValueError(f"Sensor has illegal state {new_state.state}")
self._current_power_max = current_power_max self._current_power_max = current_power_max
await self._async_control_heating()
except ValueError as ex: except ValueError as ex:
_LOGGER.error("Unable to update current_power from sensor: %s", ex) _LOGGER.error("Unable to update current_power from sensor: %s", ex)
@@ -1148,9 +1126,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
def _update_presence(self, new_state): def _update_presence(self, new_state):
_LOGGER.debug("%s - Updating presence. New state is %s", self, new_state) _LOGGER.debug("%s - Updating presence. New state is %s", self, new_state)
self._presence_state = new_state self._presence_state = new_state
if self._attr_preset_mode == PRESET_POWER or self._presence_on is False: if self._attr_preset_mode in HIDDEN_PRESETS or self._presence_on is False:
_LOGGER.info( _LOGGER.info(
"%s - Ignoring presence change cause in Power preset or presence not configured", "%s - Ignoring presence change cause in Power or Security preset or presence not configured",
self, self,
) )
return return
@@ -1231,6 +1209,26 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
HA_DOMAIN, SERVICE_TURN_OFF, data, context=self._context HA_DOMAIN, SERVICE_TURN_OFF, data, context=self._context
) )
def save_preset_mode(self):
"""Save the current preset mode to be restored later
We never save a hidden preset mode
"""
if (
self._attr_preset_mode not in HIDDEN_PRESETS
and self._attr_preset_mode is not None
):
self._saved_preset_mode = self._attr_preset_mode
async def restore_preset_mode(self):
"""Restore a previous preset mode
We never restore a hidden preset mode. Normally that is not possible
"""
if (
self._saved_preset_mode not in HIDDEN_PRESETS
and self._saved_preset_mode is not None
):
await self._async_set_preset_mode_internal(self._saved_preset_mode)
async def check_overpowering(self) -> bool: async def check_overpowering(self) -> bool:
"""Check the overpowering condition """Check the overpowering condition
Turn the preset_mode of the heater to 'power' if power conditions are exceeded Turn the preset_mode of the heater to 'power' if power conditions are exceeded
@@ -1264,9 +1262,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self, self,
self._saved_preset_mode, self._saved_preset_mode,
) )
await self.async_set_preset_mode( await self.restore_preset_mode()
self._saved_preset_mode if self._saved_preset_mode else PRESET_NONE
)
def check_date_temperature(self) -> bool: def check_date_temperature(self) -> bool:
"""Check if last temperature date is too long""" """Check if last temperature date is too long"""
@@ -1337,12 +1333,17 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._async_cancel_cycle() self._async_cancel_cycle()
self._async_cancel_cycle = None self._async_cancel_cycle = None
if time > 0 and on is True and self.check_date_temperature() is False: check_dates = self.check_date_temperature()
_LOGGER.warning("%s - Set the thermostat into security mode") if time > 0 and on is True and check_dates is False:
_LOGGER.warning("%s - Set the thermostat into security mode", self)
self._security_state = True self._security_state = True
self._saved_hvac_mode = self.hvac_mode self._saved_hvac_mode = self.hvac_mode
self.save_preset_mode()
await self._async_set_preset_mode_internal(PRESET_SECURITY)
await self.async_set_hvac_mode(HVAC_MODE_OFF) await self.async_set_hvac_mode(HVAC_MODE_OFF)
return return
if check_dates:
self._security_state = False
action_label = "start" if on else "stop" action_label = "start" if on else "stop"
if self._should_relaunch_control_heating: if self._should_relaunch_control_heating:
@@ -1516,3 +1517,26 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if self._attr_preset_mode == preset: if self._attr_preset_mode == preset:
await self._async_set_preset_mode_internal(preset, force=True) await self._async_set_preset_mode_internal(preset, force=True)
await self._async_control_heating(force=True) await self._async_control_heating(force=True)
@classmethod
def add_entity(cls, entry_id, entity):
"""Adds an entity into the VersatileRegistry entities"""
_LOGGER.debug("Adding entity %s", entry_id)
cls._registry[entry_id] = entity
_LOGGER.debug("Entity registry is now %s", cls._registry)
@classmethod
async def update_entity(cls, entry_id, infos):
"""Updates an existing entity referenced by entry_id with the infos in arguments"""
entity: VersatileThermostat = cls._registry.get(entry_id)
if entity is None:
_LOGGER.warning(
"Tries to update VersatileThermostat entity %s but was not found in thermostat registry",
entry_id,
)
return
_LOGGER.debug("We have found the entity to update")
entity.post_init(infos)
await entity.async_added_to_hass()

View File

@@ -3,9 +3,9 @@ from __future__ import annotations
import logging import logging
import copy import copy
from collections.abc import Mapping
import voluptuous as vol import voluptuous as vol
from collections.abc import Mapping
from typing import Any from typing import Any
from homeassistant.core import callback from homeassistant.core import callback
@@ -14,6 +14,8 @@ from homeassistant.config_entries import (
ConfigFlow as HAConfigFlow, ConfigFlow as HAConfigFlow,
OptionsFlow, OptionsFlow,
) )
# import homeassistant.helpers.entity_registry as entity_registry
from homeassistant.data_entry_flow import FlowHandler from homeassistant.data_entry_flow import FlowHandler
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
@@ -51,6 +53,8 @@ from .const import (
CONF_TEMP_MIN, CONF_TEMP_MIN,
) )
from .climate import VersatileThermostat
# from .climate import VersatileThermostat # from .climate import VersatileThermostat
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -469,13 +473,39 @@ class VersatileThermostatOptionsFlowHandler(
"advanced", "advanced",
self.STEP_ADVANCED_DATA_SCHEMA, self.STEP_ADVANCED_DATA_SCHEMA,
user_input, user_input,
self.async_finalize, self.async_end,
) )
async def async_finalize(self): async def async_end(self):
"""Finalization of the ConfigEntry creation""" """Finalization of the ConfigEntry creation"""
_LOGGER.debug( _LOGGER.debug(
"CTOR ConfigFlow.async_finalize - updating entry with: %s", self._infos "CTOR ConfigFlow.async_finalize - updating entry with: %s", self._infos
) )
# Find eventual existing entity to update it
# removing entities from registry (they will be recreated)
# ent_reg = entity_registry.async_get(self.hass)
# reg_entities = {
# ent.unique_id: ent.entity_id
# for ent in entity_registry.async_entries_for_config_entry(
# ent_reg, self.config_entry.entry_id
# )
# }
#
# for entry in entity_registry.async_entries_for_config_entry(
# ent_reg, self.config_entry.entry_id
# ):
# entity: VersatileThermostat = ent_reg.async_get(entry.entity_id)
# entity.async_registry_entry_updated(self._infos)
_LOGGER.debug(
"We have found entities to update: %s", self.config_entry.entry_id
)
await VersatileThermostat.update_entity(self.config_entry.entry_id, self._infos)
# for entity_id in reg_entities.values():
# _LOGGER.info("Recreating entity %s due to configuration change", entity_id)
# ent_reg.async_remove(entity_id)
#
self.hass.config_entries.async_update_entry(self.config_entry, data=self._infos) self.hass.config_entries.async_update_entry(self.config_entry, data=self._infos)
return self.async_create_entry(title=None, data=None) return self.async_create_entry(title=None, data=None)

View File

@@ -14,6 +14,9 @@ from .prop_algorithm import (
) )
PRESET_POWER = "power" PRESET_POWER = "power"
PRESET_SECURITY = "security"
HIDDEN_PRESETS = [PRESET_POWER, PRESET_SECURITY]
DOMAIN = "versatile_thermostat" DOMAIN = "versatile_thermostat"