Compare commits
33 Commits
6.4.1
...
6.8.0.beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9102b09691 | ||
|
|
5b64a8fba0 | ||
|
|
14bb7474ae | ||
|
|
bc1c18d719 | ||
|
|
c935ee3af5 | ||
|
|
ce73f1275b | ||
|
|
cd08dca913 | ||
|
|
93079e974f | ||
|
|
db38392dab | ||
|
|
ac705df862 | ||
|
|
289ccc7bb7 | ||
|
|
c1d1e8f1db | ||
|
|
71c35ecdc0 | ||
|
|
4f8e45dda6 | ||
|
|
d624c327b6 | ||
|
|
b46a24f834 | ||
|
|
d31376d55d | ||
|
|
dbfd294ff3 | ||
|
|
e111bd0647 | ||
|
|
ba69319198 | ||
|
|
f9df925181 | ||
|
|
2d72efe447 | ||
|
|
95af6eba97 | ||
|
|
06dc537767 | ||
|
|
2d79d961dc | ||
|
|
027bf8386b | ||
|
|
a0e548ef71 | ||
|
|
132519b471 | ||
|
|
e6c330fc9d | ||
|
|
968e8286ea | ||
|
|
0f60c070ab | ||
|
|
810430f7b1 | ||
|
|
b4860c2b8d |
@@ -91,6 +91,48 @@ input_number:
|
|||||||
icon: mdi:thermostat
|
icon: mdi:thermostat
|
||||||
unit_of_measurement: °C
|
unit_of_measurement: °C
|
||||||
mode: box
|
mode: box
|
||||||
|
fake_offset_calibration1:
|
||||||
|
name: Sonoff offset calibration 1
|
||||||
|
min: -12
|
||||||
|
max: 12
|
||||||
|
icon: mdi:tune
|
||||||
|
unit_of_measurement: °C
|
||||||
|
mode: box
|
||||||
|
fake_opening_degree1:
|
||||||
|
name: Sonoff Opening degree 1
|
||||||
|
min: 0
|
||||||
|
max: 100
|
||||||
|
icon: mdi:valve-open
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
mode: box
|
||||||
|
fake_closing_degree1:
|
||||||
|
name: Sonoff Closing degree 1
|
||||||
|
min: 0
|
||||||
|
max: 100
|
||||||
|
icon: mdi:valve-closed
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
mode: box
|
||||||
|
fake_offset_calibration2:
|
||||||
|
name: Sonoff offset calibration 2
|
||||||
|
min: -12
|
||||||
|
max: 12
|
||||||
|
icon: mdi:tune
|
||||||
|
unit_of_measurement: °C
|
||||||
|
mode: box
|
||||||
|
fake_opening_degree2:
|
||||||
|
name: Sonoff Opening degree 2
|
||||||
|
min: 0
|
||||||
|
max: 100
|
||||||
|
icon: mdi:valve-open
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
mode: box
|
||||||
|
fake_closing_degree2:
|
||||||
|
name: Sonoff Closing degree 2
|
||||||
|
min: 0
|
||||||
|
max: 100
|
||||||
|
icon: mdi:valve-closed
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
mode: box
|
||||||
|
|
||||||
input_boolean:
|
input_boolean:
|
||||||
# input_boolean to simulate the windows entity. Only for development environment.
|
# input_boolean to simulate the windows entity. Only for development environment.
|
||||||
@@ -142,6 +184,12 @@ input_boolean:
|
|||||||
fake_presence_sensor1:
|
fake_presence_sensor1:
|
||||||
name: Presence Sensor 1
|
name: Presence Sensor 1
|
||||||
icon: mdi:home
|
icon: mdi:home
|
||||||
|
fake_valve_sonoff_trvzb1:
|
||||||
|
name: Valve Sonoff TRVZB1
|
||||||
|
icon: mdi:valve
|
||||||
|
fake_valve_sonoff_trvzb2:
|
||||||
|
name: Valve Sonoff TRVZB2
|
||||||
|
icon: mdi:valve
|
||||||
|
|
||||||
climate:
|
climate:
|
||||||
- platform: generic_thermostat
|
- platform: generic_thermostat
|
||||||
@@ -152,6 +200,7 @@ climate:
|
|||||||
name: Underlying thermostat2
|
name: Underlying thermostat2
|
||||||
heater: input_boolean.fake_heater_switch3
|
heater: input_boolean.fake_heater_switch3
|
||||||
target_sensor: input_number.fake_temperature_sensor1
|
target_sensor: input_number.fake_temperature_sensor1
|
||||||
|
ac_mode: false
|
||||||
- platform: generic_thermostat
|
- platform: generic_thermostat
|
||||||
name: Underlying thermostat3
|
name: Underlying thermostat3
|
||||||
heater: input_boolean.fake_heater_switch3
|
heater: input_boolean.fake_heater_switch3
|
||||||
@@ -184,6 +233,16 @@ climate:
|
|||||||
name: Underlying thermostat9
|
name: Underlying thermostat9
|
||||||
heater: input_boolean.fake_heater_switch3
|
heater: input_boolean.fake_heater_switch3
|
||||||
target_sensor: input_number.fake_temperature_sensor1
|
target_sensor: input_number.fake_temperature_sensor1
|
||||||
|
- platform: generic_thermostat
|
||||||
|
name: Underlying Sonoff TRVZB1
|
||||||
|
heater: input_boolean.fake_valve_sonoff_trvzb1
|
||||||
|
target_sensor: input_number.fake_temperature_sensor1
|
||||||
|
ac_mode: false
|
||||||
|
- platform: generic_thermostat
|
||||||
|
name: Underlying Sonoff TRVZB2
|
||||||
|
heater: input_boolean.fake_valve_sonoff_trvzb2
|
||||||
|
target_sensor: input_number.fake_temperature_sensor1
|
||||||
|
ac_mode: false
|
||||||
|
|
||||||
input_datetime:
|
input_datetime:
|
||||||
fake_last_seen:
|
fake_last_seen:
|
||||||
@@ -237,14 +296,14 @@ switch:
|
|||||||
friendly_name: "Pilote chauffage SDB RDC"
|
friendly_name: "Pilote chauffage SDB RDC"
|
||||||
value_template: "{{ is_state_attr('switch_seche_serviettes_sdb_rdc', 'sensor_state', 'on') }}"
|
value_template: "{{ is_state_attr('switch_seche_serviettes_sdb_rdc', 'sensor_state', 'on') }}"
|
||||||
turn_on:
|
turn_on:
|
||||||
service: select.select_option
|
action: select.select_option
|
||||||
data:
|
data:
|
||||||
option: comfort
|
option: comfort
|
||||||
target:
|
target:
|
||||||
entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode
|
entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode
|
||||||
|
|
||||||
turn_off:
|
turn_off:
|
||||||
service: select.select_option
|
action: select.select_option
|
||||||
data:
|
data:
|
||||||
option: comfort-2
|
option: comfort-2
|
||||||
target:
|
target:
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/issue.md
vendored
2
.github/ISSUE_TEMPLATE/issue.md
vendored
@@ -4,6 +4,8 @@ about: Create a report to help us improve
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
> Please read carefuly this instructions and fill this form before writing an issue. It helps me to help you.
|
||||||
|
|
||||||
<!-- This template will allow the maintainer to be efficient and post the more accurante response as possible. There is many types / modes / configuration possible, so the analysis can be very tricky. If don't follow this template, your issue could be rejected without any message. Please help me to help you. -->
|
<!-- This template will allow the maintainer to be efficient and post the more accurante response as possible. There is many types / modes / configuration possible, so the analysis can be very tricky. If don't follow this template, your issue could be rejected without any message. Please help me to help you. -->
|
||||||
|
|
||||||
<!-- Before you open a new issue, search through the existing issues to see if others have had the same problem.
|
<!-- Before you open a new issue, search through the existing issues to see if others have had the same problem.
|
||||||
|
|||||||
87
README-fr.md
87
README-fr.md
@@ -13,6 +13,7 @@
|
|||||||
- [Dans le cas d'une configuration centrale](#dans-le-cas-dune-configuration-centrale)
|
- [Dans le cas d'une configuration centrale](#dans-le-cas-dune-configuration-centrale)
|
||||||
- [Refonte du menu de configuration](#refonte-du-menu-de-configuration)
|
- [Refonte du menu de configuration](#refonte-du-menu-de-configuration)
|
||||||
- [Les options de menu 'Configuration incomplète' et 'Finaliser'](#les-options-de-menu-configuration-incomplète-et-finaliser)
|
- [Les options de menu 'Configuration incomplète' et 'Finaliser'](#les-options-de-menu-configuration-incomplète-et-finaliser)
|
||||||
|
- [Changements dans la version 5.0](#changements-dans-la-version-50)
|
||||||
- [Merci pour la bière buymecoffee](#merci-pour-la-bière-buymecoffee)
|
- [Merci pour la bière buymecoffee](#merci-pour-la-bière-buymecoffee)
|
||||||
- [Quand l'utiliser et ne pas l'utiliser](#quand-lutiliser-et-ne-pas-lutiliser)
|
- [Quand l'utiliser et ne pas l'utiliser](#quand-lutiliser-et-ne-pas-lutiliser)
|
||||||
- [Incompatibilités](#incompatibilités)
|
- [Incompatibilités](#incompatibilités)
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
- [Compensation de la température interne](#compensation-de-la-température-interne)
|
- [Compensation de la température interne](#compensation-de-la-température-interne)
|
||||||
- [Synthèse de l'algorithme d'auto-régulation](#synthèse-de-lalgorithme-dauto-régulation)
|
- [Synthèse de l'algorithme d'auto-régulation](#synthèse-de-lalgorithme-dauto-régulation)
|
||||||
- [Le mode auto-fan](#le-mode-auto-fan)
|
- [Le mode auto-fan](#le-mode-auto-fan)
|
||||||
|
- [Le démarrage / arrêt automatique](#le-démarrage--arrêt-automatique)
|
||||||
- [Pour un thermostat de type ```thermostat_over_valve```:](#pour-un-thermostat-de-type-thermostat_over_valve)
|
- [Pour un thermostat de type ```thermostat_over_valve```:](#pour-un-thermostat-de-type-thermostat_over_valve)
|
||||||
- [Configurez les coefficients de l'algorithme TPI](#configurez-les-coefficients-de-lalgorithme-tpi)
|
- [Configurez les coefficients de l'algorithme TPI](#configurez-les-coefficients-de-lalgorithme-tpi)
|
||||||
- [Configurer les températures préréglées](#configurer-les-températures-préréglées)
|
- [Configurer les températures préréglées](#configurer-les-températures-préréglées)
|
||||||
@@ -92,6 +94,9 @@ Ce composant personnalisé pour Home Assistant est une mise à niveau et est une
|
|||||||
|
|
||||||
|
|
||||||
>  _*Historique des dernières versions*_
|
>  _*Historique des dernières versions*_
|
||||||
|
> * **Release 6.5** :
|
||||||
|
> - Ajout d'une nouvelle fonction permettant l'arrêt et la relance automatique d'un VTherm `over_climate` [585](https://github.com/jmcollin78/versatile_thermostat/issues/585)
|
||||||
|
> - Amélioration de la gestion des ouvertures au démarrage. Permet de mémoriser et de recalculer l'état d'une ouverture au redémarage de Home Assistant [504](https://github.com/jmcollin78/versatile_thermostat/issues/504)
|
||||||
> * **Release 6.0** :
|
> * **Release 6.0** :
|
||||||
> - Ajout d'entités du domaine Number permettant de configurer les températures des presets [354](https://github.com/jmcollin78/versatile_thermostat/issues/354)
|
> - Ajout d'entités du domaine Number permettant de configurer les températures des presets [354](https://github.com/jmcollin78/versatile_thermostat/issues/354)
|
||||||
> - Refonte complète du menu de configuration pour supprimer les températures et utililsation d'un menu au lieu d'un tunnel de configuration [354](https://github.com/jmcollin78/versatile_thermostat/issues/354)
|
> - Refonte complète du menu de configuration pour supprimer les températures et utililsation d'un menu au lieu d'un tunnel de configuration [354](https://github.com/jmcollin78/versatile_thermostat/issues/354)
|
||||||
@@ -100,14 +105,14 @@ Ce composant personnalisé pour Home Assistant est une mise à niveau et est une
|
|||||||
> - ajout de seuils de régulation pour les `over_valve` pour éviter de trop vider la batterie des TRV [#338](https://github.com/jmcollin78/versatile_thermostat/issues/338),
|
> - ajout de seuils de régulation pour les `over_valve` pour éviter de trop vider la batterie des TRV [#338](https://github.com/jmcollin78/versatile_thermostat/issues/338),
|
||||||
> - ajout d'une option permettant d'utiliser la température interne d'un TRV pour forcer l' auto-régulation [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348),
|
> - ajout d'une option permettant d'utiliser la température interne d'un TRV pour forcer l' auto-régulation [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348),
|
||||||
> - ajout d'une fonction de keep-alive pour les VTherm `over_switch` [#345](https://github.com/jmcollin78/versatile_thermostat/issues/345)
|
> - ajout d'une fonction de keep-alive pour les VTherm `over_switch` [#345](https://github.com/jmcollin78/versatile_thermostat/issues/345)
|
||||||
> * **Release 5.3** : Ajout d'une fonction de pilotage d'une chaudière centrale [#234](https://github.com/jmcollin78/versatile_thermostat/issues/234) - plus d'infos ici: [Le contrôle d'une chaudière centrale](#le-contrôle-dune-chaudière-centrale). Ajout de la possibilité de désactiver le mode sécurité pour le thermomètre extérieur [#343](https://github.com/jmcollin78/versatile_thermostat/issues/343)
|
|
||||||
> * **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).
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Autres versions</summary>
|
<summary>Autres versions</summary>
|
||||||
|
|
||||||
|
> * **Release 5.3** : Ajout d'une fonction de pilotage d'une chaudière centrale [#234](https://github.com/jmcollin78/versatile_thermostat/issues/234) - plus d'infos ici: [Le contrôle d'une chaudière centrale](#le-contrôle-dune-chaudière-centrale). Ajout de la possibilité de désactiver le mode sécurité pour le thermomètre extérieur [#343](https://github.com/jmcollin78/versatile_thermostat/issues/343)
|
||||||
|
> * **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.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.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.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).
|
||||||
@@ -126,6 +131,8 @@ Ce composant personnalisé pour Home Assistant est une mise à niveau et est une
|
|||||||
> * **release majeure 2.0** : ajout du thermostat "over climate" permettant de transformer n'importe quel thermostat en Versatile Thermostat et lui ajouter toutes les fonctions de ce dernier.
|
> * **release majeure 2.0** : ajout du thermostat "over climate" permettant de transformer n'importe quel thermostat en Versatile Thermostat et lui ajouter toutes les fonctions de ce dernier.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Changements dans la version 6.0</summary>
|
||||||
# Changements dans la version 6.0
|
# Changements dans la version 6.0
|
||||||
|
|
||||||
## Entités de température pour les pre-réglages
|
## Entités de température pour les pre-réglages
|
||||||
@@ -193,10 +200,13 @@ Une fois que toute la configuration est valide, la dernière option se transform
|
|||||||
Cliquez sur cette option pour créér (resp. modifier) le VTherm :
|
Cliquez sur cette option pour créér (resp. modifier) le VTherm :
|
||||||
|
|
||||||

|

|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Changements dans la version 5.0</summary>
|
<summary>Changements dans la version 5.0</summary>
|
||||||
|
|
||||||
|
# Changements dans la version 5.0
|
||||||
|
|
||||||
Vous pouvez maintenant définir une configuration centrale qui va vous permettre de mettre en commun sur tous vos VTherms (ou seulement une partie), certains attributs. Pour utiliser cette possibilité, vous devez :
|
Vous pouvez maintenant définir une configuration centrale qui va vous permettre de mettre en commun sur tous vos VTherms (ou seulement une partie), certains attributs. Pour utiliser cette possibilité, vous devez :
|
||||||
1. Créer un VTherm de type "Configuration Centrale",
|
1. Créer un VTherm de type "Configuration Centrale",
|
||||||
2. Saisir les attributs de cette configuration centrale
|
2. Saisir les attributs de cette configuration centrale
|
||||||
@@ -237,6 +247,7 @@ Certains thermostat de type TRV sont réputés incompatibles avec le Versatile T
|
|||||||
4. les thermostats de type Rointe ont tendance a se réveiller tout seul. Le reste fonctionne normalement.
|
4. les thermostats de type Rointe ont tendance a se réveiller tout seul. Le reste fonctionne normalement.
|
||||||
5. les TRV de type Aqara SRTS-A01 et MOES TV01-ZB qui n'ont pas le retour d'état `hvac_action` permettant de savoir si elle chauffe ou pas. Donc les retours d'état sont faussés, le reste à l'air fonctionnel.
|
5. les TRV de type Aqara SRTS-A01 et MOES TV01-ZB qui n'ont pas le retour d'état `hvac_action` permettant de savoir si elle chauffe ou pas. Donc les retours d'état sont faussés, le reste à l'air fonctionnel.
|
||||||
6. La clim Airwell avec l'intégration "Midea AC LAN". Si 2 commandes de VTherm sont trop rapprochées, la clim s'arrête d'elle même.
|
6. La clim Airwell avec l'intégration "Midea AC LAN". Si 2 commandes de VTherm sont trop rapprochées, la clim s'arrête d'elle même.
|
||||||
|
7. Les climates basés sur l'intégration Overkiz ne fonctionnent pas. Il parait impossible d'éteindre ni même de changer la température sur ces systèmes.
|
||||||
|
|
||||||
# Pourquoi une nouvelle implémentation du thermostat ?
|
# Pourquoi une nouvelle implémentation du thermostat ?
|
||||||
|
|
||||||
@@ -287,9 +298,6 @@ Ce composant nommé __Versatile thermostat__ gère les cas d'utilisation suivant
|
|||||||
> 3. En plus de cette configuration centralisée, tous les VTherm peuvent être contrôlées par une seule entité de type `select`. Cette fonction est nommé `central_mode`. Cela permet de stopper / démarrer / mettre en hors gel / etc tous les VTherms en une seule fois. Pour chaque VTherm, l'utilisateur indique si il est concerné par ce `central_mode`.
|
> 3. En plus de cette configuration centralisée, tous les VTherm peuvent être contrôlées par une seule entité de type `select`. Cette fonction est nommé `central_mode`. Cela permet de stopper / démarrer / mettre en hors gel / etc tous les VTherms en une seule fois. Pour chaque VTherm, l'utilisateur indique si il est concerné par ce `central_mode`.
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Création d'un nouveau Versatile Thermostat</summary>
|
|
||||||
|
|
||||||
## Création d'un nouveau Versatile Thermostat
|
## Création d'un nouveau Versatile Thermostat
|
||||||
|
|
||||||
Cliquez sur le bouton Ajouter une intégration dans la page d'intégration
|
Cliquez sur le bouton Ajouter une intégration dans la page d'intégration
|
||||||
@@ -303,10 +311,6 @@ puis
|
|||||||
La configuration peut être modifiée via la même interface. Sélectionnez simplement le thermostat à modifier, appuyez sur "Configurer" et vous pourrez modifier certains paramètres ou la configuration.
|
La configuration peut être modifiée via la même interface. Sélectionnez simplement le thermostat à modifier, appuyez sur "Configurer" et vous pourrez modifier certains paramètres ou la configuration.
|
||||||
|
|
||||||
Suivez ensuite les étapes de configuration en sélectionnant dans le menu l'option à configurer.
|
Suivez ensuite les étapes de configuration en sélectionnant dans le menu l'option à configurer.
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Choix des attributs de base</summary>
|
|
||||||
|
|
||||||
## Choix des attributs de base
|
## Choix des attributs de base
|
||||||
|
|
||||||
@@ -328,10 +332,6 @@ Donnez les principaux attributs obligatoires :
|
|||||||
>  _*Notes*_
|
>  _*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**,
|
> 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**,
|
||||||
> 2. si le cycle est trop court, le radiateur ne pourra jamais atteindre la température cible. Pour le radiateur à accumulation par exemple il sera sollicité inutilement.
|
> 2. si le cycle est trop court, le radiateur ne pourra jamais atteindre la température cible. Pour le radiateur à accumulation par exemple il sera sollicité inutilement.
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Sélectionnez des entités pilotées (sous-jacents)</summary>
|
|
||||||
|
|
||||||
## Sélectionnez des entités pilotées (sous-jacents)
|
## Sélectionnez des entités pilotées (sous-jacents)
|
||||||
|
|
||||||
@@ -510,16 +510,23 @@ Il faut évidemment que votre équipement sous-jacent soit équipée d'une venti
|
|||||||
Si votre équipement ne comprend pas le mode Turbo, le mode Forte` sera utilisé en remplacement.
|
Si votre équipement ne comprend pas le mode Turbo, le mode Forte` sera utilisé en remplacement.
|
||||||
Une fois l'écart de température redevenu faible, la ventilation se mettra dans un mode "normal" qui dépend de votre équipement à savoir (dans l'ordre) : `Silence (mute)`, `Auto (auto)`, `Faible (low)`. La première valeur qui est possible pour votre équipement sera choisie.
|
Une fois l'écart de température redevenu faible, la ventilation se mettra dans un mode "normal" qui dépend de votre équipement à savoir (dans l'ordre) : `Silence (mute)`, `Auto (auto)`, `Faible (low)`. La première valeur qui est possible pour votre équipement sera choisie.
|
||||||
|
|
||||||
|
#### Le démarrage / arrêt automatique
|
||||||
|
Cette fonction a été introduite en 6.5.0. Elle permet d'autoriser VTherm a stopper un équipement qui n'a pas besoin d'être allumé et de le redémarrer lorsque les conditions le réclame. Cette fonction est munie de 3 réglages qui permettent d'arrêter / relancer plus ou moins rapidement l'équipement.
|
||||||
|
|
||||||
|
Pour l'utiliser, vous devez :
|
||||||
|
1. Ajouter la fonction `Avec démmarrage et extinction automatique` dans le menu 'Fonctions',
|
||||||
|
2. Paramétrer le niveau de détection dans l'option 'Allumage/extinction automatique' qui s'affiche lorsque la fonction a été activée. Vous choisissez le niveau de détection entre 'Lent', 'Moyen' et 'Rapide'. Les arrêts/relances seront plus nombreux avec le niveau 'Rapide'.
|
||||||
|
|
||||||
|
Une fois paramétré, vous aurez maintenant une nouvelle entité de type `switch` qui vous permet d'autoriser ou non l'arrêt/relance automatique sans toucher à la configuration. Cette entité est disponible sur l'appareil VTherm et se nomme `switch.<name>_enable_auto_start_stop`. Cochez la pour autoriser le démarrage et extinction automatique.
|
||||||
|
|
||||||
|
L'algorithme de détection est décrit [ici](https://github.com/jmcollin78/versatile_thermostat/issues/585).
|
||||||
|
|
||||||
### Pour un thermostat de type ```thermostat_over_valve```:
|
### Pour un thermostat de type ```thermostat_over_valve```:
|
||||||

|

|
||||||
Vous pouvez choisir jusqu'à entité du domaine ```number``` ou ```ìnput_number``` qui vont commander les vannes.
|
Vous pouvez choisir jusqu'à entité du domaine ```number``` ou ```ìnput_number``` qui vont commander les vannes.
|
||||||
L'algorithme à utiliser est aujourd'hui limité à TPI est disponible. Voir [algorithme](#algorithme).
|
L'algorithme à utiliser est aujourd'hui limité à TPI est disponible. Voir [algorithme](#algorithme).
|
||||||
|
|
||||||
Il est possible de choisir un thermostat over valve qui commande une climatisation en cochant la case "AC Mode". Dans ce cas, seul le mode refroidissement sera visible.
|
Il est possible de choisir un thermostat over valve qui commande une climatisation en cochant la case "AC Mode". Dans ce cas, seul le mode refroidissement sera visible.
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Configurez les coefficients de l'algorithme TPI</summary>
|
|
||||||
|
|
||||||
## Configurez les coefficients de l'algorithme TPI
|
## Configurez les coefficients de l'algorithme TPI
|
||||||
|
|
||||||
@@ -533,10 +540,6 @@ Vous devez donner :
|
|||||||
|
|
||||||
|
|
||||||
Pour plus d'informations sur l'algorithme TPI et son réglage, veuillez vous référer à [algorithm](#algorithm).
|
Pour plus d'informations sur l'algorithme TPI et son réglage, veuillez vous référer à [algorithm](#algorithm).
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Configurer les températures préréglées</summary>
|
|
||||||
|
|
||||||
## Configurer les températures préréglées
|
## Configurer les températures préréglées
|
||||||
|
|
||||||
@@ -557,10 +560,6 @@ Les pré-réglages se font (depuis v6.0) directement depuis les entités du VThe
|
|||||||
> 3. Si vous utilisez la gestion du délestage, vous verrez un préréglage caché nommé ``power``. Le préréglage de l'élément chauffant est réglé sur « puissance » lorsque des conditions de surpuissance sont rencontrées et que le délestage est actif pour cet élément chauffant. Voir [gestion de l'alimentation](#configure-the-power-management).
|
> 3. Si vous utilisez la gestion du délestage, vous verrez un préréglage caché nommé ``power``. Le préréglage de l'élément chauffant est réglé sur « puissance » lorsque des conditions de surpuissance sont rencontrées et que le délestage est actif pour cet élément chauffant. Voir [gestion de l'alimentation](#configure-the-power-management).
|
||||||
> 4. si vous utilisez la configuration avancée, vous verrez le préréglage défini sur ``sécurité`` si la température n'a pas pu être récupérée après un certain délai
|
> 4. si vous utilisez la configuration avancée, vous verrez le préréglage défini sur ``sécurité`` si la température n'a pas pu être récupérée après un certain délai
|
||||||
> 5. Si vous ne souhaitez pas utiliser le préréglage, indiquez 0 comme température. Le préréglage sera alors ignoré et ne s'affichera pas dans le composant front
|
> 5. Si vous ne souhaitez pas utiliser le préréglage, indiquez 0 comme température. Le préréglage sera alors ignoré et ne s'affichera pas dans le composant front
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Configurer les portes/fenêtres en allumant/éteignant les thermostats</summary>
|
|
||||||
|
|
||||||
## Configurer les portes/fenêtres en allumant/éteignant les thermostats
|
## Configurer les portes/fenêtres en allumant/éteignant les thermostats
|
||||||
|
|
||||||
@@ -603,10 +602,6 @@ Et c'est tout ! votre thermostat s'éteindra lorsque les fenêtres seront ouvert
|
|||||||
> 2. Si vous n'avez pas de capteur de fenêtre/porte dans votre chambre, laissez simplement l'identifiant de l'entité du capteur vide,
|
> 2. Si vous n'avez pas de capteur de fenêtre/porte dans votre chambre, laissez simplement l'identifiant de l'entité du capteur vide,
|
||||||
> 3. **Un seul mode est permis**. On ne peut pas configurer un thermostat avec un capteur et une détection automatique. Les 2 modes risquant de se contredire, il n'est pas possible d'avoir les 2 modes en même temps,
|
> 3. **Un seul mode est permis**. On ne peut pas configurer un thermostat avec un capteur et une détection automatique. Les 2 modes risquant de se contredire, il n'est pas possible d'avoir les 2 modes en même temps,
|
||||||
> 4. Il est déconseillé d'utiliser le mode automatique pour un équipement soumis à des variations de température fréquentes et normales (couloirs, zones ouvertes, ...)
|
> 4. Il est déconseillé d'utiliser le mode automatique pour un équipement soumis à des variations de température fréquentes et normales (couloirs, zones ouvertes, ...)
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Configurer le mode d'activité ou la détection de mouvement</summary>
|
|
||||||
|
|
||||||
## Configurer le mode d'activité ou la détection de mouvement
|
## Configurer le mode d'activité ou la détection de mouvement
|
||||||
|
|
||||||
@@ -634,10 +629,6 @@ Pour que cela fonctionne, le thermostat doit être en mode préréglé « Activ
|
|||||||
|
|
||||||
>  _*Notes*_
|
>  _*Notes*_
|
||||||
1. Sachez que comme pour les autres modes prédéfinis, ``Activity`` ne sera proposé que s'il est correctement configuré. En d'autres termes, les 4 clés de configuration doivent être définies si vous souhaitez voir l'activité dans l'interface de l'assistant domestique
|
1. Sachez que comme pour les autres modes prédéfinis, ``Activity`` ne sera proposé que s'il est correctement configuré. En d'autres termes, les 4 clés de configuration doivent être définies si vous souhaitez voir l'activité dans l'interface de l'assistant domestique
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Configurer la gestion de la puissance</summary>
|
|
||||||
|
|
||||||
## Configurer la gestion de la puissance
|
## Configurer la gestion de la puissance
|
||||||
|
|
||||||
@@ -656,10 +647,6 @@ Cela vous permet de modifier la puissance maximale au fil du temps à l'aide d'u
|
|||||||
> 3. Gardez toujours une marge, car la puissance max peut être brièvement dépassée en attendant le calcul du prochain cycle typiquement ou par des équipements non régulés.
|
> 3. Gardez toujours une marge, car la puissance max peut être brièvement dépassée en attendant le calcul du prochain cycle typiquement ou par des équipements non régulés.
|
||||||
> 4. Si vous ne souhaitez pas utiliser cette fonctionnalité, laissez simplement l'identifiant des entités vide
|
> 4. Si vous ne souhaitez pas utiliser cette fonctionnalité, laissez simplement l'identifiant des entités vide
|
||||||
> 5. Si vous controlez plusieurs radiateurs, la **consommation électrique de votre chauffage** renseigné doit correspondre à la somme des puissances.
|
> 5. Si vous controlez plusieurs radiateurs, la **consommation électrique de votre chauffage** renseigné doit correspondre à la somme des puissances.
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Configurer la présence (ou l'absence)</summary>
|
|
||||||
|
|
||||||
## Configurer la présence (ou l'absence)
|
## Configurer la présence (ou l'absence)
|
||||||
|
|
||||||
@@ -681,10 +668,6 @@ ATTENTION : les groupes de personnes ne fonctionnent pas en tant que capteur de
|
|||||||
>  _*Notes*_
|
>  _*Notes*_
|
||||||
> 1. le changement de température est immédiat et se répercute sur le volet avant. Le calcul prendra en compte la nouvelle température cible au prochain calcul du cycle,
|
> 1. le changement de température est immédiat et se répercute sur le volet avant. Le calcul prendra en compte la nouvelle température cible au prochain calcul du cycle,
|
||||||
> 2. vous pouvez utiliser le capteur direct person.xxxx ou un groupe de capteurs de Home Assistant. Le capteur de présence gère les états ``on`` ou ``home`` comme présents et les états ``off`` ou ``not_home`` comme absents.
|
> 2. vous pouvez utiliser le capteur direct person.xxxx ou un groupe de capteurs de Home Assistant. Le capteur de présence gère les états ``on`` ou ``home`` comme présents et les états ``off`` ou ``not_home`` comme absents.
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Configuration avancée</summary>
|
|
||||||
|
|
||||||
## Configuration avancée
|
## Configuration avancée
|
||||||
|
|
||||||
@@ -702,6 +685,8 @@ Mettre ce paramètre à ``0.00`` déclenchera le préréglage sécurité quelque
|
|||||||
|
|
||||||
Le quatrième param§tre (``security_default_on_percent``) est la valeur de ``on_percent`` qui sera utilisée lorsque le thermostat passe en mode ``security``. Si vous mettez ``0`` alors le thermostat sera coupé lorsqu'il passe en mode ``security``, mettre 0,2% par exemple permet de garder un peu de chauffage (20% dans ce cas), même en mode ``security``. Ca évite de retrouver son logement totalement gelé lors d'une panne de thermomètre.
|
Le quatrième param§tre (``security_default_on_percent``) est la valeur de ``on_percent`` qui sera utilisée lorsque le thermostat passe en mode ``security``. Si vous mettez ``0`` alors le thermostat sera coupé lorsqu'il passe en mode ``security``, mettre 0,2% par exemple permet de garder un peu de chauffage (20% dans ce cas), même en mode ``security``. Ca évite de retrouver son logement totalement gelé lors d'une panne de thermomètre.
|
||||||
|
|
||||||
|
Note: les paramètres `security_min_on_percent` et `security_default_on_percent` ne s'applique pas aux VTherms `over_climate`.
|
||||||
|
|
||||||
Depuis la version 5.3 il est possible de désactiver la mise en sécurité suite à une absence de données du thermomètre extérieure. En effet, celui-ci ayant la plupart du temps un impact faible sur la régulation (dépendant de votre paramètrage), il est possible qu'il soit absent sans mettre en danger le logement. Pour cela, il faut ajouter les lignes suivantes dans votre `configuration.yaml` :
|
Depuis la version 5.3 il est possible de désactiver la mise en sécurité suite à une absence de données du thermomètre extérieure. En effet, celui-ci ayant la plupart du temps un impact faible sur la régulation (dépendant de votre paramètrage), il est possible qu'il soit absent sans mettre en danger le logement. Pour cela, il faut ajouter les lignes suivantes dans votre `configuration.yaml` :
|
||||||
```
|
```
|
||||||
versatile_thermostat:
|
versatile_thermostat:
|
||||||
@@ -719,10 +704,6 @@ Voir [exemple de réglages](#examples-tuning) pour avoir des exemples de réglag
|
|||||||
> 3. Un service est disponible qui permet de régler les 3 paramètres de sécurité. Ca peut servir à adapter la fonction de sécurité à votre usage,
|
> 3. Un service est disponible qui permet de régler les 3 paramètres de sécurité. Ca peut servir à adapter la fonction de sécurité à votre usage,
|
||||||
> 4. Pour un usage naturel, le ``security_default_on_percent`` doit être inférieur à ``security_min_on_percent``,
|
> 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.
|
> 5. Les thermostats de type ``thermostat_over_climate`` ne sont pas concernés par le mode security.
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Le contrôle centralisé</summary>
|
|
||||||
|
|
||||||
## Le contrôle centralisé
|
## Le contrôle centralisé
|
||||||
|
|
||||||
@@ -739,10 +720,6 @@ Il est donc possible de contrôler tous les VTherms (que ceux que l'on désigne
|
|||||||
Exemple de rendu :
|
Exemple de rendu :
|
||||||
|
|
||||||

|

|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Le contrôle d'une chaudière centrale</summary>
|
|
||||||
|
|
||||||
## Le contrôle d'une chaudière centrale
|
## Le contrôle d'une chaudière centrale
|
||||||
|
|
||||||
@@ -844,7 +821,6 @@ context:
|
|||||||
|
|
||||||
>  _*Notes*_
|
>  _*Notes*_
|
||||||
> Le contrôle par du logiciel ou du matériel de type domotique d'une chaudière centrale peut induire des risques pour son bon fonctionnement. Assurez-vous avant d'utiliser ces fonctions, que votre chaudière possède bien des fonctions de sécurité et que celles-ci fonctionnent. Allumer une chaudière si tous les robinets sont fermés peut générer de la sur-pression par exemple.
|
> Le contrôle par du logiciel ou du matériel de type domotique d'une chaudière centrale peut induire des risques pour son bon fonctionnement. Assurez-vous avant d'utiliser ces fonctions, que votre chaudière possède bien des fonctions de sécurité et que celles-ci fonctionnent. Allumer une chaudière si tous les robinets sont fermés peut générer de la sur-pression par exemple.
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Synthèse des paramètres</summary>
|
<summary>Synthèse des paramètres</summary>
|
||||||
@@ -911,6 +887,8 @@ context:
|
|||||||
| ``central_boiler_activation_service`` | Service d'activation de la chaudière | - | - | - | X |
|
| ``central_boiler_activation_service`` | Service d'activation de la chaudière | - | - | - | X |
|
||||||
| ``central_boiler_deactivation_service`` | Service de desactivation de la chaudière | - | - | - | X |
|
| ``central_boiler_deactivation_service`` | Service de desactivation de la chaudière | - | - | - | X |
|
||||||
| ``used_by_controls_central_boiler`` | Indique si le VTherm contrôle la chaudière centrale | X | X | X | - |
|
| ``used_by_controls_central_boiler`` | Indique si le VTherm contrôle la chaudière centrale | X | X | X | - |
|
||||||
|
| ``use_auto_start_stop_feature`` | Indique si la fonction de démarrage/extinction automatique est activée | - | X | - | - |
|
||||||
|
| ``auto_start_stop_lvel`` | Le niveau de détection de l'auto start/stop | - | X | - | - |
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
# Exemples de réglage
|
# Exemples de réglage
|
||||||
@@ -1168,6 +1146,9 @@ Les attributs personnalisés sont les suivants :
|
|||||||
| ``is_controlled_by_central_mode`` | True si le VTherm peut être controlé de façon centrale |
|
| ``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) |
|
| ``last_central_mode`` | Le dernier mode central utilisé (None si le VTherm n'est pas controlé en central) |
|
||||||
| ``is_used_by_central_boiler`` | Indique si le VTherm peut contrôler la chaudière centrale |
|
| ``is_used_by_central_boiler`` | Indique si le VTherm peut contrôler la chaudière centrale |
|
||||||
|
| ``auto_start_stop_enable`` | Indique si le VTherm est autorisé à s'auto démarrer/arrêter |
|
||||||
|
| ``auto_start_stop_level`` | Indique le niveau d'auto start/stop |
|
||||||
|
| ``hvac_off_reason`` | Indique la raison de l'arrêt (hvac_off) du VTherm. Ce peut être Window, Auto-start/stop ou Manuel |
|
||||||
|
|
||||||
# Quelques résultats
|
# Quelques résultats
|
||||||
|
|
||||||
@@ -1590,7 +1571,7 @@ Ces paramètres sont sensibles et assez difficiles à régler. Merci de ne les u
|
|||||||
<summary>Pourquoi mon Versatile Thermostat se met en Securite ?</summary>
|
<summary>Pourquoi mon Versatile Thermostat se met en Securite ?</summary>
|
||||||
|
|
||||||
## Pourquoi mon Versatile Thermostat se met en Securite ?
|
## Pourquoi mon Versatile Thermostat se met en Securite ?
|
||||||
Le mode sécurité n'est possible que sur les VTherm `over_switch` et `over_valve`. Il survient lorsqu'un des 2 thermomètres qui donne la température de la pièce ou la température extérieure n'a pas envoyé de valeur depuis plus de `security_delay_min` minutes et que le radiateur chauffait à au moins `security_min_on_percent`.
|
Le mode sécurité est possible sur tous les types de VTherm . Il survient lorsqu'un des 2 thermomètres qui donne la température de la pièce ou la température extérieure n'a pas envoyé de valeur depuis plus de `security_delay_min` minutes et que le radiateur chauffait à au moins `security_min_on_percent`.
|
||||||
|
|
||||||
Comme l'algorithme est basé sur les mesures de température, si elles ne sont plus reçues par le VTherm, il y a un risque de surchauffe et d'incendie. Pour éviter ça, lorsque les conditions rappelées ci-dessus sont détectées, la chauffe est limité au paramètre `security_default_on_percent`. Cette valeur doit donc être raisonnablement faible (10% est une bonne valeur). Elle permet d'éviter un incendie tout en évitant de couper totalement le radiateur (risque de gel).
|
Comme l'algorithme est basé sur les mesures de température, si elles ne sont plus reçues par le VTherm, il y a un risque de surchauffe et d'incendie. Pour éviter ça, lorsque les conditions rappelées ci-dessus sont détectées, la chauffe est limité au paramètre `security_default_on_percent`. Cette valeur doit donc être raisonnablement faible (10% est une bonne valeur). Elle permet d'éviter un incendie tout en évitant de couper totalement le radiateur (risque de gel).
|
||||||
|
|
||||||
|
|||||||
283
README.md
283
README.md
@@ -13,7 +13,7 @@
|
|||||||
- [In the case of a central configuration](#in-the-case-of-a-central-configuration)
|
- [In the case of a central configuration](#in-the-case-of-a-central-configuration)
|
||||||
- [Redesign of the configuration menu](#redesign-of-the-configuration-menu)
|
- [Redesign of the configuration menu](#redesign-of-the-configuration-menu)
|
||||||
- [The 'Incomplete configuration' and 'Finalize' menu options](#the-incomplete-configuration-and-finalize-menu-options)
|
- [The 'Incomplete configuration' and 'Finalize' menu options](#the-incomplete-configuration-and-finalize-menu-options)
|
||||||
- [Changements dans la version 5.0](#changements-dans-la-version-50)
|
- [Changes in release 5.0](#changes-in-release-50)
|
||||||
- [Thanks for the beer buymecoffee](#thanks-for-the-beer-buymecoffee)
|
- [Thanks for the beer buymecoffee](#thanks-for-the-beer-buymecoffee)
|
||||||
- [When to use / not use](#when-to-use--not-use)
|
- [When to use / not use](#when-to-use--not-use)
|
||||||
- [Incompatibilities](#incompatibilities)
|
- [Incompatibilities](#incompatibilities)
|
||||||
@@ -32,6 +32,7 @@
|
|||||||
- [Internal temperature compensation](#internal-temperature-compensation)
|
- [Internal temperature compensation](#internal-temperature-compensation)
|
||||||
- [synthesis of the self-regulation algorithm](#synthesis-of-the-self-regulation-algorithm)
|
- [synthesis of the self-regulation algorithm](#synthesis-of-the-self-regulation-algorithm)
|
||||||
- [Auto-fan mode](#auto-fan-mode)
|
- [Auto-fan mode](#auto-fan-mode)
|
||||||
|
- [Automatic start/stop](#automatic-startstop)
|
||||||
- [For a thermostat of type ```thermostat_over_valve```:](#for-a-thermostat-of-type-thermostat_over_valve)
|
- [For a thermostat of type ```thermostat_over_valve```:](#for-a-thermostat-of-type-thermostat_over_valve)
|
||||||
- [Configure the TPI algorithm coefficients](#configure-the-tpi-algorithm-coefficients)
|
- [Configure the TPI algorithm coefficients](#configure-the-tpi-algorithm-coefficients)
|
||||||
- [Configure the preset temperature](#configure-the-preset-temperature)
|
- [Configure the preset temperature](#configure-the-preset-temperature)
|
||||||
@@ -93,6 +94,9 @@
|
|||||||
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.
|
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.
|
||||||
|
|
||||||
> _*Latest releases*_
|
> _*Latest releases*_
|
||||||
|
> * **Release 6.5** :
|
||||||
|
> - Added a new function allowing the automatic shutdown and restart of a VTherm `over_climate` [585](https://github.com/jmcollin78/versatile_thermostat/issues/585)
|
||||||
|
> - Improved management of openings at startup. Allows to memorize and recalculate the state of an opening when restarting Home Assistant [504](https://github.com/jmcollin78/versatile_thermostat/issues/504)
|
||||||
> * **Release 6.0**:
|
> * **Release 6.0**:
|
||||||
> - Added entities from the Number domain to configure preset temperatures [354](https://github.com/jmcollin78/versatile_thermostat/issues/354)
|
> - Added entities from the Number domain to configure preset temperatures [354](https://github.com/jmcollin78/versatile_thermostat/issues/354)
|
||||||
> - Complete redesign of the configuration menu to remove temperatures and use a menu instead of a configuration tunnel [354](https://github.com/jmcollin78/versatile_thermostat/issues/354)
|
> - Complete redesign of the configuration menu to remove temperatures and use a menu instead of a configuration tunnel [354](https://github.com/jmcollin78/versatile_thermostat/issues/354)
|
||||||
@@ -101,13 +105,13 @@ This custom component for Home Assistant is an upgrade and is a complete rewrite
|
|||||||
> - addition of regulation thresholds for the `over_valve` to avoid draining the TRV battery too much [#338](https://github.com/jmcollin78/versatile_thermostat/issues/338),
|
> - addition of regulation thresholds for the `over_valve` to avoid draining the TRV battery too much [#338](https://github.com/jmcollin78/versatile_thermostat/issues/338),
|
||||||
> - added an option allowing the internal temperature of a TRV to be used to force self-regulation [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348),
|
> - added an option allowing the internal temperature of a TRV to be used to force self-regulation [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348),
|
||||||
> - added a keep-alive function for VTherm `over_switch` [#345](https://github.com/jmcollin78/versatile_thermostat/issues/345)
|
> - added a keep-alive function for VTherm `over_switch` [#345](https://github.com/jmcollin78/versatile_thermostat/issues/345)
|
||||||
|
<details>
|
||||||
|
<summary>Others releases</summary>
|
||||||
|
|
||||||
> * **Release 5.3**: Added a central boiler control function [#234](https://github.com/jmcollin78/versatile_thermostat/issues/234) - more information here: [Controlling a central boiler](#controlling-a-central-boiler). Added the ability to disable security mode for outdoor thermometer [#343](https://github.com/jmcollin78/versatile_thermostat/issues/343)
|
> * **Release 5.3**: Added a central boiler control function [#234](https://github.com/jmcollin78/versatile_thermostat/issues/234) - more information here: [Controlling a central boiler](#controlling-a-central-boiler). Added the ability to disable security mode for outdoor thermometer [#343](https://github.com/jmcollin78/versatile_thermostat/issues/343)
|
||||||
> * **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.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.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 5.0**: Added a central configuration allowing the sharing of attributes that can be shared [#239](https://github.com/jmcollin78/versatile_thermostat/issues/239).
|
||||||
<details>
|
|
||||||
<summary>Others releases</summary>
|
|
||||||
|
|
||||||
> * **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.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.
|
> * **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.
|
||||||
> * **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.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).
|
||||||
@@ -126,6 +130,10 @@ This custom component for Home Assistant is an upgrade and is a complete rewrite
|
|||||||
> * **major release 2.0**: addition of the "over climate" thermostat allowing you to transform any thermostat into a Versatile Thermostat and add all the functions of the latter.
|
> * **major release 2.0**: addition of the "over climate" thermostat allowing you to transform any thermostat into a Versatile Thermostat and add all the functions of the latter.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Changes in version 6.0</summary>
|
||||||
|
|
||||||
# Changes in version 6.0
|
# Changes in version 6.0
|
||||||
|
|
||||||
## Temperature entities for presets
|
## Temperature entities for presets
|
||||||
@@ -193,11 +201,12 @@ Once all configuration is valid, the last option changes to:
|
|||||||
Click on this option to create (resp. modify) the VTherm:
|
Click on this option to create (resp. modify) the VTherm:
|
||||||
|
|
||||||

|

|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Changements dans la version 5.0</summary>
|
<summary>Changes in release 5.0</summary>
|
||||||
|
|
||||||
# Changements dans la version 5.0
|
# Changes in release 5.0
|
||||||
|
|
||||||
You can now define a central configuration which will allow you to share certain attributes on all your VTherms (or only part of them). To use this possibility, you must:
|
You can now define a central configuration which will allow you to share certain attributes on all your VTherms (or only part of them). To use this possibility, you must:
|
||||||
1. Create a VTherm of type “Central Configuration”,
|
1. Create a VTherm of type “Central Configuration”,
|
||||||
@@ -238,6 +247,7 @@ Some TRV type thermostats are known to be incompatible with the Versatile Thermo
|
|||||||
4. Thermostats of type Rointe tends to awake alone even if VTherm turns it off. Others functions works fine.
|
4. Thermostats of type Rointe tends to awake alone even if VTherm turns it off. Others functions works fine.
|
||||||
5. TRV of type Aqara SRTS-A01 and MOES TV01-ZB which doesn't have the return state `hvac_action` allowing to know if it is heating or not. So return states are not available. Others features, seems to work normally.
|
5. TRV of type Aqara SRTS-A01 and MOES TV01-ZB which doesn't have the return state `hvac_action` allowing to know if it is heating or not. So return states are not available. Others features, seems to work normally.
|
||||||
6. The Airwell with the "Midea AC LAN" integration. If two orders are too close, the device shut off.
|
6. The Airwell with the "Midea AC LAN" integration. If two orders are too close, the device shut off.
|
||||||
|
7. System based on intégration Overkiz don't work as expected. It seems not possible to turn off nor sending setpoint on those systems.
|
||||||
|
|
||||||
# Why another thermostat implementation ?
|
# Why another thermostat implementation ?
|
||||||
|
|
||||||
@@ -288,9 +298,6 @@ This component named __Versatile thermostat__ manage the following use cases :
|
|||||||
> 3. In addition to this centralized configuration, all VTherms can be controlled by a single entity of type `select`. This function is named `central_mode`. This allows you to stop / start / freeze / etc. all VTherms at once. For each VTherm, the user indicates whether he is affected by this `central_mode`.
|
> 3. In addition to this centralized configuration, all VTherms can be controlled by a single entity of type `select`. This function is named `central_mode`. This allows you to stop / start / freeze / etc. all VTherms at once. For each VTherm, the user indicates whether he is affected by this `central_mode`.
|
||||||
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Creation of a new Versatile Thermostat</summary>
|
|
||||||
|
|
||||||
## Creation of a new Versatile Thermostat
|
## Creation of a new Versatile Thermostat
|
||||||
|
|
||||||
Click on Add integration button in the integration page
|
Click on Add integration button in the integration page
|
||||||
@@ -301,11 +308,6 @@ The configuration can be change through the same interface. Simply select the th
|
|||||||
Then choose the type of VTherm you want to create:
|
Then choose the type of VTherm you want to create:
|
||||||

|

|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Minimal configuration update</summary>
|
|
||||||
|
|
||||||
## Minimal configuration update
|
## Minimal configuration update
|
||||||
|
|
||||||
Then choose the “Main attributes” menu.
|
Then choose the “Main attributes” menu.
|
||||||
@@ -326,10 +328,6 @@ Give the main mandatory attributes:
|
|||||||
>  _*Notes*_
|
>  _*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**,
|
> 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**,
|
||||||
> 2. if the cycle is too short, the heater could never reach the target temperature. For the storage radiator for example it will be used unnecessarily.
|
> 2. if the cycle is too short, the heater could never reach the target temperature. For the storage radiator for example it will be used unnecessarily.
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Select the driven entity</summary>
|
|
||||||
|
|
||||||
## Select the driven entity
|
## Select the driven entity
|
||||||
|
|
||||||
@@ -391,82 +389,6 @@ These three parameters make it possible to modulate the regulation and avoid mul
|
|||||||
|
|
||||||
Self-regulation consists of forcing the equipment to go further by forcing its set temperature regularly. Its consumption can therefore be increased, as well as its wear.
|
Self-regulation consists of forcing the equipment to go further by forcing its set temperature regularly. Its consumption can therefore be increased, as well as its wear.
|
||||||
|
|
||||||
#### Self-regulation in Expert mode
|
|
||||||
|
|
||||||
In **Expert** mode you can finely adjust the auto-regulation parameters to achieve your objectives and optimize as best as possible. The algorithm calculates the difference between the setpoint and the actual temperature of the room. This discrepancy is called error.
|
|
||||||
The adjustable parameters are as follows:
|
|
||||||
1. `kp`: the factor applied to the raw error,
|
|
||||||
2. `ki`: the factor applied to the accumulation of errors,
|
|
||||||
3. `k_ext`: the factor applied to the difference between the interior temperature and the exterior temperature,
|
|
||||||
4. `offset_max`: the maximum correction (offset) that the regulation can apply,
|
|
||||||
5. `stabilization_threshold`: a stabilization threshold which, when reached by the error, resets the accumulation of errors to 0,
|
|
||||||
6. `accumulated_error_threshold`: the maximum for error accumulation.
|
|
||||||
|
|
||||||
For tuning, these observations must be taken into account:
|
|
||||||
1. `kp * error` will give the offset linked to the raw error. This offset is directly proportional to the error and will be 0 when the target is reached,
|
|
||||||
2. the accumulation of the error makes it possible to correct the stabilization of the curve while there remains an error. The error accumulates and the offset therefore gradually increases which should eventually stabilize at the target temperature. For this fundamental parameter to have an effect it must not be too small. An average value is 30
|
|
||||||
3. `ki * accumulated_error_threshold` will give the maximum offset linked to the accumulation of the error,
|
|
||||||
4. `k_ext` allows a correction to be applied immediately (without waiting for errors to accumulate) when the outside temperature is very different from the target temperature. If the stabilization is done too high when the temperature differences are significant, it is because this parameter is too high. It should be possible to cancel completely to let the first 2 offsets take place
|
|
||||||
|
|
||||||
The pre-programmed values are as follows:
|
|
||||||
|
|
||||||
Slow régulation :
|
|
||||||
|
|
||||||
kp: 0.2 # 20% of the current internal regulation offset are caused by the current difference of target temperature and room temperature
|
|
||||||
ki: 0.8 / 288.0 # 80% of the current internal regulation offset are caused by the average offset of the past 24 hours
|
|
||||||
k_ext: 1.0 / 25.0 # this will add 1°C to the offset when it's 25°C colder outdoor than indoor
|
|
||||||
offset_max: 2.0 # limit to a final offset of -2°C to +2°C
|
|
||||||
stabilization_threshold: 0.0 # this needs to be disabled as otherwise the long term accumulated error will always be reset when the temp briefly crosses from/to below/above the target
|
|
||||||
accumulated_error_threshold: 2.0 * 288 # this allows up to 2°C long term offset in both directions
|
|
||||||
|
|
||||||
Light régulation :
|
|
||||||
|
|
||||||
kp: 0.2
|
|
||||||
ki: 0.05
|
|
||||||
k_ext: 0.05
|
|
||||||
offset_max: 1.5
|
|
||||||
stabilization_threshold: 0.1
|
|
||||||
accumulated_error_threshold: 10
|
|
||||||
|
|
||||||
Medium régulation :
|
|
||||||
|
|
||||||
kp: 0.3
|
|
||||||
ki: 0.05
|
|
||||||
k_ext: 0.1
|
|
||||||
offset_max: 2
|
|
||||||
stabilization_threshold: 0.1
|
|
||||||
accumulated_error_threshold: 20
|
|
||||||
|
|
||||||
Strong régulation :
|
|
||||||
|
|
||||||
"""Strong parameters for regulation
|
|
||||||
A set of parameters which doesn't take into account the external temp
|
|
||||||
and concentrate to internal temp error + accumulated error.
|
|
||||||
This should work for cold external conditions which else generates
|
|
||||||
high external_offset"""
|
|
||||||
|
|
||||||
kp: 0.4
|
|
||||||
ki: 0.08
|
|
||||||
k_ext: 0.0
|
|
||||||
offset_max: 5
|
|
||||||
stabilization_threshold: 0.1
|
|
||||||
accumulated_error_threshold: 50
|
|
||||||
|
|
||||||
To use Expert mode you must declare the values you want to use for each of these parameters in your `configuration.yaml` in the following form:
|
|
||||||
```
|
|
||||||
versatile_thermostat:
|
|
||||||
auto_regulation_expert:
|
|
||||||
kp: 0.4
|
|
||||||
ki: 0.08
|
|
||||||
k_ext: 0.0
|
|
||||||
offset_max: 5
|
|
||||||
stabilization_threshold: 0.1
|
|
||||||
accumulated_error_threshold: 50
|
|
||||||
```
|
|
||||||
and of course, configure the VTherm's self-regulation mode in **Expert** mode. All VTherms in Expert mode will use these same settings.
|
|
||||||
|
|
||||||
For the changes to be taken into account, you must either **completely restart Home Assistant** or just the **Versatile Thermostat integration** (Dev tools / Yaml / reloading the configuration / Versatile Thermostat).
|
|
||||||
|
|
||||||
#### Internal temperature compensation
|
#### Internal temperature compensation
|
||||||
Sometimes, a device’s internal temperature sensor (like in a TRV or AC) can give inaccurate readings, especially if it’s too close to a heat source. This can cause the device to stop heating too soon.
|
Sometimes, a device’s internal temperature sensor (like in a TRV or AC) can give inaccurate readings, especially if it’s too close to a heat source. This can cause the device to stop heating too soon.
|
||||||
For example:
|
For example:
|
||||||
@@ -500,6 +422,17 @@ Obviously your underlying equipment must be equipped with ventilation and be con
|
|||||||
If your equipment does not include Turbo mode, Forte` mode will be used as a replacement.
|
If your equipment does not include Turbo mode, Forte` mode will be used as a replacement.
|
||||||
Once the temperature difference becomes low again, the ventilation will go into a "normal" mode which depends on your equipment, namely (in order): `Silence (mute)`, `Auto (auto)`, `Low (low)`. The first value that is possible for your equipment will be chosen.
|
Once the temperature difference becomes low again, the ventilation will go into a "normal" mode which depends on your equipment, namely (in order): `Silence (mute)`, `Auto (auto)`, `Low (low)`. The first value that is possible for your equipment will be chosen.
|
||||||
|
|
||||||
|
#### Automatic start/stop
|
||||||
|
This function was introduced in 6.5.0. It allows VTherm to stop equipment that does not need to be turned on and to restart it when conditions require it. This function has 3 settings that allow the equipment to be stopped/restarted more or less quickly.
|
||||||
|
|
||||||
|
To use it, you must:
|
||||||
|
1. Add the `Use the auto start and stop feature` function in the 'Features' menu,
|
||||||
|
2. Set the detection level in the `Auto start and stop` option that is displayed when the function has been activated. You choose the detection level between 'Slow', 'Medium' and 'Fast'. The 'Fast' level will result in more shutdowns/restarts.
|
||||||
|
|
||||||
|
Once configured, you will now have a new entity of type `switch` that allows you to authorize or not the automatic shutdown/restart without touching the configuration. This entity is available on the VTherm device and is called `switch.<name>_enable_auto_start_stop`. Check it to authorize the automatic startup and shutdown.
|
||||||
|
|
||||||
|
The detection algorithm is described [here](https://github.com/jmcollin78/versatile_thermostat/issues/585).
|
||||||
|
|
||||||
### For a thermostat of type ```thermostat_over_valve```:
|
### For a thermostat of type ```thermostat_over_valve```:
|
||||||

|

|
||||||
You can choose up to domain entity ```number``` or ```ìnput_number``` which will control the valves.
|
You can choose up to domain entity ```number``` or ```ìnput_number``` which will control the valves.
|
||||||
@@ -507,11 +440,6 @@ The algorithm to use is currently limited to TPI is available. See [algorithm](#
|
|||||||
|
|
||||||
It is possible to choose an over valve thermostat which controls air conditioning by checking the "AC Mode" box. In this case, only the cooling mode will be visible.
|
It is possible to choose an over valve thermostat which controls air conditioning by checking the "AC Mode" box. In this case, only the cooling mode will be visible.
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Configure the TPI algorithm coefficients</summary>
|
|
||||||
|
|
||||||
## Configure the TPI algorithm coefficients
|
## Configure the TPI algorithm coefficients
|
||||||
|
|
||||||
Ff you choose a ```over_switch``` or ```over_valve``` thermostat and select the "TPI" menu option, you will get there:
|
Ff you choose a ```over_switch``` or ```over_valve``` thermostat and select the "TPI" menu option, you will get there:
|
||||||
@@ -519,11 +447,6 @@ Ff you choose a ```over_switch``` or ```over_valve``` thermostat and select the
|
|||||||
|
|
||||||
For more informations on the TPI algorithm and tuned please refer to [algorithm](#algorithm).
|
For more informations on the TPI algorithm and tuned please refer to [algorithm](#algorithm).
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Configure the preset temperature</summary>
|
|
||||||
|
|
||||||
## Configure the preset temperature
|
## Configure the preset temperature
|
||||||
|
|
||||||
The preset mode allows you to pre-configurate targeted temperature. Used in conjonction with Scheduler (see [scheduler](#even-better-with-scheduler-component) you will have a powerfull and simple way to optimize the temperature vs electrical consumption of your hous. Preset handled are the following :
|
The preset mode allows you to pre-configurate targeted temperature. Used in conjonction with Scheduler (see [scheduler](#even-better-with-scheduler-component) you will have a powerfull and simple way to optimize the temperature vs electrical consumption of your hous. Preset handled are the following :
|
||||||
@@ -544,11 +467,6 @@ The pre-settings are made (since v6.0) directly from the VTherm entities or from
|
|||||||
> 4. if you uses the advanced configuration you will see the preset set to ``safety`` if the temperature could not be retrieved after a certain delay
|
> 4. if you uses the advanced configuration you will see the preset set to ``safety`` if the temperature could not be retrieved after a certain delay
|
||||||
> 5. ff you don't want to use the preseet, give 0 as temperature. The preset will then been ignored and will not displayed in the front component
|
> 5. ff you don't want to use the preseet, give 0 as temperature. The preset will then been ignored and will not displayed in the front component
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Configure the doors/windows turning on/off the thermostats</summary>
|
|
||||||
|
|
||||||
## Configure the doors/windows turning on/off the thermostats
|
## Configure the doors/windows turning on/off the thermostats
|
||||||
|
|
||||||
You must have chosen the ```With opening detection``` feature on the first page to arrive on this page.
|
You must have chosen the ```With opening detection``` feature on the first page to arrive on this page.
|
||||||
@@ -590,11 +508,6 @@ And that's all ! your thermostat will turn off when the windows are open and tur
|
|||||||
> 3. **Only one mode is allowed**. You cannot configure a thermostat with a sensor and automatic detection. The 2 modes may contradict each other, it is not possible to have the 2 modes at the same time,
|
> 3. **Only one mode is allowed**. You cannot configure a thermostat with a sensor and automatic detection. The 2 modes may contradict each other, it is not possible to have the 2 modes at the same time,
|
||||||
> 4. It is not recommended to use the automatic mode for equipment subject to frequent and normal temperature variations (corridors, open areas, ...)
|
> 4. It is not recommended to use the automatic mode for equipment subject to frequent and normal temperature variations (corridors, open areas, ...)
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Configure the activity mode or motion detection</summary>
|
|
||||||
|
|
||||||
## Configure the activity mode or motion detection
|
## Configure the activity mode or motion detection
|
||||||
If you choose the ```Motion management``` feature, lick on 'Validate' on the previous page and you will get there:
|
If you choose the ```Motion management``` feature, lick on 'Validate' on the previous page and you will get there:
|
||||||

|

|
||||||
@@ -619,11 +532,6 @@ For this to work, the climate thermostat should be in ``Activity`` preset mode.
|
|||||||
>  _*Notes*_
|
>  _*Notes*_
|
||||||
> 1. Be aware that as for the others preset modes, ``Activity`` will only be proposed if it's correctly configure. In other words, the 4 configuration keys have to be set if you want to see Activity in home assistant Interface
|
> 1. Be aware that as for the others preset modes, ``Activity`` will only be proposed if it's correctly configure. In other words, the 4 configuration keys have to be set if you want to see Activity in home assistant Interface
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Configure the power management</summary>
|
|
||||||
|
|
||||||
## Configure the power management
|
## Configure the power management
|
||||||
|
|
||||||
If you choose the ```Power management``` feature, click on 'Validate' on the previous page and you will get there:
|
If you choose the ```Power management``` feature, click on 'Validate' on the previous page and you will get there:
|
||||||
@@ -641,10 +549,6 @@ This allows you to change the max power along time using a Scheduler or whatever
|
|||||||
> 3. Always keep a margin, because max power can be briefly exceeded while waiting for the next cycle calculation typically or by not regulated equipement.
|
> 3. Always keep a margin, because max power can be briefly exceeded while waiting for the next cycle calculation typically or by not regulated equipement.
|
||||||
> 4. If you don't want to use this feature, just leave the entities id empty
|
> 4. If you don't want to use this feature, just leave the entities id empty
|
||||||
> 5. If you control several heaters, the **power consumption of your heater** setup should be the sum of the power.
|
> 5. If you control several heaters, the **power consumption of your heater** setup should be the sum of the power.
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Configure presence or occupancy</summary>
|
|
||||||
|
|
||||||
## Configure presence or occupancy
|
## Configure presence or occupancy
|
||||||
|
|
||||||
@@ -667,11 +571,6 @@ ATTENTION: groups of people do not function as a presence sensor. They are not r
|
|||||||
> 1. the change in temperature is immediate and is reflected on the front shutter. The calculation will take into account the new target temperature at the next calculation of the cycle,
|
> 1. the change in temperature is immediate and is reflected on the front shutter. The calculation will take into account the new target temperature at the next calculation of the cycle,
|
||||||
> 2. you can use the person.xxxx direct sensor or a group of Home Assistant sensors. The presence sensor manages the ``on`` or ``home`` states as present and the ``off`` or ``not_home`` states as absent.
|
> 2. you can use the person.xxxx direct sensor or a group of Home Assistant sensors. The presence sensor manages the ``on`` or ``home`` states as present and the ``off`` or ``not_home`` states as absent.
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Advanced configuration</summary>
|
|
||||||
|
|
||||||
## Advanced configuration
|
## Advanced configuration
|
||||||
|
|
||||||
Those parameters allows to fine tune the thermostat.
|
Those parameters allows to fine tune the thermostat.
|
||||||
@@ -688,6 +587,8 @@ Setting this parameter to ``0.00`` will trigger the safety preset regardless of
|
|||||||
|
|
||||||
The fourth parameter (``security_default_on_percent``) is the ``on_percent`` value that will be used when the thermostat enters ``safety`` mode. If you put ``0`` then the thermostat will be cut off when it goes into ``safety`` mode, putting 0.2% for example allows you to keep a little heating (20% in this case), even in mode ``safety``. It avoids finding your home totally frozen during a thermometer failure.
|
The fourth parameter (``security_default_on_percent``) is the ``on_percent`` value that will be used when the thermostat enters ``safety`` mode. If you put ``0`` then the thermostat will be cut off when it goes into ``safety`` mode, putting 0.2% for example allows you to keep a little heating (20% in this case), even in mode ``safety``. It avoids finding your home totally frozen during a thermometer failure.
|
||||||
|
|
||||||
|
Note: parameters `security_min_on_percent` et `security_default_on_percent` are not used by `over_climate` VTherm.
|
||||||
|
|
||||||
Since version 5.3 it is possible to deactivate the safety device following a lack of data from the outdoor thermometer. Indeed, this most of the time having a low impact on regulation (depending on your settings), it is possible that it is absent without endangering the home. To do this, you must add the following lines to your `configuration.yaml`:
|
Since version 5.3 it is possible to deactivate the safety device following a lack of data from the outdoor thermometer. Indeed, this most of the time having a low impact on regulation (depending on your settings), it is possible that it is absent without endangering the home. To do this, you must add the following lines to your `configuration.yaml`:
|
||||||
```
|
```
|
||||||
versatile_thermostat:
|
versatile_thermostat:
|
||||||
@@ -706,11 +607,6 @@ 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``,
|
> 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.
|
> 5. Thermostat of type ``thermostat_over_climate`` are not concerned by the safety feature.
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Centralized control</summary>
|
|
||||||
|
|
||||||
## Centralized control
|
## 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.
|
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.
|
||||||
@@ -727,11 +623,6 @@ Example rendering:
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Control of a central boiler</summary>
|
|
||||||
|
|
||||||
## Control of a central boiler
|
## Control of a central boiler
|
||||||
|
|
||||||
Since release 5.3, you have the possibility of controlling a centralized boiler. From the moment it is possible to start or stop this boiler from Home Assistant, then Versatile Thermostat will be able to control it directly.
|
Since release 5.3, you have the possibility of controlling a centralized boiler. From the moment it is possible to start or stop this boiler from Home Assistant, then Versatile Thermostat will be able to control it directly.
|
||||||
@@ -833,7 +724,112 @@ context:
|
|||||||
>  _*Notes*_
|
>  _*Notes*_
|
||||||
> Controlling a central boiler using software or hardware such as home automation can pose risks to its proper functioning. Before using these functions, make sure that your boiler has safety functions and that they are working. Turning on a boiler if all the taps are closed can generate excess pressure, for example.
|
> Controlling a central boiler using software or hardware such as home automation can pose risks to its proper functioning. Before using these functions, make sure that your boiler has safety functions and that they are working. Turning on a boiler if all the taps are closed can generate excess pressure, for example.
|
||||||
|
|
||||||
</details>
|
|
||||||
|
## Expert Mode Settings
|
||||||
|
|
||||||
|
Expert Mode settings refer to Settings made in the Home Assistant `configuration.yaml` file under the `versatile_thermostat` section. You might have to add this section by yourself to the `configuration.yaml` file.
|
||||||
|
|
||||||
|
These settings are meant to be used only in **specific niche cases and with careful considerations**.
|
||||||
|
|
||||||
|
|
||||||
|
The following sections describe the available export mode settings in detail with examples on how to configure them. Be aware that these settings require a **complete restart** of Home Assistant or a **reload of Versatile Thermostat integration** (Dev tools / Yaml / reloading the configuration / Versatile Thermostat) to take effect.
|
||||||
|
|
||||||
|
|
||||||
|
### Self-regulation in Expert mode
|
||||||
|
|
||||||
|
In **Expert** mode you can finely adjust the auto-regulation parameters to achieve your objectives and optimize as best as possible. The algorithm calculates the difference between the setpoint and the actual temperature of the room. This discrepancy is called error.
|
||||||
|
The adjustable parameters are as follows:
|
||||||
|
1. `kp`: the factor applied to the raw error,
|
||||||
|
2. `ki`: the factor applied to the accumulation of errors,
|
||||||
|
3. `k_ext`: the factor applied to the difference between the interior temperature and the exterior temperature,
|
||||||
|
4. `offset_max`: the maximum correction (offset) that the regulation can apply,
|
||||||
|
5. `stabilization_threshold`: a stabilization threshold which, when reached by the error, resets the accumulation of errors to 0,
|
||||||
|
6. `accumulated_error_threshold`: the maximum for error accumulation.
|
||||||
|
|
||||||
|
For tuning, these observations must be taken into account:
|
||||||
|
1. `kp * error` will give the offset linked to the raw error. This offset is directly proportional to the error and will be 0 when the target is reached,
|
||||||
|
2. the accumulation of the error makes it possible to correct the stabilization of the curve while there remains an error. The error accumulates and the offset therefore gradually increases which should eventually stabilize at the target temperature. For this fundamental parameter to have an effect it must not be too small. An average value is 30
|
||||||
|
3. `ki * accumulated_error_threshold` will give the maximum offset linked to the accumulation of the error,
|
||||||
|
4. `k_ext` allows a correction to be applied immediately (without waiting for errors to accumulate) when the outside temperature is very different from the target temperature. If the stabilization is done too high when the temperature differences are significant, it is because this parameter is too high. It should be possible to cancel completely to let the first 2 offsets take place
|
||||||
|
|
||||||
|
The pre-programmed values are as follows:
|
||||||
|
|
||||||
|
Slow régulation :
|
||||||
|
|
||||||
|
kp: 0.2 # 20% of the current internal regulation offset are caused by the current difference of target temperature and room temperature
|
||||||
|
ki: 0.8 / 288.0 # 80% of the current internal regulation offset are caused by the average offset of the past 24 hours
|
||||||
|
k_ext: 1.0 / 25.0 # this will add 1°C to the offset when it's 25°C colder outdoor than indoor
|
||||||
|
offset_max: 2.0 # limit to a final offset of -2°C to +2°C
|
||||||
|
stabilization_threshold: 0.0 # this needs to be disabled as otherwise the long term accumulated error will always be reset when the temp briefly crosses from/to below/above the target
|
||||||
|
accumulated_error_threshold: 2.0 * 288 # this allows up to 2°C long term offset in both directions
|
||||||
|
|
||||||
|
Light régulation :
|
||||||
|
|
||||||
|
kp: 0.2
|
||||||
|
ki: 0.05
|
||||||
|
k_ext: 0.05
|
||||||
|
offset_max: 1.5
|
||||||
|
stabilization_threshold: 0.1
|
||||||
|
accumulated_error_threshold: 10
|
||||||
|
|
||||||
|
Medium régulation :
|
||||||
|
|
||||||
|
kp: 0.3
|
||||||
|
ki: 0.05
|
||||||
|
k_ext: 0.1
|
||||||
|
offset_max: 2
|
||||||
|
stabilization_threshold: 0.1
|
||||||
|
accumulated_error_threshold: 20
|
||||||
|
|
||||||
|
Strong régulation :
|
||||||
|
|
||||||
|
"""Strong parameters for regulation
|
||||||
|
A set of parameters which doesn't take into account the external temp
|
||||||
|
and concentrate to internal temp error + accumulated error.
|
||||||
|
This should work for cold external conditions which else generates
|
||||||
|
high external_offset"""
|
||||||
|
|
||||||
|
kp: 0.4
|
||||||
|
ki: 0.08
|
||||||
|
k_ext: 0.0
|
||||||
|
offset_max: 5
|
||||||
|
stabilization_threshold: 0.1
|
||||||
|
accumulated_error_threshold: 50
|
||||||
|
|
||||||
|
To use Expert mode you must declare the values you want to use for each of these parameters in your `configuration.yaml` in the following form:
|
||||||
|
```
|
||||||
|
versatile_thermostat:
|
||||||
|
auto_regulation_expert:
|
||||||
|
kp: 0.4
|
||||||
|
ki: 0.08
|
||||||
|
k_ext: 0.0
|
||||||
|
offset_max: 5
|
||||||
|
stabilization_threshold: 0.1
|
||||||
|
accumulated_error_threshold: 50
|
||||||
|
```
|
||||||
|
and of course, configure the VTherm's self-regulation mode in **Expert** mode. All VTherms in Expert mode will use these same settings.
|
||||||
|
|
||||||
|
For the changes to be taken into account, you must either **completely restart Home Assistant** or just the **Versatile Thermostat integration** (Dev tools / Yaml / reloading the configuration / Versatile Thermostat).
|
||||||
|
|
||||||
|
|
||||||
|
### On Time Clamping (max_on_percent)
|
||||||
|
|
||||||
|
|
||||||
|
The calculated on time percent can be limited to a maximum percentage of the cycle duration. This setting has to be made in expert mode and will be used for all Versatile Thermostats.
|
||||||
|
|
||||||
|
```
|
||||||
|
versatile_thermostat:
|
||||||
|
max_on_percent: 0.8
|
||||||
|
```
|
||||||
|
|
||||||
|
The example above limits the maximum ON time to 80% (0.8) of the cycle length. If the cycle length is for example 600 seconds (10min), the maximum ON time will be limited to 480 seconds (8min). The remaining 120 seconds of the cycle will always remain in the OFF state.
|
||||||
|
|
||||||
|
There are three debug attributes of interest regarding this feature:
|
||||||
|
|
||||||
|
* `max_on_percent` # clamping setting as configured in expert mode
|
||||||
|
* `calculated_on_percent` # calculated on percent without clamping applied
|
||||||
|
* `on_percent` # used on percent with clamping applied
|
||||||
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Parameter summary</summary>
|
<summary>Parameter summary</summary>
|
||||||
@@ -900,6 +896,8 @@ context:
|
|||||||
| ``central_boiler_activation_service`` | Activation service of the boiler | - | - | - | X |
|
| ``central_boiler_activation_service`` | Activation service of the boiler | - | - | - | X |
|
||||||
| ``central_boiler_deactivation_service`` | Deactivaiton service of the boiler | - | - | - | X |
|
| ``central_boiler_deactivation_service`` | Deactivaiton service of the boiler | - | - | - | X |
|
||||||
| ``used_by_controls_central_boiler`` | Indicate if the VTherm control the central boiler | X | X | X | - |
|
| ``used_by_controls_central_boiler`` | Indicate if the VTherm control the central boiler | X | X | X | - |
|
||||||
|
| ``use_auto_start_stop_feature`` | Indique si la fonction de démarrage/extinction automatique est activée | - | X | - | - |
|
||||||
|
| ``auto_start_stop_lvel`` | Le niveau de détection de l'auto start/stop | - | X | - | - |
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
# Tuning examples
|
# Tuning examples
|
||||||
@@ -1155,6 +1153,9 @@ Custom attributes are the following:
|
|||||||
| ``is_controlled_by_central_mode`` | True if the VTherm can be centrally controlled |
|
| ``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) |
|
| ``last_central_mode`` | The last central mode used (None if the VTherm is not centrally controlled) |
|
||||||
| ``is_used_by_central_boiler`` | Indicate if the VTherm can control the central boiler |
|
| ``is_used_by_central_boiler`` | Indicate if the VTherm can control the central boiler |
|
||||||
|
| ``auto_start_stop_enable`` | Indicate if the VTherm is allowed to do auto start and stop |
|
||||||
|
| ``auto_start_stop_level`` | Give the level of auto start/stop |
|
||||||
|
| ``hvac_off_reason`` | Give the reason of stop of the VTherm. This could be Window, Auto-start/stop or Manual |
|
||||||
|
|
||||||
# Some results
|
# Some results
|
||||||
|
|
||||||
@@ -1333,7 +1334,7 @@ Example of graph obtained with Plotly :
|
|||||||
|
|
||||||
|
|
||||||
## And always better and better with the NOTIFIER daemon app to notify events
|
## 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.
|
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.
|
||||||
|
|
||||||
This is a great example of using the notifications described here [notification](#notifications).
|
This is a great example of using the notifications described here [notification](#notifications).
|
||||||
|
|
||||||
@@ -1576,7 +1577,7 @@ These parameters are sensitive and quite difficult to adjust. Please only use th
|
|||||||
|
|
||||||
## Why does my Versatile Thermostat go into Safety?
|
## Why does my Versatile Thermostat go into Safety?
|
||||||
|
|
||||||
Safety mode is only possible on VTherm `over_switch` and `over_valve`. It occurs when one of the 2 thermometers which gives the room temperature or the outside temperature has not sent a value for more than `security_delay_min` minutes and the radiator was heating at least `security_min_on_percent`.
|
Safety mode is possible on all VTherm's type. It occurs when one of the 2 thermometers which gives the room temperature or the outside temperature has not sent a value for more than `security_delay_min` minutes and the radiator was heating at least `security_min_on_percent`.
|
||||||
|
|
||||||
As the algorithm is based on temperature measurements, if they are no longer received by the VTherm, there is a risk of overheating and fire. To avoid this, when the conditions mentioned above are detected, heating is limited to the `security_default_on_percent` parameter. This value must therefore be reasonably low. It helps prevent a fire while avoiding completely cutting off the radiator (risk of freezing).
|
As the algorithm is based on temperature measurements, if they are no longer received by the VTherm, there is a risk of overheating and fire. To avoid this, when the conditions mentioned above are detected, heating is limited to the `security_default_on_percent` parameter. This value must therefore be reasonably low. It helps prevent a fire while avoiding completely cutting off the radiator (risk of freezing).
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,23 @@ from .const import (
|
|||||||
CONF_USE_CENTRAL_BOILER_FEATURE,
|
CONF_USE_CENTRAL_BOILER_FEATURE,
|
||||||
CONF_POWER_SENSOR,
|
CONF_POWER_SENSOR,
|
||||||
CONF_PRESENCE_SENSOR,
|
CONF_PRESENCE_SENSOR,
|
||||||
|
CONF_UNDERLYING_LIST,
|
||||||
|
CONF_HEATER,
|
||||||
|
CONF_HEATER_2,
|
||||||
|
CONF_HEATER_3,
|
||||||
|
CONF_HEATER_4,
|
||||||
|
CONF_CLIMATE,
|
||||||
|
CONF_CLIMATE_2,
|
||||||
|
CONF_CLIMATE_3,
|
||||||
|
CONF_CLIMATE_4,
|
||||||
|
CONF_VALVE,
|
||||||
|
CONF_VALVE_2,
|
||||||
|
CONF_VALVE_3,
|
||||||
|
CONF_VALVE_4,
|
||||||
|
CONF_THERMOSTAT_SWITCH,
|
||||||
|
CONF_THERMOSTAT_CLIMATE,
|
||||||
|
CONF_THERMOSTAT_VALVE,
|
||||||
|
CONF_MAX_ON_PERCENT,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .vtherm_api import VersatileThermostatAPI
|
from .vtherm_api import VersatileThermostatAPI
|
||||||
@@ -70,6 +87,7 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
CONF_AUTO_REGULATION_EXPERT: vol.Schema(SELF_REGULATION_PARAM_SCHEMA),
|
CONF_AUTO_REGULATION_EXPERT: vol.Schema(SELF_REGULATION_PARAM_SCHEMA),
|
||||||
CONF_SHORT_EMA_PARAMS: vol.Schema(EMA_PARAM_SCHEMA),
|
CONF_SHORT_EMA_PARAMS: vol.Schema(EMA_PARAM_SCHEMA),
|
||||||
CONF_SAFETY_MODE: vol.Schema(SAFETY_MODE_PARAM_SCHEMA),
|
CONF_SAFETY_MODE: vol.Schema(SAFETY_MODE_PARAM_SCHEMA),
|
||||||
|
vol.Optional(CONF_MAX_ON_PERCENT): vol.Coerce(float),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -162,13 +180,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
if hass.state == CoreState.running:
|
if hass.state == CoreState.running:
|
||||||
await api.reload_central_boiler_entities_list()
|
await api.reload_central_boiler_entities_list()
|
||||||
await api.init_vtherm_links()
|
await api.init_vtherm_links(entry.entry_id)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||||
"""Update listener."""
|
"""Update listener."""
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Calling update_listener entry: entry_id='%s', value='%s'",
|
||||||
|
entry.entry_id,
|
||||||
|
entry.data,
|
||||||
|
)
|
||||||
|
|
||||||
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CENTRAL_CONFIG:
|
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CENTRAL_CONFIG:
|
||||||
await reload_all_vtherm(hass)
|
await reload_all_vtherm(hass)
|
||||||
else:
|
else:
|
||||||
@@ -177,7 +202,7 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|||||||
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass)
|
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass)
|
||||||
if api is not None:
|
if api is not None:
|
||||||
await api.reload_central_boiler_entities_list()
|
await api.reload_central_boiler_entities_list()
|
||||||
await api.init_vtherm_links()
|
await api.init_vtherm_links(entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
@@ -208,10 +233,9 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
|||||||
)
|
)
|
||||||
new = {**config_entry.data}
|
new = {**config_entry.data}
|
||||||
|
|
||||||
if (
|
thermostat_type = config_entry.data.get(CONF_THERMOSTAT_TYPE)
|
||||||
config_entry.data.get(CONF_THERMOSTAT_TYPE)
|
|
||||||
== CONF_THERMOSTAT_CENTRAL_CONFIG
|
if thermostat_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
|
||||||
):
|
|
||||||
new[CONF_USE_WINDOW_FEATURE] = True
|
new[CONF_USE_WINDOW_FEATURE] = True
|
||||||
new[CONF_USE_MOTION_FEATURE] = True
|
new[CONF_USE_MOTION_FEATURE] = True
|
||||||
new[CONF_USE_POWER_FEATURE] = new.get(CONF_POWER_SENSOR, None) is not None
|
new[CONF_USE_POWER_FEATURE] = new.get(CONF_POWER_SENSOR, None) is not None
|
||||||
@@ -223,6 +247,50 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
|
|||||||
"add_central_boiler_control", False
|
"add_central_boiler_control", False
|
||||||
) or new.get(CONF_USE_CENTRAL_BOILER_FEATURE, False)
|
) or new.get(CONF_USE_CENTRAL_BOILER_FEATURE, False)
|
||||||
|
|
||||||
|
if config_entry.data.get(CONF_UNDERLYING_LIST, None) is None:
|
||||||
|
underlying_list = []
|
||||||
|
if thermostat_type == CONF_THERMOSTAT_SWITCH:
|
||||||
|
underlying_list = [
|
||||||
|
config_entry.data.get(CONF_HEATER, None),
|
||||||
|
config_entry.data.get(CONF_HEATER_2, None),
|
||||||
|
config_entry.data.get(CONF_HEATER_3, None),
|
||||||
|
config_entry.data.get(CONF_HEATER_4, None),
|
||||||
|
]
|
||||||
|
elif thermostat_type == CONF_THERMOSTAT_CLIMATE:
|
||||||
|
underlying_list = [
|
||||||
|
config_entry.data.get(CONF_CLIMATE, None),
|
||||||
|
config_entry.data.get(CONF_CLIMATE_2, None),
|
||||||
|
config_entry.data.get(CONF_CLIMATE_3, None),
|
||||||
|
config_entry.data.get(CONF_CLIMATE_4, None),
|
||||||
|
]
|
||||||
|
elif thermostat_type == CONF_THERMOSTAT_VALVE:
|
||||||
|
underlying_list = [
|
||||||
|
config_entry.data.get(CONF_VALVE, None),
|
||||||
|
config_entry.data.get(CONF_VALVE_2, None),
|
||||||
|
config_entry.data.get(CONF_VALVE_3, None),
|
||||||
|
config_entry.data.get(CONF_VALVE_4, None),
|
||||||
|
]
|
||||||
|
|
||||||
|
new[CONF_UNDERLYING_LIST] = [
|
||||||
|
entity for entity in underlying_list if entity is not None
|
||||||
|
]
|
||||||
|
|
||||||
|
for key in [
|
||||||
|
CONF_HEATER,
|
||||||
|
CONF_HEATER_2,
|
||||||
|
CONF_HEATER_3,
|
||||||
|
CONF_HEATER_4,
|
||||||
|
CONF_CLIMATE,
|
||||||
|
CONF_CLIMATE_2,
|
||||||
|
CONF_CLIMATE_3,
|
||||||
|
CONF_CLIMATE_4,
|
||||||
|
CONF_VALVE,
|
||||||
|
CONF_VALVE_2,
|
||||||
|
CONF_VALVE_3,
|
||||||
|
CONF_VALVE_4,
|
||||||
|
]:
|
||||||
|
new.pop(key, None)
|
||||||
|
|
||||||
hass.config_entries.async_update_entry(
|
hass.config_entries.async_update_entry(
|
||||||
config_entry,
|
config_entry,
|
||||||
data=new,
|
data=new,
|
||||||
|
|||||||
@@ -0,0 +1,239 @@
|
|||||||
|
# pylint: disable=line-too-long
|
||||||
|
""" This file implements the Auto start/stop algorithm as described here: https://github.com/jmcollin78/versatile_thermostat/issues/585
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
from homeassistant.components.climate import HVACMode
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
AUTO_START_STOP_LEVEL_NONE,
|
||||||
|
AUTO_START_STOP_LEVEL_FAST,
|
||||||
|
AUTO_START_STOP_LEVEL_MEDIUM,
|
||||||
|
AUTO_START_STOP_LEVEL_SLOW,
|
||||||
|
TYPE_AUTO_START_STOP_LEVELS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Some constant to make algorithm depending of level
|
||||||
|
DT_MIN = {
|
||||||
|
AUTO_START_STOP_LEVEL_NONE: 0, # Not used
|
||||||
|
AUTO_START_STOP_LEVEL_SLOW: 30,
|
||||||
|
AUTO_START_STOP_LEVEL_MEDIUM: 15,
|
||||||
|
AUTO_START_STOP_LEVEL_FAST: 7,
|
||||||
|
}
|
||||||
|
|
||||||
|
# the measurement cycle (2 min)
|
||||||
|
CYCLE_SEC = 120
|
||||||
|
|
||||||
|
# A temp hysteresis to avoid rapid OFF/ON
|
||||||
|
TEMP_HYSTERESIS = 0.5
|
||||||
|
|
||||||
|
ERROR_THRESHOLD = {
|
||||||
|
AUTO_START_STOP_LEVEL_NONE: 0, # Not used
|
||||||
|
AUTO_START_STOP_LEVEL_SLOW: 10, # 10 cycle above 1° or 5 cycle above 2°, ...
|
||||||
|
AUTO_START_STOP_LEVEL_MEDIUM: 5, # 5 cycle above 1° or 3 cycle above 2°, ..., 1 cycle above 5°
|
||||||
|
AUTO_START_STOP_LEVEL_FAST: 2, # 2 cycle above 1° or 1 cycle above 2°
|
||||||
|
}
|
||||||
|
|
||||||
|
AUTO_START_STOP_ACTION_OFF = "turnOff"
|
||||||
|
AUTO_START_STOP_ACTION_ON = "turnOn"
|
||||||
|
AUTO_START_STOP_ACTION_NOTHING = "nothing"
|
||||||
|
AUTO_START_STOP_ACTIONS = Literal[ # pylint: disable=invalid-name
|
||||||
|
AUTO_START_STOP_ACTION_OFF,
|
||||||
|
AUTO_START_STOP_ACTION_ON,
|
||||||
|
AUTO_START_STOP_ACTION_NOTHING,
|
||||||
|
]
|
||||||
|
|
||||||
|
class AutoStartStopDetectionAlgorithm:
|
||||||
|
"""The class that implements the algorithm listed above"""
|
||||||
|
|
||||||
|
_dt: float | None = None
|
||||||
|
_level: str = AUTO_START_STOP_LEVEL_NONE
|
||||||
|
_accumulated_error: float = 0
|
||||||
|
_error_threshold: float | None = None
|
||||||
|
_last_calculation_date: datetime | None = None
|
||||||
|
|
||||||
|
def __init__(self, level: TYPE_AUTO_START_STOP_LEVELS, vtherm_name) -> None:
|
||||||
|
"""Initalize a new algorithm with the right constants"""
|
||||||
|
self._vtherm_name = vtherm_name
|
||||||
|
self._init_level(level)
|
||||||
|
|
||||||
|
def _init_level(self, level: TYPE_AUTO_START_STOP_LEVELS):
|
||||||
|
"""Initialize a new level"""
|
||||||
|
if level == self._level:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._level = level
|
||||||
|
if self._level != AUTO_START_STOP_LEVEL_NONE:
|
||||||
|
self._dt = DT_MIN[level]
|
||||||
|
self._error_threshold = ERROR_THRESHOLD[level]
|
||||||
|
# reset accumulated error if we change the level
|
||||||
|
self._accumulated_error = 0
|
||||||
|
|
||||||
|
def calculate_action(
|
||||||
|
self,
|
||||||
|
hvac_mode: HVACMode | None,
|
||||||
|
saved_hvac_mode: HVACMode | None,
|
||||||
|
target_temp: float,
|
||||||
|
current_temp: float,
|
||||||
|
slope_min: float | None,
|
||||||
|
now: datetime,
|
||||||
|
) -> AUTO_START_STOP_ACTIONS:
|
||||||
|
"""Calculate an eventual action to do depending of the value in parameter"""
|
||||||
|
if self._level == AUTO_START_STOP_LEVEL_NONE:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - auto-start/stop is disabled",
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return AUTO_START_STOP_ACTION_NOTHING
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - calculate_action: hvac_mode=%s, saved_hvac_mode=%s, target_temp=%s, current_temp=%s, slope_min=%s at %s",
|
||||||
|
self,
|
||||||
|
hvac_mode,
|
||||||
|
saved_hvac_mode,
|
||||||
|
target_temp,
|
||||||
|
current_temp,
|
||||||
|
slope_min,
|
||||||
|
now,
|
||||||
|
)
|
||||||
|
|
||||||
|
if hvac_mode is None or target_temp is None or current_temp is None:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - No all mandatory parameters are set. Disable auto-start/stop",
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return AUTO_START_STOP_ACTION_NOTHING
|
||||||
|
|
||||||
|
# Calculate the error factor (P)
|
||||||
|
error = target_temp - current_temp
|
||||||
|
|
||||||
|
# reduce the error considering the dt between the last measurement
|
||||||
|
if self._last_calculation_date is not None:
|
||||||
|
dtmin = (now - self._last_calculation_date).total_seconds() / CYCLE_SEC
|
||||||
|
# ignore two calls too near (< 24 sec)
|
||||||
|
if dtmin <= 0.2:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - new calculation of auto_start_stop (%s) is too near of the last one (%s). Forget it",
|
||||||
|
self,
|
||||||
|
now,
|
||||||
|
self._last_calculation_date,
|
||||||
|
)
|
||||||
|
return AUTO_START_STOP_ACTION_NOTHING
|
||||||
|
error = error * dtmin
|
||||||
|
|
||||||
|
# If the error have change its sign, reset smoothly the accumulated error
|
||||||
|
if error * self._accumulated_error < 0:
|
||||||
|
self._accumulated_error = self._accumulated_error / 2.0
|
||||||
|
|
||||||
|
self._accumulated_error += error
|
||||||
|
|
||||||
|
# Capping of the error
|
||||||
|
self._accumulated_error = min(
|
||||||
|
self._error_threshold,
|
||||||
|
max(-self._error_threshold, self._accumulated_error),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._last_calculation_date = now
|
||||||
|
|
||||||
|
temp_at_dt = current_temp + slope_min * self._dt
|
||||||
|
|
||||||
|
# Check to turn-off
|
||||||
|
# When we hit the threshold, that mean we can turn off
|
||||||
|
if hvac_mode == HVACMode.HEAT:
|
||||||
|
if (
|
||||||
|
self._accumulated_error <= -self._error_threshold
|
||||||
|
and temp_at_dt >= target_temp + TEMP_HYSTERESIS
|
||||||
|
):
|
||||||
|
_LOGGER.info(
|
||||||
|
"%s - We need to stop, there is no need for heating for a long time.",
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return AUTO_START_STOP_ACTION_OFF
|
||||||
|
else:
|
||||||
|
_LOGGER.debug("%s - nothing to do, we are heating", self)
|
||||||
|
return AUTO_START_STOP_ACTION_NOTHING
|
||||||
|
|
||||||
|
if hvac_mode == HVACMode.COOL:
|
||||||
|
if (
|
||||||
|
self._accumulated_error >= self._error_threshold
|
||||||
|
and temp_at_dt <= target_temp - TEMP_HYSTERESIS
|
||||||
|
):
|
||||||
|
_LOGGER.info(
|
||||||
|
"%s - We need to stop, there is no need for cooling for a long time.",
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return AUTO_START_STOP_ACTION_OFF
|
||||||
|
else:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - nothing to do, we are cooling",
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return AUTO_START_STOP_ACTION_NOTHING
|
||||||
|
|
||||||
|
# check to turn on
|
||||||
|
if hvac_mode == HVACMode.OFF and saved_hvac_mode == HVACMode.HEAT:
|
||||||
|
if temp_at_dt <= target_temp - TEMP_HYSTERESIS:
|
||||||
|
_LOGGER.info(
|
||||||
|
"%s - We need to start, because it will be time to heat",
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return AUTO_START_STOP_ACTION_ON
|
||||||
|
else:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - nothing to do, we don't need to heat soon",
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return AUTO_START_STOP_ACTION_NOTHING
|
||||||
|
|
||||||
|
if hvac_mode == HVACMode.OFF and saved_hvac_mode == HVACMode.COOL:
|
||||||
|
if temp_at_dt >= target_temp + TEMP_HYSTERESIS:
|
||||||
|
_LOGGER.info(
|
||||||
|
"%s - We need to start, because it will be time to cool",
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return AUTO_START_STOP_ACTION_ON
|
||||||
|
else:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - nothing to do, we don't need to cool soon",
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return AUTO_START_STOP_ACTION_NOTHING
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - nothing to do, no conditions applied",
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
return AUTO_START_STOP_ACTION_NOTHING
|
||||||
|
|
||||||
|
def set_level(self, level: TYPE_AUTO_START_STOP_LEVELS):
|
||||||
|
"""Set a new level"""
|
||||||
|
self._init_level(level)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dt_min(self) -> float:
|
||||||
|
"""Get the dt value"""
|
||||||
|
return self._dt
|
||||||
|
|
||||||
|
@property
|
||||||
|
def accumulated_error(self) -> float:
|
||||||
|
"""Get the accumulated error value"""
|
||||||
|
return self._accumulated_error
|
||||||
|
|
||||||
|
@property
|
||||||
|
def accumulated_error_threshold(self) -> float:
|
||||||
|
"""Get the accumulated error threshold value"""
|
||||||
|
return self._error_threshold
|
||||||
|
|
||||||
|
@property
|
||||||
|
def level(self) -> TYPE_AUTO_START_STOP_LEVELS:
|
||||||
|
"""Get the level value"""
|
||||||
|
return self._level
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"AutoStartStopDetectionAlgorithm-{self._vtherm_name}"
|
||||||
@@ -19,7 +19,10 @@ from homeassistant.core import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from homeassistant.components.climate import ClimateEntity
|
from homeassistant.components.climate import ClimateEntity
|
||||||
from homeassistant.helpers.restore_state import RestoreEntity
|
from homeassistant.helpers.restore_state import (
|
||||||
|
RestoreEntity,
|
||||||
|
async_get as restore_async_get,
|
||||||
|
)
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
|
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
|
||||||
@@ -62,72 +65,7 @@ from homeassistant.const import (
|
|||||||
STATE_NOT_HOME,
|
STATE_NOT_HOME,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .const import (
|
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
DOMAIN,
|
|
||||||
DEVICE_MANUFACTURER,
|
|
||||||
CONF_POWER_SENSOR,
|
|
||||||
CONF_TEMP_SENSOR,
|
|
||||||
CONF_LAST_SEEN_TEMP_SENSOR,
|
|
||||||
CONF_EXTERNAL_TEMP_SENSOR,
|
|
||||||
CONF_MAX_POWER_SENSOR,
|
|
||||||
CONF_WINDOW_SENSOR,
|
|
||||||
CONF_WINDOW_DELAY,
|
|
||||||
CONF_WINDOW_AUTO_CLOSE_THRESHOLD,
|
|
||||||
CONF_WINDOW_AUTO_OPEN_THRESHOLD,
|
|
||||||
CONF_WINDOW_AUTO_MAX_DURATION,
|
|
||||||
CONF_MOTION_SENSOR,
|
|
||||||
CONF_MOTION_DELAY,
|
|
||||||
CONF_MOTION_OFF_DELAY,
|
|
||||||
CONF_MOTION_PRESET,
|
|
||||||
CONF_NO_MOTION_PRESET,
|
|
||||||
CONF_DEVICE_POWER,
|
|
||||||
CONF_PRESETS,
|
|
||||||
# CONF_PRESETS_AWAY,
|
|
||||||
# CONF_PRESETS_WITH_AC,
|
|
||||||
# CONF_PRESETS_AWAY_WITH_AC,
|
|
||||||
CONF_CYCLE_MIN,
|
|
||||||
CONF_PROP_FUNCTION,
|
|
||||||
CONF_TPI_COEF_INT,
|
|
||||||
CONF_TPI_COEF_EXT,
|
|
||||||
CONF_PRESENCE_SENSOR,
|
|
||||||
CONF_PRESET_POWER,
|
|
||||||
SUPPORT_FLAGS,
|
|
||||||
PRESET_FROST_PROTECTION,
|
|
||||||
PRESET_POWER,
|
|
||||||
PRESET_SECURITY,
|
|
||||||
PROPORTIONAL_FUNCTION_TPI,
|
|
||||||
PRESET_AWAY_SUFFIX,
|
|
||||||
CONF_SECURITY_DELAY_MIN,
|
|
||||||
CONF_SECURITY_MIN_ON_PERCENT,
|
|
||||||
CONF_SECURITY_DEFAULT_ON_PERCENT,
|
|
||||||
DEFAULT_SECURITY_MIN_ON_PERCENT,
|
|
||||||
DEFAULT_SECURITY_DEFAULT_ON_PERCENT,
|
|
||||||
CONF_MINIMAL_ACTIVATION_DELAY,
|
|
||||||
CONF_USE_MAIN_CENTRAL_CONFIG,
|
|
||||||
CONF_USE_TPI_CENTRAL_CONFIG,
|
|
||||||
CONF_USE_PRESETS_CENTRAL_CONFIG,
|
|
||||||
CONF_USE_WINDOW_CENTRAL_CONFIG,
|
|
||||||
CONF_USE_MOTION_CENTRAL_CONFIG,
|
|
||||||
CONF_USE_POWER_CENTRAL_CONFIG,
|
|
||||||
CONF_USE_PRESENCE_CENTRAL_CONFIG,
|
|
||||||
CONF_USE_ADVANCED_CENTRAL_CONFIG,
|
|
||||||
CONF_USE_PRESENCE_FEATURE,
|
|
||||||
CONF_TEMP_MAX,
|
|
||||||
CONF_TEMP_MIN,
|
|
||||||
HIDDEN_PRESETS,
|
|
||||||
CONF_AC_MODE,
|
|
||||||
EventType,
|
|
||||||
ATTR_MEAN_POWER_CYCLE,
|
|
||||||
ATTR_TOTAL_ENERGY,
|
|
||||||
PRESET_AC_SUFFIX,
|
|
||||||
DEFAULT_SHORT_EMA_PARAMS,
|
|
||||||
CENTRAL_MODE_AUTO,
|
|
||||||
CENTRAL_MODE_STOPPED,
|
|
||||||
CENTRAL_MODE_HEAT_ONLY,
|
|
||||||
CENTRAL_MODE_COOL_ONLY,
|
|
||||||
CENTRAL_MODE_FROST_PROTECTION,
|
|
||||||
send_vtherm_event,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .config_schema import * # pylint: disable=wildcard-import, unused-wildcard-import
|
from .config_schema import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
|
|
||||||
@@ -199,6 +137,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
"is_device_active",
|
"is_device_active",
|
||||||
"target_temperature_step",
|
"target_temperature_step",
|
||||||
"is_used_by_central_boiler",
|
"is_used_by_central_boiler",
|
||||||
|
"temperature_slope",
|
||||||
|
"max_on_percent"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -262,6 +202,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
self._attr_translation_key = "versatile_thermostat"
|
self._attr_translation_key = "versatile_thermostat"
|
||||||
|
|
||||||
self._total_energy = None
|
self._total_energy = None
|
||||||
|
_LOGGER.debug("%s - _init_ resetting energy to None", self)
|
||||||
|
|
||||||
# because energy of climate is calculated in the thermostat we have to keep that here and not in underlying entity
|
# because energy of climate is calculated in the thermostat we have to keep that here and not in underlying entity
|
||||||
self._underlying_climate_start_hvac_action_date = None
|
self._underlying_climate_start_hvac_action_date = None
|
||||||
@@ -303,6 +244,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
|
|
||||||
self._use_central_config_temperature = False
|
self._use_central_config_temperature = False
|
||||||
|
|
||||||
|
self._hvac_off_reason: HVAC_OFF_REASONS | None = None
|
||||||
|
|
||||||
self.post_init(entry_infos)
|
self.post_init(entry_infos)
|
||||||
|
|
||||||
def clean_central_config_doublon(
|
def clean_central_config_doublon(
|
||||||
@@ -532,6 +475,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
self._presence_state = None
|
self._presence_state = None
|
||||||
|
|
||||||
self._total_energy = None
|
self._total_energy = None
|
||||||
|
_LOGGER.debug("%s - post_init_ resetting energy to None", self)
|
||||||
|
|
||||||
# Read the parameter from configuration.yaml if it exists
|
# Read the parameter from configuration.yaml if it exists
|
||||||
short_ema_params = DEFAULT_SHORT_EMA_PARAMS
|
short_ema_params = DEFAULT_SHORT_EMA_PARAMS
|
||||||
@@ -560,6 +504,8 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
entry_infos.get(CONF_WINDOW_ACTION) or CONF_WINDOW_TURN_OFF
|
entry_infos.get(CONF_WINDOW_ACTION) or CONF_WINDOW_TURN_OFF
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._max_on_percent = api.max_on_percent
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - Creation of a new VersatileThermostat entity: unique_id=%s",
|
"%s - Creation of a new VersatileThermostat entity: unique_id=%s",
|
||||||
self,
|
self,
|
||||||
@@ -647,14 +593,24 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
# issue 428. Link to others entities will start at link
|
# issue 428. Link to others entities will start at link
|
||||||
# await self.async_startup()
|
# await self.async_startup()
|
||||||
|
|
||||||
|
async def async_will_remove_from_hass(self):
|
||||||
|
"""Try to force backup of entity"""
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - force write before remove. Energy is %s", self, self.total_energy
|
||||||
|
)
|
||||||
|
# Force dump in background
|
||||||
|
await restore_async_get(self.hass).async_dump_states()
|
||||||
|
|
||||||
def remove_thermostat(self):
|
def remove_thermostat(self):
|
||||||
"""Called when the thermostat will be removed"""
|
"""Called when the thermostat will be removed"""
|
||||||
_LOGGER.info("%s - Removing thermostat", self)
|
_LOGGER.info("%s - Removing thermostat", self)
|
||||||
|
|
||||||
for under in self._underlyings:
|
for under in self._underlyings:
|
||||||
under.remove_entity()
|
under.remove_entity()
|
||||||
|
|
||||||
async def async_startup(self, central_configuration):
|
async def async_startup(self, central_configuration):
|
||||||
"""Triggered on startup, used to get old state and set internal states accordingly"""
|
"""Triggered on startup, used to get old state and set internal states accordingly. This is triggered by
|
||||||
|
VTherm API"""
|
||||||
_LOGGER.debug("%s - Calling async_startup", self)
|
_LOGGER.debug("%s - Calling async_startup", self)
|
||||||
|
|
||||||
_LOGGER.debug("%s - Calling async_startup_internal", self)
|
_LOGGER.debug("%s - Calling async_startup_internal", self)
|
||||||
@@ -848,18 +804,29 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
else:
|
else:
|
||||||
self._attr_preset_mode = PRESET_NONE
|
self._attr_preset_mode = PRESET_NONE
|
||||||
|
|
||||||
|
# Restore old hvac_off_reason
|
||||||
|
self._hvac_off_reason = old_state.attributes.get(HVAC_OFF_REASON_NAME, None)
|
||||||
|
|
||||||
if old_state.state in [
|
if old_state.state in [
|
||||||
HVACMode.OFF,
|
HVACMode.OFF,
|
||||||
HVACMode.HEAT,
|
HVACMode.HEAT,
|
||||||
HVACMode.COOL,
|
HVACMode.COOL,
|
||||||
]:
|
]:
|
||||||
self._hvac_mode = old_state.state
|
self._hvac_mode = old_state.state
|
||||||
else:
|
|
||||||
if not self._hvac_mode:
|
# restpre also saved info so that window detection will work
|
||||||
self._hvac_mode = HVACMode.OFF
|
self._saved_hvac_mode = old_state.attributes.get("saved_hvac_mode", None)
|
||||||
|
self._saved_preset_mode = old_state.attributes.get(
|
||||||
|
"saved_preset_mode", None
|
||||||
|
)
|
||||||
|
|
||||||
old_total_energy = old_state.attributes.get(ATTR_TOTAL_ENERGY)
|
old_total_energy = old_state.attributes.get(ATTR_TOTAL_ENERGY)
|
||||||
self._total_energy = old_total_energy if old_total_energy else 0
|
self._total_energy = old_total_energy if old_total_energy is not None else 0
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - get_my_previous_state restored energy is %s",
|
||||||
|
self,
|
||||||
|
self._total_energy,
|
||||||
|
)
|
||||||
|
|
||||||
self.restore_specific_previous_state(old_state)
|
self.restore_specific_previous_state(old_state)
|
||||||
else:
|
else:
|
||||||
@@ -873,13 +840,20 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
"No previously saved temperature, setting to %s", self._target_temp
|
"No previously saved temperature, setting to %s", self._target_temp
|
||||||
)
|
)
|
||||||
self._total_energy = 0
|
self._total_energy = 0
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - get_my_previous_state no previous state energy is %s",
|
||||||
|
self,
|
||||||
|
self._total_energy,
|
||||||
|
)
|
||||||
|
|
||||||
self._saved_target_temp = self._target_temp
|
|
||||||
|
|
||||||
# Set default state to off
|
|
||||||
if not self._hvac_mode:
|
if not self._hvac_mode:
|
||||||
self._hvac_mode = HVACMode.OFF
|
self._hvac_mode = HVACMode.OFF
|
||||||
|
|
||||||
|
if not self.is_on and self.hvac_off_reason is None:
|
||||||
|
self.set_hvac_off_reason(HVAC_OFF_REASON_MANUAL)
|
||||||
|
|
||||||
|
self._saved_target_temp = self._target_temp
|
||||||
|
|
||||||
self.send_event(EventType.PRESET_EVENT, {"preset": self._attr_preset_mode})
|
self.send_event(EventType.PRESET_EVENT, {"preset": self._attr_preset_mode})
|
||||||
self.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": self._hvac_mode})
|
self.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": self._hvac_mode})
|
||||||
|
|
||||||
@@ -987,16 +961,6 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
@property
|
@property
|
||||||
def hvac_mode(self) -> HVACMode | None:
|
def hvac_mode(self) -> HVACMode | None:
|
||||||
"""Return current operation."""
|
"""Return current operation."""
|
||||||
# Issue #114 - returns my current hvac_mode and not the underlying hvac_mode which could be different
|
|
||||||
# delta will be managed by climate_state_change event.
|
|
||||||
# if self.is_over_climate:
|
|
||||||
# if one not OFF -> return it
|
|
||||||
# else OFF
|
|
||||||
# for under in self._underlyings:
|
|
||||||
# if (mode := under.hvac_mode) not in [HVACMode.OFF]
|
|
||||||
# return mode
|
|
||||||
# return HVACMode.OFF
|
|
||||||
|
|
||||||
return self._hvac_mode
|
return self._hvac_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -1193,6 +1157,13 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
"""True if this VTHerm uses the central configuration temperature"""
|
"""True if this VTHerm uses the central configuration temperature"""
|
||||||
return self._use_central_config_temperature
|
return self._use_central_config_temperature
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_off_reason(self) -> HVAC_OFF_REASONS:
|
||||||
|
"""Returns the reason of the last switch to HVAC_OFF
|
||||||
|
This is useful for features that turns off the VTherm like
|
||||||
|
window detection or auto-start-stop"""
|
||||||
|
return self._hvac_off_reason
|
||||||
|
|
||||||
def underlying_entity_id(self, index=0) -> str | None:
|
def underlying_entity_id(self, index=0) -> str | None:
|
||||||
"""The climate_entity_id. Added for retrocompatibility reason"""
|
"""The climate_entity_id. Added for retrocompatibility reason"""
|
||||||
if index < self.nb_underlying_entities:
|
if index < self.nb_underlying_entities:
|
||||||
@@ -1234,6 +1205,24 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
if hvac_mode is None:
|
if hvac_mode is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def save_state():
|
||||||
|
self.reset_last_change_time()
|
||||||
|
self.update_custom_attributes()
|
||||||
|
self.async_write_ha_state()
|
||||||
|
self.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": self._hvac_mode})
|
||||||
|
|
||||||
|
# If we already are in OFF, the manual OFF should just overwrite the reason and saved_hvac_mode
|
||||||
|
if self._hvac_mode == HVACMode.OFF and hvac_mode == HVACMode.OFF:
|
||||||
|
_LOGGER.info(
|
||||||
|
"%s - already in OFF. Change the reason to MANUAL and erase the saved_havc_mode"
|
||||||
|
)
|
||||||
|
self._hvac_off_reason = HVAC_OFF_REASON_MANUAL
|
||||||
|
self._saved_hvac_mode = HVACMode.OFF
|
||||||
|
|
||||||
|
save_state()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
self._hvac_mode = hvac_mode
|
self._hvac_mode = hvac_mode
|
||||||
|
|
||||||
# Delegate to all underlying
|
# Delegate to all underlying
|
||||||
@@ -1256,11 +1245,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
# 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.reset_last_temperature_time()
|
||||||
|
|
||||||
self.reset_last_change_time()
|
if self._hvac_mode != HVACMode.OFF:
|
||||||
|
self.set_hvac_off_reason(None)
|
||||||
|
|
||||||
self.update_custom_attributes()
|
save_state()
|
||||||
self.async_write_ha_state()
|
|
||||||
self.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": self._hvac_mode})
|
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
async def async_set_preset_mode(
|
async def async_set_preset_mode(
|
||||||
@@ -1760,6 +1748,19 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
for under in self._underlyings:
|
for under in self._underlyings:
|
||||||
await under.check_initial_state(self._hvac_mode)
|
await under.check_initial_state(self._hvac_mode)
|
||||||
|
|
||||||
|
# Prevent from starting a VTherm if window is open
|
||||||
|
if (
|
||||||
|
self.is_window_auto_enabled
|
||||||
|
and self._window_sensor_entity_id is not None
|
||||||
|
and self._hass.states.is_state(self._window_sensor_entity_id, STATE_ON)
|
||||||
|
and self.is_on
|
||||||
|
and self.window_action == CONF_WINDOW_TURN_OFF
|
||||||
|
):
|
||||||
|
_LOGGER.info("%s - the window is open. Prevent starting the VTherm")
|
||||||
|
self._window_auto_state = True
|
||||||
|
self.save_hvac_mode()
|
||||||
|
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||||
|
|
||||||
# Starts the initial control loop (don't wait for an update of temperature)
|
# Starts the initial control loop (don't wait for an update of temperature)
|
||||||
await self.async_control_heating(force=True)
|
await self.async_control_heating(force=True)
|
||||||
|
|
||||||
@@ -2096,6 +2097,10 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
self._hvac_mode,
|
self._hvac_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def set_hvac_off_reason(self, hvac_off_reason: HVAC_OFF_REASONS):
|
||||||
|
"""Set the reason of hvac_off"""
|
||||||
|
self._hvac_off_reason = hvac_off_reason
|
||||||
|
|
||||||
async def restore_hvac_mode(self, need_control_heating=False):
|
async def restore_hvac_mode(self, need_control_heating=False):
|
||||||
"""Restore a previous hvac_mod"""
|
"""Restore a previous hvac_mod"""
|
||||||
await self.async_set_hvac_mode(self._saved_hvac_mode, need_control_heating)
|
await self.async_set_hvac_mode(self._saved_hvac_mode, need_control_heating)
|
||||||
@@ -2227,27 +2232,34 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
if self.window_state is not STATE_ON and not first_init:
|
if self.window_state is not STATE_ON and not first_init:
|
||||||
await self.restore_hvac_mode()
|
await self.restore_hvac_mode()
|
||||||
await self.restore_preset_mode()
|
await self.restore_preset_mode()
|
||||||
|
elif self.window_state is STATE_ON and self.hvac_mode == HVACMode.OFF:
|
||||||
|
# do not restore but mark the reason of off with window detection
|
||||||
|
self.set_hvac_off_reason(HVAC_OFF_REASON_WINDOW_DETECTION)
|
||||||
return
|
return
|
||||||
|
|
||||||
if old_central_mode == CENTRAL_MODE_AUTO and self.window_state is not STATE_ON:
|
if old_central_mode == CENTRAL_MODE_AUTO and self.window_state is not STATE_ON:
|
||||||
save_all()
|
save_all()
|
||||||
|
|
||||||
if new_central_mode == CENTRAL_MODE_STOPPED:
|
if new_central_mode == CENTRAL_MODE_STOPPED:
|
||||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
if self.hvac_mode != HVACMode.OFF:
|
||||||
|
self.set_hvac_off_reason(HVAC_OFF_REASON_MANUAL)
|
||||||
|
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||||
return
|
return
|
||||||
|
|
||||||
if new_central_mode == CENTRAL_MODE_COOL_ONLY:
|
if new_central_mode == CENTRAL_MODE_COOL_ONLY:
|
||||||
if HVACMode.COOL in self.hvac_modes:
|
if HVACMode.COOL in self.hvac_modes:
|
||||||
await self.async_set_hvac_mode(HVACMode.COOL)
|
await self.async_set_hvac_mode(HVACMode.COOL)
|
||||||
else:
|
else:
|
||||||
|
self.set_hvac_off_reason(HVAC_OFF_REASON_MANUAL)
|
||||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||||
return
|
return
|
||||||
|
|
||||||
if new_central_mode == CENTRAL_MODE_HEAT_ONLY:
|
if new_central_mode == CENTRAL_MODE_HEAT_ONLY:
|
||||||
if HVACMode.HEAT in self.hvac_modes:
|
if HVACMode.HEAT in self.hvac_modes:
|
||||||
await self.async_set_hvac_mode(HVACMode.HEAT)
|
await self.async_set_hvac_mode(HVACMode.HEAT)
|
||||||
else:
|
# if not already off
|
||||||
|
elif self.hvac_mode != HVACMode.OFF:
|
||||||
|
self.set_hvac_off_reason(HVAC_OFF_REASON_MANUAL)
|
||||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2261,6 +2273,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
PRESET_FROST_PROTECTION, overwrite_saved_preset=False
|
PRESET_FROST_PROTECTION, overwrite_saved_preset=False
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
self.set_hvac_off_reason(HVAC_OFF_REASON_MANUAL)
|
||||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2440,17 +2453,27 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
"""Change the window detection state.
|
"""Change the window detection state.
|
||||||
new_state is on if an open window have been detected or off else
|
new_state is on if an open window have been detected or off else
|
||||||
"""
|
"""
|
||||||
if not new_state:
|
if new_state is False:
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"%s - Window is closed. Restoring hvac_mode '%s' if central_mode is not STOPPED",
|
"%s - Window is closed. Restoring hvac_mode '%s' if stopped by window detection or temperature %s",
|
||||||
self,
|
self,
|
||||||
self._saved_hvac_mode,
|
self._saved_hvac_mode,
|
||||||
|
self._saved_target_temp,
|
||||||
)
|
)
|
||||||
if self._window_action in [CONF_WINDOW_FROST_TEMP, CONF_WINDOW_ECO_TEMP]:
|
if self._window_action in [CONF_WINDOW_FROST_TEMP, CONF_WINDOW_ECO_TEMP]:
|
||||||
await self._async_internal_set_temperature(self._saved_target_temp)
|
await self._async_internal_set_temperature(self._saved_target_temp)
|
||||||
|
|
||||||
# default to TURN_OFF
|
# default to TURN_OFF
|
||||||
elif self._window_action in [CONF_WINDOW_TURN_OFF, CONF_WINDOW_FAN_ONLY]:
|
elif self._window_action in [CONF_WINDOW_TURN_OFF]:
|
||||||
|
if (
|
||||||
|
self.last_central_mode != CENTRAL_MODE_STOPPED
|
||||||
|
and self.hvac_off_reason == HVAC_OFF_REASON_WINDOW_DETECTION
|
||||||
|
):
|
||||||
|
self.set_hvac_off_reason(None)
|
||||||
|
await self.restore_hvac_mode(True)
|
||||||
|
elif self._window_action in [CONF_WINDOW_FAN_ONLY]:
|
||||||
if self.last_central_mode != CENTRAL_MODE_STOPPED:
|
if self.last_central_mode != CENTRAL_MODE_STOPPED:
|
||||||
|
self.set_hvac_off_reason(None)
|
||||||
await self.restore_hvac_mode(True)
|
await self.restore_hvac_mode(True)
|
||||||
else:
|
else:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
@@ -2462,6 +2485,12 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"%s - Window is open. Set hvac_mode to '%s'", self, HVACMode.OFF
|
"%s - Window is open. Set hvac_mode to '%s'", self, HVACMode.OFF
|
||||||
)
|
)
|
||||||
|
if self._window_action == CONF_WINDOW_TURN_OFF and not self.is_on:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s is already off. Forget turning off VTherm due to window detection"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
if self.last_central_mode in [CENTRAL_MODE_AUTO, None]:
|
if self.last_central_mode in [CENTRAL_MODE_AUTO, None]:
|
||||||
if self._window_action in [CONF_WINDOW_TURN_OFF, CONF_WINDOW_FAN_ONLY]:
|
if self._window_action in [CONF_WINDOW_TURN_OFF, CONF_WINDOW_FAN_ONLY]:
|
||||||
self.save_hvac_mode()
|
self.save_hvac_mode()
|
||||||
@@ -2491,6 +2520,7 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
self.find_preset_temp(PRESET_ECO)
|
self.find_preset_temp(PRESET_ECO)
|
||||||
)
|
)
|
||||||
else: # default is to turn_off
|
else: # default is to turn_off
|
||||||
|
self.set_hvac_off_reason(HVAC_OFF_REASON_WINDOW_DETECTION)
|
||||||
await self.async_set_hvac_mode(HVACMode.OFF)
|
await self.async_set_hvac_mode(HVACMode.OFF)
|
||||||
|
|
||||||
async def async_control_heating(self, force=False, _=None) -> bool:
|
async def async_control_heating(self, force=False, _=None) -> bool:
|
||||||
@@ -2633,8 +2663,27 @@ class BaseThermostat(ClimateEntity, RestoreEntity, Generic[T]):
|
|||||||
"is_device_active": self.is_device_active,
|
"is_device_active": self.is_device_active,
|
||||||
"ema_temp": self._ema_temp,
|
"ema_temp": self._ema_temp,
|
||||||
"is_used_by_central_boiler": self.is_used_by_central_boiler,
|
"is_used_by_central_boiler": self.is_used_by_central_boiler,
|
||||||
|
"temperature_slope": round(self.last_temperature_slope or 0, 3),
|
||||||
|
"hvac_off_reason": self.hvac_off_reason,
|
||||||
|
"max_on_percent": self._max_on_percent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - update_custom_attributes saved energy is %s",
|
||||||
|
self,
|
||||||
|
self.total_energy,
|
||||||
|
)
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
def async_write_ha_state(self):
|
||||||
|
"""overrides to have log"""
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - async_write_ha_state written state energy is %s",
|
||||||
|
self,
|
||||||
|
self._total_energy,
|
||||||
|
)
|
||||||
|
return super().async_write_ha_state()
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_registry_entry_updated(self):
|
def async_registry_entry_updated(self):
|
||||||
"""update the entity if the config entry have been updated
|
"""update the entity if the config entry have been updated
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ class SecurityBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
|
|||||||
entry_infos,
|
entry_infos,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the SecurityState Binary sensor"""
|
"""Initialize the SecurityState Binary sensor"""
|
||||||
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
|
super().__init__(hass, unique_id, name)
|
||||||
self._attr_name = "Security state"
|
self._attr_name = "Security state"
|
||||||
self._attr_unique_id = f"{self._device_name}_security_state"
|
self._attr_unique_id = f"{self._device_name}_security_state"
|
||||||
self._attr_is_on = False
|
self._attr_is_on = False
|
||||||
|
|||||||
@@ -37,11 +37,13 @@ from .const import (
|
|||||||
CONF_THERMOSTAT_CLIMATE,
|
CONF_THERMOSTAT_CLIMATE,
|
||||||
CONF_THERMOSTAT_VALVE,
|
CONF_THERMOSTAT_VALVE,
|
||||||
CONF_THERMOSTAT_CENTRAL_CONFIG,
|
CONF_THERMOSTAT_CENTRAL_CONFIG,
|
||||||
|
CONF_SONOFF_TRZB_MODE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .thermostat_switch import ThermostatOverSwitch
|
from .thermostat_switch import ThermostatOverSwitch
|
||||||
from .thermostat_climate import ThermostatOverClimate
|
from .thermostat_climate import ThermostatOverClimate
|
||||||
from .thermostat_valve import ThermostatOverValve
|
from .thermostat_valve import ThermostatOverValve
|
||||||
|
from .thermostat_sonoff_trvzb import ThermostatOverSonoffTRVZB
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -60,6 +62,7 @@ async def async_setup_entry(
|
|||||||
unique_id = entry.entry_id
|
unique_id = entry.entry_id
|
||||||
name = entry.data.get(CONF_NAME)
|
name = entry.data.get(CONF_NAME)
|
||||||
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
|
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
|
||||||
|
is_sonoff_trvzb = entry.data.get(CONF_SONOFF_TRZB_MODE)
|
||||||
|
|
||||||
if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
|
if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
|
||||||
return
|
return
|
||||||
@@ -69,7 +72,10 @@ async def async_setup_entry(
|
|||||||
if vt_type == CONF_THERMOSTAT_SWITCH:
|
if vt_type == CONF_THERMOSTAT_SWITCH:
|
||||||
entity = ThermostatOverSwitch(hass, unique_id, name, entry.data)
|
entity = ThermostatOverSwitch(hass, unique_id, name, entry.data)
|
||||||
elif vt_type == CONF_THERMOSTAT_CLIMATE:
|
elif vt_type == CONF_THERMOSTAT_CLIMATE:
|
||||||
entity = ThermostatOverClimate(hass, unique_id, name, entry.data)
|
if is_sonoff_trvzb is True:
|
||||||
|
entity = ThermostatOverSonoffTRVZB(hass, unique_id, name, entry.data)
|
||||||
|
else:
|
||||||
|
entity = ThermostatOverClimate(hass, unique_id, name, entry.data)
|
||||||
elif vt_type == CONF_THERMOSTAT_VALVE:
|
elif vt_type == CONF_THERMOSTAT_VALVE:
|
||||||
entity = ThermostatOverValve(hass, unique_id, name, entry.data)
|
entity = ThermostatOverValve(hass, unique_id, name, entry.data)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ from .const import DOMAIN, DEVICE_MANUFACTURER, ServiceConfigurationError
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_tz(hass: HomeAssistant):
|
def get_tz(hass: HomeAssistant):
|
||||||
"""Get the current timezone"""
|
"""Get the current timezone"""
|
||||||
|
|
||||||
|
|||||||
@@ -109,17 +109,17 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
or self._infos.get(CONF_WINDOW_AUTO_OPEN_THRESHOLD) is not None
|
or self._infos.get(CONF_WINDOW_AUTO_OPEN_THRESHOLD) is not None
|
||||||
)
|
)
|
||||||
self._infos[CONF_USE_MOTION_FEATURE] = self._infos.get(
|
self._infos[CONF_USE_MOTION_FEATURE] = self._infos.get(
|
||||||
CONF_USE_MOTION_FEATURE
|
CONF_USE_MOTION_FEATURE, False
|
||||||
) and (self._infos.get(CONF_MOTION_SENSOR) is not None or is_central_config)
|
) and (self._infos.get(CONF_MOTION_SENSOR) is not None or is_central_config)
|
||||||
|
|
||||||
self._infos[CONF_USE_POWER_FEATURE] = self._infos.get(
|
self._infos[CONF_USE_POWER_FEATURE] = self._infos.get(
|
||||||
CONF_USE_POWER_CENTRAL_CONFIG
|
CONF_USE_POWER_CENTRAL_CONFIG, False
|
||||||
) or (
|
) or (
|
||||||
self._infos.get(CONF_POWER_SENSOR) is not None
|
self._infos.get(CONF_POWER_SENSOR) is not None
|
||||||
and self._infos.get(CONF_MAX_POWER_SENSOR) is not None
|
and self._infos.get(CONF_MAX_POWER_SENSOR) is not None
|
||||||
)
|
)
|
||||||
self._infos[CONF_USE_PRESENCE_FEATURE] = (
|
self._infos[CONF_USE_PRESENCE_FEATURE] = (
|
||||||
self._infos.get(CONF_USE_PRESENCE_CENTRAL_CONFIG)
|
self._infos.get(CONF_USE_PRESENCE_CENTRAL_CONFIG, False)
|
||||||
or self._infos.get(CONF_PRESENCE_SENSOR) is not None
|
or self._infos.get(CONF_PRESENCE_SENSOR) is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -128,6 +128,11 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
and self._infos.get(CONF_CENTRAL_BOILER_DEACTIVATION_SRV) is not None
|
and self._infos.get(CONF_CENTRAL_BOILER_DEACTIVATION_SRV) is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._infos[CONF_USE_AUTO_START_STOP_FEATURE] = (
|
||||||
|
self._infos.get(CONF_USE_AUTO_START_STOP_FEATURE, False) is True
|
||||||
|
and self._infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE
|
||||||
|
)
|
||||||
|
|
||||||
def _init_central_config_flags(self, infos):
|
def _init_central_config_flags(self, infos):
|
||||||
"""Initialisation of central configuration flags"""
|
"""Initialisation of central configuration flags"""
|
||||||
is_empty: bool = not bool(infos)
|
is_empty: bool = not bool(infos)
|
||||||
@@ -140,19 +145,43 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
CONF_USE_PRESETS_CENTRAL_CONFIG,
|
CONF_USE_PRESETS_CENTRAL_CONFIG,
|
||||||
CONF_USE_PRESENCE_CENTRAL_CONFIG,
|
CONF_USE_PRESENCE_CENTRAL_CONFIG,
|
||||||
CONF_USE_ADVANCED_CENTRAL_CONFIG,
|
CONF_USE_ADVANCED_CENTRAL_CONFIG,
|
||||||
|
CONF_USE_CENTRAL_MODE,
|
||||||
):
|
):
|
||||||
if not is_empty:
|
if not is_empty:
|
||||||
current_config = self._infos.get(config, None)
|
current_config = self._infos.get(config, None)
|
||||||
self._infos[config] = current_config is True or (
|
|
||||||
current_config is None and self._central_config is not None
|
self._infos[config] = self._central_config is not None and (
|
||||||
|
current_config is True or current_config is None
|
||||||
)
|
)
|
||||||
|
# self._infos[config] = current_config is True or (
|
||||||
|
# current_config is None and self._central_config is not None
|
||||||
|
# )
|
||||||
else:
|
else:
|
||||||
self._infos[config] = self._central_config is not None
|
self._infos[config] = self._central_config is not None
|
||||||
|
|
||||||
if COMES_FROM in self._infos:
|
if COMES_FROM in self._infos:
|
||||||
del self._infos[COMES_FROM]
|
del self._infos[COMES_FROM]
|
||||||
|
|
||||||
async def validate_input(self, data: dict) -> None:
|
def check_sonoff_trvzb_nb_entities(self, data: dict) -> bool:
|
||||||
|
"""Check the number of entities for Sonoff TRVZB"""
|
||||||
|
ret = True
|
||||||
|
if (
|
||||||
|
self._infos.get(CONF_SONOFF_TRZB_MODE)
|
||||||
|
and data.get(CONF_OFFSET_CALIBRATION_LIST) is not None
|
||||||
|
):
|
||||||
|
nb_unders = len(self._infos.get(CONF_UNDERLYING_LIST))
|
||||||
|
nb_offset = len(data.get(CONF_OFFSET_CALIBRATION_LIST))
|
||||||
|
nb_opening = len(data.get(CONF_OPENING_DEGREE_LIST))
|
||||||
|
nb_closing = len(data.get(CONF_CLOSING_DEGREE_LIST))
|
||||||
|
if (
|
||||||
|
nb_unders != nb_offset
|
||||||
|
or nb_unders != nb_opening
|
||||||
|
or nb_unders != nb_closing
|
||||||
|
):
|
||||||
|
ret = False
|
||||||
|
return ret
|
||||||
|
|
||||||
|
async def validate_input(self, data: dict, step_id) -> None:
|
||||||
"""Validate the user input allows us to connect.
|
"""Validate the user input allows us to connect.
|
||||||
|
|
||||||
Data has the keys from STEP_*_DATA_SCHEMA with values provided by the user.
|
Data has the keys from STEP_*_DATA_SCHEMA with values provided by the user.
|
||||||
@@ -160,7 +189,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
|
|
||||||
# check the heater_entity_id
|
# check the heater_entity_id
|
||||||
for conf in [
|
for conf in [
|
||||||
CONF_HEATER,
|
CONF_UNDERLYING_LIST,
|
||||||
CONF_TEMP_SENSOR,
|
CONF_TEMP_SENSOR,
|
||||||
CONF_EXTERNAL_TEMP_SENSOR,
|
CONF_EXTERNAL_TEMP_SENSOR,
|
||||||
CONF_WINDOW_SENSOR,
|
CONF_WINDOW_SENSOR,
|
||||||
@@ -168,15 +197,20 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
CONF_POWER_SENSOR,
|
CONF_POWER_SENSOR,
|
||||||
CONF_MAX_POWER_SENSOR,
|
CONF_MAX_POWER_SENSOR,
|
||||||
CONF_PRESENCE_SENSOR,
|
CONF_PRESENCE_SENSOR,
|
||||||
CONF_CLIMATE,
|
CONF_OFFSET_CALIBRATION_LIST,
|
||||||
|
CONF_OPENING_DEGREE_LIST,
|
||||||
|
CONF_CLOSING_DEGREE_LIST,
|
||||||
]:
|
]:
|
||||||
d = data.get(conf, None) # pylint: disable=invalid-name
|
d = data.get(conf, None) # pylint: disable=invalid-name
|
||||||
if d is not None and self.hass.states.get(d) is None:
|
if not isinstance(d, list):
|
||||||
_LOGGER.error(
|
d = [d]
|
||||||
"Entity id %s doesn't have any state. We cannot use it in the Versatile Thermostat configuration", # pylint: disable=line-too-long
|
for e in d:
|
||||||
d,
|
if e is not None and self.hass.states.get(e) is None:
|
||||||
)
|
_LOGGER.error(
|
||||||
raise UnknownEntity(conf)
|
"Entity id %s doesn't have any state. We cannot use it in the Versatile Thermostat configuration", # pylint: disable=line-too-long
|
||||||
|
e,
|
||||||
|
)
|
||||||
|
raise UnknownEntity(conf)
|
||||||
|
|
||||||
# Check that only one window feature is used
|
# Check that only one window feature is used
|
||||||
ws = self._infos.get(CONF_WINDOW_SENSOR) # pylint: disable=invalid-name
|
ws = self._infos.get(CONF_WINDOW_SENSOR) # pylint: disable=invalid-name
|
||||||
@@ -202,6 +236,9 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
CONF_USE_PRESENCE_CENTRAL_CONFIG,
|
CONF_USE_PRESENCE_CENTRAL_CONFIG,
|
||||||
CONF_USE_PRESETS_CENTRAL_CONFIG,
|
CONF_USE_PRESETS_CENTRAL_CONFIG,
|
||||||
CONF_USE_ADVANCED_CENTRAL_CONFIG,
|
CONF_USE_ADVANCED_CENTRAL_CONFIG,
|
||||||
|
CONF_USE_CENTRAL_MODE,
|
||||||
|
# CONF_USE_CENTRAL_BOILER_FEATURE, this is for Central Config
|
||||||
|
CONF_USED_BY_CENTRAL_BOILER,
|
||||||
]:
|
]:
|
||||||
if data.get(conf) is True:
|
if data.get(conf) is True:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
@@ -220,6 +257,11 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
except ServiceConfigurationError as err:
|
except ServiceConfigurationError as err:
|
||||||
raise ServiceConfigurationError(conf) from err
|
raise ServiceConfigurationError(conf) from err
|
||||||
|
|
||||||
|
# Check that the number of offet_calibration and opening_degree and closing_degree are equals
|
||||||
|
# to the number of underlying entities
|
||||||
|
if not self.check_sonoff_trvzb_nb_entities(data):
|
||||||
|
raise SonoffTRVZBNbEntitiesIncorrect()
|
||||||
|
|
||||||
def check_config_complete(self, infos) -> bool:
|
def check_config_complete(self, infos) -> bool:
|
||||||
"""True if the config is now complete (ie all mandatory attributes are set)"""
|
"""True if the config is now complete (ie all mandatory attributes are set)"""
|
||||||
is_central_config = (
|
is_central_config = (
|
||||||
@@ -265,21 +307,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if (
|
if infos.get(CONF_UNDERLYING_LIST, None) is not None and not infos.get(
|
||||||
infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_SWITCH
|
CONF_UNDERLYING_LIST, None
|
||||||
and infos.get(CONF_HEATER, None) is None
|
|
||||||
):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if (
|
|
||||||
infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE
|
|
||||||
and infos.get(CONF_CLIMATE, None) is None
|
|
||||||
):
|
|
||||||
return False
|
|
||||||
|
|
||||||
if (
|
|
||||||
infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE
|
|
||||||
and infos.get(CONF_VALVE, None) is None
|
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -312,6 +341,25 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if (
|
||||||
|
infos.get(CONF_PROP_FUNCTION, None) == PROPORTIONAL_FUNCTION_TPI
|
||||||
|
and infos.get(CONF_USE_TPI_CENTRAL_CONFIG, False) is False
|
||||||
|
and (
|
||||||
|
infos.get(CONF_TPI_COEF_INT, None) is None
|
||||||
|
or infos.get(CONF_TPI_COEF_EXT) is None
|
||||||
|
)
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if (
|
||||||
|
infos.get(CONF_USE_PRESETS_CENTRAL_CONFIG, False) is True
|
||||||
|
and self._central_config is None
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.check_sonoff_trvzb_nb_entities(infos):
|
||||||
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def merge_user_input(self, data_schema: vol.Schema, user_input: dict):
|
def merge_user_input(self, data_schema: vol.Schema, user_input: dict):
|
||||||
@@ -341,7 +389,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
defaults.update(user_input or {})
|
defaults.update(user_input or {})
|
||||||
try:
|
try:
|
||||||
await self.validate_input(user_input)
|
await self.validate_input(user_input, step_id)
|
||||||
except UnknownEntity as err:
|
except UnknownEntity as err:
|
||||||
errors[str(err)] = "unknown_entity"
|
errors[str(err)] = "unknown_entity"
|
||||||
except WindowOpenDetectionMethod as err:
|
except WindowOpenDetectionMethod as err:
|
||||||
@@ -352,6 +400,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
errors[str(err)] = "service_configuration_format"
|
errors[str(err)] = "service_configuration_format"
|
||||||
except ConfigurationNotCompleteError as err:
|
except ConfigurationNotCompleteError as err:
|
||||||
errors["base"] = "configuration_not_complete"
|
errors["base"] = "configuration_not_complete"
|
||||||
|
except SonoffTRVZBNbEntitiesIncorrect as err:
|
||||||
|
errors["base"] = "sonoff_trvzb_nb_entities_incorrect"
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("Unexpected exception")
|
_LOGGER.exception("Unexpected exception")
|
||||||
errors["base"] = "unknown"
|
errors["base"] = "unknown"
|
||||||
@@ -431,6 +481,16 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
if self._infos[CONF_USE_PRESENCE_FEATURE] is True:
|
if self._infos[CONF_USE_PRESENCE_FEATURE] is True:
|
||||||
menu_options.append("presence")
|
menu_options.append("presence")
|
||||||
|
|
||||||
|
if self._infos.get(CONF_USE_AUTO_START_STOP_FEATURE) is True and self._infos[
|
||||||
|
CONF_THERMOSTAT_TYPE
|
||||||
|
] in [
|
||||||
|
CONF_THERMOSTAT_CLIMATE,
|
||||||
|
]:
|
||||||
|
menu_options.append("auto_start_stop")
|
||||||
|
|
||||||
|
if self._infos.get(CONF_SONOFF_TRZB_MODE) is True:
|
||||||
|
menu_options.append("sonoff_trvzb")
|
||||||
|
|
||||||
menu_options.append("advanced")
|
menu_options.append("advanced")
|
||||||
|
|
||||||
if self.check_config_complete(self._infos):
|
if self.check_config_complete(self._infos):
|
||||||
@@ -500,6 +560,24 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
"""Handle the Type flow steps"""
|
"""Handle the Type flow steps"""
|
||||||
_LOGGER.debug("Into ConfigFlow.async_step_type user_input=%s", user_input)
|
_LOGGER.debug("Into ConfigFlow.async_step_type user_input=%s", user_input)
|
||||||
|
|
||||||
|
if (
|
||||||
|
self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CLIMATE
|
||||||
|
and user_input is not None
|
||||||
|
and not user_input.get(CONF_SONOFF_TRZB_MODE)
|
||||||
|
):
|
||||||
|
# Remove TPI info
|
||||||
|
for key in [
|
||||||
|
PROPORTIONAL_FUNCTION_TPI,
|
||||||
|
CONF_PROP_FUNCTION,
|
||||||
|
CONF_TPI_COEF_INT,
|
||||||
|
CONF_TPI_COEF_EXT,
|
||||||
|
CONF_OFFSET_CALIBRATION_LIST,
|
||||||
|
CONF_OPENING_DEGREE_LIST,
|
||||||
|
CONF_CLOSING_DEGREE_LIST,
|
||||||
|
]:
|
||||||
|
if self._infos.get(key):
|
||||||
|
del self._infos[key]
|
||||||
|
|
||||||
if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_SWITCH:
|
if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_SWITCH:
|
||||||
return await self.generic_step(
|
return await self.generic_step(
|
||||||
"type", STEP_THERMOSTAT_SWITCH, user_input, self.async_step_menu
|
"type", STEP_THERMOSTAT_SWITCH, user_input, self.async_step_menu
|
||||||
@@ -520,17 +598,43 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
|
|||||||
"""Handle the Type flow steps"""
|
"""Handle the Type flow steps"""
|
||||||
_LOGGER.debug("Into ConfigFlow.async_step_features user_input=%s", user_input)
|
_LOGGER.debug("Into ConfigFlow.async_step_features user_input=%s", user_input)
|
||||||
|
|
||||||
|
schema = STEP_FEATURES_DATA_SCHEMA
|
||||||
|
if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CENTRAL_CONFIG:
|
||||||
|
schema = STEP_CENTRAL_FEATURES_DATA_SCHEMA
|
||||||
|
elif self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CLIMATE:
|
||||||
|
schema = STEP_CLIMATE_FEATURES_DATA_SCHEMA
|
||||||
|
|
||||||
return await self.generic_step(
|
return await self.generic_step(
|
||||||
"features",
|
"features",
|
||||||
(
|
schema,
|
||||||
STEP_CENTRAL_FEATURES_DATA_SCHEMA
|
|
||||||
if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CENTRAL_CONFIG
|
|
||||||
else STEP_FEATURES_DATA_SCHEMA
|
|
||||||
),
|
|
||||||
user_input,
|
user_input,
|
||||||
self.async_step_menu,
|
self.async_step_menu,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_step_auto_start_stop(self, user_input: dict | None = None) -> FlowResult:
|
||||||
|
""" Handle the Auto start stop step"""
|
||||||
|
_LOGGER.debug("Into ConfigFlow.async_step_auto_start_stop user_input=%s", user_input)
|
||||||
|
|
||||||
|
schema = STEP_AUTO_START_STOP
|
||||||
|
self._infos[COMES_FROM] = None
|
||||||
|
next_step = self.async_step_menu
|
||||||
|
|
||||||
|
return await self.generic_step("auto_start_stop", schema, user_input, next_step)
|
||||||
|
|
||||||
|
async def async_step_sonoff_trvzb(
|
||||||
|
self, user_input: dict | None = None
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle the Sonoff TRVZB configuration step"""
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Into ConfigFlow.async_step_sonoff_trvzb user_input=%s", user_input
|
||||||
|
)
|
||||||
|
|
||||||
|
schema = STEP_SONOFF_TRVZB
|
||||||
|
self._infos[COMES_FROM] = None
|
||||||
|
next_step = self.async_step_menu
|
||||||
|
|
||||||
|
return await self.generic_step("sonoff_trvzb", schema, user_input, next_step)
|
||||||
|
|
||||||
async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult:
|
async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult:
|
||||||
"""Handle the TPI flow steps"""
|
"""Handle the TPI flow steps"""
|
||||||
_LOGGER.debug("Into ConfigFlow.async_step_tpi user_input=%s", user_input)
|
_LOGGER.debug("Into ConfigFlow.async_step_tpi user_input=%s", user_input)
|
||||||
@@ -869,6 +973,8 @@ class VersatileThermostatOptionsFlowHandler(
|
|||||||
if not self._infos[CONF_USE_CENTRAL_BOILER_FEATURE]:
|
if not self._infos[CONF_USE_CENTRAL_BOILER_FEATURE]:
|
||||||
self._infos[CONF_CENTRAL_BOILER_ACTIVATION_SRV] = None
|
self._infos[CONF_CENTRAL_BOILER_ACTIVATION_SRV] = None
|
||||||
self._infos[CONF_CENTRAL_BOILER_DEACTIVATION_SRV] = None
|
self._infos[CONF_CENTRAL_BOILER_DEACTIVATION_SRV] = None
|
||||||
|
if not self._infos[CONF_USE_AUTO_START_STOP_FEATURE]:
|
||||||
|
self._infos[CONF_AUTO_START_STOP_LEVEL] = AUTO_START_STOP_LEVEL_NONE
|
||||||
|
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Recreating entry %s due to configuration change. New config is now: %s",
|
"Recreating entry %s due to configuration change. New config is now: %s",
|
||||||
|
|||||||
@@ -68,6 +68,16 @@ STEP_FEATURES_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
STEP_CLIMATE_FEATURES_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
vol.Optional(CONF_USE_PRESENCE_FEATURE, default=False): cv.boolean,
|
||||||
|
vol.Optional(CONF_USE_AUTO_START_STOP_FEATURE, default=False): cv.boolean,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
STEP_CENTRAL_FEATURES_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
|
STEP_CENTRAL_FEATURES_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_USE_WINDOW_FEATURE, default=False): cv.boolean,
|
vol.Optional(CONF_USE_WINDOW_FEATURE, default=False): cv.boolean,
|
||||||
@@ -109,17 +119,10 @@ STEP_CENTRAL_BOILER_SCHEMA = vol.Schema(
|
|||||||
|
|
||||||
STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
|
STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
|
||||||
{
|
{
|
||||||
vol.Required(CONF_HEATER): selector.EntitySelector(
|
vol.Required(CONF_UNDERLYING_LIST): selector.EntitySelector(
|
||||||
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
|
selector.EntitySelectorConfig(
|
||||||
),
|
domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN], multiple=True
|
||||||
vol.Optional(CONF_HEATER_2): selector.EntitySelector(
|
),
|
||||||
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_HEATER_3): selector.EntitySelector(
|
|
||||||
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_HEATER_4): selector.EntitySelector(
|
|
||||||
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
|
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_HEATER_KEEP_ALIVE): cv.positive_int,
|
vol.Optional(CONF_HEATER_KEEP_ALIVE): cv.positive_int,
|
||||||
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
|
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
|
||||||
@@ -134,19 +137,11 @@ STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
|
|||||||
|
|
||||||
STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name
|
STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name
|
||||||
{
|
{
|
||||||
vol.Required(CONF_CLIMATE): selector.EntitySelector(
|
vol.Required(CONF_UNDERLYING_LIST): selector.EntitySelector(
|
||||||
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
|
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN, multiple=True),
|
||||||
),
|
|
||||||
vol.Optional(CONF_CLIMATE_2): selector.EntitySelector(
|
|
||||||
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_CLIMATE_3): selector.EntitySelector(
|
|
||||||
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_CLIMATE_4): selector.EntitySelector(
|
|
||||||
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
|
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
|
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
|
||||||
|
vol.Optional(CONF_SONOFF_TRZB_MODE, default=False): cv.boolean,
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_AUTO_REGULATION_MODE, default=CONF_AUTO_REGULATION_NONE
|
CONF_AUTO_REGULATION_MODE, default=CONF_AUTO_REGULATION_NONE
|
||||||
): selector.SelectSelector(
|
): selector.SelectSelector(
|
||||||
@@ -173,17 +168,10 @@ STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name
|
|||||||
|
|
||||||
STEP_THERMOSTAT_VALVE = vol.Schema( # pylint: disable=invalid-name
|
STEP_THERMOSTAT_VALVE = vol.Schema( # pylint: disable=invalid-name
|
||||||
{
|
{
|
||||||
vol.Required(CONF_VALVE): selector.EntitySelector(
|
vol.Required(CONF_UNDERLYING_LIST): selector.EntitySelector(
|
||||||
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
|
selector.EntitySelectorConfig(
|
||||||
),
|
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
|
||||||
vol.Optional(CONF_VALVE_2): selector.EntitySelector(
|
),
|
||||||
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_VALVE_3): selector.EntitySelector(
|
|
||||||
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_VALVE_4): selector.EntitySelector(
|
|
||||||
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
|
|
||||||
),
|
),
|
||||||
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
|
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
|
||||||
[
|
[
|
||||||
@@ -196,6 +184,45 @@ STEP_THERMOSTAT_VALVE = vol.Schema( # pylint: disable=invalid-name
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
STEP_AUTO_START_STOP = vol.Schema( # pylint: disable=invalid-name
|
||||||
|
{
|
||||||
|
vol.Optional(
|
||||||
|
CONF_AUTO_START_STOP_LEVEL, default=AUTO_START_STOP_LEVEL_NONE
|
||||||
|
): selector.SelectSelector(
|
||||||
|
selector.SelectSelectorConfig(
|
||||||
|
options=CONF_AUTO_START_STOP_LEVELS,
|
||||||
|
translation_key="auto_start_stop",
|
||||||
|
mode="dropdown",
|
||||||
|
)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
STEP_SONOFF_TRVZB = vol.Schema( # pylint: disable=invalid-name
|
||||||
|
{
|
||||||
|
vol.Required(CONF_OFFSET_CALIBRATION_LIST): selector.EntitySelector(
|
||||||
|
selector.EntitySelectorConfig(
|
||||||
|
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
vol.Required(CONF_OPENING_DEGREE_LIST): selector.EntitySelector(
|
||||||
|
selector.EntitySelectorConfig(
|
||||||
|
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
vol.Required(CONF_CLOSING_DEGREE_LIST): selector.EntitySelector(
|
||||||
|
selector.EntitySelectorConfig(
|
||||||
|
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
|
||||||
|
[
|
||||||
|
PROPORTIONAL_FUNCTION_TPI,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
STEP_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
|
STEP_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
|
||||||
{
|
{
|
||||||
vol.Required(CONF_USE_TPI_CENTRAL_CONFIG, default=True): cv.boolean,
|
vol.Required(CONF_USE_TPI_CENTRAL_CONFIG, default=True): cv.boolean,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"""Constants for the Versatile Thermostat integration."""
|
"""Constants for the Versatile Thermostat integration."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from homeassistant.const import CONF_NAME, Platform
|
from homeassistant.const import CONF_NAME, Platform
|
||||||
@@ -22,8 +23,8 @@ from .prop_algorithm import (
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONFIG_VERSION = 1
|
CONFIG_VERSION = 2
|
||||||
CONFIG_MINOR_VERSION = 2
|
CONFIG_MINOR_VERSION = 0
|
||||||
|
|
||||||
PRESET_TEMP_SUFFIX = "_temp"
|
PRESET_TEMP_SUFFIX = "_temp"
|
||||||
PRESET_AC_SUFFIX = "_ac"
|
PRESET_AC_SUFFIX = "_ac"
|
||||||
@@ -51,12 +52,10 @@ PLATFORMS: list[Platform] = [
|
|||||||
# Number should be after CLIMATE
|
# Number should be after CLIMATE
|
||||||
Platform.NUMBER,
|
Platform.NUMBER,
|
||||||
Platform.BINARY_SENSOR,
|
Platform.BINARY_SENSOR,
|
||||||
|
Platform.SWITCH,
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF_HEATER = "heater_entity_id"
|
CONF_UNDERLYING_LIST = "underlying_entity_ids"
|
||||||
CONF_HEATER_2 = "heater_entity2_id"
|
|
||||||
CONF_HEATER_3 = "heater_entity3_id"
|
|
||||||
CONF_HEATER_4 = "heater_entity4_id"
|
|
||||||
CONF_HEATER_KEEP_ALIVE = "heater_keep_alive"
|
CONF_HEATER_KEEP_ALIVE = "heater_keep_alive"
|
||||||
CONF_TEMP_SENSOR = "temperature_sensor_entity_id"
|
CONF_TEMP_SENSOR = "temperature_sensor_entity_id"
|
||||||
CONF_LAST_SEEN_TEMP_SENSOR = "last_seen_temperature_sensor_entity_id"
|
CONF_LAST_SEEN_TEMP_SENSOR = "last_seen_temperature_sensor_entity_id"
|
||||||
@@ -88,23 +87,17 @@ CONF_THERMOSTAT_CENTRAL_CONFIG = "thermostat_central_config"
|
|||||||
CONF_THERMOSTAT_SWITCH = "thermostat_over_switch"
|
CONF_THERMOSTAT_SWITCH = "thermostat_over_switch"
|
||||||
CONF_THERMOSTAT_CLIMATE = "thermostat_over_climate"
|
CONF_THERMOSTAT_CLIMATE = "thermostat_over_climate"
|
||||||
CONF_THERMOSTAT_VALVE = "thermostat_over_valve"
|
CONF_THERMOSTAT_VALVE = "thermostat_over_valve"
|
||||||
CONF_CLIMATE = "climate_entity_id"
|
|
||||||
CONF_CLIMATE_2 = "climate_entity2_id"
|
|
||||||
CONF_CLIMATE_3 = "climate_entity3_id"
|
|
||||||
CONF_CLIMATE_4 = "climate_entity4_id"
|
|
||||||
CONF_USE_WINDOW_FEATURE = "use_window_feature"
|
CONF_USE_WINDOW_FEATURE = "use_window_feature"
|
||||||
CONF_USE_MOTION_FEATURE = "use_motion_feature"
|
CONF_USE_MOTION_FEATURE = "use_motion_feature"
|
||||||
CONF_USE_PRESENCE_FEATURE = "use_presence_feature"
|
CONF_USE_PRESENCE_FEATURE = "use_presence_feature"
|
||||||
CONF_USE_POWER_FEATURE = "use_power_feature"
|
CONF_USE_POWER_FEATURE = "use_power_feature"
|
||||||
CONF_USE_CENTRAL_BOILER_FEATURE = "use_central_boiler_feature"
|
CONF_USE_CENTRAL_BOILER_FEATURE = "use_central_boiler_feature"
|
||||||
|
CONF_USE_AUTO_START_STOP_FEATURE = "use_auto_start_stop_feature"
|
||||||
CONF_AC_MODE = "ac_mode"
|
CONF_AC_MODE = "ac_mode"
|
||||||
|
CONF_SONOFF_TRZB_MODE = "sonoff_trvzb_mode"
|
||||||
CONF_WINDOW_AUTO_OPEN_THRESHOLD = "window_auto_open_threshold"
|
CONF_WINDOW_AUTO_OPEN_THRESHOLD = "window_auto_open_threshold"
|
||||||
CONF_WINDOW_AUTO_CLOSE_THRESHOLD = "window_auto_close_threshold"
|
CONF_WINDOW_AUTO_CLOSE_THRESHOLD = "window_auto_close_threshold"
|
||||||
CONF_WINDOW_AUTO_MAX_DURATION = "window_auto_max_duration"
|
CONF_WINDOW_AUTO_MAX_DURATION = "window_auto_max_duration"
|
||||||
CONF_VALVE = "valve_entity_id"
|
|
||||||
CONF_VALVE_2 = "valve_entity2_id"
|
|
||||||
CONF_VALVE_3 = "valve_entity3_id"
|
|
||||||
CONF_VALVE_4 = "valve_entity4_id"
|
|
||||||
CONF_AUTO_REGULATION_MODE = "auto_regulation_mode"
|
CONF_AUTO_REGULATION_MODE = "auto_regulation_mode"
|
||||||
CONF_AUTO_REGULATION_NONE = "auto_regulation_none"
|
CONF_AUTO_REGULATION_NONE = "auto_regulation_none"
|
||||||
CONF_AUTO_REGULATION_SLOW = "auto_regulation_slow"
|
CONF_AUTO_REGULATION_SLOW = "auto_regulation_slow"
|
||||||
@@ -123,10 +116,28 @@ CONF_AUTO_FAN_MEDIUM = "auto_fan_medium"
|
|||||||
CONF_AUTO_FAN_HIGH = "auto_fan_high"
|
CONF_AUTO_FAN_HIGH = "auto_fan_high"
|
||||||
CONF_AUTO_FAN_TURBO = "auto_fan_turbo"
|
CONF_AUTO_FAN_TURBO = "auto_fan_turbo"
|
||||||
CONF_STEP_TEMPERATURE = "step_temperature"
|
CONF_STEP_TEMPERATURE = "step_temperature"
|
||||||
|
CONF_OFFSET_CALIBRATION_LIST = "offset_calibration_entity_ids"
|
||||||
|
CONF_OPENING_DEGREE_LIST = "opening_degree_entity_ids"
|
||||||
|
CONF_CLOSING_DEGREE_LIST = "closing_degree_entity_ids"
|
||||||
|
|
||||||
|
# Deprecated
|
||||||
|
CONF_HEATER = "heater_entity_id"
|
||||||
|
CONF_HEATER_2 = "heater_entity2_id"
|
||||||
|
CONF_HEATER_3 = "heater_entity3_id"
|
||||||
|
CONF_HEATER_4 = "heater_entity4_id"
|
||||||
|
CONF_CLIMATE = "climate_entity_id"
|
||||||
|
CONF_CLIMATE_2 = "climate_entity2_id"
|
||||||
|
CONF_CLIMATE_3 = "climate_entity3_id"
|
||||||
|
CONF_CLIMATE_4 = "climate_entity4_id"
|
||||||
|
CONF_VALVE = "valve_entity_id"
|
||||||
|
CONF_VALVE_2 = "valve_entity2_id"
|
||||||
|
CONF_VALVE_3 = "valve_entity3_id"
|
||||||
|
CONF_VALVE_4 = "valve_entity4_id"
|
||||||
|
|
||||||
# Global params into configuration.yaml
|
# Global params into configuration.yaml
|
||||||
CONF_SHORT_EMA_PARAMS = "short_ema_params"
|
CONF_SHORT_EMA_PARAMS = "short_ema_params"
|
||||||
CONF_SAFETY_MODE = "safety_mode"
|
CONF_SAFETY_MODE = "safety_mode"
|
||||||
|
CONF_MAX_ON_PERCENT = "max_on_percent"
|
||||||
|
|
||||||
CONF_USE_MAIN_CENTRAL_CONFIG = "use_main_central_config"
|
CONF_USE_MAIN_CENTRAL_CONFIG = "use_main_central_config"
|
||||||
CONF_USE_TPI_CENTRAL_CONFIG = "use_tpi_central_config"
|
CONF_USE_TPI_CENTRAL_CONFIG = "use_tpi_central_config"
|
||||||
@@ -145,6 +156,36 @@ CONF_CENTRAL_BOILER_DEACTIVATION_SRV = "central_boiler_deactivation_service"
|
|||||||
CONF_USED_BY_CENTRAL_BOILER = "used_by_controls_central_boiler"
|
CONF_USED_BY_CENTRAL_BOILER = "used_by_controls_central_boiler"
|
||||||
CONF_WINDOW_ACTION = "window_action"
|
CONF_WINDOW_ACTION = "window_action"
|
||||||
|
|
||||||
|
CONF_AUTO_START_STOP_LEVEL = "auto_start_stop_level"
|
||||||
|
AUTO_START_STOP_LEVEL_NONE = "auto_start_stop_none"
|
||||||
|
AUTO_START_STOP_LEVEL_SLOW = "auto_start_stop_slow"
|
||||||
|
AUTO_START_STOP_LEVEL_MEDIUM = "auto_start_stop_medium"
|
||||||
|
AUTO_START_STOP_LEVEL_FAST = "auto_start_stop_fast"
|
||||||
|
CONF_AUTO_START_STOP_LEVELS = [
|
||||||
|
AUTO_START_STOP_LEVEL_NONE,
|
||||||
|
AUTO_START_STOP_LEVEL_SLOW,
|
||||||
|
AUTO_START_STOP_LEVEL_MEDIUM,
|
||||||
|
AUTO_START_STOP_LEVEL_FAST,
|
||||||
|
]
|
||||||
|
|
||||||
|
# For explicit typing purpose only
|
||||||
|
TYPE_AUTO_START_STOP_LEVELS = Literal[ # pylint: disable=invalid-name
|
||||||
|
AUTO_START_STOP_LEVEL_FAST,
|
||||||
|
AUTO_START_STOP_LEVEL_MEDIUM,
|
||||||
|
AUTO_START_STOP_LEVEL_SLOW,
|
||||||
|
AUTO_START_STOP_LEVEL_NONE,
|
||||||
|
]
|
||||||
|
|
||||||
|
HVAC_OFF_REASON_NAME = "hvac_off_reason"
|
||||||
|
HVAC_OFF_REASON_MANUAL = "manual"
|
||||||
|
HVAC_OFF_REASON_AUTO_START_STOP = "auto_start_stop"
|
||||||
|
HVAC_OFF_REASON_WINDOW_DETECTION = "window_detection"
|
||||||
|
HVAC_OFF_REASONS = Literal[ # pylint: disable=invalid-name
|
||||||
|
HVAC_OFF_REASON_MANUAL,
|
||||||
|
HVAC_OFF_REASON_AUTO_START_STOP,
|
||||||
|
HVAC_OFF_REASON_WINDOW_DETECTION,
|
||||||
|
]
|
||||||
|
|
||||||
DEFAULT_SHORT_EMA_PARAMS = {
|
DEFAULT_SHORT_EMA_PARAMS = {
|
||||||
"max_alpha": 0.5,
|
"max_alpha": 0.5,
|
||||||
# In sec
|
# In sec
|
||||||
@@ -216,10 +257,6 @@ CONF_PRESETS_AWAY_WITH_AC_VALUES = list(CONF_PRESETS_AWAY_WITH_AC.values())
|
|||||||
ALL_CONF = (
|
ALL_CONF = (
|
||||||
[
|
[
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_HEATER,
|
|
||||||
CONF_HEATER_2,
|
|
||||||
CONF_HEATER_3,
|
|
||||||
CONF_HEATER_4,
|
|
||||||
CONF_HEATER_KEEP_ALIVE,
|
CONF_HEATER_KEEP_ALIVE,
|
||||||
CONF_TEMP_SENSOR,
|
CONF_TEMP_SENSOR,
|
||||||
CONF_EXTERNAL_TEMP_SENSOR,
|
CONF_EXTERNAL_TEMP_SENSOR,
|
||||||
@@ -249,20 +286,13 @@ ALL_CONF = (
|
|||||||
CONF_THERMOSTAT_TYPE,
|
CONF_THERMOSTAT_TYPE,
|
||||||
CONF_THERMOSTAT_SWITCH,
|
CONF_THERMOSTAT_SWITCH,
|
||||||
CONF_THERMOSTAT_CLIMATE,
|
CONF_THERMOSTAT_CLIMATE,
|
||||||
CONF_CLIMATE,
|
|
||||||
CONF_CLIMATE_2,
|
|
||||||
CONF_CLIMATE_3,
|
|
||||||
CONF_CLIMATE_4,
|
|
||||||
CONF_USE_WINDOW_FEATURE,
|
CONF_USE_WINDOW_FEATURE,
|
||||||
CONF_USE_MOTION_FEATURE,
|
CONF_USE_MOTION_FEATURE,
|
||||||
CONF_USE_PRESENCE_FEATURE,
|
CONF_USE_PRESENCE_FEATURE,
|
||||||
CONF_USE_POWER_FEATURE,
|
CONF_USE_POWER_FEATURE,
|
||||||
CONF_USE_CENTRAL_BOILER_FEATURE,
|
CONF_USE_CENTRAL_BOILER_FEATURE,
|
||||||
CONF_AC_MODE,
|
CONF_AC_MODE,
|
||||||
CONF_VALVE,
|
CONF_SONOFF_TRZB_MODE,
|
||||||
CONF_VALVE_2,
|
|
||||||
CONF_VALVE_3,
|
|
||||||
CONF_VALVE_4,
|
|
||||||
CONF_AUTO_REGULATION_MODE,
|
CONF_AUTO_REGULATION_MODE,
|
||||||
CONF_AUTO_REGULATION_DTEMP,
|
CONF_AUTO_REGULATION_DTEMP,
|
||||||
CONF_AUTO_REGULATION_PERIOD_MIN,
|
CONF_AUTO_REGULATION_PERIOD_MIN,
|
||||||
@@ -330,7 +360,11 @@ CONF_WINDOW_ACTIONS = [
|
|||||||
CONF_WINDOW_ECO_TEMP,
|
CONF_WINDOW_ECO_TEMP,
|
||||||
]
|
]
|
||||||
|
|
||||||
SUPPORT_FLAGS = ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.TURN_OFF | ClimateEntityFeature.TURN_ON
|
SUPPORT_FLAGS = (
|
||||||
|
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||||
|
| ClimateEntityFeature.TURN_OFF
|
||||||
|
| ClimateEntityFeature.TURN_ON
|
||||||
|
)
|
||||||
|
|
||||||
SERVICE_SET_PRESENCE = "set_presence"
|
SERVICE_SET_PRESENCE = "set_presence"
|
||||||
SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"
|
SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"
|
||||||
@@ -430,9 +464,9 @@ class RegulationParamVeryStrong:
|
|||||||
kp: float = 0.6
|
kp: float = 0.6
|
||||||
ki: float = 0.1
|
ki: float = 0.1
|
||||||
k_ext: float = 0.2
|
k_ext: float = 0.2
|
||||||
offset_max: float = 4
|
offset_max: float = 8
|
||||||
stabilization_threshold: float = 0.1
|
stabilization_threshold: float = 0.1
|
||||||
accumulated_error_threshold: float = 30
|
accumulated_error_threshold: float = 80
|
||||||
|
|
||||||
|
|
||||||
class EventType(Enum):
|
class EventType(Enum):
|
||||||
@@ -445,6 +479,7 @@ class EventType(Enum):
|
|||||||
CENTRAL_BOILER_EVENT: str = "versatile_thermostat_central_boiler_event"
|
CENTRAL_BOILER_EVENT: str = "versatile_thermostat_central_boiler_event"
|
||||||
PRESET_EVENT: str = "versatile_thermostat_preset_event"
|
PRESET_EVENT: str = "versatile_thermostat_preset_event"
|
||||||
WINDOW_AUTO_EVENT: str = "versatile_thermostat_window_auto_event"
|
WINDOW_AUTO_EVENT: str = "versatile_thermostat_window_auto_event"
|
||||||
|
AUTO_START_STOP_EVENT: str = "versatile_thermostat_auto_start_stop_event"
|
||||||
|
|
||||||
|
|
||||||
def send_vtherm_event(hass, event_type: EventType, entity, data: dict):
|
def send_vtherm_event(hass, event_type: EventType, entity, data: dict):
|
||||||
@@ -476,6 +511,11 @@ class ConfigurationNotCompleteError(HomeAssistantError):
|
|||||||
"""Error the configuration is not complete"""
|
"""Error the configuration is not complete"""
|
||||||
|
|
||||||
|
|
||||||
|
class SonoffTRVZBNbEntitiesIncorrect(HomeAssistantError):
|
||||||
|
"""Error to indicate there is an error in the configuration of the Sonoff TRVZB.
|
||||||
|
The number of specific entities is incorrect."""
|
||||||
|
|
||||||
|
|
||||||
class overrides: # pylint: disable=invalid-name
|
class overrides: # pylint: disable=invalid-name
|
||||||
"""An annotation to inform overrides"""
|
"""An annotation to inform overrides"""
|
||||||
|
|
||||||
|
|||||||
18
custom_components/versatile_thermostat/icons.json
Normal file
18
custom_components/versatile_thermostat/icons.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"entity": {
|
||||||
|
"climate": {
|
||||||
|
"versatile_thermostat": {
|
||||||
|
"state_attributes": {
|
||||||
|
"preset_mode": {
|
||||||
|
"state": {
|
||||||
|
"shedding": "mdi:power-plug-off",
|
||||||
|
"safety": "mdi:shield-alert",
|
||||||
|
"none": "mdi:knob",
|
||||||
|
"frost": "mdi:snowflake"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,6 @@
|
|||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": [],
|
"requirements": [],
|
||||||
"ssdp": [],
|
"ssdp": [],
|
||||||
"version": "6.3.0",
|
"version": "6.8.0",
|
||||||
"zeroconf": []
|
"zeroconf": []
|
||||||
}
|
}
|
||||||
@@ -31,6 +31,7 @@ class PropAlgorithm:
|
|||||||
cycle_min: int,
|
cycle_min: int,
|
||||||
minimal_activation_delay: int,
|
minimal_activation_delay: int,
|
||||||
vtherm_entity_id: str = None,
|
vtherm_entity_id: str = None,
|
||||||
|
max_on_percent: float = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialisation of the Proportional Algorithm"""
|
"""Initialisation of the Proportional Algorithm"""
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
@@ -78,6 +79,7 @@ class PropAlgorithm:
|
|||||||
self._off_time_sec = self._cycle_min * 60
|
self._off_time_sec = self._cycle_min * 60
|
||||||
self._security = False
|
self._security = False
|
||||||
self._default_on_percent = 0
|
self._default_on_percent = 0
|
||||||
|
self._max_on_percent = max_on_percent
|
||||||
|
|
||||||
def calculate(
|
def calculate(
|
||||||
self,
|
self,
|
||||||
@@ -161,6 +163,15 @@ class PropAlgorithm:
|
|||||||
)
|
)
|
||||||
self._on_percent = self._calculated_on_percent
|
self._on_percent = self._calculated_on_percent
|
||||||
|
|
||||||
|
if self._max_on_percent is not None and self._on_percent > self._max_on_percent:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - Heating period clamped to %s (instead of %s) due to max_on_percent setting.",
|
||||||
|
self._vtherm_entity_id,
|
||||||
|
self._max_on_percent,
|
||||||
|
self._on_percent,
|
||||||
|
)
|
||||||
|
self._on_percent = self._max_on_percent
|
||||||
|
|
||||||
self._on_time_sec = self._on_percent * self._cycle_min * 60
|
self._on_time_sec = self._on_percent * self._cycle_min * 60
|
||||||
|
|
||||||
# Do not heat for less than xx sec
|
# Do not heat for less than xx sec
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ from homeassistant.components.sensor import (
|
|||||||
SensorEntity,
|
SensorEntity,
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
SensorStateClass,
|
SensorStateClass,
|
||||||
UnitOfTemperature,
|
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
|
||||||
@@ -50,6 +49,7 @@ from .const import (
|
|||||||
CONF_THERMOSTAT_TYPE,
|
CONF_THERMOSTAT_TYPE,
|
||||||
CONF_THERMOSTAT_CENTRAL_CONFIG,
|
CONF_THERMOSTAT_CENTRAL_CONFIG,
|
||||||
CONF_USE_CENTRAL_BOILER_FEATURE,
|
CONF_USE_CENTRAL_BOILER_FEATURE,
|
||||||
|
CONF_SONOFF_TRZB_MODE,
|
||||||
overrides,
|
overrides,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -71,6 +71,7 @@ async def async_setup_entry(
|
|||||||
unique_id = entry.entry_id
|
unique_id = entry.entry_id
|
||||||
name = entry.data.get(CONF_NAME)
|
name = entry.data.get(CONF_NAME)
|
||||||
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
|
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
|
||||||
|
is_sonoff_trvzb = entry.data.get(CONF_SONOFF_TRZB_MODE)
|
||||||
|
|
||||||
entities = None
|
entities = None
|
||||||
|
|
||||||
@@ -99,10 +100,16 @@ async def async_setup_entry(
|
|||||||
entities.append(OnTimeSensor(hass, unique_id, name, entry.data))
|
entities.append(OnTimeSensor(hass, unique_id, name, entry.data))
|
||||||
entities.append(OffTimeSensor(hass, unique_id, name, entry.data))
|
entities.append(OffTimeSensor(hass, unique_id, name, entry.data))
|
||||||
|
|
||||||
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE:
|
if (
|
||||||
|
entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE
|
||||||
|
or is_sonoff_trvzb
|
||||||
|
):
|
||||||
entities.append(ValveOpenPercentSensor(hass, unique_id, name, entry.data))
|
entities.append(ValveOpenPercentSensor(hass, unique_id, name, entry.data))
|
||||||
|
|
||||||
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE:
|
if (
|
||||||
|
entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE
|
||||||
|
and not is_sonoff_trvzb
|
||||||
|
):
|
||||||
entities.append(
|
entities.append(
|
||||||
RegulatedTemperatureSensor(hass, unique_id, name, entry.data)
|
RegulatedTemperatureSensor(hass, unique_id, name, entry.data)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
"power": "Power management",
|
"power": "Power management",
|
||||||
"presence": "Presence detection",
|
"presence": "Presence detection",
|
||||||
"advanced": "Advanced parameters",
|
"advanced": "Advanced parameters",
|
||||||
|
"auto_start_stop": "Auto start and stop",
|
||||||
|
"sonoff_trvzb": "Sonoff TRVZB configuration",
|
||||||
"finalize": "All done",
|
"finalize": "All done",
|
||||||
"configuration_not_complete": "Configuration not complete"
|
"configuration_not_complete": "Configuration not complete"
|
||||||
}
|
}
|
||||||
@@ -63,28 +65,19 @@
|
|||||||
"use_motion_feature": "Use motion detection",
|
"use_motion_feature": "Use motion detection",
|
||||||
"use_power_feature": "Use power management",
|
"use_power_feature": "Use power management",
|
||||||
"use_presence_feature": "Use presence detection",
|
"use_presence_feature": "Use presence detection",
|
||||||
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page"
|
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
|
||||||
|
"use_auto_start_stop_feature": "Use the auto start and stop feature"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"title": "Linked entities",
|
"title": "Linked entities",
|
||||||
"description": "Linked entities attributes",
|
"description": "Linked entities attributes",
|
||||||
"data": {
|
"data": {
|
||||||
"heater_entity_id": "1st heater switch",
|
"underlying_entity_ids": "The device(s) to be controlled",
|
||||||
"heater_entity2_id": "2nd heater switch",
|
|
||||||
"heater_entity3_id": "3rd heater switch",
|
|
||||||
"heater_entity4_id": "4th heater switch",
|
|
||||||
"heater_keep_alive": "Switch keep-alive interval in seconds",
|
"heater_keep_alive": "Switch keep-alive interval in seconds",
|
||||||
"proportional_function": "Algorithm",
|
"proportional_function": "Algorithm",
|
||||||
"climate_entity_id": "1st underlying climate",
|
|
||||||
"climate_entity2_id": "2nd underlying climate",
|
|
||||||
"climate_entity3_id": "3rd underlying climate",
|
|
||||||
"climate_entity4_id": "4th underlying climate",
|
|
||||||
"ac_mode": "AC mode",
|
"ac_mode": "AC mode",
|
||||||
"valve_entity_id": "1st valve number",
|
"sonoff_trvzb_mode": "SONOFF TRVZB mode",
|
||||||
"valve_entity2_id": "2nd valve number",
|
|
||||||
"valve_entity3_id": "3rd valve number",
|
|
||||||
"valve_entity4_id": "4th valve number",
|
|
||||||
"auto_regulation_mode": "Self-regulation",
|
"auto_regulation_mode": "Self-regulation",
|
||||||
"auto_regulation_dtemp": "Regulation threshold",
|
"auto_regulation_dtemp": "Regulation threshold",
|
||||||
"auto_regulation_periode_min": "Regulation minimum period",
|
"auto_regulation_periode_min": "Regulation minimum period",
|
||||||
@@ -93,21 +86,11 @@
|
|||||||
"auto_fan_mode": "Auto fan mode"
|
"auto_fan_mode": "Auto fan mode"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"heater_entity_id": "Mandatory heater entity id",
|
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
|
||||||
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not required",
|
|
||||||
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not required",
|
|
||||||
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not required",
|
|
||||||
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
|
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
|
||||||
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||||
"climate_entity_id": "Underlying climate entity id",
|
|
||||||
"climate_entity2_id": "2nd underlying climate entity id",
|
|
||||||
"climate_entity3_id": "3rd underlying climate entity id",
|
|
||||||
"climate_entity4_id": "4th underlying climate entity id",
|
|
||||||
"ac_mode": "Use the Air Conditioning (AC) mode",
|
"ac_mode": "Use the Air Conditioning (AC) mode",
|
||||||
"valve_entity_id": "1st valve number entity id",
|
"sonoff_trvzb_mode": "The underlyings are SONOFF TRVZB. You have to configure some extra entities in the specific menu option 'Sonoff trvzb configuration'",
|
||||||
"valve_entity2_id": "2nd valve number entity id",
|
|
||||||
"valve_entity3_id": "3rd valve number entity id",
|
|
||||||
"valve_entity4_id": "4th valve number entity id",
|
|
||||||
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
||||||
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
|
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
|
||||||
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
||||||
@@ -223,6 +206,34 @@
|
|||||||
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
|
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
|
||||||
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
|
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"central_boiler": {
|
||||||
|
"title": "Control of the central boiler",
|
||||||
|
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
|
||||||
|
"data": {
|
||||||
|
"central_boiler_activation_service": "Command to turn-on",
|
||||||
|
"central_boiler_deactivation_service": "Command to turn-off"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
|
||||||
|
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sonoff_trvzb": {
|
||||||
|
"title": "Sonoff TRVZB configuration",
|
||||||
|
"description": "Specific Sonoff TRVZB configuration",
|
||||||
|
"data": {
|
||||||
|
"offset_calibration_entity_ids": "Offset calibration entities",
|
||||||
|
"opening_degree_entity_ids": "Opening degree entities",
|
||||||
|
"closing_degree_entity_ids": "Closing degree entities",
|
||||||
|
"proportional_function": "Algorithm"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. There should be one per underlying climate entities",
|
||||||
|
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
|
||||||
|
"closing_degree_entity_ids": "The list of the 'closing degree' entities. There should be one per underlying climate entities",
|
||||||
|
"proportional_function": "Algorithm to use (TPI is the only one for now)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -262,6 +273,8 @@
|
|||||||
"power": "Power management",
|
"power": "Power management",
|
||||||
"presence": "Presence detection",
|
"presence": "Presence detection",
|
||||||
"advanced": "Advanced parameters",
|
"advanced": "Advanced parameters",
|
||||||
|
"auto_start_stop": "Auto start and stop",
|
||||||
|
"sonoff_trvzb": "Sonoff TRVZB configuration",
|
||||||
"finalize": "All done",
|
"finalize": "All done",
|
||||||
"configuration_not_complete": "Configuration not complete"
|
"configuration_not_complete": "Configuration not complete"
|
||||||
}
|
}
|
||||||
@@ -298,28 +311,19 @@
|
|||||||
"use_motion_feature": "Use motion detection",
|
"use_motion_feature": "Use motion detection",
|
||||||
"use_power_feature": "Use power management",
|
"use_power_feature": "Use power management",
|
||||||
"use_presence_feature": "Use presence detection",
|
"use_presence_feature": "Use presence detection",
|
||||||
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page"
|
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
|
||||||
|
"use_auto_start_stop_feature": "Use the auto start and stop feature"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"title": "Entities - {name}",
|
"title": "Entities - {name}",
|
||||||
"description": "Linked entities attributes",
|
"description": "Linked entities attributes",
|
||||||
"data": {
|
"data": {
|
||||||
"heater_entity_id": "1st heater switch",
|
"underlying_entity_ids": "The device(s) to be controlled",
|
||||||
"heater_entity2_id": "2nd heater switch",
|
|
||||||
"heater_entity3_id": "3rd heater switch",
|
|
||||||
"heater_entity4_id": "4th heater switch",
|
|
||||||
"heater_keep_alive": "Switch keep-alive interval in seconds",
|
"heater_keep_alive": "Switch keep-alive interval in seconds",
|
||||||
"proportional_function": "Algorithm",
|
"proportional_function": "Algorithm",
|
||||||
"climate_entity_id": "1st underlying climate",
|
|
||||||
"climate_entity2_id": "2nd underlying climate",
|
|
||||||
"climate_entity3_id": "3rd underlying climate",
|
|
||||||
"climate_entity4_id": "4th underlying climate",
|
|
||||||
"ac_mode": "AC mode",
|
"ac_mode": "AC mode",
|
||||||
"valve_entity_id": "1st valve number",
|
"sonoff_trvzb_mode": "SONOFF TRVZB mode",
|
||||||
"valve_entity2_id": "2nd valve number",
|
|
||||||
"valve_entity3_id": "3rd valve number",
|
|
||||||
"valve_entity4_id": "4th valve number",
|
|
||||||
"auto_regulation_mode": "Self-regulation",
|
"auto_regulation_mode": "Self-regulation",
|
||||||
"auto_regulation_dtemp": "Regulation threshold",
|
"auto_regulation_dtemp": "Regulation threshold",
|
||||||
"auto_regulation_periode_min": "Regulation minimum period",
|
"auto_regulation_periode_min": "Regulation minimum period",
|
||||||
@@ -328,21 +332,11 @@
|
|||||||
"auto_fan_mode": "Auto fan mode"
|
"auto_fan_mode": "Auto fan mode"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"heater_entity_id": "Mandatory heater entity id",
|
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
|
||||||
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not used",
|
|
||||||
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used",
|
|
||||||
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
|
|
||||||
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
|
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
|
||||||
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||||
"climate_entity_id": "Underlying climate entity id",
|
|
||||||
"climate_entity2_id": "2nd underlying climate entity id",
|
|
||||||
"climate_entity3_id": "3rd underlying climate entity id",
|
|
||||||
"climate_entity4_id": "4th underlying climate entity id",
|
|
||||||
"ac_mode": "Use the Air Conditioning (AC) mode",
|
"ac_mode": "Use the Air Conditioning (AC) mode",
|
||||||
"valve_entity_id": "1st valve number entity id",
|
"sonoff_trvzb_mode": "The underlyings are SONOFF TRVZB. You have to configure some extra entities in the specific menu option 'Sonoff trvzb configuration'",
|
||||||
"valve_entity2_id": "2nd valve number entity id",
|
|
||||||
"valve_entity3_id": "3rd valve number entity id",
|
|
||||||
"valve_entity4_id": "4th valve number entity id",
|
|
||||||
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
||||||
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
|
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
|
||||||
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
||||||
@@ -458,6 +452,34 @@
|
|||||||
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
|
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
|
||||||
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
|
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"central_boiler": {
|
||||||
|
"title": "Control of the central boiler - {name}",
|
||||||
|
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
|
||||||
|
"data": {
|
||||||
|
"central_boiler_activation_service": "Command to turn-on",
|
||||||
|
"central_boiler_deactivation_service": "Command to turn-off"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
|
||||||
|
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sonoff_trvzb": {
|
||||||
|
"title": "Sonoff TRVZB configuration - {name}",
|
||||||
|
"description": "Specific Sonoff TRVZB configuration",
|
||||||
|
"data": {
|
||||||
|
"offset_calibration_entity_ids": "Offset calibration entities",
|
||||||
|
"opening_degree_entity_ids": "Opening degree entities",
|
||||||
|
"closing_degree_entity_ids": "Closing degree entities",
|
||||||
|
"proportional_function": "Algorithm"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. There should be one per underlying climate entities",
|
||||||
|
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
|
||||||
|
"closing_degree_entity_ids": "The list of the 'closing degree' entities. There should be one per underlying climate entities",
|
||||||
|
"proportional_function": "Algorithm to use (TPI is the only one for now)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -465,7 +487,8 @@
|
|||||||
"unknown_entity": "Unknown entity id",
|
"unknown_entity": "Unknown entity id",
|
||||||
"window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both",
|
"window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both",
|
||||||
"no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.",
|
"no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.",
|
||||||
"service_configuration_format": "The format of the service configuration is wrong"
|
"service_configuration_format": "The format of the service configuration is wrong",
|
||||||
|
"sonoff_trvzb_nb_entities_incorrect": "The number of specific entities for Sonoff TRVZB should be equal to the number of underlyings"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Device is already configured"
|
"already_configured": "Device is already configured"
|
||||||
@@ -514,6 +537,14 @@
|
|||||||
"comfort": "Comfort",
|
"comfort": "Comfort",
|
||||||
"boost": "Boost"
|
"boost": "Boost"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"auto_start_stop": {
|
||||||
|
"options": {
|
||||||
|
"auto_start_stop_none": "No auto start/stop",
|
||||||
|
"auto_start_stop_slow": "Slow detection",
|
||||||
|
"auto_start_stop_medium": "Medium detection",
|
||||||
|
"auto_start_stop_fast": "Fast detection"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
@@ -524,7 +555,8 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"power": "Shedding",
|
"power": "Shedding",
|
||||||
"security": "Safety",
|
"security": "Safety",
|
||||||
"none": "Manual"
|
"none": "Manual",
|
||||||
|
"frost": "Frost"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
168
custom_components/versatile_thermostat/switch.py
Normal file
168
custom_components/versatile_thermostat/switch.py
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
## pylint: disable=unused-argument
|
||||||
|
|
||||||
|
""" Implements the VersatileThermostat select component """
|
||||||
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
|
||||||
|
from homeassistant.components.switch import SwitchEntity
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.helpers.restore_state import RestoreEntity
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from .commons import VersatileThermostatBaseEntity
|
||||||
|
|
||||||
|
from .const import * # pylint: disable=unused-wildcard-import,wildcard-import
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up the VersatileThermostat switches 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)
|
||||||
|
auto_start_stop_feature = entry.data.get(CONF_USE_AUTO_START_STOP_FEATURE)
|
||||||
|
|
||||||
|
entities = []
|
||||||
|
if vt_type == CONF_THERMOSTAT_CLIMATE:
|
||||||
|
entities.append(FollowUnderlyingTemperatureChange(hass, unique_id, name, entry))
|
||||||
|
|
||||||
|
if auto_start_stop_feature is True:
|
||||||
|
# Creates a switch to enable the auto-start/stop
|
||||||
|
enable_entity = AutoStartStopEnable(hass, unique_id, name, entry)
|
||||||
|
entities.append(enable_entity)
|
||||||
|
|
||||||
|
async_add_entities(entities, True)
|
||||||
|
|
||||||
|
|
||||||
|
class AutoStartStopEnable(VersatileThermostatBaseEntity, SwitchEntity, RestoreEntity):
|
||||||
|
"""The that enables the ManagedDevice optimisation with"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigEntry
|
||||||
|
):
|
||||||
|
super().__init__(hass, unique_id, name)
|
||||||
|
self._attr_name = "Enable auto start/stop"
|
||||||
|
self._attr_unique_id = f"{self._device_name}_enable_auto_start_stop"
|
||||||
|
self._default_value = (
|
||||||
|
entry_infos.data.get(CONF_AUTO_START_STOP_LEVEL)
|
||||||
|
!= AUTO_START_STOP_LEVEL_NONE
|
||||||
|
)
|
||||||
|
self._attr_is_on = self._default_value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self) -> str | None:
|
||||||
|
"""The icon"""
|
||||||
|
return "mdi:power-sleep"
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
await super().async_added_to_hass()
|
||||||
|
|
||||||
|
# Récupérer le dernier état sauvegardé de l'entité
|
||||||
|
last_state = await self.async_get_last_state()
|
||||||
|
|
||||||
|
# Si l'état précédent existe, vous pouvez l'utiliser
|
||||||
|
if last_state is not None:
|
||||||
|
self._attr_is_on = last_state.state == "on"
|
||||||
|
else:
|
||||||
|
# If no previous state set it to false by default
|
||||||
|
self._attr_is_on = self._default_value
|
||||||
|
|
||||||
|
self.update_my_state_and_vtherm()
|
||||||
|
|
||||||
|
def update_my_state_and_vtherm(self):
|
||||||
|
"""Update the auto_start_stop_enable flag in my VTherm"""
|
||||||
|
self.async_write_ha_state()
|
||||||
|
if self.my_climate is not None:
|
||||||
|
self.my_climate.set_auto_start_stop_enable(self._attr_is_on)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the entity on."""
|
||||||
|
self.turn_on()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the entity off."""
|
||||||
|
self.turn_off()
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
def turn_off(self, **kwargs: Any):
|
||||||
|
self._attr_is_on = False
|
||||||
|
self.update_my_state_and_vtherm()
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
def turn_on(self, **kwargs: Any):
|
||||||
|
self._attr_is_on = True
|
||||||
|
self.update_my_state_and_vtherm()
|
||||||
|
|
||||||
|
|
||||||
|
class FollowUnderlyingTemperatureChange(
|
||||||
|
VersatileThermostatBaseEntity, SwitchEntity, RestoreEntity
|
||||||
|
):
|
||||||
|
"""The that enables the ManagedDevice optimisation with"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigEntry
|
||||||
|
):
|
||||||
|
super().__init__(hass, unique_id, name)
|
||||||
|
self._attr_name = "Follow underlying temp change"
|
||||||
|
self._attr_unique_id = f"{self._device_name}_follow_underlying_temp_change"
|
||||||
|
self._attr_is_on = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def icon(self) -> str | None:
|
||||||
|
"""The icon"""
|
||||||
|
return "mdi:content-copy"
|
||||||
|
|
||||||
|
async def async_added_to_hass(self):
|
||||||
|
await super().async_added_to_hass()
|
||||||
|
|
||||||
|
# Récupérer le dernier état sauvegardé de l'entité
|
||||||
|
last_state = await self.async_get_last_state()
|
||||||
|
|
||||||
|
# Si l'état précédent existe, vous pouvez l'utiliser
|
||||||
|
if last_state is not None:
|
||||||
|
self._attr_is_on = last_state.state == "on"
|
||||||
|
else:
|
||||||
|
# If no previous state set it to false by default
|
||||||
|
self._attr_is_on = False
|
||||||
|
|
||||||
|
self.update_my_state_and_vtherm()
|
||||||
|
|
||||||
|
def update_my_state_and_vtherm(self):
|
||||||
|
"""Update the follow flag in my VTherm"""
|
||||||
|
self.async_write_ha_state()
|
||||||
|
if self.my_climate is not None:
|
||||||
|
self.my_climate.set_follow_underlying_temp_change(self._attr_is_on)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the entity on."""
|
||||||
|
self.turn_on()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn the entity off."""
|
||||||
|
self.turn_off()
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
def turn_off(self, **kwargs: Any):
|
||||||
|
self._attr_is_on = False
|
||||||
|
self.update_my_state_and_vtherm()
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
def turn_on(self, **kwargs: Any):
|
||||||
|
self._attr_is_on = True
|
||||||
|
self.update_my_state_and_vtherm()
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# pylint: disable=line-too-long, too-many-lines
|
# pylint: disable=line-too-long, too-many-lines, abstract-method
|
||||||
""" A climate over switch classe """
|
""" A climate over climate classe """
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta, datetime
|
from datetime import timedelta, datetime
|
||||||
|
|
||||||
@@ -20,40 +20,15 @@ from .commons import NowClass, round_to_nearest
|
|||||||
from .base_thermostat import BaseThermostat, ConfigData
|
from .base_thermostat import BaseThermostat, ConfigData
|
||||||
from .pi_algorithm import PITemperatureRegulator
|
from .pi_algorithm import PITemperatureRegulator
|
||||||
|
|
||||||
from .const import (
|
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
overrides,
|
|
||||||
DOMAIN,
|
|
||||||
CONF_CLIMATE,
|
|
||||||
CONF_CLIMATE_2,
|
|
||||||
CONF_CLIMATE_3,
|
|
||||||
CONF_CLIMATE_4,
|
|
||||||
CONF_AUTO_REGULATION_MODE,
|
|
||||||
CONF_AUTO_REGULATION_NONE,
|
|
||||||
CONF_AUTO_REGULATION_SLOW,
|
|
||||||
CONF_AUTO_REGULATION_LIGHT,
|
|
||||||
CONF_AUTO_REGULATION_MEDIUM,
|
|
||||||
CONF_AUTO_REGULATION_STRONG,
|
|
||||||
CONF_AUTO_REGULATION_EXPERT,
|
|
||||||
CONF_AUTO_REGULATION_DTEMP,
|
|
||||||
CONF_AUTO_REGULATION_PERIOD_MIN,
|
|
||||||
CONF_AUTO_REGULATION_USE_DEVICE_TEMP,
|
|
||||||
CONF_AUTO_FAN_MODE,
|
|
||||||
CONF_AUTO_FAN_NONE,
|
|
||||||
CONF_AUTO_FAN_LOW,
|
|
||||||
CONF_AUTO_FAN_MEDIUM,
|
|
||||||
CONF_AUTO_FAN_HIGH,
|
|
||||||
CONF_AUTO_FAN_TURBO,
|
|
||||||
RegulationParamSlow,
|
|
||||||
RegulationParamLight,
|
|
||||||
RegulationParamMedium,
|
|
||||||
RegulationParamStrong,
|
|
||||||
AUTO_FAN_DTEMP_THRESHOLD,
|
|
||||||
AUTO_FAN_DEACTIVATED_MODES,
|
|
||||||
UnknownEntity,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .vtherm_api import VersatileThermostatAPI
|
from .vtherm_api import VersatileThermostatAPI
|
||||||
from .underlyings import UnderlyingClimate
|
from .underlyings import UnderlyingClimate
|
||||||
|
from .auto_start_stop_algorithm import (
|
||||||
|
AutoStartStopDetectionAlgorithm,
|
||||||
|
AUTO_START_STOP_ACTION_OFF,
|
||||||
|
AUTO_START_STOP_ACTION_ON,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -64,7 +39,6 @@ HVAC_ACTION_ON = [ # pylint: disable=invalid-name
|
|||||||
HVACAction.HEATING,
|
HVACAction.HEATING,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
||||||
"""Representation of a base class for a Versatile Thermostat over a climate"""
|
"""Representation of a base class for a Versatile Thermostat over a climate"""
|
||||||
|
|
||||||
@@ -81,26 +55,31 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
# The fan_mode name depending of the current_mode
|
# The fan_mode name depending of the current_mode
|
||||||
_auto_activated_fan_mode: str | None = None
|
_auto_activated_fan_mode: str | None = None
|
||||||
_auto_deactivated_fan_mode: str | None = None
|
_auto_deactivated_fan_mode: str | None = None
|
||||||
|
_auto_start_stop_level: TYPE_AUTO_START_STOP_LEVELS = AUTO_START_STOP_LEVEL_NONE
|
||||||
|
_auto_start_stop_algo: AutoStartStopDetectionAlgorithm | None = None
|
||||||
|
_is_auto_start_stop_enabled: bool = False
|
||||||
|
_follow_underlying_temp_change: bool = False
|
||||||
|
|
||||||
_entity_component_unrecorded_attributes = (
|
_entity_component_unrecorded_attributes = BaseThermostat._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
|
||||||
BaseThermostat._entity_component_unrecorded_attributes.union(
|
frozenset(
|
||||||
frozenset(
|
{
|
||||||
{
|
"is_over_climate",
|
||||||
"is_over_climate",
|
"start_hvac_action_date",
|
||||||
"start_hvac_action_date",
|
"underlying_entities",
|
||||||
"underlying_climate_0",
|
"regulation_accumulated_error",
|
||||||
"underlying_climate_1",
|
"auto_regulation_mode",
|
||||||
"underlying_climate_2",
|
"auto_fan_mode",
|
||||||
"underlying_climate_3",
|
"current_auto_fan_mode",
|
||||||
"regulation_accumulated_error",
|
"auto_activated_fan_mode",
|
||||||
"auto_regulation_mode",
|
"auto_deactivated_fan_mode",
|
||||||
"auto_fan_mode",
|
"auto_regulation_use_device_temp",
|
||||||
"current_auto_fan_mode",
|
"auto_start_stop_level",
|
||||||
"auto_activated_fan_mode",
|
"auto_start_stop_dtmin",
|
||||||
"auto_deactivated_fan_mode",
|
"auto_start_stop_enable",
|
||||||
"auto_regulation_use_device_temp",
|
"auto_start_stop_accumulated_error",
|
||||||
}
|
"auto_start_stop_accumulated_error_threshold",
|
||||||
)
|
"follow_underlying_temp_change",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -113,20 +92,72 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
self._regulated_target_temp = self.target_temperature
|
self._regulated_target_temp = self.target_temperature
|
||||||
self._last_regulation_change = NowClass.get_now(hass)
|
self._last_regulation_change = NowClass.get_now(hass)
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
def post_init(self, config_entry: ConfigData):
|
||||||
|
"""Initialize the Thermostat"""
|
||||||
|
|
||||||
|
super().post_init(config_entry)
|
||||||
|
|
||||||
|
for climate in config_entry.get(CONF_UNDERLYING_LIST):
|
||||||
|
under = UnderlyingClimate(
|
||||||
|
hass=self._hass,
|
||||||
|
thermostat=self,
|
||||||
|
climate_entity_id=climate,
|
||||||
|
)
|
||||||
|
self._underlyings.append(under)
|
||||||
|
|
||||||
|
self.choose_auto_regulation_mode(
|
||||||
|
config_entry.get(CONF_AUTO_REGULATION_MODE)
|
||||||
|
if config_entry.get(CONF_AUTO_REGULATION_MODE) is not None
|
||||||
|
else CONF_AUTO_REGULATION_NONE
|
||||||
|
)
|
||||||
|
|
||||||
|
self._auto_regulation_dtemp = (
|
||||||
|
config_entry.get(CONF_AUTO_REGULATION_DTEMP)
|
||||||
|
if config_entry.get(CONF_AUTO_REGULATION_DTEMP) is not None
|
||||||
|
else 0.5
|
||||||
|
)
|
||||||
|
self._auto_regulation_period_min = (
|
||||||
|
config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN)
|
||||||
|
if config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None
|
||||||
|
else 5
|
||||||
|
)
|
||||||
|
|
||||||
|
self._auto_fan_mode = (
|
||||||
|
config_entry.get(CONF_AUTO_FAN_MODE)
|
||||||
|
if config_entry.get(CONF_AUTO_FAN_MODE) is not None
|
||||||
|
else CONF_AUTO_FAN_NONE
|
||||||
|
)
|
||||||
|
|
||||||
|
self._auto_regulation_use_device_temp = config_entry.get(
|
||||||
|
CONF_AUTO_REGULATION_USE_DEVICE_TEMP, False
|
||||||
|
)
|
||||||
|
|
||||||
|
use_auto_start_stop = config_entry.get(CONF_USE_AUTO_START_STOP_FEATURE, False)
|
||||||
|
if use_auto_start_stop:
|
||||||
|
self._auto_start_stop_level = config_entry.get(
|
||||||
|
CONF_AUTO_START_STOP_LEVEL, AUTO_START_STOP_LEVEL_NONE
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self._auto_start_stop_level = AUTO_START_STOP_LEVEL_NONE
|
||||||
|
|
||||||
|
# Instanciate the auto start stop algo
|
||||||
|
self._auto_start_stop_algo = AutoStartStopDetectionAlgorithm(
|
||||||
|
self._auto_start_stop_level, self.name
|
||||||
|
)
|
||||||
|
|
||||||
@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 True
|
return True
|
||||||
|
|
||||||
@property
|
def calculate_hvac_action(self, under_list: list) -> HVACAction | None:
|
||||||
def hvac_action(self) -> HVACAction | None:
|
"""Calculate an hvac action based on the hvac_action of the list in argument"""
|
||||||
"""Returns the current hvac_action by checking all hvac_action of the underlyings"""
|
|
||||||
|
|
||||||
# if one not IDLE or OFF -> return it
|
# if one not IDLE or OFF -> return it
|
||||||
# else if one IDLE -> IDLE
|
# else if one IDLE -> IDLE
|
||||||
# else OFF
|
# else OFF
|
||||||
one_idle = False
|
one_idle = False
|
||||||
for under in self._underlyings:
|
for under in under_list:
|
||||||
if (action := under.hvac_action) not in [
|
if (action := under.hvac_action) not in [
|
||||||
HVACAction.IDLE,
|
HVACAction.IDLE,
|
||||||
HVACAction.OFF,
|
HVACAction.OFF,
|
||||||
@@ -138,6 +169,11 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
return HVACAction.IDLE
|
return HVACAction.IDLE
|
||||||
return HVACAction.OFF
|
return HVACAction.OFF
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self) -> HVACAction | None:
|
||||||
|
"""Returns the current hvac_action by checking all hvac_action of the underlyings"""
|
||||||
|
return self.calculate_hvac_action(self._underlyings)
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
async def _async_internal_set_temperature(self, temperature: float):
|
async def _async_internal_set_temperature(self, temperature: float):
|
||||||
"""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"""
|
||||||
@@ -228,18 +264,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
and self.auto_regulation_use_device_temp
|
and self.auto_regulation_use_device_temp
|
||||||
# and we have access to the device temp
|
# and we have access to the device temp
|
||||||
and (device_temp := under.underlying_current_temperature) is not None
|
and (device_temp := under.underlying_current_temperature) is not None
|
||||||
# issue 467 - always apply offset. TODO removes this if ok
|
|
||||||
# and target is not reach (ie we need regulation)
|
|
||||||
# and (
|
|
||||||
# (
|
|
||||||
# self.hvac_mode == HVACMode.COOL
|
|
||||||
# and self.target_temperature < self.current_temperature
|
|
||||||
# )
|
|
||||||
# or (
|
|
||||||
# self.hvac_mode == HVACMode.HEAT
|
|
||||||
# and self.target_temperature > self.current_temperature
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
):
|
):
|
||||||
offset_temp = device_temp - self.current_temperature
|
offset_temp = device_temp - self.current_temperature
|
||||||
|
|
||||||
@@ -304,53 +328,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
)
|
)
|
||||||
await self.async_set_fan_mode(self._auto_deactivated_fan_mode)
|
await self.async_set_fan_mode(self._auto_deactivated_fan_mode)
|
||||||
|
|
||||||
@overrides
|
|
||||||
def post_init(self, config_entry: ConfigData):
|
|
||||||
"""Initialize the Thermostat"""
|
|
||||||
|
|
||||||
super().post_init(config_entry)
|
|
||||||
for climate in [
|
|
||||||
CONF_CLIMATE,
|
|
||||||
CONF_CLIMATE_2,
|
|
||||||
CONF_CLIMATE_3,
|
|
||||||
CONF_CLIMATE_4,
|
|
||||||
]:
|
|
||||||
if config_entry.get(climate):
|
|
||||||
self._underlyings.append(
|
|
||||||
UnderlyingClimate(
|
|
||||||
hass=self._hass,
|
|
||||||
thermostat=self,
|
|
||||||
climate_entity_id=config_entry.get(climate),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.choose_auto_regulation_mode(
|
|
||||||
config_entry.get(CONF_AUTO_REGULATION_MODE)
|
|
||||||
if config_entry.get(CONF_AUTO_REGULATION_MODE) is not None
|
|
||||||
else CONF_AUTO_REGULATION_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
self._auto_regulation_dtemp = (
|
|
||||||
config_entry.get(CONF_AUTO_REGULATION_DTEMP)
|
|
||||||
if config_entry.get(CONF_AUTO_REGULATION_DTEMP) is not None
|
|
||||||
else 0.5
|
|
||||||
)
|
|
||||||
self._auto_regulation_period_min = (
|
|
||||||
config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN)
|
|
||||||
if config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None
|
|
||||||
else 5
|
|
||||||
)
|
|
||||||
|
|
||||||
self._auto_fan_mode = (
|
|
||||||
config_entry.get(CONF_AUTO_FAN_MODE)
|
|
||||||
if config_entry.get(CONF_AUTO_FAN_MODE) is not None
|
|
||||||
else CONF_AUTO_FAN_NONE
|
|
||||||
)
|
|
||||||
|
|
||||||
self._auto_regulation_use_device_temp = config_entry.get(
|
|
||||||
CONF_AUTO_REGULATION_USE_DEVICE_TEMP, False
|
|
||||||
)
|
|
||||||
|
|
||||||
def choose_auto_regulation_mode(self, auto_regulation_mode: str):
|
def choose_auto_regulation_mode(self, auto_regulation_mode: str):
|
||||||
"""Choose or change the regulation mode"""
|
"""Choose or change the regulation mode"""
|
||||||
self._auto_regulation_mode = auto_regulation_mode
|
self._auto_regulation_mode = auto_regulation_mode
|
||||||
@@ -521,18 +498,10 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
self._attr_extra_state_attributes["start_hvac_action_date"] = (
|
self._attr_extra_state_attributes["start_hvac_action_date"] = (
|
||||||
self._underlying_climate_start_hvac_action_date
|
self._underlying_climate_start_hvac_action_date
|
||||||
)
|
)
|
||||||
self._attr_extra_state_attributes["underlying_climate_0"] = self._underlyings[
|
|
||||||
0
|
self._attr_extra_state_attributes["underlying_entities"] = [
|
||||||
].entity_id
|
underlying.entity_id for underlying in self._underlyings
|
||||||
self._attr_extra_state_attributes["underlying_climate_1"] = (
|
]
|
||||||
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
|
|
||||||
)
|
|
||||||
self._attr_extra_state_attributes["underlying_climate_2"] = (
|
|
||||||
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
|
|
||||||
)
|
|
||||||
self._attr_extra_state_attributes["underlying_climate_3"] = (
|
|
||||||
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.is_regulated:
|
if self.is_regulated:
|
||||||
self._attr_extra_state_attributes["is_regulated"] = self.is_regulated
|
self._attr_extra_state_attributes["is_regulated"] = self.is_regulated
|
||||||
@@ -563,7 +532,30 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
self.auto_regulation_use_device_temp
|
self.auto_regulation_use_device_temp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._attr_extra_state_attributes["auto_start_stop_enable"] = (
|
||||||
|
self.auto_start_stop_enable
|
||||||
|
)
|
||||||
|
|
||||||
|
self._attr_extra_state_attributes["auto_start_stop_level"] = (
|
||||||
|
self._auto_start_stop_algo.level
|
||||||
|
)
|
||||||
|
self._attr_extra_state_attributes["auto_start_stop_dtmin"] = (
|
||||||
|
self._auto_start_stop_algo.dt_min
|
||||||
|
)
|
||||||
|
self._attr_extra_state_attributes["auto_start_stop_accumulated_error"] = (
|
||||||
|
self._auto_start_stop_algo.accumulated_error
|
||||||
|
)
|
||||||
|
|
||||||
|
self._attr_extra_state_attributes[
|
||||||
|
"auto_start_stop_accumulated_error_threshold"
|
||||||
|
] = self._auto_start_stop_algo.accumulated_error_threshold
|
||||||
|
|
||||||
|
self._attr_extra_state_attributes["follow_underlying_temp_change"] = (
|
||||||
|
self._follow_underlying_temp_change
|
||||||
|
)
|
||||||
|
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - Calling update_custom_attributes: %s",
|
"%s - Calling update_custom_attributes: %s",
|
||||||
self,
|
self,
|
||||||
@@ -610,8 +602,18 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
|
|
||||||
if self._total_energy is None:
|
if self._total_energy is None:
|
||||||
self._total_energy = added_energy
|
self._total_energy = added_energy
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - incremente_energy set energy is %s",
|
||||||
|
self,
|
||||||
|
self._total_energy,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self._total_energy += added_energy
|
self._total_energy += added_energy
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - incremente_energy incremented energy is %s",
|
||||||
|
self,
|
||||||
|
self._total_energy,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s - added energy is %.3f . Total energy is now: %.3f",
|
"%s - added energy is %.3f . Total energy is now: %.3f",
|
||||||
@@ -719,7 +721,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Forget event when the new target temperature is out of range
|
# Ignore new target temperature when out of range
|
||||||
if (
|
if (
|
||||||
not new_target_temp is None
|
not new_target_temp is None
|
||||||
and not self._attr_min_temp is None
|
and not self._attr_min_temp is None
|
||||||
@@ -733,7 +735,8 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
self._attr_min_temp,
|
self._attr_min_temp,
|
||||||
self._attr_max_temp,
|
self._attr_max_temp,
|
||||||
)
|
)
|
||||||
return
|
new_target_temp = None
|
||||||
|
under_temp_diff = 0
|
||||||
|
|
||||||
# A real changes have to be managed
|
# A real changes have to be managed
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
@@ -852,7 +855,12 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
changes = True
|
changes = True
|
||||||
|
|
||||||
# try to manage new target temperature set if state if no other changes have been found
|
# try to manage new target temperature set if state if no other changes have been found
|
||||||
if not changes:
|
# and if a target temperature have already been sent
|
||||||
|
if (
|
||||||
|
self._follow_underlying_temp_change
|
||||||
|
and not changes
|
||||||
|
and under.last_sent_temperature is not None
|
||||||
|
):
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Do temperature check. under.last_sent_temperature is %s, new_target_temp is %s",
|
"Do temperature check. under.last_sent_temperature is %s, new_target_temp is %s",
|
||||||
under.last_sent_temperature,
|
under.last_sent_temperature,
|
||||||
@@ -876,11 +884,92 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
|
|
||||||
await end_climate_changed(changes)
|
await end_climate_changed(changes)
|
||||||
|
|
||||||
|
async def check_auto_start_stop(self):
|
||||||
|
"""Check the auto-start-stop and an eventual action
|
||||||
|
Return False if we should stop the control_heating method"""
|
||||||
|
slope = (self.last_temperature_slope or 0) / 60 # to have the slope in °/min
|
||||||
|
action = self._auto_start_stop_algo.calculate_action(
|
||||||
|
self.hvac_mode,
|
||||||
|
self._saved_hvac_mode,
|
||||||
|
self.target_temperature,
|
||||||
|
self.current_temperature,
|
||||||
|
slope,
|
||||||
|
self.now,
|
||||||
|
)
|
||||||
|
_LOGGER.debug("%s - auto_start_stop action is %s", self, action)
|
||||||
|
if action == AUTO_START_STOP_ACTION_OFF and self.is_on:
|
||||||
|
_LOGGER.info(
|
||||||
|
"%s - Turning OFF the Vtherm due to auto-start-stop conditions",
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
self.set_hvac_off_reason(HVAC_OFF_REASON_AUTO_START_STOP)
|
||||||
|
await self.async_turn_off()
|
||||||
|
|
||||||
|
# Send an event
|
||||||
|
self.send_event(
|
||||||
|
event_type=EventType.AUTO_START_STOP_EVENT,
|
||||||
|
data={
|
||||||
|
"type": "stop",
|
||||||
|
"name": self.name,
|
||||||
|
"cause": "Auto stop conditions reached",
|
||||||
|
"hvac_mode": self.hvac_mode,
|
||||||
|
"saved_hvac_mode": self._saved_hvac_mode,
|
||||||
|
"target_temperature": self.target_temperature,
|
||||||
|
"current_temperature": self.current_temperature,
|
||||||
|
"temperature_slope": round(slope, 3),
|
||||||
|
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
|
||||||
|
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stop here
|
||||||
|
return False
|
||||||
|
elif (
|
||||||
|
action == AUTO_START_STOP_ACTION_ON
|
||||||
|
and self.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
|
||||||
|
):
|
||||||
|
_LOGGER.info(
|
||||||
|
"%s - Turning ON the Vtherm due to auto-start-stop conditions", self
|
||||||
|
)
|
||||||
|
await self.async_turn_on()
|
||||||
|
|
||||||
|
# Send an event
|
||||||
|
self.send_event(
|
||||||
|
event_type=EventType.AUTO_START_STOP_EVENT,
|
||||||
|
data={
|
||||||
|
"type": "start",
|
||||||
|
"name": self.name,
|
||||||
|
"cause": "Auto start conditions reached",
|
||||||
|
"hvac_mode": self.hvac_mode,
|
||||||
|
"saved_hvac_mode": self._saved_hvac_mode,
|
||||||
|
"target_temperature": self.target_temperature,
|
||||||
|
"current_temperature": self.current_temperature,
|
||||||
|
"temperature_slope": round(slope, 3),
|
||||||
|
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
|
||||||
|
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.update_custom_attributes()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
async def async_control_heating(self, force=False, _=None) -> bool:
|
async def async_control_heating(self, force=False, _=None) -> bool:
|
||||||
"""The main function used to run the calculation at each cycle"""
|
"""The main function used to run the calculation at each cycle"""
|
||||||
ret = await super().async_control_heating(force, _)
|
ret = await super().async_control_heating(force, _)
|
||||||
|
|
||||||
|
# Check if we need to auto start/stop the Vtherm
|
||||||
|
if self.auto_start_stop_enable:
|
||||||
|
continu = await self.check_auto_start_stop()
|
||||||
|
if not continu:
|
||||||
|
return ret
|
||||||
|
else:
|
||||||
|
_LOGGER.debug("%s - auto start/stop is disabled", self)
|
||||||
|
|
||||||
|
# Continue the normal async_control_heating
|
||||||
|
|
||||||
|
# Send the regulated temperature to the underlyings
|
||||||
await self._send_regulated_temperature()
|
await self._send_regulated_temperature()
|
||||||
|
|
||||||
if self._auto_fan_mode and self._auto_fan_mode != CONF_AUTO_FAN_NONE:
|
if self._auto_fan_mode and self._auto_fan_mode != CONF_AUTO_FAN_NONE:
|
||||||
@@ -888,6 +977,16 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def set_auto_start_stop_enable(self, is_enabled: bool):
|
||||||
|
"""Enable/Disable the auto-start/stop feature"""
|
||||||
|
self._is_auto_start_stop_enabled = is_enabled
|
||||||
|
self.update_custom_attributes()
|
||||||
|
|
||||||
|
def set_follow_underlying_temp_change(self, follow: bool):
|
||||||
|
"""Set the flaf follow the underlying temperature changes"""
|
||||||
|
self._follow_underlying_temp_change = follow
|
||||||
|
self.update_custom_attributes()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auto_regulation_mode(self) -> str | None:
|
def auto_regulation_mode(self) -> str | None:
|
||||||
"""Get the regulation mode"""
|
"""Get the regulation mode"""
|
||||||
@@ -1015,6 +1114,14 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_humidity(self) -> float | None:
|
||||||
|
"""Return the humidity."""
|
||||||
|
if self.underlying_entity(0):
|
||||||
|
return self.underlying_entity(0).current_humidity
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_aux_heat(self) -> bool | None:
|
def is_aux_heat(self) -> bool | None:
|
||||||
"""Return true if aux heater.
|
"""Return true if aux heater.
|
||||||
@@ -1034,6 +1141,21 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def auto_start_stop_level(self) -> TYPE_AUTO_START_STOP_LEVELS:
|
||||||
|
"""Return the auto start/stop level."""
|
||||||
|
return self._auto_start_stop_level
|
||||||
|
|
||||||
|
@property
|
||||||
|
def auto_start_stop_enable(self) -> bool:
|
||||||
|
"""Returns the auto_start_stop_enable"""
|
||||||
|
return self._is_auto_start_stop_enabled
|
||||||
|
|
||||||
|
@property
|
||||||
|
def follow_underlying_temp_change(self) -> bool:
|
||||||
|
"""Get the follow underlying temp change flag"""
|
||||||
|
return self._follow_underlying_temp_change
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def init_underlyings(self):
|
def init_underlyings(self):
|
||||||
"""Init the underlyings if not already done"""
|
"""Init the underlyings if not already done"""
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
# pylint: disable=line-too-long, too-many-lines, abstract-method
|
||||||
|
""" A climate over Sonoff TRVZB classe """
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.components.climate import HVACMode, HVACAction
|
||||||
|
|
||||||
|
from .underlyings import UnderlyingSonoffTRVZB
|
||||||
|
|
||||||
|
# from .commons import NowClass, round_to_nearest
|
||||||
|
from .base_thermostat import ConfigData
|
||||||
|
from .thermostat_climate import ThermostatOverClimate
|
||||||
|
from .prop_algorithm import PropAlgorithm
|
||||||
|
|
||||||
|
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||||
|
|
||||||
|
# from .vtherm_api import VersatileThermostatAPI
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ThermostatOverSonoffTRVZB(ThermostatOverClimate):
|
||||||
|
"""This class represent a VTherm over a Sonoff TRVZB climate"""
|
||||||
|
|
||||||
|
_entity_component_unrecorded_attributes = ThermostatOverClimate._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
|
||||||
|
frozenset(
|
||||||
|
{
|
||||||
|
"is_over_climate",
|
||||||
|
"is_over_sonoff_trvzb",
|
||||||
|
"underlying_entities",
|
||||||
|
"on_time_sec",
|
||||||
|
"off_time_sec",
|
||||||
|
"cycle_min",
|
||||||
|
"function",
|
||||||
|
"tpi_coef_int",
|
||||||
|
"tpi_coef_ext",
|
||||||
|
"power_percent",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_underlyings_sonoff_trvzb: list[UnderlyingSonoffTRVZB] = []
|
||||||
|
_valve_open_percent: int = 0
|
||||||
|
_last_calculation_timestamp: datetime | None = None
|
||||||
|
_auto_regulation_dpercent: float | None = None
|
||||||
|
_auto_regulation_period_min: int | None = None
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigData
|
||||||
|
):
|
||||||
|
"""Initialize the ThermostatOverSonoffTRVZB class"""
|
||||||
|
_LOGGER.debug("%s - creating a ThermostatOverSonoffTRVZB VTherm", name)
|
||||||
|
super().__init__(hass, unique_id, name, entry_infos)
|
||||||
|
# self._valve_open_percent: int = 0
|
||||||
|
# self._last_calculation_timestamp: datetime | None = None
|
||||||
|
# self._auto_regulation_dpercent: float | None = None
|
||||||
|
# self._auto_regulation_period_min: int | None = None
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
def post_init(self, config_entry: ConfigData):
|
||||||
|
"""Initialize the Thermostat and underlyings
|
||||||
|
Beware that the underlyings list contains the climate which represent the Sonoff TRVZB
|
||||||
|
but also the UnderlyingSonoff which reprensent the valve"""
|
||||||
|
|
||||||
|
super().post_init(config_entry)
|
||||||
|
|
||||||
|
self._auto_regulation_dpercent = (
|
||||||
|
config_entry.get(CONF_AUTO_REGULATION_DTEMP)
|
||||||
|
if config_entry.get(CONF_AUTO_REGULATION_DTEMP) is not None
|
||||||
|
else 0.0
|
||||||
|
)
|
||||||
|
self._auto_regulation_period_min = (
|
||||||
|
config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN)
|
||||||
|
if config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialization of the TPI algo
|
||||||
|
self._prop_algorithm = PropAlgorithm(
|
||||||
|
self._proportional_function,
|
||||||
|
self._tpi_coef_int,
|
||||||
|
self._tpi_coef_ext,
|
||||||
|
self._cycle_min,
|
||||||
|
self._minimal_activation_delay,
|
||||||
|
self.name,
|
||||||
|
)
|
||||||
|
|
||||||
|
for idx, _ in enumerate(config_entry.get(CONF_UNDERLYING_LIST)):
|
||||||
|
offset = config_entry.get(CONF_OFFSET_CALIBRATION_LIST)[idx]
|
||||||
|
opening = config_entry.get(CONF_OPENING_DEGREE_LIST)[idx]
|
||||||
|
closing = config_entry.get(CONF_CLOSING_DEGREE_LIST)[idx]
|
||||||
|
under = UnderlyingSonoffTRVZB(
|
||||||
|
hass=self._hass,
|
||||||
|
thermostat=self,
|
||||||
|
offset_calibration_entity_id=offset,
|
||||||
|
opening_degree_entity_id=opening,
|
||||||
|
closing_degree_entity_id=closing,
|
||||||
|
climate_underlying=self._underlyings[idx],
|
||||||
|
)
|
||||||
|
self._underlyings_sonoff_trvzb.append(under)
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
def update_custom_attributes(self):
|
||||||
|
"""Custom attributes"""
|
||||||
|
super().update_custom_attributes()
|
||||||
|
|
||||||
|
self._attr_extra_state_attributes["is_over_sonoff_trvzb"] = (
|
||||||
|
self.is_over_sonoff_trvzb
|
||||||
|
)
|
||||||
|
|
||||||
|
self._attr_extra_state_attributes["underlying_sonoff_trvzb_entities"] = [
|
||||||
|
underlying.entity_id for underlying in self._underlyings_sonoff_trvzb
|
||||||
|
]
|
||||||
|
|
||||||
|
self._attr_extra_state_attributes["on_percent"] = (
|
||||||
|
self._prop_algorithm.on_percent
|
||||||
|
)
|
||||||
|
self._attr_extra_state_attributes["power_percent"] = self.power_percent
|
||||||
|
self._attr_extra_state_attributes["on_time_sec"] = (
|
||||||
|
self._prop_algorithm.on_time_sec
|
||||||
|
)
|
||||||
|
self._attr_extra_state_attributes["off_time_sec"] = (
|
||||||
|
self._prop_algorithm.off_time_sec
|
||||||
|
)
|
||||||
|
self._attr_extra_state_attributes["cycle_min"] = self._cycle_min
|
||||||
|
self._attr_extra_state_attributes["function"] = self._proportional_function
|
||||||
|
self._attr_extra_state_attributes["tpi_coef_int"] = self._tpi_coef_int
|
||||||
|
self._attr_extra_state_attributes["tpi_coef_ext"] = self._tpi_coef_ext
|
||||||
|
|
||||||
|
self._attr_extra_state_attributes["valve_open_percent"] = (
|
||||||
|
self.valve_open_percent
|
||||||
|
)
|
||||||
|
|
||||||
|
self._attr_extra_state_attributes["auto_regulation_dpercent"] = (
|
||||||
|
self._auto_regulation_dpercent
|
||||||
|
)
|
||||||
|
self._attr_extra_state_attributes["auto_regulation_period_min"] = (
|
||||||
|
self._auto_regulation_period_min
|
||||||
|
)
|
||||||
|
self._attr_extra_state_attributes["last_calculation_timestamp"] = (
|
||||||
|
self._last_calculation_timestamp.astimezone(self._current_tz).isoformat()
|
||||||
|
if self._last_calculation_timestamp
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
self.async_write_ha_state()
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - Calling update_custom_attributes: %s",
|
||||||
|
self,
|
||||||
|
self._attr_extra_state_attributes,
|
||||||
|
)
|
||||||
|
|
||||||
|
@overrides
|
||||||
|
def recalculate(self):
|
||||||
|
"""A utility function to force the calculation of a the algo and
|
||||||
|
update the custom attributes and write the state
|
||||||
|
"""
|
||||||
|
_LOGGER.debug("%s - recalculate the open percent", self)
|
||||||
|
|
||||||
|
# TODO this is exactly the same method as the thermostat_valve recalculate. Put that in common
|
||||||
|
|
||||||
|
# For testing purpose. Should call _set_now() before
|
||||||
|
now = self.now
|
||||||
|
|
||||||
|
if self._last_calculation_timestamp is not None:
|
||||||
|
period = (now - self._last_calculation_timestamp).total_seconds() / 60
|
||||||
|
if period < self._auto_regulation_period_min:
|
||||||
|
_LOGGER.info(
|
||||||
|
"%s - do not calculate TPI because regulation_period (%d) is not exceeded",
|
||||||
|
self,
|
||||||
|
period,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
self._prop_algorithm.calculate(
|
||||||
|
self._target_temp,
|
||||||
|
self._cur_temp,
|
||||||
|
self._cur_ext_temp,
|
||||||
|
self._hvac_mode or HVACMode.OFF,
|
||||||
|
)
|
||||||
|
|
||||||
|
new_valve_percent = round(
|
||||||
|
max(0, min(self.proportional_algorithm.on_percent, 1)) * 100
|
||||||
|
)
|
||||||
|
|
||||||
|
# Issue 533 - don't filter with dtemp if valve should be close. Else it will never close
|
||||||
|
if new_valve_percent < self._auto_regulation_dpercent:
|
||||||
|
new_valve_percent = 0
|
||||||
|
|
||||||
|
dpercent = new_valve_percent - self.valve_open_percent
|
||||||
|
if (
|
||||||
|
new_valve_percent > 0
|
||||||
|
and -1 * self._auto_regulation_dpercent
|
||||||
|
<= dpercent
|
||||||
|
< self._auto_regulation_dpercent
|
||||||
|
):
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - do not calculate TPI because regulation_dpercent (%.1f) is not exceeded",
|
||||||
|
self,
|
||||||
|
dpercent,
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._valve_open_percent == new_valve_percent:
|
||||||
|
_LOGGER.debug("%s - no change in valve_open_percent.", self)
|
||||||
|
return
|
||||||
|
|
||||||
|
self._valve_open_percent = new_valve_percent
|
||||||
|
|
||||||
|
for under in self._underlyings_sonoff_trvzb:
|
||||||
|
under.set_valve_open_percent()
|
||||||
|
|
||||||
|
self._last_calculation_timestamp = now
|
||||||
|
|
||||||
|
self.update_custom_attributes()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_over_sonoff_trvzb(self) -> bool:
|
||||||
|
"""True if the Thermostat is over_sonoff_trvzb"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def power_percent(self) -> float | None:
|
||||||
|
"""Get the current on_percent value"""
|
||||||
|
if self._prop_algorithm:
|
||||||
|
return round(self._prop_algorithm.on_percent * 100, 0)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# @property
|
||||||
|
# def hvac_modes(self) -> list[HVACMode]:
|
||||||
|
# """Get the hvac_modes"""
|
||||||
|
# return self._hvac_list
|
||||||
|
|
||||||
|
@property
|
||||||
|
def valve_open_percent(self) -> int:
|
||||||
|
"""Gives the percentage of valve needed"""
|
||||||
|
if self._hvac_mode == HVACMode.OFF:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return self._valve_open_percent
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self) -> HVACAction | None:
|
||||||
|
"""Returns the current hvac_action by checking all hvac_action of the _underlyings_sonoff_trvzb"""
|
||||||
|
|
||||||
|
return self.calculate_hvac_action(self._underlyings_sonoff_trvzb)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# pylint: disable=line-too-long
|
# pylint: disable=line-too-long, abstract-method
|
||||||
|
|
||||||
""" A climate over switch classe """
|
""" A climate over switch classe """
|
||||||
import logging
|
import logging
|
||||||
@@ -10,10 +10,7 @@ from homeassistant.helpers.event import (
|
|||||||
from homeassistant.components.climate import HVACMode
|
from homeassistant.components.climate import HVACMode
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_HEATER,
|
CONF_UNDERLYING_LIST,
|
||||||
CONF_HEATER_2,
|
|
||||||
CONF_HEATER_3,
|
|
||||||
CONF_HEATER_4,
|
|
||||||
CONF_HEATER_KEEP_ALIVE,
|
CONF_HEATER_KEEP_ALIVE,
|
||||||
CONF_INVERSE_SWITCH,
|
CONF_INVERSE_SWITCH,
|
||||||
overrides,
|
overrides,
|
||||||
@@ -25,7 +22,6 @@ from .prop_algorithm import PropAlgorithm
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
||||||
"""Representation of a base class for a Versatile Thermostat over a switch."""
|
"""Representation of a base class for a Versatile Thermostat over a switch."""
|
||||||
|
|
||||||
@@ -35,10 +31,7 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
|||||||
{
|
{
|
||||||
"is_over_switch",
|
"is_over_switch",
|
||||||
"is_inversed",
|
"is_inversed",
|
||||||
"underlying_switch_0",
|
"underlying_entities",
|
||||||
"underlying_switch_1",
|
|
||||||
"underlying_switch_2",
|
|
||||||
"underlying_switch_3",
|
|
||||||
"on_time_sec",
|
"on_time_sec",
|
||||||
"off_time_sec",
|
"off_time_sec",
|
||||||
"cycle_min",
|
"cycle_min",
|
||||||
@@ -46,6 +39,7 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
|||||||
"tpi_coef_int",
|
"tpi_coef_int",
|
||||||
"tpi_coef_ext",
|
"tpi_coef_ext",
|
||||||
"power_percent",
|
"power_percent",
|
||||||
|
"calculated_on_percent",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -88,15 +82,10 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
|||||||
self._cycle_min,
|
self._cycle_min,
|
||||||
self._minimal_activation_delay,
|
self._minimal_activation_delay,
|
||||||
self.name,
|
self.name,
|
||||||
|
max_on_percent=self._max_on_percent,
|
||||||
)
|
)
|
||||||
|
|
||||||
lst_switches = [config_entry.get(CONF_HEATER)]
|
lst_switches = config_entry.get(CONF_UNDERLYING_LIST)
|
||||||
if config_entry.get(CONF_HEATER_2):
|
|
||||||
lst_switches.append(config_entry.get(CONF_HEATER_2))
|
|
||||||
if config_entry.get(CONF_HEATER_3):
|
|
||||||
lst_switches.append(config_entry.get(CONF_HEATER_3))
|
|
||||||
if config_entry.get(CONF_HEATER_4):
|
|
||||||
lst_switches.append(config_entry.get(CONF_HEATER_4))
|
|
||||||
|
|
||||||
delta_cycle = self._cycle_min * 60 / len(lst_switches)
|
delta_cycle = self._cycle_min * 60 / len(lst_switches)
|
||||||
for idx, switch in enumerate(lst_switches):
|
for idx, switch in enumerate(lst_switches):
|
||||||
@@ -140,16 +129,10 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
|||||||
self._attr_extra_state_attributes["is_over_switch"] = self.is_over_switch
|
self._attr_extra_state_attributes["is_over_switch"] = self.is_over_switch
|
||||||
self._attr_extra_state_attributes["is_inversed"] = self.is_inversed
|
self._attr_extra_state_attributes["is_inversed"] = self.is_inversed
|
||||||
self._attr_extra_state_attributes["keep_alive_sec"] = under0.keep_alive_sec
|
self._attr_extra_state_attributes["keep_alive_sec"] = under0.keep_alive_sec
|
||||||
self._attr_extra_state_attributes["underlying_switch_0"] = under0.entity_id
|
|
||||||
self._attr_extra_state_attributes["underlying_switch_1"] = (
|
self._attr_extra_state_attributes["underlying_entities"] = [
|
||||||
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
|
underlying.entity_id for underlying in self._underlyings
|
||||||
)
|
]
|
||||||
self._attr_extra_state_attributes["underlying_switch_2"] = (
|
|
||||||
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
|
|
||||||
)
|
|
||||||
self._attr_extra_state_attributes["underlying_switch_3"] = (
|
|
||||||
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
|
|
||||||
)
|
|
||||||
|
|
||||||
self._attr_extra_state_attributes[
|
self._attr_extra_state_attributes[
|
||||||
"on_percent"
|
"on_percent"
|
||||||
@@ -165,6 +148,9 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
|||||||
self._attr_extra_state_attributes["function"] = self._proportional_function
|
self._attr_extra_state_attributes["function"] = self._proportional_function
|
||||||
self._attr_extra_state_attributes["tpi_coef_int"] = self._tpi_coef_int
|
self._attr_extra_state_attributes["tpi_coef_int"] = self._tpi_coef_int
|
||||||
self._attr_extra_state_attributes["tpi_coef_ext"] = self._tpi_coef_ext
|
self._attr_extra_state_attributes["tpi_coef_ext"] = self._tpi_coef_ext
|
||||||
|
self._attr_extra_state_attributes[
|
||||||
|
"calculated_on_percent"
|
||||||
|
] = self._prop_algorithm.calculated_on_percent
|
||||||
|
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
@@ -201,8 +187,18 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
|
|||||||
|
|
||||||
if self._total_energy is None:
|
if self._total_energy is None:
|
||||||
self._total_energy = added_energy
|
self._total_energy = added_energy
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - incremente_energy set energy is %s",
|
||||||
|
self,
|
||||||
|
self._total_energy,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self._total_energy += added_energy
|
self._total_energy += added_energy
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - incremente_energy increment energy is %s",
|
||||||
|
self,
|
||||||
|
self._total_energy,
|
||||||
|
)
|
||||||
|
|
||||||
self.update_custom_attributes()
|
self.update_custom_attributes()
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# pylint: disable=line-too-long
|
# pylint: disable=line-too-long, abstract-method
|
||||||
""" A climate over switch classe """
|
""" A climate over switch classe """
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta, datetime
|
from datetime import timedelta, datetime
|
||||||
@@ -15,10 +15,7 @@ from .base_thermostat import BaseThermostat, ConfigData
|
|||||||
from .prop_algorithm import PropAlgorithm
|
from .prop_algorithm import PropAlgorithm
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_VALVE,
|
CONF_UNDERLYING_LIST,
|
||||||
CONF_VALVE_2,
|
|
||||||
CONF_VALVE_3,
|
|
||||||
CONF_VALVE_4,
|
|
||||||
# This is not really self-regulation but regulation here
|
# This is not really self-regulation but regulation here
|
||||||
CONF_AUTO_REGULATION_DTEMP,
|
CONF_AUTO_REGULATION_DTEMP,
|
||||||
CONF_AUTO_REGULATION_PERIOD_MIN,
|
CONF_AUTO_REGULATION_PERIOD_MIN,
|
||||||
@@ -29,7 +26,6 @@ from .underlyings import UnderlyingValve
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=abstract-method
|
class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=abstract-method
|
||||||
"""Representation of a class for a Versatile Thermostat over a Valve"""
|
"""Representation of a class for a Versatile Thermostat over a Valve"""
|
||||||
|
|
||||||
@@ -37,10 +33,7 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
|
|||||||
frozenset(
|
frozenset(
|
||||||
{
|
{
|
||||||
"is_over_valve",
|
"is_over_valve",
|
||||||
"underlying_valve_0",
|
"underlying_entities",
|
||||||
"underlying_valve_1",
|
|
||||||
"underlying_valve_2",
|
|
||||||
"underlying_valve_3",
|
|
||||||
"on_time_sec",
|
"on_time_sec",
|
||||||
"off_time_sec",
|
"off_time_sec",
|
||||||
"cycle_min",
|
"cycle_min",
|
||||||
@@ -50,6 +43,7 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
|
|||||||
"auto_regulation_dpercent",
|
"auto_regulation_dpercent",
|
||||||
"auto_regulation_period_min",
|
"auto_regulation_period_min",
|
||||||
"last_calculation_timestamp",
|
"last_calculation_timestamp",
|
||||||
|
"calculated_on_percent",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -103,15 +97,10 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
|
|||||||
self._cycle_min,
|
self._cycle_min,
|
||||||
self._minimal_activation_delay,
|
self._minimal_activation_delay,
|
||||||
self.name,
|
self.name,
|
||||||
|
max_on_percent=self._max_on_percent,
|
||||||
)
|
)
|
||||||
|
|
||||||
lst_valves = [config_entry.get(CONF_VALVE)]
|
lst_valves = config_entry.get(CONF_UNDERLYING_LIST)
|
||||||
if config_entry.get(CONF_VALVE_2):
|
|
||||||
lst_valves.append(config_entry.get(CONF_VALVE_2))
|
|
||||||
if config_entry.get(CONF_VALVE_3):
|
|
||||||
lst_valves.append(config_entry.get(CONF_VALVE_3))
|
|
||||||
if config_entry.get(CONF_VALVE_4):
|
|
||||||
lst_valves.append(config_entry.get(CONF_VALVE_4))
|
|
||||||
|
|
||||||
for _, valve in enumerate(lst_valves):
|
for _, valve in enumerate(lst_valves):
|
||||||
self._underlyings.append(
|
self._underlyings.append(
|
||||||
@@ -163,18 +152,10 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
|
|||||||
"valve_open_percent"
|
"valve_open_percent"
|
||||||
] = self.valve_open_percent
|
] = self.valve_open_percent
|
||||||
self._attr_extra_state_attributes["is_over_valve"] = self.is_over_valve
|
self._attr_extra_state_attributes["is_over_valve"] = self.is_over_valve
|
||||||
self._attr_extra_state_attributes["underlying_valve_0"] = self._underlyings[
|
|
||||||
0
|
self._attr_extra_state_attributes["underlying_entities"] = [
|
||||||
].entity_id
|
underlying.entity_id for underlying in self._underlyings
|
||||||
self._attr_extra_state_attributes["underlying_valve_1"] = (
|
]
|
||||||
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
|
|
||||||
)
|
|
||||||
self._attr_extra_state_attributes["underlying_valve_2"] = (
|
|
||||||
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
|
|
||||||
)
|
|
||||||
self._attr_extra_state_attributes["underlying_valve_3"] = (
|
|
||||||
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
|
|
||||||
)
|
|
||||||
|
|
||||||
self._attr_extra_state_attributes[
|
self._attr_extra_state_attributes[
|
||||||
"on_percent"
|
"on_percent"
|
||||||
@@ -200,6 +181,9 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
|
|||||||
if self._last_calculation_timestamp
|
if self._last_calculation_timestamp
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
self._attr_extra_state_attributes[
|
||||||
|
"calculated_on_percent"
|
||||||
|
] = self._prop_algorithm.calculated_on_percent
|
||||||
|
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
@@ -285,8 +269,18 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
|
|||||||
|
|
||||||
if self._total_energy is None:
|
if self._total_energy is None:
|
||||||
self._total_energy = added_energy
|
self._total_energy = added_energy
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - incremente_energy set energy is %s",
|
||||||
|
self,
|
||||||
|
self._total_energy,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self._total_energy += added_energy
|
self._total_energy += added_energy
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - get_my_previous_state increment energy is %s",
|
||||||
|
self,
|
||||||
|
self._total_energy,
|
||||||
|
)
|
||||||
|
|
||||||
self.update_custom_attributes()
|
self.update_custom_attributes()
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
"power": "Power management",
|
"power": "Power management",
|
||||||
"presence": "Presence detection",
|
"presence": "Presence detection",
|
||||||
"advanced": "Advanced parameters",
|
"advanced": "Advanced parameters",
|
||||||
|
"auto_start_stop": "Auto start and stop",
|
||||||
|
"sonoff_trvzb": "Sonoff TRVZB configuration",
|
||||||
"finalize": "All done",
|
"finalize": "All done",
|
||||||
"configuration_not_complete": "Configuration not complete"
|
"configuration_not_complete": "Configuration not complete"
|
||||||
}
|
}
|
||||||
@@ -63,28 +65,19 @@
|
|||||||
"use_motion_feature": "Use motion detection",
|
"use_motion_feature": "Use motion detection",
|
||||||
"use_power_feature": "Use power management",
|
"use_power_feature": "Use power management",
|
||||||
"use_presence_feature": "Use presence detection",
|
"use_presence_feature": "Use presence detection",
|
||||||
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page"
|
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
|
||||||
|
"use_auto_start_stop_feature": "Use the auto start and stop feature"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"title": "Linked entities",
|
"title": "Linked entities",
|
||||||
"description": "Linked entities attributes",
|
"description": "Linked entities attributes",
|
||||||
"data": {
|
"data": {
|
||||||
"heater_entity_id": "1st heater switch",
|
"underlying_entity_ids": "The device(s) to be controlled",
|
||||||
"heater_entity2_id": "2nd heater switch",
|
|
||||||
"heater_entity3_id": "3rd heater switch",
|
|
||||||
"heater_entity4_id": "4th heater switch",
|
|
||||||
"heater_keep_alive": "Switch keep-alive interval in seconds",
|
"heater_keep_alive": "Switch keep-alive interval in seconds",
|
||||||
"proportional_function": "Algorithm",
|
"proportional_function": "Algorithm",
|
||||||
"climate_entity_id": "1st underlying climate",
|
|
||||||
"climate_entity2_id": "2nd underlying climate",
|
|
||||||
"climate_entity3_id": "3rd underlying climate",
|
|
||||||
"climate_entity4_id": "4th underlying climate",
|
|
||||||
"ac_mode": "AC mode",
|
"ac_mode": "AC mode",
|
||||||
"valve_entity_id": "1st valve number",
|
"sonoff_trvzb_mode": "SONOFF TRVZB mode",
|
||||||
"valve_entity2_id": "2nd valve number",
|
|
||||||
"valve_entity3_id": "3rd valve number",
|
|
||||||
"valve_entity4_id": "4th valve number",
|
|
||||||
"auto_regulation_mode": "Self-regulation",
|
"auto_regulation_mode": "Self-regulation",
|
||||||
"auto_regulation_dtemp": "Regulation threshold",
|
"auto_regulation_dtemp": "Regulation threshold",
|
||||||
"auto_regulation_periode_min": "Regulation minimum period",
|
"auto_regulation_periode_min": "Regulation minimum period",
|
||||||
@@ -93,21 +86,11 @@
|
|||||||
"auto_fan_mode": "Auto fan mode"
|
"auto_fan_mode": "Auto fan mode"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"heater_entity_id": "Mandatory heater entity id",
|
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
|
||||||
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not required",
|
|
||||||
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not required",
|
|
||||||
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not required",
|
|
||||||
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
|
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
|
||||||
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||||
"climate_entity_id": "Underlying climate entity id",
|
|
||||||
"climate_entity2_id": "2nd underlying climate entity id",
|
|
||||||
"climate_entity3_id": "3rd underlying climate entity id",
|
|
||||||
"climate_entity4_id": "4th underlying climate entity id",
|
|
||||||
"ac_mode": "Use the Air Conditioning (AC) mode",
|
"ac_mode": "Use the Air Conditioning (AC) mode",
|
||||||
"valve_entity_id": "1st valve number entity id",
|
"sonoff_trvzb_mode": "The underlyings are SONOFF TRVZB. You have to configure some extra entities in the specific menu option 'Sonoff trvzb configuration'",
|
||||||
"valve_entity2_id": "2nd valve number entity id",
|
|
||||||
"valve_entity3_id": "3rd valve number entity id",
|
|
||||||
"valve_entity4_id": "4th valve number entity id",
|
|
||||||
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
||||||
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
|
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
|
||||||
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
||||||
@@ -223,6 +206,34 @@
|
|||||||
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
|
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
|
||||||
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
|
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"central_boiler": {
|
||||||
|
"title": "Control of the central boiler",
|
||||||
|
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
|
||||||
|
"data": {
|
||||||
|
"central_boiler_activation_service": "Command to turn-on",
|
||||||
|
"central_boiler_deactivation_service": "Command to turn-off"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
|
||||||
|
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sonoff_trvzb": {
|
||||||
|
"title": "Sonoff TRVZB configuration",
|
||||||
|
"description": "Specific Sonoff TRVZB configuration",
|
||||||
|
"data": {
|
||||||
|
"offset_calibration_entity_ids": "Offset calibration entities",
|
||||||
|
"opening_degree_entity_ids": "Opening degree entities",
|
||||||
|
"closing_degree_entity_ids": "Closing degree entities",
|
||||||
|
"proportional_function": "Algorithm"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. There should be one per underlying climate entities",
|
||||||
|
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
|
||||||
|
"closing_degree_entity_ids": "The list of the 'closing degree' entities. There should be one per underlying climate entities",
|
||||||
|
"proportional_function": "Algorithm to use (TPI is the only one for now)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -262,6 +273,8 @@
|
|||||||
"power": "Power management",
|
"power": "Power management",
|
||||||
"presence": "Presence detection",
|
"presence": "Presence detection",
|
||||||
"advanced": "Advanced parameters",
|
"advanced": "Advanced parameters",
|
||||||
|
"auto_start_stop": "Auto start and stop",
|
||||||
|
"sonoff_trvzb": "Sonoff TRVZB configuration",
|
||||||
"finalize": "All done",
|
"finalize": "All done",
|
||||||
"configuration_not_complete": "Configuration not complete"
|
"configuration_not_complete": "Configuration not complete"
|
||||||
}
|
}
|
||||||
@@ -298,28 +311,19 @@
|
|||||||
"use_motion_feature": "Use motion detection",
|
"use_motion_feature": "Use motion detection",
|
||||||
"use_power_feature": "Use power management",
|
"use_power_feature": "Use power management",
|
||||||
"use_presence_feature": "Use presence detection",
|
"use_presence_feature": "Use presence detection",
|
||||||
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page"
|
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
|
||||||
|
"use_auto_start_stop_feature": "Use the auto start and stop feature"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"title": "Entities - {name}",
|
"title": "Entities - {name}",
|
||||||
"description": "Linked entities attributes",
|
"description": "Linked entities attributes",
|
||||||
"data": {
|
"data": {
|
||||||
"heater_entity_id": "1st heater switch",
|
"underlying_entity_ids": "The device(s) to be controlled",
|
||||||
"heater_entity2_id": "2nd heater switch",
|
|
||||||
"heater_entity3_id": "3rd heater switch",
|
|
||||||
"heater_entity4_id": "4th heater switch",
|
|
||||||
"heater_keep_alive": "Switch keep-alive interval in seconds",
|
"heater_keep_alive": "Switch keep-alive interval in seconds",
|
||||||
"proportional_function": "Algorithm",
|
"proportional_function": "Algorithm",
|
||||||
"climate_entity_id": "1st underlying climate",
|
|
||||||
"climate_entity2_id": "2nd underlying climate",
|
|
||||||
"climate_entity3_id": "3rd underlying climate",
|
|
||||||
"climate_entity4_id": "4th underlying climate",
|
|
||||||
"ac_mode": "AC mode",
|
"ac_mode": "AC mode",
|
||||||
"valve_entity_id": "1st valve number",
|
"sonoff_trvzb_mode": "SONOFF TRVZB mode",
|
||||||
"valve_entity2_id": "2nd valve number",
|
|
||||||
"valve_entity3_id": "3rd valve number",
|
|
||||||
"valve_entity4_id": "4th valve number",
|
|
||||||
"auto_regulation_mode": "Self-regulation",
|
"auto_regulation_mode": "Self-regulation",
|
||||||
"auto_regulation_dtemp": "Regulation threshold",
|
"auto_regulation_dtemp": "Regulation threshold",
|
||||||
"auto_regulation_periode_min": "Regulation minimum period",
|
"auto_regulation_periode_min": "Regulation minimum period",
|
||||||
@@ -328,21 +332,11 @@
|
|||||||
"auto_fan_mode": "Auto fan mode"
|
"auto_fan_mode": "Auto fan mode"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"heater_entity_id": "Mandatory heater entity id",
|
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
|
||||||
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not used",
|
|
||||||
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used",
|
|
||||||
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
|
|
||||||
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
|
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
|
||||||
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
"proportional_function": "Algorithm to use (TPI is the only one for now)",
|
||||||
"climate_entity_id": "Underlying climate entity id",
|
|
||||||
"climate_entity2_id": "2nd underlying climate entity id",
|
|
||||||
"climate_entity3_id": "3rd underlying climate entity id",
|
|
||||||
"climate_entity4_id": "4th underlying climate entity id",
|
|
||||||
"ac_mode": "Use the Air Conditioning (AC) mode",
|
"ac_mode": "Use the Air Conditioning (AC) mode",
|
||||||
"valve_entity_id": "1st valve number entity id",
|
"sonoff_trvzb_mode": "The underlyings are SONOFF TRVZB. You have to configure some extra entities in the specific menu option 'Sonoff trvzb configuration'",
|
||||||
"valve_entity2_id": "2nd valve number entity id",
|
|
||||||
"valve_entity3_id": "3rd valve number entity id",
|
|
||||||
"valve_entity4_id": "4th valve number entity id",
|
|
||||||
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
"auto_regulation_mode": "Auto adjustment of the target temperature",
|
||||||
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
|
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
|
||||||
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
|
||||||
@@ -458,6 +452,34 @@
|
|||||||
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
|
"security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
|
||||||
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
|
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"central_boiler": {
|
||||||
|
"title": "Control of the central boiler",
|
||||||
|
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
|
||||||
|
"data": {
|
||||||
|
"central_boiler_activation_service": "Command to turn-on",
|
||||||
|
"central_boiler_deactivation_service": "Command to turn-off"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
|
||||||
|
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sonoff_trvzb": {
|
||||||
|
"title": "Sonoff TRVZB configuration",
|
||||||
|
"description": "Specific Sonoff TRVZB configuration",
|
||||||
|
"data": {
|
||||||
|
"offset_calibration_entity_ids": "Offset calibration entities",
|
||||||
|
"opening_degree_entity_ids": "Opening degree entities",
|
||||||
|
"closing_degree_entity_ids": "Closing degree entities",
|
||||||
|
"proportional_function": "Algorithm"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. There should be one per underlying climate entities",
|
||||||
|
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
|
||||||
|
"closing_degree_entity_ids": "The list of the 'closing degree' entities. There should be one per underlying climate entities",
|
||||||
|
"proportional_function": "Algorithm to use (TPI is the only one for now)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -465,7 +487,8 @@
|
|||||||
"unknown_entity": "Unknown entity id",
|
"unknown_entity": "Unknown entity id",
|
||||||
"window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both",
|
"window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both",
|
||||||
"no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.",
|
"no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.",
|
||||||
"service_configuration_format": "The format of the service configuration is wrong"
|
"service_configuration_format": "The format of the service configuration is wrong",
|
||||||
|
"sonoff_trvzb_nb_entities_incorrect": "The number of specific entities for Sonoff TRVZB should be equal to the number of underlyings"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Device is already configured"
|
"already_configured": "Device is already configured"
|
||||||
@@ -514,6 +537,14 @@
|
|||||||
"comfort": "Comfort",
|
"comfort": "Comfort",
|
||||||
"boost": "Boost"
|
"boost": "Boost"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"auto_start_stop": {
|
||||||
|
"options": {
|
||||||
|
"auto_start_stop_none": "No auto start/stop",
|
||||||
|
"auto_start_stop_slow": "Slow detection",
|
||||||
|
"auto_start_stop_medium": "Medium detection",
|
||||||
|
"auto_start_stop_fast": "Fast detection"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
@@ -524,7 +555,8 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"power": "Shedding",
|
"power": "Shedding",
|
||||||
"security": "Safety",
|
"security": "Safety",
|
||||||
"none": "Manual"
|
"none": "Manual",
|
||||||
|
"frost": "Frost"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
"power": "Gestion de la puissance",
|
"power": "Gestion de la puissance",
|
||||||
"presence": "Détection de présence",
|
"presence": "Détection de présence",
|
||||||
"advanced": "Paramètres avancés",
|
"advanced": "Paramètres avancés",
|
||||||
|
"auto_start_stop": "Allumage/extinction automatique",
|
||||||
|
"sonoff_trvzb": "Configuration spécifique à Sonoff TRVZB",
|
||||||
"finalize": "Finaliser la création",
|
"finalize": "Finaliser la création",
|
||||||
"configuration_not_complete": "Configuration incomplète"
|
"configuration_not_complete": "Configuration incomplète"
|
||||||
}
|
}
|
||||||
@@ -63,55 +65,36 @@
|
|||||||
"use_motion_feature": "Avec détection de mouvement",
|
"use_motion_feature": "Avec détection de mouvement",
|
||||||
"use_power_feature": "Avec gestion de la puissance",
|
"use_power_feature": "Avec gestion de la puissance",
|
||||||
"use_presence_feature": "Avec détection de présence",
|
"use_presence_feature": "Avec détection de présence",
|
||||||
"use_central_boiler_feature": "Ajouter une chaudière centrale. Cochez pour ajouter un controle sur une chaudière centrale. Vous devrez ensuite configurer les VTherms qui commande la chaudière centrale pour que cette option prenne effet. Si au moins un des VTherm a besoin de chauffer, la chaudière centrale sera activée. Si aucun VTherm n'a besoin de chauffer, elle sera éteinte. Les commandes pour allumer/éteindre la chaudière centrale sont données dans la page de configuration suivante."
|
"use_central_boiler_feature": "Ajouter une chaudière centrale. Cochez pour ajouter un controle sur une chaudière centrale. Vous devrez ensuite configurer les VTherms qui commande la chaudière centrale pour que cette option prenne effet. Si au moins un des VTherm a besoin de chauffer, la chaudière centrale sera activée. Si aucun VTherm n'a besoin de chauffer, elle sera éteinte. Les commandes pour allumer/éteindre la chaudière centrale sont données dans la page de configuration suivante.",
|
||||||
|
"use_auto_start_stop_feature": "Avec démarrage et extinction automatique"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"title": "Entité(s) liée(s)",
|
"title": "Entité(s) liée(s)",
|
||||||
"description": "Attributs de(s) l'entité(s) liée(s)",
|
"description": "Attributs de(s) l'entité(s) liée(s)",
|
||||||
"data": {
|
"data": {
|
||||||
"heater_entity_id": "1er radiateur",
|
"underlying_entity_ids": "Les équipements à controller",
|
||||||
"heater_entity2_id": "2ème radiateur",
|
|
||||||
"heater_entity3_id": "3ème radiateur",
|
|
||||||
"heater_entity4_id": "4ème radiateur",
|
|
||||||
"heater_keep_alive": "keep-alive (sec)",
|
"heater_keep_alive": "keep-alive (sec)",
|
||||||
"proportional_function": "Algorithme",
|
"proportional_function": "Algorithme",
|
||||||
"climate_entity_id": "Thermostat sous-jacent",
|
|
||||||
"climate_entity2_id": "2ème thermostat sous-jacent",
|
|
||||||
"climate_entity3_id": "3ème thermostat sous-jacent",
|
|
||||||
"climate_entity4_id": "4ème thermostat sous-jacent",
|
|
||||||
"ac_mode": "AC mode ?",
|
"ac_mode": "AC mode ?",
|
||||||
"valve_entity_id": "1ère valve number",
|
"sonoff_trvzb_mode": "Mode Sonoff TRVZB",
|
||||||
"valve_entity2_id": "2ème valve number",
|
|
||||||
"valve_entity3_id": "3ème valve number",
|
|
||||||
"valve_entity4_id": "4ème valve number",
|
|
||||||
"auto_regulation_mode": "Auto-régulation",
|
"auto_regulation_mode": "Auto-régulation",
|
||||||
"auto_regulation_dtemp": "Seuil de régulation",
|
"auto_regulation_dtemp": "Seuil de régulation",
|
||||||
"auto_regulation_periode_min": "Période minimale de régulation",
|
"auto_regulation_periode_min": "Période minimale de régulation",
|
||||||
"auto_regulation_use_device_temp": "Utiliser la température interne du sous-jacent",
|
"auto_regulation_use_device_temp": "Compenser la température interne du sous-jacent",
|
||||||
"inverse_switch_command": "Inverser la commande",
|
"inverse_switch_command": "Inverser la commande",
|
||||||
"auto_fan_mode": " Auto ventilation mode"
|
"auto_fan_mode": " Auto ventilation mode"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
|
"underlying_entity_ids": "La liste des équipements qui seront controlés par ce VTherm",
|
||||||
"heater_entity2_id": "Optionnel entity id du 2ème radiateur",
|
|
||||||
"heater_entity3_id": "Optionnel entity id du 3ème radiateur",
|
|
||||||
"heater_entity4_id": "Optionnel entity id du 4ème radiateur",
|
|
||||||
"heater_keep_alive": "Intervalle de rafraichissement du switch en secondes. Laisser vide pour désactiver. À n'utiliser que pour les switchs qui le nécessite.",
|
"heater_keep_alive": "Intervalle de rafraichissement du switch en secondes. Laisser vide pour désactiver. À n'utiliser que pour les switchs qui le nécessite.",
|
||||||
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
|
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
|
||||||
"climate_entity_id": "Entity id du thermostat sous-jacent",
|
|
||||||
"climate_entity2_id": "Entity id du 2ème thermostat sous-jacent",
|
|
||||||
"climate_entity3_id": "Entity id du 3ème thermostat sous-jacent",
|
|
||||||
"climate_entity4_id": "Entity id du 4ème thermostat sous-jacent",
|
|
||||||
"ac_mode": "Utilisation du mode Air Conditionné (AC)",
|
"ac_mode": "Utilisation du mode Air Conditionné (AC)",
|
||||||
"valve_entity_id": "Entity id de la 1ère valve",
|
"sonoff_trvzb_mode": "Les équipements sont des Sonoff TRVZB. Vous devez configurer les entités dédiées dans le menu 'Configuration Sonoff TRVZB'",
|
||||||
"valve_entity2_id": "Entity id de la 2ème valve",
|
|
||||||
"valve_entity3_id": "Entity id de la 3ème valve",
|
|
||||||
"valve_entity4_id": "Entity id de la 4ème valve",
|
|
||||||
"auto_regulation_mode": "Ajustement automatique de la température cible",
|
"auto_regulation_mode": "Ajustement automatique de la température cible",
|
||||||
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-dessous duquel la régulation ne sera pas envoyée",
|
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-dessous duquel la régulation ne sera pas envoyée",
|
||||||
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
|
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
|
||||||
"auto_regulation_use_device_temp": "Utiliser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
|
"auto_regulation_use_device_temp": "Compenser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
|
||||||
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
|
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
|
||||||
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
|
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
|
||||||
}
|
}
|
||||||
@@ -235,6 +218,22 @@
|
|||||||
"central_boiler_activation_service": "Commande à éxecuter pour allumer la chaudière centrale au format entity_id/service_name[/attribut:valeur]",
|
"central_boiler_activation_service": "Commande à éxecuter pour allumer la chaudière centrale au format entity_id/service_name[/attribut:valeur]",
|
||||||
"central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]"
|
"central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sonoff_trvzb": {
|
||||||
|
"title": "Configuration Sonoff TRVZB",
|
||||||
|
"description": "Configuration spécifique des Sonoff TRVZB",
|
||||||
|
"data": {
|
||||||
|
"offset_calibration_entity_ids": "Entités de 'Offset calibration'",
|
||||||
|
"opening_degree_entity_ids": "Entités de 'Opening degree'",
|
||||||
|
"closing_degree_entity_ids": "Entités de 'Closing degree'",
|
||||||
|
"proportional_function": "Algorithme"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"offset_calibration_entity_ids": "La liste des entités 'offset calibration' entities. Il doit y en avoir une par entité climate sous-jacente",
|
||||||
|
"opening_degree_entity_ids": "La liste des entités 'opening degree' entities. Il doit y en avoir une par entité climate sous-jacente",
|
||||||
|
"closing_degree_entity_ids": "La liste des entités 'closing degree' entities. Il doit y en avoir une par entité climate sous-jacente",
|
||||||
|
"proportional_function": "Algorithme à utiliser (seulement TPI est disponible)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -260,7 +259,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"title": "Menu",
|
"title": "Menu - {name}",
|
||||||
"description": "Paramétrez votre thermostat. Vous pourrez finaliser la configuration quand tous les paramètres auront été saisis.",
|
"description": "Paramétrez votre thermostat. Vous pourrez finaliser la configuration quand tous les paramètres auront été saisis.",
|
||||||
"menu_options": {
|
"menu_options": {
|
||||||
"main": "Principaux Attributs",
|
"main": "Principaux Attributs",
|
||||||
@@ -274,6 +273,8 @@
|
|||||||
"power": "Gestion de la puissance",
|
"power": "Gestion de la puissance",
|
||||||
"presence": "Détection de présence",
|
"presence": "Détection de présence",
|
||||||
"advanced": "Paramètres avancés",
|
"advanced": "Paramètres avancés",
|
||||||
|
"auto_start_stop": "Allumage/extinction automatique",
|
||||||
|
"sonoff_trvzb": "Configuration spécifique à Sonoff TRVZB",
|
||||||
"finalize": "Finaliser les modifications",
|
"finalize": "Finaliser les modifications",
|
||||||
"configuration_not_complete": "Configuration incomplète"
|
"configuration_not_complete": "Configuration incomplète"
|
||||||
}
|
}
|
||||||
@@ -310,55 +311,36 @@
|
|||||||
"use_motion_feature": "Avec détection de mouvement",
|
"use_motion_feature": "Avec détection de mouvement",
|
||||||
"use_power_feature": "Avec gestion de la puissance",
|
"use_power_feature": "Avec gestion de la puissance",
|
||||||
"use_presence_feature": "Avec détection de présence",
|
"use_presence_feature": "Avec détection de présence",
|
||||||
"use_central_boiler_feature": "Ajouter une chaudière centrale. Cochez pour ajouter un controle sur une chaudière centrale. Vous devrez ensuite configurer les VTherms qui commande la chaudière centrale pour que cette option prenne effet. Si au moins un des VTherm a besoin de chauffer, la chaudière centrale sera activée. Si aucun VTherm n'a besoin de chauffer, elle sera éteinte. Les commandes pour allumer/éteindre la chaudière centrale sont données dans la page de configuration suivante."
|
"use_central_boiler_feature": "Ajouter une chaudière centrale. Cochez pour ajouter un controle sur une chaudière centrale. Vous devrez ensuite configurer les VTherms qui commande la chaudière centrale pour que cette option prenne effet. Si au moins un des VTherm a besoin de chauffer, la chaudière centrale sera activée. Si aucun VTherm n'a besoin de chauffer, elle sera éteinte. Les commandes pour allumer/éteindre la chaudière centrale sont données dans la page de configuration suivante.",
|
||||||
|
"use_auto_start_stop_feature": "Avec démarrage et extinction automatique"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
"title": "Entités - {name}",
|
"title": "Entité(s) liée(s) - {name}",
|
||||||
"description": "Attributs de(s) l'entité(s) liée(s)",
|
"description": "Attributs de(s) l'entité(s) liée(s)",
|
||||||
"data": {
|
"data": {
|
||||||
"heater_entity_id": "1er radiateur",
|
"underlying_entity_ids": "Les équipements à controller",
|
||||||
"heater_entity2_id": "2ème radiateur",
|
"heater_keep_alive": "keep-alive (sec)",
|
||||||
"heater_entity3_id": "3ème radiateur",
|
|
||||||
"heater_entity4_id": "4ème radiateur",
|
|
||||||
"heater_keep_alive": "Keep-alive (sec)",
|
|
||||||
"proportional_function": "Algorithme",
|
"proportional_function": "Algorithme",
|
||||||
"climate_entity_id": "Thermostat sous-jacent",
|
|
||||||
"climate_entity2_id": "2ème thermostat sous-jacent",
|
|
||||||
"climate_entity3_id": "3ème thermostat sous-jacent",
|
|
||||||
"climate_entity4_id": "4ème thermostat sous-jacent",
|
|
||||||
"ac_mode": "AC mode ?",
|
"ac_mode": "AC mode ?",
|
||||||
"valve_entity_id": "1ère valve",
|
"sonoff_trvzb_mode": "Mode Sonoff TRVZB",
|
||||||
"valve_entity2_id": "2ème valve",
|
"auto_regulation_mode": "Auto-régulation",
|
||||||
"valve_entity3_id": "3ème valve",
|
|
||||||
"valve_entity4_id": "4ème valve",
|
|
||||||
"auto_regulation_mode": "Auto-regulation",
|
|
||||||
"auto_regulation_dtemp": "Seuil de régulation",
|
"auto_regulation_dtemp": "Seuil de régulation",
|
||||||
"auto_regulation_periode_min": "Période minimale de régulation",
|
"auto_regulation_periode_min": "Période minimale de régulation",
|
||||||
"auto_regulation_use_device_temp": "Utiliser la température interne du sous-jacent",
|
"auto_regulation_use_device_temp": "Compenser la température interne du sous-jacent",
|
||||||
"inverse_switch_command": "Inverser la commande",
|
"inverse_switch_command": "Inverser la commande",
|
||||||
"auto_fan_mode": "Auto fan mode"
|
"auto_fan_mode": " Auto ventilation mode"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"heater_entity_id": "Entity id du 1er radiateur obligatoire",
|
"underlying_entity_ids": "La liste des équipements qui seront controlés par ce VTherm",
|
||||||
"heater_entity2_id": "Optionnel entity id du 2ème radiateur",
|
|
||||||
"heater_entity3_id": "Optionnel entity id du 3ème radiateur",
|
|
||||||
"heater_entity4_id": "Optionnel entity id du 4ème radiateur",
|
|
||||||
"heater_keep_alive": "Intervalle de rafraichissement du switch en secondes. Laisser vide pour désactiver. À n'utiliser que pour les switchs qui le nécessite.",
|
"heater_keep_alive": "Intervalle de rafraichissement du switch en secondes. Laisser vide pour désactiver. À n'utiliser que pour les switchs qui le nécessite.",
|
||||||
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
|
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
|
||||||
"climate_entity_id": "Entity id du thermostat sous-jacent",
|
|
||||||
"climate_entity2_id": "Entity id du 2ème thermostat sous-jacent",
|
|
||||||
"climate_entity3_id": "Entity id du 3ème thermostat sous-jacent",
|
|
||||||
"climate_entity4_id": "Entity id du 4ème thermostat sous-jacent",
|
|
||||||
"ac_mode": "Utilisation du mode Air Conditionné (AC)",
|
"ac_mode": "Utilisation du mode Air Conditionné (AC)",
|
||||||
"valve_entity_id": "Entity id de la 1ère valve",
|
"sonoff_trvzb_mode": "Les équipements sont des Sonoff TRVZB. Vous devez configurer les entités dédiées dans le menu 'Configuration Sonoff TRVZB'",
|
||||||
"valve_entity2_id": "Entity id de la 2ème valve",
|
"auto_regulation_mode": "Ajustement automatique de la température cible",
|
||||||
"valve_entity3_id": "Entity id de la 3ème valve",
|
|
||||||
"valve_entity4_id": "Entity id de la 4ème valve",
|
|
||||||
"auto_regulation_mode": "Ajustement automatique de la consigne",
|
|
||||||
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-dessous duquel la régulation ne sera pas envoyée",
|
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-dessous duquel la régulation ne sera pas envoyée",
|
||||||
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
|
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
|
||||||
"auto_regulation_use_device_temp": "Utiliser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
|
"auto_regulation_use_device_temp": "Compenser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
|
||||||
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
|
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
|
||||||
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
|
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
|
||||||
}
|
}
|
||||||
@@ -476,6 +458,22 @@
|
|||||||
"central_boiler_activation_service": "Commande à éxecuter pour allumer la chaudière centrale au format entity_id/service_name[/attribut:valeur]",
|
"central_boiler_activation_service": "Commande à éxecuter pour allumer la chaudière centrale au format entity_id/service_name[/attribut:valeur]",
|
||||||
"central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]"
|
"central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"sonoff_trvzb": {
|
||||||
|
"title": "Configuration Sonoff TRVZB - {name}",
|
||||||
|
"description": "Configuration spécifique des Sonoff TRVZB",
|
||||||
|
"data": {
|
||||||
|
"offset_calibration_entity_ids": "Entités de 'Offset calibration'",
|
||||||
|
"opening_degree_entity_ids": "Entités de 'Opening degree'",
|
||||||
|
"closing_degree_entity_ids": "Entités de 'Closing degree'",
|
||||||
|
"proportional_function": "Algorithme"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"offset_calibration_entity_ids": "La liste des entités 'offset calibration' entities. Il doit y en avoir une par entité climate sous-jacente",
|
||||||
|
"opening_degree_entity_ids": "La liste des entités 'opening degree' entities. Il doit y en avoir une par entité climate sous-jacente",
|
||||||
|
"closing_degree_entity_ids": "La liste des entités 'closing degree' entities. Il doit y en avoir une par entité climate sous-jacente",
|
||||||
|
"proportional_function": "Algorithme à utiliser (seulement TPI est disponible)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
@@ -483,7 +481,8 @@
|
|||||||
"unknown_entity": "entity id inconnu",
|
"unknown_entity": "entity id inconnu",
|
||||||
"window_open_detection_method": "Une seule méthode de détection des ouvertures ouvertes doit être utilisée. Utilisez le détecteur d'ouverture ou les seuils de température mais pas les deux.",
|
"window_open_detection_method": "Une seule méthode de détection des ouvertures ouvertes doit être utilisée. Utilisez le détecteur d'ouverture ou les seuils de température mais pas les deux.",
|
||||||
"no_central_config": "Vous ne pouvez pas cocher 'Utiliser la configuration centrale' car aucune configuration centrale n'a été trouvée. Vous devez créer un Versatile Thermostat de type 'Central Configuration' pour pouvoir l'utiliser.",
|
"no_central_config": "Vous ne pouvez pas cocher 'Utiliser la configuration centrale' car aucune configuration centrale n'a été trouvée. Vous devez créer un Versatile Thermostat de type 'Central Configuration' pour pouvoir l'utiliser.",
|
||||||
"service_configuration_format": "Mauvais format de la configuration du service"
|
"service_configuration_format": "Mauvais format de la configuration du service",
|
||||||
|
"sonoff_trvzb_nb_entities_incorrect": "Le nombre d'entités spécifiques au Sonoff TRVZB doit être égal au nombre d'entité sous-jacentes"
|
||||||
},
|
},
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "Le device est déjà configuré"
|
"already_configured": "Le device est déjà configuré"
|
||||||
@@ -532,6 +531,14 @@
|
|||||||
"comfort": "Confort",
|
"comfort": "Confort",
|
||||||
"boost": "Renforcé (boost)"
|
"boost": "Renforcé (boost)"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"auto_start_stop": {
|
||||||
|
"options": {
|
||||||
|
"auto_start_stop_none": "No auto start/stop",
|
||||||
|
"auto_start_stop_slow": "Slow detection",
|
||||||
|
"auto_start_stop_medium": "Medium detection",
|
||||||
|
"auto_start_stop_fast": "Fast detection"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
@@ -542,7 +549,8 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"power": "Délestage",
|
"power": "Délestage",
|
||||||
"security": "Sécurité",
|
"security": "Sécurité",
|
||||||
"none": "Manuel"
|
"none": "Manuel",
|
||||||
|
"frost": "Hors Gel"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -364,7 +364,8 @@
|
|||||||
"state": {
|
"state": {
|
||||||
"power": "Ripartizione",
|
"power": "Ripartizione",
|
||||||
"security": "Sicurezza",
|
"security": "Sicurezza",
|
||||||
"none": "Manuale"
|
"none": "Manuale",
|
||||||
|
"frost": "Gelo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# pylint: disable=unused-argument, line-too-long
|
# pylint: disable=unused-argument, line-too-long, too-many-lines
|
||||||
|
|
||||||
""" Underlying entities classes """
|
""" Underlying entities classes """
|
||||||
import logging
|
import logging
|
||||||
@@ -53,6 +53,9 @@ class UnderlyingEntityType(StrEnum):
|
|||||||
# a valve
|
# a valve
|
||||||
VALVE = "valve"
|
VALVE = "valve"
|
||||||
|
|
||||||
|
# a Sonoff TRVZB
|
||||||
|
SONOFF_TRVZB = "sonoff_trvzb"
|
||||||
|
|
||||||
|
|
||||||
class UnderlyingEntity:
|
class UnderlyingEntity:
|
||||||
"""Represent a underlying device which could be a switch or a climate"""
|
"""Represent a underlying device which could be a switch or a climate"""
|
||||||
@@ -62,6 +65,7 @@ class UnderlyingEntity:
|
|||||||
_thermostat: Any
|
_thermostat: Any
|
||||||
_entity_id: str
|
_entity_id: str
|
||||||
_type: UnderlyingEntityType
|
_type: UnderlyingEntityType
|
||||||
|
_hvac_mode: HVACMode | None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -75,6 +79,7 @@ class UnderlyingEntity:
|
|||||||
self._thermostat = thermostat
|
self._thermostat = thermostat
|
||||||
self._type = entity_type
|
self._type = entity_type
|
||||||
self._entity_id = entity_id
|
self._entity_id = entity_id
|
||||||
|
self._hvac_mode = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self._thermostat) + "-" + self._entity_id
|
return str(self._thermostat) + "-" + self._entity_id
|
||||||
@@ -100,13 +105,24 @@ class UnderlyingEntity:
|
|||||||
|
|
||||||
async def set_hvac_mode(self, hvac_mode: HVACMode):
|
async def set_hvac_mode(self, hvac_mode: HVACMode):
|
||||||
"""Set the HVACmode"""
|
"""Set the HVACmode"""
|
||||||
|
self._hvac_mode = hvac_mode
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_mode(self) -> HVACMode | None:
|
||||||
|
"""Return the current hvac_mode"""
|
||||||
|
return self._hvac_mode
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_device_active(self) -> bool | None:
|
def is_device_active(self) -> bool | None:
|
||||||
"""If the toggleable device is currently active."""
|
"""If the toggleable device is currently active."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_action(self) -> HVACAction:
|
||||||
|
"""Calculate a hvac_action"""
|
||||||
|
return HVACAction.HEATING if self.is_device_active is True else HVACAction.OFF
|
||||||
|
|
||||||
async def set_temperature(self, temperature, max_temp, min_temp):
|
async def set_temperature(self, temperature, max_temp, min_temp):
|
||||||
"""Set the target temperature"""
|
"""Set the target temperature"""
|
||||||
return
|
return
|
||||||
@@ -181,7 +197,6 @@ class UnderlyingSwitch(UnderlyingEntity):
|
|||||||
_initialDelaySec: int
|
_initialDelaySec: int
|
||||||
_on_time_sec: int
|
_on_time_sec: int
|
||||||
_off_time_sec: int
|
_off_time_sec: int
|
||||||
_hvac_mode: HVACMode
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -204,7 +219,6 @@ class UnderlyingSwitch(UnderlyingEntity):
|
|||||||
self._should_relaunch_control_heating = False
|
self._should_relaunch_control_heating = False
|
||||||
self._on_time_sec = 0
|
self._on_time_sec = 0
|
||||||
self._off_time_sec = 0
|
self._off_time_sec = 0
|
||||||
self._hvac_mode = None
|
|
||||||
self._keep_alive = IntervalCaller(hass, keep_alive_sec)
|
self._keep_alive = IntervalCaller(hass, keep_alive_sec)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -237,8 +251,8 @@ class UnderlyingSwitch(UnderlyingEntity):
|
|||||||
await self.turn_off()
|
await self.turn_off()
|
||||||
self._cancel_cycle()
|
self._cancel_cycle()
|
||||||
|
|
||||||
if self._hvac_mode != hvac_mode:
|
if self.hvac_mode != hvac_mode:
|
||||||
self._hvac_mode = hvac_mode
|
super().set_hvac_mode(hvac_mode)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
@@ -713,6 +727,13 @@ class UnderlyingClimate(UnderlyingEntity):
|
|||||||
return []
|
return []
|
||||||
return self._underlying_climate.hvac_modes
|
return self._underlying_climate.hvac_modes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_humidity(self) -> float | None:
|
||||||
|
"""Get the humidity"""
|
||||||
|
if not self.is_initialized:
|
||||||
|
return None
|
||||||
|
return self._underlying_climate.current_humidity
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fan_modes(self) -> list[str]:
|
def fan_modes(self) -> list[str]:
|
||||||
"""Get the fan_modes"""
|
"""Get the fan_modes"""
|
||||||
@@ -847,11 +868,12 @@ class UnderlyingValve(UnderlyingEntity):
|
|||||||
_hvac_mode: HVACMode
|
_hvac_mode: HVACMode
|
||||||
# This is the percentage of opening int integer (from 0 to 100)
|
# This is the percentage of opening int integer (from 0 to 100)
|
||||||
_percent_open: int
|
_percent_open: int
|
||||||
|
_last_sent_temperature = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, hass: HomeAssistant, thermostat: Any, valve_entity_id: str
|
self, hass: HomeAssistant, thermostat: Any, valve_entity_id: str
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the underlying switch"""
|
"""Initialize the underlying valve"""
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
hass=hass,
|
hass=hass,
|
||||||
@@ -865,13 +887,12 @@ class UnderlyingValve(UnderlyingEntity):
|
|||||||
self._percent_open = self._thermostat.valve_open_percent
|
self._percent_open = self._thermostat.valve_open_percent
|
||||||
self._valve_entity_id = valve_entity_id
|
self._valve_entity_id = valve_entity_id
|
||||||
|
|
||||||
async def send_percent_open(self):
|
async def _send_value_to_number(self, number_entity_id: str, value: int):
|
||||||
"""Send the percent open to the underlying valve"""
|
"""Send a value to a number entity"""
|
||||||
# This may fails if called after shutdown
|
|
||||||
try:
|
try:
|
||||||
data = {"value": self._percent_open}
|
data = {"value": value}
|
||||||
target = {ATTR_ENTITY_ID: self._entity_id}
|
target = {ATTR_ENTITY_ID: number_entity_id}
|
||||||
domain = self._entity_id.split(".")[0]
|
domain = number_entity_id.split(".")[0]
|
||||||
await self._hass.services.async_call(
|
await self._hass.services.async_call(
|
||||||
domain=domain,
|
domain=domain,
|
||||||
service=SERVICE_SET_VALUE,
|
service=SERVICE_SET_VALUE,
|
||||||
@@ -883,6 +904,11 @@ class UnderlyingValve(UnderlyingEntity):
|
|||||||
# This could happens in unit test if input_number domain is not yet loaded
|
# This could happens in unit test if input_number domain is not yet loaded
|
||||||
# raise err
|
# raise err
|
||||||
|
|
||||||
|
async def send_percent_open(self):
|
||||||
|
"""Send the percent open to the underlying valve"""
|
||||||
|
# This may fails if called after shutdown
|
||||||
|
return await self._send_value_to_number(self._entity_id, self._percent_open)
|
||||||
|
|
||||||
async def turn_off(self):
|
async def turn_off(self):
|
||||||
"""Turn heater toggleable device off."""
|
"""Turn heater toggleable device off."""
|
||||||
_LOGGER.debug("%s - Stopping underlying valve entity %s", self, self._entity_id)
|
_LOGGER.debug("%s - Stopping underlying valve entity %s", self, self._entity_id)
|
||||||
@@ -988,3 +1014,111 @@ class UnderlyingValve(UnderlyingEntity):
|
|||||||
def remove_entity(self):
|
def remove_entity(self):
|
||||||
"""Remove the entity after stopping its cycle"""
|
"""Remove the entity after stopping its cycle"""
|
||||||
self._cancel_cycle()
|
self._cancel_cycle()
|
||||||
|
|
||||||
|
|
||||||
|
class UnderlyingSonoffTRVZB(UnderlyingValve):
|
||||||
|
"""A specific underlying class for Sonoff TRVZB TRV"""
|
||||||
|
|
||||||
|
_offset_calibration_entity_id: str
|
||||||
|
_opening_degree_entity_id: str
|
||||||
|
_closing_degree_entity_id: str
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
thermostat: Any,
|
||||||
|
offset_calibration_entity_id: str,
|
||||||
|
opening_degree_entity_id: str,
|
||||||
|
closing_degree_entity_id: str,
|
||||||
|
climate_underlying: UnderlyingClimate,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the underlying Sonoff TRV"""
|
||||||
|
super().__init__(hass, thermostat, opening_degree_entity_id)
|
||||||
|
self._offset_calibration_entity_id = offset_calibration_entity_id
|
||||||
|
self._opening_degree_entity_id = opening_degree_entity_id
|
||||||
|
self._closing_degree_entity_id = closing_degree_entity_id
|
||||||
|
self._climate_underlying = climate_underlying
|
||||||
|
self._is_min_max_initialized = False
|
||||||
|
self._max_opening_degree = None
|
||||||
|
self._min_offset_calibration = None
|
||||||
|
self._max_offset_calibration = None
|
||||||
|
|
||||||
|
async def send_percent_open(self):
|
||||||
|
"""Send the percent open to the underlying valve"""
|
||||||
|
if not self._is_min_max_initialized:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - initialize min offset_calibration and max open_degree", self
|
||||||
|
)
|
||||||
|
self._max_opening_degree = self._hass.states.get(
|
||||||
|
self._opening_degree_entity_id
|
||||||
|
).attributes.get("max")
|
||||||
|
self._min_offset_calibration = self._hass.states.get(
|
||||||
|
self._offset_calibration_entity_id
|
||||||
|
).attributes.get("min")
|
||||||
|
self._max_offset_calibration = self._hass.states.get(
|
||||||
|
self._offset_calibration_entity_id
|
||||||
|
).attributes.get("max")
|
||||||
|
|
||||||
|
self._is_min_max_initialized = (
|
||||||
|
self._max_opening_degree is not None
|
||||||
|
and self._min_offset_calibration is not None
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self._is_min_max_initialized:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"%s - impossible to initialize max_opening_degree or min_offset_calibration. Abort sending percent open to the valve. This could be a temporary message at startup."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Send opening_degree
|
||||||
|
await super().send_percent_open()
|
||||||
|
|
||||||
|
# Send closing_degree.
|
||||||
|
await self._send_value_to_number(
|
||||||
|
self._closing_degree_entity_id,
|
||||||
|
closing_degree := self._max_opening_degree - self._percent_open,
|
||||||
|
)
|
||||||
|
|
||||||
|
# send offset_calibration to the difference between target temp and local temp
|
||||||
|
offset = 0
|
||||||
|
if (
|
||||||
|
local_temp := self._climate_underlying.underlying_current_temperature
|
||||||
|
) is not None and (
|
||||||
|
room_temp := self._thermostat.current_temperature
|
||||||
|
) is not None:
|
||||||
|
offset = min(
|
||||||
|
self._max_offset_calibration,
|
||||||
|
max(self._min_offset_calibration, room_temp - local_temp),
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._send_value_to_number(self._offset_calibration_entity_id, offset)
|
||||||
|
|
||||||
|
_LOGGER.debug(
|
||||||
|
"%s - SonoffTRVZB - I have sent offset_calibration=%s opening_degree=%s closing_degree=%s",
|
||||||
|
self,
|
||||||
|
offset,
|
||||||
|
self._percent_open,
|
||||||
|
closing_degree,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def offset_calibration_entity_id(self) -> str:
|
||||||
|
"""The offset_calibration_entity_id"""
|
||||||
|
return self._offset_calibration_entity_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def opening_degree_entity_id(self) -> str:
|
||||||
|
"""The offset_calibration_entity_id"""
|
||||||
|
return self._opening_degree_entity_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def closing_degree_entity_id(self) -> str:
|
||||||
|
"""The offset_calibration_entity_id"""
|
||||||
|
return self._closing_degree_entity_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hvac_modes(self) -> list[HVACMode]:
|
||||||
|
"""Get the hvac_modes"""
|
||||||
|
if not self.is_initialized:
|
||||||
|
return []
|
||||||
|
return [HVACMode.OFF, HVACMode.HEAT]
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from .const import (
|
|||||||
CONF_SAFETY_MODE,
|
CONF_SAFETY_MODE,
|
||||||
CONF_THERMOSTAT_TYPE,
|
CONF_THERMOSTAT_TYPE,
|
||||||
CONF_THERMOSTAT_CENTRAL_CONFIG,
|
CONF_THERMOSTAT_CENTRAL_CONFIG,
|
||||||
|
CONF_MAX_ON_PERCENT,
|
||||||
)
|
)
|
||||||
|
|
||||||
VTHERM_API_NAME = "vtherm_api"
|
VTHERM_API_NAME = "vtherm_api"
|
||||||
@@ -60,6 +61,7 @@ class VersatileThermostatAPI(dict):
|
|||||||
self._central_mode_select = None
|
self._central_mode_select = None
|
||||||
# A dict that will store all Number entities which holds the temperature
|
# A dict that will store all Number entities which holds the temperature
|
||||||
self._number_temperatures = dict()
|
self._number_temperatures = dict()
|
||||||
|
self._max_on_percent = None
|
||||||
|
|
||||||
def find_central_configuration(self):
|
def find_central_configuration(self):
|
||||||
"""Search for a central configuration"""
|
"""Search for a central configuration"""
|
||||||
@@ -107,6 +109,12 @@ class VersatileThermostatAPI(dict):
|
|||||||
if self._safety_mode:
|
if self._safety_mode:
|
||||||
_LOGGER.debug("We have found safet_mode params %s", self._safety_mode)
|
_LOGGER.debug("We have found safet_mode params %s", self._safety_mode)
|
||||||
|
|
||||||
|
self._max_on_percent = config.get(CONF_MAX_ON_PERCENT)
|
||||||
|
if self._max_on_percent:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"We have found max_on_percent setting %s", self._max_on_percent
|
||||||
|
)
|
||||||
|
|
||||||
def register_central_boiler(self, central_boiler_entity):
|
def register_central_boiler(self, central_boiler_entity):
|
||||||
"""Register the central boiler entity. This is used by the CentralBoilerBinarySensor
|
"""Register the central boiler entity. This is used by the CentralBoilerBinarySensor
|
||||||
class to register itself at creation"""
|
class to register itself at creation"""
|
||||||
@@ -150,10 +158,11 @@ class VersatileThermostatAPI(dict):
|
|||||||
return entity.state
|
return entity.state
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def init_vtherm_links(self):
|
async def init_vtherm_links(self, entry_id=None):
|
||||||
"""Initialize all VTherms entities links
|
"""Initialize all VTherms entities links
|
||||||
This method is called when HA is fully started (and all entities should be initialized)
|
This method is called when HA is fully started (and all entities should be initialized)
|
||||||
Or when we need to reload all VTherm links (with Number temp entities, central boiler, ...)
|
Or when we need to reload all VTherm links (with Number temp entities, central boiler, ...)
|
||||||
|
If entry_id is set, only the VTherm of this entry will be reloaded
|
||||||
"""
|
"""
|
||||||
await self.reload_central_boiler_binary_listener()
|
await self.reload_central_boiler_binary_listener()
|
||||||
await self.reload_central_boiler_entities_list()
|
await self.reload_central_boiler_entities_list()
|
||||||
@@ -170,12 +179,14 @@ class VersatileThermostatAPI(dict):
|
|||||||
# ):
|
# ):
|
||||||
# await entity.init_presets(self.find_central_configuration())
|
# await entity.init_presets(self.find_central_configuration())
|
||||||
|
|
||||||
# A little hack to test if the climate is a VTherm. Cannot use isinstance due to circular dependency of BaseThermostat
|
# A little hack to test if the climate is a VTherm. Cannot use isinstance
|
||||||
|
# due to circular dependency of BaseThermostat
|
||||||
if (
|
if (
|
||||||
entity.device_info
|
entity.device_info
|
||||||
and entity.device_info.get("model", None) == DOMAIN
|
and entity.device_info.get("model", None) == DOMAIN
|
||||||
):
|
):
|
||||||
await entity.async_startup(self.find_central_configuration())
|
if entry_id is None or entry_id == entity.unique_id:
|
||||||
|
await entity.async_startup(self.find_central_configuration())
|
||||||
|
|
||||||
async def init_vtherm_preset_with_central(self):
|
async def init_vtherm_preset_with_central(self):
|
||||||
"""Init all VTherm presets when the VTherm uses central temperature"""
|
"""Init all VTherm presets when the VTherm uses central temperature"""
|
||||||
@@ -239,6 +250,11 @@ class VersatileThermostatAPI(dict):
|
|||||||
"""Get the safety_mode params"""
|
"""Get the safety_mode params"""
|
||||||
return self._safety_mode
|
return self._safety_mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_on_percent(self):
|
||||||
|
"""Get the max_open_percent params"""
|
||||||
|
return self._max_on_percent
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def central_boiler_entity(self):
|
def central_boiler_entity(self):
|
||||||
"""Get the central boiler binary_sensor entity"""
|
"""Get the central boiler binary_sensor entity"""
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
"content_in_root": false,
|
"content_in_root": false,
|
||||||
"render_readme": true,
|
"render_readme": true,
|
||||||
"hide_default_branch": false,
|
"hide_default_branch": false,
|
||||||
"homeassistant": "2024.9.3"
|
"homeassistant": "2024.10.4"
|
||||||
}
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
homeassistant==2024.9.3
|
homeassistant==2024.10.4
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ MOCK_TH_OVER_SWITCH_CENTRAL_MAIN_CONFIG = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MOCK_TH_OVER_SWITCH_TYPE_CONFIG = {
|
MOCK_TH_OVER_SWITCH_TYPE_CONFIG = {
|
||||||
CONF_HEATER: "switch.mock_switch",
|
CONF_UNDERLYING_LIST: ["switch.mock_switch"],
|
||||||
CONF_HEATER_KEEP_ALIVE: 0,
|
CONF_HEATER_KEEP_ALIVE: 0,
|
||||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||||
CONF_AC_MODE: False,
|
CONF_AC_MODE: False,
|
||||||
@@ -82,17 +82,14 @@ MOCK_TH_OVER_SWITCH_TYPE_CONFIG = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MOCK_TH_OVER_SWITCH_AC_TYPE_CONFIG = {
|
MOCK_TH_OVER_SWITCH_AC_TYPE_CONFIG = {
|
||||||
CONF_HEATER: "switch.mock_air_conditioner",
|
CONF_UNDERLYING_LIST: ["switch.mock_air_conditioner"],
|
||||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||||
CONF_AC_MODE: True,
|
CONF_AC_MODE: True,
|
||||||
CONF_INVERSE_SWITCH: False,
|
CONF_INVERSE_SWITCH: False,
|
||||||
}
|
}
|
||||||
|
|
||||||
MOCK_TH_OVER_4SWITCH_TYPE_CONFIG = {
|
MOCK_TH_OVER_4SWITCH_TYPE_CONFIG = {
|
||||||
CONF_HEATER: "switch.mock_4switch0",
|
CONF_UNDERLYING_LIST: ["switch.mock_4switch0", "switch.mock_4switch1","switch.mock_4switch2","switch.mock_4switch3"],
|
||||||
CONF_HEATER_2: "switch.mock_4switch1",
|
|
||||||
CONF_HEATER_3: "switch.mock_4switch2",
|
|
||||||
CONF_HEATER_4: "switch.mock_4switch3",
|
|
||||||
CONF_HEATER_KEEP_ALIVE: 0,
|
CONF_HEATER_KEEP_ALIVE: 0,
|
||||||
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
|
||||||
CONF_AC_MODE: False,
|
CONF_AC_MODE: False,
|
||||||
@@ -105,7 +102,7 @@ MOCK_TH_OVER_SWITCH_TPI_CONFIG = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MOCK_TH_OVER_CLIMATE_TYPE_CONFIG = {
|
MOCK_TH_OVER_CLIMATE_TYPE_CONFIG = {
|
||||||
CONF_CLIMATE: "climate.mock_climate",
|
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
|
||||||
CONF_AC_MODE: False,
|
CONF_AC_MODE: False,
|
||||||
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
|
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
|
||||||
CONF_AUTO_REGULATION_DTEMP: 0.5,
|
CONF_AUTO_REGULATION_DTEMP: 0.5,
|
||||||
@@ -115,7 +112,7 @@ MOCK_TH_OVER_CLIMATE_TYPE_CONFIG = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MOCK_TH_OVER_CLIMATE_TYPE_USE_DEVICE_TEMP_CONFIG = {
|
MOCK_TH_OVER_CLIMATE_TYPE_USE_DEVICE_TEMP_CONFIG = {
|
||||||
CONF_CLIMATE: "climate.mock_climate",
|
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
|
||||||
CONF_AC_MODE: False,
|
CONF_AC_MODE: False,
|
||||||
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
|
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
|
||||||
CONF_AUTO_REGULATION_DTEMP: 0.1,
|
CONF_AUTO_REGULATION_DTEMP: 0.1,
|
||||||
@@ -125,13 +122,13 @@ MOCK_TH_OVER_CLIMATE_TYPE_USE_DEVICE_TEMP_CONFIG = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG = {
|
MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG = {
|
||||||
CONF_CLIMATE: "climate.mock_climate",
|
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
|
||||||
CONF_AC_MODE: False,
|
CONF_AC_MODE: False,
|
||||||
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_NONE,
|
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_NONE,
|
||||||
}
|
}
|
||||||
|
|
||||||
MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG = {
|
MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG = {
|
||||||
CONF_CLIMATE: "climate.mock_climate",
|
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
|
||||||
CONF_AC_MODE: True,
|
CONF_AC_MODE: True,
|
||||||
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
|
CONF_AUTO_REGULATION_MODE: CONF_AUTO_REGULATION_STRONG,
|
||||||
CONF_AUTO_REGULATION_DTEMP: 0.5,
|
CONF_AUTO_REGULATION_DTEMP: 0.5,
|
||||||
|
|||||||
1465
tests/test_auto_start_stop.py
Normal file
1465
tests/test_auto_start_stop.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -630,6 +630,7 @@ async def test_climate_ac_only_change_central_mode_true(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 1. set hvac_mode to COOL and preet ECO
|
||||||
with patch("homeassistant.core.ServiceRegistry.async_call"), patch(
|
with patch("homeassistant.core.ServiceRegistry.async_call"), patch(
|
||||||
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||||
return_value=fake_underlying_climate,
|
return_value=fake_underlying_climate,
|
||||||
@@ -982,7 +983,8 @@ async def test_switch_change_central_mode_true_with_cool_only_and_window(
|
|||||||
await select_entity.async_select_option(CENTRAL_MODE_COOL_ONLY)
|
await select_entity.async_select_option(CENTRAL_MODE_COOL_ONLY)
|
||||||
|
|
||||||
assert entity.last_central_mode is CENTRAL_MODE_COOL_ONLY
|
assert entity.last_central_mode is CENTRAL_MODE_COOL_ONLY
|
||||||
await entity.async_set_hvac_mode(HVACMode.OFF)
|
assert entity.hvac_mode is HVACMode.OFF
|
||||||
|
assert entity.hvac_off_reason == HVAC_OFF_REASON_MANUAL
|
||||||
await entity.async_set_preset_mode(PRESET_ACTIVITY)
|
await entity.async_set_preset_mode(PRESET_ACTIVITY)
|
||||||
assert entity._saved_hvac_mode == HVACMode.HEAT
|
assert entity._saved_hvac_mode == HVACMode.HEAT
|
||||||
assert entity._saved_preset_mode == PRESET_ACTIVITY
|
assert entity._saved_preset_mode == PRESET_ACTIVITY
|
||||||
@@ -1000,12 +1002,14 @@ async def test_switch_change_central_mode_true_with_cool_only_and_window(
|
|||||||
|
|
||||||
await try_function(None)
|
await try_function(None)
|
||||||
|
|
||||||
assert mock_send_event.call_count == 1
|
# The VTherm is already off -> window detection is ignored
|
||||||
mock_send_event.assert_has_calls(
|
assert mock_send_event.call_count == 0
|
||||||
[call.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF})]
|
# mock_send_event.assert_has_calls(
|
||||||
)
|
# [call.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF})]
|
||||||
|
# )
|
||||||
|
|
||||||
assert entity.hvac_mode == HVACMode.OFF
|
assert entity.hvac_mode == HVACMode.OFF
|
||||||
|
assert entity.hvac_off_reason == HVAC_OFF_REASON_MANUAL
|
||||||
assert entity.preset_mode == PRESET_ACTIVITY
|
assert entity.preset_mode == PRESET_ACTIVITY
|
||||||
assert entity._saved_hvac_mode == HVACMode.HEAT
|
assert entity._saved_hvac_mode == HVACMode.HEAT
|
||||||
assert entity._saved_preset_mode == PRESET_ACTIVITY
|
assert entity._saved_preset_mode == PRESET_ACTIVITY
|
||||||
@@ -1021,6 +1025,8 @@ async def test_switch_change_central_mode_true_with_cool_only_and_window(
|
|||||||
assert entity.last_central_mode is CENTRAL_MODE_AUTO
|
assert entity.last_central_mode is CENTRAL_MODE_AUTO
|
||||||
# No change
|
# No change
|
||||||
assert entity.hvac_mode == HVACMode.OFF
|
assert entity.hvac_mode == HVACMode.OFF
|
||||||
|
# We have to a reason of WINDOW_DETECTION
|
||||||
|
assert entity.hvac_off_reason == HVAC_OFF_REASON_WINDOW_DETECTION
|
||||||
assert entity.preset_mode == PRESET_ACTIVITY
|
assert entity.preset_mode == PRESET_ACTIVITY
|
||||||
assert entity._saved_hvac_mode == HVACMode.HEAT
|
assert entity._saved_hvac_mode == HVACMode.HEAT
|
||||||
assert entity._saved_preset_mode == PRESET_ACTIVITY
|
assert entity._saved_preset_mode == PRESET_ACTIVITY
|
||||||
@@ -1046,6 +1052,7 @@ async def test_switch_change_central_mode_true_with_cool_only_and_window(
|
|||||||
|
|
||||||
# We should stay off because central is STOPPED
|
# We should stay off because central is STOPPED
|
||||||
assert entity.hvac_mode == HVACMode.HEAT
|
assert entity.hvac_mode == HVACMode.HEAT
|
||||||
|
assert entity.hvac_off_reason is None
|
||||||
assert entity.preset_mode == PRESET_ACTIVITY
|
assert entity.preset_mode == PRESET_ACTIVITY
|
||||||
assert entity._saved_hvac_mode == HVACMode.HEAT
|
assert entity._saved_hvac_mode == HVACMode.HEAT
|
||||||
assert entity._saved_preset_mode == PRESET_ACTIVITY
|
assert entity._saved_preset_mode == PRESET_ACTIVITY
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,10 +11,16 @@ from homeassistant.components.climate import (
|
|||||||
SERVICE_SET_TEMPERATURE,
|
SERVICE_SET_TEMPERATURE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||||
|
|
||||||
from custom_components.versatile_thermostat.thermostat_climate import (
|
from custom_components.versatile_thermostat.thermostat_climate import (
|
||||||
ThermostatOverClimate,
|
ThermostatOverClimate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from custom_components.versatile_thermostat.switch import (
|
||||||
|
FollowUnderlyingTemperatureChange,
|
||||||
|
)
|
||||||
|
|
||||||
from .commons import *
|
from .commons import *
|
||||||
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
logging.getLogger().setLevel(logging.DEBUG)
|
||||||
@@ -195,7 +201,7 @@ async def test_bug_82(
|
|||||||
|
|
||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
async def test_bug_101(
|
async def test_underlying_change_follow(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
skip_hass_states_is_state,
|
skip_hass_states_is_state,
|
||||||
skip_turn_on_off_heater,
|
skip_turn_on_off_heater,
|
||||||
@@ -229,12 +235,27 @@ async def test_bug_101(
|
|||||||
entity = await create_thermostat(hass, entry, "climate.theoverclimatemockname")
|
entity = await create_thermostat(hass, entry, "climate.theoverclimatemockname")
|
||||||
|
|
||||||
assert entity
|
assert entity
|
||||||
|
|
||||||
assert entity.name == "TheOverClimateMockName"
|
assert entity.name == "TheOverClimateMockName"
|
||||||
assert entity.is_over_climate is True
|
assert entity.is_over_climate is True
|
||||||
assert entity.hvac_mode is HVACMode.OFF
|
assert entity.hvac_mode is HVACMode.OFF
|
||||||
# because in MockClimate HVACAction is HEATING if hvac_mode is not set
|
# because in MockClimate HVACAction is HEATING if hvac_mode is not set
|
||||||
assert entity.hvac_action is HVACAction.HEATING
|
assert entity.hvac_action is HVACAction.HEATING
|
||||||
|
assert entity.follow_underlying_temp_change is False
|
||||||
|
|
||||||
|
follow_entity: FollowUnderlyingTemperatureChange = search_entity(
|
||||||
|
hass,
|
||||||
|
"switch.theoverclimatemockname_follow_underlying_temp_change",
|
||||||
|
SWITCH_DOMAIN,
|
||||||
|
)
|
||||||
|
assert follow_entity is not None
|
||||||
|
assert follow_entity.state is STATE_OFF
|
||||||
|
|
||||||
|
# follow the underlying temp change
|
||||||
|
follow_entity.turn_on()
|
||||||
|
|
||||||
|
assert entity.follow_underlying_temp_change is True
|
||||||
|
assert follow_entity.state is STATE_ON
|
||||||
|
|
||||||
# Underlying should have been shutdown
|
# Underlying should have been shutdown
|
||||||
assert mock_underlying_set_hvac_mode.call_count == 1
|
assert mock_underlying_set_hvac_mode.call_count == 1
|
||||||
mock_underlying_set_hvac_mode.assert_has_calls(
|
mock_underlying_set_hvac_mode.assert_has_calls(
|
||||||
@@ -320,6 +341,165 @@ async def test_bug_101(
|
|||||||
assert entity.preset_mode is PRESET_NONE
|
assert entity.preset_mode is PRESET_NONE
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
|
async def test_underlying_change_not_follow(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
skip_hass_states_is_state,
|
||||||
|
skip_turn_on_off_heater,
|
||||||
|
skip_send_event,
|
||||||
|
):
|
||||||
|
"""Test that when a underlying climate target temp is changed, the VTherm change its own temperature target and switch to manual"""
|
||||||
|
|
||||||
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||||
|
now: datetime = datetime.now(tz=tz)
|
||||||
|
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="TheOverClimateMockName",
|
||||||
|
unique_id="uniqueId",
|
||||||
|
data=PARTIAL_CLIMATE_NOT_REGULATED_CONFIG, # 5 minutes security delay
|
||||||
|
)
|
||||||
|
|
||||||
|
# Underlying is in HEAT mode but should be shutdown at startup
|
||||||
|
fake_underlying_climate = MockClimate(
|
||||||
|
hass, "mockUniqueId", "MockClimateName", {}, HVACMode.HEAT, HVACAction.HEATING
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||||
|
) as mock_send_event, patch(
|
||||||
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||||
|
return_value=fake_underlying_climate,
|
||||||
|
) as mock_find_climate, patch(
|
||||||
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
||||||
|
) as mock_underlying_set_hvac_mode:
|
||||||
|
entity = await create_thermostat(hass, entry, "climate.theoverclimatemockname")
|
||||||
|
|
||||||
|
assert entity
|
||||||
|
|
||||||
|
assert entity.name == "TheOverClimateMockName"
|
||||||
|
assert entity.is_over_climate is True
|
||||||
|
assert entity.hvac_mode is HVACMode.OFF
|
||||||
|
# because in MockClimate HVACAction is HEATING if hvac_mode is not set
|
||||||
|
assert entity.hvac_action is HVACAction.HEATING
|
||||||
|
assert entity.target_temperature == 15
|
||||||
|
assert entity.preset_mode is PRESET_NONE
|
||||||
|
|
||||||
|
# default value
|
||||||
|
assert entity.follow_underlying_temp_change is False
|
||||||
|
|
||||||
|
follow_entity: FollowUnderlyingTemperatureChange = search_entity(
|
||||||
|
hass,
|
||||||
|
"switch.theoverclimatemockname_follow_underlying_temp_change",
|
||||||
|
SWITCH_DOMAIN,
|
||||||
|
)
|
||||||
|
assert follow_entity is not None
|
||||||
|
assert follow_entity.state is STATE_OFF
|
||||||
|
|
||||||
|
# follow the underlying temp change
|
||||||
|
follow_entity.turn_off()
|
||||||
|
|
||||||
|
assert entity.follow_underlying_temp_change is False
|
||||||
|
assert follow_entity.state is STATE_OFF
|
||||||
|
|
||||||
|
# 1. Force preset mode
|
||||||
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||||
|
assert entity.hvac_mode == HVACMode.HEAT
|
||||||
|
await entity.async_set_preset_mode(PRESET_COMFORT)
|
||||||
|
assert entity.preset_mode == PRESET_COMFORT
|
||||||
|
assert entity.target_temperature == 17
|
||||||
|
|
||||||
|
# 2. Change the target temp of underlying thermostat at 11 sec later to avoid temporal filter
|
||||||
|
event_timestamp = now + timedelta(seconds=30)
|
||||||
|
await send_climate_change_event_with_temperature(
|
||||||
|
entity,
|
||||||
|
HVACMode.HEAT,
|
||||||
|
HVACMode.HEAT,
|
||||||
|
HVACAction.OFF,
|
||||||
|
HVACAction.OFF,
|
||||||
|
event_timestamp,
|
||||||
|
21,
|
||||||
|
True,
|
||||||
|
"climate.mock_climate", # the underlying climate entity id
|
||||||
|
)
|
||||||
|
# Should NOT have been switched to Manual preset
|
||||||
|
assert entity.target_temperature == 17
|
||||||
|
assert entity.preset_mode is PRESET_COMFORT
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
|
async def test_bug_615(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
skip_hass_states_is_state,
|
||||||
|
skip_turn_on_off_heater,
|
||||||
|
skip_send_event,
|
||||||
|
):
|
||||||
|
"""Test that when a underlying climate target temp is changed, the VTherm don't change its own temperature target if no
|
||||||
|
target_temperature have already been sent"""
|
||||||
|
|
||||||
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||||
|
now: datetime = datetime.now(tz=tz)
|
||||||
|
|
||||||
|
entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="TheOverClimateMockName",
|
||||||
|
unique_id="uniqueId",
|
||||||
|
data=PARTIAL_CLIMATE_NOT_REGULATED_CONFIG, # 5 minutes security delay
|
||||||
|
)
|
||||||
|
|
||||||
|
# Underlying is in HEAT mode but should be shutdown at startup
|
||||||
|
fake_underlying_climate = MockClimate(
|
||||||
|
hass, "mockUniqueId", "MockClimateName", {}, HVACMode.HEAT, HVACAction.HEATING
|
||||||
|
)
|
||||||
|
|
||||||
|
# 1. create the thermostat
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||||
|
), patch(
|
||||||
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||||
|
return_value=fake_underlying_climate,
|
||||||
|
):
|
||||||
|
vtherm = await create_thermostat(hass, entry, "climate.theoverclimatemockname")
|
||||||
|
|
||||||
|
assert vtherm
|
||||||
|
|
||||||
|
assert vtherm.name == "TheOverClimateMockName"
|
||||||
|
assert vtherm.is_over_climate is True
|
||||||
|
assert vtherm.hvac_mode is HVACMode.OFF
|
||||||
|
# because in MockClimate HVACAction is HEATING if hvac_mode is not set
|
||||||
|
assert vtherm.hvac_action is HVACAction.HEATING
|
||||||
|
|
||||||
|
# Force a preset_mode without sending a temperature (as it was restored with a preset)
|
||||||
|
vtherm._attr_preset_mode = PRESET_BOOST
|
||||||
|
|
||||||
|
assert vtherm.target_temperature == vtherm.min_temp
|
||||||
|
assert vtherm.preset_mode is PRESET_BOOST
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
|
||||||
|
) as mock_underlying_set_hvac_mode:
|
||||||
|
# 2. Change the target temp of underlying thermostat at now + 1 min
|
||||||
|
now = now + timedelta(minutes=1)
|
||||||
|
await send_climate_change_event_with_temperature(
|
||||||
|
vtherm,
|
||||||
|
HVACMode.OFF,
|
||||||
|
HVACMode.OFF,
|
||||||
|
HVACAction.OFF,
|
||||||
|
HVACAction.OFF,
|
||||||
|
now,
|
||||||
|
25,
|
||||||
|
True,
|
||||||
|
"climate.mock_climate", # the underlying climate entity id
|
||||||
|
)
|
||||||
|
# Should NOT have been taken the new target temp nor have change the preset
|
||||||
|
assert vtherm.target_temperature == vtherm.min_temp
|
||||||
|
assert vtherm.preset_mode is PRESET_BOOST
|
||||||
|
|
||||||
|
mock_underlying_set_hvac_mode.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
async def test_bug_508(
|
async def test_bug_508(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@@ -593,13 +773,26 @@ async def test_ignore_temp_outside_minmax_range(
|
|||||||
assert mock_find_climate.mock_calls[0] == call()
|
assert mock_find_climate.mock_calls[0] == call()
|
||||||
mock_find_climate.assert_has_calls([call.find_underlying_entity()])
|
mock_find_climate.assert_has_calls([call.find_underlying_entity()])
|
||||||
|
|
||||||
# 1. Force preset mode
|
# 1. VTherm must follow the underlying's temperature changes
|
||||||
|
follow_entity: FollowUnderlyingTemperatureChange = search_entity(
|
||||||
|
hass,
|
||||||
|
"switch.theoverclimatemockname_follow_underlying_temp_change",
|
||||||
|
SWITCH_DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
# follow the underlying temp change
|
||||||
|
follow_entity.turn_on()
|
||||||
|
|
||||||
|
assert entity.follow_underlying_temp_change is True
|
||||||
|
assert follow_entity.state is STATE_ON
|
||||||
|
|
||||||
|
# 2. Force preset mode
|
||||||
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
await entity.async_set_hvac_mode(HVACMode.HEAT)
|
||||||
assert entity.hvac_mode == HVACMode.HEAT
|
assert entity.hvac_mode == HVACMode.HEAT
|
||||||
await entity.async_set_preset_mode(PRESET_COMFORT)
|
await entity.async_set_preset_mode(PRESET_COMFORT)
|
||||||
assert entity.preset_mode == PRESET_COMFORT
|
assert entity.preset_mode == PRESET_COMFORT
|
||||||
|
|
||||||
# 1. Try to set the target temperature to a below min_temp -> should be ignored
|
# 3. Try to set the target temperature to a below min_temp -> should be ignored
|
||||||
# Wait 11 sec
|
# Wait 11 sec
|
||||||
event_timestamp = now + timedelta(seconds=11)
|
event_timestamp = now + timedelta(seconds=11)
|
||||||
assert entity.is_regulated is False
|
assert entity.is_regulated is False
|
||||||
@@ -607,8 +800,8 @@ async def test_ignore_temp_outside_minmax_range(
|
|||||||
entity,
|
entity,
|
||||||
HVACMode.HEAT,
|
HVACMode.HEAT,
|
||||||
HVACMode.HEAT,
|
HVACMode.HEAT,
|
||||||
HVACAction.OFF,
|
HVACAction.HEATING,
|
||||||
HVACAction.OFF,
|
HVACAction.HEATING,
|
||||||
event_timestamp,
|
event_timestamp,
|
||||||
entity.min_temp - 1,
|
entity.min_temp - 1,
|
||||||
True,
|
True,
|
||||||
@@ -616,18 +809,393 @@ async def test_ignore_temp_outside_minmax_range(
|
|||||||
)
|
)
|
||||||
assert entity.target_temperature == 17
|
assert entity.target_temperature == 17
|
||||||
|
|
||||||
# 2. Try to set the target temperature to a above max_temp -> should be ignored
|
# 4. Try to set the target temperature to a above max_temp -> should be ignored
|
||||||
event_timestamp = event_timestamp + timedelta(seconds=11)
|
event_timestamp = event_timestamp + timedelta(seconds=11)
|
||||||
assert entity.is_regulated is False
|
assert entity.is_regulated is False
|
||||||
await send_climate_change_event_with_temperature(
|
await send_climate_change_event_with_temperature(
|
||||||
entity,
|
entity,
|
||||||
HVACMode.HEAT,
|
HVACMode.HEAT,
|
||||||
HVACMode.HEAT,
|
HVACMode.HEAT,
|
||||||
HVACAction.OFF,
|
HVACAction.HEATING,
|
||||||
HVACAction.OFF,
|
HVACAction.HEATING,
|
||||||
event_timestamp,
|
event_timestamp,
|
||||||
entity.max_temp + 1,
|
entity.max_temp + 1,
|
||||||
True,
|
True,
|
||||||
"climate.mock_climate", # the underlying climate entity id
|
"climate.mock_climate", # the underlying climate entity id
|
||||||
)
|
)
|
||||||
assert entity.target_temperature == 17
|
assert entity.target_temperature == 17
|
||||||
|
|
||||||
|
# 5. Switch off the VTherm and receive an event from the underlying with a temp to be ignored,
|
||||||
|
# but an HVACAction to be taken into account
|
||||||
|
await entity.async_set_hvac_mode(HVACMode.OFF)
|
||||||
|
assert entity.hvac_mode == HVACMode.OFF
|
||||||
|
|
||||||
|
fake_underlying_climate.set_hvac_mode(HVACMode.OFF)
|
||||||
|
fake_underlying_climate.set_hvac_action(HVACAction.IDLE)
|
||||||
|
|
||||||
|
event_timestamp = event_timestamp + timedelta(seconds=11)
|
||||||
|
await send_climate_change_event_with_temperature(
|
||||||
|
entity,
|
||||||
|
HVACMode.OFF,
|
||||||
|
HVACMode.HEAT,
|
||||||
|
HVACAction.IDLE,
|
||||||
|
HVACAction.HEATING,
|
||||||
|
event_timestamp,
|
||||||
|
entity.min_temp - 1,
|
||||||
|
True,
|
||||||
|
"climate.mock_climate", # the underlying climate entity id
|
||||||
|
)
|
||||||
|
assert entity.target_temperature == 17
|
||||||
|
assert entity.hvac_action == HVACAction.IDLE
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
|
async def test_manual_hvac_off_should_take_the_lead_over_window(
|
||||||
|
hass: HomeAssistant, skip_hass_states_is_state
|
||||||
|
):
|
||||||
|
"""Test than a manual hvac_off is taken into account over a window hvac_off"""
|
||||||
|
|
||||||
|
# The temperatures to set
|
||||||
|
temps = {
|
||||||
|
"frost": 7.0,
|
||||||
|
"eco": 17.0,
|
||||||
|
"comfort": 19.0,
|
||||||
|
"boost": 21.0,
|
||||||
|
"eco_ac": 27.0,
|
||||||
|
"comfort_ac": 25.0,
|
||||||
|
"boost_ac": 23.0,
|
||||||
|
"frost_away": 7.1,
|
||||||
|
"eco_away": 17.1,
|
||||||
|
"comfort_away": 19.1,
|
||||||
|
"boost_away": 21.1,
|
||||||
|
"eco_ac_away": 27.1,
|
||||||
|
"comfort_ac_away": 25.1,
|
||||||
|
"boost_ac_away": 23.1,
|
||||||
|
}
|
||||||
|
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="TheOverClimateMockName",
|
||||||
|
unique_id="overClimateUniqueId",
|
||||||
|
data={
|
||||||
|
CONF_NAME: "overClimate",
|
||||||
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||||
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
|
||||||
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||||
|
CONF_CYCLE_MIN: 5,
|
||||||
|
CONF_TEMP_MIN: 15,
|
||||||
|
CONF_TEMP_MAX: 30,
|
||||||
|
CONF_USE_WINDOW_FEATURE: True,
|
||||||
|
CONF_WINDOW_SENSOR: "binary_sensor.window_sensor",
|
||||||
|
CONF_WINDOW_DELAY: 10,
|
||||||
|
CONF_USE_MOTION_FEATURE: False,
|
||||||
|
CONF_USE_POWER_FEATURE: False,
|
||||||
|
CONF_USE_AUTO_START_STOP_FEATURE: True,
|
||||||
|
CONF_USE_PRESENCE_FEATURE: True,
|
||||||
|
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
|
||||||
|
CONF_CLIMATE: "climate.mock_climate",
|
||||||
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||||
|
CONF_SECURITY_DELAY_MIN: 5,
|
||||||
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
||||||
|
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
|
||||||
|
CONF_AC_MODE: True,
|
||||||
|
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
fake_underlying_climate = MockClimate(
|
||||||
|
hass=hass,
|
||||||
|
unique_id="mock_climate",
|
||||||
|
name="mock_climate",
|
||||||
|
hvac_modes=[HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT],
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||||
|
return_value=fake_underlying_climate,
|
||||||
|
):
|
||||||
|
vtherm: ThermostatOverClimate = await create_thermostat(
|
||||||
|
hass, config_entry, "climate.overclimate"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert vtherm is not None
|
||||||
|
|
||||||
|
# Initialize all temps
|
||||||
|
await set_all_climate_preset_temp(hass, vtherm, temps, "overclimate")
|
||||||
|
|
||||||
|
# Check correct initialization of auto_start_stop attributes
|
||||||
|
assert (
|
||||||
|
vtherm._attr_extra_state_attributes["auto_start_stop_level"]
|
||||||
|
== AUTO_START_STOP_LEVEL_FAST
|
||||||
|
)
|
||||||
|
|
||||||
|
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
|
||||||
|
enable_entity = search_entity(
|
||||||
|
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
|
||||||
|
)
|
||||||
|
assert enable_entity is not None
|
||||||
|
assert enable_entity.state == STATE_ON
|
||||||
|
|
||||||
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||||
|
now: datetime = datetime.now(tz=tz)
|
||||||
|
|
||||||
|
# 1. Set mode to Heat and preset to Comfort and close the window
|
||||||
|
send_window_change_event(vtherm, False, False, now, False)
|
||||||
|
await send_presence_change_event(vtherm, True, False, now)
|
||||||
|
await send_temperature_change_event(vtherm, 18, now, True)
|
||||||
|
await vtherm.async_set_hvac_mode(HVACMode.HEAT)
|
||||||
|
await vtherm.async_set_preset_mode(PRESET_COMFORT)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert vtherm.target_temperature == 19.0
|
||||||
|
# VTherm should be heating
|
||||||
|
assert vtherm.hvac_mode == HVACMode.HEAT
|
||||||
|
# VTherm window_state should be off
|
||||||
|
assert vtherm.window_state == STATE_OFF
|
||||||
|
|
||||||
|
# 2. Open the window and wait for the delay
|
||||||
|
now = now + timedelta(minutes=2)
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||||
|
) as mock_send_event, patch(
|
||||||
|
"homeassistant.helpers.condition.state", return_value=True
|
||||||
|
):
|
||||||
|
vtherm._set_now(now)
|
||||||
|
try_function = await send_window_change_event(
|
||||||
|
vtherm, True, False, now, sleep=False
|
||||||
|
)
|
||||||
|
|
||||||
|
await try_function(None)
|
||||||
|
|
||||||
|
# Nothing should have change (window event is ignoed as we are already OFF)
|
||||||
|
assert vtherm.hvac_mode == HVACMode.OFF
|
||||||
|
assert vtherm.hvac_off_reason == HVAC_OFF_REASON_WINDOW_DETECTION
|
||||||
|
assert vtherm._saved_hvac_mode == HVACMode.HEAT
|
||||||
|
|
||||||
|
assert mock_send_event.call_count == 1
|
||||||
|
|
||||||
|
assert vtherm.window_state == STATE_ON
|
||||||
|
|
||||||
|
# 3. Turn off manually the VTherm. This should be taken into account
|
||||||
|
now = now + timedelta(minutes=1)
|
||||||
|
vtherm._set_now(now)
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||||
|
) as mock_send_event:
|
||||||
|
await vtherm.async_set_hvac_mode(HVACMode.OFF)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Should be off with reason MANUAL
|
||||||
|
assert vtherm.hvac_mode == HVACMode.OFF
|
||||||
|
assert vtherm.hvac_off_reason == HVAC_OFF_REASON_MANUAL
|
||||||
|
assert vtherm._saved_hvac_mode == HVACMode.OFF
|
||||||
|
# Window state should not change
|
||||||
|
assert vtherm.window_state == STATE_ON
|
||||||
|
|
||||||
|
assert mock_send_event.call_count == 1
|
||||||
|
mock_send_event.assert_has_calls(
|
||||||
|
[
|
||||||
|
call(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF}),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. close the window -> we should stay off reason manual
|
||||||
|
now = now + timedelta(minutes=1)
|
||||||
|
vtherm._set_now(now)
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||||
|
) as mock_send_event, patch(
|
||||||
|
"homeassistant.helpers.condition.state", return_value=True
|
||||||
|
):
|
||||||
|
try_function = await send_window_change_event(
|
||||||
|
vtherm, False, True, now, sleep=False
|
||||||
|
)
|
||||||
|
|
||||||
|
await try_function(None)
|
||||||
|
|
||||||
|
# The VTherm should turn on and off again due to auto-start-stop
|
||||||
|
assert vtherm.hvac_mode == HVACMode.OFF
|
||||||
|
assert vtherm.hvac_off_reason is HVAC_OFF_REASON_MANUAL
|
||||||
|
assert vtherm._saved_hvac_mode == HVACMode.OFF
|
||||||
|
|
||||||
|
assert vtherm.window_state == STATE_OFF
|
||||||
|
assert mock_send_event.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
|
async def test_manual_hvac_off_should_take_the_lead_over_auto_start_stop(
|
||||||
|
hass: HomeAssistant, skip_hass_states_is_state
|
||||||
|
):
|
||||||
|
"""Test than a manual hvac_off is taken into account over a auto-start/stop hvac_off"""
|
||||||
|
|
||||||
|
# The temperatures to set
|
||||||
|
temps = {
|
||||||
|
"frost": 7.0,
|
||||||
|
"eco": 17.0,
|
||||||
|
"comfort": 19.0,
|
||||||
|
"boost": 21.0,
|
||||||
|
"eco_ac": 27.0,
|
||||||
|
"comfort_ac": 25.0,
|
||||||
|
"boost_ac": 23.0,
|
||||||
|
"frost_away": 7.1,
|
||||||
|
"eco_away": 17.1,
|
||||||
|
"comfort_away": 19.1,
|
||||||
|
"boost_away": 21.1,
|
||||||
|
"eco_ac_away": 27.1,
|
||||||
|
"comfort_ac_away": 25.1,
|
||||||
|
"boost_ac_away": 23.1,
|
||||||
|
}
|
||||||
|
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
title="TheOverClimateMockName",
|
||||||
|
unique_id="overClimateUniqueId",
|
||||||
|
data={
|
||||||
|
CONF_NAME: "overClimate",
|
||||||
|
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
|
||||||
|
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
|
||||||
|
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
|
||||||
|
CONF_CYCLE_MIN: 5,
|
||||||
|
CONF_TEMP_MIN: 15,
|
||||||
|
CONF_TEMP_MAX: 30,
|
||||||
|
CONF_USE_WINDOW_FEATURE: True,
|
||||||
|
CONF_WINDOW_SENSOR: "binary_sensor.window_sensor",
|
||||||
|
CONF_WINDOW_DELAY: 10,
|
||||||
|
CONF_USE_MOTION_FEATURE: False,
|
||||||
|
CONF_USE_POWER_FEATURE: False,
|
||||||
|
CONF_USE_AUTO_START_STOP_FEATURE: True,
|
||||||
|
CONF_USE_PRESENCE_FEATURE: True,
|
||||||
|
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
|
||||||
|
CONF_CLIMATE: "climate.mock_climate",
|
||||||
|
CONF_MINIMAL_ACTIVATION_DELAY: 30,
|
||||||
|
CONF_SECURITY_DELAY_MIN: 5,
|
||||||
|
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
|
||||||
|
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
|
||||||
|
CONF_AC_MODE: True,
|
||||||
|
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
fake_underlying_climate = MockClimate(
|
||||||
|
hass=hass,
|
||||||
|
unique_id="mock_climate",
|
||||||
|
name="mock_climate",
|
||||||
|
hvac_modes=[HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT],
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
|
||||||
|
return_value=fake_underlying_climate,
|
||||||
|
):
|
||||||
|
vtherm: ThermostatOverClimate = await create_thermostat(
|
||||||
|
hass, config_entry, "climate.overclimate"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert vtherm is not None
|
||||||
|
|
||||||
|
# Initialize all temps
|
||||||
|
await set_all_climate_preset_temp(hass, vtherm, temps, "overclimate")
|
||||||
|
|
||||||
|
# Check correct initialization of auto_start_stop attributes
|
||||||
|
assert (
|
||||||
|
vtherm._attr_extra_state_attributes["auto_start_stop_level"]
|
||||||
|
== AUTO_START_STOP_LEVEL_FAST
|
||||||
|
)
|
||||||
|
|
||||||
|
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
|
||||||
|
enable_entity = search_entity(
|
||||||
|
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
|
||||||
|
)
|
||||||
|
assert enable_entity is not None
|
||||||
|
assert enable_entity.state == STATE_ON
|
||||||
|
|
||||||
|
tz = get_tz(hass) # pylint: disable=invalid-name
|
||||||
|
now: datetime = datetime.now(tz=tz)
|
||||||
|
|
||||||
|
# 1. Set mode to Heat and preset to Comfort
|
||||||
|
send_window_change_event(vtherm, False, False, now, False)
|
||||||
|
await send_presence_change_event(vtherm, True, False, now)
|
||||||
|
await send_temperature_change_event(vtherm, 18, now, True)
|
||||||
|
await vtherm.async_set_hvac_mode(HVACMode.HEAT)
|
||||||
|
await vtherm.async_set_preset_mode(PRESET_COMFORT)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert vtherm.target_temperature == 19.0
|
||||||
|
# VTherm should be heating
|
||||||
|
assert vtherm.hvac_mode == HVACMode.HEAT
|
||||||
|
|
||||||
|
# 2. Set current temperature to 21 5 min later -> should turn off VTherm
|
||||||
|
now = now + timedelta(minutes=5)
|
||||||
|
vtherm._set_now(now)
|
||||||
|
# reset accumulated error (only for testing)
|
||||||
|
vtherm._auto_start_stop_algo._accumulated_error = 0
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||||
|
) as mock_send_event:
|
||||||
|
await send_temperature_change_event(vtherm, 21, now, True)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# VTherm should no more be heating
|
||||||
|
assert vtherm.hvac_mode == HVACMode.OFF
|
||||||
|
assert vtherm.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
|
||||||
|
assert vtherm._saved_hvac_mode == HVACMode.HEAT
|
||||||
|
assert mock_send_event.call_count == 2 # turned to off
|
||||||
|
|
||||||
|
mock_send_event.assert_has_calls(
|
||||||
|
[
|
||||||
|
call(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF}),
|
||||||
|
call(
|
||||||
|
event_type=EventType.AUTO_START_STOP_EVENT,
|
||||||
|
data={
|
||||||
|
"type": "stop",
|
||||||
|
"name": "overClimate",
|
||||||
|
"cause": "Auto stop conditions reached",
|
||||||
|
"hvac_mode": HVACMode.OFF,
|
||||||
|
"saved_hvac_mode": HVACMode.HEAT,
|
||||||
|
"target_temperature": 19.0,
|
||||||
|
"current_temperature": 21.0,
|
||||||
|
"temperature_slope": 0.3,
|
||||||
|
"accumulated_error": -2,
|
||||||
|
"accumulated_error_threshold": 2,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. Turn off manually the VTherm. This should be taken into account
|
||||||
|
now = now + timedelta(minutes=1)
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||||
|
) as mock_send_event:
|
||||||
|
await vtherm.async_set_hvac_mode(HVACMode.OFF)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Should be off with reason MANUAL
|
||||||
|
assert vtherm.hvac_mode == HVACMode.OFF
|
||||||
|
assert vtherm.hvac_off_reason == HVAC_OFF_REASON_MANUAL
|
||||||
|
assert vtherm._saved_hvac_mode == HVACMode.OFF
|
||||||
|
|
||||||
|
assert mock_send_event.call_count == 1
|
||||||
|
mock_send_event.assert_has_calls(
|
||||||
|
[
|
||||||
|
call(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF}),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 4. removes the auto-start/stop detection
|
||||||
|
now = now + timedelta(minutes=5)
|
||||||
|
vtherm._set_now(now)
|
||||||
|
with patch(
|
||||||
|
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
|
||||||
|
) as mock_send_event, patch(
|
||||||
|
"homeassistant.helpers.condition.state", return_value=True
|
||||||
|
):
|
||||||
|
await send_temperature_change_event(vtherm, 15, now, True)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# VTherm should no more be heating
|
||||||
|
assert vtherm.hvac_mode == HVACMode.OFF
|
||||||
|
assert vtherm.hvac_off_reason == HVAC_OFF_REASON_MANUAL
|
||||||
|
assert vtherm._saved_hvac_mode == HVACMode.OFF
|
||||||
|
assert mock_send_event.call_count == 0 # nothing have change
|
||||||
|
|||||||
@@ -125,6 +125,39 @@ async def test_tpi_calculation(
|
|||||||
assert tpi_algo.on_time_sec == 0
|
assert tpi_algo.on_time_sec == 0
|
||||||
assert tpi_algo.off_time_sec == 300
|
assert tpi_algo.off_time_sec == 300
|
||||||
|
|
||||||
|
"""
|
||||||
|
Test the max_on_percent clamping calculations
|
||||||
|
"""
|
||||||
|
tpi_algo._max_on_percent = 0.8
|
||||||
|
|
||||||
|
# no clamping
|
||||||
|
tpi_algo.calculate(15, 14.7, 15, HVACMode.HEAT)
|
||||||
|
assert tpi_algo.on_percent == 0.09
|
||||||
|
assert tpi_algo.calculated_on_percent == 0.09
|
||||||
|
assert tpi_algo.on_time_sec == 0
|
||||||
|
assert tpi_algo.off_time_sec == 300
|
||||||
|
|
||||||
|
# no clamping (calculated_on_percent = 0.79)
|
||||||
|
tpi_algo.calculate(15, 12.5, 11, HVACMode.HEAT)
|
||||||
|
assert tpi_algo.on_percent == 0.79
|
||||||
|
assert tpi_algo.calculated_on_percent == 0.79
|
||||||
|
assert tpi_algo.on_time_sec == 237
|
||||||
|
assert tpi_algo.off_time_sec == 63
|
||||||
|
|
||||||
|
# clamping to 80% (calculated_on_percent = 1)
|
||||||
|
tpi_algo.calculate(15, 10, 7, HVACMode.HEAT)
|
||||||
|
assert tpi_algo.on_percent == 0.8 # should be clamped to 80%
|
||||||
|
assert tpi_algo.calculated_on_percent == 1 # calculated percentage should not be affected by clamping
|
||||||
|
assert tpi_algo.on_time_sec == 240 # capped at 80%
|
||||||
|
assert tpi_algo.off_time_sec == 60
|
||||||
|
|
||||||
|
# clamping to 80% (calculated_on_percent = 0.81)
|
||||||
|
tpi_algo.calculate(15, 12.5, 9, HVACMode.HEAT)
|
||||||
|
assert tpi_algo.on_percent == 0.80 # should be clamped to 80%
|
||||||
|
assert tpi_algo.calculated_on_percent == 0.81 # calculated percentage should not be affected by clamping
|
||||||
|
assert tpi_algo.on_time_sec == 240 # capped at 80%
|
||||||
|
assert tpi_algo.off_time_sec == 60
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||||
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
@pytest.mark.parametrize("expected_lingering_timers", [True])
|
||||||
|
|||||||
@@ -205,6 +205,8 @@ async def test_window_management_time_enough(
|
|||||||
assert mock_heater_off.call_count == 2
|
assert mock_heater_off.call_count == 2
|
||||||
assert mock_condition.call_count == 1
|
assert mock_condition.call_count == 1
|
||||||
assert entity.hvac_mode is HVACMode.OFF
|
assert entity.hvac_mode is HVACMode.OFF
|
||||||
|
assert entity._saved_hvac_mode is HVACMode.HEAT
|
||||||
|
assert entity.hvac_off_reason == HVAC_OFF_REASON_WINDOW_DETECTION
|
||||||
assert entity.window_state == STATE_ON
|
assert entity.window_state == STATE_ON
|
||||||
|
|
||||||
# Close the window
|
# Close the window
|
||||||
@@ -242,6 +244,9 @@ async def test_window_management_time_enough(
|
|||||||
any_order=False,
|
any_order=False,
|
||||||
)
|
)
|
||||||
assert entity.preset_mode is PRESET_BOOST
|
assert entity.preset_mode is PRESET_BOOST
|
||||||
|
assert entity.hvac_mode is HVACMode.HEAT
|
||||||
|
assert entity._saved_hvac_mode is HVACMode.HEAT # No change
|
||||||
|
assert entity.hvac_off_reason == None
|
||||||
|
|
||||||
# Clean the entity
|
# Clean the entity
|
||||||
entity.remove_thermostat()
|
entity.remove_thermostat()
|
||||||
@@ -1339,6 +1344,7 @@ async def test_window_action_fan_only(hass: HomeAssistant, skip_hass_states_is_s
|
|||||||
# The underlying should be in FAN_ONLY hvac_mode
|
# The underlying should be in FAN_ONLY hvac_mode
|
||||||
assert entity.hvac_mode is HVACMode.FAN_ONLY
|
assert entity.hvac_mode is HVACMode.FAN_ONLY
|
||||||
assert entity._saved_hvac_mode is HVACMode.HEAT
|
assert entity._saved_hvac_mode is HVACMode.HEAT
|
||||||
|
assert entity.hvac_off_reason is None # Hvac is not off
|
||||||
assert entity.preset_mode is PRESET_COMFORT
|
assert entity.preset_mode is PRESET_COMFORT
|
||||||
|
|
||||||
# 3. Close the window
|
# 3. Close the window
|
||||||
@@ -1357,7 +1363,7 @@ async def test_window_action_fan_only(hass: HomeAssistant, skip_hass_states_is_s
|
|||||||
await try_function(None)
|
await try_function(None)
|
||||||
|
|
||||||
# Wait for initial delay of heater
|
# Wait for initial delay of heater
|
||||||
await asyncio.sleep(0.3)
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert entity.window_state == STATE_OFF
|
assert entity.window_state == STATE_OFF
|
||||||
assert mock_send_event.call_count == 1
|
assert mock_send_event.call_count == 1
|
||||||
@@ -1379,6 +1385,7 @@ async def test_window_action_fan_only(hass: HomeAssistant, skip_hass_states_is_s
|
|||||||
)
|
)
|
||||||
assert entity.hvac_mode is HVACMode.HEAT
|
assert entity.hvac_mode is HVACMode.HEAT
|
||||||
assert entity.preset_mode is PRESET_COMFORT
|
assert entity.preset_mode is PRESET_COMFORT
|
||||||
|
assert entity.hvac_off_reason is None
|
||||||
|
|
||||||
# Clean the entity
|
# Clean the entity
|
||||||
entity.remove_thermostat()
|
entity.remove_thermostat()
|
||||||
|
|||||||
Reference in New Issue
Block a user