Compare commits

...

4 Commits

Author SHA1 Message Date
Jean-Marc Collin 382f6f99c6 Issue #162 - overpowering mode after preset change 2023-11-06 16:43:59 +00:00
Jean-Marc Collin 95c4aa8ae9 Issue #174 - regression following PR#150 2023-11-06 16:13:35 +00:00
Jean-Marc Collin a6a47fde53 Resolve devcontainers warnings 2023-11-06 15:58:09 +00:00
echopage e08f51b4f2 Update it.json (#172)
Verifica e sostituzione terminologie errate
2023-11-06 16:17:19 +01:00
11 changed files with 277 additions and 106 deletions
+4 -3
View File
@@ -21,7 +21,9 @@
"ms-python.python", "ms-python.python",
"github.vscode-pull-request-github", "github.vscode-pull-request-github",
"ryanluker.vscode-coverage-gutters", "ryanluker.vscode-coverage-gutters",
"ms-python.vscode-pylance" "ms-python.black-formatter",
"ms-python.pylint",
"ferrierbenjamin.fold-unfold-all-icone"
], ],
// "mounts": [ // "mounts": [
// "source=${localWorkspaceFolder}/.devcontainer/configuration.yaml,target=${localWorkspaceFolder}/config/www/community/,type=bind,consistency=cached", // "source=${localWorkspaceFolder}/.devcontainer/configuration.yaml,target=${localWorkspaceFolder}/config/www/community/,type=bind,consistency=cached",
@@ -40,8 +42,7 @@
// "terminal.integrated.shell.linux": "/bin/bash", // "terminal.integrated.shell.linux": "/bin/bash",
"python.pythonPath": "/usr/bin/python3", "python.pythonPath": "/usr/bin/python3",
"python.analysis.autoSearchPaths": true, "python.analysis.autoSearchPaths": true,
"python.linting.pylintEnabled": true, "pylint.lintOnChange": false,
"python.linting.enabled": true,
"python.formatting.provider": "black", "python.formatting.provider": "black",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black", "python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"editor.formatOnPaste": false, "editor.formatOnPaste": false,
+3 -3
View File
@@ -1,9 +1,9 @@
{ {
"[python]": { "[python]": {
"editor.defaultFormatter": "ms-python.python" "editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true
}, },
"python.linting.pylintEnabled": true, "pylint.lintOnChange": false,
"python.linting.enabled": true,
"files.associations": { "files.associations": {
"*.yaml": "home-assistant" "*.yaml": "home-assistant"
}, },
+2 -2
View File
@@ -51,7 +51,7 @@
- [Attributs personnalisés](#attributs-personnalisés) - [Attributs personnalisés](#attributs-personnalisés)
- [Quelques résultats](#quelques-résultats) - [Quelques résultats](#quelques-résultats)
- [Encore mieux](#encore-mieux) - [Encore mieux](#encore-mieux)
- [Bien mieux avec le Veersatile Thermostat UI Card](#bien-mieux-avec-le-veersatile-thermostat-ui-card) - [Bien mieux avec le Versatile Thermostat UI Card](#bien-mieux-avec-le-versatile-thermostat-ui-card)
- [Encore mieux avec le composant Scheduler !](#encore-mieux-avec-le-composant-scheduler-) - [Encore mieux avec le composant Scheduler !](#encore-mieux-avec-le-composant-scheduler-)
- [Encore bien mieux avec la custom:simple-thermostat front integration](#encore-bien-mieux-avec-la-customsimple-thermostat-front-integration) - [Encore bien mieux avec la custom:simple-thermostat front integration](#encore-bien-mieux-avec-la-customsimple-thermostat-front-integration)
- [Toujours mieux avec Apex-chart pour régler votre thermostat](#toujours-mieux-avec-apex-chart-pour-régler-votre-thermostat) - [Toujours mieux avec Apex-chart pour régler votre thermostat](#toujours-mieux-avec-apex-chart-pour-régler-votre-thermostat)
@@ -735,7 +735,7 @@ Enjoy !
# Encore mieux # Encore mieux
## Bien mieux avec le Veersatile Thermostat UI Card ## Bien mieux avec le 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 : 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) ![image](https://github.com/jmcollin78/versatile-thermostat-ui-card/blob/master/assets/1.png?raw=true)
@@ -130,47 +130,50 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
_motion_state: bool _motion_state: bool
_presence_state: bool _presence_state: bool
_window_auto_state: bool _window_auto_state: bool
#PR - Adding Window ByPass
_window_bypass_state: bool _window_bypass_state: bool
_underlyings: list[UnderlyingEntity] _underlyings: list[UnderlyingEntity]
_last_change_time: datetime _last_change_time: datetime
_entity_component_unrecorded_attributes = ClimateEntity._entity_component_unrecorded_attributes.union(frozenset( _entity_component_unrecorded_attributes = (
{ ClimateEntity._entity_component_unrecorded_attributes.union(
"type", frozenset(
"eco_temp", {
"boost_temp", "type",
"comfort_temp", "eco_temp",
"eco_away_temp", "boost_temp",
"boost_away_temp", "comfort_temp",
"comfort_away_temp", "eco_away_temp",
"power_temp", "boost_away_temp",
"ac_mode", "comfort_away_temp",
"current_power_max", "power_temp",
"saved_preset_mode", "ac_mode",
"saved_target_temp", "current_power_max",
"saved_hvac_mode", "saved_preset_mode",
"security_delay_min", "saved_target_temp",
"security_min_on_percent", "saved_hvac_mode",
"security_default_on_percent", "security_delay_min",
"last_temperature_datetime", "security_min_on_percent",
"last_ext_temperature_datetime", "security_default_on_percent",
"minimal_activation_delay_sec", "last_temperature_datetime",
"device_power", "last_ext_temperature_datetime",
"mean_cycle_power", "minimal_activation_delay_sec",
"last_update_datetime", "device_power",
"timezone", "mean_cycle_power",
"window_sensor_entity_id", "last_update_datetime",
"window_delay_sec", "timezone",
"window_auto_open_threshold", "window_sensor_entity_id",
"window_auto_close_threshold", "window_delay_sec",
"window_auto_max_duration", "window_auto_open_threshold",
"motion_sensor_entity_id", "window_auto_close_threshold",
"presence_sensor_entity_id", "window_auto_max_duration",
"power_sensor_entity_id", "motion_sensor_entity_id",
"max_power_sensor_entity_id", "presence_sensor_entity_id",
} "power_sensor_entity_id",
)) "max_power_sensor_entity_id",
}
)
)
)
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None: def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the thermostat.""" """Initialize the thermostat."""
@@ -621,7 +624,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
STATE_UNAVAILABLE, STATE_UNAVAILABLE,
STATE_UNKNOWN, STATE_UNKNOWN,
): ):
self._window_state = (window_state.state == STATE_ON) self._window_state = window_state.state == STATE_ON
_LOGGER.debug( _LOGGER.debug(
"%s - Window state have been retrieved: %s", "%s - Window state have been retrieved: %s",
self, self,
@@ -762,17 +765,17 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
@property @property
def is_over_climate(self) -> bool: def is_over_climate(self) -> bool:
""" True if the Thermostat is over_climate""" """True if the Thermostat is over_climate"""
return False return False
@property @property
def is_over_switch(self) -> bool: def is_over_switch(self) -> bool:
""" True if the Thermostat is over_switch""" """True if the Thermostat is over_switch"""
return False return False
@property @property
def is_over_valve(self) -> bool: def is_over_valve(self) -> bool:
""" True if the Thermostat is over_valve""" """True if the Thermostat is over_valve"""
return False return False
@property @property
@@ -933,10 +936,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
if not self._device_power: if not self._device_power:
return None return None
return float( return float(self._device_power * self._prop_algorithm.on_percent)
self._device_power
* self._prop_algorithm.on_percent
)
@property @property
def total_energy(self) -> float | None: def total_energy(self) -> float | None:
@@ -963,7 +963,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
"""Get the window_auto_state""" """Get the window_auto_state"""
return STATE_ON if self._window_auto_state else STATE_OFF return STATE_ON if self._window_auto_state else STATE_OFF
#PR - Adding Window ByPass
@property @property
def window_bypass_state(self) -> bool | None: def window_bypass_state(self) -> bool | None:
"""Get the Window Bypass""" """Get the Window Bypass"""
@@ -1219,7 +1218,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
async def _async_internal_set_temperature(self, temperature): async def _async_internal_set_temperature(self, temperature):
"""Set the target temperature and the target temperature of underlying climate if any """Set the target temperature and the target temperature of underlying climate if any
For testing purpose you can pass an event_timestamp. For testing purpose you can pass an event_timestamp.
""" """
self._target_temp = temperature self._target_temp = temperature
return return
@@ -1307,7 +1306,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
_LOGGER.debug( _LOGGER.debug(
"Window delay condition is not satisfied. Ignore window event" "Window delay condition is not satisfied. Ignore window event"
) )
self._window_state = (old_state.state == STATE_ON) self._window_state = old_state.state == STATE_ON
return return
_LOGGER.debug("%s - Window delay condition is satisfied", self) _LOGGER.debug("%s - Window delay condition is satisfied", self)
@@ -1318,13 +1317,14 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
_LOGGER.debug("%s - no change in window state. Forget the event") _LOGGER.debug("%s - no change in window state. Forget the event")
return return
self._window_state = new_state.state == STATE_ON
self._window_state = (new_state.state == STATE_ON) # PR - Adding Window ByPass
#PR - Adding Window ByPass
_LOGGER.debug("%s - Window ByPass is : %s", self, self._window_bypass_state) _LOGGER.debug("%s - Window ByPass is : %s", self, self._window_bypass_state)
if self._window_bypass_state: if self._window_bypass_state:
_LOGGER.info("%s - Window ByPass is activated. Ignore window event", self) _LOGGER.info(
"%s - Window ByPass is activated. Ignore window event", self
)
else: else:
if not self._window_state: if not self._window_state:
_LOGGER.info( _LOGGER.info(
@@ -1827,7 +1827,15 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
self._device_power, self._device_power,
) )
ret = (self._current_power + self._device_power) >= self._current_power_max if self.is_over_climate:
power_consumption_max = self._device_power
else:
power_consumption_max = max(
self._device_power / self.nb_underlying_entities,
self._device_power * self._prop_algorithm.on_percent,
)
ret = (self._current_power + power_consumption_max) >= self._current_power_max
if not self._overpowering_state and ret and self._hvac_mode != HVACMode.OFF: if not self._overpowering_state and ret and self._hvac_mode != HVACMode.OFF:
_LOGGER.warning( _LOGGER.warning(
"%s - overpowering is detected. Heater preset will be set to 'power'", "%s - overpowering is detected. Heater preset will be set to 'power'",
@@ -1845,6 +1853,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
"current_power": self._current_power, "current_power": self._current_power,
"device_power": self._device_power, "device_power": self._device_power,
"current_power_max": self._current_power_max, "current_power_max": self._current_power_max,
"current_power_consumption": power_consumption_max,
}, },
) )
@@ -2129,7 +2138,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
"overpowering_state": self.overpowering_state, "overpowering_state": self.overpowering_state,
"presence_state": self._presence_state, "presence_state": self._presence_state,
"window_auto_state": self.window_auto_state, "window_auto_state": self.window_auto_state,
#PR - Adding Window ByPass
"window_bypass_state": self._window_bypass_state, "window_bypass_state": self._window_bypass_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_min_on_percent": self._security_min_on_percent,
@@ -2256,14 +2264,25 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
target: target:
entity_id: climate.thermostat_1 entity_id: climate.thermostat_1
""" """
_LOGGER.info("%s - Calling service_set_window_bypass, window_bypass: %s", self, window_bypass) _LOGGER.info(
"%s - Calling service_set_window_bypass, window_bypass: %s",
self,
window_bypass,
)
self._window_bypass_state = window_bypass self._window_bypass_state = window_bypass
if not self._window_bypass_state and self._window_state: if not self._window_bypass_state and self._window_state:
_LOGGER.info("%s - Last window state was open & ByPass is now off. Set hvac_mode to '%s'", self, HVACMode.OFF) _LOGGER.info(
"%s - Last window state was open & ByPass is now off. Set hvac_mode to '%s'",
self,
HVACMode.OFF,
)
self.save_hvac_mode() self.save_hvac_mode()
await self.async_set_hvac_mode(HVACMode.OFF) await self.async_set_hvac_mode(HVACMode.OFF)
if self._window_bypass_state and self._window_state: if self._window_bypass_state and self._window_state:
_LOGGER.info("%s - Last window state was open & ByPass is now on. Set hvac_mode to last available mode", self) _LOGGER.info(
"%s - Last window state was open & ByPass is now on. Set hvac_mode to last available mode",
self,
)
await self.restore_hvac_mode(True) await self.restore_hvac_mode(True)
self.update_custom_attributes() self.update_custom_attributes()
@@ -348,6 +348,7 @@
}, },
"auto_regulation_mode": { "auto_regulation_mode": {
"options": { "options": {
"auto_regulation_slow": "Slow",
"auto_regulation_strong": "Strong", "auto_regulation_strong": "Strong",
"auto_regulation_medium": "Medium", "auto_regulation_medium": "Medium",
"auto_regulation_light": "Light", "auto_regulation_light": "Light",
@@ -348,6 +348,7 @@
}, },
"auto_regulation_mode": { "auto_regulation_mode": {
"options": { "options": {
"auto_regulation_slow": "Slow",
"auto_regulation_strong": "Strong", "auto_regulation_strong": "Strong",
"auto_regulation_medium": "Medium", "auto_regulation_medium": "Medium",
"auto_regulation_light": "Light", "auto_regulation_light": "Light",
@@ -349,6 +349,7 @@
}, },
"auto_regulation_mode": { "auto_regulation_mode": {
"options": { "options": {
"auto_regulation_slow": "Lente",
"auto_regulation_strong": "Forte", "auto_regulation_strong": "Forte",
"auto_regulation_medium": "Moyenne", "auto_regulation_medium": "Moyenne",
"auto_regulation_light": "Légère", "auto_regulation_light": "Légère",
@@ -30,15 +30,15 @@
"heater_entity3_id": "Terzo riscaldatore", "heater_entity3_id": "Terzo riscaldatore",
"heater_entity4_id": "Quarto riscaldatore", "heater_entity4_id": "Quarto riscaldatore",
"proportional_function": "Algoritmo", "proportional_function": "Algoritmo",
"climate_entity_id": "Termostato sottostante", "climate_entity_id": "Primo termostato",
"climate_entity2_id": "Secundo termostato sottostante", "climate_entity2_id": "Secondo termostato",
"climate_entity3_id": "Terzo termostato sottostante", "climate_entity3_id": "Terzo termostato",
"climate_entity4_id": "Quarto termostato sottostante", "climate_entity4_id": "Quarto termostato",
"ac_mode": "AC mode ?", "ac_mode": "AC mode ?",
"valve_entity_id": "Primo valvola numero", "valve_entity_id": "Prima valvola",
"valve_entity2_id": "Secondo valvola numero", "valve_entity2_id": "Seconda valvolao",
"valve_entity3_id": "Terzo valvola numero", "valve_entity3_id": "Terza valvola",
"valve_entity4_id": "Quarto valvola numero", "valve_entity4_id": "Quarta valvola",
"auto_regulation_mode": "Autoregolamentazione", "auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Comando inverso" "inverse_switch_command": "Comando inverso"
}, },
@@ -48,15 +48,15 @@
"heater_entity3_id": "Entity id del terzo riscaldatore facoltativo. Lasciare vuoto se non utilizzato", "heater_entity3_id": "Entity id del terzo riscaldatore facoltativo. Lasciare vuoto se non utilizzato",
"heater_entity4_id": "Entity id del quarto riscaldatore facoltativo. Lasciare vuoto se non utilizzato", "heater_entity4_id": "Entity id del quarto riscaldatore facoltativo. Lasciare vuoto se non utilizzato",
"proportional_function": "Algoritmo da utilizzare (il TPI per adesso è l'unico)", "proportional_function": "Algoritmo da utilizzare (il TPI per adesso è l'unico)",
"climate_entity_id": "Entity id del termostato sottostante", "climate_entity_id": "Entity id del primo termostato",
"climate_entity2_id": "Entity id del secundo termostato sottostante", "climate_entity2_id": "Entity id del secondo termostato",
"climate_entity3_id": "Entity id del terzo termostato sottostante", "climate_entity3_id": "Entity id del terzo termostato",
"climate_entity4_id": "Entity id del quarto termostato sottostante", "climate_entity4_id": "Entity id del quarto termostato",
"ac_mode": "Utilizzare la modalità AC (Air Conditioned) ?", "ac_mode": "Utilizzare la modalità AC (Air Conditioned) ?",
"valve_entity_id": "Entity id del primo valvola numero", "valve_entity_id": "Entity id della prima valvola",
"valve_entity2_id": "Entity id del secondo valvola numero", "valve_entity2_id": "Entity id della seconda valvola",
"valve_entity3_id": "Entity id del terzo valvola numero", "valve_entity3_id": "Entity id della terza valvola",
"valve_entity4_id": "Entity id del quarto valvola numero", "valve_entity4_id": "Entity id della quarta valvola",
"auto_regulation_mode": "Regolazione automatica della temperatura target", "auto_regulation_mode": "Regolazione automatica della temperatura target",
"inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo" "inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo"
} }
@@ -188,15 +188,15 @@
"heater_entity3_id": "Terzo riscaldatore", "heater_entity3_id": "Terzo riscaldatore",
"heater_entity4_id": "Quarto riscaldatore", "heater_entity4_id": "Quarto riscaldatore",
"proportional_function": "Algoritmo", "proportional_function": "Algoritmo",
"climate_entity_id": "Termostato sottostante", "climate_entity_id": "Primo termostato",
"climate_entity2_id": "Secundo termostato sottostante", "climate_entity2_id": "Secondo termostato",
"climate_entity3_id": "Terzo termostato sottostante", "climate_entity3_id": "Terzo termostato",
"climate_entity4_id": "Quarto termostato sottostante", "climate_entity4_id": "Quarto termostato",
"ac_mode": "AC mode ?", "ac_mode": "AC mode ?",
"valve_entity_id": "Primo valvola numero", "valve_entity_id": "Prima valvola",
"valve_entity2_id": "Secondo valvola numero", "valve_entity2_id": "Seconda valvola",
"valve_entity3_id": "Terzo valvola numero", "valve_entity3_id": "Terza valvola",
"valve_entity4_id": "Quarto valvola numero", "valve_entity4_id": "Quarta valvola",
"auto_regulation_mode": "Autoregolamentazione", "auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Comando inverso" "inverse_switch_command": "Comando inverso"
}, },
@@ -206,15 +206,15 @@
"heater_entity3_id": "Entity id del terzo riscaldatore facoltativo. Lasciare vuoto se non utilizzato", "heater_entity3_id": "Entity id del terzo riscaldatore facoltativo. Lasciare vuoto se non utilizzato",
"heater_entity4_id": "Entity id del quarto riscaldatore facoltativo. Lasciare vuoto se non utilizzato", "heater_entity4_id": "Entity id del quarto riscaldatore facoltativo. Lasciare vuoto se non utilizzato",
"proportional_function": "Algoritmo da utilizzare (il TPI per adesso è l'unico)", "proportional_function": "Algoritmo da utilizzare (il TPI per adesso è l'unico)",
"climate_entity_id": "Entity id del termostato sottostante", "climate_entity_id": "Entity id del primo termostato",
"climate_entity2_id": "Entity id del secundo termostato sottostante", "climate_entity2_id": "Entity id del secondo termostato",
"climate_entity3_id": "Entity id del terzo termostato sottostante", "climate_entity3_id": "Entity id del terzo termostato",
"climate_entity4_id": "Entity id del quarto termostato sottostante", "climate_entity4_id": "Entity id del quarto termostato",
"ac_mode": "Utilizzare la modalità AC (Air Conditioned) ?", "ac_mode": "Utilizzare la modalità AC (Air Conditioned) ?",
"valve_entity_id": "Entity id del primo valvola numero", "valve_entity_id": "Entity id della prima valvola",
"valve_entity2_id": "Entity id del secondo valvola numero", "valve_entity2_id": "Entity id della seconda valvola",
"valve_entity3_id": "Entity id del terzo valvola numero", "valve_entity3_id": "Entity id della terza valvola",
"valve_entity4_id": "Entity id del quarto valvola numero", "valve_entity4_id": "Entity id della quarta valvola",
"auto_regulation_mode": "Autoregolamentazione", "auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo" "inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo"
} }
@@ -252,9 +252,9 @@
"data_description": { "data_description": {
"window_sensor_entity_id": "Lasciare vuoto se non deve essere utilizzato alcun sensore finestra", "window_sensor_entity_id": "Lasciare vuoto se non deve essere utilizzato alcun sensore finestra",
"window_delay": "Ritardo in secondi prima che il rilevamento del sensore sia preso in considerazione", "window_delay": "Ritardo in secondi prima che il rilevamento del sensore sia preso in considerazione",
"window_auto_open_threshold": "Valore consigliato: tra 0.05 e 0.1. Lasciare vuoto se il rilevamento automatico della finestra aperta non è utilizzato", "window_auto_open_threshold": "Valore consigliato: tra 0.05 e 0.1 - Lasciare vuoto se il rilevamento automatico della finestra aperta non è utilizzato",
"window_auto_close_threshold": "Valore consigliato: 0. Lasciare vuoto se il rilevamento automatico della finestra aperta non è utilizzato", "window_auto_close_threshold": "Valore consigliato: 0 - Lasciare vuoto se il rilevamento automatico della finestra aperta non è utilizzato",
"window_auto_max_duration": "Valore consigliato: 60 (un'ora). Lasciare vuoto se il rilevamento automatico della finestra aperta non è utilizzato" "window_auto_max_duration": "Valore consigliato: 60 minuti. Lasciare vuoto se il rilevamento automatico della finestra aperta non è utilizzato"
} }
}, },
"motion": { "motion": {
@@ -320,12 +320,13 @@
"thermostat_type": { "thermostat_type": {
"options": { "options": {
"thermostat_over_switch": "Termostato su un interruttore", "thermostat_over_switch": "Termostato su un interruttore",
"thermostat_over_climate": "Termostato sopra un altro termostato", "thermostat_over_climate": "Termostato su un climatizzatore",
"thermostat_over_valve": "Thermostato su una valvola" "thermostat_over_valve": "Termostato su una valvola"
} }
}, },
"auto_regulation_mode": { "auto_regulation_mode": {
"options": { "options": {
"auto_regulation_slow": "Lento",
"auto_regulation_strong": "Forte", "auto_regulation_strong": "Forte",
"auto_regulation_medium": "Media", "auto_regulation_medium": "Media",
"auto_regulation_light": "Leggera", "auto_regulation_light": "Leggera",
@@ -348,6 +348,7 @@
}, },
"auto_regulation_mode": { "auto_regulation_mode": {
"options": { "options": {
"auto_regulation_slow": "Slow",
"auto_regulation_strong": "Strong", "auto_regulation_strong": "Strong",
"auto_regulation_medium": "Medium", "auto_regulation_medium": "Medium",
"auto_regulation_light": "Light", "auto_regulation_light": "Light",
+145 -3
View File
@@ -163,7 +163,9 @@ async def test_one_switch_cycle(
# assert entity.underlying_entity(0)._should_relaunch_control_heating is True # assert entity.underlying_entity(0)._should_relaunch_control_heating is True
# Simulate the relaunch # Simulate the relaunch
await entity.underlying_entity(0)._turn_on_later( # pylint: disable=protected-access await entity.underlying_entity(
0
)._turn_on_later( # pylint: disable=protected-access
None None
) )
# wait restart # wait restart
@@ -184,7 +186,9 @@ async def test_one_switch_cycle(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active", "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
return_value=True, return_value=True,
) as mock_device_active: ) as mock_device_active:
await entity.underlying_entity(0)._turn_off_later( # pylint: disable=protected-access await entity.underlying_entity(
0
)._turn_off_later( # pylint: disable=protected-access
None None
) )
@@ -207,7 +211,9 @@ async def test_one_switch_cycle(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active", "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
return_value=True, return_value=True,
) as mock_device_active: ) as mock_device_active:
await entity.underlying_entity(0)._turn_on_later( # pylint: disable=protected-access await entity.underlying_entity(
0
)._turn_on_later( # pylint: disable=protected-access
None None
) )
@@ -591,3 +597,139 @@ async def test_multiple_climates_underlying_changes(
assert entity.hvac_mode == HVACMode.HEAT assert entity.hvac_mode == HVACMode.HEAT
assert entity.hvac_action == HVACAction.IDLE assert entity.hvac_action == HVACAction.IDLE
assert entity.is_device_active is False # pylint: disable=protected-access assert entity.is_device_active is False # pylint: disable=protected-access
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_multiple_switch_power_management(
hass: HomeAssistant, skip_hass_states_is_state
):
"""Test the Power management"""
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOver4SwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 8,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: True,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: "switch.mock_switch1",
CONF_HEATER_2: "switch.mock_switch2",
CONF_HEATER_3: "switch.mock_switch3",
CONF_HEATER_4: "switch.mock_switch4",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
CONF_DEVICE_POWER: 100,
CONF_PRESET_POWER: 12,
},
)
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theover4switchmockname"
)
assert entity
assert entity.is_over_climate is False
assert entity.nb_underlying_entities == 4
tpi_algo = entity._prop_algorithm
assert tpi_algo
await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
assert entity.target_temperature == 19
# 1. Send power mesurement
await send_power_change_event(entity, 50, datetime.now())
# Send power max mesurement
await send_max_power_change_event(entity, 300, datetime.now())
assert await entity.check_overpowering() is False
# All configuration is complete and power is < power_max
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is False
# 2. Send power max mesurement too low and HVACMode is on
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
) as mock_heater_on, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off:
# 100 of the device / 4 -> 25, current power 50 so max is 75
await send_max_power_change_event(entity, 74, datetime.now())
assert await entity.check_overpowering() is True
# All configuration is complete and power is > power_max we switch to POWER preset
assert entity.preset_mode is PRESET_POWER
assert entity.overpowering_state is True
assert entity.target_temperature == 12
assert mock_send_event.call_count == 2
mock_send_event.assert_has_calls(
[
call.send_event(EventType.PRESET_EVENT, {"preset": PRESET_POWER}),
call.send_event(
EventType.POWER_EVENT,
{
"type": "start",
"current_power": 50,
"device_power": 100,
"current_power_max": 74,
"current_power_consumption": 25.0,
},
),
],
any_order=True,
)
assert mock_heater_on.call_count == 0
assert mock_heater_off.call_count == 4 # The fourth are shutdown
# 3. change PRESET
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
await entity.async_set_preset_mode(PRESET_ECO)
assert entity.preset_mode is PRESET_ECO
# No change
assert entity.overpowering_state is True
# 4. Send hugh power max mesurement to release overpowering
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
) as mock_heater_on, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off:
# 100 of the device / 4 -> 25, current power 50 so max is 75. With 150 no overheating
await send_max_power_change_event(entity, 150, datetime.now())
assert await entity.check_overpowering() is False
# All configuration is complete and power is > power_max we switch to POWER preset
assert entity.preset_mode is PRESET_ECO
assert entity.overpowering_state is False
assert entity.target_temperature == 17
assert (
mock_heater_on.call_count == 0
) # The fourth are not restarted because temperature is enought
assert mock_heater_off.call_count == 0
+5 -1
View File
@@ -4,8 +4,11 @@ from unittest.mock import patch, call
from datetime import datetime, timedelta from datetime import datetime, timedelta
import logging import logging
from custom_components.versatile_thermostat.thermostat_switch import ThermostatOverSwitch from custom_components.versatile_thermostat.thermostat_switch import (
ThermostatOverSwitch,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
@@ -185,6 +188,7 @@ async def test_power_management_hvac_on(hass: HomeAssistant, skip_hass_states_is
"current_power": 50, "current_power": 50,
"device_power": 100, "device_power": 100,
"current_power_max": 149, "current_power_max": 149,
"current_power_consumption": 100.0,
}, },
), ),
], ],