Compare commits

..

10 Commits

Author SHA1 Message Date
Jean-Marc Collin
5063374b97 Allow calculation even if slope is None 2024-10-31 22:29:30 +00:00
Jean-Marc Collin
461db8d86c Change algo to take slop into account 2024-10-31 21:57:55 +00:00
Jean-Marc Collin
38c4b067b1 FIX too much start/stop 2024-10-31 21:30:44 +00:00
Jean-Marc Collin
32b0bcce19 + comment 2024-10-31 11:48:52 +00:00
Jean-Marc Collin
d971cc6aed Add change_preset test 2024-10-31 11:48:01 +00:00
Jean-Marc Collin
5bcbbb2d84 All is fine 2024-10-31 11:27:38 +00:00
Jean-Marc Collin
7854b44a2e Change algo 2024-10-29 21:48:47 +00:00
Jean-Marc Collin
3219fd293e With config flow ok 2024-10-27 10:42:30 +00:00
Jean-Marc Collin
db3afdf887 Auto start/stop alog and testu + ConfigFlow 2024-10-27 10:05:15 +00:00
Jean-Marc Collin
17ebf629e6 Migrate to HA 2024.10.4 2024-10-27 10:05:14 +00:00
238 changed files with 4531 additions and 12217 deletions

View File

@@ -91,48 +91,6 @@ input_number:
icon: mdi:thermostat
unit_of_measurement: °C
mode: box
fake_offset_calibration1:
name: Sonoff offset calibration 1
min: -12
max: 12
icon: mdi:tune
unit_of_measurement: °C
mode: box
fake_opening_degree1:
name: Sonoff Opening degree 1
min: 0
max: 100
icon: mdi:valve-open
unit_of_measurement: "%"
mode: box
fake_closing_degree1:
name: Sonoff Closing degree 1
min: 0
max: 100
icon: mdi:valve-closed
unit_of_measurement: "%"
mode: box
fake_offset_calibration2:
name: Sonoff offset calibration 2
min: -12
max: 12
icon: mdi:tune
unit_of_measurement: °C
mode: box
fake_opening_degree2:
name: Sonoff Opening degree 2
min: 0
max: 100
icon: mdi:valve-open
unit_of_measurement: "%"
mode: box
fake_closing_degree2:
name: Sonoff Closing degree 2
min: 0
max: 100
icon: mdi:valve-closed
unit_of_measurement: "%"
mode: box
input_boolean:
# input_boolean to simulate the windows entity. Only for development environment.
@@ -184,12 +142,6 @@ input_boolean:
fake_presence_sensor1:
name: Presence Sensor 1
icon: mdi:home
fake_valve_sonoff_trvzb1:
name: Valve Sonoff TRVZB1
icon: mdi:valve
fake_valve_sonoff_trvzb2:
name: Valve Sonoff TRVZB2
icon: mdi:valve
climate:
- platform: generic_thermostat
@@ -200,7 +152,6 @@ climate:
name: Underlying thermostat2
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
ac_mode: false
- platform: generic_thermostat
name: Underlying thermostat3
heater: input_boolean.fake_heater_switch3
@@ -233,16 +184,6 @@ climate:
name: Underlying thermostat9
heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying Sonoff TRVZB1
heater: input_boolean.fake_valve_sonoff_trvzb1
target_sensor: input_number.fake_temperature_sensor1
ac_mode: false
- platform: generic_thermostat
name: Underlying Sonoff TRVZB2
heater: input_boolean.fake_valve_sonoff_trvzb2
target_sensor: input_number.fake_temperature_sensor1
ac_mode: false
input_datetime:
fake_last_seen:
@@ -296,14 +237,14 @@ switch:
friendly_name: "Pilote chauffage SDB RDC"
value_template: "{{ is_state_attr('switch_seche_serviettes_sdb_rdc', 'sensor_state', 'on') }}"
turn_on:
action: select.select_option
service: select.select_option
data:
option: comfort
target:
entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode
turn_off:
action: select.select_option
service: select.select_option
data:
option: comfort-2
target:

View File

@@ -4,8 +4,6 @@ about: Create a report to help us improve
---
> Please read carefuly this instructions and fill this form before writing an issue. It helps me to help you.
<!-- This template will allow the maintainer to be efficient and post the more accurante response as possible. There is many types / modes / configuration possible, so the analysis can be very tricky. If don't follow this template, your issue could be rejected without any message. Please help me to help you. -->
<!-- Before you open a new issue, search through the existing issues to see if others have had the same problem.

File diff suppressed because it is too large Load Diff

1689
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -38,23 +38,6 @@ from .const import (
CONF_USE_CENTRAL_BOILER_FEATURE,
CONF_POWER_SENSOR,
CONF_PRESENCE_SENSOR,
CONF_UNDERLYING_LIST,
CONF_HEATER,
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
CONF_VALVE,
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_VALVE,
CONF_MAX_ON_PERCENT,
)
from .vtherm_api import VersatileThermostatAPI
@@ -87,7 +70,6 @@ CONFIG_SCHEMA = vol.Schema(
CONF_AUTO_REGULATION_EXPERT: vol.Schema(SELF_REGULATION_PARAM_SCHEMA),
CONF_SHORT_EMA_PARAMS: vol.Schema(EMA_PARAM_SCHEMA),
CONF_SAFETY_MODE: vol.Schema(SAFETY_MODE_PARAM_SCHEMA),
vol.Optional(CONF_MAX_ON_PERCENT): vol.Coerce(float),
}
),
},
@@ -180,20 +162,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if hass.state == CoreState.running:
await api.reload_central_boiler_entities_list()
await api.init_vtherm_links(entry.entry_id)
await api.init_vtherm_links()
return True
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update listener."""
_LOGGER.debug(
"Calling update_listener entry: entry_id='%s', value='%s'",
entry.entry_id,
entry.data,
)
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CENTRAL_CONFIG:
await reload_all_vtherm(hass)
else:
@@ -202,7 +177,7 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass)
if api is not None:
await api.reload_central_boiler_entities_list()
await api.init_vtherm_links(entry.entry_id)
await api.init_vtherm_links()
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@@ -233,9 +208,10 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
)
new = {**config_entry.data}
thermostat_type = config_entry.data.get(CONF_THERMOSTAT_TYPE)
if thermostat_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
if (
config_entry.data.get(CONF_THERMOSTAT_TYPE)
== CONF_THERMOSTAT_CENTRAL_CONFIG
):
new[CONF_USE_WINDOW_FEATURE] = True
new[CONF_USE_MOTION_FEATURE] = True
new[CONF_USE_POWER_FEATURE] = new.get(CONF_POWER_SENSOR, None) is not None
@@ -247,50 +223,6 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"add_central_boiler_control", False
) or new.get(CONF_USE_CENTRAL_BOILER_FEATURE, False)
if config_entry.data.get(CONF_UNDERLYING_LIST, None) is None:
underlying_list = []
if thermostat_type == CONF_THERMOSTAT_SWITCH:
underlying_list = [
config_entry.data.get(CONF_HEATER, None),
config_entry.data.get(CONF_HEATER_2, None),
config_entry.data.get(CONF_HEATER_3, None),
config_entry.data.get(CONF_HEATER_4, None),
]
elif thermostat_type == CONF_THERMOSTAT_CLIMATE:
underlying_list = [
config_entry.data.get(CONF_CLIMATE, None),
config_entry.data.get(CONF_CLIMATE_2, None),
config_entry.data.get(CONF_CLIMATE_3, None),
config_entry.data.get(CONF_CLIMATE_4, None),
]
elif thermostat_type == CONF_THERMOSTAT_VALVE:
underlying_list = [
config_entry.data.get(CONF_VALVE, None),
config_entry.data.get(CONF_VALVE_2, None),
config_entry.data.get(CONF_VALVE_3, None),
config_entry.data.get(CONF_VALVE_4, None),
]
new[CONF_UNDERLYING_LIST] = [
entity for entity in underlying_list if entity is not None
]
for key in [
CONF_HEATER,
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
CONF_VALVE,
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
]:
new.pop(key, None)
hass.config_entries.async_update_entry(
config_entry,
data=new,

View File

@@ -3,7 +3,7 @@
"""
import logging
from datetime import datetime
from datetime import datetime, timedelta
from typing import Literal
from homeassistant.components.climate import HVACMode
@@ -13,6 +13,7 @@ from .const import (
AUTO_START_STOP_LEVEL_FAST,
AUTO_START_STOP_LEVEL_MEDIUM,
AUTO_START_STOP_LEVEL_SLOW,
CONF_AUTO_START_STOP_LEVELS,
TYPE_AUTO_START_STOP_LEVELS,
)
@@ -30,9 +31,6 @@ DT_MIN = {
# the measurement cycle (2 min)
CYCLE_SEC = 120
# A temp hysteresis to avoid rapid OFF/ON
TEMP_HYSTERESIS = 0.5
ERROR_THRESHOLD = {
AUTO_START_STOP_LEVEL_NONE: 0, # Not used
AUTO_START_STOP_LEVEL_SLOW: 10, # 10 cycle above 1° or 5 cycle above 2°, ...
@@ -57,13 +55,10 @@ class AutoStartStopDetectionAlgorithm:
_accumulated_error: float = 0
_error_threshold: float | None = None
_last_calculation_date: datetime | None = None
_last_switch_date: datetime | None = None
def __init__(self, level: TYPE_AUTO_START_STOP_LEVELS, vtherm_name) -> None:
"""Initalize a new algorithm with the right constants"""
self._vtherm_name = vtherm_name
self._last_calculation_date = None
self._last_switch_date = None
self._init_level(level)
def _init_level(self, level: TYPE_AUTO_START_STOP_LEVELS):
@@ -146,26 +141,17 @@ class AutoStartStopDetectionAlgorithm:
temp_at_dt = current_temp + slope_min * self._dt
# Calculate the number of minute from last_switch
nb_minutes_since_last_switch = 999
if self._last_switch_date is not None:
nb_minutes_since_last_switch = (
now - self._last_switch_date
).total_seconds() / 60
# Check to turn-off
# When we hit the threshold, that mean we can turn off
if hvac_mode == HVACMode.HEAT:
if (
self._accumulated_error <= -self._error_threshold
and temp_at_dt >= target_temp + TEMP_HYSTERESIS
and nb_minutes_since_last_switch >= self._dt
and temp_at_dt >= target_temp
):
_LOGGER.info(
"%s - We need to stop, there is no need for heating for a long time.",
self,
)
self._last_switch_date = now
return AUTO_START_STOP_ACTION_OFF
else:
_LOGGER.debug("%s - nothing to do, we are heating", self)
@@ -174,14 +160,12 @@ class AutoStartStopDetectionAlgorithm:
if hvac_mode == HVACMode.COOL:
if (
self._accumulated_error >= self._error_threshold
and temp_at_dt <= target_temp - TEMP_HYSTERESIS
and nb_minutes_since_last_switch >= self._dt
and temp_at_dt <= target_temp
):
_LOGGER.info(
"%s - We need to stop, there is no need for cooling for a long time.",
self,
)
self._last_switch_date = now
return AUTO_START_STOP_ACTION_OFF
else:
_LOGGER.debug(
@@ -192,15 +176,11 @@ class AutoStartStopDetectionAlgorithm:
# check to turn on
if hvac_mode == HVACMode.OFF and saved_hvac_mode == HVACMode.HEAT:
if (
temp_at_dt <= target_temp - TEMP_HYSTERESIS
and nb_minutes_since_last_switch >= self._dt
):
if temp_at_dt <= target_temp:
_LOGGER.info(
"%s - We need to start, because it will be time to heat",
self,
)
self._last_switch_date = now
return AUTO_START_STOP_ACTION_ON
else:
_LOGGER.debug(
@@ -210,15 +190,11 @@ class AutoStartStopDetectionAlgorithm:
return AUTO_START_STOP_ACTION_NOTHING
if hvac_mode == HVACMode.OFF and saved_hvac_mode == HVACMode.COOL:
if (
temp_at_dt >= target_temp + TEMP_HYSTERESIS
and nb_minutes_since_last_switch >= self._dt
):
if temp_at_dt >= target_temp:
_LOGGER.info(
"%s - We need to start, because it will be time to cool",
self,
)
self._last_switch_date = now
return AUTO_START_STOP_ACTION_ON
else:
_LOGGER.debug(
@@ -257,10 +233,5 @@ class AutoStartStopDetectionAlgorithm:
"""Get the level value"""
return self._level
@property
def last_switch_date(self) -> datetime | None:
"""Get the last of the last switch"""
return self._last_switch_date
def __str__(self) -> str:
return f"AutoStartStopDetectionAlgorithm-{self._vtherm_name}"

View File

@@ -9,6 +9,7 @@ from datetime import timedelta, datetime
from types import MappingProxyType
from typing import Any, TypeVar, Generic
from homeassistant.util import dt as dt_util
from homeassistant.core import (
HomeAssistant,
callback,
@@ -18,10 +19,7 @@ from homeassistant.core import (
)
from homeassistant.components.climate import ClimateEntity
from homeassistant.helpers.restore_state import (
RestoreEntity,
async_get as restore_async_get,
)
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.entity import Entity
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
@@ -64,7 +62,72 @@ from homeassistant.const import (
STATE_NOT_HOME,
)
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .const import (
DOMAIN,
DEVICE_MANUFACTURER,
CONF_POWER_SENSOR,
CONF_TEMP_SENSOR,
CONF_LAST_SEEN_TEMP_SENSOR,
CONF_EXTERNAL_TEMP_SENSOR,
CONF_MAX_POWER_SENSOR,
CONF_WINDOW_SENSOR,
CONF_WINDOW_DELAY,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD,
CONF_WINDOW_AUTO_OPEN_THRESHOLD,
CONF_WINDOW_AUTO_MAX_DURATION,
CONF_MOTION_SENSOR,
CONF_MOTION_DELAY,
CONF_MOTION_OFF_DELAY,
CONF_MOTION_PRESET,
CONF_NO_MOTION_PRESET,
CONF_DEVICE_POWER,
CONF_PRESETS,
# CONF_PRESETS_AWAY,
# CONF_PRESETS_WITH_AC,
# CONF_PRESETS_AWAY_WITH_AC,
CONF_CYCLE_MIN,
CONF_PROP_FUNCTION,
CONF_TPI_COEF_INT,
CONF_TPI_COEF_EXT,
CONF_PRESENCE_SENSOR,
CONF_PRESET_POWER,
SUPPORT_FLAGS,
PRESET_FROST_PROTECTION,
PRESET_POWER,
PRESET_SECURITY,
PROPORTIONAL_FUNCTION_TPI,
PRESET_AWAY_SUFFIX,
CONF_SECURITY_DELAY_MIN,
CONF_SECURITY_MIN_ON_PERCENT,
CONF_SECURITY_DEFAULT_ON_PERCENT,
DEFAULT_SECURITY_MIN_ON_PERCENT,
DEFAULT_SECURITY_DEFAULT_ON_PERCENT,
CONF_MINIMAL_ACTIVATION_DELAY,
CONF_USE_MAIN_CENTRAL_CONFIG,
CONF_USE_TPI_CENTRAL_CONFIG,
CONF_USE_PRESETS_CENTRAL_CONFIG,
CONF_USE_WINDOW_CENTRAL_CONFIG,
CONF_USE_MOTION_CENTRAL_CONFIG,
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,
CONF_AC_MODE,
EventType,
ATTR_MEAN_POWER_CYCLE,
ATTR_TOTAL_ENERGY,
PRESET_AC_SUFFIX,
DEFAULT_SHORT_EMA_PARAMS,
CENTRAL_MODE_AUTO,
CENTRAL_MODE_STOPPED,
CENTRAL_MODE_HEAT_ONLY,
CENTRAL_MODE_COOL_ONLY,
CENTRAL_MODE_FROST_PROTECTION,
send_vtherm_event,
)
from .config_schema import * # pylint: disable=wildcard-import, unused-wildcard-import
@@ -79,6 +142,13 @@ _LOGGER = logging.getLogger(__name__)
ConfigData = MappingProxyType[str, Any]
T = TypeVar("T", bound=UnderlyingEntity)
def get_tz(hass: HomeAssistant):
"""Get the current timezone"""
return dt_util.get_time_zone(hass.config.time_zone)
class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
"""Representation of a base class for all Versatile Thermostat device."""
@@ -127,12 +197,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
"max_power_sensor_entity_id",
"temperature_unit",
"is_device_active",
"nb_device_actives",
"target_temperature_step",
"is_used_by_central_boiler",
"temperature_slope",
"max_on_percent",
"have_valve_regulation",
}
)
)
@@ -196,7 +262,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self._attr_translation_key = "versatile_thermostat"
self._total_energy = None
_LOGGER.debug("%s - _init_ resetting energy to None", self)
# because energy of climate is calculated in the thermostat we have to keep that here and not in underlying entity
self._underlying_climate_start_hvac_action_date = None
@@ -238,8 +303,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self._use_central_config_temperature = False
self._hvac_off_reason: HVAC_OFF_REASONS | None = None
self.post_init(entry_infos)
def clean_central_config_doublon(
@@ -454,8 +517,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
else DEFAULT_SECURITY_DEFAULT_ON_PERCENT
)
self._minimal_activation_delay = entry_infos.get(CONF_MINIMAL_ACTIVATION_DELAY)
self._last_temperature_measure = self.now
self._last_ext_temperature_measure = self.now
self._last_temperature_measure = datetime.now(tz=self._current_tz)
self._last_ext_temperature_measure = datetime.now(tz=self._current_tz)
self._security_state = False
# Initiate the ProportionalAlgorithm
@@ -469,7 +532,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self._presence_state = None
self._total_energy = None
_LOGGER.debug("%s - post_init_ resetting energy to None", self)
# Read the parameter from configuration.yaml if it exists
short_ema_params = DEFAULT_SHORT_EMA_PARAMS
@@ -498,8 +560,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
entry_infos.get(CONF_WINDOW_ACTION) or CONF_WINDOW_TURN_OFF
)
self._max_on_percent = api.max_on_percent
_LOGGER.debug(
"%s - Creation of a new VersatileThermostat entity: unique_id=%s",
self,
@@ -587,24 +647,14 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
# issue 428. Link to others entities will start at link
# await self.async_startup()
async def async_will_remove_from_hass(self):
"""Try to force backup of entity"""
_LOGGER.debug(
"%s - force write before remove. Energy is %s", self, self.total_energy
)
# Force dump in background
await restore_async_get(self.hass).async_dump_states()
def remove_thermostat(self):
"""Called when the thermostat will be removed"""
_LOGGER.info("%s - Removing thermostat", self)
for under in self._underlyings:
under.remove_entity()
async def async_startup(self, central_configuration):
"""Triggered on startup, used to get old state and set internal states accordingly. This is triggered by
VTherm API"""
"""Triggered on startup, used to get old state and set internal states accordingly"""
_LOGGER.debug("%s - Calling async_startup", self)
_LOGGER.debug("%s - Calling async_startup_internal", self)
@@ -798,29 +848,18 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
else:
self._attr_preset_mode = PRESET_NONE
# Restore old hvac_off_reason
self._hvac_off_reason = old_state.attributes.get(HVAC_OFF_REASON_NAME, None)
if old_state.state in [
HVACMode.OFF,
HVACMode.HEAT,
HVACMode.COOL,
]:
self._hvac_mode = old_state.state
# restpre also saved info so that window detection will work
self._saved_hvac_mode = old_state.attributes.get("saved_hvac_mode", None)
self._saved_preset_mode = old_state.attributes.get(
"saved_preset_mode", None
)
else:
if not self._hvac_mode:
self._hvac_mode = HVACMode.OFF
old_total_energy = old_state.attributes.get(ATTR_TOTAL_ENERGY)
self._total_energy = old_total_energy if old_total_energy is not None else 0
_LOGGER.debug(
"%s - get_my_previous_state restored energy is %s",
self,
self._total_energy,
)
self._total_energy = old_total_energy if old_total_energy else 0
self.restore_specific_previous_state(old_state)
else:
@@ -834,20 +873,13 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
"No previously saved temperature, setting to %s", self._target_temp
)
self._total_energy = 0
_LOGGER.debug(
"%s - get_my_previous_state no previous state energy is %s",
self,
self._total_energy,
)
if not self._hvac_mode:
self._hvac_mode = HVACMode.OFF
if not self.is_on and self.hvac_off_reason is None:
self.set_hvac_off_reason(HVAC_OFF_REASON_MANUAL)
self._saved_target_temp = self._target_temp
# Set default state to off
if not self._hvac_mode:
self._hvac_mode = HVACMode.OFF
self.send_event(EventType.PRESET_EVENT, {"preset": self._attr_preset_mode})
self.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": self._hvac_mode})
@@ -955,6 +987,16 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
@property
def hvac_mode(self) -> HVACMode | None:
"""Return current operation."""
# Issue #114 - returns my current hvac_mode and not the underlying hvac_mode which could be different
# delta will be managed by climate_state_change event.
# if self.is_over_climate:
# if one not OFF -> return it
# else OFF
# for under in self._underlyings:
# if (mode := under.hvac_mode) not in [HVACMode.OFF]
# return mode
# return HVACMode.OFF
return self._hvac_mode
@property
@@ -996,15 +1038,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
return True
return False
@property
def nb_device_actives(self) -> int:
"""Calculate the number of active devices"""
ret = 0
for under in self._underlyings:
if under.is_device_active:
ret += 1
return ret
@property
def current_temperature(self) -> float | None:
"""Return the sensor temperature."""
@@ -1132,11 +1165,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
"""Returns the underlying entities"""
return self._underlyings
@property
def activable_underlying_entities(self) -> list | None:
"""Returns the activable underlying entities for controling the central boiler"""
return self.underlying_entities
def find_underlying_by_entity_id(self, entity_id: str) -> Entity | None:
"""Get the underlying entity by a entity_id"""
for under in self._underlyings:
@@ -1165,13 +1193,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
"""True if this VTHerm uses the central configuration temperature"""
return self._use_central_config_temperature
@property
def hvac_off_reason(self) -> HVAC_OFF_REASONS:
"""Returns the reason of the last switch to HVAC_OFF
This is useful for features that turns off the VTherm like
window detection or auto-start-stop"""
return self._hvac_off_reason
def underlying_entity_id(self, index=0) -> str | None:
"""The climate_entity_id. Added for retrocompatibility reason"""
if index < self.nb_underlying_entities:
@@ -1213,24 +1234,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
if hvac_mode is None:
return
def save_state():
self.reset_last_change_time()
self.update_custom_attributes()
self.async_write_ha_state()
self.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": self._hvac_mode})
# If we already are in OFF, the manual OFF should just overwrite the reason and saved_hvac_mode
if self._hvac_mode == HVACMode.OFF and hvac_mode == HVACMode.OFF:
_LOGGER.info(
"%s - already in OFF. Change the reason to MANUAL and erase the saved_havc_mode"
)
self._hvac_off_reason = HVAC_OFF_REASON_MANUAL
self._saved_hvac_mode = HVACMode.OFF
save_state()
return
self._hvac_mode = hvac_mode
# Delegate to all underlying
@@ -1253,10 +1256,11 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
# Ensure we update the current operation after changing the mode
self.reset_last_temperature_time()
if self._hvac_mode != HVACMode.OFF:
self.set_hvac_off_reason(None)
self.reset_last_change_time()
save_state()
self.update_custom_attributes()
self.async_write_ha_state()
self.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": self._hvac_mode})
@overrides
async def async_set_preset_mode(
@@ -1350,7 +1354,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self, old_preset_mode: str | None = None
): # pylint: disable=unused-argument
"""Reset to now the last change time"""
self._last_change_time = self.now
self._last_change_time = datetime.now(tz=self._current_tz)
_LOGGER.debug("%s - last_change_time is now %s", self, self._last_change_time)
def reset_last_temperature_time(self, old_preset_mode: str | None = None):
@@ -1360,7 +1364,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
and old_preset_mode not in HIDDEN_PRESETS
):
self._last_temperature_measure = self._last_ext_temperature_measure = (
self.now
datetime.now(tz=self._current_tz)
)
def find_preset_temp(self, preset_mode: str):
@@ -1393,10 +1397,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
)
if motion_preset in self._presets:
if self._presence_on and self.presence_state in [STATE_OFF, None]:
return self._presets_away[motion_preset + PRESET_AWAY_SUFFIX]
else:
return self._presets[motion_preset]
return self._presets[motion_preset]
else:
return None
else:
@@ -1466,16 +1467,16 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
"""Extract the last_changed state from State or return now if not available"""
return (
state.last_changed.astimezone(self._current_tz)
if isinstance(state.last_changed, datetime)
else self.now
if state.last_changed is not None
else datetime.now(tz=self._current_tz)
)
def get_last_updated_date_or_now(self, state: State) -> datetime:
"""Extract the last_changed state from State or return now if not available"""
return (
state.last_updated.astimezone(self._current_tz)
if isinstance(state.last_updated, datetime)
else self.now
if state.last_updated is not None
else datetime.now(tz=self._current_tz)
)
@callback
@@ -1759,19 +1760,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
for under in self._underlyings:
await under.check_initial_state(self._hvac_mode)
# Prevent from starting a VTherm if window is open
if (
self.is_window_auto_enabled
and self._window_sensor_entity_id is not None
and self._hass.states.is_state(self._window_sensor_entity_id, STATE_ON)
and self.is_on
and self.window_action == CONF_WINDOW_TURN_OFF
):
_LOGGER.info("%s - the window is open. Prevent starting the VTherm")
self._window_auto_state = True
self.save_hvac_mode()
await self.async_set_hvac_mode(HVACMode.OFF)
# Starts the initial control loop (don't wait for an update of temperature)
await self.async_control_heating(force=True)
@@ -1917,12 +1905,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
STATE_NOT_HOME,
):
return
if self._attr_preset_mode not in [
PRESET_BOOST,
PRESET_COMFORT,
PRESET_ECO,
PRESET_ACTIVITY,
]:
if self._attr_preset_mode not in [PRESET_BOOST, PRESET_COMFORT, PRESET_ECO]:
return
new_temp = self.find_preset_temp(self.preset_mode)
@@ -2012,7 +1995,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
if in_cycle:
slope = self._window_auto_algo.check_age_last_measurement(
temperature=self._ema_temp,
datetime_now=self.now,
datetime_now=datetime.now(get_tz(self._hass)),
)
else:
slope = self._window_auto_algo.add_temp_measurement(
@@ -2113,10 +2096,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self._hvac_mode,
)
def set_hvac_off_reason(self, hvac_off_reason: HVAC_OFF_REASONS):
"""Set the reason of hvac_off"""
self._hvac_off_reason = hvac_off_reason
async def restore_hvac_mode(self, need_control_heating=False):
"""Restore a previous hvac_mod"""
await self.async_set_hvac_mode(self._saved_hvac_mode, need_control_heating)
@@ -2248,34 +2227,27 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
if self.window_state is not STATE_ON and not first_init:
await self.restore_hvac_mode()
await self.restore_preset_mode()
elif self.window_state is STATE_ON and self.hvac_mode == HVACMode.OFF:
# do not restore but mark the reason of off with window detection
self.set_hvac_off_reason(HVAC_OFF_REASON_WINDOW_DETECTION)
return
if old_central_mode == CENTRAL_MODE_AUTO and self.window_state is not STATE_ON:
save_all()
if new_central_mode == CENTRAL_MODE_STOPPED:
if self.hvac_mode != HVACMode.OFF:
self.set_hvac_off_reason(HVAC_OFF_REASON_MANUAL)
await self.async_set_hvac_mode(HVACMode.OFF)
await self.async_set_hvac_mode(HVACMode.OFF)
return
if new_central_mode == CENTRAL_MODE_COOL_ONLY:
if HVACMode.COOL in self.hvac_modes:
await self.async_set_hvac_mode(HVACMode.COOL)
else:
self.set_hvac_off_reason(HVAC_OFF_REASON_MANUAL)
await self.async_set_hvac_mode(HVACMode.OFF)
return
if new_central_mode == CENTRAL_MODE_HEAT_ONLY:
if HVACMode.HEAT in self.hvac_modes:
await self.async_set_hvac_mode(HVACMode.HEAT)
# if not already off
elif self.hvac_mode != HVACMode.OFF:
self.set_hvac_off_reason(HVAC_OFF_REASON_MANUAL)
else:
await self.async_set_hvac_mode(HVACMode.OFF)
return
@@ -2289,7 +2261,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
PRESET_FROST_PROTECTION, overwrite_saved_preset=False
)
else:
self.set_hvac_off_reason(HVAC_OFF_REASON_MANUAL)
await self.async_set_hvac_mode(HVACMode.OFF)
return
@@ -2300,11 +2271,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
@property
def now(self) -> datetime:
"""Get now. The local datetime or the overloaded _set_now date"""
return self._now if self._now is not None else NowClass.get_now(self._hass)
return self._now if self._now is not None else datetime.now(self._current_tz)
async def check_safety(self) -> bool:
"""Check if last temperature date is too long"""
now = self.now
delta_temp = (
now - self._last_temperature_measure.replace(tzinfo=self._current_tz)
@@ -2470,27 +2440,17 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
"""Change the window detection state.
new_state is on if an open window have been detected or off else
"""
if new_state is False:
if not new_state:
_LOGGER.info(
"%s - Window is closed. Restoring hvac_mode '%s' if stopped by window detection or temperature %s",
"%s - Window is closed. Restoring hvac_mode '%s' if central_mode is not STOPPED",
self,
self._saved_hvac_mode,
self._saved_target_temp,
)
if self._window_action in [CONF_WINDOW_FROST_TEMP, CONF_WINDOW_ECO_TEMP]:
await self._async_internal_set_temperature(self._saved_target_temp)
# default to TURN_OFF
elif self._window_action in [CONF_WINDOW_TURN_OFF]:
if (
self.last_central_mode != CENTRAL_MODE_STOPPED
and self.hvac_off_reason == HVAC_OFF_REASON_WINDOW_DETECTION
):
self.set_hvac_off_reason(None)
await self.restore_hvac_mode(True)
elif self._window_action in [CONF_WINDOW_FAN_ONLY]:
elif self._window_action in [CONF_WINDOW_TURN_OFF, CONF_WINDOW_FAN_ONLY]:
if self.last_central_mode != CENTRAL_MODE_STOPPED:
self.set_hvac_off_reason(None)
await self.restore_hvac_mode(True)
else:
_LOGGER.error(
@@ -2500,14 +2460,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
)
else:
_LOGGER.info(
"%s - Window is open. Apply window action %s", self, self._window_action
"%s - Window is open. Set hvac_mode to '%s'", self, HVACMode.OFF
)
if self._window_action == CONF_WINDOW_TURN_OFF and not self.is_on:
_LOGGER.debug(
"%s is already off. Forget turning off VTherm due to window detection"
)
return
if self.last_central_mode in [CENTRAL_MODE_AUTO, None]:
if self._window_action in [CONF_WINDOW_TURN_OFF, CONF_WINDOW_FAN_ONLY]:
self.save_hvac_mode()
@@ -2537,7 +2491,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
self.find_preset_temp(PRESET_ECO)
)
else: # default is to turn_off
self.set_hvac_off_reason(HVAC_OFF_REASON_WINDOW_DETECTION)
await self.async_set_hvac_mode(HVACMode.OFF)
async def async_control_heating(self, force=False, _=None) -> bool:
@@ -2672,40 +2625,16 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
"device_power": self._device_power,
ATTR_MEAN_POWER_CYCLE: self.mean_cycle_power,
ATTR_TOTAL_ENERGY: self.total_energy,
"last_update_datetime": self.now.isoformat(),
"last_update_datetime": datetime.now()
.astimezone(self._current_tz)
.isoformat(),
"timezone": str(self._current_tz),
"temperature_unit": self.temperature_unit,
"is_device_active": self.is_device_active,
"nb_device_actives": self.nb_device_actives,
"ema_temp": self._ema_temp,
"is_used_by_central_boiler": self.is_used_by_central_boiler,
"temperature_slope": round(self.last_temperature_slope or 0, 3),
"hvac_off_reason": self.hvac_off_reason,
"max_on_percent": self._max_on_percent,
"have_valve_regulation": self.have_valve_regulation,
}
_LOGGER.debug(
"%s - update_custom_attributes saved energy is %s",
self,
self.total_energy,
)
@overrides
def async_write_ha_state(self):
"""overrides to have log"""
_LOGGER.debug(
"%s - async_write_ha_state written state energy is %s",
self,
self._total_energy,
)
return super().async_write_ha_state()
@property
def have_valve_regulation(self) -> bool:
"""True if the Thermostat is regulated by valve"""
return False
@callback
def async_registry_entry_updated(self):
"""update the entity if the config entry have been updated

View File

@@ -108,7 +108,7 @@ class SecurityBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on
self._attr_is_on = self.my_climate.security_state is True
@@ -147,7 +147,7 @@ class OverpoweringBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on
self._attr_is_on = self.my_climate.overpowering_state is True
@@ -186,7 +186,7 @@ class WindowBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on
# Issue 120 - only take defined presence value
@@ -236,7 +236,7 @@ class MotionBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on
# Issue 120 - only take defined presence value
if self.my_climate.motion_state in [STATE_ON, STATE_OFF]:
@@ -277,7 +277,7 @@ class PresenceBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on
# Issue 120 - only take defined presence value
if self.my_climate.presence_state in [STATE_ON, STATE_OFF]:
@@ -317,7 +317,7 @@ class WindowByPassBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on
if self.my_climate.window_bypass_state in [True, False]:
self._attr_is_on = self.my_climate.window_bypass_state

View File

@@ -22,12 +22,26 @@ from homeassistant.const import (
STATE_NOT_HOME,
)
from .const import * # pylint: disable=wildcard-import,unused-wildcard-import
from .const import (
DOMAIN,
PLATFORMS,
CONF_PRESETS_WITH_AC,
SERVICE_SET_PRESENCE,
SERVICE_SET_PRESET_TEMPERATURE,
SERVICE_SET_SECURITY,
SERVICE_SET_WINDOW_BYPASS,
SERVICE_SET_AUTO_REGULATION_MODE,
SERVICE_SET_AUTO_FAN_MODE,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_VALVE,
CONF_THERMOSTAT_CENTRAL_CONFIG,
)
from .thermostat_switch import ThermostatOverSwitch
from .thermostat_climate import ThermostatOverClimate
from .thermostat_valve import ThermostatOverValve
from .thermostat_climate_valve import ThermostatOverClimateValve
_LOGGER = logging.getLogger(__name__)
@@ -46,9 +60,6 @@ async def async_setup_entry(
unique_id = entry.entry_id
name = entry.data.get(CONF_NAME)
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
have_valve_regulation = (
entry.data.get(CONF_AUTO_REGULATION_MODE) == CONF_AUTO_REGULATION_VALVE
)
if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
return
@@ -58,10 +69,7 @@ async def async_setup_entry(
if vt_type == CONF_THERMOSTAT_SWITCH:
entity = ThermostatOverSwitch(hass, unique_id, name, entry.data)
elif vt_type == CONF_THERMOSTAT_CLIMATE:
if have_valve_regulation is True:
entity = ThermostatOverClimateValve(hass, unique_id, name, entry.data)
else:
entity = ThermostatOverClimate(hass, unique_id, name, entry.data)
entity = ThermostatOverClimate(hass, unique_id, name, entry.data)
elif vt_type == CONF_THERMOSTAT_VALVE:
entity = ThermostatOverValve(hass, unique_id, name, entry.data)
else:

View File

@@ -3,20 +3,39 @@
# pylint: disable=line-too-long
import logging
from datetime import timedelta
from datetime import timedelta, datetime
from homeassistant.core import HomeAssistant, callback, Event
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.helpers.event import async_track_state_change_event, async_call_later
from homeassistant.util import dt as dt_util
from .base_thermostat import BaseThermostat
from .const import DOMAIN, DEVICE_MANUFACTURER, ServiceConfigurationError
_LOGGER = logging.getLogger(__name__)
def get_tz(hass: HomeAssistant):
"""Get the current timezone"""
return dt_util.get_time_zone(hass.config.time_zone)
class NowClass:
"""For testing purpose only"""
@staticmethod
def get_now(hass: HomeAssistant) -> datetime:
"""A test function to get the now.
For testing purpose this method can be overriden to get a specific
timestamp.
"""
return datetime.now(get_tz(hass))
def round_to_nearest(n: float, x: float) -> float:
"""Round a number to the nearest x (which should be decimal but not null)
Example:

View File

@@ -29,6 +29,27 @@ COMES_FROM = "comes_from"
_LOGGER = logging.getLogger(__name__)
# Not used but can be useful in other context
# def schema_defaults(schema, **defaults):
# """Create a new schema with default values filled in."""
# copy = schema.extend({})
# for field, field_type in copy.schema.items():
# if isinstance(field_type, vol.In):
# value = None
#
# if value in field_type.container:
# # field.default = vol.default_factory(value)
# field.description = {"suggested_value": value}
# continue
#
# if field.schema in defaults:
# # field.default = vol.default_factory(defaults[field])
# field.description = {"suggested_value": defaults[field]}
# return copy
#
def add_suggested_values_to_schema(
data_schema: vol.Schema, suggested_values: Mapping[str, Any]
) -> vol.Schema:
@@ -56,6 +77,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
VERSION = CONFIG_VERSION
MINOR_VERSION = CONFIG_MINOR_VERSION
_infos: dict
_placeholders = {
CONF_NAME: "",
}
@@ -63,7 +85,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
def __init__(self, infos) -> None:
super().__init__()
_LOGGER.debug("CTOR BaseConfigFlow infos: %s", infos)
self._infos: dict = infos
self._infos = infos
# VTherm API should have been initialized before arriving here
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
@@ -72,8 +94,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
else:
self._central_config = None
self._init_central_config_flags(infos)
self._init_feature_flags(infos)
self._init_central_config_flags(infos)
def _init_feature_flags(self, _):
"""Fix features selection depending to infos"""
@@ -87,17 +109,17 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
or self._infos.get(CONF_WINDOW_AUTO_OPEN_THRESHOLD) is not None
)
self._infos[CONF_USE_MOTION_FEATURE] = self._infos.get(
CONF_USE_MOTION_FEATURE, False
CONF_USE_MOTION_FEATURE
) and (self._infos.get(CONF_MOTION_SENSOR) is not None or is_central_config)
self._infos[CONF_USE_POWER_FEATURE] = self._infos.get(
CONF_USE_POWER_CENTRAL_CONFIG, False
CONF_USE_POWER_CENTRAL_CONFIG
) or (
self._infos.get(CONF_POWER_SENSOR) is not None
and self._infos.get(CONF_MAX_POWER_SENSOR) is not None
)
self._infos[CONF_USE_PRESENCE_FEATURE] = (
self._infos.get(CONF_USE_PRESENCE_CENTRAL_CONFIG, False)
self._infos.get(CONF_USE_PRESENCE_CENTRAL_CONFIG)
or self._infos.get(CONF_PRESENCE_SENSOR) is not None
)
@@ -107,7 +129,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
)
self._infos[CONF_USE_AUTO_START_STOP_FEATURE] = (
self._infos.get(CONF_USE_AUTO_START_STOP_FEATURE, False) is True
self._infos.get(CONF_USE_AUTO_START_STOP_FEATURE) is True
and self._infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE
)
@@ -123,62 +145,19 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
CONF_USE_PRESETS_CENTRAL_CONFIG,
CONF_USE_PRESENCE_CENTRAL_CONFIG,
CONF_USE_ADVANCED_CENTRAL_CONFIG,
CONF_USE_CENTRAL_MODE,
):
if not is_empty:
current_config = self._infos.get(config, None)
self._infos[config] = self._central_config is not None and (
current_config is True or current_config is None
self._infos[config] = current_config is True or (
current_config is None and self._central_config is not None
)
# self._infos[config] = current_config is True or (
# current_config is None and self._central_config is not None
# )
else:
self._infos[config] = self._central_config is not None
if COMES_FROM in self._infos:
del self._infos[COMES_FROM]
def is_valve_regulation_selected(self, infos) -> bool:
"""True of the valve regulation mode is selected"""
return infos.get(CONF_AUTO_REGULATION_MODE, None) == CONF_AUTO_REGULATION_VALVE
def check_valve_regulation_nb_entities(self, data: dict, step_id=None) -> bool:
"""Check the number of entities for Valve regulation"""
if step_id not in ["type", "valve_regulation", "check_complete"]:
return True
underlyings_to_check = data if step_id == "type" else self._infos
# underlyings_to_check = self._infos # data if step_id == "type" else self._infos
regulation_infos_to_check = (
data if step_id == "valve_regulation" else self._infos
)
ret = True
if (
self.is_valve_regulation_selected(underlyings_to_check)
and step_id != "type"
):
nb_unders = len(underlyings_to_check.get(CONF_UNDERLYING_LIST))
nb_offset = len(
regulation_infos_to_check.get(CONF_OFFSET_CALIBRATION_LIST, [])
)
nb_opening = len(
regulation_infos_to_check.get(CONF_OPENING_DEGREE_LIST, [])
)
nb_closing = len(
regulation_infos_to_check.get(CONF_CLOSING_DEGREE_LIST, [])
)
if (
nb_unders != nb_opening
or (nb_unders != nb_offset and nb_offset > 0)
or (nb_unders != nb_closing and nb_closing > 0)
):
ret = False
return ret
async def validate_input(self, data: dict, step_id) -> None:
async def validate_input(self, data: dict) -> None:
"""Validate the user input allows us to connect.
Data has the keys from STEP_*_DATA_SCHEMA with values provided by the user.
@@ -186,7 +165,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
# check the heater_entity_id
for conf in [
CONF_UNDERLYING_LIST,
CONF_HEATER,
CONF_TEMP_SENSOR,
CONF_EXTERNAL_TEMP_SENSOR,
CONF_WINDOW_SENSOR,
@@ -194,20 +173,15 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
CONF_POWER_SENSOR,
CONF_MAX_POWER_SENSOR,
CONF_PRESENCE_SENSOR,
CONF_OFFSET_CALIBRATION_LIST,
CONF_OPENING_DEGREE_LIST,
CONF_CLOSING_DEGREE_LIST,
CONF_CLIMATE,
]:
d = data.get(conf, None) # pylint: disable=invalid-name
if not isinstance(d, list):
d = [d]
for e in d:
if e is not None and self.hass.states.get(e) is None:
_LOGGER.error(
"Entity id %s doesn't have any state. We cannot use it in the Versatile Thermostat configuration", # pylint: disable=line-too-long
e,
)
raise UnknownEntity(conf)
if d is not None and self.hass.states.get(d) is None:
_LOGGER.error(
"Entity id %s doesn't have any state. We cannot use it in the Versatile Thermostat configuration", # pylint: disable=line-too-long
d,
)
raise UnknownEntity(conf)
# Check that only one window feature is used
ws = self._infos.get(CONF_WINDOW_SENSOR) # pylint: disable=invalid-name
@@ -233,9 +207,6 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
CONF_USE_PRESENCE_CENTRAL_CONFIG,
CONF_USE_PRESETS_CENTRAL_CONFIG,
CONF_USE_ADVANCED_CENTRAL_CONFIG,
CONF_USE_CENTRAL_MODE,
# CONF_USE_CENTRAL_BOILER_FEATURE, this is for Central Config
CONF_USED_BY_CENTRAL_BOILER,
]:
if data.get(conf) is True:
_LOGGER.error(
@@ -254,11 +225,6 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
except ServiceConfigurationError as err:
raise ServiceConfigurationError(conf) from err
# Check that the number of offet_calibration and opening_degree and closing_degree are equals
# to the number of underlying entities
if not self.check_valve_regulation_nb_entities(data, step_id):
raise ValveRegulationNbEntitiesIncorrect()
def check_config_complete(self, infos) -> bool:
"""True if the config is now complete (ie all mandatory attributes are set)"""
is_central_config = (
@@ -304,8 +270,21 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
):
return False
if infos.get(CONF_UNDERLYING_LIST, None) is not None and not infos.get(
CONF_UNDERLYING_LIST, None
if (
infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_SWITCH
and infos.get(CONF_HEATER, None) is None
):
return False
if (
infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE
and infos.get(CONF_CLIMATE, None) is None
):
return False
if (
infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE
and infos.get(CONF_VALVE, None) is None
):
return False
@@ -338,25 +317,6 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
):
return False
if (
infos.get(CONF_PROP_FUNCTION, None) == PROPORTIONAL_FUNCTION_TPI
and infos.get(CONF_USE_TPI_CENTRAL_CONFIG, False) is False
and (
infos.get(CONF_TPI_COEF_INT, None) is None
or infos.get(CONF_TPI_COEF_EXT) is None
)
):
return False
if (
infos.get(CONF_USE_PRESETS_CENTRAL_CONFIG, False) is True
and self._central_config is None
):
return False
if not self.check_valve_regulation_nb_entities(infos, "check_complete"):
return False
return True
def merge_user_input(self, data_schema: vol.Schema, user_input: dict):
@@ -386,7 +346,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
if user_input is not None:
defaults.update(user_input or {})
try:
await self.validate_input(user_input, step_id)
await self.validate_input(user_input)
except UnknownEntity as err:
errors[str(err)] = "unknown_entity"
except WindowOpenDetectionMethod as err:
@@ -397,8 +357,6 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
errors[str(err)] = "service_configuration_format"
except ConfigurationNotCompleteError as err:
errors["base"] = "configuration_not_complete"
except ValveRegulationNbEntitiesIncorrect as err:
errors["base"] = "valve_regulation_nb_entities_incorrect"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
@@ -450,7 +408,6 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
if (
self._infos.get(CONF_PROP_FUNCTION) == PROPORTIONAL_FUNCTION_TPI
or is_central_config
or self.is_valve_regulation_selected(self._infos)
):
menu_options.append("tpi")
@@ -486,9 +443,6 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
]:
menu_options.append("auto_start_stop")
if self.is_valve_regulation_selected(self._infos):
menu_options.append("valve_regulation")
menu_options.append("advanced")
if self.check_config_complete(self._infos):
@@ -558,23 +512,6 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
"""Handle the Type flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_type user_input=%s", user_input)
if (
self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CLIMATE
and user_input is not None
and not self.is_valve_regulation_selected(user_input)
):
# Remove TPI info
for key in [
CONF_PROP_FUNCTION,
CONF_TPI_COEF_INT,
CONF_TPI_COEF_EXT,
CONF_OFFSET_CALIBRATION_LIST,
CONF_OPENING_DEGREE_LIST,
CONF_CLOSING_DEGREE_LIST,
]:
if self._infos.get(key):
del self._infos[key]
if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_SWITCH:
return await self.generic_step(
"type", STEP_THERMOSTAT_SWITCH, user_input, self.async_step_menu
@@ -618,22 +555,6 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
return await self.generic_step("auto_start_stop", schema, user_input, next_step)
async def async_step_valve_regulation(
self, user_input: dict | None = None
) -> FlowResult:
"""Handle the valve regulation configuration step"""
_LOGGER.debug(
"Into ConfigFlow.async_step_valve_regulation user_input=%s", user_input
)
schema = STEP_VALVE_REGULATION
self._infos[COMES_FROM] = None
next_step = self.async_step_menu
return await self.generic_step(
"valve_regulation", schema, user_input, next_step
)
async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult:
"""Handle the TPI flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_tpi user_input=%s", user_input)

View File

@@ -119,10 +119,17 @@ STEP_CENTRAL_BOILER_SCHEMA = vol.Schema(
STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_UNDERLYING_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN], multiple=True
),
vol.Required(CONF_HEATER): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
),
vol.Optional(CONF_HEATER_2): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
),
vol.Optional(CONF_HEATER_3): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
),
vol.Optional(CONF_HEATER_4): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
),
vol.Optional(CONF_HEATER_KEEP_ALIVE): cv.positive_int,
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
@@ -137,8 +144,17 @@ STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_UNDERLYING_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN, multiple=True),
vol.Required(CONF_CLIMATE): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
),
vol.Optional(CONF_CLIMATE_2): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
),
vol.Optional(CONF_CLIMATE_3): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
),
vol.Optional(CONF_CLIMATE_4): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
),
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
vol.Optional(
@@ -167,10 +183,17 @@ STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name
STEP_THERMOSTAT_VALVE = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_UNDERLYING_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
),
vol.Required(CONF_VALVE): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Optional(CONF_VALVE_2): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Optional(CONF_VALVE_3): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Optional(CONF_VALVE_4): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
[
@@ -197,31 +220,6 @@ STEP_AUTO_START_STOP = vol.Schema( # pylint: disable=invalid-name
}
)
STEP_VALVE_REGULATION = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_OPENING_DEGREE_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
),
),
vol.Optional(CONF_OFFSET_CALIBRATION_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
),
),
vol.Optional(CONF_CLOSING_DEGREE_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
),
),
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
[
PROPORTIONAL_FUNCTION_TPI,
]
),
}
)
STEP_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_USE_TPI_CENTRAL_CONFIG, default=True): cv.boolean,
@@ -230,16 +228,8 @@ STEP_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
STEP_CENTRAL_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_TPI_COEF_INT, default=0.6): selector.NumberSelector(
selector.NumberSelectorConfig(
min=0.0, max=1.0, step=0.01, mode=selector.NumberSelectorMode.BOX
)
),
vol.Required(CONF_TPI_COEF_EXT, default=0.01): selector.NumberSelector(
selector.NumberSelectorConfig(
min=0.0, max=1.0, step=0.01, mode=selector.NumberSelectorMode.BOX
)
),
vol.Required(CONF_TPI_COEF_INT, default=0.6): vol.Coerce(float),
vol.Required(CONF_TPI_COEF_EXT, default=0.01): vol.Coerce(float),
}
)

View File

@@ -2,12 +2,9 @@
"""Constants for the Versatile Thermostat integration."""
import logging
import math
from typing import Literal
from datetime import datetime
from enum import Enum
from homeassistant.core import HomeAssistant
from homeassistant.const import CONF_NAME, Platform
from homeassistant.components.climate import (
@@ -19,7 +16,6 @@ from homeassistant.components.climate import (
)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util import dt as dt_util
from .prop_algorithm import (
PROPORTIONAL_FUNCTION_TPI,
@@ -27,8 +23,8 @@ from .prop_algorithm import (
_LOGGER = logging.getLogger(__name__)
CONFIG_VERSION = 2
CONFIG_MINOR_VERSION = 0
CONFIG_VERSION = 1
CONFIG_MINOR_VERSION = 2
PRESET_TEMP_SUFFIX = "_temp"
PRESET_AC_SUFFIX = "_ac"
@@ -59,7 +55,10 @@ PLATFORMS: list[Platform] = [
Platform.SWITCH,
]
CONF_UNDERLYING_LIST = "underlying_entity_ids"
CONF_HEATER = "heater_entity_id"
CONF_HEATER_2 = "heater_entity2_id"
CONF_HEATER_3 = "heater_entity3_id"
CONF_HEATER_4 = "heater_entity4_id"
CONF_HEATER_KEEP_ALIVE = "heater_keep_alive"
CONF_TEMP_SENSOR = "temperature_sensor_entity_id"
CONF_LAST_SEEN_TEMP_SENSOR = "last_seen_temperature_sensor_entity_id"
@@ -91,6 +90,10 @@ CONF_THERMOSTAT_CENTRAL_CONFIG = "thermostat_central_config"
CONF_THERMOSTAT_SWITCH = "thermostat_over_switch"
CONF_THERMOSTAT_CLIMATE = "thermostat_over_climate"
CONF_THERMOSTAT_VALVE = "thermostat_over_valve"
CONF_CLIMATE = "climate_entity_id"
CONF_CLIMATE_2 = "climate_entity2_id"
CONF_CLIMATE_3 = "climate_entity3_id"
CONF_CLIMATE_4 = "climate_entity4_id"
CONF_USE_WINDOW_FEATURE = "use_window_feature"
CONF_USE_MOTION_FEATURE = "use_motion_feature"
CONF_USE_PRESENCE_FEATURE = "use_presence_feature"
@@ -101,9 +104,12 @@ CONF_AC_MODE = "ac_mode"
CONF_WINDOW_AUTO_OPEN_THRESHOLD = "window_auto_open_threshold"
CONF_WINDOW_AUTO_CLOSE_THRESHOLD = "window_auto_close_threshold"
CONF_WINDOW_AUTO_MAX_DURATION = "window_auto_max_duration"
CONF_VALVE = "valve_entity_id"
CONF_VALVE_2 = "valve_entity2_id"
CONF_VALVE_3 = "valve_entity3_id"
CONF_VALVE_4 = "valve_entity4_id"
CONF_AUTO_REGULATION_MODE = "auto_regulation_mode"
CONF_AUTO_REGULATION_NONE = "auto_regulation_none"
CONF_AUTO_REGULATION_VALVE = "auto_regulation_valve"
CONF_AUTO_REGULATION_SLOW = "auto_regulation_slow"
CONF_AUTO_REGULATION_LIGHT = "auto_regulation_light"
CONF_AUTO_REGULATION_MEDIUM = "auto_regulation_medium"
@@ -120,28 +126,10 @@ CONF_AUTO_FAN_MEDIUM = "auto_fan_medium"
CONF_AUTO_FAN_HIGH = "auto_fan_high"
CONF_AUTO_FAN_TURBO = "auto_fan_turbo"
CONF_STEP_TEMPERATURE = "step_temperature"
CONF_OFFSET_CALIBRATION_LIST = "offset_calibration_entity_ids"
CONF_OPENING_DEGREE_LIST = "opening_degree_entity_ids"
CONF_CLOSING_DEGREE_LIST = "closing_degree_entity_ids"
# Deprecated
CONF_HEATER = "heater_entity_id"
CONF_HEATER_2 = "heater_entity2_id"
CONF_HEATER_3 = "heater_entity3_id"
CONF_HEATER_4 = "heater_entity4_id"
CONF_CLIMATE = "climate_entity_id"
CONF_CLIMATE_2 = "climate_entity2_id"
CONF_CLIMATE_3 = "climate_entity3_id"
CONF_CLIMATE_4 = "climate_entity4_id"
CONF_VALVE = "valve_entity_id"
CONF_VALVE_2 = "valve_entity2_id"
CONF_VALVE_3 = "valve_entity3_id"
CONF_VALVE_4 = "valve_entity4_id"
# Global params into configuration.yaml
CONF_SHORT_EMA_PARAMS = "short_ema_params"
CONF_SAFETY_MODE = "safety_mode"
CONF_MAX_ON_PERCENT = "max_on_percent"
CONF_USE_MAIN_CENTRAL_CONFIG = "use_main_central_config"
CONF_USE_TPI_CENTRAL_CONFIG = "use_tpi_central_config"
@@ -180,16 +168,6 @@ TYPE_AUTO_START_STOP_LEVELS = Literal[ # pylint: disable=invalid-name
AUTO_START_STOP_LEVEL_NONE,
]
HVAC_OFF_REASON_NAME = "hvac_off_reason"
HVAC_OFF_REASON_MANUAL = "manual"
HVAC_OFF_REASON_AUTO_START_STOP = "auto_start_stop"
HVAC_OFF_REASON_WINDOW_DETECTION = "window_detection"
HVAC_OFF_REASONS = Literal[ # pylint: disable=invalid-name
HVAC_OFF_REASON_MANUAL,
HVAC_OFF_REASON_AUTO_START_STOP,
HVAC_OFF_REASON_WINDOW_DETECTION,
]
DEFAULT_SHORT_EMA_PARAMS = {
"max_alpha": 0.5,
# In sec
@@ -261,6 +239,10 @@ CONF_PRESETS_AWAY_WITH_AC_VALUES = list(CONF_PRESETS_AWAY_WITH_AC.values())
ALL_CONF = (
[
CONF_NAME,
CONF_HEATER,
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_HEATER_KEEP_ALIVE,
CONF_TEMP_SENSOR,
CONF_EXTERNAL_TEMP_SENSOR,
@@ -290,12 +272,20 @@ ALL_CONF = (
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
CONF_USE_WINDOW_FEATURE,
CONF_USE_MOTION_FEATURE,
CONF_USE_PRESENCE_FEATURE,
CONF_USE_POWER_FEATURE,
CONF_USE_CENTRAL_BOILER_FEATURE,
CONF_AC_MODE,
CONF_VALVE,
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
CONF_AUTO_REGULATION_MODE,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
@@ -329,7 +319,6 @@ CONF_FUNCTIONS = [
CONF_AUTO_REGULATION_MODES = [
CONF_AUTO_REGULATION_NONE,
CONF_AUTO_REGULATION_VALVE,
CONF_AUTO_REGULATION_LIGHT,
CONF_AUTO_REGULATION_MEDIUM,
CONF_AUTO_REGULATION_STRONG,
@@ -364,11 +353,7 @@ CONF_WINDOW_ACTIONS = [
CONF_WINDOW_ECO_TEMP,
]
SUPPORT_FLAGS = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TURN_OFF
| ClimateEntityFeature.TURN_ON
)
SUPPORT_FLAGS = ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
SERVICE_SET_PRESENCE = "set_presence"
SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"
@@ -468,9 +453,9 @@ class RegulationParamVeryStrong:
kp: float = 0.6
ki: float = 0.1
k_ext: float = 0.2
offset_max: float = 8
offset_max: float = 4
stabilization_threshold: float = 0.1
accumulated_error_threshold: float = 80
accumulated_error_threshold: float = 30
class EventType(Enum):
@@ -495,38 +480,6 @@ def send_vtherm_event(hass, event_type: EventType, entity, data: dict):
hass.bus.fire(event_type.value, data)
def get_safe_float(hass, entity_id: str):
"""Get a safe float state value for an entity.
Return None if entity is not available"""
if (
entity_id is None
or not (state := hass.states.get(entity_id))
or state.state == "unknown"
or state.state == "unavailable"
):
return None
float_val = float(state.state)
return None if math.isinf(float_val) or not math.isfinite(float_val) else float_val
def get_tz(hass: HomeAssistant):
"""Get the current timezone"""
return dt_util.get_time_zone(hass.config.time_zone)
class NowClass:
"""For testing purpose only"""
@staticmethod
def get_now(hass: HomeAssistant) -> datetime:
"""A test function to get the now.
For testing purpose this method can be overriden to get a specific
timestamp.
"""
return datetime.now(get_tz(hass))
class UnknownEntity(HomeAssistantError):
"""Error to indicate there is an unknown entity_id given."""
@@ -547,11 +500,6 @@ class ConfigurationNotCompleteError(HomeAssistantError):
"""Error the configuration is not complete"""
class ValveRegulationNbEntitiesIncorrect(HomeAssistantError):
"""Error to indicate there is an error in the configuration of the TRV with valve regulation.
The number of specific entities is incorrect."""
class overrides: # pylint: disable=invalid-name
"""An annotation to inform overrides"""

View File

@@ -1,18 +0,0 @@
{
"entity": {
"climate": {
"versatile_thermostat": {
"state_attributes": {
"preset_mode": {
"state": {
"shedding": "mdi:power-plug-off",
"safety": "mdi:shield-alert",
"none": "mdi:knob",
"frost": "mdi:snowflake"
}
}
}
}
}
}
}

View File

@@ -14,6 +14,6 @@
"quality_scale": "silver",
"requirements": [],
"ssdp": [],
"version": "6.8.3",
"version": "6.5.0",
"zeroconf": []
}

View File

@@ -26,14 +26,20 @@ MIN_NB_POINT = 4 # do not calculate slope until we have enough point
class WindowOpenDetectionAlgorithm:
"""The class that implements the algorithm listed above"""
_alert_threshold: float
_end_alert_threshold: float
_last_slope: float
_last_datetime: datetime
_last_temperature: float
_nb_point: int
def __init__(self, alert_threshold, end_alert_threshold) -> None:
"""Initalize a new algorithm with the both threshold"""
self._alert_threshold: float = alert_threshold
self._end_alert_threshold: float = end_alert_threshold
self._last_slope: float | None = None
self._last_datetime: datetime = None
self._last_temperature: float | None = None
self._nb_point: int = 0
self._alert_threshold = alert_threshold
self._end_alert_threshold = end_alert_threshold
self._last_slope = None
self._last_datetime = None
self._nb_point = 0
def check_age_last_measurement(self, temperature, datetime_now) -> float:
""" " Check if last measurement is old and add

View File

@@ -31,7 +31,6 @@ class PropAlgorithm:
cycle_min: int,
minimal_activation_delay: int,
vtherm_entity_id: str = None,
max_on_percent: float = None,
) -> None:
"""Initialisation of the Proportional Algorithm"""
_LOGGER.debug(
@@ -79,7 +78,6 @@ class PropAlgorithm:
self._off_time_sec = self._cycle_min * 60
self._security = False
self._default_on_percent = 0
self._max_on_percent = max_on_percent
def calculate(
self,
@@ -163,15 +161,6 @@ class PropAlgorithm:
)
self._on_percent = self._calculated_on_percent
if self._max_on_percent is not None and self._on_percent > self._max_on_percent:
_LOGGER.debug(
"%s - Heating period clamped to %s (instead of %s) due to max_on_percent setting.",
self._vtherm_entity_id,
self._max_on_percent,
self._on_percent,
)
self._on_percent = self._max_on_percent
self._on_time_sec = self._on_percent * self._cycle_min * 60
# Do not heat for less than xx sec

View File

@@ -3,7 +3,7 @@
import logging
import math
from homeassistant.core import HomeAssistant, callback, Event, CoreState, State
from homeassistant.core import HomeAssistant, callback, Event, CoreState
from homeassistant.const import (
UnitOfTime,
@@ -17,19 +17,20 @@ from homeassistant.components.sensor import (
SensorEntity,
SensorDeviceClass,
SensorStateClass,
UnitOfTemperature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import (
async_track_state_change_event,
)
from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.components.climate import (
ClimateEntity,
DOMAIN as CLIMATE_DOMAIN,
HVACAction,
HVACMode,
)
@@ -49,8 +50,6 @@ from .const import (
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_USE_CENTRAL_BOILER_FEATURE,
CONF_AUTO_REGULATION_VALVE,
CONF_AUTO_REGULATION_MODE,
overrides,
)
@@ -72,9 +71,6 @@ async def async_setup_entry(
unique_id = entry.entry_id
name = entry.data.get(CONF_NAME)
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
have_valve_regulation = (
entry.data.get(CONF_AUTO_REGULATION_MODE) == CONF_AUTO_REGULATION_VALVE
)
entities = None
@@ -103,16 +99,10 @@ async def async_setup_entry(
entities.append(OnTimeSensor(hass, unique_id, name, entry.data))
entities.append(OffTimeSensor(hass, unique_id, name, entry.data))
if (
entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE
or have_valve_regulation
):
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE:
entities.append(ValveOpenPercentSensor(hass, unique_id, name, entry.data))
if (
entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE
and not have_valve_regulation
):
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE:
entities.append(
RegulatedTemperatureSensor(hass, unique_id, name, entry.data)
)
@@ -133,7 +123,7 @@ class EnergySensor(VersatileThermostatBaseEntity, SensorEntity):
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
energy = self.my_climate.total_energy
if energy is None:
@@ -188,7 +178,7 @@ class MeanPowerSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
if math.isnan(float(self.my_climate.mean_cycle_power)) or math.isinf(
self.my_climate.mean_cycle_power
@@ -245,7 +235,7 @@ class OnPercentSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
on_percent = (
float(self.my_climate.proportional_algorithm.on_percent)
@@ -300,7 +290,7 @@ class ValveOpenPercentSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_native_value
self._attr_native_value = self.my_climate.valve_open_percent
@@ -342,7 +332,7 @@ class OnTimeSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
on_time = (
float(self.my_climate.proportional_algorithm.on_time_sec)
@@ -391,7 +381,7 @@ class OffTimeSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
off_time = (
float(self.my_climate.proportional_algorithm.off_time_sec)
@@ -439,7 +429,7 @@ class LastTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_native_value
self._attr_native_value = self.my_climate.last_temperature_measure
@@ -468,7 +458,7 @@ class LastExtTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_native_value
self._attr_native_value = self.my_climate.last_ext_temperature_measure
@@ -497,7 +487,7 @@ class TemperatureSlopeSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
last_slope = self.my_climate.last_temperature_slope
if last_slope is None:
@@ -550,7 +540,7 @@ class RegulatedTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
new_temp = self.my_climate.regulated_target_temp
if new_temp is None:
@@ -601,7 +591,7 @@ class EMATemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback
async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
_LOGGER.debug("%s - climate state change", self._attr_unique_id)
new_ema = self.my_climate.ema_temperature
if new_ema is None:
@@ -708,7 +698,7 @@ class NbActiveDeviceForBoilerSensor(SensorEntity):
for entity in component.entities:
if isinstance(entity, BaseThermostat) and entity.is_used_by_central_boiler:
self._entities.append(entity)
for under in entity.activable_underlying_entities:
for under in entity.underlying_entities:
underlying_entities_id.append(under.entity_id)
if len(underlying_entities_id) > 0:
# Arme l'écoute de la première entité
@@ -728,65 +718,25 @@ class NbActiveDeviceForBoilerSensor(SensorEntity):
await self.calculate_nb_active_devices(None)
async def calculate_nb_active_devices(self, event: Event):
async def calculate_nb_active_devices(self, _):
"""Calculate the number of active VTherm that have an
influence on central boiler"""
# _LOGGER.debug("%s- calculate_nb_active_devices - the event is %s ", self, event)
if event is not None:
new_state: State = event.data.get("new_state")
# _LOGGER.debug(
# "%s - calculate_nb_active_devices new_state is %s", self, new_state
# )
if not new_state:
return
old_state: State = event.data.get("old_state")
# For underlying climate, we need to observe also the hvac_action if available
new_hvac_action = new_state.attributes.get("hvac_action")
old_hvac_action = (
old_state.attributes.get("hvac_action")
if old_state is not None
else None
)
# Filter events that are not interested for us
if (
old_state is not None
and new_state.state == old_state.state
and new_hvac_action == old_hvac_action
):
# A false state change
return
_LOGGER.debug(
"%s - calculating the number of active underlying device for boiler activation. change change from %s to %s",
self,
old_state,
new_state,
)
else:
_LOGGER.debug(
"%s - calculating the number of active underlying device for boiler activation. First time calculation",
self,
)
_LOGGER.debug("%s - calculating the number of active VTherm", self)
nb_active = 0
for entity in self._entities:
nb_active += entity.nb_device_actives
_LOGGER.debug(
"After examining the hvac_action of %s, nb_active is %s",
"Examining the hvac_action of %s",
entity.name,
nb_active,
)
if (
entity.hvac_mode in [HVACMode.HEAT, HVACMode.AUTO]
and entity.hvac_action == HVACAction.HEATING
):
for under in entity.underlying_entities:
nb_active += 1 if under.is_device_active else 0
self._attr_native_value = nb_active
_LOGGER.debug(
"%s - Number of active underlying entities is %s", self, nb_active
)
self.async_write_ha_state()
def __str__(self):

View File

@@ -28,7 +28,6 @@
"presence": "Presence detection",
"advanced": "Advanced parameters",
"auto_start_stop": "Auto start and stop",
"valve_regulation": "Valve regulation configuration",
"finalize": "All done",
"configuration_not_complete": "Configuration not complete"
}
@@ -65,7 +64,7 @@
"use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection",
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after selecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
"use_auto_start_stop_feature": "Use the auto start and stop feature"
}
},
@@ -73,10 +72,21 @@
"title": "Linked entities",
"description": "Linked entities attributes",
"data": {
"underlying_entity_ids": "The device(s) to be controlled",
"heater_entity_id": "1st heater switch",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm",
"climate_entity_id": "1st underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode",
"valve_entity_id": "1st valve number",
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimum period",
@@ -85,10 +95,21 @@
"auto_fan_mode": "Auto fan mode"
},
"data_description": {
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
"heater_entity_id": "Mandatory heater entity id",
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not required",
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not required",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not required",
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id",
"climate_entity2_id": "2nd underlying climate entity id",
"climate_entity3_id": "3rd underlying climate entity id",
"climate_entity4_id": "4th underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode",
"valve_entity_id": "1st valve number entity id",
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
@@ -204,34 +225,6 @@
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
}
},
"central_boiler": {
"title": "Control of the central boiler",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
"data": {
"central_boiler_activation_service": "Command to turn-on",
"central_boiler_deactivation_service": "Command to turn-off"
},
"data_description": {
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
}
},
"valve_regulation": {
"title": "Self-regulation with valve",
"description": "Configuration for self-regulation with direct control of the valve",
"data": {
"offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities",
"closing_degree_entity_ids": "Closing degree entities",
"proportional_function": "Algorithm"
},
"data_description": {
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
"closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"proportional_function": "Algorithm to use (TPI is the only one for now)"
}
}
},
"error": {
@@ -272,7 +265,6 @@
"presence": "Presence detection",
"advanced": "Advanced parameters",
"auto_start_stop": "Auto start and stop",
"valve_regulation": "Valve regulation configuration",
"finalize": "All done",
"configuration_not_complete": "Configuration not complete"
}
@@ -309,7 +301,7 @@
"use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection",
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after selecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
"use_auto_start_stop_feature": "Use the auto start and stop feature"
}
},
@@ -317,10 +309,21 @@
"title": "Entities - {name}",
"description": "Linked entities attributes",
"data": {
"underlying_entity_ids": "The device(s) to be controlled",
"heater_entity_id": "1st heater switch",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm",
"climate_entity_id": "1st underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode",
"valve_entity_id": "1st valve number",
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimum period",
@@ -329,10 +332,21 @@
"auto_fan_mode": "Auto fan mode"
},
"data_description": {
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
"heater_entity_id": "Mandatory heater entity id",
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not used",
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id",
"climate_entity2_id": "2nd underlying climate entity id",
"climate_entity3_id": "3rd underlying climate entity id",
"climate_entity4_id": "4th underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode",
"valve_entity_id": "1st valve number entity id",
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
@@ -448,34 +462,6 @@
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
}
},
"central_boiler": {
"title": "Control of the central boiler - {name}",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
"data": {
"central_boiler_activation_service": "Command to turn-on",
"central_boiler_deactivation_service": "Command to turn-off"
},
"data_description": {
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
}
},
"valve_regulation": {
"title": "Self-regulation with valve - {name}",
"description": "Configuration for self-regulation with direct control of the valve",
"data": {
"offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities",
"closing_degree_entity_ids": "Closing degree entities",
"proportional_function": "Algorithm"
},
"data_description": {
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
"closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"proportional_function": "Algorithm to use (TPI is the only one for now)"
}
}
},
"error": {
@@ -483,8 +469,7 @@
"unknown_entity": "Unknown entity id",
"window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both",
"no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.",
"service_configuration_format": "The format of the service configuration is wrong",
"valve_regulation_nb_entities_incorrect": "The number of valve entities for valve regulation should be equal to the number of underlyings"
"service_configuration_format": "The format of the service configuration is wrong"
},
"abort": {
"already_configured": "Device is already configured"
@@ -506,8 +491,7 @@
"auto_regulation_medium": "Medium",
"auto_regulation_light": "Light",
"auto_regulation_expert": "Expert",
"auto_regulation_none": "No auto-regulation",
"auto_regulation_valve": "Direct control of valve"
"auto_regulation_none": "No auto-regulation"
}
},
"auto_fan_mode": {
@@ -552,8 +536,7 @@
"state": {
"power": "Shedding",
"security": "Safety",
"none": "Manual",
"frost": "Frost"
"none": "Manual"
}
}
}

View File

@@ -34,16 +34,10 @@ async def async_setup_entry(
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
auto_start_stop_feature = entry.data.get(CONF_USE_AUTO_START_STOP_FEATURE)
entities = []
if vt_type == CONF_THERMOSTAT_CLIMATE:
entities.append(FollowUnderlyingTemperatureChange(hass, unique_id, name, entry))
if auto_start_stop_feature is True:
# Creates a switch to enable the auto-start/stop
enable_entity = AutoStartStopEnable(hass, unique_id, name, entry)
entities.append(enable_entity)
async_add_entities(entities, True)
if vt_type == CONF_THERMOSTAT_CLIMATE and auto_start_stop_feature is True:
# Creates a switch to enable the auto-start/stop
enable_entity = AutoStartStopEnable(hass, unique_id, name, entry)
async_add_entities([enable_entity], True)
class AutoStartStopEnable(VersatileThermostatBaseEntity, SwitchEntity, RestoreEntity):
@@ -54,7 +48,7 @@ class AutoStartStopEnable(VersatileThermostatBaseEntity, SwitchEntity, RestoreEn
):
super().__init__(hass, unique_id, name)
self._attr_name = "Enable auto start/stop"
self._attr_unique_id = f"{self._device_name}_enable_auto_start_stop"
self._attr_unique_id = f"{self._device_name}_enbale_auto_start_stop"
self._default_value = (
entry_infos.data.get(CONF_AUTO_START_STOP_LEVEL)
!= AUTO_START_STOP_LEVEL_NONE
@@ -64,7 +58,7 @@ class AutoStartStopEnable(VersatileThermostatBaseEntity, SwitchEntity, RestoreEn
@property
def icon(self) -> str | None:
"""The icon"""
return "mdi:power-sleep"
return "mdi:power-settings"
async def async_added_to_hass(self):
await super().async_added_to_hass()
@@ -106,63 +100,3 @@ class AutoStartStopEnable(VersatileThermostatBaseEntity, SwitchEntity, RestoreEn
def turn_on(self, **kwargs: Any):
self._attr_is_on = True
self.update_my_state_and_vtherm()
class FollowUnderlyingTemperatureChange(
VersatileThermostatBaseEntity, SwitchEntity, RestoreEntity
):
"""The that enables the ManagedDevice optimisation with"""
def __init__(
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigEntry
):
super().__init__(hass, unique_id, name)
self._attr_name = "Follow underlying temp change"
self._attr_unique_id = f"{self._device_name}_follow_underlying_temp_change"
self._attr_is_on = False
@property
def icon(self) -> str | None:
"""The icon"""
return "mdi:content-copy"
async def async_added_to_hass(self):
await super().async_added_to_hass()
# Récupérer le dernier état sauvegardé de l'entité
last_state = await self.async_get_last_state()
# Si l'état précédent existe, vous pouvez l'utiliser
if last_state is not None:
self._attr_is_on = last_state.state == "on"
else:
# If no previous state set it to false by default
self._attr_is_on = False
self.update_my_state_and_vtherm()
def update_my_state_and_vtherm(self):
"""Update the follow flag in my VTherm"""
self.async_write_ha_state()
if self.my_climate is not None:
self.my_climate.set_follow_underlying_temp_change(self._attr_is_on)
@callback
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on."""
self.turn_on()
@callback
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off."""
self.turn_off()
@overrides
def turn_off(self, **kwargs: Any):
self._attr_is_on = False
self.update_my_state_and_vtherm()
@overrides
def turn_on(self, **kwargs: Any):
self._attr_is_on = True
self.update_my_state_and_vtherm()

View File

@@ -1,5 +1,5 @@
# pylint: disable=line-too-long, too-many-lines, abstract-method
""" A climate over climate classe """
# pylint: disable=line-too-long, too-many-lines
""" A climate over switch classe """
import logging
from datetime import timedelta, datetime
@@ -16,11 +16,45 @@ from homeassistant.components.climate import (
ClimateEntityFeature,
)
from .commons import round_to_nearest
from .commons import NowClass, round_to_nearest
from .base_thermostat import BaseThermostat, ConfigData
from .pi_algorithm import PITemperatureRegulator
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .const import (
overrides,
DOMAIN,
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
CONF_AUTO_REGULATION_MODE,
CONF_AUTO_REGULATION_NONE,
CONF_AUTO_REGULATION_SLOW,
CONF_AUTO_REGULATION_LIGHT,
CONF_AUTO_REGULATION_MEDIUM,
CONF_AUTO_REGULATION_STRONG,
CONF_AUTO_REGULATION_EXPERT,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
CONF_AUTO_REGULATION_USE_DEVICE_TEMP,
CONF_AUTO_FAN_MODE,
CONF_AUTO_FAN_NONE,
CONF_AUTO_FAN_LOW,
CONF_AUTO_FAN_MEDIUM,
CONF_AUTO_FAN_HIGH,
CONF_AUTO_FAN_TURBO,
RegulationParamSlow,
RegulationParamLight,
RegulationParamMedium,
RegulationParamStrong,
AUTO_FAN_DTEMP_THRESHOLD,
AUTO_FAN_DEACTIVATED_MODES,
CONF_AUTO_START_STOP_LEVEL,
AUTO_START_STOP_LEVEL_NONE,
TYPE_AUTO_START_STOP_LEVELS,
UnknownEntity,
EventType,
)
from .vtherm_api import VersatileThermostatAPI
from .underlyings import UnderlyingClimate
@@ -42,27 +76,47 @@ HVAC_ACTION_ON = [ # pylint: disable=invalid-name
class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
"""Representation of a base class for a Versatile Thermostat over a climate"""
_entity_component_unrecorded_attributes = BaseThermostat._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
frozenset(
{
"is_over_climate",
"start_hvac_action_date",
"underlying_entities",
"regulation_accumulated_error",
"auto_regulation_mode",
"auto_fan_mode",
"current_auto_fan_mode",
"auto_activated_fan_mode",
"auto_deactivated_fan_mode",
"auto_regulation_use_device_temp",
"auto_start_stop_level",
"auto_start_stop_dtmin",
"auto_start_stop_enable",
"auto_start_stop_accumulated_error",
"auto_start_stop_accumulated_error_threshold",
"auto_start_stop_last_switch_date",
"follow_underlying_temp_change",
}
_auto_regulation_mode: str | None = None
_regulation_algo = None
_regulated_target_temp: float | None = None
_auto_regulation_dtemp: float | None = None
_auto_regulation_period_min: int | None = None
_last_regulation_change: datetime | None = None
# The fan mode configured in configEntry
_auto_fan_mode: str | None = None
# The current fan mode (could be change by service call)
_current_auto_fan_mode: str | None = None
# The fan_mode name depending of the current_mode
_auto_activated_fan_mode: str | None = None
_auto_deactivated_fan_mode: str | None = None
_auto_start_stop_level: TYPE_AUTO_START_STOP_LEVELS = AUTO_START_STOP_LEVEL_NONE
_auto_start_stop_algo: AutoStartStopDetectionAlgorithm | None = None
_is_auto_start_stop_enabled: bool = False
_entity_component_unrecorded_attributes = (
BaseThermostat._entity_component_unrecorded_attributes.union(
frozenset(
{
"is_over_climate",
"start_hvac_action_date",
"underlying_climate_0",
"underlying_climate_1",
"underlying_climate_2",
"underlying_climate_3",
"regulation_accumulated_error",
"auto_regulation_mode",
"auto_fan_mode",
"current_auto_fan_mode",
"auto_activated_fan_mode",
"auto_deactivated_fan_mode",
"auto_regulation_use_device_temp",
"auto_start_stop_level",
"auto_start_stop_dtmin",
"auto_start_stop_enable",
"auto_start_stop_accumulated_error",
"auto_start_stop_accumulated_error_threshold",
}
)
)
)
@@ -70,44 +124,30 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigData
):
"""Initialize the thermostat over switch."""
self._auto_regulation_mode: str | None = None
self._regulation_algo = None
self._regulated_target_temp: float | None = None
self._auto_regulation_dtemp: float | None = None
self._auto_regulation_period_min: int | None = None
self._last_regulation_change: datetime | None = None
# The fan mode configured in configEntry
self._auto_fan_mode: str | None = None
# The current fan mode (could be change by service call)
self._current_auto_fan_mode: str | None = None
# The fan_mode name depending of the current_mode
self._auto_activated_fan_mode: str | None = None
self._auto_deactivated_fan_mode: str | None = None
self._auto_start_stop_level: TYPE_AUTO_START_STOP_LEVELS = (
AUTO_START_STOP_LEVEL_NONE
)
self._auto_start_stop_algo: AutoStartStopDetectionAlgorithm | None = None
self._is_auto_start_stop_enabled: bool = False
self._follow_underlying_temp_change: bool = False
self._last_regulation_change = None # NowClass.get_now(hass)
# super.__init__ calls post_init at the end. So it must be called after regulation initialization
super().__init__(hass, unique_id, name, entry_infos)
self._regulated_target_temp = self.target_temperature
self._last_regulation_change = NowClass.get_now(hass)
@overrides
def post_init(self, config_entry: ConfigData):
"""Initialize the Thermostat"""
super().post_init(config_entry)
for climate in config_entry.get(CONF_UNDERLYING_LIST):
under = UnderlyingClimate(
hass=self._hass,
thermostat=self,
climate_entity_id=climate,
)
self._underlyings.append(under)
for climate in [
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
]:
if config_entry.get(climate):
self._underlyings.append(
UnderlyingClimate(
hass=self._hass,
thermostat=self,
climate_entity_id=config_entry.get(climate),
)
)
self.choose_auto_regulation_mode(
config_entry.get(CONF_AUTO_REGULATION_MODE)
@@ -136,14 +176,9 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
CONF_AUTO_REGULATION_USE_DEVICE_TEMP, False
)
use_auto_start_stop = config_entry.get(CONF_USE_AUTO_START_STOP_FEATURE, False)
if use_auto_start_stop:
self._auto_start_stop_level = config_entry.get(
CONF_AUTO_START_STOP_LEVEL, AUTO_START_STOP_LEVEL_NONE
)
else:
self._auto_start_stop_level = AUTO_START_STOP_LEVEL_NONE
self._auto_start_stop_level = config_entry.get(
CONF_AUTO_START_STOP_LEVEL, AUTO_START_STOP_LEVEL_NONE
)
# Instanciate the auto start stop algo
self._auto_start_stop_algo = AutoStartStopDetectionAlgorithm(
self._auto_start_stop_level, self.name
@@ -154,13 +189,15 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
"""True if the Thermostat is over_climate"""
return True
def calculate_hvac_action(self, under_list: list) -> HVACAction | None:
"""Calculate an hvac action based on the hvac_action of the list in argument"""
@property
def hvac_action(self) -> HVACAction | None:
"""Returns the current hvac_action by checking all hvac_action of the underlyings"""
# if one not IDLE or OFF -> return it
# else if one IDLE -> IDLE
# else OFF
one_idle = False
for under in under_list:
for under in self._underlyings:
if (action := under.hvac_action) not in [
HVACAction.IDLE,
HVACAction.OFF,
@@ -172,18 +209,12 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
return HVACAction.IDLE
return HVACAction.OFF
@property
def hvac_action(self) -> HVACAction | None:
"""Returns the current hvac_action by checking all hvac_action of the underlyings"""
return self.calculate_hvac_action(self._underlyings)
@overrides
async def _async_internal_set_temperature(self, temperature: float):
"""Set the target temperature and the target temperature of underlying climate if any"""
await super()._async_internal_set_temperature(temperature)
self._regulation_algo.set_target_temp(self.target_temperature)
# Is necessary cause control_heating method will not force the update.
await self._send_regulated_temperature(force=True)
async def _send_regulated_temperature(self, force=False):
@@ -209,18 +240,16 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
force,
)
if self._last_regulation_change is not None:
period = (
float((self.now - self._last_regulation_change).total_seconds()) / 60.0
now: datetime = NowClass.get_now(self._hass)
period = float((now - self._last_regulation_change).total_seconds()) / 60.0
if not force and period < self._auto_regulation_period_min:
_LOGGER.info(
"%s - period (%.1f) min is < %.0f min -> forget the regulation send",
self,
period,
self._auto_regulation_period_min,
)
if not force and period < self._auto_regulation_period_min:
_LOGGER.info(
"%s - period (%.1f) min is < %.0f min -> forget the regulation send",
self,
period,
self._auto_regulation_period_min,
)
return
return
if not self._regulated_target_temp:
self._regulated_target_temp = self.target_temperature
@@ -258,7 +287,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
new_regulated_temp,
)
self._last_regulation_change = self.now
self._last_regulation_change = now
for under in self._underlyings:
# issue 348 - use device temperature if configured as offset
offset_temp = 0
@@ -504,10 +533,18 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
self._attr_extra_state_attributes["start_hvac_action_date"] = (
self._underlying_climate_start_hvac_action_date
)
self._attr_extra_state_attributes["underlying_entities"] = [
underlying.entity_id for underlying in self._underlyings
]
self._attr_extra_state_attributes["underlying_climate_0"] = self._underlyings[
0
].entity_id
self._attr_extra_state_attributes["underlying_climate_1"] = (
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
)
self._attr_extra_state_attributes["underlying_climate_2"] = (
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
)
self._attr_extra_state_attributes["underlying_climate_3"] = (
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
)
if self.is_regulated:
self._attr_extra_state_attributes["is_regulated"] = self.is_regulated
@@ -556,16 +593,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
"auto_start_stop_accumulated_error_threshold"
] = self._auto_start_stop_algo.accumulated_error_threshold
self._attr_extra_state_attributes["auto_start_stop_last_switch_date"] = (
self._auto_start_stop_algo.last_switch_date
)
self._attr_extra_state_attributes["follow_underlying_temp_change"] = (
self._follow_underlying_temp_change
)
self.async_write_ha_state()
_LOGGER.debug(
"%s - Calling update_custom_attributes: %s",
self,
@@ -612,18 +640,8 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
if self._total_energy is None:
self._total_energy = added_energy
_LOGGER.debug(
"%s - incremente_energy set energy is %s",
self,
self._total_energy,
)
else:
self._total_energy += added_energy
_LOGGER.debug(
"%s - incremente_energy incremented energy is %s",
self,
self._total_energy,
)
_LOGGER.debug(
"%s - added energy is %.3f . Total energy is now: %.3f",
@@ -731,7 +749,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
)
return
# Ignore new target temperature when out of range
# Forget event when the new target temperature is out of range
if (
not new_target_temp is None
and not self._attr_min_temp is None
@@ -745,8 +763,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
self._attr_min_temp,
self._attr_max_temp,
)
new_target_temp = None
under_temp_diff = 0
return
# A real changes have to be managed
_LOGGER.info(
@@ -865,12 +882,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
changes = True
# try to manage new target temperature set if state if no other changes have been found
# and if a target temperature have already been sent
if (
self._follow_underlying_temp_change
and not changes
and under.last_sent_temperature is not None
):
if not changes:
_LOGGER.debug(
"Do temperature check. under.last_sent_temperature is %s, new_target_temp is %s",
under.last_sent_temperature,
@@ -894,76 +906,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
await end_climate_changed(changes)
async def check_auto_start_stop(self):
"""Check the auto-start-stop and an eventual action
Return False if we should stop the control_heating method"""
slope = (self.last_temperature_slope or 0) / 60 # to have the slope in °/min
action = self._auto_start_stop_algo.calculate_action(
self.hvac_mode,
self._saved_hvac_mode,
self.target_temperature,
self.current_temperature,
slope,
self.now,
)
_LOGGER.debug("%s - auto_start_stop action is %s", self, action)
if action == AUTO_START_STOP_ACTION_OFF and self.is_on:
_LOGGER.info(
"%s - Turning OFF the Vtherm due to auto-start-stop conditions",
self,
)
self.set_hvac_off_reason(HVAC_OFF_REASON_AUTO_START_STOP)
await self.async_turn_off()
# Send an event
self.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "stop",
"name": self.name,
"cause": "Auto stop conditions reached",
"hvac_mode": self.hvac_mode,
"saved_hvac_mode": self._saved_hvac_mode,
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": round(slope, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
# Stop here
return False
elif (
action == AUTO_START_STOP_ACTION_ON
and self.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
):
_LOGGER.info(
"%s - Turning ON the Vtherm due to auto-start-stop conditions", self
)
await self.async_turn_on()
# Send an event
self.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "start",
"name": self.name,
"cause": "Auto start conditions reached",
"hvac_mode": self.hvac_mode,
"saved_hvac_mode": self._saved_hvac_mode,
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": round(slope, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
self.update_custom_attributes()
return True
@overrides
async def async_control_heating(self, force=False, _=None) -> bool:
"""The main function used to run the calculation at each cycle"""
@@ -971,11 +913,66 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
# Check if we need to auto start/stop the Vtherm
if self.auto_start_stop_enable:
continu = await self.check_auto_start_stop()
if not continu:
slope = (
self._window_auto_algo.last_slope or 0
) / 60 # to have the slope in °/min
action = self._auto_start_stop_algo.calculate_action(
self.hvac_mode,
self._saved_hvac_mode,
self.target_temperature,
self.current_temperature,
slope,
self.now,
)
_LOGGER.debug("%s - auto_start_stop action is %s", self, action)
if action == AUTO_START_STOP_ACTION_OFF:
_LOGGER.info(
"%s - Turning OFF the Vtherm due to auto-start-stop conditions",
self,
)
await self.async_turn_off()
# Send an event
self.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "stop",
"name:": self.name,
"cause": "Auto stop conditions reached",
"hvac_mode": self.hvac_mode,
"saved_hvac_mode": self._saved_hvac_mode,
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": slope,
},
)
# Stop here
return ret
elif action == AUTO_START_STOP_ACTION_ON:
_LOGGER.info(
"%s - Turning ON the Vtherm due to auto-start-stop conditions", self
)
await self.async_turn_on()
# Send an event
self.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "start",
"name:": self.name,
"cause": "Auto start conditions reached",
"hvac_mode": self.hvac_mode,
"saved_hvac_mode": self._saved_hvac_mode,
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": slope,
},
)
self.update_custom_attributes()
else:
_LOGGER.debug("%s - auto start/stop is disabled", self)
_LOGGER.debug("%s - auto start/stop is disabled")
# Continue the normal async_control_heating
@@ -990,37 +987,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
def set_auto_start_stop_enable(self, is_enabled: bool):
"""Enable/Disable the auto-start/stop feature"""
self._is_auto_start_stop_enabled = is_enabled
if (
self.hvac_mode == HVACMode.OFF
and self.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
):
_LOGGER.debug(
"%s - the vtherm is off cause auto-start/stop and enable have been set to false -> starts the VTherm"
)
self.hass.create_task(self.async_turn_on())
# Send an event
self.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "start",
"name": self.name,
"cause": "Auto start stop disabled",
"hvac_mode": self.hvac_mode,
"saved_hvac_mode": self._saved_hvac_mode,
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": round(self.last_temperature_slope or 0, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
self.update_custom_attributes()
def set_follow_underlying_temp_change(self, follow: bool):
"""Set the flaf follow the underlying temperature changes"""
self._follow_underlying_temp_change = follow
self.update_custom_attributes()
@property
@@ -1119,6 +1085,15 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
return self._support_flags
# We keep the step configured for the VTherm and not the step of the underlying
# @property
# def target_temperature_step(self) -> float | None:
# """Return the supported step of target temperature."""
# if self.underlying_entity(0):
# return self.underlying_entity(0).target_temperature_step
#
# return None
@property
def target_temperature_high(self) -> float | None:
"""Return the highbound target temperature we try to reach.
@@ -1141,14 +1116,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
return None
@property
def current_humidity(self) -> float | None:
"""Return the humidity."""
if self.underlying_entity(0):
return self.underlying_entity(0).current_humidity
return None
@property
def is_aux_heat(self) -> bool | None:
"""Return true if aux heater.
@@ -1178,11 +1145,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
"""Returns the auto_start_stop_enable"""
return self._is_auto_start_stop_enabled
@property
def follow_underlying_temp_change(self) -> bool:
"""Get the follow underlying temp change flag"""
return self._follow_underlying_temp_change
@overrides
def init_underlyings(self):
"""Init the underlyings if not already done"""
@@ -1285,13 +1247,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_SLOW)
elif auto_regulation_mode == "Expert":
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_EXPERT)
else:
_LOGGER.warning(
"%s - auto_regulation_mode %s is not supported",
self,
auto_regulation_mode,
)
return
await self._send_regulated_temperature()
self.update_custom_attributes()

View File

@@ -1,295 +0,0 @@
# pylint: disable=line-too-long, too-many-lines, abstract-method
""" A climate with a direct valve regulation class """
import logging
from datetime import datetime
from homeassistant.core import HomeAssistant
from homeassistant.components.climate import HVACMode, HVACAction
from .underlyings import UnderlyingValveRegulation
# from .commons import NowClass, round_to_nearest
from .base_thermostat import ConfigData
from .thermostat_climate import ThermostatOverClimate
from .prop_algorithm import PropAlgorithm
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
# from .vtherm_api import VersatileThermostatAPI
_LOGGER = logging.getLogger(__name__)
class ThermostatOverClimateValve(ThermostatOverClimate):
"""This class represent a VTherm over a climate with a direct valve regulation"""
_entity_component_unrecorded_attributes = ThermostatOverClimate._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
frozenset(
{
"is_over_climate",
"have_valve_regulation",
"underlying_entities",
"on_time_sec",
"off_time_sec",
"cycle_min",
"function",
"tpi_coef_int",
"tpi_coef_ext",
"power_percent",
}
)
)
def __init__(
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigData
):
"""Initialize the ThermostatOverClimateValve class"""
_LOGGER.debug("%s - creating a ThermostatOverClimateValve VTherm", name)
self._underlyings_valve_regulation: list[UnderlyingValveRegulation] = []
self._valve_open_percent: int | None = None
self._last_calculation_timestamp: datetime | None = None
self._auto_regulation_dpercent: float | None = None
self._auto_regulation_period_min: int | None = None
super().__init__(hass, unique_id, name, entry_infos)
@overrides
def post_init(self, config_entry: ConfigData):
"""Initialize the Thermostat and underlyings
Beware that the underlyings list contains the climate which represent the TRV
but also the UnderlyingValveRegulation which reprensent the valve"""
super().post_init(config_entry)
self._auto_regulation_dpercent = (
config_entry.get(CONF_AUTO_REGULATION_DTEMP)
if config_entry.get(CONF_AUTO_REGULATION_DTEMP) is not None
else 0.0
)
self._auto_regulation_period_min = (
config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN)
if config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None
else 0
)
# Initialization of the TPI algo
self._prop_algorithm = PropAlgorithm(
self._proportional_function,
self._tpi_coef_int,
self._tpi_coef_ext,
self._cycle_min,
self._minimal_activation_delay,
self.name,
)
offset_list = config_entry.get(CONF_OFFSET_CALIBRATION_LIST, [])
opening_list = config_entry.get(CONF_OPENING_DEGREE_LIST)
closing_list = config_entry.get(CONF_CLOSING_DEGREE_LIST, [])
for idx, _ in enumerate(config_entry.get(CONF_UNDERLYING_LIST)):
offset = offset_list[idx] if idx < len(offset_list) else None
# number of opening should equal number of underlying
opening = opening_list[idx]
closing = closing_list[idx] if idx < len(closing_list) else None
under = UnderlyingValveRegulation(
hass=self._hass,
thermostat=self,
offset_calibration_entity_id=offset,
opening_degree_entity_id=opening,
closing_degree_entity_id=closing,
climate_underlying=self._underlyings[idx],
)
self._underlyings_valve_regulation.append(under)
@overrides
def update_custom_attributes(self):
"""Custom attributes"""
super().update_custom_attributes()
self._attr_extra_state_attributes["have_valve_regulation"] = (
self.have_valve_regulation
)
self._attr_extra_state_attributes["underlyings_valve_regulation"] = [
underlying.valve_entity_ids
for underlying in self._underlyings_valve_regulation
]
self._attr_extra_state_attributes["on_percent"] = (
self._prop_algorithm.on_percent
)
self._attr_extra_state_attributes["power_percent"] = self.power_percent
self._attr_extra_state_attributes["on_time_sec"] = (
self._prop_algorithm.on_time_sec
)
self._attr_extra_state_attributes["off_time_sec"] = (
self._prop_algorithm.off_time_sec
)
self._attr_extra_state_attributes["cycle_min"] = self._cycle_min
self._attr_extra_state_attributes["function"] = self._proportional_function
self._attr_extra_state_attributes["tpi_coef_int"] = self._tpi_coef_int
self._attr_extra_state_attributes["tpi_coef_ext"] = self._tpi_coef_ext
self._attr_extra_state_attributes["valve_open_percent"] = (
self.valve_open_percent
)
self._attr_extra_state_attributes["auto_regulation_dpercent"] = (
self._auto_regulation_dpercent
)
self._attr_extra_state_attributes["auto_regulation_period_min"] = (
self._auto_regulation_period_min
)
self._attr_extra_state_attributes["last_calculation_timestamp"] = (
self._last_calculation_timestamp.astimezone(self._current_tz).isoformat()
if self._last_calculation_timestamp
else None
)
self.async_write_ha_state()
_LOGGER.debug(
"%s - Calling update_custom_attributes: %s",
self,
self._attr_extra_state_attributes,
)
@overrides
def recalculate(self):
"""A utility function to force the calculation of a the algo and
update the custom attributes and write the state
"""
_LOGGER.debug("%s - recalculate the open percent", self)
# TODO this is exactly the same method as the thermostat_valve recalculate. Put that in common
# For testing purpose. Should call _set_now() before
now = self.now
if self._last_calculation_timestamp is not None:
period = (now - self._last_calculation_timestamp).total_seconds() / 60
if period < self._auto_regulation_period_min:
_LOGGER.info(
"%s - do not calculate TPI because regulation_period (%d) is not exceeded",
self,
period,
)
return
self._prop_algorithm.calculate(
self._target_temp,
self._cur_temp,
self._cur_ext_temp,
self._hvac_mode or HVACMode.OFF,
)
new_valve_percent = round(
max(0, min(self.proportional_algorithm.on_percent, 1)) * 100
)
# Issue 533 - don't filter with dtemp if valve should be close. Else it will never close
if new_valve_percent < self._auto_regulation_dpercent:
new_valve_percent = 0
dpercent = (
new_valve_percent - self._valve_open_percent
if self._valve_open_percent is not None
else 0
)
if (
self._last_calculation_timestamp is not None
and new_valve_percent > 0
and -1 * self._auto_regulation_dpercent
<= dpercent
< self._auto_regulation_dpercent
):
_LOGGER.debug(
"%s - do not calculate TPI because regulation_dpercent (%.1f) is not exceeded",
self,
dpercent,
)
return
if (
self._last_calculation_timestamp is not None
and self._valve_open_percent == new_valve_percent
):
_LOGGER.debug("%s - no change in valve_open_percent.", self)
return
self._valve_open_percent = new_valve_percent
self._last_calculation_timestamp = now
super().recalculate()
async def _send_regulated_temperature(self, force=False):
"""Sends the regulated temperature to all underlying"""
if self.target_temperature is None:
return
for under in self._underlyings:
if self.target_temperature != under.last_sent_temperature:
await under.set_temperature(
self.target_temperature,
self._attr_max_temp,
self._attr_min_temp,
)
for under in self._underlyings_valve_regulation:
await under.set_valve_open_percent()
@property
def have_valve_regulation(self) -> bool:
"""True if the Thermostat is regulated by valve"""
return True
@property
def power_percent(self) -> float | None:
"""Get the current on_percent value"""
if self._prop_algorithm:
return round(self._prop_algorithm.on_percent * 100, 0)
else:
return None
# @property
# def hvac_modes(self) -> list[HVACMode]:
# """Get the hvac_modes"""
# return self._hvac_list
@property
def valve_open_percent(self) -> int:
"""Gives the percentage of valve needed"""
if self._hvac_mode == HVACMode.OFF or self._valve_open_percent is None:
return 0
else:
return self._valve_open_percent
@property
def hvac_action(self) -> HVACAction | None:
"""Returns the current hvac_action by checking all hvac_action of the _underlyings_valve_regulation"""
return self.calculate_hvac_action(self._underlyings_valve_regulation)
@property
def is_device_active(self) -> bool:
"""A hack to overrides the state from underlyings"""
return self.valve_open_percent > 0
@property
def nb_device_actives(self) -> int:
"""Calculate the number of active devices"""
if self.is_device_active:
return len(self._underlyings_valve_regulation)
else:
return 0
@property
def activable_underlying_entities(self) -> list | None:
"""Returns the activable underlying entities for controling the central boiler"""
return self._underlyings_valve_regulation
@overrides
async def service_set_auto_regulation_mode(self, auto_regulation_mode: str):
"""This should not be possible in valve regulation mode"""
return

View File

@@ -1,4 +1,4 @@
# pylint: disable=line-too-long, abstract-method
# pylint: disable=line-too-long
""" A climate over switch classe """
import logging
@@ -7,11 +7,13 @@ from homeassistant.helpers.event import (
async_track_state_change_event,
EventStateChangedData,
)
from homeassistant.core import HomeAssistant
from homeassistant.components.climate import HVACMode
from .const import (
CONF_UNDERLYING_LIST,
CONF_HEATER,
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_HEATER_KEEP_ALIVE,
CONF_INVERSE_SWITCH,
overrides,
@@ -23,6 +25,7 @@ from .prop_algorithm import PropAlgorithm
_LOGGER = logging.getLogger(__name__)
class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
"""Representation of a base class for a Versatile Thermostat over a switch."""
@@ -32,7 +35,10 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
{
"is_over_switch",
"is_inversed",
"underlying_entities",
"underlying_switch_0",
"underlying_switch_1",
"underlying_switch_2",
"underlying_switch_3",
"on_time_sec",
"off_time_sec",
"cycle_min",
@@ -40,16 +46,16 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
"tpi_coef_int",
"tpi_coef_ext",
"power_percent",
"calculated_on_percent",
}
)
)
)
def __init__(self, hass: HomeAssistant, unique_id, name, config_entry) -> None:
"""Initialize the thermostat over switch."""
self._is_inversed: bool | None = None
super().__init__(hass, unique_id, name, config_entry)
# useless for now
# def __init__(self, hass: HomeAssistant, unique_id, name, config_entry) -> None:
# """Initialize the thermostat over switch."""
# super().__init__(hass, unique_id, name, config_entry)
_is_inversed: bool | None = None
@property
def is_over_switch(self) -> bool:
@@ -82,10 +88,15 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
self._cycle_min,
self._minimal_activation_delay,
self.name,
max_on_percent=self._max_on_percent,
)
lst_switches = config_entry.get(CONF_UNDERLYING_LIST)
lst_switches = [config_entry.get(CONF_HEATER)]
if config_entry.get(CONF_HEATER_2):
lst_switches.append(config_entry.get(CONF_HEATER_2))
if config_entry.get(CONF_HEATER_3):
lst_switches.append(config_entry.get(CONF_HEATER_3))
if config_entry.get(CONF_HEATER_4):
lst_switches.append(config_entry.get(CONF_HEATER_4))
delta_cycle = self._cycle_min * 60 / len(lst_switches)
for idx, switch in enumerate(lst_switches):
@@ -129,10 +140,16 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
self._attr_extra_state_attributes["is_over_switch"] = self.is_over_switch
self._attr_extra_state_attributes["is_inversed"] = self.is_inversed
self._attr_extra_state_attributes["keep_alive_sec"] = under0.keep_alive_sec
self._attr_extra_state_attributes["underlying_entities"] = [
underlying.entity_id for underlying in self._underlyings
]
self._attr_extra_state_attributes["underlying_switch_0"] = under0.entity_id
self._attr_extra_state_attributes["underlying_switch_1"] = (
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
)
self._attr_extra_state_attributes["underlying_switch_2"] = (
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
)
self._attr_extra_state_attributes["underlying_switch_3"] = (
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
)
self._attr_extra_state_attributes[
"on_percent"
@@ -148,9 +165,6 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
self._attr_extra_state_attributes["function"] = self._proportional_function
self._attr_extra_state_attributes["tpi_coef_int"] = self._tpi_coef_int
self._attr_extra_state_attributes["tpi_coef_ext"] = self._tpi_coef_ext
self._attr_extra_state_attributes[
"calculated_on_percent"
] = self._prop_algorithm.calculated_on_percent
self.async_write_ha_state()
_LOGGER.debug(
@@ -187,18 +201,8 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
if self._total_energy is None:
self._total_energy = added_energy
_LOGGER.debug(
"%s - incremente_energy set energy is %s",
self,
self._total_energy,
)
else:
self._total_energy += added_energy
_LOGGER.debug(
"%s - incremente_energy increment energy is %s",
self,
self._total_energy,
)
self.update_custom_attributes()

View File

@@ -1,4 +1,4 @@
# pylint: disable=line-too-long, abstract-method
# pylint: disable=line-too-long
""" A climate over switch classe """
import logging
from datetime import timedelta, datetime
@@ -15,7 +15,10 @@ from .base_thermostat import BaseThermostat, ConfigData
from .prop_algorithm import PropAlgorithm
from .const import (
CONF_UNDERLYING_LIST,
CONF_VALVE,
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
# This is not really self-regulation but regulation here
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
@@ -26,6 +29,7 @@ from .underlyings import UnderlyingValve
_LOGGER = logging.getLogger(__name__)
class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=abstract-method
"""Representation of a class for a Versatile Thermostat over a Valve"""
@@ -33,7 +37,10 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
frozenset(
{
"is_over_valve",
"underlying_entities",
"underlying_valve_0",
"underlying_valve_1",
"underlying_valve_2",
"underlying_valve_3",
"on_time_sec",
"off_time_sec",
"cycle_min",
@@ -43,7 +50,6 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
"auto_regulation_dpercent",
"auto_regulation_period_min",
"last_calculation_timestamp",
"calculated_on_percent",
}
)
)
@@ -97,10 +103,15 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
self._cycle_min,
self._minimal_activation_delay,
self.name,
max_on_percent=self._max_on_percent,
)
lst_valves = config_entry.get(CONF_UNDERLYING_LIST)
lst_valves = [config_entry.get(CONF_VALVE)]
if config_entry.get(CONF_VALVE_2):
lst_valves.append(config_entry.get(CONF_VALVE_2))
if config_entry.get(CONF_VALVE_3):
lst_valves.append(config_entry.get(CONF_VALVE_3))
if config_entry.get(CONF_VALVE_4):
lst_valves.append(config_entry.get(CONF_VALVE_4))
for _, valve in enumerate(lst_valves):
self._underlyings.append(
@@ -152,10 +163,18 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
"valve_open_percent"
] = self.valve_open_percent
self._attr_extra_state_attributes["is_over_valve"] = self.is_over_valve
self._attr_extra_state_attributes["underlying_entities"] = [
underlying.entity_id for underlying in self._underlyings
]
self._attr_extra_state_attributes["underlying_valve_0"] = self._underlyings[
0
].entity_id
self._attr_extra_state_attributes["underlying_valve_1"] = (
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
)
self._attr_extra_state_attributes["underlying_valve_2"] = (
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
)
self._attr_extra_state_attributes["underlying_valve_3"] = (
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
)
self._attr_extra_state_attributes[
"on_percent"
@@ -181,9 +200,6 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
if self._last_calculation_timestamp
else None
)
self._attr_extra_state_attributes[
"calculated_on_percent"
] = self._prop_algorithm.calculated_on_percent
self.async_write_ha_state()
_LOGGER.debug(
@@ -248,9 +264,8 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
self._valve_open_percent = new_valve_percent
# is one in start_cycle now
# for under in self._underlyings:
# under.set_valve_open_percent()
for under in self._underlyings:
under.set_valve_open_percent()
self._last_calculation_timestamp = now
@@ -270,18 +285,8 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
if self._total_energy is None:
self._total_energy = added_energy
_LOGGER.debug(
"%s - incremente_energy set energy is %s",
self,
self._total_energy,
)
else:
self._total_energy += added_energy
_LOGGER.debug(
"%s - get_my_previous_state increment energy is %s",
self,
self._total_energy,
)
self.update_custom_attributes()

View File

@@ -28,7 +28,6 @@
"presence": "Presence detection",
"advanced": "Advanced parameters",
"auto_start_stop": "Auto start and stop",
"valve_regulation": "Valve regulation configuration",
"finalize": "All done",
"configuration_not_complete": "Configuration not complete"
}
@@ -65,7 +64,7 @@
"use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection",
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after selecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
"use_auto_start_stop_feature": "Use the auto start and stop feature"
}
},
@@ -73,10 +72,21 @@
"title": "Linked entities",
"description": "Linked entities attributes",
"data": {
"underlying_entity_ids": "The device(s) to be controlled",
"heater_entity_id": "1st heater switch",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm",
"climate_entity_id": "1st underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode",
"valve_entity_id": "1st valve number",
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimum period",
@@ -85,10 +95,21 @@
"auto_fan_mode": "Auto fan mode"
},
"data_description": {
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
"heater_entity_id": "Mandatory heater entity id",
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not required",
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not required",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not required",
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id",
"climate_entity2_id": "2nd underlying climate entity id",
"climate_entity3_id": "3rd underlying climate entity id",
"climate_entity4_id": "4th underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode",
"valve_entity_id": "1st valve number entity id",
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
@@ -204,34 +225,6 @@
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
}
},
"central_boiler": {
"title": "Control of the central boiler",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
"data": {
"central_boiler_activation_service": "Command to turn-on",
"central_boiler_deactivation_service": "Command to turn-off"
},
"data_description": {
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
}
},
"valve_regulation": {
"title": "Self-regulation with valve",
"description": "Configuration for self-regulation with direct control of the valve",
"data": {
"offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities",
"closing_degree_entity_ids": "Closing degree entities",
"proportional_function": "Algorithm"
},
"data_description": {
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
"closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"proportional_function": "Algorithm to use (TPI is the only one for now)"
}
}
},
"error": {
@@ -272,7 +265,6 @@
"presence": "Presence detection",
"advanced": "Advanced parameters",
"auto_start_stop": "Auto start and stop",
"valve_regulation": "Valve regulation configuration",
"finalize": "All done",
"configuration_not_complete": "Configuration not complete"
}
@@ -309,7 +301,7 @@
"use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection",
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after selecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
"use_auto_start_stop_feature": "Use the auto start and stop feature"
}
},
@@ -317,10 +309,21 @@
"title": "Entities - {name}",
"description": "Linked entities attributes",
"data": {
"underlying_entity_ids": "The device(s) to be controlled",
"heater_entity_id": "1st heater switch",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm",
"climate_entity_id": "1st underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode",
"valve_entity_id": "1st valve number",
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimum period",
@@ -329,10 +332,21 @@
"auto_fan_mode": "Auto fan mode"
},
"data_description": {
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
"heater_entity_id": "Mandatory heater entity id",
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not used",
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id",
"climate_entity2_id": "2nd underlying climate entity id",
"climate_entity3_id": "3rd underlying climate entity id",
"climate_entity4_id": "4th underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode",
"valve_entity_id": "1st valve number entity id",
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
@@ -448,34 +462,6 @@
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
}
},
"central_boiler": {
"title": "Control of the central boiler - {name}",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
"data": {
"central_boiler_activation_service": "Command to turn-on",
"central_boiler_deactivation_service": "Command to turn-off"
},
"data_description": {
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
}
},
"valve_regulation": {
"title": "Self-regulation with valve - {name}",
"description": "Configuration for self-regulation with direct control of the valve",
"data": {
"offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities",
"closing_degree_entity_ids": "Closing degree entities",
"proportional_function": "Algorithm"
},
"data_description": {
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
"closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"proportional_function": "Algorithm to use (TPI is the only one for now)"
}
}
},
"error": {
@@ -483,8 +469,7 @@
"unknown_entity": "Unknown entity id",
"window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both",
"no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.",
"service_configuration_format": "The format of the service configuration is wrong",
"valve_regulation_nb_entities_incorrect": "The number of valve entities for valve regulation should be equal to the number of underlyings"
"service_configuration_format": "The format of the service configuration is wrong"
},
"abort": {
"already_configured": "Device is already configured"
@@ -506,8 +491,7 @@
"auto_regulation_medium": "Medium",
"auto_regulation_light": "Light",
"auto_regulation_expert": "Expert",
"auto_regulation_none": "No auto-regulation",
"auto_regulation_valve": "Direct control of valve"
"auto_regulation_none": "No auto-regulation"
}
},
"auto_fan_mode": {
@@ -552,8 +536,7 @@
"state": {
"power": "Shedding",
"security": "Safety",
"none": "Manual",
"frost": "Frost"
"none": "Manual"
}
}
}

View File

@@ -28,7 +28,6 @@
"presence": "Détection de présence",
"advanced": "Paramètres avancés",
"auto_start_stop": "Allumage/extinction automatique",
"valve_regulation": "Configuration de la regulation par vanne",
"finalize": "Finaliser la création",
"configuration_not_complete": "Configuration incomplète"
}
@@ -73,26 +72,48 @@
"title": "Entité(s) liée(s)",
"description": "Attributs de(s) l'entité(s) liée(s)",
"data": {
"underlying_entity_ids": "Les équipements à controller",
"heater_entity_id": "1er radiateur",
"heater_entity2_id": "2ème radiateur",
"heater_entity3_id": "3ème radiateur",
"heater_entity4_id": "4ème radiateur",
"heater_keep_alive": "keep-alive (sec)",
"proportional_function": "Algorithme",
"climate_entity_id": "Thermostat sous-jacent",
"climate_entity2_id": "2ème thermostat sous-jacent",
"climate_entity3_id": "3ème thermostat sous-jacent",
"climate_entity4_id": "4ème thermostat sous-jacent",
"ac_mode": "AC mode ?",
"valve_entity_id": "1ère valve number",
"valve_entity2_id": "2ème valve number",
"valve_entity3_id": "3ème valve number",
"valve_entity4_id": "4ème valve number",
"auto_regulation_mode": "Auto-régulation",
"auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale de régulation",
"auto_regulation_use_device_temp": "Compenser la température interne du sous-jacent",
"auto_regulation_use_device_temp": "Utiliser la température interne du sous-jacent",
"inverse_switch_command": "Inverser la commande",
"auto_fan_mode": " Auto ventilation mode"
},
"data_description": {
"underlying_entity_ids": "La liste des équipements qui seront controlés par ce VTherm",
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
"heater_entity2_id": "Optionnel entity id du 2ème radiateur",
"heater_entity3_id": "Optionnel entity id du 3ème radiateur",
"heater_entity4_id": "Optionnel entity id du 4ème radiateur",
"heater_keep_alive": "Intervalle de rafraichissement du switch en secondes. Laisser vide pour désactiver. À n'utiliser que pour les switchs qui le nécessite.",
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
"climate_entity_id": "Entity id du thermostat sous-jacent",
"climate_entity2_id": "Entity id du 2ème thermostat sous-jacent",
"climate_entity3_id": "Entity id du 3ème thermostat sous-jacent",
"climate_entity4_id": "Entity id du 4ème thermostat sous-jacent",
"ac_mode": "Utilisation du mode Air Conditionné (AC)",
"auto_regulation_mode": "Utilisation de l'auto-régulation faite par VTherm",
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les vannes) en-dessous duquel la régulation ne sera pas envoyée",
"valve_entity_id": "Entity id de la 1ère valve",
"valve_entity2_id": "Entity id de la 2ème valve",
"valve_entity3_id": "Entity id de la 3ème valve",
"valve_entity4_id": "Entity id de la 4ème valve",
"auto_regulation_mode": "Ajustement automatique de la température cible",
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
"auto_regulation_use_device_temp": "Compenser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
"auto_regulation_use_device_temp": "Utiliser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
}
@@ -216,22 +237,6 @@
"central_boiler_activation_service": "Commande à éxecuter pour allumer la chaudière centrale au format entity_id/service_name[/attribut:valeur]",
"central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]"
}
},
"valve_regulation": {
"title": "Auto-régulation par vanne",
"description": "Configuration de l'auto-régulation par controle direct de la vanne",
"data": {
"offset_calibration_entity_ids": "Entités de 'calibrage du décalage''",
"opening_degree_entity_ids": "Entités 'ouverture de vanne'",
"closing_degree_entity_ids": "Entités 'fermeture de la vanne'",
"proportional_function": "Algorithme"
},
"data_description": {
"offset_calibration_entity_ids": "La liste des entités 'calibrage du décalage' (offset calibration). Configurez le si votre TRV possède cette fonction pour une meilleure régulation. Il doit y en avoir une par entité climate sous-jacente",
"opening_degree_entity_ids": "La liste des entités 'ouverture de vanne'. Il doit y en avoir une par entité climate sous-jacente",
"closing_degree_entity_ids": "La liste des entités 'fermeture de la vanne'. Configurez le si votre TRV possède cette fonction pour une meilleure régulation. Il doit y en avoir une par entité climate sous-jacente",
"proportional_function": "Algorithme à utiliser (seulement TPI est disponible)"
}
}
},
"error": {
@@ -272,7 +277,6 @@
"presence": "Détection de présence",
"advanced": "Paramètres avancés",
"auto_start_stop": "Allumage/extinction automatique",
"valve_regulation": "Configuration de la regulation par vanne",
"finalize": "Finaliser les modifications",
"configuration_not_complete": "Configuration incomplète"
}
@@ -314,29 +318,51 @@
}
},
"type": {
"title": "Entité(s) liée(s) - {name}",
"title": "Entités - {name}",
"description": "Attributs de(s) l'entité(s) liée(s)",
"data": {
"underlying_entity_ids": "Les équipements à controller",
"heater_keep_alive": "keep-alive (sec)",
"heater_entity_id": "1er radiateur",
"heater_entity2_id": "2ème radiateur",
"heater_entity3_id": "3ème radiateur",
"heater_entity4_id": "4ème radiateur",
"heater_keep_alive": "Keep-alive (sec)",
"proportional_function": "Algorithme",
"climate_entity_id": "Thermostat sous-jacent",
"climate_entity2_id": "2ème thermostat sous-jacent",
"climate_entity3_id": "3ème thermostat sous-jacent",
"climate_entity4_id": "4ème thermostat sous-jacent",
"ac_mode": "AC mode ?",
"auto_regulation_mode": "Auto-régulation",
"valve_entity_id": "1ère valve",
"valve_entity2_id": "2ème valve",
"valve_entity3_id": "3ème valve",
"valve_entity4_id": "4ème valve",
"auto_regulation_mode": "Auto-regulation",
"auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale de régulation",
"auto_regulation_use_device_temp": "Compenser la température interne du sous-jacent",
"auto_regulation_use_device_temp": "Utiliser la température interne du sous-jacent",
"inverse_switch_command": "Inverser la commande",
"auto_fan_mode": " Auto ventilation mode"
"auto_fan_mode": "Auto fan mode"
},
"data_description": {
"underlying_entity_ids": "La liste des équipements qui seront controlés par ce VTherm",
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
"heater_entity2_id": "Optionnel entity id du 2ème radiateur",
"heater_entity3_id": "Optionnel entity id du 3ème radiateur",
"heater_entity4_id": "Optionnel entity id du 4ème radiateur",
"heater_keep_alive": "Intervalle de rafraichissement du switch en secondes. Laisser vide pour désactiver. À n'utiliser que pour les switchs qui le nécessite.",
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
"climate_entity_id": "Entity id du thermostat sous-jacent",
"climate_entity2_id": "Entity id du 2ème thermostat sous-jacent",
"climate_entity3_id": "Entity id du 3ème thermostat sous-jacent",
"climate_entity4_id": "Entity id du 4ème thermostat sous-jacent",
"ac_mode": "Utilisation du mode Air Conditionné (AC)",
"auto_regulation_mode": "Utilisation de l'auto-régulation faite par VTherm",
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les vannes) en-dessous duquel la régulation ne sera pas envoyée",
"valve_entity_id": "Entity id de la 1ère valve",
"valve_entity2_id": "Entity id de la 2ème valve",
"valve_entity3_id": "Entity id de la 3ème valve",
"valve_entity4_id": "Entity id de la 4ème valve",
"auto_regulation_mode": "Ajustement automatique de la consigne",
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
"auto_regulation_use_device_temp": "Compenser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
"auto_regulation_use_device_temp": "Utiliser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
}
@@ -454,22 +480,6 @@
"central_boiler_activation_service": "Commande à éxecuter pour allumer la chaudière centrale au format entity_id/service_name[/attribut:valeur]",
"central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]"
}
},
"valve_regulation": {
"title": "Auto-régulation par vanne - {name}",
"description": "Configuration de l'auto-régulation par controle direct de la vanne",
"data": {
"offset_calibration_entity_ids": "Entités de 'calibrage du décalage''",
"opening_degree_entity_ids": "Entités 'ouverture de vanne'",
"closing_degree_entity_ids": "Entités 'fermeture de la vanne'",
"proportional_function": "Algorithme"
},
"data_description": {
"offset_calibration_entity_ids": "La liste des entités 'calibrage du décalage' (offset calibration). Configurez le si votre TRV possède cette fonction pour une meilleure régulation. Il doit y en avoir une par entité climate sous-jacente",
"opening_degree_entity_ids": "La liste des entités 'ouverture de vanne'. Il doit y en avoir une par entité climate sous-jacente",
"closing_degree_entity_ids": "La liste des entités 'fermeture de la vanne'. Configurez le si votre TRV possède cette fonction pour une meilleure régulation. Il doit y en avoir une par entité climate sous-jacente",
"proportional_function": "Algorithme à utiliser (seulement TPI est disponible)"
}
}
},
"error": {
@@ -477,8 +487,7 @@
"unknown_entity": "entity id inconnu",
"window_open_detection_method": "Une seule méthode de détection des ouvertures ouvertes doit être utilisée. Utilisez le détecteur d'ouverture ou les seuils de température mais pas les deux.",
"no_central_config": "Vous ne pouvez pas cocher 'Utiliser la configuration centrale' car aucune configuration centrale n'a été trouvée. Vous devez créer un Versatile Thermostat de type 'Central Configuration' pour pouvoir l'utiliser.",
"service_configuration_format": "Mauvais format de la configuration du service",
"valve_regulation_nb_entities_incorrect": "Le nombre d'entités pour la régulation par vanne doit être égal au nombre d'entité sous-jacentes"
"service_configuration_format": "Mauvais format de la configuration du service"
},
"abort": {
"already_configured": "Le device est déjà configuré"
@@ -500,8 +509,7 @@
"auto_regulation_medium": "Moyenne",
"auto_regulation_light": "Légère",
"auto_regulation_expert": "Expert",
"auto_regulation_none": "Aucune",
"auto_regulation_valve": "Contrôle direct de la vanne"
"auto_regulation_none": "Aucune"
}
},
"auto_fan_mode": {
@@ -546,8 +554,7 @@
"state": {
"power": "Délestage",
"security": "Sécurité",
"none": "Manuel",
"frost": "Hors Gel"
"none": "Manuel"
}
}
}

View File

@@ -364,8 +364,7 @@
"state": {
"power": "Ripartizione",
"security": "Sicurezza",
"none": "Manuale",
"frost": "Gelo"
"none": "Manuale"
}
}
}

View File

@@ -1,4 +1,4 @@
# pylint: disable=unused-argument, line-too-long, too-many-lines
# pylint: disable=unused-argument, line-too-long
""" Underlying entities classes """
import logging
@@ -32,7 +32,7 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import async_call_later
from homeassistant.util.unit_conversion import TemperatureConverter
from .const import UnknownEntity, overrides, get_safe_float
from .const import UnknownEntity, overrides
from .keep_alive import IntervalCaller
_LOGGER = logging.getLogger(__name__)
@@ -53,9 +53,6 @@ class UnderlyingEntityType(StrEnum):
# a valve
VALVE = "valve"
# a direct valve regulation
VALVE_REGULATION = "valve_regulation"
class UnderlyingEntity:
"""Represent a underlying device which could be a switch or a climate"""
@@ -65,7 +62,6 @@ class UnderlyingEntity:
_thermostat: Any
_entity_id: str
_type: UnderlyingEntityType
_hvac_mode: HVACMode | None
def __init__(
self,
@@ -79,7 +75,6 @@ class UnderlyingEntity:
self._thermostat = thermostat
self._type = entity_type
self._entity_id = entity_id
self._hvac_mode = None
def __str__(self):
return str(self._thermostat) + "-" + self._entity_id
@@ -105,24 +100,13 @@ class UnderlyingEntity:
async def set_hvac_mode(self, hvac_mode: HVACMode):
"""Set the HVACmode"""
self._hvac_mode = hvac_mode
return
@property
def hvac_mode(self) -> HVACMode | None:
"""Return the current hvac_mode"""
return self._hvac_mode
@property
def is_device_active(self) -> bool | None:
"""If the toggleable device is currently active."""
return None
@property
def hvac_action(self) -> HVACAction:
"""Calculate a hvac_action"""
return HVACAction.HEATING if self.is_device_active is True else HVACAction.OFF
async def set_temperature(self, temperature, max_temp, min_temp):
"""Set the target temperature"""
return
@@ -197,6 +181,7 @@ class UnderlyingSwitch(UnderlyingEntity):
_initialDelaySec: int
_on_time_sec: int
_off_time_sec: int
_hvac_mode: HVACMode
def __init__(
self,
@@ -219,6 +204,7 @@ class UnderlyingSwitch(UnderlyingEntity):
self._should_relaunch_control_heating = False
self._on_time_sec = 0
self._off_time_sec = 0
self._hvac_mode = None
self._keep_alive = IntervalCaller(hass, keep_alive_sec)
@property
@@ -251,8 +237,8 @@ class UnderlyingSwitch(UnderlyingEntity):
await self.turn_off()
self._cancel_cycle()
if self.hvac_mode != hvac_mode:
super().set_hvac_mode(hvac_mode)
if self._hvac_mode != hvac_mode:
self._hvac_mode = hvac_mode
return True
else:
return False
@@ -727,13 +713,6 @@ class UnderlyingClimate(UnderlyingEntity):
return []
return self._underlying_climate.hvac_modes
@property
def current_humidity(self) -> float | None:
"""Get the humidity"""
if not self.is_initialized:
return None
return self._underlying_climate.current_humidity
@property
def fan_modes(self) -> list[str]:
"""Get the fan_modes"""
@@ -868,16 +847,11 @@ class UnderlyingValve(UnderlyingEntity):
_hvac_mode: HVACMode
# This is the percentage of opening int integer (from 0 to 100)
_percent_open: int
_last_sent_temperature = None
def __init__(
self,
hass: HomeAssistant,
thermostat: Any,
valve_entity_id: str,
entity_type: UnderlyingEntityType = UnderlyingEntityType.VALVE,
self, hass: HomeAssistant, thermostat: Any, valve_entity_id: str
) -> None:
"""Initialize the underlying valve"""
"""Initialize the underlying switch"""
super().__init__(
hass=hass,
@@ -888,15 +862,16 @@ class UnderlyingValve(UnderlyingEntity):
self._async_cancel_cycle = None
self._should_relaunch_control_heating = False
self._hvac_mode = None
self._percent_open = None # self._thermostat.valve_open_percent
self._percent_open = self._thermostat.valve_open_percent
self._valve_entity_id = valve_entity_id
async def _send_value_to_number(self, number_entity_id: str, value: int):
"""Send a value to a number entity"""
async def send_percent_open(self):
"""Send the percent open to the underlying valve"""
# This may fails if called after shutdown
try:
data = {"value": value}
target = {ATTR_ENTITY_ID: number_entity_id}
domain = number_entity_id.split(".")[0]
data = {"value": self._percent_open}
target = {ATTR_ENTITY_ID: self._entity_id}
domain = self._entity_id.split(".")[0]
await self._hass.services.async_call(
domain=domain,
service=SERVICE_SET_VALUE,
@@ -908,11 +883,6 @@ class UnderlyingValve(UnderlyingEntity):
# This could happens in unit test if input_number domain is not yet loaded
# raise err
async def send_percent_open(self):
"""Send the percent open to the underlying valve"""
# This may fails if called after shutdown
return await self._send_value_to_number(self._entity_id, self._percent_open)
async def turn_off(self):
"""Turn heater toggleable device off."""
_LOGGER.debug("%s - Stopping underlying valve entity %s", self, self._entity_id)
@@ -924,7 +894,7 @@ class UnderlyingValve(UnderlyingEntity):
async def turn_on(self):
"""Nothing to do for Valve because it cannot be turned on"""
await self.set_valve_open_percent()
self.set_valve_open_percent()
async def set_hvac_mode(self, hvac_mode: HVACMode) -> bool:
"""Set the HVACmode. Returns true if something have change"""
@@ -962,8 +932,11 @@ class UnderlyingValve(UnderlyingEntity):
force=False,
):
"""We use this function to change the on_percent"""
# if force:
await self.set_valve_open_percent()
if force:
# self._percent_open = self.cap_sent_value(self._percent_open)
# await self.send_percent_open()
# avoid to send 2 times the same value at startup
self.set_valve_open_percent()
@overrides
def cap_sent_value(self, value) -> float:
@@ -996,7 +969,7 @@ class UnderlyingValve(UnderlyingEntity):
return new_value
async def set_valve_open_percent(self):
def set_valve_open_percent(self):
"""Update the valve open percent"""
caped_val = self.cap_sent_value(self._thermostat.valve_open_percent)
if self._percent_open == caped_val:
@@ -1010,181 +983,8 @@ class UnderlyingValve(UnderlyingEntity):
"%s - Setting valve ouverture percent to %s", self, self._percent_open
)
# Send the change to the valve, in background
# self._hass.create_task(self.send_percent_open())
await self.send_percent_open()
self._hass.create_task(self.send_percent_open())
def remove_entity(self):
"""Remove the entity after stopping its cycle"""
self._cancel_cycle()
class UnderlyingValveRegulation(UnderlyingValve):
"""A specific underlying class for Valve regulation"""
def __init__(
self,
hass: HomeAssistant,
thermostat: Any,
offset_calibration_entity_id: str,
opening_degree_entity_id: str,
closing_degree_entity_id: str,
climate_underlying: UnderlyingClimate,
) -> None:
"""Initialize the underlying TRV with valve regulation"""
super().__init__(
hass,
thermostat,
opening_degree_entity_id,
entity_type=UnderlyingEntityType.VALVE_REGULATION,
)
self._offset_calibration_entity_id: str = offset_calibration_entity_id
self._opening_degree_entity_id: str = opening_degree_entity_id
self._closing_degree_entity_id: str = closing_degree_entity_id
self._climate_underlying = climate_underlying
self._is_min_max_initialized: bool = False
self._max_opening_degree: float = None
self._min_offset_calibration: float = None
self._max_offset_calibration: float = None
async def send_percent_open(self):
"""Send the percent open to the underlying valve"""
if not self._is_min_max_initialized:
_LOGGER.debug(
"%s - initialize min offset_calibration and max open_degree", self
)
self._max_opening_degree = self._hass.states.get(
self._opening_degree_entity_id
).attributes.get("max")
if self.have_offset_calibration_entity:
self._min_offset_calibration = self._hass.states.get(
self._offset_calibration_entity_id
).attributes.get("min")
self._max_offset_calibration = self._hass.states.get(
self._offset_calibration_entity_id
).attributes.get("max")
self._is_min_max_initialized = self._max_opening_degree is not None and (
not self.have_offset_calibration_entity
or (
self._min_offset_calibration is not None
and self._max_offset_calibration is not None
)
)
if not self._is_min_max_initialized:
_LOGGER.warning(
"%s - impossible to initialize max_opening_degree or min_offset_calibration. Abort sending percent open to the valve. This could be a temporary message at startup."
)
return
# Send opening_degree
await super().send_percent_open()
# Send closing_degree if set
closing_degree = None
if self.have_closing_degree_entity:
await self._send_value_to_number(
self._closing_degree_entity_id,
closing_degree := self._max_opening_degree - self._percent_open,
)
# send offset_calibration to the difference between target temp and local temp
offset = None
if self.have_offset_calibration_entity:
if (
(local_temp := self._climate_underlying.underlying_current_temperature)
is not None
and (room_temp := self._thermostat.current_temperature) is not None
and (
current_offset := get_safe_float(
self._hass, self._offset_calibration_entity_id
)
)
is not None
):
offset = min(
self._max_offset_calibration,
max(
self._min_offset_calibration,
room_temp - (local_temp - current_offset),
),
)
await self._send_value_to_number(
self._offset_calibration_entity_id, offset
)
_LOGGER.debug(
"%s - valve regulation - I have sent offset_calibration=%s opening_degree=%s closing_degree=%s",
self,
offset,
self._percent_open,
closing_degree,
)
@property
def offset_calibration_entity_id(self) -> str:
"""The offset_calibration_entity_id"""
return self._offset_calibration_entity_id
@property
def opening_degree_entity_id(self) -> str:
"""The offset_calibration_entity_id"""
return self._opening_degree_entity_id
@property
def closing_degree_entity_id(self) -> str:
"""The offset_calibration_entity_id"""
return self._closing_degree_entity_id
@property
def have_closing_degree_entity(self) -> bool:
"""Return True if the underlying have a closing_degree entity"""
return self._closing_degree_entity_id is not None
@property
def have_offset_calibration_entity(self) -> bool:
"""Return True if the underlying have a offset_calibration entity"""
return self._offset_calibration_entity_id is not None
@property
def hvac_modes(self) -> list[HVACMode]:
"""Get the hvac_modes"""
if not self.is_initialized:
return []
return [HVACMode.OFF, HVACMode.HEAT]
@overrides
async def start_cycle(
self,
hvac_mode: HVACMode,
_1,
_2,
_3,
force=False,
):
"""We use this function to change the on_percent"""
# if force:
await self.set_valve_open_percent()
@property
def is_device_active(self):
"""If the opening valve is open."""
try:
return get_safe_float(self._hass, self._opening_degree_entity_id) > 0
except Exception: # pylint: disable=broad-exception-caught
return False
@property
def valve_entity_ids(self) -> [str]:
"""get an arrary with all entityd id of the valve"""
ret = []
for entity in [
self.opening_degree_entity_id,
self.closing_degree_entity_id,
self.offset_calibration_entity_id,
]:
if entity:
ret.append(entity)
return ret

View File

@@ -15,7 +15,6 @@ from .const import (
CONF_SAFETY_MODE,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_MAX_ON_PERCENT,
)
VTHERM_API_NAME = "vtherm_api"
@@ -61,7 +60,6 @@ class VersatileThermostatAPI(dict):
self._central_mode_select = None
# A dict that will store all Number entities which holds the temperature
self._number_temperatures = dict()
self._max_on_percent = None
def find_central_configuration(self):
"""Search for a central configuration"""
@@ -109,12 +107,6 @@ class VersatileThermostatAPI(dict):
if self._safety_mode:
_LOGGER.debug("We have found safet_mode params %s", self._safety_mode)
self._max_on_percent = config.get(CONF_MAX_ON_PERCENT)
if self._max_on_percent:
_LOGGER.debug(
"We have found max_on_percent setting %s", self._max_on_percent
)
def register_central_boiler(self, central_boiler_entity):
"""Register the central boiler entity. This is used by the CentralBoilerBinarySensor
class to register itself at creation"""
@@ -158,11 +150,10 @@ class VersatileThermostatAPI(dict):
return entity.state
return None
async def init_vtherm_links(self, entry_id=None):
async def init_vtherm_links(self):
"""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, ...)
If entry_id is set, only the VTherm of this entry will be reloaded
"""
await self.reload_central_boiler_binary_listener()
await self.reload_central_boiler_entities_list()
@@ -179,14 +170,12 @@ class VersatileThermostatAPI(dict):
# ):
# await entity.init_presets(self.find_central_configuration())
# A little hack to test if the climate is a VTherm. Cannot use isinstance
# due to circular dependency of BaseThermostat
# A little hack to test if the climate is a VTherm. Cannot use isinstance due to circular dependency of BaseThermostat
if (
entity.device_info
and entity.device_info.get("model", None) == DOMAIN
):
if entry_id is None or entry_id == entity.unique_id:
await entity.async_startup(self.find_central_configuration())
await entity.async_startup(self.find_central_configuration())
async def init_vtherm_preset_with_central(self):
"""Init all VTherm presets when the VTherm uses central temperature"""
@@ -250,11 +239,6 @@ class VersatileThermostatAPI(dict):
"""Get the safety_mode params"""
return self._safety_mode
@property
def max_on_percent(self):
"""Get the max_open_percent params"""
return self._max_on_percent
@property
def central_boiler_entity(self):
"""Get the central boiler binary_sensor entity"""

View File

@@ -1,211 +0,0 @@
# Some Essential Add-Ons
- [Some Essential Add-Ons](#some-essential-add-ons)
- [the Versatile Thermostat UI Card](#the-versatile-thermostat-ui-card)
- [the Scheduler Component!](#the-scheduler-component)
- [Regulation curves with Plotly to Fine-Tune Your Thermostat](#regulation-curves-with-plotly-to-fine-tune-your-thermostat)
- [Event notification with the AppDaemon NOTIFIER](#event-notification-with-the-appdaemon-notifier)
## the Versatile Thermostat UI Card
A dedicated card for the Versatile Thermostat has been developed (based on Better Thermostat). It is available here: [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card) and offers a modern view of all the VTherm statuses:
![image](https://github.com/jmcollin78/versatile-thermostat-ui-card/blob/master/assets/1.png?raw=true)
## the Scheduler Component!
To make the most out of the Versatile Thermostat, I recommend using it with the [Scheduler Component](https://github.com/nielsfaber/scheduler-component). The scheduler component provides climate scheduling based on predefined modes. While this feature is somewhat limited with the generic thermostat, it becomes very powerful when paired with the Versatile Thermostat.
Assuming you have installed both the Versatile Thermostat and the Scheduler Component, heres an example:
In Scheduler, add a schedule:
![image](https://user-images.githubusercontent.com/1717155/119146454-ee1a9d80-ba4a-11eb-80ae-3074c3511830.png)
Choose the "Climate" group, select one (or more) entity, pick "MAKE SCHEME," and click next:
(You can also choose "SET PRESET," but I prefer "MAKE SCHEME.")
![image](https://user-images.githubusercontent.com/1717155/119147210-aa746380-ba4b-11eb-8def-479a741c0ba7.png)
Define your mode scheme and save:
![image](https://user-images.githubusercontent.com/1717155/119147784-2f5f7d00-ba4c-11eb-9de4-5e62ff5e71a8.png)
In this example, I set ECO mode during the night and when no one is home during the day, BOOST in the morning, and COMFORT in the evening.
I hope this example helps; feel free to share your feedback!
## Regulation curves with Plotly to Fine-Tune Your Thermostat
You can obtain a curve similar to the one shown in [some results](#some-results) using a Plotly graph configuration by leveraging the thermostat's custom attributes described [here](#custom-attributes):
Replace the values between `[[ ]]` with your own.
<details>
```yaml
- type: custom:plotly-graph
entities:
- entity: '[[climate]]'
attribute: temperature
yaxis: y1
name: Consigne
- entity: '[[climate]]'
attribute: current_temperature
yaxis: y1
name:
- entity: '[[climate]]'
attribute: ema_temp
yaxis: y1
name: Ema
- entity: '[[climate]]'
attribute: on_percent
yaxis: y2
name: Power percent
fill: tozeroy
fillcolor: rgba(200, 10, 10, 0.3)
line:
color: rgba(200, 10, 10, 0.9)
- entity: '[[slope]]'
name: Slope
fill: tozeroy
yaxis: y9
fillcolor: rgba(100, 100, 100, 0.3)
line:
color: rgba(100, 100, 100, 0.9)
hours_to_show: 4
refresh_interval: 10
height: 800
config:
scrollZoom: true
layout:
margin:
r: 50
legend:
x: 0
'y': 1.2
groupclick: togglegroup
title:
side: top right
yaxis:
visible: true
position: 0
yaxis2:
visible: true
position: 0
fixedrange: true
range:
- 0
- 1
yaxis9:
visible: true
fixedrange: false
range:
- -2
- 2
position: 1
xaxis:
rangeselector:
'y': 1.1
x: 0.7
buttons:
- count: 1
step: hour
- count: 12
step: hour
- count: 1
step: day
- count: 7
step: day
```
</details>
Example of curves obtained with Plotly:
![image](images/plotly-curves.png)
## Event notification with the AppDaemon NOTIFIER
This automation leverages the excellent AppDaemon app named NOTIFIER, developed by Horizon Domotique, demonstrated [here](https://www.youtube.com/watch?v=chJylIK0ASo&ab_channel=HorizonDomotique), and the code is available [here](https://github.com/jlpouffier/home-assistant-config/blob/master/appdaemon/apps/notifier.py). It allows users to be notified of security-related events occurring on any Versatile Thermostat.
This is a great example of using the notifications described here: [notification](#notifications).
<details>
```yaml
alias: Surveillance Mode Sécurité chauffage
description: Envoi une notification si un thermostat passe en mode sécurité ou power
trigger:
- platform: event
event_type: versatile_thermostat_security_event
id: versatile_thermostat_security_event
- platform: event
event_type: versatile_thermostat_power_event
id: versatile_thermostat_power_event
- platform: event
event_type: versatile_thermostat_temperature_event
id: versatile_thermostat_temperature_event
condition: []
action:
- choose:
- conditions:
- condition: trigger
id: versatile_thermostat_security_event
sequence:
- event: NOTIFIER
event_data:
action: send_to_jmc
title: >-
Radiateur {{ trigger.event.data.name }} - {{
trigger.event.data.type }} Sécurité
message: >-
Le radiateur {{ trigger.event.data.name }} est passé en {{
trigger.event.data.type }} sécurité car le thermomètre ne répond
plus.\n{{ trigger.event.data }}
callback:
- title: Stopper chauffage
event: stopper_chauffage
image_url: /media/local/alerte-securite.jpg
click_url: /lovelace-chauffage/4
icon: mdi:radiator-off
tag: radiateur_security_alerte
persistent: true
- conditions:
- condition: trigger
id: versatile_thermostat_power_event
sequence:
- event: NOTIFIER
event_data:
action: send_to_jmc
title: >-
Radiateur {{ trigger.event.data.name }} - {{
trigger.event.data.type }} Délestage
message: >-
Le radiateur {{ trigger.event.data.name }} est passé en {{
trigger.event.data.type }} délestage car la puissance max est
dépassée.\n{{ trigger.event.data }}
callback:
- title: Stopper chauffage
event: stopper_chauffage
image_url: /media/local/alerte-delestage.jpg
click_url: /lovelace-chauffage/4
icon: mdi:radiator-off
tag: radiateur_power_alerte
persistent: true
- conditions:
- condition: trigger
id: versatile_thermostat_temperature_event
sequence:
- event: NOTIFIER
event_data:
action: send_to_jmc
title: >-
Le thermomètre du radiateur {{ trigger.event.data.name }} ne
répond plus
message: >-
Le thermomètre du radiateur {{ trigger.event.data.name }} ne
répond plus depuis longtemps.\n{{ trigger.event.data }}
image_url: /media/local/thermometre-alerte.jpg
click_url: /lovelace-chauffage/4
icon: mdi:radiator-disabled
tag: radiateur_thermometre_alerte
persistent: true
mode: queued
max: 30
```
</details>

View File

@@ -1,67 +0,0 @@
# The Different Algorithms Used
- [The Different Algorithms Used](#the-different-algorithms-used)
- [The TPI Algorithm](#the-tpi-algorithm)
- [Configuring the TPI Algorithm Coefficients](#configuring-the-tpi-algorithm-coefficients)
- [Principle](#principle)
- [The Self-Regulation Algorithm (Without Valve Control)](#the-self-regulation-algorithm-without-valve-control)
- [The Auto-Start/Stop Function Algorithm](#the-auto-startstop-function-algorithm)
## The TPI Algorithm
### Configuring the TPI Algorithm Coefficients
If you have selected a thermostat of type `over_switch`, `over_valve`, or `over_climate` with self-regulation in `Direct Valve Control` mode and choose the "TPI" option in the menu, you will land on this page:
![image](images/config-tpi.png)
You need to provide:
1. the coefficient `coef_int` for the TPI algorithm,
2. the coefficient `coef_ext` for the TPI algorithm.
### Principle
The TPI algorithm calculates the On vs Off percentage for the radiator at each cycle, using the target temperature, the current room temperature, and the current outdoor temperature. This algorithm is only applicable for Versatile Thermostats operating in `over_switch` and `over_valve` modes.
The percentage is calculated using this formula:
on_percent = coef_int * (target_temperature - current_temperature) + coef_ext * (target_temperature - outdoor_temperature)
Then, the algorithm ensures that 0 <= on_percent <= 1.
The default values for `coef_int` and `coef_ext` are `0.6` and `0.01`, respectively. These default values are suitable for a standard well-insulated room.
When adjusting these coefficients, keep the following in mind:
1. **If the target temperature is not reached** after stabilization, increase `coef_ext` (the `on_percent` is too low),
2. **If the target temperature is exceeded** after stabilization, decrease `coef_ext` (the `on_percent` is too high),
3. **If reaching the target temperature is too slow**, increase `coef_int` to provide more power to the heater,
4. **If reaching the target temperature is too fast and oscillations occur** around the target, decrease `coef_int` to provide less power to the radiator.
In `over_valve` mode, the `on_percent` value is converted to a percentage (0 to 100%) and directly controls the valve's opening level.
## The Self-Regulation Algorithm (Without Valve Control)
The self-regulation algorithm can be summarized as follows:
1. Initialize the target temperature as the VTherm setpoint,
2. If self-regulation is enabled:
1. Calculate the regulated temperature (valid for a VTherm),
2. Use this temperature as the target,
3. For each underlying device of the VTherm:
1. If "Use Internal Temperature" is checked:
1. Calculate the compensation (`trv_internal_temp - room_temp`),
2. Add the offset to the target temperature,
3. Send the target temperature (= regulated_temp + (internal_temp - room_temp)) to the underlying device.
## The Auto-Start/Stop Function Algorithm
The algorithm used in the auto-start/stop function operates as follows:
1. If "Enable Auto-Start/Stop" is off, stop here.
2. If VTherm is on and in Heating mode, when `error_accumulated` < `-error_threshold` -> turn off and save HVAC mode.
3. If VTherm is on and in Cooling mode, when `error_accumulated` > `error_threshold` -> turn off and save HVAC mode.
4. If VTherm is off and the saved HVAC mode is Heating, and `current_temperature + slope * dt <= target_temperature`, turn on and set the HVAC mode to the saved mode.
5. If VTherm is off and the saved HVAC mode is Cooling, and `current_temperature + slope * dt >= target_temperature`, turn on and set the HVAC mode to the saved mode.
6. `error_threshold` is set to `10 (° * min)` for slow detection, `5` for medium, and `2` for fast.
`dt` is set to `30 min` for slow, `15 min` for medium, and `7 min` for fast detection levels.
The function is detailed [here](https://github.com/jmcollin78/versatile_thermostat/issues/585).

View File

@@ -1,45 +0,0 @@
- [Choosing Basic Attributes](#choosing-basic-attributes)
- [Choosing the features to Use](#choosing-the-features-to-use)
# Choosing Basic Attributes
Select the "Main Attributes" menu.
![image](images/config-main.png)
Provide the mandatory main attributes. These attributes are common to all VTherms:
1. A name (this will be both the integration's name and the `climate` entity name),
2. An entity ID of a temperature sensor that provides the room temperature where the radiator is installed,
3. An optional sensor entity providing the last seen date and time of the sensor (`last_seen`). If available, specify it here. It helps prevent safety shutdowns when the temperature is stable, and the sensor stops reporting for a long time (see [here](troubleshooting.md#why-does-my-versatile-thermostat-go-into-safety-mode)),
4. A cycle duration in minutes. At each cycle:
1. For `over_switch`: VTherm will turn the radiator on/off, modulating the proportion of time it is on,
2. For `over_valve`: VTherm will calculate a new valve opening level and send it if it has changed,
3. For `over_climate`: The cycle performs basic controls and recalculates the self-regulation coefficients. The cycle may result in a new setpoint sent to underlying devices or a valve opening adjustment in the case of a controllable TRV.
5. The equipment's power, which will activate power and energy consumption sensors for the device. If multiple devices are linked to the same VTherm, specify the total maximum power of all devices here,
6. The option to use additional parameters from centralized configuration:
1. Outdoor temperature sensor,
2. Minimum/maximum temperature and temperature step size,
7. The option to control the thermostat centrally. See [centralized control](#centralized-control),
8. A checkbox if this VTherm is used to trigger a central boiler.
> ![Tip](images/tips.png) _*Notes*_
> 1. With the `over_switch` and `over_valve` types, calculations are performed at each cycle. In case of changing conditions, you will need to wait for the next cycle to see a change. For this reason, the cycle should not be too long. **5 minutes is a good value**, but it should be adjusted to your heating type. The greater the inertia, the longer the cycle should be. See [Tuning examples](tuning-examples.md).
> 2. If the cycle is too short, the radiator may never reach the target temperature. For example, with a storage heater, it will be unnecessarily activated.
# Choosing the features to Use
Select the "Features" menu.
![image](images/config-features.png)
Choose the features you want to use for this VTherm:
1. **Opening detection** (doors, windows) stops heating when an opening is detected. (see [managing openings](feature-window.md)),
2. **Motion detection**: VTherm can adjust the target temperature when motion is detected in the room. (see [motion detection](feature-motion.md)),
3. **Power management**: VTherm can stop a device if the power consumption in your home exceeds a threshold. (see [load-shedding management](feature-power.md)),
4. **Presence detection**: If you have a sensor indicating presence or absence in your home, you can use it to change the target temperature. See [presence management](feature-presence.md). Note the difference between this function and motion detection: presence is typically used at the home level, while motion detection is more room-specific.
5. **Automatic start/stop**: For `over_climate` VTherms only. This function stops a device when VTherm detects it will not be needed for a while. It uses the temperature curve to predict when the device will be needed again and turns it back on at that time. See [automatic start/stop management](feature-auto-start-stop.md).
> ![Tip](images/tips.png) _*Notes*_
> 1. The list of available functions adapts to your VTherm type.
> 2. When you enable a function, a new menu entry is added to configure it.
> 3. You cannot validate the creation of a VTherm if all parameters for all enabled functions have not been configured.

View File

@@ -1,71 +0,0 @@
# Choosing a VTherm
- [Choosing a VTherm](#choosing-a-vtherm)
- [Creating a New Versatile Thermostat](#creating-a-new-versatile-thermostat)
- [Choosing a VTherm Type](#choosing-a-vtherm-type)
- [Centralized configuration](#centralized-configuration)
- [VTherm over a switch](#vtherm-over-a-switch)
- [VTherm over another thermostat](#vtherm-over-another-thermostat)
- [VTherm over a valve](#vtherm-over-a-valve)
- [Making the right choice](#making-the-right-choice)
- [Reference Article](#reference-article)
> ![Tip](images/tips.png) _*Notes*_
>
> There are three ways to work with VTherms:
> 1. Each Versatile Thermostat is fully configured independently. Choose this option if you do not want any centralized configuration or management.
> 2. Some aspects are configured centrally. For example, you can define the minimum/maximum temperatures, open window detection parameters, etc., at a single central instance. For each VTherm you configure, you can then choose to use the central configuration or override it with custom parameters.
> 3. In addition to centralized configuration, all VTherms can be controlled by a single `select` entity called `central_mode`. This feature allows you to stop/start/set frost protection/etc. for all VTherms at once. For each VTherm, you can specify if it is affected by this `central_mode`.
## Creating a New Versatile Thermostat
Click on "Add Integration" on the integration page (or click 'Add device' in the integration page)
![image](images/add-an-integration.png)
then:
![image](images/config-main0.png)
The configuration can be modified via the same interface. Simply select the thermostat to modify, press "Configure," and you will be able to change some parameters or settings.
Follow the configuration steps by selecting the menu option to configure.
# Choosing a VTherm Type
## Centralized configuration
This option allows you to configure certain repetitive aspects for all VTherms at once, such as:
1. Parameters for different algorithms (TPI, open window detection, motion detection, power sensors for your home, presence detection). These parameters apply across all VTherms. You only need to enter them once in `Centralized Configuration`. This configuration does not create a VTherm itself but centralizes parameters that would be tedious to re-enter for each VTherm. Note that you can override these parameters on individual VTherms to specialize them if needed.
2. Configuration for controlling a central heating system,
3. Certain advanced parameters, such as safety settings.
## VTherm over a switch
This VTherm type controls a switch that turns a radiator on or off. The switch can be a physical switch directly controlling a radiator (often electric) or a virtual switch that can perform any action when turned on or off. The latter type can, for example, control pilot wire switches or DIY pilot wire solutions with diodes. VTherm modulates the proportion of time the radiator is on (`on_percent`) to achieve the desired temperature. If it is cold, it turns on more frequently (up to 100%); if it is warm, it reduces the on time.
The underlying entities for this type are `switches` or `input_booleans`.
## VTherm over another thermostat
When your device is controlled by a `climate` entity in Home Assistant and you only have access to this, you should use this VTherm type. In this case, VTherm simply adjusts the target temperature of the underlying `climate` entity.
This type also includes advanced self-regulation features to adjust the setpoint sent to the underlying device, helping to achieve the target temperature faster and mitigating poor internal regulation. For example, if the device's internal thermometer is too close to the heating element, it may incorrectly assume the room is warm while the setpoint is far from being achieved in other areas.
Since version 6.8, this VTherm type can also regulate directly by controlling the valve. Ideal for controllable TRVs, this type is recommended if you have such devices.
The underlying entities for this VTherm type are exclusively `climate`.
## VTherm over a valve
If the only entity available to regulate your radiator's temperature is a `number` entity, you should use the `over_valve` type. VTherm adjusts the valve opening based on the difference between the target temperature and the actual room temperature (and the outdoor temperature, if available).
This type can be used for TRVs without an associated `climate` entity or other DIY solutions exposing a `number` entity.
# Making the right choice
> ![Tip](images/tips.png) _*How to Choose the Type*_
> Choosing the correct type is crucial. It cannot be changed later via the configuration interface. To make the right choice, consider the following questions:
> 1. **What type of equipment will I control?** Follow this order of preference:
> 1. If you have a controllable thermostatic valve (TRV) in Home Assistant through a `number` entity (e.g., a Shelly TRV), choose the `over_valve` type. This is the most direct type and ensures the best regulation.
> 2. If you have an electric radiator (with or without a pilot wire) controlled by a `switch` entity to turn it on/off, then the `over_switch` type is preferable. Regulation will be managed by the Versatile Thermostat based on the temperature measured by your thermometer at its placement location.
> 3. In all other cases, use the `over_climate` mode. You retain your original `climate` entity, and the Versatile Thermostat "only" controls the on/off state and target temperature of your original thermostat. Regulation is handled by your original thermostat in this case. This mode is particularly suited for all-in-one reversible air conditioning systems exposed as a `climate` entity in Home Assistant. Advanced self-regulation can achieve the setpoint faster by forcing the setpoint or directly controlling the valve when possible.
> 2. **What type of regulation do I want?** If the controlled equipment has its own built-in regulation mechanism (e.g., HVAC systems, certain TRVs) and it works well, choose `over_climate`. For TRVs with a controllable valve in Home Assistant, the `over_climate` type with `Direct Valve Control` self-regulation is the best choice.
# Reference Article
For more information on these concepts, refer to this article (in French): https://www.hacf.fr/optimisation-versatile-thermostat/#optimiser-vos-vtherm

View File

@@ -1,53 +0,0 @@
# Advanced Configuration
- [Advanced Configuration](#advanced-configuration)
- [Advanced Settings](#advanced-settings)
- [Minimum Activation Delay](#minimum-activation-delay)
- [Safety Mode](#safety-mode)
These settings refine the thermostat's operation, particularly the safety mechanism for a _VTherm_. Missing temperature sensors (room or outdoor) can pose a risk to your home. For instance, if the temperature sensor gets stuck at 10°C, the `over_climate` or `over_valve` _VTherm_ types will command maximum heating of the underlying devices, which could lead to room overheating or even property damage, at worst resulting in a fire hazard.
To prevent this, _VTherm_ ensures that thermometers report values regularly. If they don't, the _VTherm_ switches to a special mode called Safety Mode. This mode ensures minimal heating to prevent the opposite risk: a completely unheated home in the middle of winter, for example.
The challenge lies in that some thermometers—especially battery-operated ones—only send temperature updates when the value changes. It is entirely possible to receive no temperature updates for hours without the thermometer failing. The parameters below allow fine-tuning of the thresholds for activating Safety Mode.
If your thermometer has a `last seen` attribute indicating the last contact time, you can specify it in the _VTherm_'s main attributes to greatly reduce false Safety Mode activations. See [configuration](base-attributes.md#choosing-base-attributes) and [troubleshooting](troubleshooting.md#why-does-my-versatile-thermostat-switch-to-safety-mode).
For `over_climate` _VTherms_, which self-regulate, Safety Mode is disabled. In this case, there is no danger, only the risk of an incorrect temperature.
## Advanced Settings
The advanced configuration form looks like this:
![image](images/config-advanced.png)
### Minimum Activation Delay
The first delay (`minimal_activation_delay_sec`) in seconds is the minimum acceptable delay to turn on the heating. If the calculated activation time is shorter than this value, the heating remains off. This parameter only applies to _VTherm_ with cyclic triggering `over_switch`. If the activation time is too short, rapid switching will not allow the device to heat up properly.
### Safety Mode
The second delay (`security_delay_min`) is the maximum time between two temperature measurements before the _VTherm_ switches to Safety Mode.
The third parameter (`security_min_on_percent`) is the minimum `on_percent` below which Safety Mode will not be activated. This setting prevents activating Safety Mode if the controlled radiator does not heat sufficiently. In this case, there is no physical risk to the home, only the risk of overheating or underheating.
Setting this parameter to `0.00` will trigger Safety Mode regardless of the last heating setting, whereas `1.00` will never trigger Safety Mode (effectively disabling the feature). This can be useful to adapt the safety mechanism to your specific needs.
The fourth parameter (`security_default_on_percent`) defines the `on_percent` used when the thermostat switches to `security` mode. Setting it to `0` will turn off the thermostat in Safety Mode, while setting it to a value like `0.2` (20%) ensures some heating remains, avoiding a completely frozen home in case of a thermometer failure.
It is possible to disable Safety Mode triggered by missing data from the outdoor thermometer. Since the outdoor thermometer usually has a minor impact on regulation (depending on your configuration), it might not be critical if it's unavailable. To do this, add the following lines to your `configuration.yaml`:
```yaml
versatile_thermostat:
...
safety_mode:
check_outdoor_sensor: false
```
By default, the outdoor thermometer can trigger Safety Mode if it stops sending data. Remember that Home Assistant must be restarted for these changes to take effect. This setting applies to all _VTherms_ sharing the outdoor thermometer.
> ![Tip](images/tips.png) _*Notes*_
> 1. When the temperature sensor resumes reporting, the preset will be restored to its previous value.
> 2. Two temperature sources are required: the indoor and outdoor temperatures. Both must report values, or the thermostat will switch to "security" preset.
> 3. An action is available to adjust the three safety parameters. This can help adapt Safety Mode to your needs.
> 4. For normal use, `security_default_on_percent` should be lower than `security_min_on_percent`.
> 5. If you use the Versatile Thermostat UI card (see [here](additions.md#better-with-the-versatile-thermostat-ui-card)), a _VTherm_ in Safety Mode is indicated by a gray overlay showing the faulty thermometer and the time since its last value update: ![safety mode](images/safety-mode-icon.png).

View File

@@ -1,39 +0,0 @@
# Auto-start / Auto-stop
- [Auto-start / Auto-stop](#auto-start--auto-stop)
- [Configure Auto-start/stop](#configure-auto-startstop)
- [Usage](#usage)
This feature allows _VTherm_ to stop an appliance that doesn't need to be on and restart it when conditions require it. This function includes three settings that control how quickly the appliance is stopped and restarted.
Exclusively reserved for _VTherm_ of type `over_climate`, it applies to the following use case:
1. Your appliance is permanently powered on and consumes electricity even when heating (or cooling) is not needed. This is often the case with heat pumps (_PAC_) that consume power even in standby mode.
2. The temperature conditions are such that heating (or cooling) is not needed for a long period: the setpoint is higher (or lower) than the room temperature.
3. The temperature rises (or falls), remains stable, or falls (or rises) slowly.
In such cases, it is preferable to ask the appliance to turn off to avoid unnecessary power consumption in standby mode.
## Configure Auto-start/stop
To use this feature, you need to:
1. Add the `With auto-start and stop` function in the 'Functions' menu.
2. Set the detection level in the 'Auto-start/stop' option that appears when the function is activated. Choose the detection level between 'Slow', 'Medium', and 'Fast'. With the 'Fast' setting, stops and restarts will occur more frequently.
![image](images/config-auto-start-stop.png)
The 'Slow' setting allows about 30 minutes between a stop and a restart,
The 'Medium' setting sets the threshold to about 15 minutes, and the 'Fast' setting puts it at 7 minutes.
Note that these are not absolute settings since the algorithm takes into account the slope of the room temperature curve to respond accordingly. It is still possible that a restart occurs shortly after a stop if the temperature drops significantly.
## Usage
Once the function is configured, you will now have a new `switch` type entity that allows you to enable or disable auto-start/stop without modifying the configuration. This entity is available on the _VTherm_ device and is named `switch.<name>_enable_auto_start_stop`.
![image](images/enable-auto-start-stop-entity.png)
Check the box to allow auto-start and auto-stop, and leave it unchecked to disable the feature.
> ![Tip](images/tips.png) _*Notes*_
> 1. The detection algorithm is described [here](algorithms.md#auto-startstop-algorithm).
> 2. Some appliances (boilers, underfloor heating, _PAC_, etc.) may not like being started/stopped too frequently. If that's the case, it might be better to disable the function when you know the appliance will be used. For example, I disable this feature during the day when presence is detected because I know my _PAC_ will turn on often. I enable auto-start/stop at night or when no one is home, as the setpoint is lowered and it rarely triggers.
> 3. If you use the Versatile Thermostat UI card (see [here](additions.md#better-with-the-versatile-thermostat-ui-card)), a checkbox is directly visible on the card to disable auto-start/stop, and a _VTherm_ stopped by auto-start/stop is indicated by the icon: ![auto-start/stop icon](images/auto-start-stop-icon.png).

View File

@@ -1,123 +0,0 @@
# Le contrôle d'une chaudière centrale# Controlling a Central Boiler
- [Le contrôle d'une chaudière centrale# Controlling a Central Boiler](#le-contrôle-dune-chaudière-centrale-controlling-a-central-boiler)
- [Principle](#principle)
- [Configuration](#configuration)
- [How to Find the Right Action?](#how-to-find-the-right-action)
- [Events](#events)
- [Warning](#warning)
You can control a centralized boiler. As long as it's possible to trigger or stop the boiler from Home Assistant, Versatile Thermostat will be able to control it directly.
## Principle
The basic principle is as follows:
1. A new entity of type `binary_sensor`, named by default `binary_sensor.central_boiler`, is added.
2. In the configuration of the _VTherms_, you specify whether the _VTherm_ should control the boiler. In a heterogeneous installation, some _VTherms_ should control the boiler, and others should not. Therefore, you need to indicate in each _VTherm_ configuration whether it controls the boiler.
3. The `binary_sensor.central_boiler` listens for state changes in the equipment of the _VTherms_ marked as controlling the boiler.
4. When the number of devices controlled by the _VTherm_ requesting heating (i.e., when its `hvac_action` changes to `Heating`) exceeds a configurable threshold, the `binary_sensor.central_boiler` turns `on`, and **if an activation service has been configured, that service is called**.
5. If the number of devices requesting heating drops below the threshold, the `binary_sensor.central_boiler` turns `off`, and **if a deactivation service has been configured, that service is called**.
6. You have access to two entities:
- A `number` type entity, named by default `number.boiler_activation_threshold`, which gives the activation threshold. This threshold is the number of devices (radiators) requesting heating.
- A `sensor` type entity, named by default `sensor.nb_device_active_for_boiler`, which shows the number of devices requesting heating. For example, a _VTherm_ with 4 valves, 3 of which request heating, will make this sensor show 3. Only the devices from _VTherms_ marked to control the central boiler are counted.
You therefore always have the information to manage and adjust the triggering of the boiler.
All these entities are linked to the central configuration service:
![Boiler Control Entities](images/entitites-central-boiler.png)
## Configuration
To configure this feature, you need a centralized configuration (see [Configuration](#configuration)) and check the 'Add Central Boiler' box:
![Add a Central Boiler](images/config-central-boiler-1.png)
On the next page, you can provide the configuration for the actions (e.g., services) to be called when the boiler is turned on/off:
![Add a Central Boiler](images/config-central-boiler-2.png)
The actions (e.g., services) are configured as described on the page:
1. The general format is `entity_id/service_id[/attribute:value]` (where `/attribute:value` is optional).
2. `entity_id` is the name of the entity controlling the boiler in the form `domain.entity_name`. For example: `switch.chaudiere` for a boiler controlled by a switch, or `climate.chaudière` for a boiler controlled by a thermostat, or any other entity that allows boiler control (there is no limitation). You can also toggle inputs (`helpers`) such as `input_boolean` or `input_number`.
3. `service_id` is the name of the service to be called in the form `domain.service_name`. For example: `switch.turn_on`, `switch.turn_off`, `climate.set_temperature`, `climate.set_hvac_mode` are valid examples.
4. Some services require a parameter. This could be the 'HVAC Mode' for `climate.set_hvac_mode` or the target temperature for `climate.set_temperature`. This parameter should be configured in the format `attribute:value` at the end of the string.
Examples (to adjust to your case):
- `climate.chaudiere/climate.set_hvac_mode/hvac_mode:heat`: to turn the boiler thermostat on in heating mode.
- `climate.chaudiere/climate.set_hvac_mode/hvac_mode:off`: to turn off the boiler thermostat.
- `switch.pompe_chaudiere/switch.turn_on`: to turn on the switch powering the boiler pump.
- `switch.pompe_chaudiere/switch.turn_off`: to turn off the switch powering the boiler pump.
- ...
### How to Find the Right Action?
To find the correct action to use, it's best to go to "Developer Tools / Services", search for the action to call, the entity to control, and any required parameters.
Click 'Call Service'. If your boiler turns on, you have the correct configuration. Then switch to YAML mode and copy the parameters.
Example:
In "Developer Tools / Actions":
![Service Configuration](images/dev-tools-turnon-boiler-1.png)
In YAML mode:
![Service Configuration](images/dev-tools-turnon-boiler-2.png)
The service to configure will then be: `climate.sonoff/climate.set_hvac_mode/hvac_mode:heat` (note the removal of spaces in `hvac_mode:heat`).
Do the same for the off service, and youre ready to go.
## Events
Each successful boiler activation or deactivation sends an event from Versatile Thermostat. This can be captured by an automation, for example, to notify you of the change.
The events look like this:
An activation event:
```yaml
event_type: versatile_thermostat_central_boiler_event
data:
central_boiler: true
entity_id: binary_sensor.central_boiler
name: Central boiler
state_attributes: null
origin: LOCAL
time_fired: "2024-01-14T11:33:52.342026+00:00"
context:
id: 01HM3VZRJP3WYYWPNSDAFARW1T
parent_id: null
user_id: null
```yaml
event_type: versatile_thermostat_central_boiler_event
data:
central_boiler: true
entity_id: binary_sensor.central_boiler
name: Central boiler
state_attributes: null
origin: LOCAL
time_fired: "2024-01-14T11:33:52.342026+00:00"
context:
id: 01HM3VZRJP3WYYWPNSDAFARW1T
parent_id: null
user_id: null
```
Un évènement d'extinction :
```yaml
event_type: versatile_thermostat_central_boiler_event
data:
central_boiler: false
entity_id: binary_sensor.central_boiler
name: Central boiler
state_attributes: null
origin: LOCAL
time_fired: "2024-01-14T11:43:52.342026+00:00"
context:
id: 01HM3VZRJP3WYYWPNSDAFBRW1T
parent_id: null
user_id: null
```
## Warning
> ![Astuce](images/tips.png) _*Notes*_
>
> Software or home automation control of a central boiler may pose risks to its proper operation. Before using these functions, ensure that your boiler has proper safety features and that they are functioning correctly. For example, turning on a boiler with all valves closed can create excessive pressure.

View File

@@ -1,31 +0,0 @@
# Centralized Control
- [Centralized Control](#centralized-control)
- [Configuration of Centralized Control](#configuration-of-centralized-control)
- [Usage](#usage)
This feature allows you to control all your _VTherms_ from a single control point.
A typical use case is when you leave for an extended period and want to set all your _VTherms_ to frost protection, and when you return, you want to set them back to their initial state.
Centralized control is done from a special _VTherm_ called centralized configuration. See [here](creation.md#centralized-configuration) for more information.
## Configuration of Centralized Control
If you have set up a centralized configuration, you will have a new entity named `select.central_mode` that allows you to control all _VTherms_ with a single action.
![central_mode](images/central-mode.png)
This entity appears as a list of choices containing the following options:
1. `Auto`: the 'normal' mode where each _VTherm_ operates autonomously,
2. `Stopped`: all _VTherms_ are turned off (`hvac_off`),
3. `Heat only`: all _VTherms_ are set to heating mode if supported, otherwise they are stopped,
4. `Cool only`: all _VTherms_ are set to cooling mode if supported, otherwise they are stopped,
5. `Frost protection`: all _VTherms_ are set to frost protection mode if supported, otherwise they are stopped.
## Usage
For a _VTherm_ to be controllable centrally, its configuration attribute named `use_central_mode` must be true. This attribute is available in the configuration page `Main Attributes`.
![central_mode](images/use-central-mode.png)
This means you can control all _VTherms_ (those explicitly designated) with a single control.

View File

@@ -1,40 +0,0 @@
# Motion or Activity Detection
- [Motion or Activity Detection](#motion-or-activity-detection)
- [Configure Activity Mode or Motion Detection](#configure-activity-mode-or-motion-detection)
- [Usage](#usage)
This feature allows you to change presets when motion is detected in a room. If you don't want to heat your office when the room is occupied and only when the room is occupied, you need a motion (or presence) sensor in the room and configure this feature.
This function is often confused with the presence feature. They are complementary but not interchangeable. The 'motion' function is local to a room equipped with a motion sensor, while the 'presence' function is designed to be global to the entire home.
## Configure Activity Mode or Motion Detection
If you have chosen the `With motion detection` feature:
![image](images/config-motion.png)
What we need:
- a **motion sensor**. Entity ID of a motion sensor. The states of the motion sensor must be "on" (motion detected) or "off" (no motion detected),
- a **detection delay** (in seconds) defining how long we wait for confirmation of the motion before considering the motion. This parameter can be **greater than your motion sensor's delay**, otherwise, the detection will happen with every motion detected by the sensor,
- an **inactivity delay** (in seconds) defining how long we wait for confirmation of no motion before no longer considering the motion,
- a **"motion" preset**. We will use the temperature of this preset when activity is detected,
- a **"no motion" preset**. We will use the temperature of this second preset when no activity is detected.
## Usage
To tell a _VTherm_ that it should listen to the motion sensor, you must set it to the special 'Activity' preset. If you have installed the Versatile Thermostat UI card (see [here](additions.md#much-better-with-the-versatile-thermostat-ui-card)), this preset is displayed as follows: ![activity preset](images/activity-preset-icon.png).
You can then, upon request, set a _VTherm_ to motion detection mode.
The behavior will be as follows:
- we have a room with a thermostat set to activity mode, the "motion" mode chosen is comfort (21.5°C), the "no motion" mode chosen is Eco (18.5°C), and the motion delay is 30 seconds on detection and 5 minutes on the end of detection.
- the room has been empty for a while (no activity detected), the setpoint temperature in this room is 18.5°.
- someone enters the room, and activity is detected if the motion is present for at least 30 seconds. The temperature then goes up to 21.5°.
- if the motion is present for less than 30 seconds (quick passage), the temperature stays at 18.5°.
- imagine the temperature has gone up to 21.5°, when the person leaves the room, after 5 minutes the temperature is returned to 18.5°.
- if the person returns before the 5 minutes, the temperature stays at 21.5°.
> ![Tip](images/tips.png) _*Notes*_
> 1. As with other presets, `Activity` will only be offered if it is correctly configured. In other words, all 4 configuration keys must be set.
> 2. If you are using the Versatile Thermostat UI card (see [here](additions.md#much-better-with-the-versatile-thermostat-ui-card)), motion detection is represented as follows: ![motion](images/motion-detection-icon.png).

View File

@@ -1,46 +0,0 @@
# Power Management - Load Shedding
- [Power Management - Load Shedding](#power-management---load-shedding)
- [Configure Power Management](#configure-power-management)
This feature allows you to regulate the electricity consumption of your heaters. Known as load shedding, this feature enables you to limit the electrical consumption of your heating device if overcapacity conditions are detected.
You will need a **sensor for the total instantaneous power consumption** of your home, as well as a **sensor for the maximum allowed power**.
The behavior of this feature is basic:
1. when the _VTherm_ is about to turn on a device,
2. it compares the last known value of the power consumption sensor with the last value of the maximum allowed power. If there is a remaining margin greater than or equal to the declared power of the _VTherm_'s devices, then the _VTherm_ and its devices will be turned on. Otherwise, they will remain off until the next cycle.
WARNING: This very basic operation **is not a safety function** but more of an optimization feature to manage consumption at the cost of heating performance. Overloads may occur depending on the frequency of updates from your consumption sensors, and the actual power used by your devices. Therefore, you must always maintain a safety margin.
Typical use case:
1. you have an electricity meter limited to 11 kW,
2. you occasionally charge an electric vehicle at 5 kW,
3. that leaves 6 kW for everything else, including heating,
4. you have 1 kW of other equipment running,
5. you have declared a sensor (`input_number`) for the maximum allowed power at 9 kW (= 11 kW - the reserve for other devices - margin)
If the vehicle is charging, the total power consumed is 6 kW (5+1), and a _VTherm_ will only turn on if its declared power is 3 kW max (9 kW - 6 kW).
If the vehicle is charging and another _VTherm_ of 2 kW is running, the total power consumed is 8 kW (5+1+2), and a _VTherm_ will only turn on if its declared power is 1 kW max (9 kW - 8 kW). Otherwise, it will wait until the next cycle.
If the vehicle is not charging, the total power consumed is 1 kW, and a _VTherm_ will only turn on if its declared power is 8 kW max (9 kW - 1 kW).
## Configure Power Management
If you have chosen the `With power detection` feature, configure it as follows:
![image](images/config-power.png)
1. the entity ID of the **instantaneous power consumption sensor** for your home,
2. the entity ID of the **maximum allowed power sensor**,
3. the temperature to apply if load shedding is activated.
Note that all power values must have the same units (kW or W, for example).
Having a **maximum allowed power sensor** allows you to adjust the maximum power over time using a scheduler or automation.
> ![Tip](images/tips.png) _*Notes*_
>
> 1. In case of load shedding, the radiator is set to the preset named `power`. This is a hidden preset, and you cannot select it manually.
> 2. Always keep a margin, as the maximum power may briefly be exceeded while waiting for the next cycle calculation, or due to unregulated equipment.
> 3. If you don't want to use this feature, uncheck it in the 'Functions' menu.
> 4. If a _VTherm_ controls multiple devices, the **electrical consumption of your heating** must match the sum of the powers.
> 5. If you are using the Versatile Thermostat UI card (see [here](additions.md#much-better-with-the-versatile-thermostat-ui-card)), load shedding is represented as follows: ![load shedding](images/power-exceeded-icon.png).

View File

@@ -1,24 +0,0 @@
# Presence / Absence Management
- [Presence / Absence Management](#presence--absence-management)
- [Configure Presence (or Absence)](#configure-presence-or-absence)
## Configure Presence (or Absence)
If this feature is selected, it allows you to dynamically adjust the preset temperatures of the thermostat when presence (or absence) is detected. To do this, you need to configure the temperature to be used for each preset when presence is disabled. When the presence sensor turns off, these temperatures will be applied. When it turns back on, the "normal" temperature configured for the preset will be used. See [preset management](feature-presets.md).
To configure presence, fill out this form:
![image](images/config-presence.png)
For this, you simply need to configure an **occupancy sensor** whose state must be 'on' or 'home' if someone is present, or 'off' or 'not_home' otherwise.
Temperatures are configured in the entities of the device corresponding to your _VTherm_ (Settings/Integration/Versatile Thermostat/the vtherm).
WARNING: People groups do not work as a presence sensor. They are not recognized as a presence sensor. You need to use a template as described here [Using a People Group as a Presence Sensor](troubleshooting.md#using-a-people-group-as-a-presence-sensor).
> ![Tip](images/tips.png) _*Notes*_
>
> 1. The temperature change is immediate and is reflected on the front panel. The calculation will consider the new target temperature at the next cycle calculation.
> 2. You can use the direct person.xxxx sensor or a Home Assistant sensor group. The presence sensor handles the states `on` or `home` as present and `off` or `not_home` as absent.
> 3. To pre-heat your home when everyone is absent, you can add an `input_boolean` entity to your people group. If you set this `input_boolean` to 'On', the presence sensor will be forced to 'On' and the presets with presence will be used. You can also set this `input_boolean` to 'On' via an automation, for example, when you leave a zone to start preheating your home.

View File

@@ -1,30 +0,0 @@
# Presets (Pre-configured Settings)
- [Presets (Pre-configured Settings)](#presets-pre-configured-settings)
- [Configure Pre-configured Temperatures](#configure-pre-configured-temperatures)
## Configure Pre-configured Temperatures
The preset mode allows you to pre-configure the target temperature. Used in conjunction with Scheduler (see [scheduler](additions#the-scheduler-component)), you'll have a powerful and simple way to optimize the temperature relative to the electricity consumption in your home. The managed presets are as follows:
- **Eco**: the device is in energy-saving mode
- **Comfort**: the device is in comfort mode
- **Boost**: the device fully opens all valves
If the AC mode is used, you can also configure temperatures when the equipment is in air conditioning mode.
**None** is always added to the list of modes, as it is a way to not use presets but instead set a **manual temperature**.
The presets are configured directly from the _VTherm_ entities or the central configuration if you're using centralized control. Upon creating the _VTherm_, you will have different entities that will allow you to set the temperatures for each preset:
![presets](images/config-preset-temp.png).
The list of entities varies depending on your feature choices:
1. If the 'presence detection' function is activated, you will have the presets with an "absence" version prefixed with _abs_.
2. If you have selected the _AC_ option, you will also have presets for 'air conditioning' prefixed with _clim_.
> ![Tip](images/tips.png) _*Notes*_
>
> 1. When you manually change the target temperature, the preset switches to None (no preset).
> 2. The standard preset `Away` is a hidden preset that cannot be directly selected. Versatile Thermostat uses presence management or motion detection to automatically and dynamically adjust the target temperature based on presence in the home or activity in the room. See [presence management](feature-presence.md).
> 3. If you're using load shedding management, you will see a hidden preset named `power`. The heating element's preset is set to "power" when overload conditions are met and load shedding is active for that heating element. See [power management](feature-power.md).
> 4. If you're using advanced configuration, you will see the preset set to `safety` if the temperature could not be retrieved after a certain delay. See [Safety Mode](feature-advanced.md#safety-mode).

View File

@@ -1,64 +0,0 @@
# Door/Window Open Detection
- [Door/Window Open Detection](#doorwindow-open-detection)
- [Sensor Mode](#sensor-mode)
- [Auto Mode](#auto-mode)
You must have selected the `With Open Detection` feature on the first page to reach this page.
Open detection can be done in two ways:
1. By using a sensor placed on the opening (sensor mode),
2. By detecting a sudden temperature drop (auto mode)
## Sensor Mode
To switch to sensor mode, you need to provide an entity of type `binary_sensor` or `input_boolean`.
In this mode, you need to fill in the following information:
![mode window sensor](images/config-window-sensor.png)
1. A **delay in seconds** before any change. This allows you to open a window quickly without stopping the heating.
2. The action to take when the opening is detected as open. The possible actions are:
1. _Turn off_: the _VTherm_ will be turned off.
2. _Fan only_: heating or cooling will be turned off, but the equipment will continue to ventilate (for compatible equipment).
3. _Frost protection_: the "Frost Protection" preset temperature will be selected on the _VTherm_ without changing the current preset (see notes below).
4. _Eco_: the "Eco" preset temperature will be applied to the _VTherm_ without changing the current preset (see notes below).
When the detector switches to open:
1. _VTherm_ waits for the specified delay.
2. If the window is still open after the delay, the _VTherm_ state (Heating / Cooling / ..., current preset, current target temperature) is saved and the action is performed.
Similarly, when the detector switches to closed:
1. _VTherm_ waits for the specified delay.
2. If the window is still closed after the delay, the state before the window opening is restored.
## Auto Mode
In auto mode, the configuration is as follows:
![image](images/config-window-auto.png)
1. A **delay in seconds** before any change. This allows you to open a window quickly without stopping the heating.
2. A detection threshold in degrees per hour. When the temperature drops beyond this threshold, the thermostat will turn off. The lower this value, the faster the detection (with a higher risk of false positives).
3. A threshold for ending detection in degrees per hour. When the temperature drop exceeds this value, the thermostat will return to the previous mode (mode and preset).
4. A maximum detection duration. Beyond this duration, the thermostat will return to its previous mode and preset even if the temperature continues to drop.
5. The action to take when the opening is detected as open. The actions are the same as in sensor mode described above.
To adjust the thresholds, it is recommended to start with the reference values and adjust the detection thresholds. Some tests gave me the following values (for an office):
- Detection threshold: 3°C/hour
- No detection threshold: 0°C/hour
- Max duration: 30 min.
A new sensor called "slope" has been added for all thermostats. It provides the slope of the temperature curve in °C/hour (or °K/hour). This slope is smoothed and filtered to avoid aberrant thermometer values that could interfere with the measurement.
![image](images/temperature-slope.png)
To adjust it properly, it is recommended to display both the temperature curve and the slope of the curve ("slope") on the same historical graph:
![image](images/window-auto-tuning.png)
> ![Tip](images/tips.png) _*Notes*_
>
> 1. If you want to use **multiple door/window sensors** to automate your thermostat, simply create a group with the usual behavior (https://www.home-assistant.io/integrations/binary_sensor.group/)
> 2. If you don't have a door/window sensor in your room, simply leave the sensor entity ID empty.
> 3. **Only one mode is allowed**. You cannot configure a thermostat with both a sensor and auto detection. The two modes might contradict each other, so both modes cannot be active at the same time.
> 4. It is not recommended to use auto mode for equipment subjected to frequent and normal temperature variations (hallways, open areas, etc.).
> 5. To avoid interfering with your current preset settings, the actions _Frost protection_ and _Eco_ change the target temperature without changing the preset. So, you may notice a discrepancy between the selected preset and the setpoint.
> 6. If you use the Versatile Thermostat UI card (see [here](additions.md#even-better-with-the-versatile-thermostat-ui-card)), open detection is represented as follows: ![window](images/window-detection-icon.png).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1,19 +0,0 @@
# How to Install Versatile Thermostat?
## HACS Installation (Recommended)
1. Install [HACS](https://hacs.xyz/). This way, you will automatically receive updates.
2. The Versatile Thermostat integration is now available directly from the HACS interface (Integrations tab).
3. Search for and install "Versatile Thermostat" in HACS and click "Install".
4. Restart Home Assistant.
5. Then, you can add a Versatile Thermostat integration in the Settings / Integrations page. Add as many thermostats as needed (usually one per radiator or group of radiators that need to be controlled, or per pump in the case of a centralized heating system).
## Manual Installation
1. Using your tool of choice, open your Home Assistant configuration directory (where you will find `configuration.yaml`).
2. If you don't have a `custom_components` directory, you need to create one.
3. Inside the `custom_components` directory, create a new folder called `versatile_thermostat`.
4. Download _all_ files from the `custom_components/versatile_thermostat/` directory (folder) in this repository.
5. Place the downloaded files in the new folder you created.
6. Restart Home Assistant.
7. Configure the new Versatile Thermostat integration.

File diff suppressed because it is too large Load Diff

View File

@@ -1,101 +0,0 @@
# `over_climate` Type Thermostat
- [`over_climate` Type Thermostat](#over_climate-type-thermostat)
- [Prerequisites](#prerequisites)
- [Configuration](#configuration)
- [The Underlying Entities](#the-underlying-entities)
- [AC Mode](#ac-mode)
- [Self-Regulation](#self-regulation)
- [Auto-Fan (Auto Ventilation)](#auto-fan-auto-ventilation)
- [Compensating for the Internal Temperature of the Underlying Equipment](#compensating-for-the-internal-temperature-of-the-underlying-equipment)
- [Specific Functions](#specific-functions)
- [Follow Underlying Temperature Changes](#follow-underlying-temperature-changes)
## Prerequisites
The installation should look like this:
![installation `over_climate`](images/over-climate-schema.png)
1. The user or automation, or the Scheduler, sets a setpoint via a preset or directly using a temperature.
2. Periodically, the internal thermometer (2), external thermometer (2b), or equipment's internal thermometer (2c) sends the measured temperature. The internal thermometer should be placed in a relevant spot for the user's comfort: ideally in the middle of the living space. Avoid placing it too close to a window or equipment.
3. Based on the setpoint values, differences, and self-regulation parameters (see [auto-regulation](self-regulation.md)), VTherm will calculate a setpoint to send to the underlying `climate` entity.
4. The `climate` entity controls the equipment using its own protocol.
5. Depending on the chosen regulation options, VTherm may directly control the opening of a thermostatic valve or calibrate the equipment so that its internal temperature reflects the room temperature.
## Configuration
Click on the "Underlying Entities" option from the menu, and you will see this configuration page:
![image](images/config-linked-entity2.png)
### The Underlying Entities
In the "Equipment to Control" list, you should add the `climate` entities that will be controlled by VTherm. Only entities of type `climate` are accepted.
### AC Mode
You can choose an `over_climate` thermostat to control an air conditioner (reversible or not) by checking the "AC Mode" box. If the equipment allows it, both 'Heating' and 'Cooling' modes will be available.
### Self-Regulation
In `over_climate` mode, the device uses its own regulation algorithm: it turns on/off and pauses automatically based on the setpoint transmitted by VTherm through its `climate` entity. It uses its internal thermometer and the received setpoint.
Depending on the equipment, this internal regulation may vary in quality. It greatly depends on the quality of the equipment, the functionality of its internal thermometer, and its internal algorithm. To improve equipment that regulates poorly, VTherm offers a way to adjust the setpoint it sends by increasing or decreasing it based on the room temperature measured by VTherm, rather than the internal temperature.
The self-regulation options are described in detail [here](self-regulation.md).
To avoid overloading the underlying equipment (some may beep unpleasantly, others run on batteries, etc.), two thresholds are available to limit the number of requests:
1. Regulation Threshold: a threshold in ° below which a new setpoint will not be sent. If the last setpoint was 22°, the next one will be 22° ± regulation threshold.
2. Minimum Regulation Period (in minutes): a minimum time interval below which a new setpoint will not be sent. If the last setpoint was sent at 11:00, the next one cannot be sent before 11:00 + minimum regulation period.
Improperly setting these thresholds may prevent correct self-regulation as new setpoints won't be sent.
### Auto-Fan (Auto Ventilation)
This mode, introduced in version 4.3, forces the use of ventilation if the temperature difference is significant. By activating ventilation, heat distribution occurs more quickly, which helps achieve the target temperature faster.
You can choose which ventilation level to activate from the following options: Low, Medium, High, Turbo.
Of course, your underlying equipment must have ventilation, and it must be controllable for this to work. If your equipment doesn't include the Turbo mode, the High mode will be used instead. Once the temperature difference becomes small again, the ventilation will switch to a "normal" mode, which depends on your equipment (in order): `Mute`, `Auto`, `Low`. The first available mode for your equipment will be chosen.
### Compensating for the Internal Temperature of the Underlying Equipment
Sometimes, the internal thermometer of the underlying equipment (TRV, air conditioner, etc.) is inaccurate to the point that self-regulation is insufficient. This happens when the internal thermometer is placed too close to the heat source. The internal temperature rises much faster than the room temperature, leading to regulation failures.
Example:
1. Room temperature is 18°, setpoint is 20°.
2. The internal temperature of the equipment is 22°.
3. If VTherm sends a setpoint of 21° (= 20° + 1° of self-regulation), the equipment will not heat because its internal temperature (22°) is higher than the setpoint (21°).
To address this, a new optional feature has been added in version 5.4: ![Use of Internal Temperature](images/config-use-internal-temp.png)
When activated, this feature adds the difference between the internal temperature and the room temperature to the setpoint to force heating.
In the above example, the difference is +4° (22° - 18°), so VTherm will send 25° (21° + 4°) to the equipment, forcing it to heat.
This difference is calculated for each underlying equipment since each has its own internal temperature. For example, a VTherm connected to three TRVs, each with its own internal temperature.
This results in much more effective self-regulation that avoids issues with large internal temperature differences due to faulty sensors.
However, be aware that some internal temperatures fluctuate so quickly and inaccurately that they completely skew the calculation. In this case, its better to disable this option.
You will find advice on how to adjust these settings properly on the page [self-regulation](self-regulation.md).
## Specific Functions
Specific functions can be configured through a dedicated option in the menu.
The specific functions that require configuration for this type of VTherm are:
1. Auto-Start/Stop: Automatic start and stop of VTherm based on usage forecasts. This is described here: [auto-start/stop function](feature-auto-start-stop.md).
2. If valve regulation is chosen, the TPI algorithm configuration is accessible from the menu. See ([algorithms](algorithms.md)).
## Follow Underlying Temperature Changes
Some users want to continue using their equipment as before (without _VTherm_). For example, you might want to use the remote control of your _PAC_ or turn the knob on your _TRV_.
If you are in this case, an entity has been added to the _VTherm_ device called `Follow underlying temp changes`:
![Track temperature changes](images/entity-follow-under-temp-change.png)
When this entity is 'On', all temperature or state changes made directly on the underlying equipment are reflected in _VTherm_.
Be careful, if you use this feature, your equipment is now controlled in two ways: _VTherm_ and directly by you. The commands might be contradictory, which could lead to confusion about the equipment's state. _VTherm_ is equipped with a delay mechanism that prevents loops: the user gives a setpoint, which is captured by _VTherm_ and changes the setpoint, ... This delay may cause the change made directly on the equipment to be ignored if these changes are too close together in time.
Some equipment (like Daikin, for example) changes state by itself. If the checkbox is checked, it may turn off the _VTherm_ when that's not what you intended.
That's why it's better not to use it. It generates a lot of confusion and many support requests.

View File

@@ -1,55 +0,0 @@
# `over_switch` Type Thermostat
- [`over_switch` Type Thermostat](#over_switch-type-thermostat)
- [Prerequisites](#prerequisites)
- [Configuration](#configuration)
- [The Underlying Entities](#the-underlying-entities)
- [Keep-Alive](#keep-alive)
- [AC Mode](#ac-mode)
- [Command Inversion](#command-inversion)
## Prerequisites
The installation should look like this:
![installation `over_switch`](images/over-switch-schema.png)
1. The user or automation, or the Scheduler, sets a setpoint via a preset or directly using a temperature.
2. Periodically, the internal thermometer (2) or external thermometer (2b) sends the measured temperature. The internal thermometer should be placed in a relevant spot for the user's comfort: ideally in the middle of the living space. Avoid placing it too close to a window or too near the radiator.
3. Based on the setpoint values, the different temperatures, and the TPI algorithm parameters (see [TPI](algorithms.md#lalgorithme-tpi)), VTherm will calculate a percentage of the on-time.
4. It will then regularly command the turning on and off of the underlying `switch` entities.
5. These underlying switch entities will control the physical switch.
6. The physical switch will turn the radiator on or off.
> The on-time percentage is recalculated each cycle, which is what allows regulating the room temperature.
## Configuration
Click on the "Underlying Entities" option from the menu, and you will see this configuration page:
![image](images/config-linked-entity.png)
### The Underlying Entities
In the "Equipment to Control" list, you should add the switches that will be controlled by VTherm. Only `switch` or `input_boolean` entities are accepted.
The algorithm currently available is TPI. See [algorithm](#algorithm).
If multiple entities are configured, the thermostat staggers the activations to minimize the number of switches on at any given time. This allows for better power distribution, as each radiator will turn on in turn.
VTherm will smooth the consumed power as much as possible by alternating activations. Example of staggered activations:
![image](images/multi-switch-activation.png)
Of course, if the requested power (`on_percent`) is too high, there will be an overlap of activations.
### Keep-Alive
Some equipment requires periodic activation to prevent a safety shutdown. Known as "keep-alive," this function can be activated by entering a non-zero number of seconds in the thermostat's keep-alive interval field. To disable the function or if in doubt, leave it empty or enter zero (default value).
### AC Mode
It is possible to choose a `thermostat_over_switch` to control an air conditioner by checking the "AC Mode" box. In this case, only the cooling mode will be visible.
### Command Inversion
If your equipment is controlled by a pilot wire with a diode, you may need to check the "Invert the Command" box. This will set the switch to `On` when you need to turn off the equipment and to `Off` when you need to turn it on. The cycle times will be inverted with this option.

View File

@@ -1,30 +0,0 @@
# `thermostat_over_valve` Type Thermostat
> ![Attention](images/tips.png) _*Notes*_
> 1. The `over_valve` type is often confused with the `over_climate` type equipped with auto-regulation and direct valve control.
> 2. You should only choose this type when you do not have an associated `climate` entity for your _TRV_ in Home Assistant, and if you only have a `number` type entity to control the valve's opening percentage. The `over_climate` with auto-regulation on the valve is much more powerful than the `over_valve` type.
## Prerequisites
The installation should be similar to the `over_switch` VTherm setup, except that the controlled equipment is directly the valve of a _TRV_:
![installation `over_valve`](images/over-valve-schema.png)
1. The user or automation, or the Scheduler, sets a setpoint via a preset or directly using a temperature.
2. Periodically, the internal thermometer (2) or external thermometer (2b) or internal thermometer of the equipment (2c) sends the measured temperature. The internal thermometer should be placed in a relevant spot for the user's comfort: ideally in the middle of the living space. Avoid placing it too close to a window or too near the equipment.
3. Based on the setpoint values, the different temperatures, and the TPI algorithm parameters (see [TPI](algorithms.md#lalgorithme-tpi)), VTherm will calculate the valve's opening percentage.
4. It will then modify the value of the underlying `number` entities.
5. These underlying `number` entities will control the valve opening rate on the _TRV_.
6. This will regulate the radiator's heating.
> The opening rate is recalculated each cycle, which allows regulating the room temperature.
## Configuration
Click on the "Underlying Entities" option from the menu, and you will see this configuration page, you should add the `number` entities that will be controlled by VTherm. Only `number` or `input_number` entities are accepted.
![image](images/config-linked-entity3.png)
The algorithm currently available is TPI. See [algorithm](#algorithm).
It is possible to choose a `thermostat_over_valve` to control an air conditioner by checking the "AC Mode" box. In this case, only the cooling mode will be visible.

View File

@@ -1,43 +0,0 @@
# When to Use and Not Use It
This thermostat can control 3 types of equipment:
1. A radiator that only works in on/off mode (called `thermostat_over_switch`). The minimum configuration required to use this type of thermostat is:
1. An equipment like a radiator (a `switch` or equivalent),
2. A temperature sensor for the room (or an input_number),
3. An external temperature sensor (consider the weather integration if you don't have one).
2. Another thermostat that has its own modes of operation (called `thermostat_over_climate`). For this type of thermostat, the minimum configuration requires:
1. Equipment like air conditioning, a thermostatic valve controlled by its own `climate` entity.
3. Equipment that can take a value from 0 to 100% (called `thermostat_over_valve`). At 0, heating is off, and at 100% it is fully open. This type allows controlling a thermostatic valve (e.g., Shelly valve) that exposes a `number` type entity, enabling direct control of the valve's opening. Versatile Thermostat regulates the room temperature by adjusting the opening percentage, using both the internal and external temperature sensors, and utilizing the TPI algorithm described below.
The `over_climate` type allows you to add all the features offered by VersatileThermostat to your existing equipment. The VersatileThermostat `climate` entity will control your underlying `climate` entity, turn it off if windows are open, switch to Eco mode if no one is present, etc. See [here](#pourquoi-un-nouveau-thermostat-implémentation). For this type of thermostat, all heating cycles are controlled by the underlying `climate` entity and not by the Versatile Thermostat itself. An optional auto-regulation function allows Versatile Thermostat to adjust the setpoint temperature to the underlying entity in order to reach the setpoint.
Installations with a pilot wire and activation diode benefit from an option that allows inverting the on/off control of the underlying radiator. To do this, use the `over_switch` type and check the "Invert command" option.
# Why a New Thermostat Implementation?
This component, called __Versatile Thermostat__, manages the following use cases:
- Configuration via the standard integration graphical interface (using the Config Entry flow),
- Full use of **preset mode**,
- Disable preset mode when the temperature is **set manually** on a thermostat,
- Turn off/on a thermostat or change preset when a **door or windows are opened/closed** after a certain delay,
- Change preset when **activity is detected** or not in a room for a defined time,
- Use a **TPI (Time Proportional Interval)** algorithm thanks to [[Argonaute](https://forum.hacf.fr/u/argonaute/summary)],
- Add **load shedding** management or regulation to not exceed a defined total power. When the maximum power is exceeded, a hidden preset of "power" is set on the `climate` entity. When the power goes below the maximum, the previous preset is restored.
- **Presence management**. This feature allows dynamically modifying the preset temperature based on the presence sensor in your home.
- **Actions to interact with the thermostat** from other integrations: you can force presence/non-presence using a service, and you can dynamically change preset temperatures and modify security settings.
- Add sensors to view the thermostat's internal states,
- Centralized control of all Versatile Thermostats to stop them all, set them all to frost protection, force them all to heating mode (in winter), force them all to cooling mode (in summer).
- Control of a central heating boiler and VTherms that must control this boiler.
- Automatic start/stop based on usage prediction for `over_climate`.
All these functions are configurable either centrally or individually depending on your needs.
## Incompatibilities
Some TRV type thermostats are known to be incompatible with Versatile Thermostat. This includes the following valves:
1. Danfoss POPP valves with temperature feedback. It is impossible to turn off this valve as it auto-regulates itself, causing conflicts with VTherm.
2. "Homematic" thermostats (and possibly Homematic IP) are known to have issues with Versatile Thermostat due to the limitations of the underlying RF protocol. This problem particularly arises when trying to control multiple Homematic thermostats at once in a single VTherm instance. To reduce service cycle load, you can, for example, group thermostats using Homematic-specific procedures (e.g., using a wall-mounted thermostat) and let Versatile Thermostat control only the wall-mounted thermostat directly. Another option is to control a single thermostat and propagate mode and temperature changes via automation.
3. Heatzy type thermostats that do not support `set_temperature` commands.
4. Rointe type thermostats tend to wake up on their own. The rest works normally.
5. TRVs like Aqara SRTS-A01 and MOES TV01-ZB, which lack the `hvac_action` state feedback to determine whether they are heating or not. Therefore, state feedback is inaccurate, but the rest seems functional.
6. Airwell air conditioners with the "Midea AC LAN" integration. If two VTherm commands are too close together, the air conditioner stops itself.
7. Climates based on the Overkiz integration do not work. It seems impossible to turn off or even change the temperature on these systems.

View File

@@ -1,275 +0,0 @@
# Reference Documentation
- [Reference Documentation](#reference-documentation)
- [Parameter Summary](#parameter-summary)
- [Sensors](#sensors)
- [Actions (Services)](#actions-services)
- [Force Presence/Occupation](#force-presenceoccupation)
- [Modify the Preset Temperature](#modify-the-preset-temperature)
- [Modify Security Settings](#modify-security-settings)
- [ByPass Window Check](#bypass-window-check)
- [Events](#events)
- [Custom Attributes](#custom-attributes)
## Parameter Summary
| Parameter | Label | "over switch" | "over climate" | "over valve" | "central configuration" |
| ----------------------------------------- | ---------------------------------------------------------- | ------------- | ------------------- | ------------ | ----------------------- |
| ``name`` | Name | X | X | X | - |
| ``thermostat_type`` | Thermostat type | X | X | X | - |
| ``temperature_sensor_entity_id`` | Temperature sensor entity id | X | X (auto-regulation) | X | - |
| ``external_temperature_sensor_entity_id`` | External temperature sensor entity id | X | X (auto-regulation) | X | X |
| ``cycle_min`` | Cycle duration (minutes) | X | X | X | - |
| ``temp_min`` | Minimum allowed temperature | X | X | X | X |
| ``temp_max`` | Maximum allowed temperature | X | X | X | X |
| ``device_power`` | Device power | X | X | X | - |
| ``use_central_mode`` | Enable centralized control | X | X | X | - |
| ``use_window_feature`` | With window detection | X | X | X | - |
| ``use_motion_feature`` | With motion detection | X | X | X | - |
| ``use_power_feature`` | With power management | X | X | X | - |
| ``use_presence_feature`` | With presence detection | X | X | X | - |
| ``heater_entity1_id`` | 1st heater | X | - | - | - |
| ``heater_entity2_id`` | 2nd heater | X | - | - | - |
| ``heater_entity3_id`` | 3rd heater | X | - | - | - |
| ``heater_entity4_id`` | 4th heater | X | - | - | - |
| ``heater_keep_alive`` | Switch refresh interval | X | - | - | - |
| ``proportional_function`` | Algorithm | X | - | - | - |
| ``climate_entity1_id`` | Underlying thermostat | - | X | - | - |
| ``climate_entity2_id`` | 2nd underlying thermostat | - | X | - | - |
| ``climate_entity3_id`` | 3rd underlying thermostat | - | X | - | - |
| ``climate_entity4_id`` | 4th underlying thermostat | - | X | - | - |
| ``valve_entity1_id`` | Underlying valve | - | - | X | - |
| ``valve_entity2_id`` | 2nd underlying valve | - | - | X | - |
| ``valve_entity3_id`` | 3rd underlying valve | - | - | X | - |
| ``valve_entity4_id`` | 4th underlying valve | - | - | X | - |
| ``ac_mode`` | Use of air conditioning (AC)? | X | X | X | - |
| ``tpi_coef_int`` | Coefficient for internal temperature delta | X | - | X | X |
| ``tpi_coef_ext`` | Coefficient for external temperature delta | X | - | X | X |
| ``frost_temp`` | Frost preset temperature | X | X | X | X |
| ``window_sensor_entity_id`` | Window sensor (entity id) | X | X | X | - |
| ``window_delay`` | Delay before turn-off (seconds) | X | X | X | X |
| ``window_auto_open_threshold`` | High drop threshold for automatic detection (°/min) | X | X | X | X |
| ``window_auto_close_threshold`` | Low drop threshold for automatic closure detection (°/min) | X | X | X | X |
| ``window_auto_max_duration`` | Maximum duration of automatic turn-off (minutes) | X | X | X | X |
| ``motion_sensor_entity_id`` | Motion sensor entity id | X | X | X | - |
| ``motion_delay`` | Delay before motion is considered (seconds) | X | X | X | - |
| ``motion_off_delay`` | Delay before end of motion is considered (seconds) | X | X | X | X |
| ``motion_preset`` | Preset to use if motion is detected | X | X | X | X |
| ``no_motion_preset`` | Preset to use if no motion is detected | X | X | X | X |
| ``power_sensor_entity_id`` | Total power sensor (entity id) | X | X | X | X |
| ``max_power_sensor_entity_id`` | Max power sensor (entity id) | X | X | X | X |
| ``power_temp`` | Temperature during load shedding | X | X | X | X |
| ``presence_sensor_entity_id`` | Presence sensor entity id (true if someone is present) | X | X | X | - |
| ``minimal_activation_delay`` | Minimum activation delay | X | - | - | X |
| ``security_delay_min`` | Maximum delay between two temperature measurements | X | - | X | X |
| ``security_min_on_percent`` | Minimum power percentage to enter security mode | X | - | X | X |
| ``auto_regulation_mode`` | Auto-regulation mode | - | X | - | - |
| ``auto_regulation_dtemp`` | Auto-regulation threshold | - | X | - | - |
| ``auto_regulation_period_min`` | Minimum auto-regulation period | - | X | - | - |
| ``inverse_switch_command`` | Inverse the switch command (for switches with pilot wire) | X | - | - | - |
| ``auto_fan_mode`` | Automatic fan mode | - | X | - | - |
| ``auto_regulation_use_device_temp`` | Use of internal temperature of the underlying device | - | X | - | - |
| ``use_central_boiler_feature`` | Add central boiler control | - | - | - | X |
| ``central_boiler_activation_service`` | Boiler activation service | - | - | - | X |
| ``central_boiler_deactivation_service`` | Boiler deactivation service | - | - | - | X |
| ``used_by_controls_central_boiler`` | Indicates if the VTherm controls the central boiler | X | X | X | - |
| ``use_auto_start_stop_feature`` | Indicates if the auto start/stop feature is enabled | - | X | - | - |
| ``auto_start_stop_level`` | The detection level for auto start/stop | - | X | - | - |
# Sensors
With the thermostat, sensors are available to visualize alerts and the internal state of the thermostat. These are available in the entities of the device associated with the thermostat:
![image](images/thermostat-sensors.png)
In order, there are:
1. the main ``climate`` entity for thermostat control,
2. the entity allowing the auto-start/stop feature,
3. the entity allowing _VTherm_ to follow changes in the underlying device,
4. the energy consumed by the thermostat (value that increments continuously),
5. the time of receipt of the last external temperature,
6. the time of receipt of the last internal temperature,
7. the average power of the device during the cycle (for TPI only),
8. the time spent in the off state during the cycle (TPI only),
9. the time spent in the on state during the cycle (TPI only),
10. the load shedding state,
11. the power percentage during the cycle (TPI only),
12. the presence state (if presence management is configured),
13. the security state,
14. the window state (if window management is configured),
15. the motion state (if motion management is configured),
16. the valve opening percentage (for `over_valve` type),
The presence of these entities depends on whether the associated feature is enabled.
To color the sensors, add these lines and customize them as needed in your `configuration.yaml`:
```yaml
frontend:
themes:
versatile_thermostat_theme:
state-binary_sensor-safety-on-color: "#FF0B0B"
state-binary_sensor-power-on-color: "#FF0B0B"
state-binary_sensor-window-on-color: "rgb(156, 39, 176)"
state-binary_sensor-motion-on-color: "rgb(156, 39, 176)"
state-binary_sensor-presence-on-color: "lightgreen"
state-binary_sensor-running-on-color: "orange"
```
and choose the theme ```versatile_thermostat_theme``` in the panel configuration. You will get something like this:
![image](images/colored-thermostat-sensors.png)
# Actions (Services)
This custom implementation offers specific actions (services) to facilitate integration with other Home Assistant components.
## Force Presence/Occupation
This service allows you to force the presence state independently of the presence sensor. This can be useful if you want to manage presence via a service rather than a sensor. For example, you can use your alarm to force absence when it is turned on.
The code to call this service is as follows:
```yaml
service : versatile_thermostat.set_presence
Les données:
présence : "off"
cible:
entity_id : climate.my_thermostat
```
## Modify the Preset Temperature
This service is useful if you want to dynamically change the preset temperature. Instead of switching presets, some use cases require modifying the temperature of the preset. This way, you can keep the scheduler unchanged to manage the preset while adjusting the preset temperature.
If the modified preset is currently selected, the target temperature change is immediate and will be applied in the next calculation cycle.
You can modify one or both temperatures (when present or absent) of each preset.
Use the following code to set the preset temperature:
```yaml
service: versatile_thermostat.set_preset_temperature
data:
preset: boost
temperature: 17.8
temperature_away: 15
target:
entity_id: climate.my_thermostat
```
Or, to change the preset for the Air Conditioning (AC) mode, add the `_ac` prefix to the preset name like this:
```yaml
service: versatile_thermostat.set_preset_temperature
data:
preset: boost_ac
temperature: 25
temperature_away: 30
target:
entity_id: climate.my_thermostat
```
> ![Tip](images/tips.png) _*Notes*_
>
> - After a restart, presets are reset to the configured temperature. If you want your change to be permanent, you need to modify the preset temperature in the integration configuration.
## Modify Security Settings
This service allows you to dynamically modify the security settings described here [Advanced Configuration](#advanced-configuration).
If the thermostat is in ``security`` mode, the new settings are applied immediately.
To change the security settings, use the following code:
```yaml
service: versatile_thermostat.set_security
data:
min_on_percent: "0.5"
default_on_percent: "0.1"
delay_min: 60
target:
entity_id: climate.my_thermostat
```
## ByPass Window Check
This service allows you to enable or disable a bypass for the window check.
It allows the thermostat to continue heating even if the window is detected as open.
When set to ``true``, changes to the window's status will no longer affect the thermostat. When set to ``false``, the thermostat will be disabled if the window is still open.
To change the bypass setting, use the following code:
```yaml
service: versatile_thermostat.set_window_bypass
data:
bypass: true
target:
entity_id: climate.my_thermostat
```
# Events
The key events of the thermostat are notified via the message bus.
The following events are notified:
- ``versatile_thermostat_security_event``: the thermostat enters or exits the ``security`` preset
- ``versatile_thermostat_power_event``: the thermostat enters or exits the ``power`` preset
- ``versatile_thermostat_temperature_event``: one or both temperature measurements of the thermostat haven't been updated for more than `security_delay_min`` minutes
- ``versatile_thermostat_hvac_mode_event``: the thermostat is turned on or off. This event is also broadcast at the thermostat's startup
- ``versatile_thermostat_preset_event``: a new preset is selected on the thermostat. This event is also broadcast at the thermostat's startup
- ``versatile_thermostat_central_boiler_event``: an event indicating a change in the boiler's state
- ``versatile_thermostat_auto_start_stop_event``: an event indicating a stop or restart made by the auto-start/stop function
If you've followed along, when a thermostat switches to security mode, 3 events are triggered:
1. ``versatile_thermostat_temperature_event`` to indicate that a thermometer is no longer responding,
2. ``versatile_thermostat_preset_event`` to indicate the switch to the ``security`` preset,
3. ``versatile_thermostat_hvac_mode_event`` to indicate the potential shutdown of the thermostat
Each event carries the event's key values (temperatures, current preset, current power, ...) as well as the thermostat's states.
You can easily capture these events in an automation, for example, to notify users.
# Custom Attributes
To adjust the algorithm, you have access to the entire context seen and calculated by the thermostat via dedicated attributes. You can view (and use) these attributes in the "Developer Tools / States" section of HA. Enter your thermostat and you will see something like this:
![image](images/dev-tools-climate.png)
The custom attributes are as follows:
| Attribute | Meaning |
| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| ``hvac_modes`` | The list of modes supported by the thermostat |
| ``temp_min`` | The minimum temperature |
| ``temp_max`` | The maximum temperature |
| ``preset_modes`` | The presets visible for this thermostat. Hidden presets are not displayed here |
| ``temperature_actuelle`` | The current temperature as reported by the sensor |
| ``temperature`` | The target temperature |
| ``action_hvac`` | The action currently being executed by the heater. Can be idle, heating |
| ``preset_mode`` | The preset currently selected. Can be one of the 'preset_modes' or a hidden preset like power |
| ``[eco/confort/boost]_temp`` | The temperature configured for preset xxx |
| ``[eco/confort/boost]_away_temp`` | The temperature configured for preset xxx when presence is disabled or not_home |
| ``temp_power`` | The temperature used during loss detection |
| ``on_percent`` | The calculated on percentage by the TPI algorithm |
| ``on_time_sec`` | The on period in seconds. Should be ```on_percent * cycle_min``` |
| ``off_time_sec`` | The off period in seconds. Should be ```(1 - on_percent) * cycle_min``` |
| ``cycle_min`` | The calculation cycle in minutes |
| ``function`` | The algorithm used for the cycle calculation |
| ``tpi_coef_int`` | The ``coef_int`` of the TPI algorithm |
| ``tpi_coef_ext`` | The ``coef_ext`` of the TPI algorithm |
| ``saved_preset_mode`` | The last preset used before automatic preset switching |
| ``saved_target_temp`` | The last temperature used before automatic switching |
| ``window_state`` | The last known state of the window sensor. None if the window is not configured |
| ``window_bypass_state`` | True if the window open detection bypass is enabled |
| ``motion_state`` | The last known state of the motion sensor. None if motion detection is not configured |
| ``overpowering_state`` | The last known state of the overpower sensor. None if power management is not configured |
| ``presence_state`` | The last known state of the presence sensor. None if presence detection is not configured |
| ``security_delay_min`` | The delay before activating security mode when one of the two temperature sensors stops sending measurements |
| ``security_min_on_percent`` | The heating percentage below which the thermostat will not switch to security |
| ``security_default_on_percent`` | The heating percentage used when the thermostat is in security mode |
| ``last_temperature_datetime`` | The date and time in ISO8866 format of the last internal temperature reception |
| ``last_ext_temperature_datetime`` | The date and time in ISO8866 format of the last external temperature reception |
| ``security_state`` | The security state. True or false |
| ``minimal_activation_delay_sec`` | The minimal activation delay in seconds |
| ``last_update_datetime`` | The date and time in ISO8866 format of this state |
| ``friendly_name`` | The name of the thermostat |
| ``supported_features`` | A combination of all features supported by this thermostat. See the official climate integration documentation for more information |
| ``valve_open_percent`` | The valve opening percentage |
| ``regulated_target_temperature`` | The target temperature calculated by self-regulation |
| ``is_inversed`` | True if the control is inverted (pilot wire with diode) |
| ``is_controlled_by_central_mode`` | True if the VTherm can be centrally controlled |
| ``last_central_mode`` | The last central mode used (None if the VTherm is not centrally controlled) |
| ``is_used_by_central_boiler`` | Indicates if the VTherm can control the central boiler |
| ``auto_start_stop_enable`` | Indicates if the VTherm is allowed to auto start/stop |
| ``auto_start_stop_level`` | Indicates the auto start/stop level |
| ``hvac_off_reason`` | Indicates the reason for the thermostat's off state (hvac_off). It can be Window, Auto-start/stop, or Manual |
These attributes will be requested when you need assistance.

View File

@@ -1,50 +0,0 @@
# Release Notes
![New](images/new-icon.png)
> * **Release 6.8**:
> - Added a new regulation method for `over_climate` type Versatile Thermostats. This method, called 'Direct Valve Control', allows direct control of a TRV valve and possibly an offset to calibrate the internal thermometer of your TRV. This new method has been tested with Sonoff TRVZB and extended to other TRV types where the valve can be directly controlled via `number` entities. More information [here](over-climate.md#lauto-régulation) and [here](self-regulation.md#auto-régulation-par-contrôle-direct-de-la-vanne).
## **Release 6.5** :
- Added a new feature for the automatic stop and restart of a `VTherm over_climate` [585](https://github.com/jmcollin78/versatile_thermostat/issues/585)
- Improved handling of openings on startup. Allows to memorize and recalculate the state of an opening on Home Assistant restart [504](https://github.com/jmcollin78/versatile_thermostat/issues/504)
## **Release 6.0** :
- Added `number` domain entities to configure preset temperatures [354](https://github.com/jmcollin78/versatile_thermostat/issues/354)
- Complete redesign of the configuration menu to remove temperatures and use a menu instead of a configuration tunnel [354](https://github.com/jmcollin78/versatile_thermostat/issues/354)
## **Release 5.4** :
- Added temperature step [#311](https://github.com/jmcollin78/versatile_thermostat/issues/311),
- Added regulation thresholds for `over_valve` to prevent excessive battery drain for TRVs [#338](https://github.com/jmcollin78/versatile_thermostat/issues/338),
- Added an option to use the internal temperature of a TRV to force auto-regulation [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348),
- Added a keep-alive function for `over_switch` VTherms [#345](https://github.com/jmcollin78/versatile_thermostat/issues/345)
<details>
<summary>Older releases</summary>
> * **Release 5.3** : Added a function to control a central boiler [#234](https://github.com/jmcollin78/versatile_thermostat/issues/234) - more info here: [Central Boiler Control](#le-contrôle-dune-chaudière-centrale). Added the ability to disable security mode for the external thermometer [#343](https://github.com/jmcollin78/versatile_thermostat/issues/343)
> * **Release 5.2** : Added a `central_mode` to control all VTherms centrally [#158](https://github.com/jmcollin78/versatile_thermostat/issues/158).
> * **Release 5.1** : Limitation of values sent to valves and to the underlying climate temperature.
> * **Release 5.0** : Added central configuration to combine configurable attributes [#239](https://github.com/jmcollin78/versatile_thermostat/issues/239).
> * **Release 4.3** : Added an auto-fan mode for `over_climate` type to activate ventilation if temperature difference is large [#223](https://github.com/jmcollin78/versatile_thermostat/issues/223).
> * **Release 4.2** : The temperature curve slope is now calculated in °/hour instead of °/min [#242](https://github.com/jmcollin78/versatile_thermostat/issues/242). Fixed automatic opening detection by adding temperature curve smoothing.
> * **Release 4.1** : Added an **Expert** regulation mode where users can specify their own auto-regulation parameters instead of using pre-programmed ones [#194](https://github.com/jmcollin78/versatile_thermostat/issues/194).
> * **Release 4.0** : Added support for **Versatile Thermostat UI Card**. See [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card). Added a **Slow** regulation mode for slow-latency heating devices [#168](https://github.com/jmcollin78/versatile_thermostat/issues/168). Changed how **power is calculated** for VTherms with multi-underlying equipment [#146](https://github.com/jmcollin78/versatile_thermostat/issues/146). Added support for AC and Heat for VTherms via a switch [#144](https://github.com/jmcollin78/versatile_thermostat/pull/144)
> * **Release 3.8**: Added an **auto-regulation** function for `over_climate` thermostats regulated by the underlying climate. See [Auto-regulation](#lauto-régulation) and [#129](https://github.com/jmcollin78/versatile_thermostat/issues/129). Added the **ability to invert control** for `over_switch` thermostats to address installations with pilot wire and diode [#124](https://github.com/jmcollin78/versatile_thermostat/issues/124).
> * **Release 3.7**: Added the `over_valve` Versatile Thermostat type to control a TRV valve directly or any other dimmer type equipment for heating. Regulation is done directly by adjusting the percentage of opening of the underlying entity: 0 means the valve is off, 100 means the valve is fully open. See [#131](https://github.com/jmcollin78/versatile_thermostat/issues/131). Added a bypass function for opening detection [#138](https://github.com/jmcollin78/versatile_thermostat/issues/138). Added Slovak language support.
> * **Release 3.6**: Added the `motion_off_delay` parameter to improve motion detection handling [#116](https://github.com/jmcollin78/versatile_thermostat/issues/116), [#128](https://github.com/jmcollin78/versatile_thermostat/issues/128). Added AC mode (air conditioning) for `over_switch` VTherm. Prepared the GitHub project to facilitate contributions [#127](https://github.com/jmcollin78/versatile_thermostat/issues/127)
> * **Release 3.5**: Multiple thermostats possible in "thermostat over climate" mode [#113](https://github.com/jmcollin78/versatile_thermostat/issues/113)
> * **Release 3.4**: Bug fix and exposure of preset temperatures for AC mode [#103](https://github.com/jmcollin78/versatile_thermostat/issues/103)
> * **Release 3.3**: Added Air Conditioning (AC) mode. This function allows you to use the AC mode of your underlying thermostat. To use it, you must check the "Use AC Mode" option and define temperature values for presets and away presets.
> * **Release 3.2** : Added the ability to control multiple switches from the same thermostat. In this mode, switches are triggered with a delay to minimize the power required at any given time (minimizing overlap periods). See [Configuration](#sélectionnez-des-entités-pilotées)
> * **Release 3.1** : Added window/door open detection by temperature drop. This new feature automatically stops a radiator when the temperature drops suddenly. See [Auto Mode](#le-mode-auto)
> * **Major Release 3.0** : Added thermostat equipment and associated sensors (binary and non-binary). Much closer to the Home Assistant philosophy, you now have direct access to the energy consumed by the radiator controlled by the thermostat and many other sensors useful for your automations and dashboards.
> * **Release 2.3** : Added measurement of power and energy for the radiator controlled by the thermostat.
> * **Release 2.2** : Added a safety function to prevent leaving a radiator heating indefinitely in case of thermometer failure.
> * **Major Release 2.0** : Added the "over climate" thermostat allowing any thermostat to be transformed into a Versatile Thermostat and gain all its functionalities.
</details>
> ![Tip](images/tips.png) _*Notes*_
>
> Complete release notes are available on the [GitHub of the integration](https://github.com/jmcollin78/versatile_thermostat/releases/).

View File

@@ -1,152 +0,0 @@
# Self-regulation
- [Self-regulation](#self-regulation)
- [Self-regulation in Expert Mode](#self-regulation-in-expert-mode)
- [Summary of the Self-regulation Algorithm](#summary-of-the-self-regulation-algorithm)
You have the option to activate the self-regulation feature only for _VTherms_ of type `over_climate`.
There are generally two cases:
1. If your underlying device is a _TRV_ and the valve is directly controllable in Home Assistant (e.g., Sonoff TRVZB), this function will allow _VTherm_ to directly manipulate the valve opening to regulate the temperature. The opening is then calculated by a _TPI_-type algorithm (see [here](algorithms.md)).
2. Otherwise, Versatile Thermostat will adjust the temperature setpoint given to the underlying climate to ensure the room temperature actually reaches the setpoint.
## Configuration
### Self-regulation by direct valve control
This type of self-regulation, named `Direct Valve Control`, requires:
1. An entity of type `climate` that is included in the _VTherm_'s underlying devices.
2. An entity of type `number` to control the valve opening rate of the _TRV_.
3. An optional entity of type `number` for calibrating the internal temperature of the underlying device.
4. An optional entity of type `number` to control the valve closure.
When the chosen self-regulation is `Direct Valve Control` on an _VTherm_ `over_climate`, a new configuration page named `Valve Regulation Configuration` appears:
![Configuration Menu](images/config-self-regulation-valve-1.png)
This allows you to configure the valve control entities:
![Configuration Entities](images/config-self-regulation-valve-2.png)
You need to provide:
1. As many valve opening control entities as there are underlying devices, and in the same order. These parameters are mandatory.
2. As many temperature calibration entities as there are underlying devices, and in the same order. These parameters are optional; they must either all be provided or none.
3. As many valve closure control entities as there are underlying devices, and in the same order. These parameters are optional; they must either all be provided or none.
The opening rate calculation algorithm is based on the _TPI_ algorithm described [here](algorithms.md). This is the same algorithm used for _VTherms_ `over_switch` and `over_valve`.
If a valve closure rate entity is configured, it will be set to 100 minus the opening rate to force the valve into a particular state.
### Other self-regulation
In the second case, Versatile Thermostat calculates an offset based on the following information:
1. The current difference between the actual temperature and the setpoint temperature, called the gross error.
2. The accumulation of past errors.
3. The difference between the outdoor temperature and the setpoint.
These three pieces of information are combined to calculate the offset, which will be added to the current setpoint and sent to the underlying climate.
Self-regulation is configured with:
1. A regulation degree:
1. Light - for small self-regulation needs. In this mode, the maximum offset will be 1.5°C.
2. Medium - for medium self-regulation needs. A maximum offset of 2°C is possible in this mode.
3. Strong - for high self-regulation needs. The maximum offset is 3°C in this mode, and the self-regulation will react strongly to temperature changes.
2. A self-regulation threshold: the value below which no new regulation will be applied. For example, if at time t the offset is 2°C, and at the next calculation, the offset is 2.4°C, the regulation will not be applied. It will only be applied when the difference between the two offsets is at least equal to this threshold.
3. Minimum period between two self-regulations: this number, expressed in minutes, indicates the duration between two regulation changes.
These three parameters allow you to adjust the regulation and avoid applying too many regulation changes. Some devices, like TRVs or boilers, don't like frequent setpoint changes.
> ![Tip](images/tips.png) _*Setup advice*_
> 1. Do not start self-regulation immediately. Observe how your equipment's natural regulation works. If you notice that the setpoint is not reached or takes too long to reach, start the regulation.
> 2. Start with light self-regulation and keep both parameters at their default values. Wait a few days and check if the situation improves.
> 3. If it's not enough, switch to medium self-regulation and wait for stabilization.
> 4. If it's still not enough, switch to strong self-regulation.
> 5. If it's still not correct, you will need to switch to expert mode to finely adjust the regulation parameters.
Self-regulation forces the equipment to push further by regularly adjusting its setpoint. This can increase both its consumption and wear.
#### Self-regulation in Expert Mode
In **Expert** mode, you can finely adjust the self-regulation parameters to meet your goals and optimize performance. The algorithm calculates the gap between the setpoint and the actual room temperature. This gap is called the error.
The adjustable parameters are as follows:
1. `kp`: the factor applied to the gross error,
2. `ki`: the factor applied to the accumulated errors,
3. `k_ext`: the factor applied to the difference between the indoor temperature and the outdoor temperature,
4. `offset_max`: the maximum correction (offset) that the regulation can apply,
5. `stabilization_threshold`: a stabilization threshold, which when reached by the error resets the accumulated errors to 0,
6. `accumulated_error_threshold`: the maximum for error accumulation.
For tuning, the following observations should be considered:
1. `kp * error` will give the offset related to the gross error. This offset is directly proportional to the error and will be 0 when the target is reached.
2. The accumulation of the error helps correct the stabilization curve even if there is still an error. The error accumulates and the offset increases progressively, which should stabilize the temperature around the target. To have a noticeable effect, this parameter should not be too small. A medium value is 30.
3. `ki * accumulated_error_threshold` will give the maximum offset related to the accumulated error.
4. `k_ext` allows immediate (without waiting for accumulated errors) correction when the outdoor temperature is much different from the target temperature. If the stabilization occurs too high when the temperature differences are large, this parameter might be too high. It should be adjustable to zero to allow the first two offsets to do the work.
The pre-programmed values are as follows:
**Slow regulation**:
kp: 0.2 # 20% of the current internal regulation offset are caused by the current difference of target temperature and room temperature
ki: 0.8 / 288.0 # 80% of the current internal regulation offset are caused by the average offset of the past 24 hours
k_ext: 1.0 / 25.0 # this will add 1°C to the offset when it's 25°C colder outdoor than indoor
offset_max: 2.0 # limit to a final offset of -2°C to +2°C
stabilization_threshold: 0.0 # this needs to be disabled as otherwise the long term accumulated error will always be reset when the temp briefly crosses from/to below/above the target
accumulated_error_threshold: 2.0 * 288 # this allows up to 2°C long term offset in both directions
**Light regulation**:
kp: 0.2
ki: 0.05
k_ext: 0.05
offset_max: 1.5
stabilization_threshold: 0.1
accumulated_error_threshold: 10
**Medium regulation**:
kp: 0.3
ki: 0.05
k_ext: 0.1
offset_max: 2
stabilization_threshold: 0.1
accumulated_error_threshold: 20
**Strong regulation**:
"""Strong parameters for regulation
A set of parameters which doesn't take into account the external temp
and concentrate on internal temp error + accumulated error.
This should work for cold external conditions which otherwise generate
high external_offset"""
kp: 0.4
ki: 0.08
k_ext: 0.0
offset_max: 5
stabilization_threshold: 0.1
accumulated_error_threshold: 50
To use Expert mode, you must declare the values you wish to use for each of these parameters in your `configuration.yaml` as follows. Example for 'Extreme regulation':
```yaml
versatile_thermostat:
auto_regulation_expert:
kp: 0.6
ki: 0.1
k_ext: 0.0
offset_max: 10
stabilization_threshold: 0.1
accumulated_error_threshold: 80
```
and of course, configure the auto-regulation mode of the VTherm to Expert mode. All _VTherms_ in **Expert** mode will use the same parameters, it is not possible to have different expert settings.
To apply the changes, you must either **restart Home Assistant completely** or just the Versatile Thermostat integration (Developer Tools / YAML / Reload Configuration / Versatile Thermostat).
> ![Tip](images/tips.png) _*Notes*_
>
> 1. In expert mode, it is rarely necessary to use the option [Compensate the internal temperature of the underlying](over-climate.md#compensate-the-internal-temperature-of-the-underlying). This could result in very high setpoints.
## Summary of the Auto-Regulation Algorithm
A summary of the auto-regulation algorithm is described [here](algorithms.md#the-auto-regulation-algorithm-without-valve-control)

View File

@@ -1,211 +0,0 @@
# Troubleshooting
- [Troubleshooting](#troubleshooting)
- [Using a Heatzy](#using-a-heatzy)
- [Using a radiator with a pilot wire (Nodon SIN-4-FP-21)](#using-a-radiator-with-a-pilot-wire-nodon-sin-4-fp-21)
- [Only the first radiator heats](#only-the-first-radiator-heats)
- [The radiator heats even though the setpoint temperature is exceeded, or it does not heat when the room temperature is well below the setpoint](#the-radiator-heats-even-though-the-setpoint-temperature-is-exceeded-or-it-does-not-heat-when-the-room-temperature-is-well-below-the-setpoint)
- [Type `over_switch` or `over_valve`](#type-over_switch-or-over_valve)
- [Type `over_climate`](#type-over_climate)
- [Adjust the window open detection parameters in auto mode](#adjust-the-window-open-detection-parameters-in-auto-mode)
- [Why is my Versatile Thermostat going into Safety Mode?](#why-is-my-versatile-thermostat-going-into-safety-mode)
- [How to detect Safety Mode?](#how-to-detect-safety-mode)
- [How to Be Notified When This Happens?](#how-to-be-notified-when-this-happens)
- [How to Fix It?](#how-to-fix-it)
- [Using a Group of People as a Presence Sensor](#using-a-group-of-people-as-a-presence-sensor)
- [Enable Logs for the Versatile Thermostat](#enable-logs-for-the-versatile-thermostat)
## Using a Heatzy
Using a Heatzy or Nodon is possible provided you use a virtual switch with this model:
```yaml
- platform: template
switches:
chauffage_sdb:
unique_id: chauffage_sdb
friendly_name: Bathroom heating
value_template: "{{ is_state_attr('climate.bathroom', 'preset_mode', 'comfort') }}"
icon_template: >-
{% if is_state_attr('climate.bathroom', 'preset_mode', 'comfort') %}
mdi:radiator
{% elif is_state_attr('climate.bathroom', 'preset_mode', 'away') %}
mdi:snowflake
{% else %}
mdi:radiator-disabled
{% endif %}
turn_on:
service: climate.set_preset_mode
entity_id: climate.bathroom
data:
preset_mode: "comfort"
turn_off:
service: climate.set_preset_mode
entity_id: climate.bathroom
data:
preset_mode: "eco"
```
Thanks to @gael for this example.
## Using a radiator with a pilot wire (Nodon SIN-4-FP-21)
As with the Heatzy above, you can use a virtual switch that will change the preset of your radiator based on the VTherms on/off state.
Example:
```yaml
- platform: template
switches:
chauffage_chb_parents:
unique_id: chauffage_chb_parents
friendly_name: Chauffage chambre parents
value_template: "{{ is_state('select.fp_chb_parents_pilot_wire_mode', 'comfort') }}"
icon_template: >-
{% if is_state('select.fp_chb_parents_pilot_wire_mode', 'comfort') %}
mdi:radiator
{% elif is_state('select.fp_chb_parents_pilot_wire_mode', 'frost_protection') %}
mdi:snowflake
{% else %}
mdi:radiator-disabled
{% endif %}
turn_on:
service: select.select_option
target:
entity_id: select.fp_chb_parents_pilot_wire_mode
data:
option: comfort
turn_off:
service: select.select_option
target:
entity_id: select.fp_chb_parents_pilot_wire_mode
data:
option: eco
```
Another more complex example is [here](https://github.com/jmcollin78/versatile_thermostat/discussions/431#discussioncomment-11393065)
## Only the first radiator heats
In `over_switch` mode, if multiple radiators are configured for the same VTherm, the heating will be triggered sequentially to smooth out the consumption peaks as much as possible.
This is completely normal and intentional. It is described here: [For a thermostat of type ```thermostat_over_switch```](#for-a-thermostat-of-type-thermostat_over_switch)
## The radiator heats even though the setpoint temperature is exceeded, or it does not heat when the room temperature is well below the setpoint
### Type `over_switch` or `over_valve`
With a VTherm of type `over_switch` or `over_valve`, this issue simply indicates that the TPI algorithm parameters are not properly configured. See [TPI Algorithm](#tpi-algorithm) to optimize the settings.
### Type `over_climate`
With a VTherm of type `over_climate`, the regulation is handled directly by the underlying `climate`, and VTherm simply transmits the setpoints to it. So if the radiator is heating even though the setpoint temperature is exceeded, it is likely that its internal temperature measurement is biased. This often happens with TRVs and reversible air conditioners that have an internal temperature sensor, either too close to the heating element (so it's too cold in winter).
Examples of discussions on these topics: [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348), [#316](https://github.com/jmcollin78/versatile_thermostat/issues/316), [#312](https://github.com/jmcollin78/versatile_thermostat/discussions/312), [#278](https://github.com/jmcollin78/versatile_thermostat/discussions/278)
To resolve this, VTherm is equipped with a feature called self-regulation, which allows it to adjust the setpoint sent to the underlying device until the setpoint is met. This function compensates for the bias of internal temperature sensors. If the bias is significant, the regulation should also be significant. See [Self-regulation](self-regulation.md) for configuring self-regulation.
## Adjust the window open detection parameters in auto mode
If you are unable to configure the automatic window open detection function (see [auto](feature-window.md#auto-mode)), you can try modifying the temperature smoothing algorithm parameters.
Indeed, the automatic window open detection is based on calculating the temperature slope. To avoid artifacts caused by an imprecise temperature sensor, this slope is calculated using a temperature smoothed with an algorithm called Exponential Moving Average (EMA).
This algorithm has 3 parameters:
1. `lifecycle_sec`: the duration in seconds considered for smoothing. The higher it is, the smoother the temperature will be, but the detection delay will also increase.
2. `max_alpha`: if two temperature readings are far apart in time, the second one will carry much more weight. This parameter limits the weight of a reading that comes well after the previous one. This value must be between 0 and 1. The lower it is, the less distant readings are taken into account. The default value is 0.5, meaning that a new temperature reading will never weigh more than half of the moving average.
3. `precision`: the number of digits after the decimal point retained for calculating the moving average.
To change these parameters, you need to modify the `configuration.yaml` file and add the following section (the values below are the default values):
```yaml
versatile_thermostat:
short_ema_params:
max_alpha: 0.5
halflife_sec: 300
precision: 2
```
These parameters are sensitive and quite difficult to adjust. Please only use them if you know what youre doing and if your temperature readings are not already smoothed.
## Why is my Versatile Thermostat going into Safety Mode?
Safety mode is only available for VTherm types `over_switch` and `over_valve`. It occurs when one of the two thermometers (providing either the room temperature or the external temperature) has not sent a value for more than `security_delay_min` minutes, and the radiator had been heating at least `security_min_on_percent`. See [safety mode](feature-advanced.md#safety-mode)
Since the algorithm relies on temperature measurements, if they are no longer received by the VTherm, there is a risk of overheating and fire. To prevent this, when the above conditions are detected, heating is limited to the `security_default_on_percent` parameter. This value should therefore be reasonably low (10% is a good value). It helps avoid a fire while preventing the radiator from being completely turned off (risk of freezing).
All these parameters are configured on the last page of the VTherm configuration: "Advanced Settings".
### How to detect Safety Mode?
The first symptom is an unusually low temperature with a short and consistent heating time during each cycle.
Example:
[security mode](images/security-mode-symptome1.png)
If you have installed the [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card), the affected VTherm will appear like this:
[security mode UI Card](images/security-mode-symptome2.png)
You can also check the VTherm's attributes for the dates of the last received values. **The attributes are available in the Developer Tools / States**.
Example:
```yaml
security_state: true
last_temperature_datetime: "2023-12-06T18:43:28.346010+01:00"
last_ext_temperature_datetime: "2023-12-06T13:04:35.164367+01:00"
last_update_datetime: "2023-12-06T18:43:28.351103+01:00"
...
security_delay_min: 60
```
We can see that:
1. The VTherm is indeed in safety mode (`security_state: true`),
2. The current time is 06/12/2023 at 18:43:28 (`last_update_datetime: "2023-12-06T18:43:28.351103+01:00"`),
3. The last reception time of the room temperature is 06/12/2023 at 18:43:28 (`last_temperature_datetime: "2023-12-06T18:43:28.346010+01:00"`), so it's recent,
4. The last reception time of the external temperature is 06/12/2023 at 13:04:35 (`last_ext_temperature_datetime: "2023-12-06T13:04:35.164367+01:00"`). The external temperature is over 5 hours late, which triggered the safety mode, as the threshold is set to 60 minutes (`security_delay_min: 60`).
### How to Be Notified When This Happens?
The VTherm sends an event as soon as this happens and again at the end of the safety alert. You can capture these events in an automation and send a notification, blink a light, trigger a siren, etc. It's up to you.
For handling events generated by VTherm, see [Events](#events).
### How to Fix It?
It depends on the cause of the problem:
1. If a sensor is faulty, it should be repaired (replace batteries, change it, check the weather integration that provides the external temperature, etc.),
2. If the `security_delay_min` parameter is too small, it may generate many false alerts. A correct value is around 60 minutes, especially if you have battery-powered temperature sensors. See [my settings](tuning-examples.md#battery-powered-temperature-sensor),
3. Some temperature sensors don't send measurements if the temperature hasn't changed. So if the temperature stays very stable for a long time, safety mode can trigger. This is not a big issue since it will deactivate once the VTherm receives a new temperature. On some thermometers (e.g., TuYA or Zigbee), you can force a max delay between two measurements. The max delay should be set to a value lower than `security_delay_min`,
4. As soon as the temperature is received again, safety mode will turn off, and the previous preset, target temperature, and mode values will be restored.
5. If the external temperature sensor is faulty, you can disable safety mode triggering as it has a minimal impact on the results. To do so, see [here](feature-advanced.md#safety-mode).
## Using a Group of People as a Presence Sensor
Unfortunately, groups of people are not recognized as presence sensors. Therefore, you cannot use them directly in VTherm.
A workaround is to create a binary sensor template with the following code:
File `template.yaml`:
```yaml
- binary_sensor:
- name: maison_occupee
unique_id: maison_occupee
state: "{{is_state('person.person1', 'home') or is_state('person.person2', 'home') or is_state('input_boolean.force_presence', 'on')}}"
device_class: occupancy
```
In this example, note the use of an `input_boolean` called `force_presence`, which forces the sensor to `True`, thereby forcing any VTherm that uses it to have active presence. This can be used, for example, to trigger a pre-heating of the house when leaving work or when an unrecognized person is present in HA.
File `configuration.yaml`:
```yaml
...
template: !include templates.yaml
...
```
## Enable Logs for the Versatile Thermostat
Sometimes, you will need to enable logs to fine-tune your analysis. To do this, edit the `logger.yaml` file in your configuration and configure the logs as follows:
```yaml
default: xxxx
logs:
custom_components.versatile_thermostat: info
```
You must reload the YAML configuration (Developer Tools / YAML / Reload all YAML configuration) or restart Home Assistant for this change to take effect.
Be careful, in debug mode, Versatile Thermostat is very verbose and can quickly slow down Home Assistant or saturate your hard drive. If you switch to debug mode for anomaly analysis, do so only for the time needed to reproduce the bug and disable debug mode immediately afterward.

View File

@@ -1,60 +0,0 @@
# Tuning Examples
- [Tuning Examples](#tuning-examples)
- [Electric Heating](#electric-heating)
- [Central Heating (gas or oil heating)](#central-heating-gas-or-oil-heating)
- [Battery-Powered Temperature Sensor](#battery-powered-temperature-sensor)
- [Reactive Temperature Sensor (plugged in)](#reactive-temperature-sensor-plugged-in)
- [My Presets](#my-presets)
## Electric Heating
- Cycle: between 5 and 10 minutes,
- minimal_activation_delay_sec: 30 seconds
## Central Heating (gas or oil heating)
- Cycle: between 30 and 60 minutes,
- minimal_activation_delay_sec: 300 seconds (due to response time)
## Battery-Powered Temperature Sensor
These sensors are often sluggish and do not always send temperature readings when the temperature is stable. Therefore, the settings should be loose to avoid false positives.
- security_delay_min: 60 minutes (because these sensors are sluggish)
- security_min_on_percent: 0.7 (70% - the system goes into security mode if the heater was on more than 70% of the time)
- security_default_on_percent: 0.4 (40% - in security mode, we maintain 40% heating time to avoid getting too cold)
These settings should be understood as follows:
> If the thermometer stops sending temperature readings for 1 hour and the heating percentage (``on_percent``) was greater than 70%, then the heating percentage will be reduced to 40%.
Feel free to adjust these settings to your specific case!
The important thing is not to take too much risk with these parameters: assume you are absent for a long period, and the batteries of your thermometer are running low, your heater will run 40% of the time during the whole failure period.
Versatile Thermostat allows you to be notified when such an event occurs. Set up the appropriate alerts as soon as you start using this thermostat. See (#notifications).
## Reactive Temperature Sensor (plugged in)
A powered thermometer is supposed to be very regular in sending temperature readings. If it doesn't send anything for 15 minutes, it most likely has an issue, and we can react faster without the risk of a false positive.
- security_delay_min: 15 minutes
- security_min_on_percent: 0.5 (50% - the system goes into ``security`` preset if the heater was on more than 50% of the time)
- security_default_on_percent: 0.25 (25% - in ``security`` preset, we keep 25% heating time)
## My Presets
This is just an example of how I use the preset. You can adapt it to your configuration, but it may be useful to understand its functionality.
``Frost Protection``: 10°C
``Eco``: 17°C
``Comfort``: 19°C
``Boost``: 20°C
When presence is disabled:
``Frost Protection``: 10°C
``Eco``: 16.5°C
``Comfort``: 17°C
``Boost``: 17.5°C
The motion detector in my office is configured to use ``Boost`` when motion is detected and ``Eco`` otherwise.
The security mode is configured as follows:
![My settings](images/my-tuning.png)

View File

@@ -1,216 +0,0 @@
# Quelques compléments indispensables
- [Quelques compléments indispensables](#quelques-compléments-indispensables)
- [Versatile Thermostat UI Card](#versatile-thermostat-ui-card)
- [Composant Scheduler !](#composant-scheduler-)
- [Courbes de régulattion avec Plotly](#courbes-de-régulattion-avec-plotly)
- [Les notification avec l'AappDaemon NOTIFIER](#les-notification-avec-laappdaemon-notifier)
## Versatile Thermostat UI Card
Une carte spéciale pour le Versatile Thermostat a été développée (sur la base du Better Thermostat). Elle est dispo ici [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card) et propose une vision moderne de tous les status du VTherm :
![image](https://github.com/jmcollin78/versatile-thermostat-ui-card/blob/master/assets/1.png?raw=true)
## Composant Scheduler !
Afin de profiter de toute la puissance du Versatile Thermostat, je vous invite à l'utiliser avec https://github.com/nielsfaber/scheduler-component
En effet, le composant scheduler propose une gestion de la base climatique sur les modes prédéfinis. Cette fonctionnalité a un intérêt limité avec le thermostat générique mais elle devient très puissante avec le Versatile Thermostat :
À partir d'ici, je suppose que vous avez installé Versatile Thermostat et Scheduler Component.
Dans Scheduler, ajoutez un planning :
![image](https://user-images.githubusercontent.com/1717155/119146454-ee1a9d80-ba4a-11eb-80ae-3074c3511830.png)
Choisissez le groupe "climat", choisissez une (ou plusieurs) entité(s), sélectionnez "MAKE SCHEME" et cliquez sur suivant :
(il est possible de choisir "SET PRESET", mais je préfère utiliser "MAKE SCHEME")
![image](https://user-images.githubusercontent.com/1717155/119147210-aa746380-ba4b-11eb-8def-479a741c0ba7.png)
Définissez votre schéma de mode et enregistrez :
![image](https://user-images.githubusercontent.com/1717155/119147784-2f5f7d00-ba4c-11eb-9de4-5e62ff5e71a8.png)
Dans cet exemple, j'ai réglé le mode ECO pendant la nuit et le jour lorsqu'il n'y a personne à la maison BOOST le matin et CONFORT le soir.
J'espère que cet exemple vous aidera, n'hésitez pas à me faire part de vos retours !
## Courbes de régulattion avec Plotly
Vous pouvez obtenir une courbe comme celle présentée dans [some results](#some-results) avec une sorte de configuration de graphique Plotly uniquement en utilisant les attributs personnalisés du thermostat décrits [ici](#custom-attributes) :
Remplacez les valeurs entre [[ ]] par les votres.
<details>
```yaml
- type: custom:plotly-graph
entities:
- entity: '[[climate]]'
attribute: temperature
yaxis: y1
name: Consigne
- entity: '[[climate]]'
attribute: current_temperature
yaxis: y1
name:
- entity: '[[climate]]'
attribute: ema_temp
yaxis: y1
name: Ema
- entity: '[[climate]]'
attribute: on_percent
yaxis: y2
name: Power percent
fill: tozeroy
fillcolor: rgba(200, 10, 10, 0.3)
line:
color: rgba(200, 10, 10, 0.9)
- entity: '[[slope]]'
name: Slope
fill: tozeroy
yaxis: y9
fillcolor: rgba(100, 100, 100, 0.3)
line:
color: rgba(100, 100, 100, 0.9)
hours_to_show: 4
refresh_interval: 10
height: 800
config:
scrollZoom: true
layout:
margin:
r: 50
legend:
x: 0
'y': 1.2
groupclick: togglegroup
title:
side: top right
yaxis:
visible: true
position: 0
yaxis2:
visible: true
position: 0
fixedrange: true
range:
- 0
- 1
yaxis9:
visible: true
fixedrange: false
range:
- -2
- 2
position: 1
xaxis:
rangeselector:
'y': 1.1
x: 0.7
buttons:
- count: 1
step: hour
- count: 12
step: hour
- count: 1
step: day
- count: 7
step: day
```
</details>
Exemple de courbes obtenues avec Plotly :
![image](images/plotly-curves.png)
## Les notification avec l'AappDaemon NOTIFIER
Cette automatisation utilise l'excellente App Daemon nommée NOTIFIER développée par Horizon Domotique que vous trouverez en démonstration [ici](https://www.youtube.com/watch?v=chJylIK0ASo&ab_channel=HorizonDomotique) et le code est [ici](https://github.com/jlpouffier/home-assistant-config/blob/master/appdaemon/apps/notifier.py). Elle permet de notifier les utilisateurs du logement lorsqu'un des évènements touchant à la sécurité survient sur un des Versatile Thermostats.
C'est un excellent exemple de l'utilisation des notifications décrites ici [notification](#notifications).
<details>
```yaml
alias: Surveillance Mode Sécurité chauffage
description: Envoi une notification si un thermostat passe en mode sécurité ou power
trigger:
- platform: event
event_type: versatile_thermostat_security_event
id: versatile_thermostat_security_event
- platform: event
event_type: versatile_thermostat_power_event
id: versatile_thermostat_power_event
- platform: event
event_type: versatile_thermostat_temperature_event
id: versatile_thermostat_temperature_event
condition: []
action:
- choose:
- conditions:
- condition: trigger
id: versatile_thermostat_security_event
sequence:
- event: NOTIFIER
event_data:
action: send_to_jmc
title: >-
Radiateur {{ trigger.event.data.name }} - {{
trigger.event.data.type }} Sécurité
message: >-
Le radiateur {{ trigger.event.data.name }} est passé en {{
trigger.event.data.type }} sécurité car le thermomètre ne répond
plus.\n{{ trigger.event.data }}
callback:
- title: Stopper chauffage
event: stopper_chauffage
image_url: /media/local/alerte-securite.jpg
click_url: /lovelace-chauffage/4
icon: mdi:radiator-off
tag: radiateur_security_alerte
persistent: true
- conditions:
- condition: trigger
id: versatile_thermostat_power_event
sequence:
- event: NOTIFIER
event_data:
action: send_to_jmc
title: >-
Radiateur {{ trigger.event.data.name }} - {{
trigger.event.data.type }} Délestage
message: >-
Le radiateur {{ trigger.event.data.name }} est passé en {{
trigger.event.data.type }} délestage car la puissance max est
dépassée.\n{{ trigger.event.data }}
callback:
- title: Stopper chauffage
event: stopper_chauffage
image_url: /media/local/alerte-delestage.jpg
click_url: /lovelace-chauffage/4
icon: mdi:radiator-off
tag: radiateur_power_alerte
persistent: true
- conditions:
- condition: trigger
id: versatile_thermostat_temperature_event
sequence:
- event: NOTIFIER
event_data:
action: send_to_jmc
title: >-
Le thermomètre du radiateur {{ trigger.event.data.name }} ne
répond plus
message: >-
Le thermomètre du radiateur {{ trigger.event.data.name }} ne
répond plus depuis longtemps.\n{{ trigger.event.data }}
image_url: /media/local/thermometre-alerte.jpg
click_url: /lovelace-chauffage/4
icon: mdi:radiator-disabled
tag: radiateur_thermometre_alerte
persistent: true
mode: queued
max: 30
```
</details>

View File

@@ -1,69 +0,0 @@
# Les différents algorithmes utilisés
- [Les différents algorithmes utilisés](#les-différents-algorithmes-utilisés)
- [L'algorithme TPI](#lalgorithme-tpi)
- [Configurez les coefficients de l'algorithme TPI](#configurez-les-coefficients-de-lalgorithme-tpi)
- [Principe](#principe)
- [L'algorithme d'auto-régulation (sans contrôle de la vanne)](#lalgorithme-dauto-régulation-sans-contrôle-de-la-vanne)
- [L'algorithme de la fonction d'auto-start/stop](#lalgorithme-de-la-fonction-dauto-startstop)
## L'algorithme TPI
### Configurez les coefficients de l'algorithme TPI
Si vous avez choisi un thermostat de type ```over_switch``` ou ```over_valve``` ou `over_climate` avec l'auto-régulation `Controle direct de la vanne` et que vous sélectionnez l'option "TPI" vous menu, vous arriverez sur cette page :
![image](images/config-tpi.png)
Vous devez donner :
1. le coefficient coef_int de l'algorithme TPI,
2. le coefficient coef_ext de l'algorithme TPI
### Principe
L'algorithme TPI consiste à calculer à chaque cycle un pourcentage d'état On vs Off pour le radiateur en utilisant la température cible, la température actuelle dans la pièce et la température extérieure actuelle. Cet algorithme n'est donc valable que pour les Versatile Thermostat qui régulent : `over_switch` et `over_valve`.
Le pourcentage est calculé avec cette formule :
on_percent = coef_int * (température cible - température actuelle) + coef_ext * (température cible - température extérieure)
Ensuite, l'algo fait en sorte que 0 <= on_percent <= 1
Les valeurs par défaut pour coef_int et coef_ext sont respectivement : ``0.6`` et ``0.01``. Ces valeurs par défaut conviennent à une pièce standard bien isolée.
Pour régler ces coefficients, gardez à l'esprit que :
1. **si la température cible n'est pas atteinte** après une situation stable, vous devez augmenter le ``coef_ext`` (le ``on_percent`` est trop bas),
2. **si la température cible est dépassée** après une situation stable, vous devez diminuer le ``coef_ext`` (le ``on_percent`` est trop haut),
3. **si l'atteinte de la température cible est trop lente**, vous pouvez augmenter le ``coef_int`` pour donner plus de puissance au réchauffeur,
4. **si l'atteinte de la température cible est trop rapide et que des oscillations apparaissent** autour de la cible, vous pouvez diminuer le ``coef_int`` pour donner moins de puissance au radiateur.
En type `over_valve` le `on_percent` est ramené à une valeur entre 0 et 100% et sert directement à commander l'ouverture de la vanne.
## L'algorithme d'auto-régulation (sans contrôle de la vanne)
L'algorithme d'auto-régulation peut être synthétisé comme suit:
1. initialiser la température cible comme la consigne du VTherm,
1. Si l'auto-régulation est activée,
1. calcule de la température régulée (valable pour un VTherm),
2. prendre cette température comme cible,
2. Pour chaque sous-jacent du VTherm,
1. Si "utiliser la température interne" est cochée,
1. calcule de la compensation (trv internal temp - room temp),
2. ajout de l'écart à la température cible,
3. envoie de la température cible ( = temp regulee + (temp interne - temp pièce)) au sous-jacent
## L'algorithme de la fonction d'auto-start/stop
L'algorithme utilisé dans la fonction d'auto-start/stop est le suivant :
1. if enable aut-start/stop is off, stop here.
2. If VTherm is on and in heating mode, when error_accumulated is < -error_threshold -> turn off and save hvac mode,
3. If VTherm is on and in Cooling mode, when error_accumulated is > error_threshold -> turn off and save hvac mode,
4. If VTherm is off and saved hvac mode is Heating and current_temperature + slope x dt <= target_temperature then turn on and set havc mode to the saved hvac_mode,
5. If VTherm is off and saved hvac mode is Cooling and current_temperature + slope x dt >= target_temperature then turn on and set havc mode to the saved hvac_mode
6. error_threshold is set to respectively 10 (° * min) in slow, 5 in medium and 2 in fast.
dt is set to respectively 30 min in slow, 15 min in medium and 7 min in fast detection level.
La fonction est décrite dans le détail [ici](https://github.com/jmcollin78/versatile_thermostat/issues/585).

View File

@@ -1,46 +0,0 @@
- [Choix des attributs de base](#choix-des-attributs-de-base)
- [Choix des fonctions utilisées](#choix-des-fonctions-utilisées)
# Choix des attributs de base
Choisisez le menu "Principaux attributs".
![image](images/config-main.png)
Donnez les principaux attributs obligatoires. Ces attributs sont communs à tous les VTherms :
1. un nom (sera le nom de l'intégration et aussi le nom de l'entité `climate`)
4. un identifiant d'entité de capteur de température qui donne la température de la pièce dans laquelle le radiateur est installé,
5. une entité facultative de capteur de donnant la date et heure de dernière vue du capteur (`last_seen`). Si vous avez ce capteur donnez le ici, il permet d'éviter des mises en sécurité lorsque la température est stable et que le capteur ne remonte plus de température pendant longtemps. (cf. [ici](troubleshooting.md#pourquoi-mon-versatile-thermostat-se-met-en-securite-)),
6. une durée de cycle en minutes. A chaque cycle :
1. `over_switch` : VTherm allumera/éteindra le radiateur en modulant la proportion de temps allumé,
2. `over_valve` : VTherm calculera une nouvelle ouverture de la vanne et lui enverra si elle a changée,
3. `over_climate` : le cycle permet d'effectuer les contrôles de base et recalcule les coefficients de l'auto-régulation. Le cycle peut déboucher sur une nouvelle consigne envoyée au sous-jacents ou sur une modification d'ouverture de la vanne dans le cas d'un _TRV_ dont la vanne est commandable.
7. une puissance de l'équipement ce qui va activer les capteurs de puissance et énergie consommée par l'appareil. Si plusieurs équipements sont reliés au même VTherm, il faut indiquer ici le total des puissances max des équipements,
8. la possibilité d'utiliser des paramètres complémentaires venant de la configuration centralisée :
1. capteur de température extérieure,
2. température minimale / maximale et pas de température
9. la possibilité de controler le thermostat de façon centralisée. Cf [controle centralisé](#le-contrôle-centralisé),
10. une case à cocher si ce VTherm est utilisé pour déclencher une éventuelle chaudière centrale.
> ![Astuce](images/tips.png) _*Notes*_
> 1. avec les types ```over_switch``` et ```over_valve```, les calculs sont effectués à chaque cycle. Donc en cas de changement de conditions, il faudra attendre le prochain cycle pour voir un changement. Pour cette raison, le cycle ne doit pas être trop long. **5 min est une bonne valeur** mais doit être adapté à votre type de chauffage. Plus l'inertie est grande et plus le cycle doit être long. Cf. [Exemples de réglages](tuning-examples.md),
> 2. si le cycle est trop court, le radiateur ne pourra jamais atteindre la température cible. Pour le radiateur à accumulation par exemple il sera sollicité inutilement.
# Choix des fonctions utilisées
Choisissez le menu "Fonctions".
![image](images/config-features.png)
Les différentes fonctions que vous souhaitez utiliser pour ce VTherm :
1. la détection d'ouvertures (portes, fenêtres) permettant de stopper le chauffage lorsque l'ouverture est ouverte. (cf. [gestion des ouvertures](feature-window.md))
2. la détection de mouvement : VTherm peut adapter une consigne de température lorsqu'un mouvement est détecté dans la pièce. (cf. [détection du mouvement](feature-motion.md))
3. la gestion de la puissance : VTherm peut stopper un équipement si la puissance consommée dans votre habitation dépasse un seuil. (cf. [gestion du délestage](feature-power.md))
4. la détection de présence : si vous avez un capteur indiquant une présence ou non dans votre habitation, vous pouvez l'utiliser pour changer la température de consigne. Cf. [gestion de la présence](feature-presence.md). Attention de ne pas confondre cette fonction avec la détection de mouvement. La présence est plus faite pour être à l'échelle de l'habitation alors que le mouvement est plus fait pour être à l'échelle de la pièce.
5. l'arrêt/démarrage automatique : pour les VTherm de type `over_climate` uniquement. Cette fonction permet d'arrêter un équipement lorsque VTherm détete qu'il ne sera plus néessaire pendant un certain temps. Il utilise la courbe de température pour prévoir quand l'équipement sera de nouveau utile et le rallumera à ce moment là. Cf. [gestion de l'arrêt/démarrage automatique](feature-auto-start-stop.md)
> ![Astuce](images/tips.png) _*Notes*_
> 1. La liste des fonctions disponibles s'adapte à votre type de VTherm.
> 2. Lorsque vous cochez une fonction, une nouvelle entrée menu s'ajoute pour configurer la fonction.
> 3. Vous ne pourrez pas valider la création d'un VTherm si tous les paramètres de toutes les fonctions n'ont pas été saisis.

Some files were not shown because too many files have changed in this diff Show More