Compare commits

...

5 Commits

Author SHA1 Message Date
Jean-Marc Collin 7dbdcf0ee4 Fix security modes corner cases 2023-02-11 12:38:14 +01:00
Jean-Marc Collin ade1ee4365 Missing translations for preset mode Power and Security #46
Set preset internal #45
Notify (send event messages) when something important happens #43
Enhancing Security mode #42
Rename None preset to Manual #3
2023-02-11 10:34:45 +01:00
adi90x 7b57f7da28 Minimal security threeshold (#44)
* Add SECURITY_MIN_ON_PERCENT
2023-02-10 19:13:57 +01:00
Jean-Marc Collin 9fe307ba1e FIX Issue #38 Support Fahrenheit 2023-02-09 00:05:51 +01:00
Jean-Marc Collin 717c893c75 Reset security mode when preset or hvac_mode change #41 2023-02-07 07:47:07 +01:00
9 changed files with 316 additions and 103 deletions
+5 -1
View File
@@ -247,6 +247,9 @@ Le premier délai (minimal_activation_delay_sec) en sec dans le délai minimum a
Le deuxième délai (security_delay_min) est le délai maximal entre deux mesures de température avant de régler le préréglage sur ``security`` et d'éteindre le thermostat. Si le capteur de température ne donne plus de mesures de température, le thermostat et le radiateur s'éteindront après ce délai et le préréglage du thermostat sera réglé sur ``security``. Ceci est utile pour éviter une surchauffe si la batterie de votre capteur de température est trop faible. Le deuxième délai (security_delay_min) est le délai maximal entre deux mesures de température avant de régler le préréglage sur ``security`` et d'éteindre le thermostat. Si le capteur de température ne donne plus de mesures de température, le thermostat et le radiateur s'éteindront après ce délai et le préréglage du thermostat sera réglé sur ``security``. Ceci est utile pour éviter une surchauffe si la batterie de votre capteur de température est trop faible.
Le troisième paramétre (security_min_on_percent) est la valeur minimal de on_percent en dessous de laquelle le préréglage sécurité ne sera pas activé.
Mettre ce paramètre à ``0.00`` déclenchera le préréglage sécurité quelque soit la dernière consigne de chauffage, à l'inverse ``1.00`` ne déclenchera jamais le préréglage sécurité.
Voir [exemple de réglages](#examples-tuning) pour avoir des exemples de réglage communs Voir [exemple de réglages](#examples-tuning) pour avoir des exemples de réglage communs
> ![Astuce](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_ > ![Astuce](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_
@@ -375,7 +378,8 @@ Les attributs personnalisés sont les suivants :
| ``motion_state`` | Le dernier état connu du capteur de mouvement. Aucun si le mouvement n'est pas configuré | | ``motion_state`` | Le dernier état connu du capteur de mouvement. Aucun si le mouvement n'est pas configuré |
| ``overpowering_state`` | Le dernier état connu du capteur surpuissant. Aucun si la gestion de l'alimentation n'est pas configurée | | ``overpowering_state`` | Le dernier état connu du capteur surpuissant. Aucun si la gestion de l'alimentation n'est pas configurée |
| ``presence_state`` | Le dernier état connu du capteur de présence. Aucun si la gestion de présence n'est pas configurée | | ``presence_state`` | Le dernier état connu du capteur de présence. Aucun si la gestion de présence n'est pas configurée |
| ``delay_security_min`` | Le délai avant de régler le mode de sécurité lorsque le capteur de température est éteint | | ``security_delay_min`` | Le délai avant de régler le mode de sécurité lorsque le capteur de température est éteint |
| ``security_min_on_percent`` | Seuil en dessous duquel le thermostat ne passera pas en sécurité |
| ``last_temperature_datetime`` | La date et l'heure au format ISO8866 de la dernière réception de température interne | | ``last_temperature_datetime`` | La date et l'heure au format ISO8866 de la dernière réception de température interne |
| ``last_ext_temperature_datetime`` | La date et l'heure au format ISO8866 de la dernière réception de température extérieure | | ``last_ext_temperature_datetime`` | La date et l'heure au format ISO8866 de la dernière réception de température extérieure |
| ``**état_sécurité**`` | L'état de sécurité. vrai ou faux | | ``**état_sécurité**`` | L'état de sécurité. vrai ou faux |
+5 -2
View File
@@ -233,13 +233,15 @@ The first delay (minimal_activation_delay_sec) in sec in the minimum delay accep
The second delay (security_delay_min) is the maximal delay between two temperature measure before setting the preset to ``security`` and turning off the thermostat. If the temperature sensor is no more giving temperature measures, the thermostat and heater will turns off after this delay and the preset of the thermostat will be set to ``security``. This is useful to avoid overheating is the battery of your temperature sensor is too low. The second delay (security_delay_min) is the maximal delay between two temperature measure before setting the preset to ``security`` and turning off the thermostat. If the temperature sensor is no more giving temperature measures, the thermostat and heater will turns off after this delay and the preset of the thermostat will be set to ``security``. This is useful to avoid overheating is the battery of your temperature sensor is too low.
The third parameter (security_min_on_percent) is the minimal on_percent value below which the security preset won't be trigger. If you set it to ``0.00`` security preset will be trigger regardeless of the heating on_percent when there is a temperature loss, at the opposite ``1.00`` will never trigger the security preset.
See [exemple tuning](#examples-tuning) to have some commons tuning examples See [exemple tuning](#examples-tuning) to have some commons tuning examples
> ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_ > ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_
1. The ``security`` preset is a hidden preset. You cannot select it manually or by the preset service, 1. The ``security`` preset is a hidden preset. You cannot select it manually or by the preset service,
2. When the temperature sensor will comes to live and re-send temperatures, the preset will be restored to its previous value, 2. When the temperature sensor will comes to live and re-send temperatures, the preset will be restored to its previous value,
3. Beware that two temperatures are needed: internal temp and external temp and each should give temperature else the thermostat will be in ``security`` preset. 3. Beware that two temperatures are needed: internal temp and external temp and each should give temperature else the thermostat will be in ``security`` preset.
# Examples tuning # Examples tuning
## Electrical heater ## Electrical heater
@@ -253,7 +255,7 @@ See [exemple tuning](#examples-tuning) to have some commons tuning examples
## Temperature sensor will battery ## Temperature sensor will battery
- security_delay_min: 60 min (because those sensors are leazy) - security_delay_min: 60 min (because those sensors are leazy)
## Reponsive temperature sensor ## Reponsive temperature sensor
- security_delay_min: 15 min - security_delay_min: 15 min
## My preset configuration ## My preset configuration
@@ -362,6 +364,7 @@ Custom attributes are the following:
| ``overpowering_state`` | The last known state of the overpowering sensor. None if power management is not configured | | ``overpowering_state`` | The last known state of the overpowering sensor. None if power management is not configured |
| ``presence_state`` | The last known state of the presence sensor. None if presence management is not configured | | ``presence_state`` | The last known state of the presence sensor. None if presence management is not configured |
| ``security_delay_min`` | The delay before setting the security mode when temperature sensor are off | | ``security_delay_min`` | The delay before setting the security mode when temperature sensor are off |
| ``security_min_on_percent`` | The minimal on_percent below which security preset won't be trigger |
| ``last_temperature_datetime`` | The date and time in ISO8866 format of the last internal temperature reception | | ``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 | | ``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 | | ``security_state`` | The security state. true or false |
+149 -29
View File
@@ -20,7 +20,6 @@ from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.util.unit_system import UnitOfTemperature
from homeassistant.helpers.event import ( from homeassistant.helpers.event import (
async_track_state_change_event, async_track_state_change_event,
@@ -117,6 +116,10 @@ from .const import (
SERVICE_SET_PRESET_TEMPERATURE, SERVICE_SET_PRESET_TEMPERATURE,
PRESET_AWAY_SUFFIX, PRESET_AWAY_SUFFIX,
CONF_SECURITY_DELAY_MIN, 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_MINIMAL_ACTIVATION_DELAY,
CONF_TEMP_MAX, CONF_TEMP_MAX,
CONF_TEMP_MIN, CONF_TEMP_MIN,
@@ -126,6 +129,7 @@ from .const import (
CONF_THERMOSTAT_CLIMATE, CONF_THERMOSTAT_CLIMATE,
CONF_CLIMATE, CONF_CLIMATE,
UnknownEntity, UnknownEntity,
EventType,
) )
from .prop_algorithm import PropAlgorithm from .prop_algorithm import PropAlgorithm
@@ -182,7 +186,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
# No more needed # No more needed
# _registry: dict[str, object] = {} # _registry: dict[str, object] = {}
def __init__(self, hass, unique_id, name, entry_infos) -> None: def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the thermostat.""" """Initialize the thermostat."""
super().__init__() super().__init__()
@@ -217,7 +221,10 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._presence_state = None self._presence_state = None
self._overpowering_state = None self._overpowering_state = None
self._should_relaunch_control_heating = None self._should_relaunch_control_heating = None
self._security_delay_min = None self._security_delay_min = None
self._security_min_on_percent = None
self._security_default_on_percent = None
self._security_state = None self._security_state = None
self._thermostat_type = None self._thermostat_type = None
@@ -240,7 +247,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
) )
# convert entry_infos into usable attributes # convert entry_infos into usable attributes
presets = {} presets = {}
for (key, value) in CONF_PRESETS.items(): for key, value in CONF_PRESETS.items():
_LOGGER.debug("looking for key=%s, value=%s", key, value) _LOGGER.debug("looking for key=%s, value=%s", key, value)
if value in entry_infos: if value in entry_infos:
presets[key] = entry_infos.get(value) presets[key] = entry_infos.get(value)
@@ -248,7 +255,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
_LOGGER.debug("value %s not found in Entry", value) _LOGGER.debug("value %s not found in Entry", value)
presets_away = {} presets_away = {}
for (key, value) in CONF_PRESETS_AWAY.items(): for key, value in CONF_PRESETS_AWAY.items():
_LOGGER.debug("looking for key=%s, value=%s", key, value) _LOGGER.debug("looking for key=%s, value=%s", key, value)
if value in entry_infos: if value in entry_infos:
presets_away[key] = entry_infos.get(value) presets_away[key] = entry_infos.get(value)
@@ -306,7 +313,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
# self.hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF] # self.hvac_list = [HVAC_MODE_COOL, HVAC_MODE_OFF]
# else: # else:
self._hvac_list = [HVACMode.HEAT, HVACMode.OFF] self._hvac_list = [HVACMode.HEAT, HVACMode.OFF]
self._unit = UnitOfTemperature.CELSIUS
self._unit = self._hass.config.units.temperature_unit
# Will be restored if possible # Will be restored if possible
self._hvac_mode = None # HVAC_MODE_OFF self._hvac_mode = None # HVAC_MODE_OFF
self._saved_hvac_mode = self._hvac_mode self._saved_hvac_mode = self._hvac_mode
@@ -363,6 +371,14 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._tpi_coef_ext = 0 self._tpi_coef_ext = 0
self._security_delay_min = entry_infos.get(CONF_SECURITY_DELAY_MIN) self._security_delay_min = entry_infos.get(CONF_SECURITY_DELAY_MIN)
self._security_min_on_percent = (
entry_infos.get(CONF_SECURITY_MIN_ON_PERCENT)
or DEFAULT_SECURITY_MIN_ON_PERCENT
)
self._security_default_on_percent = (
entry_infos.get(CONF_SECURITY_DEFAULT_ON_PERCENT)
or DEFAULT_SECURITY_DEFAULT_ON_PERCENT
)
self._minimal_activation_delay = entry_infos.get(CONF_MINIMAL_ACTIVATION_DELAY) self._minimal_activation_delay = entry_infos.get(CONF_MINIMAL_ACTIVATION_DELAY)
self._last_temperature_mesure = datetime.now() self._last_temperature_mesure = datetime.now()
self._last_ext_temperature_mesure = datetime.now() self._last_ext_temperature_mesure = datetime.now()
@@ -727,7 +743,12 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
float(old_state.attributes[ATTR_TEMPERATURE]) float(old_state.attributes[ATTR_TEMPERATURE])
) )
if old_state.attributes.get(ATTR_PRESET_MODE) in self._attr_preset_modes: old_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE)
# Never restore a Power or Security preset
if (
old_preset_mode in self._attr_preset_modes
and old_preset_mode not in HIDDEN_PRESETS
):
self._attr_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE) self._attr_preset_mode = old_state.attributes.get(ATTR_PRESET_MODE)
self.save_preset_mode() self.save_preset_mode()
@@ -751,6 +772,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if not self._hvac_mode: if not self._hvac_mode:
self._hvac_mode = HVACMode.OFF 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})
_LOGGER.info( _LOGGER.info(
"%s - restored state is target_temp=%.1f, preset_mode=%s, hvac_mode=%s", "%s - restored state is target_temp=%.1f, preset_mode=%s, hvac_mode=%s",
self, self,
@@ -829,6 +853,9 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
@property @property
def temperature_unit(self): def temperature_unit(self):
"""Return the unit of measurement.""" """Return the unit of measurement."""
if self._is_over_climate and self._underlying_climate:
return self._underlying_climate.temperature_unit
return self._unit return self._unit
@property @property
@@ -1003,8 +1030,11 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
_LOGGER.error("Unrecognized hvac mode: %s", hvac_mode) _LOGGER.error("Unrecognized hvac mode: %s", hvac_mode)
return return
# Ensure we update the current operation after changing the mode # Ensure we update the current operation after changing the mode
self.reset_last_temperature_time()
self.update_custom_attributes() self.update_custom_attributes()
self.async_write_ha_state() self.async_write_ha_state()
self.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": self._hvac_mode})
async def async_set_preset_mode(self, preset_mode): async def async_set_preset_mode(self, preset_mode):
"""Set new preset mode.""" """Set new preset mode."""
@@ -1025,6 +1055,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if preset_mode == self._attr_preset_mode and not force: if preset_mode == self._attr_preset_mode and not force:
# I don't think we need to call async_write_ha_state if we didn't change the state # I don't think we need to call async_write_ha_state if we didn't change the state
return return
old_preset_mode = self._attr_preset_mode
if preset_mode == PRESET_NONE: if preset_mode == PRESET_NONE:
self._attr_preset_mode = PRESET_NONE self._attr_preset_mode = PRESET_NONE
if self._saved_target_temp: if self._saved_target_temp:
@@ -1040,9 +1071,21 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self.find_preset_temp(preset_mode) self.find_preset_temp(preset_mode)
) )
self.save_preset_mode() self.reset_last_temperature_time(old_preset_mode)
self.save_preset_mode()
self.recalculate() self.recalculate()
self.send_event(EventType.PRESET_EVENT, {"preset": self._attr_preset_mode})
def reset_last_temperature_time(self, old_preset_mode=None):
"""Reset to now the last temperature time if conditions are satisfied"""
if (
self._attr_preset_mode not in HIDDEN_PRESETS
and old_preset_mode not in HIDDEN_PRESETS
):
self._last_temperature_mesure = (
self._last_ext_temperature_mesure
) = datetime.now()
def find_preset_temp(self, preset_mode): def find_preset_temp(self, preset_mode):
"""Find the right temperature of a preset considering the presence if configured""" """Find the right temperature of a preset considering the presence if configured"""
@@ -1152,7 +1195,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
async def _async_temperature_changed(self, event): async def _async_temperature_changed(self, event):
"""Handle temperature changes.""" """Handle temperature changes."""
new_state = event.data.get("new_state") new_state = event.data.get("new_state")
_LOGGER.info( _LOGGER.debug(
"%s - Temperature changed. Event.new_state is %s", "%s - Temperature changed. Event.new_state is %s",
self, self,
new_state, new_state,
@@ -1167,7 +1210,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
async def _async_ext_temperature_changed(self, event): async def _async_ext_temperature_changed(self, event):
"""Handle external temperature changes.""" """Handle external temperature changes."""
new_state = event.data.get("new_state") new_state = event.data.get("new_state")
_LOGGER.info( _LOGGER.debug(
"%s - external Temperature changed. Event.new_state is %s", "%s - external Temperature changed. Event.new_state is %s",
self, self,
new_state, new_state,
@@ -1609,6 +1652,15 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self.save_preset_mode() self.save_preset_mode()
await self._async_underlying_entity_turn_off() await self._async_underlying_entity_turn_off()
await self._async_set_preset_mode_internal(PRESET_POWER) await self._async_set_preset_mode_internal(PRESET_POWER)
self.send_event(
EventType.POWER_EVENT,
{
"type": "start",
"current_power": self._current_power,
"device_power": self._device_power,
"current_power_max": self._current_power_max,
},
)
# Check if we need to remove the POWER preset # Check if we need to remove the POWER preset
if ( if (
@@ -1624,6 +1676,15 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if self._is_over_climate: if self._is_over_climate:
await self.restore_hvac_mode() await self.restore_hvac_mode()
await self.restore_preset_mode() await self.restore_preset_mode()
self.send_event(
EventType.POWER_EVENT,
{
"type": "end",
"current_power": self._current_power,
"device_power": self._device_power,
"current_power_max": self._current_power_max,
},
)
self._overpowering_state = ret self._overpowering_state = ret
return self._overpowering_state return self._overpowering_state
@@ -1635,12 +1696,6 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
delta_ext_temp = ( delta_ext_temp = (
now - self._last_ext_temperature_mesure now - self._last_ext_temperature_mesure
).total_seconds() / 60.0 ).total_seconds() / 60.0
_LOGGER.debug(
"%s - checking security delta_temp=%.1f delta_ext_temp=%.1f",
self,
delta_temp,
delta_ext_temp,
)
temp_cond: bool = ( temp_cond: bool = (
delta_temp > self._security_delay_min delta_temp > self._security_delay_min
@@ -1653,7 +1708,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
switch_cond: bool = ( switch_cond: bool = (
not self._is_over_climate not self._is_over_climate
and self._prop_algorithm is not None and self._prop_algorithm is not None
and self._prop_algorithm.on_percent > 0.75 and self._prop_algorithm.calculated_on_percent
> self._security_min_on_percent
) )
ret = False ret = False
@@ -1669,24 +1725,63 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
) )
ret = True ret = True
_LOGGER.debug(
"%s - checking security delta_temp=%.1f delta_ext_temp=%.1f temp_cond=%s climate_cond=%s switch_cond=%s",
self,
delta_temp,
delta_ext_temp,
temp_cond,
climate_cond,
switch_cond,
)
if temp_cond and switch_cond: if temp_cond and switch_cond:
if not self._security_state: if not self._security_state:
_LOGGER.warning( _LOGGER.warning(
"%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and on_percent is high (%.2f). Set it into security mode", "%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and on_percent (%.2f) is over defined value (%.2f). Set it into security mode",
self, self,
self._security_delay_min, self._security_delay_min,
delta_temp, delta_temp,
delta_ext_temp, delta_ext_temp,
self._prop_algorithm.on_percent, self._prop_algorithm.on_percent,
self._security_min_on_percent,
) )
ret = True ret = True
if not self._security_state and temp_cond:
self.send_event(
EventType.TEMPERATURE_EVENT,
{
"last_temperature_mesure": self._last_temperature_mesure.isoformat(),
"last_ext_temperature_mesure": self._last_ext_temperature_mesure.isoformat(),
"current_temp": self._cur_temp,
"current_ext_temp": self._cur_ext_temp,
"target_temp": self.target_temperature,
},
)
if not self._security_state and ret: if not self._security_state and ret:
self._security_state = ret self._security_state = ret
self.save_hvac_mode() self.save_hvac_mode()
self.save_preset_mode() self.save_preset_mode()
await self._async_set_preset_mode_internal(PRESET_SECURITY) await self._async_set_preset_mode_internal(PRESET_SECURITY)
await self.async_set_hvac_mode(HVACMode.OFF) # Turn off the underlying climate or heater if security default on_percent is 0
if self._is_over_climate or self._security_default_on_percent <= 0.0:
await self.async_set_hvac_mode(HVACMode.OFF)
if self._prop_algorithm:
self._prop_algorithm.set_security(self._security_default_on_percent)
self.send_event(
EventType.SECURITY_EVENT,
{
"type": "start",
"last_temperature_mesure": self._last_temperature_mesure.isoformat(),
"last_ext_temperature_mesure": self._last_ext_temperature_mesure.isoformat(),
"current_temp": self._cur_temp,
"current_ext_temp": self._cur_ext_temp,
"target_temp": self.target_temperature,
},
)
if ( if (
self._security_state self._security_state
@@ -1700,15 +1795,30 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
self._saved_preset_mode, self._saved_preset_mode,
) )
self._security_state = ret self._security_state = ret
await self.restore_hvac_mode() # Restore hvac_mode if previously saved
if self._is_over_climate or self._security_default_on_percent <= 0.0:
await self.restore_hvac_mode()
await self.restore_preset_mode() await self.restore_preset_mode()
if self._prop_algorithm:
self._prop_algorithm.unset_security()
self.send_event(
EventType.SECURITY_EVENT,
{
"type": "end",
"last_temperature_mesure": self._last_temperature_mesure.isoformat(),
"last_ext_temperature_mesure": self._last_ext_temperature_mesure.isoformat(),
"current_temp": self._cur_temp,
"current_ext_temp": self._cur_ext_temp,
"target_temp": self.target_temperature,
},
)
return ret return ret
async def _async_control_heating(self, force=False, _=None): async def _async_control_heating(self, force=False, _=None):
"""The main function used to run the calculation at each cycle""" """The main function used to run the calculation at each cycle"""
_LOGGER.info( _LOGGER.debug(
"%s - Checking new cycle. hvac_mode=%s, security_state=%s, preset_mode=%s", "%s - Checking new cycle. hvac_mode=%s, security_state=%s, preset_mode=%s",
self, self,
self._hvac_mode, self._hvac_mode,
@@ -1723,8 +1833,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
return return
security: bool = await self.check_security() security: bool = await self.check_security()
if security: if security and self._is_over_climate:
_LOGGER.debug("%s - End of cycle (security)", self) _LOGGER.debug("%s - End of cycle (security and over climate)", self)
return return
# Stop here if we are off # Stop here if we are off
@@ -1737,7 +1847,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if not self._is_over_climate: if not self._is_over_climate:
on_time_sec: int = self._prop_algorithm.on_time_sec on_time_sec: int = self._prop_algorithm.on_time_sec
off_time_sec: int = self._prop_algorithm.off_time_sec off_time_sec: int = self._prop_algorithm.off_time_sec
_LOGGER.info(
_LOGGER.debug(
"%s - Checking new cycle. on_time_sec=%.0f, off_time_sec=%.0f, security_state=%s, preset_mode=%s", "%s - Checking new cycle. on_time_sec=%.0f, off_time_sec=%.0f, security_state=%s, preset_mode=%s",
self, self,
on_time_sec, on_time_sec,
@@ -1778,13 +1889,12 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
return return
if on: if on:
security = ( if await self.check_overpowering():
await self.check_security()
or await self.check_overpowering()
)
if security:
_LOGGER.debug("%s - End of cycle (3)", self) _LOGGER.debug("%s - End of cycle (3)", self)
return return
# Security mode could have change the on_time percent
await self.check_security()
time = self._prop_algorithm.on_time_sec
action_label = "start" if on else "stop" action_label = "start" if on else "stop"
if self._should_relaunch_control_heating: if self._should_relaunch_control_heating:
@@ -1799,7 +1909,7 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if time > 0: if time > 0:
_LOGGER.info( _LOGGER.info(
"%s - !!! %s heating for %d min %d sec", "%s - %s heating for %d min %d sec",
self, self,
action_label, action_label,
time // 60, time // 60,
@@ -1894,6 +2004,8 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
"overpowering_state": self._overpowering_state, "overpowering_state": self._overpowering_state,
"presence_state": self._presence_state, "presence_state": self._presence_state,
"security_delay_min": self._security_delay_min, "security_delay_min": self._security_delay_min,
"security_min_on_percent": self._security_min_on_percent,
"security_default_on_percent": self._security_default_on_percent,
"last_temperature_datetime": self._last_temperature_mesure.isoformat(), "last_temperature_datetime": self._last_temperature_mesure.isoformat(),
"last_ext_temperature_datetime": self._last_ext_temperature_mesure.isoformat(), "last_ext_temperature_datetime": self._last_ext_temperature_mesure.isoformat(),
"security_state": self._security_state, "security_state": self._security_state,
@@ -1983,3 +2095,11 @@ class VersatileThermostat(ClimateEntity, RestoreEntity):
if self._attr_preset_mode == preset: if self._attr_preset_mode == preset:
await self._async_set_preset_mode_internal(preset, force=True) await self._async_set_preset_mode_internal(preset, force=True)
await self._async_control_heating(force=True) await self._async_control_heating(force=True)
def send_event(self, event_type: EventType, data: dict):
"""Send an event"""
_LOGGER.info("%s - Sending event %s with data: %s", self, event_type, data)
data["entity_id"] = self.entity_id
data["name"] = self.name
data["state_attributes"] = self.state_attributes
self._hass.bus.fire(event_type.value, data)
@@ -63,6 +63,10 @@ from .const import (
CONF_PRESENCE_SENSOR, CONF_PRESENCE_SENSOR,
PROPORTIONAL_FUNCTION_TPI, PROPORTIONAL_FUNCTION_TPI,
CONF_SECURITY_DELAY_MIN, 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_MINIMAL_ACTIVATION_DELAY,
CONF_TEMP_MAX, CONF_TEMP_MAX,
CONF_TEMP_MIN, CONF_TEMP_MIN,
@@ -176,53 +180,16 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
is_empty or self._infos.get(CONF_PRESENCE_SENSOR) is not None is_empty or self._infos.get(CONF_PRESENCE_SENSOR) is not None
) )
# self.hass = async_get_hass()
# ent_reg = async_get(hass=self.hass)
# climates = []
# switches = []
# temp_sensors = []
# power_sensors = []
# window_sensors = []
# presence_sensors = []
#
# k: str
# for k in ent_reg.entities:
# v: RegistryEntry = ent_reg.entities[k]
# _LOGGER.debug("Looking entity: %s", k)
# # if k.startswith(CLIMATE_DOMAIN) and (
# # infos is None or k != infos.get("entity_id")
# # ):
# # _LOGGER.debug("Climate !")
# # climates.append(k)
# if k.startswith(SWITCH_DOMAIN) or k.startswith(INPUT_BOOLEAN_DOMAIN):
# _LOGGER.debug("Switch !")
# switches.append(k)
# elif is_temperature_sensor(v):
# _LOGGER.debug("Temperature sensor !")
# temp_sensors.append(k)
# elif is_power_sensor(v):
# _LOGGER.debug("Power sensor !")
# power_sensors.append(k)
# elif k.startswith(PERSON_DOMAIN):
# _LOGGER.debug("Presence sensor !")
# presence_sensors.append(k)
#
# # window sensor and presence
# if k.startswith(INPUT_BOOLEAN_DOMAIN) or k.startswith(BINARY_SENSOR_DOMAIN):
# _LOGGER.debug("Window or presence sensor !")
# window_sensors.append(k)
# presence_sensors.append(k)
#
# # Special case for climates which are not in EntityRegistry
# climates = self.find_all_climates()
self.STEP_USER_DATA_SCHEMA = vol.Schema( self.STEP_USER_DATA_SCHEMA = vol.Schema(
{ {
vol.Required(CONF_NAME): cv.string, vol.Required(CONF_NAME): cv.string,
vol.Required( vol.Required(
CONF_THERMOSTAT_TYPE, default=CONF_THERMOSTAT_SWITCH CONF_THERMOSTAT_TYPE, default=CONF_THERMOSTAT_SWITCH
): vol.In(CONF_THERMOSTAT_TYPES), ): selector.SelectSelector(
selector.SelectSelectorConfig(
options=CONF_THERMOSTAT_TYPES, translation_key="thermostat_type"
)
),
vol.Required(CONF_TEMP_SENSOR): selector.EntitySelector( vol.Required(CONF_TEMP_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig( selector.EntitySelectorConfig(
domain=[SENSOR_DOMAIN, INPUT_NUMBER_DOMAIN] domain=[SENSOR_DOMAIN, INPUT_NUMBER_DOMAIN]
@@ -353,6 +320,14 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
CONF_MINIMAL_ACTIVATION_DELAY, default=10 CONF_MINIMAL_ACTIVATION_DELAY, default=10
): cv.positive_int, ): cv.positive_int,
vol.Required(CONF_SECURITY_DELAY_MIN, default=60): cv.positive_int, vol.Required(CONF_SECURITY_DELAY_MIN, default=60): cv.positive_int,
vol.Required(
CONF_SECURITY_MIN_ON_PERCENT,
default=DEFAULT_SECURITY_MIN_ON_PERCENT,
): vol.Coerce(float),
vol.Required(
CONF_SECURITY_DEFAULT_ON_PERCENT,
default=DEFAULT_SECURITY_DEFAULT_ON_PERCENT,
): vol.Coerce(float),
} }
) )
@@ -1,5 +1,6 @@
"""Constants for the Versatile Thermostat integration.""" """Constants for the Versatile Thermostat integration."""
from enum import Enum
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
from homeassistant.components.climate.const import ( from homeassistant.components.climate.const import (
# PRESET_ACTIVITY, # PRESET_ACTIVITY,
@@ -44,6 +45,8 @@ CONF_MINIMAL_ACTIVATION_DELAY = "minimal_activation_delay"
CONF_TEMP_MIN = "temp_min" CONF_TEMP_MIN = "temp_min"
CONF_TEMP_MAX = "temp_max" CONF_TEMP_MAX = "temp_max"
CONF_SECURITY_DELAY_MIN = "security_delay_min" CONF_SECURITY_DELAY_MIN = "security_delay_min"
CONF_SECURITY_MIN_ON_PERCENT = "security_min_on_percent"
CONF_SECURITY_DEFAULT_ON_PERCENT = "security_default_on_percent"
CONF_THERMOSTAT_TYPE = "thermostat_type" CONF_THERMOSTAT_TYPE = "thermostat_type"
CONF_THERMOSTAT_SWITCH = "thermostat_over_switch" CONF_THERMOSTAT_SWITCH = "thermostat_over_switch"
CONF_THERMOSTAT_CLIMATE = "thermostat_over_climate" CONF_THERMOSTAT_CLIMATE = "thermostat_over_climate"
@@ -102,6 +105,8 @@ ALL_CONF = (
CONF_TEMP_MIN, CONF_TEMP_MIN,
CONF_TEMP_MAX, CONF_TEMP_MAX,
CONF_SECURITY_DELAY_MIN, CONF_SECURITY_DELAY_MIN,
CONF_SECURITY_MIN_ON_PERCENT,
CONF_SECURITY_DEFAULT_ON_PERCENT,
CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH, CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE, CONF_THERMOSTAT_CLIMATE,
@@ -126,6 +131,17 @@ SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE
SERVICE_SET_PRESENCE = "set_presence" SERVICE_SET_PRESENCE = "set_presence"
SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature" SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"
DEFAULT_SECURITY_MIN_ON_PERCENT = 0.5
DEFAULT_SECURITY_DEFAULT_ON_PERCENT = 0.1
class EventType(Enum):
SECURITY_EVENT: str = "versatile_thermostat_security_event"
POWER_EVENT: str = "versatile_thermostat_power_event"
TEMPERATURE_EVENT: str = "versatile_thermostat_temperature_event"
HVAC_MODE_EVENT: str = "versatile_thermostat_hvac_mode_event"
PRESET_EVENT: str = "versatile_thermostat_preset_event"
class UnknownEntity(HomeAssistantError): class UnknownEntity(HomeAssistantError):
"""Error to indicate there is an unknown entity_id given.""" """Error to indicate there is an unknown entity_id given."""
@@ -37,8 +37,11 @@ class PropAlgorithm:
self._cycle_min = cycle_min self._cycle_min = cycle_min
self._minimal_activation_delay = minimal_activation_delay self._minimal_activation_delay = minimal_activation_delay
self._on_percent = 0 self._on_percent = 0
self._calculated_on_percent = 0
self._on_time_sec = 0 self._on_time_sec = 0
self._off_time_sec = self._cycle_min * 60 self._off_time_sec = self._cycle_min * 60
self._security = False
self._default_on_percent = 0
def calculate( def calculate(
self, target_temp: float, current_temp: float, ext_current_temp: float self, target_temp: float, current_temp: float, ext_current_temp: float
@@ -48,7 +51,7 @@ class PropAlgorithm:
_LOGGER.warning( _LOGGER.warning(
"Proportional algorithm: calculation is not possible cause target_temp or current_temp is null. Heating will be disabled" # pylint: disable=line-too-long "Proportional algorithm: calculation is not possible cause target_temp or current_temp is null. Heating will be disabled" # pylint: disable=line-too-long
) )
self._on_percent = 0 self._calculated_on_percent = 0
else: else:
delta_temp = target_temp - current_temp delta_temp = target_temp - current_temp
delta_ext_temp = ( delta_ext_temp = (
@@ -56,7 +59,7 @@ class PropAlgorithm:
) )
if self._function == PROPORTIONAL_FUNCTION_TPI: if self._function == PROPORTIONAL_FUNCTION_TPI:
self._on_percent = ( self._calculated_on_percent = (
self._tpi_coef_int * delta_temp self._tpi_coef_int * delta_temp
+ self._tpi_coef_ext * delta_ext_temp + self._tpi_coef_ext * delta_ext_temp
) )
@@ -65,7 +68,35 @@ class PropAlgorithm:
"Proportional algorithm: unknown %s function. Heating will be disabled", "Proportional algorithm: unknown %s function. Heating will be disabled",
self._function, self._function,
) )
self._on_percent = 0 self._calculated_on_percent = 0
self._calculate_internal()
_LOGGER.debug(
"heating percent calculated for current_temp %.1f, ext_current_temp %.1f and target_temp %.1f is %.2f, on_time is %d (sec), off_time is %d (sec)", # pylint: disable=line-too-long
current_temp if current_temp else -9999.0,
ext_current_temp if ext_current_temp else -9999.0,
target_temp if target_temp else -9999.0,
self._calculated_on_percent,
self.on_time_sec,
self.off_time_sec,
)
def _calculate_internal(self):
"""Finish the calculation to get the on_percent in seconds"""
if self._security:
_LOGGER.debug(
"Security is On using the default_on_percent %f",
self._default_on_percent,
)
self._on_percent = self._default_on_percent
else:
_LOGGER.debug(
"Security is Off using the calculated_on_percent %f",
self._calculated_on_percent,
)
self._on_percent = self._calculated_on_percent
# calculated on_time duration in seconds # calculated on_time duration in seconds
if self._on_percent > 1: if self._on_percent > 1:
@@ -92,21 +123,31 @@ class PropAlgorithm:
self._off_time_sec = self._cycle_min * 60 - self._on_time_sec self._off_time_sec = self._cycle_min * 60 - self._on_time_sec
_LOGGER.debug( def set_security(self, default_on_percent: float):
"heating percent calculated for current_temp %.1f, ext_current_temp %.1f and target_temp %.1f is %.2f, on_time is %d (sec), off_time is %d (sec)", # pylint: disable=line-too-long """Set a default value for on_percent (used for security mode)"""
current_temp if current_temp else -9999.0, self._security = True
ext_current_temp if ext_current_temp else -9999.0, self._default_on_percent = default_on_percent
target_temp if target_temp else -9999.0, self._calculate_internal()
self._on_percent,
self.on_time_sec, def unset_security(self):
self.off_time_sec, """Unset the security mode"""
) self._security = False
self._calculate_internal()
@property @property
def on_percent(self) -> float: def on_percent(self) -> float:
"""Returns the percentage the heater must be ON (1 means the heater will be always on, 0 never on)""" # pylint: disable=line-too-long """Returns the percentage the heater must be ON
In security mode this value is overriden with the _default_on_percent
(1 means the heater will be always on, 0 never on)""" # pylint: disable=line-too-long
return round(self._on_percent, 2) return round(self._on_percent, 2)
@property
def calculated_on_percent(self) -> float:
"""Returns the calculated percentage the heater must be ON
Calculated means NOT overriden even in security mode
(1 means the heater will be always on, 0 never on)""" # pylint: disable=line-too-long
return round(self._calculated_on_percent, 2)
@property @property
def on_time_sec(self) -> int: def on_time_sec(self) -> int:
"""Returns the calculated time in sec the heater must be ON""" """Returns the calculated time in sec the heater must be ON"""
@@ -89,7 +89,9 @@
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThis parameters can lead to a very bad temperature or power regulation.", "description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThis parameters can lead to a very bad temperature or power regulation.",
"data": { "data": {
"minimal_activation_delay": "Delay in secondes under which the equipment will not be activated", "minimal_activation_delay": "Delay in secondes under which the equipment will not be activated",
"security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a sceurity off state" "security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a sceurity off state",
"security_min_on_percent": "Minimal heating percent value for security preset activation. Below this amount of on_percent the thermostat won't go into security preset",
"security_default_on_percent": "The default heating percent value in security preset. Set to 0 to switch off heater in security present"
} }
} }
}, },
@@ -190,7 +192,9 @@
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThis parameters can lead to a very bad temperature or power regulation.", "description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThis parameters can lead to a very bad temperature or power regulation.",
"data": { "data": {
"minimal_activation_delay": "Delay in secondes under which the equipment will not be activated", "minimal_activation_delay": "Delay in secondes under which the equipment will not be activated",
"security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a sceurity off state" "security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a security off state",
"security_min_on_percent": "Minimal heating percent value for security preset activation. Below this amount of on_percent the thermostat won't go into security preset",
"security_default_on_percent": "The default heating percent value in security preset. Set to 0 to switch off heater in security present"
} }
} }
}, },
@@ -213,13 +217,27 @@
"entity": { "entity": {
"climate": { "climate": {
"versatile_thermostat": { "versatile_thermostat": {
"states_attributes": { "state_attributes": {
"preset_mode": { "preset_mode": {
"power": "Shedding", "state": {
"security": "Security" "power": "Shedding",
"security": "Security",
"none": "Manual"
}
} }
} }
} }
} }
},
"state_attributes": {
"_": {
"preset_mode": {
"state": {
"power": "Shedding",
"security": "Security",
"none": "Manual"
}
}
}
} }
} }
@@ -89,7 +89,9 @@
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThis parameters can lead to a very bad temperature or power regulation.", "description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThis parameters can lead to a very bad temperature or power regulation.",
"data": { "data": {
"minimal_activation_delay": "Delay in secondes under which the equipment will not be activated", "minimal_activation_delay": "Delay in secondes under which the equipment will not be activated",
"security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a sceurity off state" "security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a sceurity off state",
"security_min_on_percent": "Minimal heating percent value for security preset activation. Below this amount of on_percent the thermostat won't go into security preset",
"security_default_on_percent": "The default heating percent value in security preset. Set to 0 to switch off heater in security present"
} }
} }
}, },
@@ -190,7 +192,9 @@
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThis parameters can lead to a very bad temperature or power regulation.", "description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThis parameters can lead to a very bad temperature or power regulation.",
"data": { "data": {
"minimal_activation_delay": "Delay in secondes under which the equipment will not be activated", "minimal_activation_delay": "Delay in secondes under which the equipment will not be activated",
"security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a sceurity off state" "security_delay_min": "Maximum allowed delay in minutes between two temperature mesures. Above this delay, the thermostat will turn to a sceurity off state",
"security_min_on_percent": "Minimal heating percent value for security preset activation. Below this amount of on_percent the thermostat won't go into security preset",
"security_default_on_percent": "The default heating percent value in security preset. Set to 0 to switch off heater in security present"
} }
} }
}, },
@@ -213,13 +217,27 @@
"entity": { "entity": {
"climate": { "climate": {
"versatile_thermostat": { "versatile_thermostat": {
"states_attributes": { "state_attributes": {
"preset_mode": { "preset_mode": {
"power": "Shedding", "state": {
"security": "Security" "power": "Shedding",
"security": "Security",
"none": "Manual"
}
} }
} }
} }
} }
},
"state_attributes": {
"_": {
"preset_mode": {
"state": {
"power": "Shedding",
"security": "Security",
"none": "Manual"
}
}
}
} }
} }
@@ -87,8 +87,10 @@
"title": "Parameters avancés", "title": "Parameters avancés",
"description": "Configuration des paramètres avancés. Laissez les valeurs par défaut si vous ne savez pas ce que vous faites.\nCes paramètres peuvent induire des mauvais comportements du thermostat.", "description": "Configuration des paramètres avancés. Laissez les valeurs par défaut si vous ne savez pas ce que vous faites.\nCes paramètres peuvent induire des mauvais comportements du thermostat.",
"data": { "data": {
"minimal_activation_delay": "Délai en seondes en-dessous duquel l'équipement ne sera pas activé", "minimal_activation_delay": "Délai en secondes en-dessous duquel l'équipement ne sera pas activé",
"security_delay_min": "Délai maximal autorisé en minutes entre 2 mesures de températures. Au-dessus de ce délai, le thermostat se mettra en position éteinte de sécurité" "security_delay_min": "Délai maximal autorisé en minutes entre 2 mesures de températures. Au-dessus de ce délai, le thermostat se mettra en position éteinte de sécurité",
"security_min_on_percent": "Seuil minimal de pourcentage de chauffage en-dessous duquel le préréglage sécurité ne sera jamais activé",
"security_default_on_percent": "Valeur par défaut pour le pourcentage de chauffage en mode sécurité. Mettre 0 pour éteindre le radiateur en mode sécurité"
} }
} }
}, },
@@ -190,7 +192,9 @@
"description": "Configuration des paramètres avancés. Laissez les valeurs par défaut si vous ne savez pas ce que vous faites.\nCes paramètres peuvent induire des mauvais comportements du thermostat.", "description": "Configuration des paramètres avancés. Laissez les valeurs par défaut si vous ne savez pas ce que vous faites.\nCes paramètres peuvent induire des mauvais comportements du thermostat.",
"data": { "data": {
"minimal_activation_delay": "Délai en seondes en-dessous duquel l'équipement ne sera pas activé", "minimal_activation_delay": "Délai en seondes en-dessous duquel l'équipement ne sera pas activé",
"security_delay_min": "Délai maximal autorisé en minutes entre 2 mesures de températures. Au-dessus de ce délai, le thermostat se mettra en position éteinte de sécurité" "security_delay_min": "Délai maximal autorisé en minutes entre 2 mesures de températures. Au-dessus de ce délai, le thermostat se mettra en position éteinte de sécurité",
"security_min_on_percent": "Seuil minimal de pourcentage de chauffage en-dessous duquel le préréglage sécurité ne sera jamais activé",
"security_default_on_percent": "Valeur par défaut pour le pourcentage de chauffage en mode sécurité. Mettre 0 pour éteindre le radiateur en mode sécurité"
} }
} }
}, },
@@ -213,13 +217,27 @@
"entity": { "entity": {
"climate": { "climate": {
"versatile_thermostat": { "versatile_thermostat": {
"states_attributes": { "state_attributes": {
"preset_mode": { "preset_mode": {
"power": "Délestage", "state": {
"security": "Sécurité" "power": "Délestage",
"security": "Sécurité",
"none": "Manuel"
}
} }
} }
} }
} }
},
"state_attributes": {
"_": {
"preset_mode": {
"state": {
"power": "Délestage",
"security": "Sécurité",
"none": "Manuel"
}
}
}
} }
} }