Feature 158 central mode (#309)
* Normal algo working and testu ok * Fix interaction with window * FIX complex scenario * pylint warning * Release * Issue #306 * Issue #306 --------- Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
default_config:
|
||||
|
||||
# ffmeg
|
||||
ffmpeg:
|
||||
|
||||
logger:
|
||||
default: info
|
||||
logs:
|
||||
|
||||
39
README-fr.md
39
README-fr.md
@@ -34,6 +34,7 @@
|
||||
- [Configurer la gestion de la puissance](#configurer-la-gestion-de-la-puissance)
|
||||
- [Configurer la présence ou l'occupation](#configurer-la-présence-ou-loccupation)
|
||||
- [Configuration avancée](#configuration-avancée)
|
||||
- [Le contrôle centralisé](#le-contrôle-centralisé)
|
||||
- [Synthèse des paramètres](#synthèse-des-paramètres)
|
||||
- [Exemples de réglage](#exemples-de-réglage)
|
||||
- [Chauffage électrique](#chauffage-électrique)
|
||||
@@ -74,14 +75,16 @@ Ce composant personnalisé pour Home Assistant est une mise à niveau et est une
|
||||
|
||||
|
||||
>  _*Nouveautés*_
|
||||
> * **Release 5.2** : Ajout d'un `central_mode` permettant de piloter tous les VTherms de façon centralisée [#158](https://github.com/jmcollin78/versatile_thermostat/issues/158).
|
||||
> * **Release 5.1** : Limitation des valeurs envoyées aux valves et au température envoyées au climate sous-jacent.
|
||||
> * **Release 5.0** : Ajout d'une configuration centrale permettant de mettre en commun les attributs qui peuvent l'être [#239](https://github.com/jmcollin78/versatile_thermostat/issues/239).
|
||||
> * **Release 4.3** : Ajout d'un mode auto-fan pour le type `over_climate` permettant d'activer la ventilation si l'écart de température est important [#223](https://github.com/jmcollin78/versatile_thermostat/issues/223).
|
||||
> * **Release 4.2** : Le calcul de la pente de la courbe de température se fait maintenant en °/heure et non plus en °/min [#242](https://github.com/jmcollin78/versatile_thermostat/issues/242). Correction de la détection automatique des ouvertures par l'ajout d'un lissage de la courbe de température .
|
||||
> * **Release 4.1** : Ajout d'un mode de régulation **Expert** dans lequel l'utilisateur peut spécifier ses propres paramètres d'auto-régulation au lieu d'utiliser les pre-programmés [#194](https://github.com/jmcollin78/versatile_thermostat/issues/194).
|
||||
> * **Release 4.0** : Ajout de la prise en charge de la **Versatile Thermostat UI Card**. Voir [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card). Ajout d'un mode de régulation **Slow** pour les appareils de chauffage à latence lente [#168](https://github.com/jmcollin78/versatile_thermostat/issues/168). Changement de la façon dont **la puissance est calculée** dans le cas de VTherm avec des équipements multi-sous-jacents [#146](https://github.com/jmcollin78/versatile_thermostat/issues/146). Ajout de la prise en charge de AC et Heat pour VTherm via un interrupteur également [#144](https://github.com/jmcollin78/versatile_thermostat/pull/144)
|
||||
<details>
|
||||
<summary>Autres versions</summary>
|
||||
|
||||
> * **Release 4.1** : Ajout d'un mode de régulation **Expert** dans lequel l'utilisateur peut spécifier ses propres paramètres d'auto-régulation au lieu d'utiliser les pre-programmés [#194](https://github.com/jmcollin78/versatile_thermostat/issues/194).
|
||||
> * **Release 4.0** : Ajout de la prise en charge de la **Versatile Thermostat UI Card**. Voir [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card). Ajout d'un mode de régulation **Slow** pour les appareils de chauffage à latence lente [#168](https://github.com/jmcollin78/versatile_thermostat/issues/168). Changement de la façon dont **la puissance est calculée** dans le cas de VTherm avec des équipements multi-sous-jacents [#146](https://github.com/jmcollin78/versatile_thermostat/issues/146). Ajout de la prise en charge de AC et Heat pour VTherm via un interrupteur également [#144](https://github.com/jmcollin78/versatile_thermostat/pull/144)
|
||||
> * **Release 3.8**: Ajout d'une **fonction d'auto-régulation** pour les thermostats `over climate` dont la régulation est faite par le climate sous-jacent. Cf. [L'auto-régulation](#lauto-régulation) et [#129](https://github.com/jmcollin78/versatile_thermostat/issues/129). Ajout de la **possibilité d'inverser la commande** pour un thermostat `over switch` pour adresser les installations avec fil pilote et diode [#124](https://github.com/jmcollin78/versatile_thermostat/issues/124).
|
||||
> * **Release 3.7**: Ajout du type de **Versatile Thermostat `over valve`** pour piloter une vanne TRV directement ou tout autre équipement type gradateur pour le chauffage. La régulation se fait alors directement en agissant sur le pourcentage d'ouverture de l'entité sous-jacente : 0 la vanne est coupée, 100 : la vanne est ouverte à fond. Cf. [#131](https://github.com/jmcollin78/versatile_thermostat/issues/131). Ajout d'une fonction permettant le bypass de la détection d'ouverture [#138](https://github.com/jmcollin78/versatile_thermostat/issues/138). Ajout de la langue Slovaque
|
||||
> * **Release 3.6**: Ajout du paramètre `motion_off_delay` pour améliorer la gestion de des mouvements [#116](https://github.com/jmcollin78/versatile_thermostat/issues/116), [#128](https://github.com/jmcollin78/versatile_thermostat/issues/128). Ajout du mode AC (air conditionné) pour un VTherm over switch. Préparation du projet Github pour faciliter les contributions [#127](https://github.com/jmcollin78/versatile_thermostat/issues/127)
|
||||
@@ -150,7 +153,8 @@ Ce composant nommé __Versatile thermostat__ gère les cas d'utilisation suivant
|
||||
- Ajouter une **gestion de délestage** ou une régulation pour ne pas dépasser une puissance totale définie. Lorsque la puissance maximale est dépassée, un préréglage caché de « puissance » est défini sur l'entité climatique. Lorsque la puissance passe en dessous du maximum, le préréglage précédent est restauré.
|
||||
- La **gestion de la présence à domicile**. Cette fonctionnalité vous permet de modifier dynamiquement la température du préréglage en tenant compte d'un capteur de présence de votre maison.
|
||||
- Des **services pour interagir avec le thermostat** à partir d'autres intégrations : vous pouvez forcer la présence / la non-présence à l'aide d'un service, et vous pouvez modifier dynamiquement la température des préréglages et changer les paramètres de sécurité.
|
||||
- Ajouter des capteurs pour voir les états internes du thermostat.
|
||||
- Ajouter des capteurs pour voir les états internes du thermostat,
|
||||
- Contrôle centralisé de tous les Versatile Thermostat pour les stopper tous, les passer tous en hors-gel, les forcer en mode Chauffage (l'hiver), les forcer en mode Climatisation (l'été).
|
||||
|
||||
# Comment installer cet incroyable Thermostat Versatile ?
|
||||
|
||||
@@ -187,7 +191,9 @@ Suivez ensuite les étapes de configuration comme suit :
|
||||
|
||||
## Choix des attributs de base
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
Donnez les principaux attributs obligatoires :
|
||||
1. un nom (sera le nom de l'intégration et aussi le nom de l'entité climate)
|
||||
@@ -197,7 +203,8 @@ Donnez les principaux attributs obligatoires :
|
||||
6. une durée de cycle en minutes. A chaque cycle, le radiateur s'allumera puis s'éteindra pendant une durée calculée afin d'atteindre la température ciblée (voir [preset](#configure-the-preset-temperature) ci-dessous). En mode ```over_climate```, le cycle ne sert qu'à faire des controles de base mais ne régule pas directement la température. C'est le ```climate``` sous-jacent qui le fait,
|
||||
7. les températures minimales et maximales du thermostat,
|
||||
8. une puissance de l'équipement ce qui va activer les capteurs de puissance et énergie consommée par l'appareil,
|
||||
9. la liste des fonctionnalités qui seront utilisées pour ce thermostat. En fonction de vos choix, les écrans de configuration suivants s'afficheront ou pas.
|
||||
9. la possibilité de controler le thermostat de façon centralisée. Cf [controle centralisé](#le-contrôle-centralisé),
|
||||
10. la liste des fonctionnalités qui seront utilisées pour ce thermostat. En fonction de vos choix, les écrans de configuration suivants s'afficheront ou pas.
|
||||
|
||||
>  _*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**,
|
||||
@@ -515,6 +522,21 @@ Voir [exemple de réglages](#examples-tuning) pour avoir des exemples de réglag
|
||||
> 4. Pour un usage naturel, le ``security_default_on_percent`` doit être inférieur à ``security_min_on_percent``,
|
||||
> 5. Les thermostats de type ``thermostat_over_climate`` ne sont pas concernés par le mode security.
|
||||
|
||||
## Le contrôle centralisé
|
||||
Depuis la release 5.2, si vous avez défini une configuration centralisée, vous avez une nouvelle entité nommée `select.central_mode` qui permet de piloter tous les VTherms avec une seule action. Pour qu'un VTherm soit contrôlable de façon centralisée, il faut que son attribut de configuration nommé `use_central_mode` soit vrai.
|
||||
|
||||
Cette entité se présente sous la forme d'une liste de choix qui contient les choix suivants :
|
||||
1. `Auto` : le mode 'normal' dans lequel chaque VTherm se comporte comme dans les versions précédentes,
|
||||
2. `Stooped` : tous les VTherms sont mis à l'arrêt (`hvac_off`),
|
||||
3. `Heat only` : tous les VTherms sont mis en mode chauffage lorsque ce mode est supporté par le VTherm, sinon il est stoppé,
|
||||
3. `Cool only` : tous les VTherms sont mis en mode climatisation lorsque ce mode est supporté par le VTherm, sinon il est stoppé,
|
||||
4. `Frost protection` : tous les VTherms sont mis en preset hors-gel lorsque ce preset est supporté par le VTherm, sinon il est stoppé.
|
||||
|
||||
Il est donc possible de contrôler tous les VTherms (que ceux que l'on désigne explicitement) avec un seul contrôle.
|
||||
Exemple de rendu :
|
||||
|
||||

|
||||
|
||||
## Synthèse des paramètres
|
||||
|
||||
| Paramètre | Libellé | "over switch" | "over climate" | "over valve" | "configuration centrale" |
|
||||
@@ -527,6 +549,7 @@ Voir [exemple de réglages](#examples-tuning) pour avoir des exemples de réglag
|
||||
| ``temp_min`` | Température minimale permise | X | X | X | X |
|
||||
| ``temp_max`` | Température maximale permise | X | X | X | X |
|
||||
| ``device_power`` | Puissance de l'équipement | X | X | X | - |
|
||||
| ``use_central_mode`` | Autorisation du contrôle centralisé | X | X | X | - |
|
||||
| ``use_window_feature`` | Avec détection des ouvertures | X | X | X | - |
|
||||
| ``use_motion_feature`` | Avec détection de mouvement | X | X | X | - |
|
||||
| ``use_power_feature`` | Avec gestion de la puissance | X | X | X | - |
|
||||
@@ -835,6 +858,8 @@ Les attributs personnalisés sont les suivants :
|
||||
| ``valve_open_percent`` | Le pourcentage d'ouverture de la vanne |
|
||||
| ``regulated_target_temperature`` | La température de consigne calculée par l'auto-régulation |
|
||||
| ``is_inversed`` | True si la commande est inversée (fil pilote avec diode) |
|
||||
| ``is_controlled_by_central_mode`` | True si le VTherm peut être controlé de façon centrale |
|
||||
| ``last_central_mode`` | Le dernier mode central utilisé (None si le VTherm n'est pas controlé en central) |
|
||||
|
||||
# Quelques résultats
|
||||
|
||||
@@ -1008,6 +1033,10 @@ Remplacez les valeurs entre [[ ]] par les votres.
|
||||
step: day
|
||||
```
|
||||
|
||||
Exemple de courbes obtenues avec Plotly :
|
||||
|
||||

|
||||
|
||||
## Et toujours de mieux en mieux avec l'AappDaemon NOTIFIER pour notifier les évènements
|
||||
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.
|
||||
|
||||
|
||||
43
README.md
43
README.md
@@ -34,6 +34,7 @@
|
||||
- [Configure the power management](#configure-the-power-management)
|
||||
- [Configure presence or occupancy](#configure-presence-or-occupancy)
|
||||
- [Advanced configuration](#advanced-configuration)
|
||||
- [Centralized control](#centralized-control)
|
||||
- [Parameters synthesis](#parameters-synthesis)
|
||||
- [Examples tuning](#examples-tuning)
|
||||
- [Electrical heater](#electrical-heater)
|
||||
@@ -74,16 +75,18 @@
|
||||
This custom component for Home Assistant is an upgrade and is a complete rewrite of the component "Awesome thermostat" (see [Github](https://github.com/dadge/awesome_thermostat)) with addition of features.
|
||||
|
||||
> _*News*_
|
||||
> * **Release 5.2**: Added a `central_mode` allowing all VTherms to be controlled centrally [#158](https://github.com/jmcollin78/versatile_thermostat/issues/158).
|
||||
> * **Release 5.1**: Limitation of the values sent to the valves and the temperature sent to the underlying climate.
|
||||
> * **Release 5.0**: Added a central configuration allowing the sharing of attributes that can be shared [#239](https://github.com/jmcollin78/versatile_thermostat/issues/239).
|
||||
> * **Release 4.3**: Added an auto-fan mode for the `over_climate` type allowing ventilation to be activated if the temperature difference is significant [#223](https://github.com/jmcollin78/versatile_thermostat/issues/223).
|
||||
> * **Release 4.2**: The calculation of the slope of the temperature curve is now done in °/hour and no longer in °/min [#242](https://github.com/jmcollin78/versatile_thermostat/issues/242). Correction of automatic detection of openings by adding smoothing of the temperature curve.
|
||||
<details>
|
||||
<summary>Others releases</summary>
|
||||
|
||||
> * **Release 4.1**: Added an **Expert** regulation mode in which the user can specify their own auto-regulation parameters instead of using the pre-programmed ones [#194]( https://github.com/jmcollin78/versatile_thermostat/issues/194).
|
||||
> * **Release 4.0**: Added the support of **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). Change the way **the power is calculated** in case of VTherm with multi-underlying equipements [#146](https://github.com/jmcollin78/versatile_thermostat/issues/146). Added the support of AC and Heat for VTherm over switch alse [#144](https://github.com/jmcollin78/versatile_thermostat/pull/144)
|
||||
> * **Release 3.8**: Added a **self-regulation function** for `over climate` thermostats whose regulation is done by the underlying climate. See [Self-regulation](#self-regulation) and [#129](https://github.com/jmcollin78/versatile_thermostat/issues/129). Added the possibility of **inverting the command** for an `over switch` thermostat to address installations with pilot wire and diode [#124](https://github.com/jmcollin78/versatile_thermostat/issues/124).
|
||||
> * **Release 3.7**: Addition of the **Versatile Thermostat type `over valve`** to control a TRV valve directly or any other dimmer type equipment for heating. Regulation is then done directly by acting on the opening percentage of the underlying entity: 0 the valve is cut off, 100: the valve is fully opened. See [#131](https://github.com/jmcollin78/versatile_thermostat/issues/131). Added a function allowing the bypass of opening detection [#138](https://github.com/jmcollin78/versatile_thermostat/issues/138). Added Slovak language
|
||||
<details>
|
||||
<summary>Others releases</summary>
|
||||
|
||||
> * **Release 3.6**: Added the `motion_off_delay` parameter to improve motion management [#116](https://github.com/jmcollin78/versatile_thermostat/issues/116), [#128](https://github.com/jmcollin78/versatile_thermostat/issues/128). Added AC (air conditioning) mode for a VTherm over switch. Preparing the Github project to facilitate contributions [#127](https://github.com/jmcollin78/versatile_thermostat/issues/127)
|
||||
> * **Release 3.5**: Multiple thermostats when using "thermostat over another thermostat" mode [#113](https://github.com/jmcollin78/versatile_thermostat/issues/113)
|
||||
> * **Release 3.4**: bug fixes and expose preset temperatures for AC mode [#103](https://github.com/jmcollin78/versatile_thermostat/issues/103)
|
||||
@@ -150,7 +153,8 @@ This component named __Versatile thermostat__ manage the following use cases :
|
||||
- Add **power shedding management** or regulation to avoid exceeding a defined total power. When max power is exceeded, a hidden 'power' preset is set on the climate entity. When power goes below the max, the previous preset is restored.
|
||||
- Add **home presence management**. This feature allows you to dynamically change the temperature of preset considering a occupancy sensor of your home.
|
||||
- Add **services to interact with the thermostat** from others integration: you can force the presence / un-presence using a service, and you can dynamically change the temperature of the presets and change dynamically the safety parameters.
|
||||
- Add sensors to see the internal states of the thermostat
|
||||
- Add sensors to see the internal states of the thermostat,
|
||||
- Centralized control of all Versatile Thermostats to stop them all, switch them all to frost protection, force them into Heating mode (winter), force them into Cooling mode (summer).
|
||||
|
||||
# How to install this incredible Versatile Thermostat ?
|
||||
|
||||
@@ -185,7 +189,10 @@ The configuration can be change through the same interface. Simply select the th
|
||||
Then follow the configurations steps as follow:
|
||||
|
||||
## Minimal configuration update
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Give the main mandatory attributes:
|
||||
1. a name (will be the name of the integration and also the name of the climate entity)
|
||||
@@ -195,7 +202,8 @@ Give the main mandatory attributes:
|
||||
6. a cycle duration in minutes. On each cycle, the heater will cycle on and then off for a calculated time to reach the target temperature (see [preset](#configure-the-preset-temperature) below). In ```over_climate``` mode, the cycle is only used to carry out basic controls but does not directly regulate the temperature. It's the underlying climate that does it,
|
||||
7. minimum and maximum thermostat temperatures,
|
||||
8. the power of the l'équipement which will activate the power and energy sensors of the device,
|
||||
9. the list of features that will be used for this thermostat. Depending on your choices, the following configuration screens will appear or not.
|
||||
9. the possibility of controlling the thermostat centrally. Cf [centralized control](#centralized-control),
|
||||
10. the list of features that will be used for this thermostat. Depending on your choices, the following configuration screens will appear or not.
|
||||
|
||||
>  _*Notes*_
|
||||
> 1. With the ```thermostat_over_switch``` type, calculation are done at each cycle. So in case of conditions change, you will have to wait for the next cycle to see a change. For this reason, the cycle should not be too long. **5 min is a good value**,
|
||||
@@ -500,6 +508,21 @@ See [example tuning](#examples-tuning) for common tuning examples
|
||||
> 4. For natural usage, the ``security_default_on_percent`` should be less than ``security_min_on_percent``,
|
||||
> 5. Thermostat of type ``thermostat_over_climate`` are not concerned by the safety feature.
|
||||
|
||||
## Centralized control
|
||||
Since release 5.2, if you have defined a centralized configuration, you have a new entity named `select.central_mode` which allows you to control all VTherms with a single action. For a VTherm to be centrally controllable, its configuration attribute named `use_central_mode` must be true.
|
||||
|
||||
This entity is presented in the form of a list of choices which contains the following choices:
|
||||
1. `Auto`: the 'normal' mode in which each VTherm behaves as in previous versions,
|
||||
2. `Stooped`: all VTherms are turned off (`hvac_off`),
|
||||
3. `Heat only`: all VTherms are put in heating mode when this mode is supported by the VTherm, otherwise it is stopped,
|
||||
3. `Cool only`: all VTherms are put in cooling mode when this mode is supported by the VTherm, otherwise it is stopped,
|
||||
4. `Frost protection`: all VTherms are put in frost protection preset when this preset is supported by the VTherm, otherwise it is stopped.
|
||||
|
||||
It is therefore possible to control all VTherms (only those explicitly designated) with a single control.
|
||||
Example rendering:
|
||||
|
||||

|
||||
|
||||
## Parameters synthesis
|
||||
|
||||
| Paramètre | Libellé | "over switch" | "over climate" | "over valve" | "central configuration" |
|
||||
@@ -512,6 +535,7 @@ See [example tuning](#examples-tuning) for common tuning examples
|
||||
| ``temp_min`` | Minimal temperature allowed | X | X | X | X |
|
||||
| ``temp_max`` | Maximal temperature allowed | X | X | X | X |
|
||||
| ``device_power`` | Total device power | X | X | X | - |
|
||||
| ``use_central_mode`` | Allow the centralized control | X | X | X | - |
|
||||
| ``use_window_feature`` | Use window detection | X | X | X | - |
|
||||
| ``use_motion_feature`` | Use motion detection | X | X | X | - |
|
||||
| ``use_power_feature`` | Use power management | X | X | X | - |
|
||||
@@ -819,6 +843,8 @@ Custom attributes are the following:
|
||||
| ``valve_open_percent`` | The opening percentage of the valve |
|
||||
| ``regulated_target_temperature`` | The self-regulated target temperature calculated |
|
||||
| ``is_inversed`` | True if the command is inversed (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) |
|
||||
|
||||
# Some results
|
||||
|
||||
@@ -991,6 +1017,11 @@ Replace values in [[ ]] by yours.
|
||||
step: day
|
||||
```
|
||||
|
||||
Example of graph obtained with Plotly :
|
||||
|
||||

|
||||
|
||||
|
||||
## And always better and better with the NOTIFIER daemon app to notify events
|
||||
This automation uses the excellent App Daemon named NOTIFIER developed by Horizon Domotique that you will find in demonstration [here](https://www.youtube.com/watch?v=chJylIK0ASo&ab_channel=HorizonDomotique) and the code is [here](https ://github.com/jlpouffier/home-assistant-config/blob/master/appdaemon/apps/notifier.py). It allows you to notify the users of the accommodation when one of the events affecting safety occurs on one of the Versatile Thermostats.
|
||||
|
||||
|
||||
@@ -116,6 +116,11 @@ from .const import (
|
||||
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,
|
||||
)
|
||||
|
||||
from .config_schema import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
@@ -158,6 +163,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
frozenset(
|
||||
{
|
||||
"is_on",
|
||||
"is_controlled_by_central_mode",
|
||||
"last_central_mode",
|
||||
"type",
|
||||
"frost_temp",
|
||||
"eco_temp",
|
||||
@@ -273,6 +280,9 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
self._now = None
|
||||
|
||||
self._attr_fan_mode = None
|
||||
|
||||
self._is_central_mode = None
|
||||
self._last_central_mode = None
|
||||
self.post_init(entry_infos)
|
||||
|
||||
def clean_central_config_doublon(self, config_entry, central_config) -> dict:
|
||||
@@ -434,6 +444,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
self._presence_on = self._presence_sensor_entity_id is not None
|
||||
|
||||
if self._ac_mode:
|
||||
# Added by https://github.com/jmcollin78/versatile_thermostat/pull/144
|
||||
# Some over_switch can do both heating and cooling
|
||||
self._hvac_list = [HVACMode.HEAT, HVACMode.COOL, HVACMode.OFF]
|
||||
else:
|
||||
self._hvac_list = [HVACMode.HEAT, HVACMode.OFF]
|
||||
@@ -552,6 +564,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
short_ema_params.get("max_alpha"),
|
||||
)
|
||||
|
||||
self._is_central_mode = not (
|
||||
entry_infos.get(CONF_USE_CENTRAL_MODE) is False
|
||||
) # Default value (None) is True
|
||||
|
||||
_LOGGER.debug(
|
||||
"%s - Creation of a new VersatileThermostat entity: unique_id=%s",
|
||||
self,
|
||||
@@ -1130,6 +1146,17 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
"""True if the VTherm is on (! HVAC_OFF)"""
|
||||
return self.hvac_mode and self.hvac_mode != HVACMode.OFF
|
||||
|
||||
@property
|
||||
def is_controlled_by_central_mode(self) -> bool:
|
||||
"""Returns True if this VTherm can be controlled by the central_mode"""
|
||||
return self._is_central_mode
|
||||
|
||||
@property
|
||||
def last_central_mode(self) -> str | None:
|
||||
"""Returns the last central_mode taken into account.
|
||||
Is None if the VTherm is not controlled by central_mode"""
|
||||
return self._last_central_mode
|
||||
|
||||
def underlying_entity_id(self, index=0) -> str | None:
|
||||
"""The climate_entity_id. Added for retrocompatibility reason"""
|
||||
if index < self.nb_underlying_entities:
|
||||
@@ -1177,11 +1204,11 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
)
|
||||
|
||||
# If AC is on maybe we have to change the temperature in force mode, but not in frost mode (there is no Frost protection possible in AC mode)
|
||||
if self._ac_mode:
|
||||
if self._hvac_mode == HVACMode.COOL:
|
||||
if self.preset_mode != PRESET_FROST_PROTECTION:
|
||||
await self._async_set_preset_mode_internal(self._attr_preset_mode, True)
|
||||
else:
|
||||
await self._async_set_preset_mode_internal(PRESET_ECO, True)
|
||||
await self._async_set_preset_mode_internal(PRESET_ECO, True, False)
|
||||
|
||||
if need_control_heating and sub_need_control_heating:
|
||||
await self.async_control_heating(force=True)
|
||||
@@ -1195,12 +1222,17 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
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):
|
||||
@overrides
|
||||
async def async_set_preset_mode(self, preset_mode, overwrite_saved_preset=True):
|
||||
"""Set new preset mode."""
|
||||
await self._async_set_preset_mode_internal(preset_mode)
|
||||
await self._async_set_preset_mode_internal(
|
||||
preset_mode, force=False, overwrite_saved_preset=overwrite_saved_preset
|
||||
)
|
||||
await self.async_control_heating(force=True)
|
||||
|
||||
async def _async_set_preset_mode_internal(self, preset_mode, force=False):
|
||||
async def _async_set_preset_mode_internal(
|
||||
self, preset_mode, force=False, overwrite_saved_preset=True
|
||||
):
|
||||
"""Set new preset mode."""
|
||||
_LOGGER.info("%s - Set preset_mode: %s force=%s", self, preset_mode, force)
|
||||
if (
|
||||
@@ -1215,10 +1247,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
# I don't think we need to call async_write_ha_state if we didn't change the state
|
||||
return
|
||||
|
||||
# In security mode don't change preset but memorise the new expected preset when security will be off
|
||||
# In safety mode don't change preset but memorise the new expected preset when security will be off
|
||||
if preset_mode != PRESET_SECURITY and self._security_state:
|
||||
_LOGGER.debug(
|
||||
"%s - is in security mode. Just memorise the new expected ", self
|
||||
"%s - is in safety mode. Just memorise the new expected ", self
|
||||
)
|
||||
if preset_mode not in HIDDEN_PRESETS:
|
||||
self._saved_preset_mode = preset_mode
|
||||
@@ -1242,7 +1274,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
|
||||
self.reset_last_temperature_time(old_preset_mode)
|
||||
|
||||
self.save_preset_mode()
|
||||
if overwrite_saved_preset:
|
||||
self.save_preset_mode()
|
||||
self.recalculate()
|
||||
self.send_event(EventType.PRESET_EVENT, {"preset": self._attr_preset_mode})
|
||||
|
||||
@@ -1442,16 +1475,19 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
else:
|
||||
if not self._window_state:
|
||||
_LOGGER.info(
|
||||
"%s - Window is closed. Restoring hvac_mode '%s'",
|
||||
"%s - Window is closed. Restoring hvac_mode '%s' if central_mode is not STOPPED",
|
||||
self,
|
||||
self._saved_hvac_mode,
|
||||
)
|
||||
await self.restore_hvac_mode(True)
|
||||
if self.last_central_mode != CENTRAL_MODE_STOPPED:
|
||||
await self.restore_hvac_mode(True)
|
||||
elif self._window_state:
|
||||
_LOGGER.info(
|
||||
"%s - Window is open. Set hvac_mode to '%s'", self, HVACMode.OFF
|
||||
)
|
||||
self.save_hvac_mode()
|
||||
if self.last_central_mode in [CENTRAL_MODE_AUTO, None]:
|
||||
self.save_hvac_mode()
|
||||
|
||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||
self.update_custom_attributes()
|
||||
|
||||
@@ -1604,7 +1640,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
state.last_changed.astimezone(self._current_tz),
|
||||
)
|
||||
|
||||
# try to restart if we were in security mode
|
||||
# try to restart if we were in safety mode
|
||||
if self._security_state:
|
||||
await self.check_security()
|
||||
|
||||
@@ -1631,7 +1667,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
state.last_changed.astimezone(self._current_tz),
|
||||
)
|
||||
|
||||
# try to restart if we were in security mode
|
||||
# try to restart if we were in safety mode
|
||||
if self._security_state:
|
||||
await self.check_security()
|
||||
except ValueError as ex:
|
||||
@@ -1998,6 +2034,67 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
|
||||
return self._overpowering_state
|
||||
|
||||
async def check_central_mode(self, new_central_mode, old_central_mode) -> None:
|
||||
"""Take into account a central mode change"""
|
||||
if not self.is_controlled_by_central_mode:
|
||||
self._last_central_mode = None
|
||||
return
|
||||
|
||||
_LOGGER.info(
|
||||
"%s - Central mode have change from %s to %s",
|
||||
self,
|
||||
old_central_mode,
|
||||
new_central_mode,
|
||||
)
|
||||
|
||||
self._last_central_mode = new_central_mode
|
||||
|
||||
def save_all():
|
||||
"""save preset and hvac_mode"""
|
||||
self.save_preset_mode()
|
||||
self.save_hvac_mode()
|
||||
|
||||
if new_central_mode == CENTRAL_MODE_AUTO:
|
||||
if self.window_state is not STATE_ON:
|
||||
await self.restore_hvac_mode()
|
||||
await self.restore_preset_mode()
|
||||
|
||||
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:
|
||||
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:
|
||||
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)
|
||||
else:
|
||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||
return
|
||||
|
||||
if new_central_mode == CENTRAL_MODE_FROST_PROTECTION:
|
||||
if (
|
||||
PRESET_FROST_PROTECTION in self.preset_modes
|
||||
and HVACMode.HEAT in self.hvac_modes
|
||||
):
|
||||
await self.async_set_hvac_mode(HVACMode.HEAT)
|
||||
await self.async_set_preset_mode(
|
||||
PRESET_FROST_PROTECTION, overwrite_saved_preset=False
|
||||
)
|
||||
else:
|
||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||
return
|
||||
|
||||
def _set_now(self, now: datetime):
|
||||
"""Set the now timestamp. This is only for tests purpose"""
|
||||
self._now = now
|
||||
@@ -2064,7 +2161,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
if shouldStartSecurity:
|
||||
if shouldClimateBeInSecurity:
|
||||
_LOGGER.warning(
|
||||
"%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and underlying climate is %s. Set it into security mode",
|
||||
"%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and underlying climate is %s. Setting it into safety mode",
|
||||
self,
|
||||
self._security_delay_min,
|
||||
delta_temp,
|
||||
@@ -2073,13 +2170,13 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
)
|
||||
elif shouldSwitchBeInSecurity:
|
||||
_LOGGER.warning(
|
||||
"%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",
|
||||
"%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 safety mode",
|
||||
self,
|
||||
self._security_delay_min,
|
||||
delta_temp,
|
||||
delta_ext_temp,
|
||||
self._prop_algorithm.on_percent,
|
||||
self._security_min_on_percent,
|
||||
self._prop_algorithm.on_percent * 100,
|
||||
self._security_min_on_percent * 100,
|
||||
)
|
||||
|
||||
self.send_event(
|
||||
@@ -2097,7 +2194,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
},
|
||||
)
|
||||
|
||||
# Start security mode
|
||||
# Start safety mode
|
||||
if shouldStartSecurity:
|
||||
self._security_state = True
|
||||
self.save_hvac_mode()
|
||||
@@ -2125,10 +2222,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
},
|
||||
)
|
||||
|
||||
# Stop security mode
|
||||
# Stop safety mode
|
||||
if shouldStopSecurity:
|
||||
_LOGGER.warning(
|
||||
"%s - End of security mode. restoring hvac_mode to %s and preset_mode to %s",
|
||||
"%s - End of safety mode. restoring hvac_mode to %s and preset_mode to %s",
|
||||
self,
|
||||
self._saved_hvac_mode,
|
||||
self._saved_preset_mode,
|
||||
@@ -2239,6 +2336,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
"hvac_mode": self.hvac_mode,
|
||||
"preset_mode": self.preset_mode,
|
||||
"type": self._thermostat_type,
|
||||
"is_controlled_by_central_mode": self.is_controlled_by_central_mode,
|
||||
"last_central_mode": self.last_central_mode,
|
||||
"frost_temp": self._presets[PRESET_FROST_PROTECTION],
|
||||
"eco_temp": self._presets[PRESET_ECO],
|
||||
"boost_temp": self._presets[PRESET_BOOST],
|
||||
@@ -2374,11 +2473,11 @@ class BaseThermostat(ClimateEntity, RestoreEntity):
|
||||
entity_id: climate.thermostat_2
|
||||
"""
|
||||
_LOGGER.info(
|
||||
"%s - Calling service_set_security, delay_min: %s, min_on_percent: %s, default_on_percent: %s",
|
||||
"%s - Calling service_set_security, delay_min: %s, min_on_percent: %s %%, default_on_percent: %s %%",
|
||||
self,
|
||||
delay_min,
|
||||
min_on_percent,
|
||||
default_on_percent,
|
||||
min_on_percent*100,
|
||||
default_on_percent*100,
|
||||
)
|
||||
if delay_min:
|
||||
self._security_delay_min = delay_min
|
||||
|
||||
@@ -42,6 +42,7 @@ STEP_MAIN_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
|
||||
),
|
||||
vol.Required(CONF_CYCLE_MIN, default=5): cv.positive_int,
|
||||
vol.Optional(CONF_DEVICE_POWER, default="1"): vol.Coerce(float),
|
||||
vol.Optional(CONF_USE_CENTRAL_MODE, default=True): cv.boolean,
|
||||
vol.Optional(CONF_USE_WINDOW_FEATURE, default=False): cv.boolean,
|
||||
vol.Optional(CONF_USE_MOTION_FEATURE, default=False): cv.boolean,
|
||||
vol.Optional(CONF_USE_POWER_FEATURE, default=False): cv.boolean,
|
||||
|
||||
@@ -35,7 +35,12 @@ HIDDEN_PRESETS = [PRESET_POWER, PRESET_SECURITY]
|
||||
|
||||
DOMAIN = "versatile_thermostat"
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.CLIMATE, Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||
PLATFORMS: list[Platform] = [
|
||||
Platform.CLIMATE,
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.SENSOR,
|
||||
Platform.SELECT,
|
||||
]
|
||||
|
||||
CONF_HEATER = "heater_entity_id"
|
||||
CONF_HEATER_2 = "heater_entity2_id"
|
||||
@@ -113,6 +118,8 @@ CONF_USE_PRESENCE_CENTRAL_CONFIG = "use_presence_central_config"
|
||||
CONF_USE_PRESETS_CENTRAL_CONFIG = "use_presets_central_config"
|
||||
CONF_USE_ADVANCED_CENTRAL_CONFIG = "use_advanced_central_config"
|
||||
|
||||
CONF_USE_CENTRAL_MODE = "use_central_mode"
|
||||
|
||||
DEFAULT_SHORT_EMA_PARAMS = {
|
||||
"max_alpha": 0.5,
|
||||
# In sec
|
||||
@@ -242,6 +249,7 @@ ALL_CONF = (
|
||||
CONF_USE_POWER_CENTRAL_CONFIG,
|
||||
CONF_USE_PRESENCE_CENTRAL_CONFIG,
|
||||
CONF_USE_ADVANCED_CENTRAL_CONFIG,
|
||||
CONF_USE_CENTRAL_MODE,
|
||||
]
|
||||
+ CONF_PRESETS_VALUES
|
||||
+ CONF_PRESETS_AWAY_VALUES
|
||||
@@ -297,6 +305,19 @@ AUTO_FAN_DEACTIVATED_MODES = ["mute", "auto", "low"]
|
||||
|
||||
CENTRAL_CONFIG_NAME = "Central configuration"
|
||||
|
||||
CENTRAL_MODE_AUTO = "Auto"
|
||||
CENTRAL_MODE_STOPPED = "Stopped"
|
||||
CENTRAL_MODE_HEAT_ONLY = "Heat only"
|
||||
CENTRAL_MODE_COOL_ONLY = "Cool only"
|
||||
CENTRAL_MODE_FROST_PROTECTION = "Frost protection"
|
||||
CENTRAL_MODES = [
|
||||
CENTRAL_MODE_AUTO,
|
||||
CENTRAL_MODE_STOPPED,
|
||||
CENTRAL_MODE_HEAT_ONLY,
|
||||
CENTRAL_MODE_COOL_ONLY,
|
||||
CENTRAL_MODE_FROST_PROTECTION,
|
||||
]
|
||||
|
||||
|
||||
# A special regulation parameter suggested by @Maia here: https://github.com/jmcollin78/versatile_thermostat/discussions/154
|
||||
class RegulationParamSlow:
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
"quality_scale": "silver",
|
||||
"requirements": [],
|
||||
"ssdp": [],
|
||||
"version": "4.3.0",
|
||||
"version": "5.2.0",
|
||||
"zeroconf": []
|
||||
}
|
||||
@@ -140,27 +140,27 @@ class PropAlgorithm:
|
||||
self._off_time_sec = self._cycle_min * 60 - self._on_time_sec
|
||||
|
||||
def set_security(self, default_on_percent: float):
|
||||
"""Set a default value for on_percent (used for security mode)"""
|
||||
"""Set a default value for on_percent (used for safety mode)"""
|
||||
self._security = True
|
||||
self._default_on_percent = default_on_percent
|
||||
self._calculate_internal()
|
||||
|
||||
def unset_security(self):
|
||||
"""Unset the security mode"""
|
||||
"""Unset the safety mode"""
|
||||
self._security = False
|
||||
self._calculate_internal()
|
||||
|
||||
@property
|
||||
def on_percent(self) -> float:
|
||||
"""Returns the percentage the heater must be ON
|
||||
In security mode this value is overriden with the _default_on_percent
|
||||
In safety 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)
|
||||
|
||||
@property
|
||||
def calculated_on_percent(self) -> float:
|
||||
"""Returns the calculated percentage the heater must be ON
|
||||
Calculated means NOT overriden even in security mode
|
||||
Calculated means NOT overriden even in safety mode
|
||||
(1 means the heater will be always on, 0 never on)""" # pylint: disable=line-too-long
|
||||
return round(self._calculated_on_percent, 2)
|
||||
|
||||
|
||||
134
custom_components/versatile_thermostat/select.py
Normal file
134
custom_components/versatile_thermostat/select.py
Normal file
@@ -0,0 +1,134 @@
|
||||
# pylint: disable=unused-argument
|
||||
|
||||
""" Implements the VersatileThermostat select component """
|
||||
import logging
|
||||
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_START
|
||||
from homeassistant.core import HomeAssistant, CoreState, callback
|
||||
|
||||
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
||||
|
||||
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
DEVICE_MANUFACTURER,
|
||||
CONF_NAME,
|
||||
CONF_THERMOSTAT_TYPE,
|
||||
CONF_THERMOSTAT_CENTRAL_CONFIG,
|
||||
CENTRAL_MODE_AUTO,
|
||||
CENTRAL_MODES,
|
||||
overrides,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the VersatileThermostat selects with config flow."""
|
||||
_LOGGER.debug(
|
||||
"Calling async_setup_entry entry=%s, data=%s", entry.entry_id, entry.data
|
||||
)
|
||||
|
||||
unique_id = entry.entry_id
|
||||
name = entry.data.get(CONF_NAME)
|
||||
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
|
||||
|
||||
if vt_type != CONF_THERMOSTAT_CENTRAL_CONFIG:
|
||||
return
|
||||
|
||||
entities = [
|
||||
CentralModeSelect(hass, unique_id, name, entry.data),
|
||||
]
|
||||
|
||||
async_add_entities(entities, True)
|
||||
|
||||
|
||||
class CentralModeSelect(SelectEntity, RestoreEntity):
|
||||
"""Representation of a Energy sensor which exposes the energy"""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
|
||||
"""Initialize the energy sensor"""
|
||||
self._config_id = unique_id
|
||||
self._device_name = entry_infos.get(CONF_NAME)
|
||||
self._attr_name = "Central Mode"
|
||||
self._attr_unique_id = "central_mode"
|
||||
self._attr_options = CENTRAL_MODES
|
||||
self._attr_current_option = CENTRAL_MODE_AUTO
|
||||
|
||||
@property
|
||||
def icon(self) -> str | None:
|
||||
return "mdi:form-select"
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return the device info."""
|
||||
return DeviceInfo(
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, self._config_id)},
|
||||
name=self._device_name,
|
||||
manufacturer=DEVICE_MANUFACTURER,
|
||||
model=DOMAIN,
|
||||
)
|
||||
|
||||
@overrides
|
||||
async def async_added_to_hass(self) -> None:
|
||||
await super().async_added_to_hass()
|
||||
|
||||
old_state = await self.async_get_last_state()
|
||||
_LOGGER.debug(
|
||||
"%s - Calling async_added_to_hass old_state is %s", self, old_state
|
||||
)
|
||||
if old_state is not None:
|
||||
self._attr_current_option = old_state.state
|
||||
|
||||
@callback
|
||||
async def _async_startup_internal(*_):
|
||||
_LOGGER.debug("%s - Calling async_startup_internal", self)
|
||||
await self.notify_central_mode_change()
|
||||
|
||||
if self.hass.state == CoreState.running:
|
||||
await _async_startup_internal()
|
||||
else:
|
||||
self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_START, _async_startup_internal
|
||||
)
|
||||
|
||||
@overrides
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
old_option = self._attr_current_option
|
||||
|
||||
if option == old_option:
|
||||
return
|
||||
|
||||
if option in CENTRAL_MODES:
|
||||
self._attr_current_option = option
|
||||
await self.notify_central_mode_change(old_central_mode=old_option)
|
||||
|
||||
async def notify_central_mode_change(self, old_central_mode=None):
|
||||
"""Notify all VTherm that the central_mode have change"""
|
||||
# Update all VTherm states
|
||||
component: EntityComponent[ClimateEntity] = self.hass.data[CLIMATE_DOMAIN]
|
||||
for entity in component.entities:
|
||||
if isinstance(entity, BaseThermostat):
|
||||
_LOGGER.debug(
|
||||
"Changing the central_mode. We have find %s to update",
|
||||
entity.name,
|
||||
)
|
||||
await entity.check_central_mode(
|
||||
self._attr_current_option, old_central_mode
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"VersatileThermostat-{self.name}"
|
||||
@@ -24,6 +24,7 @@
|
||||
"temp_min": "Minimal temperature allowed",
|
||||
"temp_max": "Maximal temperature allowed",
|
||||
"device_power": "Device power",
|
||||
"use_central_mode": "Enable the control by central mode ('central_mode')",
|
||||
"use_window_feature": "Use window detection",
|
||||
"use_motion_feature": "Use motion detection",
|
||||
"use_power_feature": "Use power management",
|
||||
@@ -31,6 +32,7 @@
|
||||
"use_main_central_config": "Use central main configuration"
|
||||
},
|
||||
"data_description": {
|
||||
"use_central_mode": "Check to enable the control of the VTherm with the select central_mode entities",
|
||||
"use_main_central_config": "Check to use the central main configuration. Uncheck to use a specific main configuration for this VTherm",
|
||||
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
|
||||
}
|
||||
@@ -254,6 +256,7 @@
|
||||
"temp_min": "Minimal temperature allowed",
|
||||
"temp_max": "Maximal temperature allowed",
|
||||
"device_power": "Device power",
|
||||
"use_central_mode": "Enable the control by central mode ('central_mode')",
|
||||
"use_window_feature": "Use window detection",
|
||||
"use_motion_feature": "Use motion detection",
|
||||
"use_power_feature": "Use power management",
|
||||
@@ -261,6 +264,7 @@
|
||||
"use_main_central_config": "Use central main configuration"
|
||||
},
|
||||
"data_description": {
|
||||
"use_central_mode": "Check to enable the control of the VTherm with the select central_mode entities",
|
||||
"use_main_central_config": "Check to use the central main configuration. Uncheck to use a specific configuration for this VTherm",
|
||||
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"temp_min": "Minimal temperature allowed",
|
||||
"temp_max": "Maximal temperature allowed",
|
||||
"device_power": "Device power",
|
||||
"use_central_mode": "Enable the control by central mode ('central_mode')",
|
||||
"use_window_feature": "Use window detection",
|
||||
"use_motion_feature": "Use motion detection",
|
||||
"use_power_feature": "Use power management",
|
||||
@@ -31,6 +32,7 @@
|
||||
"use_main_central_config": "Use central main configuration"
|
||||
},
|
||||
"data_description": {
|
||||
"use_central_mode": "Check to enable the control of the VTherm with the select central_mode entities",
|
||||
"use_main_central_config": "Check to use the central main configuration. Uncheck to use a specific main configuration for this VTherm",
|
||||
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
|
||||
}
|
||||
@@ -254,6 +256,7 @@
|
||||
"temp_min": "Minimal temperature allowed",
|
||||
"temp_max": "Maximal temperature allowed",
|
||||
"device_power": "Device power",
|
||||
"use_central_mode": "Enable the control by central mode ('central_mode')",
|
||||
"use_window_feature": "Use window detection",
|
||||
"use_motion_feature": "Use motion detection",
|
||||
"use_power_feature": "Use power management",
|
||||
@@ -261,6 +264,7 @@
|
||||
"use_main_central_config": "Use central main configuration"
|
||||
},
|
||||
"data_description": {
|
||||
"use_central_mode": "Check to enable the control of the VTherm with the select central_mode entities",
|
||||
"use_main_central_config": "Check to use the central main configuration. Uncheck to use a specific configuration for this VTherm",
|
||||
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"temp_min": "Température minimale permise",
|
||||
"temp_max": "Température maximale permise",
|
||||
"device_power": "Puissance de l'équipement",
|
||||
"use_central_mode": "Autoriser le controle par le mode central ('central_mode`)",
|
||||
"use_window_feature": "Avec détection des ouvertures",
|
||||
"use_motion_feature": "Avec détection de mouvement",
|
||||
"use_power_feature": "Avec gestion de la puissance",
|
||||
@@ -31,6 +32,7 @@
|
||||
"use_main_central_config": "Utiliser la configuration centrale principale"
|
||||
},
|
||||
"data_description": {
|
||||
"use_central_mode": "Cochez pour autoriser le contrôle du VTherm par la liste déroulante 'central_mode' de l'entité configuration centrale",
|
||||
"external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure. N'est pas utilisé si la configuration centrale est utilisée",
|
||||
"use_main_central_config": "Cochez pour utiliser la configuration centrale principale. Décochez et saisissez les attributs pour utiliser une configuration spécifique principale"
|
||||
}
|
||||
@@ -254,6 +256,7 @@
|
||||
"temp_min": "Température minimale permise",
|
||||
"temp_max": "Température maximale permise",
|
||||
"device_power": "Puissance de l'équipement",
|
||||
"use_central_mode": "Autoriser le controle par le mode central ('central_mode`)",
|
||||
"use_window_feature": "Avec détection des ouvertures",
|
||||
"use_motion_feature": "Avec détection de mouvement",
|
||||
"use_power_feature": "Avec gestion de la puissance",
|
||||
@@ -261,6 +264,7 @@
|
||||
"use_main_central_config": "Utiliser la configuration centrale"
|
||||
},
|
||||
"data_description": {
|
||||
"use_central_mode": "Cochez pour autoriser le contrôle du VTherm par la liste déroulante 'central_mode' de l'entité configuration centrale",
|
||||
"use_main_central_config": "Cochez pour utiliser la configuration centrale. Décochez et saisissez les attributs pour utiliser une configuration spécifique",
|
||||
"external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure. N'est pas utilisé si la configuration centrale est utilisée"
|
||||
}
|
||||
|
||||
@@ -141,8 +141,9 @@ class UnderlyingEntity:
|
||||
await self.set_hvac_mode(hvac_mode)
|
||||
elif hvac_mode != HVACMode.OFF and not self.is_device_active:
|
||||
_LOGGER.warning(
|
||||
"%s - The hvac mode is ON, but the underlying device is not ON. Turning on device %s",
|
||||
"%s - The hvac mode is %s, but the underlying device is not ON. Turning on device %s if needed",
|
||||
self,
|
||||
hvac_mode,
|
||||
self._entity_id,
|
||||
)
|
||||
await self.set_hvac_mode(hvac_mode)
|
||||
@@ -354,7 +355,7 @@ class UnderlyingSwitch(UnderlyingEntity):
|
||||
if await self._thermostat.check_overpowering():
|
||||
_LOGGER.debug("%s - End of cycle (3)", self)
|
||||
return
|
||||
# Security mode could have change the on_time percent
|
||||
# safety mode could have change the on_time percent
|
||||
await self._thermostat.check_security()
|
||||
time = self._on_time_sec
|
||||
|
||||
@@ -790,6 +791,7 @@ class UnderlyingValve(UnderlyingEntity):
|
||||
):
|
||||
"""We use this function to change the on_percent"""
|
||||
if force:
|
||||
self._percent_open = self.cap_sent_value(self._percent_open)
|
||||
await self.send_percent_open()
|
||||
|
||||
@overrides
|
||||
|
||||
@@ -20,7 +20,6 @@ class VersatileThermostatAPI(dict):
|
||||
"""The VersatileThermostatAPI"""
|
||||
|
||||
_hass: HomeAssistant = None
|
||||
# _entries: Dict(str, ConfigEntry)
|
||||
|
||||
@classmethod
|
||||
def get_vtherm_api(cls, hass=None):
|
||||
@@ -64,14 +63,12 @@ class VersatileThermostatAPI(dict):
|
||||
def add_entry(self, entry: ConfigEntry):
|
||||
"""Add a new entry"""
|
||||
_LOGGER.debug("Add the entry %s", entry.entry_id)
|
||||
# self._entries[entry.entry_id] = entry
|
||||
# Add the entry in hass.data
|
||||
VersatileThermostatAPI._hass.data[DOMAIN][entry.entry_id] = entry
|
||||
|
||||
def remove_entry(self, entry: ConfigEntry):
|
||||
"""Remove an entry"""
|
||||
_LOGGER.debug("Remove the entry %s", entry.entry_id)
|
||||
# self._entries.pop(entry.entry_id)
|
||||
VersatileThermostatAPI._hass.data[DOMAIN].pop(entry.entry_id)
|
||||
# If not more entries are preset, remove the API
|
||||
if len(self) == 0:
|
||||
|
||||
BIN
images/central_mode.png
Normal file
BIN
images/central_mode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 45 KiB |
BIN
images/config-main0.png
Normal file
BIN
images/config-main0.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
images/plotly-curves.png
Normal file
BIN
images/plotly-curves.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
7
pyrightconfig.json
Normal file
7
pyrightconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"include": [
|
||||
"custom_components/versatile_thermostat/**",
|
||||
"homeassistant/**"
|
||||
],
|
||||
"reportShadowedImports": false
|
||||
}
|
||||
@@ -185,6 +185,7 @@ class MockClimate(ClimateEntity):
|
||||
hvac_mode: HVACMode = HVACMode.OFF,
|
||||
hvac_action: HVACAction = HVACAction.OFF,
|
||||
fan_modes: list[str] = None,
|
||||
hvac_modes: list[str] = None,
|
||||
) -> None:
|
||||
"""Initialize the thermostat."""
|
||||
|
||||
@@ -200,7 +201,11 @@ class MockClimate(ClimateEntity):
|
||||
HVACAction.OFF if hvac_mode == HVACMode.OFF else HVACAction.HEATING
|
||||
)
|
||||
self._attr_hvac_mode = hvac_mode
|
||||
self._attr_hvac_modes = [HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT]
|
||||
self._attr_hvac_modes = (
|
||||
hvac_modes
|
||||
if hvac_modes is not None
|
||||
else [HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT]
|
||||
)
|
||||
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
self._attr_target_temperature = 20
|
||||
self._attr_current_temperature = 15
|
||||
|
||||
@@ -50,7 +50,8 @@ MOCK_TH_OVER_CLIMATE_MAIN_CONFIG = {
|
||||
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||
CONF_CYCLE_MIN: 5,
|
||||
CONF_DEVICE_POWER: 1,
|
||||
CONF_USE_MAIN_CENTRAL_CONFIG: False
|
||||
CONF_USE_MAIN_CENTRAL_CONFIG: False,
|
||||
CONF_USE_CENTRAL_MODE: True
|
||||
# Keep default values which are False
|
||||
}
|
||||
|
||||
|
||||
@@ -358,7 +358,7 @@ async def test_bug_82(
|
||||
skip_turn_on_off_heater,
|
||||
skip_send_event,
|
||||
):
|
||||
"""Test that when a underlying climate is not available the VTherm doesn't go into security mode"""
|
||||
"""Test that when a underlying climate is not available the VTherm doesn't go into safety mode"""
|
||||
|
||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||
now: datetime = datetime.now(tz=tz)
|
||||
@@ -427,7 +427,7 @@ async def test_bug_82(
|
||||
assert mock_find_climate.mock_calls[0] == call()
|
||||
mock_find_climate.assert_has_calls([call.find_underlying_entity()])
|
||||
|
||||
# Force security mode
|
||||
# Force safety mode
|
||||
assert entity._last_ext_temperature_measure is not None
|
||||
assert entity._last_temperature_measure is not None
|
||||
assert (
|
||||
|
||||
1040
tests/test_central_mode.py
Normal file
1040
tests/test_central_mode.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -363,6 +363,7 @@ async def test_user_config_flow_window_auto_ok(
|
||||
CONF_USE_POWER_FEATURE: False,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_WINDOW_DELAY: 30, # the default value is added
|
||||
CONF_USE_CENTRAL_MODE: True, # True is the defaulf value
|
||||
} | MOCK_TH_OVER_SWITCH_TYPE_CONFIG | MOCK_TH_OVER_SWITCH_TPI_CONFIG | MOCK_WINDOW_AUTO_CONFIG | {
|
||||
CONF_USE_MAIN_CENTRAL_CONFIG: True,
|
||||
CONF_USE_TPI_CENTRAL_CONFIG: False,
|
||||
@@ -510,6 +511,7 @@ async def test_user_config_flow_over_4_switches(
|
||||
CONF_USE_POWER_FEATURE: False,
|
||||
CONF_USE_PRESENCE_FEATURE: False,
|
||||
CONF_USE_MAIN_CENTRAL_CONFIG: True,
|
||||
CONF_USE_CENTRAL_MODE: False,
|
||||
}
|
||||
|
||||
TYPE_CONFIG = { # pylint: disable=wildcard-import, invalid-name
|
||||
|
||||
@@ -288,7 +288,7 @@ async def test_security_feature_back_on_percent(
|
||||
assert entity.security_state is False
|
||||
assert mock_send_event.call_count == 0
|
||||
|
||||
# 3. Set security mode with a preset change
|
||||
# 3. Set safety mode with a preset change
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
@@ -400,7 +400,7 @@ async def test_security_over_climate(
|
||||
skip_turn_on_off_heater,
|
||||
skip_send_event,
|
||||
):
|
||||
"""Test that when a underlying climate is not available the VTherm doesn't go into security mode"""
|
||||
"""Test that when a underlying climate is not available the VTherm doesn't go into safety mode"""
|
||||
|
||||
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||
now: datetime = datetime.now(tz=tz)
|
||||
@@ -471,7 +471,7 @@ async def test_security_over_climate(
|
||||
assert mock_find_climate.mock_calls[0] == call()
|
||||
mock_find_climate.assert_has_calls([call.find_underlying_entity()])
|
||||
|
||||
# Force security mode
|
||||
# Force safety mode
|
||||
assert entity._last_ext_temperature_measure is not None
|
||||
assert entity._last_temperature_measure is not None
|
||||
assert (
|
||||
@@ -505,7 +505,7 @@ async def test_security_over_climate(
|
||||
event_timestamp = now - timedelta(minutes=6)
|
||||
|
||||
await send_temperature_change_event(entity, 15, event_timestamp)
|
||||
# Should stay False because a climate is never in security mode
|
||||
# Should stay False because a climate is never in safety mode
|
||||
assert entity.security_state is False
|
||||
assert entity.preset_mode == "none"
|
||||
assert entity._saved_preset_mode == "none"
|
||||
|
||||
@@ -129,7 +129,7 @@ async def test_over_switch_ac_full_start(
|
||||
|
||||
assert entity.hvac_mode is HVACMode.OFF
|
||||
assert entity.hvac_action is HVACAction.OFF
|
||||
assert entity.target_temperature == 16 # eco_ac_away
|
||||
assert entity.target_temperature == 27 # eco_ac_away (no change)
|
||||
|
||||
# Close a window
|
||||
with patch("homeassistant.helpers.condition.state", return_value=True):
|
||||
|
||||
@@ -283,6 +283,18 @@ async def test_over_valve_full_start(
|
||||
assert entity.is_device_active is True
|
||||
assert entity.hvac_action == HVACAction.HEATING
|
||||
|
||||
# Test window open/close (with a normal min/max so that is_device_active is False when open_percent is 0)
|
||||
expected_state = State(
|
||||
entity_id="number.mock_valve", state="0", attributes={"min": 0, "max": 99}
|
||||
)
|
||||
|
||||
with patch(
|
||||
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||
) as mock_send_event, patch(
|
||||
"homeassistant.core.ServiceRegistry.async_call"
|
||||
) as mock_service_call, patch(
|
||||
"homeassistant.core.StateMachine.get", return_value=expected_state
|
||||
):
|
||||
# Open a window
|
||||
with patch("homeassistant.helpers.condition.state", return_value=True):
|
||||
event_timestamp = now - timedelta(minutes=1)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# pylint: disable=unused-argument, line-too-long, protected-access
|
||||
# pylint: disable=unused-argument, line-too-long, protected-access, too-many-lines
|
||||
""" Test the Window management """
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
Reference in New Issue
Block a user