Compare commits
4 Commits
1.0.0beta3
...
1.1.0.beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62425bb91f | ||
|
|
952d6d5e97 | ||
|
|
638e007c21 | ||
|
|
c520d7fad5 |
@@ -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,10 +95,16 @@ 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,
|
||||||
PRESET_AWAY_SUFFIX,
|
PRESET_AWAY_SUFFIX,
|
||||||
|
CONF_SECURITY_DELAY_MIN,
|
||||||
|
CONF_MINIMAL_ACTIVATION_DELAY,
|
||||||
|
CONF_TEMP_MAX,
|
||||||
|
CONF_TEMP_MIN,
|
||||||
|
HIDDEN_PRESETS,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .prop_algorithm import PropAlgorithm
|
from .prop_algorithm import PropAlgorithm
|
||||||
@@ -115,71 +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)
|
|
||||||
|
|
||||||
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,
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
True,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add services
|
# Add services
|
||||||
platform = entity_platform.async_get_current_platform()
|
platform = entity_platform.async_get_current_platform()
|
||||||
@@ -207,38 +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,
|
|
||||||
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,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize the thermostat."""
|
"""Initialize the thermostat."""
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -248,32 +169,100 @@ 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._power_sensor_entity_id = power_sensor_entity_id
|
self._saved_preset_mode = None
|
||||||
self._max_power_sensor_entity_id = max_power_sensor_entity_id
|
self._fan_mode = None
|
||||||
self._window_sensor_entity_id = window_sensor_entity_id
|
self._humidity = None
|
||||||
self._window_delay_sec = window_delay_sec
|
self._swing_mode = None
|
||||||
self._window_delay_sec = window_delay_sec
|
self._current_power = None
|
||||||
self._motion_sensor_entity_id = motion_sensor_entity_id
|
self._current_power_max = None
|
||||||
self._motion_delay_sec = motion_delay_sec
|
self._window_state = None
|
||||||
self._motion_preset = motion_preset
|
self._motion_state = None
|
||||||
self._no_motion_preset = no_motion_preset
|
self._saved_hvac_mode = None
|
||||||
|
self._window_call_cancel = None
|
||||||
|
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 != None
|
self._presence_on = self._presence_sensor_entity_id is not None
|
||||||
|
|
||||||
# TODO if self.ac_mode:
|
# TODO if self.ac_mode:
|
||||||
# self.hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF]
|
# self.hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF]
|
||||||
@@ -300,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
|
||||||
@@ -335,18 +324,23 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
)
|
)
|
||||||
self._tpi_coef_ext = 0
|
self._tpi_coef_ext = 0
|
||||||
|
|
||||||
|
self._security_delay_min = entry_infos.get(CONF_SECURITY_DELAY_MIN)
|
||||||
|
self._minimal_activation_delay = entry_infos.get(CONF_MINIMAL_ACTIVATION_DELAY)
|
||||||
|
self._last_temperature_mesure = datetime.now()
|
||||||
|
self._last_ext_temperature_mesure = datetime.now()
|
||||||
|
self._security_state = False
|
||||||
|
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,
|
||||||
self._tpi_coef_ext,
|
self._tpi_coef_ext,
|
||||||
self._cycle_min,
|
self._cycle_min,
|
||||||
|
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
|
||||||
@@ -360,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]
|
||||||
@@ -380,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):
|
||||||
@@ -480,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
|
||||||
@@ -500,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)]
|
||||||
@@ -654,7 +652,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
_LOGGER.debug("%s - Calling async_startup", self)
|
_LOGGER.debug("%s - Calling async_startup", self)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_startup_internal(*_):
|
async def _async_startup_internal(*_):
|
||||||
_LOGGER.debug("%s - Calling async_startup_internal", self)
|
_LOGGER.debug("%s - Calling async_startup_internal", self)
|
||||||
need_write_state = False
|
need_write_state = False
|
||||||
temperature_state = self.hass.states.get(self._temp_sensor_entity_id)
|
temperature_state = self.hass.states.get(self._temp_sensor_entity_id)
|
||||||
@@ -667,7 +665,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self,
|
self,
|
||||||
float(temperature_state.state),
|
float(temperature_state.state),
|
||||||
)
|
)
|
||||||
self._async_update_temp(temperature_state)
|
await self._async_update_temp(temperature_state)
|
||||||
need_write_state = True
|
need_write_state = True
|
||||||
|
|
||||||
if self._ext_temp_sensor_entity_id:
|
if self._ext_temp_sensor_entity_id:
|
||||||
@@ -683,7 +681,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self,
|
self,
|
||||||
float(ext_temperature_state.state),
|
float(ext_temperature_state.state),
|
||||||
)
|
)
|
||||||
self._async_update_ext_temp(ext_temperature_state)
|
await self._async_update_ext_temp(ext_temperature_state)
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - external temperature sensor have NOT been retrieved cause unknown or unavailable",
|
"%s - external temperature sensor have NOT been retrieved cause unknown or unavailable",
|
||||||
@@ -790,7 +788,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
await self.get_my_previous_state()
|
await self.get_my_previous_state()
|
||||||
|
|
||||||
if self.hass.state == CoreState.running:
|
if self.hass.state == CoreState.running:
|
||||||
_async_startup_internal()
|
await _async_startup_internal()
|
||||||
else:
|
else:
|
||||||
self.hass.bus.async_listen_once(
|
self.hass.bus.async_listen_once(
|
||||||
EVENT_HOMEASSISTANT_START, _async_startup_internal
|
EVENT_HOMEASSISTANT_START, _async_startup_internal
|
||||||
@@ -822,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
|
||||||
@@ -869,7 +867,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
if new_state is None or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
if new_state is None or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
||||||
return
|
return
|
||||||
|
|
||||||
self._async_update_temp(new_state)
|
await self._async_update_temp(new_state)
|
||||||
self.recalculate()
|
self.recalculate()
|
||||||
await self._async_control_heating(force=False)
|
await self._async_control_heating(force=False)
|
||||||
|
|
||||||
@@ -884,7 +882,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
if new_state is None or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
if new_state is None or new_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
||||||
return
|
return
|
||||||
|
|
||||||
self._async_update_ext_temp(new_state)
|
await self._async_update_ext_temp(new_state)
|
||||||
self.recalculate()
|
self.recalculate()
|
||||||
await self._async_control_heating(force=False)
|
await self._async_control_heating(force=False)
|
||||||
|
|
||||||
@@ -1028,24 +1026,35 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_update_temp(self, state):
|
async def _async_update_temp(self, state):
|
||||||
"""Update thermostat with latest state from sensor."""
|
"""Update thermostat with latest state from sensor."""
|
||||||
try:
|
try:
|
||||||
cur_temp = float(state.state)
|
cur_temp = float(state.state)
|
||||||
if math.isnan(cur_temp) or math.isinf(cur_temp):
|
if math.isnan(cur_temp) or math.isinf(cur_temp):
|
||||||
raise ValueError(f"Sensor has illegal state {state.state}")
|
raise ValueError(f"Sensor has illegal state {state.state}")
|
||||||
self._cur_temp = cur_temp
|
self._cur_temp = cur_temp
|
||||||
|
self._last_temperature_mesure = datetime.now()
|
||||||
|
# try to restart if we were in security mode
|
||||||
|
if self._security_state:
|
||||||
|
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)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_update_ext_temp(self, state):
|
async def _async_update_ext_temp(self, state):
|
||||||
"""Update thermostat with latest state from sensor."""
|
"""Update thermostat with latest state from sensor."""
|
||||||
try:
|
try:
|
||||||
cur_ext_temp = float(state.state)
|
cur_ext_temp = float(state.state)
|
||||||
if math.isnan(cur_ext_temp) or math.isinf(cur_ext_temp):
|
if math.isnan(cur_ext_temp) or math.isinf(cur_ext_temp):
|
||||||
raise ValueError(f"Sensor has illegal state {state.state}")
|
raise ValueError(f"Sensor has illegal state {state.state}")
|
||||||
self._cur_ext_temp = cur_ext_temp
|
self._cur_ext_temp = cur_ext_temp
|
||||||
|
self._last_ext_temperature_mesure = datetime.now()
|
||||||
|
# try to restart if we were in security mode
|
||||||
|
if self._security_state:
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -1069,6 +1078,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
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
|
||||||
|
|
||||||
|
if self._attr_preset_mode == PRESET_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)
|
||||||
|
|
||||||
@@ -1091,6 +1103,8 @@ 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
|
||||||
|
if self._attr_preset_mode == PRESET_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)
|
||||||
@@ -1115,9 +1129,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
|
||||||
@@ -1198,6 +1212,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
|
||||||
@@ -1231,9 +1265,29 @@ 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:
|
||||||
|
"""Check if last temperature date is too long"""
|
||||||
|
now = datetime.now()
|
||||||
|
delta_temp = (now - self._last_temperature_mesure).total_seconds() / 60.0
|
||||||
|
delta_ext_temp = (
|
||||||
|
now - self._last_ext_temperature_mesure
|
||||||
|
).total_seconds() / 60.0
|
||||||
|
if (
|
||||||
|
delta_temp > self._security_delay_min
|
||||||
|
or delta_ext_temp > self._security_delay_min
|
||||||
|
):
|
||||||
|
_LOGGER.warning(
|
||||||
|
"%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f)",
|
||||||
|
self,
|
||||||
|
self._security_delay_min,
|
||||||
|
delta_temp,
|
||||||
|
delta_ext_temp,
|
||||||
)
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
async def _async_control_heating(self, force=False, time=None):
|
async def _async_control_heating(self, force=False, time=None):
|
||||||
"""The main function used to run the calculation at each cycle"""
|
"""The main function used to run the calculation at each cycle"""
|
||||||
@@ -1246,22 +1300,24 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self._attr_preset_mode,
|
self._attr_preset_mode,
|
||||||
)
|
)
|
||||||
await self._async_heater_turn_off()
|
await self._async_heater_turn_off()
|
||||||
|
_LOGGER.debug("%s - End of cycle (0)", self)
|
||||||
return
|
return
|
||||||
|
|
||||||
on_time_sec: int = self._prop_algorithm.on_time_sec
|
on_time_sec: int = self._prop_algorithm.on_time_sec
|
||||||
off_time_sec: int = self._prop_algorithm.off_time_sec
|
off_time_sec: int = self._prop_algorithm.off_time_sec
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"%s - Running new cycle at %s. on_time_sec=%.0f, off_time_sec=%.0f",
|
"%s - Checking new cycle. on_time_sec=%.0f, off_time_sec=%.0f, security_state=%s, preset_mode=%s",
|
||||||
self,
|
self,
|
||||||
time,
|
|
||||||
on_time_sec,
|
on_time_sec,
|
||||||
off_time_sec,
|
off_time_sec,
|
||||||
|
self._security_state,
|
||||||
|
self._attr_preset_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Cancel eventual previous cycle if any
|
# Cancel eventual previous cycle if any
|
||||||
if self._async_cancel_cycle is not None:
|
if self._async_cancel_cycle is not None:
|
||||||
if force:
|
if force:
|
||||||
_LOGGER.debug("%s - we force a new cycle")
|
_LOGGER.debug("%s - we force a new cycle", self)
|
||||||
self._async_cancel_cycle()
|
self._async_cancel_cycle()
|
||||||
self._async_cancel_cycle = None
|
self._async_cancel_cycle = None
|
||||||
else:
|
else:
|
||||||
@@ -1270,22 +1326,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
self,
|
self,
|
||||||
)
|
)
|
||||||
self._should_relaunch_control_heating = True
|
self._should_relaunch_control_heating = True
|
||||||
|
_LOGGER.debug("%s - End of cycle (1)", self)
|
||||||
return
|
return
|
||||||
# await self._async_cancel_cycle()
|
|
||||||
# self._async_cancel_cycle = None
|
|
||||||
# Don't turn off if we will turn on just after
|
|
||||||
# if on_time_sec <= 0:
|
|
||||||
# await self._async_heater_turn_off()
|
|
||||||
|
|
||||||
if self._hvac_mode == HVAC_MODE_HEAT and on_time_sec > 0:
|
if self._hvac_mode == HVAC_MODE_HEAT and on_time_sec > 0:
|
||||||
# _LOGGER.info(
|
|
||||||
# "%s - start heating for %d min %d sec ",
|
|
||||||
# self,
|
|
||||||
# on_time_sec // 60,
|
|
||||||
# on_time_sec % 60,
|
|
||||||
# )
|
|
||||||
#
|
|
||||||
# await self._async_heater_turn_on()
|
|
||||||
|
|
||||||
async def _turn_on_off_later(
|
async def _turn_on_off_later(
|
||||||
on: bool, time, heater_action, next_cycle_action
|
on: bool, time, heater_action, next_cycle_action
|
||||||
@@ -1293,6 +1337,21 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
if self._async_cancel_cycle:
|
if self._async_cancel_cycle:
|
||||||
self._async_cancel_cycle()
|
self._async_cancel_cycle()
|
||||||
self._async_cancel_cycle = None
|
self._async_cancel_cycle = None
|
||||||
|
_LOGGER.debug("%s - Stopping cycle during calculation", self)
|
||||||
|
|
||||||
|
check_dates = self.check_date_temperature()
|
||||||
|
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._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)
|
||||||
|
# The cycle is not restarted in security mode. It will be restarted by a condition changes
|
||||||
|
_LOGGER.debug("%s - End of cycle (2)", self)
|
||||||
|
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:
|
||||||
@@ -1300,28 +1359,28 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
"Don't %s cause a cycle have to be relaunch", action_label
|
"Don't %s cause a cycle have to be relaunch", action_label
|
||||||
)
|
)
|
||||||
self._should_relaunch_control_heating = False
|
self._should_relaunch_control_heating = False
|
||||||
await self._async_control_heating()
|
self.hass.create_task(self._async_control_heating())
|
||||||
|
# await self._async_control_heating()
|
||||||
|
_LOGGER.debug("%s - End of cycle (3)", self)
|
||||||
return
|
return
|
||||||
else:
|
|
||||||
|
if time > 0:
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"%s - %s heating for %d min %d sec",
|
"%s - !!! %s heating for %d min %d sec",
|
||||||
self,
|
self,
|
||||||
action_label,
|
action_label,
|
||||||
time // 60,
|
time // 60,
|
||||||
time % 60,
|
time % 60,
|
||||||
)
|
)
|
||||||
if time > 0:
|
await heater_action()
|
||||||
await heater_action()
|
else:
|
||||||
else:
|
_LOGGER.debug("%s - No action on heater cause duration is 0", self)
|
||||||
_LOGGER.debug(
|
self.update_custom_attributes()
|
||||||
"%s - No action on heater cause duration is 0", self
|
self._async_cancel_cycle = async_call_later(
|
||||||
)
|
self.hass,
|
||||||
self.update_custom_attributes()
|
time,
|
||||||
self._async_cancel_cycle = async_call_later(
|
next_cycle_action,
|
||||||
self.hass,
|
)
|
||||||
time,
|
|
||||||
next_cycle_action,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def _turn_on_later(_):
|
async def _turn_on_later(_):
|
||||||
await _turn_on_off_later(
|
await _turn_on_off_later(
|
||||||
@@ -1331,30 +1390,6 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
next_cycle_action=_turn_off_later,
|
next_cycle_action=_turn_off_later,
|
||||||
)
|
)
|
||||||
|
|
||||||
# if self._async_cancel_cycle:
|
|
||||||
# self._async_cancel_cycle()
|
|
||||||
# self._async_cancel_cycle = None
|
|
||||||
#
|
|
||||||
# if self._should_relaunch_control_heating:
|
|
||||||
# _LOGGER.debug("Don't stop cause a cycle have to be relaunch")
|
|
||||||
# self._should_relaunch_control_heating = False
|
|
||||||
# await self._async_control_heating()
|
|
||||||
# return
|
|
||||||
# else:
|
|
||||||
# _LOGGER.info(
|
|
||||||
# "%s - stop heating for %d min %d sec",
|
|
||||||
# self,
|
|
||||||
# off_time_sec // 60,
|
|
||||||
# off_time_sec % 60,
|
|
||||||
# )
|
|
||||||
# await self._async_heater_turn_off()
|
|
||||||
# self.update_custom_attributes()
|
|
||||||
# self._async_cancel_cycle = async_call_later(
|
|
||||||
# self.hass,
|
|
||||||
# off_time_sec,
|
|
||||||
# _turn_off_later,
|
|
||||||
# )
|
|
||||||
|
|
||||||
async def _turn_off_later(_):
|
async def _turn_off_later(_):
|
||||||
await _turn_on_off_later(
|
await _turn_on_off_later(
|
||||||
on=False,
|
on=False,
|
||||||
@@ -1363,36 +1398,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
next_cycle_action=_turn_on_later,
|
next_cycle_action=_turn_on_later,
|
||||||
)
|
)
|
||||||
|
|
||||||
# if self._async_cancel_cycle:
|
|
||||||
# self._async_cancel_cycle()
|
|
||||||
# self._async_cancel_cycle = None
|
|
||||||
#
|
|
||||||
# if self._should_relaunch_control_heating:
|
|
||||||
# _LOGGER.debug("Don't stop cause a cycle have to be relaunch")
|
|
||||||
# self._should_relaunch_control_heating = False
|
|
||||||
# await self._async_control_heating()
|
|
||||||
# return
|
|
||||||
# else:
|
|
||||||
# _LOGGER.info(
|
|
||||||
# "%s - stop heating for %d min %d sec",
|
|
||||||
# self,
|
|
||||||
# off_time_sec // 60,
|
|
||||||
# off_time_sec % 60,
|
|
||||||
# )
|
|
||||||
# await self._async_heater_turn_off()
|
|
||||||
# self.update_custom_attributes()
|
|
||||||
# self._async_cancel_cycle = async_call_later(
|
|
||||||
# self.hass,
|
|
||||||
# on_time_sec,
|
|
||||||
# _turn_on_later,
|
|
||||||
# )
|
|
||||||
await _turn_on_later(None)
|
await _turn_on_later(None)
|
||||||
# # Program turn off
|
|
||||||
# self._async_cancel_cycle = async_call_later(
|
|
||||||
# self.hass,
|
|
||||||
# on_time_sec,
|
|
||||||
# _turn_off_later,
|
|
||||||
# )
|
|
||||||
|
|
||||||
elif self._is_device_active:
|
elif self._is_device_active:
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
@@ -1446,10 +1452,16 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
|
|||||||
"tpi_coef_ext": self._tpi_coef_ext,
|
"tpi_coef_ext": self._tpi_coef_ext,
|
||||||
"saved_preset_mode": self._saved_preset_mode,
|
"saved_preset_mode": self._saved_preset_mode,
|
||||||
"saved_target_temp": self._saved_target_temp,
|
"saved_target_temp": self._saved_target_temp,
|
||||||
|
"saved_hvac_mode": self._saved_hvac_mode,
|
||||||
"window_state": self._window_state,
|
"window_state": self._window_state,
|
||||||
"motion_state": self._motion_state,
|
"motion_state": self._motion_state,
|
||||||
"overpowering_state": self._overpowering_state,
|
"overpowering_state": self._overpowering_state,
|
||||||
"presence_state": self._presence_state,
|
"presence_state": self._presence_state,
|
||||||
|
"security_delay_min": self._security_delay_min,
|
||||||
|
"last_temperature_datetime": self._last_temperature_mesure.isoformat(),
|
||||||
|
"last_ext_temperature_datetime": self._last_ext_temperature_mesure.isoformat(),
|
||||||
|
"security_state": self._security_state,
|
||||||
|
"minimal_activation_delay_sec": self._minimal_activation_delay,
|
||||||
"last_update_datetime": datetime.now().isoformat(),
|
"last_update_datetime": datetime.now().isoformat(),
|
||||||
}
|
}
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
@@ -1513,3 +1525,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()
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -45,8 +47,14 @@ from .const import (
|
|||||||
CONF_TPI_COEF_INT,
|
CONF_TPI_COEF_INT,
|
||||||
CONF_PRESENCE_SENSOR,
|
CONF_PRESENCE_SENSOR,
|
||||||
PROPORTIONAL_FUNCTION_TPI,
|
PROPORTIONAL_FUNCTION_TPI,
|
||||||
|
CONF_SECURITY_DELAY_MIN,
|
||||||
|
CONF_MINIMAL_ACTIVATION_DELAY,
|
||||||
|
CONF_TEMP_MAX,
|
||||||
|
CONF_TEMP_MIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .climate import VersatileThermostat
|
||||||
|
|
||||||
# from .climate import VersatileThermostat
|
# from .climate import VersatileThermostat
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -117,6 +125,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
PROPORTIONAL_FUNCTION_TPI,
|
PROPORTIONAL_FUNCTION_TPI,
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
vol.Required(CONF_TEMP_MIN, default=7): vol.Coerce(float),
|
||||||
|
vol.Required(CONF_TEMP_MAX, default=35): vol.Coerce(float),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -174,6 +184,15 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.STEP_ADVANCED_DATA_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(
|
||||||
|
CONF_MINIMAL_ACTIVATION_DELAY, default=10
|
||||||
|
): cv.positive_int,
|
||||||
|
vol.Required(CONF_SECURITY_DELAY_MIN, default=60): cv.positive_int,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
async def validate_input(self, data: dict) -> dict[str]:
|
async def validate_input(self, data: dict) -> dict[str]:
|
||||||
"""Validate the user input allows us to connect.
|
"""Validate the user input allows us to connect.
|
||||||
|
|
||||||
@@ -303,6 +322,17 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
"presence",
|
"presence",
|
||||||
self.STEP_PRESENCE_DATA_SCHEMA,
|
self.STEP_PRESENCE_DATA_SCHEMA,
|
||||||
user_input,
|
user_input,
|
||||||
|
self.async_step_advanced,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_advanced(self, user_input: dict | None = None) -> FlowResult:
|
||||||
|
"""Handle the advanced parameter flow steps"""
|
||||||
|
_LOGGER.debug("Into ConfigFlow.async_step_advanced user_input=%s", user_input)
|
||||||
|
|
||||||
|
return await self.generic_step(
|
||||||
|
"advanced",
|
||||||
|
self.STEP_ADVANCED_DATA_SCHEMA,
|
||||||
|
user_input,
|
||||||
self.async_finalize, # pylint: disable=no-member
|
self.async_finalize, # pylint: disable=no-member
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -417,7 +447,7 @@ class VersatileThermostatOptionsFlowHandler(
|
|||||||
"power",
|
"power",
|
||||||
self.STEP_POWER_DATA_SCHEMA,
|
self.STEP_POWER_DATA_SCHEMA,
|
||||||
user_input,
|
user_input,
|
||||||
self.async_step_presence, # pylint: disable=no-member
|
self.async_step_presence,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_step_presence(self, user_input: dict | None = None) -> FlowResult:
|
async def async_step_presence(self, user_input: dict | None = None) -> FlowResult:
|
||||||
@@ -430,13 +460,52 @@ class VersatileThermostatOptionsFlowHandler(
|
|||||||
"presence",
|
"presence",
|
||||||
self.STEP_PRESENCE_DATA_SCHEMA,
|
self.STEP_PRESENCE_DATA_SCHEMA,
|
||||||
user_input,
|
user_input,
|
||||||
self.async_finalize, # pylint: disable=no-member
|
self.async_step_advanced,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_finalize(self):
|
async def async_step_advanced(self, user_input: dict | None = None) -> FlowResult:
|
||||||
|
"""Handle the advanced flow steps"""
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Into OptionsFlowHandler.async_step_advanced user_input=%s", user_input
|
||||||
|
)
|
||||||
|
|
||||||
|
return await self.generic_step(
|
||||||
|
"advanced",
|
||||||
|
self.STEP_ADVANCED_DATA_SCHEMA,
|
||||||
|
user_input,
|
||||||
|
self.async_end,
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
@@ -35,6 +38,10 @@ CONF_TPI_COEF_INT = "tpi_coef_int"
|
|||||||
CONF_TPI_COEF_EXT = "tpi_coef_ext"
|
CONF_TPI_COEF_EXT = "tpi_coef_ext"
|
||||||
CONF_PRESENCE_SENSOR = "presence_sensor_entity_id"
|
CONF_PRESENCE_SENSOR = "presence_sensor_entity_id"
|
||||||
CONF_PRESET_POWER = "power_temp"
|
CONF_PRESET_POWER = "power_temp"
|
||||||
|
CONF_MINIMAL_ACTIVATION_DELAY = "minimal_activation_delay"
|
||||||
|
CONF_TEMP_MIN = "temp_min"
|
||||||
|
CONF_TEMP_MAX = "temp_max"
|
||||||
|
CONF_SECURITY_DELAY_MIN = "security_delay_min"
|
||||||
|
|
||||||
CONF_PRESETS = {
|
CONF_PRESETS = {
|
||||||
p: f"{p}_temp"
|
p: f"{p}_temp"
|
||||||
@@ -81,6 +88,10 @@ ALL_CONF = (
|
|||||||
CONF_TPI_COEF_INT,
|
CONF_TPI_COEF_INT,
|
||||||
CONF_TPI_COEF_EXT,
|
CONF_TPI_COEF_EXT,
|
||||||
CONF_PRESENCE_SENSOR,
|
CONF_PRESENCE_SENSOR,
|
||||||
|
CONF_MINIMAL_ACTIVATION_DELAY,
|
||||||
|
CONF_TEMP_MIN,
|
||||||
|
CONF_TEMP_MAX,
|
||||||
|
CONF_SECURITY_DELAY_MIN,
|
||||||
]
|
]
|
||||||
+ CONF_PRESETS_VALUES
|
+ CONF_PRESETS_VALUES
|
||||||
+ CONF_PRESETS_AWAY_VALUES,
|
+ CONF_PRESETS_AWAY_VALUES,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import logging
|
import logging
|
||||||
import math
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -21,20 +20,22 @@ class PropAlgorithm:
|
|||||||
tpi_coef_int,
|
tpi_coef_int,
|
||||||
tpi_coef_ext,
|
tpi_coef_ext,
|
||||||
cycle_min: int,
|
cycle_min: int,
|
||||||
|
minimal_activation_delay: int,
|
||||||
):
|
):
|
||||||
"""Initialisation of the Proportional Algorithm"""
|
"""Initialisation of the Proportional Algorithm"""
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Creation new PropAlgorithm function_type: %s, tpi_coef_int: %s, tpi_coef_ext: %s, cycle_min:%d",
|
"Creation new PropAlgorithm function_type: %s, tpi_coef_int: %s, tpi_coef_ext: %s, cycle_min:%d, minimal_activation_delay:%d",
|
||||||
function_type,
|
function_type,
|
||||||
tpi_coef_int,
|
tpi_coef_int,
|
||||||
tpi_coef_ext,
|
tpi_coef_ext,
|
||||||
cycle_min,
|
cycle_min,
|
||||||
|
minimal_activation_delay,
|
||||||
)
|
)
|
||||||
# TODO test function_type, bias, cycle_min
|
|
||||||
self._function = function_type
|
self._function = function_type
|
||||||
self._tpi_coef_int = tpi_coef_int
|
self._tpi_coef_int = tpi_coef_int
|
||||||
self._tpi_coef_ext = tpi_coef_ext
|
self._tpi_coef_ext = tpi_coef_ext
|
||||||
self._cycle_min = cycle_min
|
self._cycle_min = cycle_min
|
||||||
|
self._minimal_activation_delay = minimal_activation_delay
|
||||||
self._on_percent = 0
|
self._on_percent = 0
|
||||||
self._on_time_sec = 0
|
self._on_time_sec = 0
|
||||||
self._off_time_sec = self._cycle_min * 60
|
self._off_time_sec = self._cycle_min * 60
|
||||||
@@ -74,12 +75,19 @@ class PropAlgorithm:
|
|||||||
self._on_time_sec = self._on_percent * self._cycle_min * 60
|
self._on_time_sec = self._on_percent * self._cycle_min * 60
|
||||||
|
|
||||||
# Do not heat for less than xx sec
|
# Do not heat for less than xx sec
|
||||||
if self._on_time_sec < PROPORTIONAL_MIN_DURATION_SEC:
|
if self._on_time_sec < self._minimal_activation_delay:
|
||||||
_LOGGER.debug(
|
if self._on_time_sec > 0:
|
||||||
"No heating period due to heating period too small (%f < %f)",
|
_LOGGER.info(
|
||||||
self._on_time_sec,
|
"No heating period due to heating period too small (%f < %f)",
|
||||||
PROPORTIONAL_MIN_DURATION_SEC,
|
self._on_time_sec,
|
||||||
)
|
self._minimal_activation_delay,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"No heating period due to heating period too small (%f < %f)",
|
||||||
|
self._on_time_sec,
|
||||||
|
self._minimal_activation_delay,
|
||||||
|
)
|
||||||
self._on_time_sec = 0
|
self._on_time_sec = 0
|
||||||
|
|
||||||
self._off_time_sec = self._cycle_min * 60 - self._on_time_sec
|
self._off_time_sec = self._cycle_min * 60 - self._on_time_sec
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ set_presence:
|
|||||||
description: Force the presence mode in thermostat
|
description: Force the presence mode in thermostat
|
||||||
target:
|
target:
|
||||||
entity:
|
entity:
|
||||||
multiple: true
|
|
||||||
integration: versatile_thermostat
|
integration: versatile_thermostat
|
||||||
fields:
|
fields:
|
||||||
presence:
|
presence:
|
||||||
@@ -26,7 +25,6 @@ set_preset_temperature:
|
|||||||
description: Change the target temperature of a preset
|
description: Change the target temperature of a preset
|
||||||
target:
|
target:
|
||||||
entity:
|
entity:
|
||||||
multiple: true
|
|
||||||
integration: versatile_thermostat
|
integration: versatile_thermostat
|
||||||
fields:
|
fields:
|
||||||
preset:
|
preset:
|
||||||
|
|||||||
@@ -12,7 +12,9 @@
|
|||||||
"temperature_sensor_entity_id": "Temperature sensor entity id",
|
"temperature_sensor_entity_id": "Temperature sensor entity id",
|
||||||
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
|
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
|
||||||
"cycle_min": "Cycle duration (minutes)",
|
"cycle_min": "Cycle duration (minutes)",
|
||||||
"proportional_function": "Algorithm to use (TPI is the only one for now)"
|
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||||
|
"temp_min": "Minimal temperature allowed",
|
||||||
|
"temp_max": "Maximal temperature allowed"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tpi": {
|
"tpi": {
|
||||||
@@ -69,6 +71,14 @@
|
|||||||
"comfort_away_temp": "Temperature in Comfort preset when no presence",
|
"comfort_away_temp": "Temperature in Comfort preset when no presence",
|
||||||
"boost_away_temp": "Temperature in Boost preset when no presence"
|
"boost_away_temp": "Temperature in Boost preset when no presence"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"title": "Advanced parameters",
|
||||||
|
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThis parameters can lead to a very bad temperature or power regulation.",
|
||||||
|
"data": {
|
||||||
|
"minimal_activation_delay": "Delay in secondes under which the equipment will not be activated",
|
||||||
|
"security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a sceurity off state"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -91,7 +101,9 @@
|
|||||||
"temperature_sensor_entity_id": "Temperature sensor entity id",
|
"temperature_sensor_entity_id": "Temperature sensor entity id",
|
||||||
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
|
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
|
||||||
"cycle_min": "Cycle duration (minutes)",
|
"cycle_min": "Cycle duration (minutes)",
|
||||||
"proportional_function": "Algorithm to use (TPI is the only one for now)"
|
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||||
|
"temp_min": "Minimal temperature allowed",
|
||||||
|
"temp_max": "Maximal temperature allowed"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tpi": {
|
"tpi": {
|
||||||
@@ -148,6 +160,14 @@
|
|||||||
"comfort_away_temp": "Temperature in Comfort preset when no presence",
|
"comfort_away_temp": "Temperature in Comfort preset when no presence",
|
||||||
"boost_away_temp": "Temperature in Boost preset when no presence"
|
"boost_away_temp": "Temperature in Boost preset when no presence"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"title": "Advanced parameters",
|
||||||
|
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThis parameters can lead to a very bad temperature or power regulation.",
|
||||||
|
"data": {
|
||||||
|
"minimal_activation_delay": "Delay in secondes under which the equipment will not be activated",
|
||||||
|
"security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a sceurity off state"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
|||||||
@@ -12,7 +12,9 @@
|
|||||||
"temperature_sensor_entity_id": "Temperature sensor entity id",
|
"temperature_sensor_entity_id": "Temperature sensor entity id",
|
||||||
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
|
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
|
||||||
"cycle_min": "Cycle duration (minutes)",
|
"cycle_min": "Cycle duration (minutes)",
|
||||||
"proportional_function": "Algorithm to use (TPI is the only one for now)"
|
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||||
|
"temp_min": "Minimal temperature allowed",
|
||||||
|
"temp_max": "Maximal temperature allowed"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tpi": {
|
"tpi": {
|
||||||
@@ -69,6 +71,14 @@
|
|||||||
"comfort_away_temp": "Temperature in Comfort preset when no presence",
|
"comfort_away_temp": "Temperature in Comfort preset when no presence",
|
||||||
"boost_away_temp": "Temperature in Boost preset when no presence"
|
"boost_away_temp": "Temperature in Boost preset when no presence"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"title": "Advanced parameters",
|
||||||
|
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThis parameters can lead to a very bad temperature or power regulation.",
|
||||||
|
"data": {
|
||||||
|
"minimal_activation_delay": "Delay in secondes under which the equipment will not be activated",
|
||||||
|
"security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a sceurity off state"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -91,7 +101,9 @@
|
|||||||
"temperature_sensor_entity_id": "Temperature sensor entity id",
|
"temperature_sensor_entity_id": "Temperature sensor entity id",
|
||||||
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
|
"external_temperature_sensor_entity_id": "External temperature sensor entity id",
|
||||||
"cycle_min": "Cycle duration (minutes)",
|
"cycle_min": "Cycle duration (minutes)",
|
||||||
"proportional_function": "Algorithm to use (TPI is the only one for now)"
|
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||||
|
"temp_min": "Minimal temperature allowed",
|
||||||
|
"temp_max": "Maximal temperature allowed"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tpi": {
|
"tpi": {
|
||||||
@@ -148,6 +160,14 @@
|
|||||||
"comfort_away_temp": "Temperature in Comfort preset when no presence",
|
"comfort_away_temp": "Temperature in Comfort preset when no presence",
|
||||||
"boost_away_temp": "Temperature in Boost preset when no presence"
|
"boost_away_temp": "Temperature in Boost preset when no presence"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"title": "Advanced parameters",
|
||||||
|
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThis parameters can lead to a very bad temperature or power regulation.",
|
||||||
|
"data": {
|
||||||
|
"minimal_activation_delay": "Delay in secondes under which the equipment will not be activated",
|
||||||
|
"security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a sceurity off state"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
|||||||
@@ -12,7 +12,9 @@
|
|||||||
"temperature_sensor_entity_id": "Température sensor entity id",
|
"temperature_sensor_entity_id": "Température sensor entity id",
|
||||||
"external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id",
|
"external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id",
|
||||||
"cycle_min": "Durée du cycle (minutes)",
|
"cycle_min": "Durée du cycle (minutes)",
|
||||||
"proportional_function": "Algorithm à utiliser (Seul TPI est disponible pour l'instant)"
|
"proportional_function": "Algorithm à utiliser (Seul TPI est disponible pour l'instant)",
|
||||||
|
"temp_min": "Température minimale permise",
|
||||||
|
"temp_max": "Température maximale permise"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tpi": {
|
"tpi": {
|
||||||
@@ -69,6 +71,14 @@
|
|||||||
"comfort_away_temp": "Température en preset Comfort en cas d'absence",
|
"comfort_away_temp": "Température en preset Comfort en cas d'absence",
|
||||||
"boost_away_temp": "Température en preset Boost en cas d'absence"
|
"boost_away_temp": "Température en preset Boost en cas d'absence"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"title": "Parameters avancés",
|
||||||
|
"description": "Configuration des paramètres avancés. Laissez les valeurs par défaut si vous ne savez pas ce que vous faites.\nCes paramètres peuvent induire des mauvais comportements du thermostat.",
|
||||||
|
"data": {
|
||||||
|
"minimal_activation_delay": "Délai en seondes en-dessous duquel l'équipement ne sera pas activé",
|
||||||
|
"security_delay_min": "Délai maximal autorisé en minutes entre 2 mesures de températures. Au-dessus de ce délai, le thermostat se mettra en position éteinte de sécurité"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -91,7 +101,9 @@
|
|||||||
"temperature_sensor_entity_id": "Température sensor entity id",
|
"temperature_sensor_entity_id": "Température sensor entity id",
|
||||||
"external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id",
|
"external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id",
|
||||||
"cycle_min": "Durée du cycle (minutes)",
|
"cycle_min": "Durée du cycle (minutes)",
|
||||||
"proportional_function": "Algorithm à utiliser (Seul TPI est disponible pour l'instant)"
|
"proportional_function": "Algorithm à utiliser (Seul TPI est disponible pour l'instant)",
|
||||||
|
"temp_min": "Température minimale permise",
|
||||||
|
"temp_max": "Température maximale permise"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tpi": {
|
"tpi": {
|
||||||
@@ -148,6 +160,14 @@
|
|||||||
"comfort_away_temp": "Température en preset Comfort en cas d'absence",
|
"comfort_away_temp": "Température en preset Comfort en cas d'absence",
|
||||||
"boost_away_temp": "Température en preset Boost en cas d'absence"
|
"boost_away_temp": "Température en preset Boost en cas d'absence"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"advanced": {
|
||||||
|
"title": "Parameters avancés",
|
||||||
|
"description": "Configuration des paramètres avancés. Laissez les valeurs par défaut si vous ne savez pas ce que vous faites.\nCes paramètres peuvent induire des mauvais comportements du thermostat.",
|
||||||
|
"data": {
|
||||||
|
"minimal_activation_delay": "Délai en seondes en-dessous duquel l'équipement ne sera pas activé",
|
||||||
|
"security_delay_min": "Délai maximal autorisé en minutes entre 2 mesures de températures. Au-dessus de ce délai, le thermostat se mettra en position éteinte de sécurité"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
|
|||||||
Reference in New Issue
Block a user