Compare commits

...

33 Commits

Author SHA1 Message Date
Jean-Marc Collin
76382ebb35 issue #325 - restore self-regulation errors after restart (#366)
* issue #325 - creates regulation_algo in post_init only

* Remove github pages deployment

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-01-26 18:38:36 +01:00
Paulo Ferreira de Castro
90f9a0e1e3 Change log level of "Window auto event is ignored" message from info to debug (#350) 2024-01-26 14:13:56 +01:00
Paulo Ferreira de Castro
ed977b53cd Add more type hints in the thermostat classes and selected files (#364) 2024-01-26 10:51:25 +01:00
Jean-Marc Collin
5d453393f8 Change incompatilibity 2024-01-24 07:14:06 +00:00
Frederic Seiler
d2f2ab7804 Typo fix (#362) 2024-01-24 07:37:02 +01:00
Jean-Marc Collin
b0b6d0478d Incompatibility with Sonoff TRVZB 2024-01-24 06:32:26 +00:00
Jean-Marc Collin
f8a2c9baa9 FIX default value for regulation valve 2024-01-21 18:43:05 +00:00
Jean-Marc Collin
8cbd81012c Issue 338 limit regulation over valve to avoid drowning battery of the TRV (#356)
* With testus ok

* Clean TPI algo

* Commenet failed testu

* Documentation

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-01-21 19:36:44 +01:00
Jean-Marc Collin
26844593b1 Add step temperature - Issue #311 (#355)
* Add step temperature in config

* All testus ok

* Keep the step of the VTherm and not the step of the underlying

* Release 5.4.0

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-01-21 12:36:14 +01:00
Jean-Marc Collin
c12a91a5ff Feature 234 add central boiler helper (#352)
* Creation of the central boiler config + binary_sensor entity

* Fonctional before testu. Miss the service call

* Full featured but without testu

* Documentation and release.

* Add events in README

* FIX #341 - when window state change, open_valve_percent should be resend

* Issue #343 - disable safety mode for outdoor thermometer

* Issue #255 - Specify window action on window open detection

* Add en and string translation

* central boiler - add entites to fine tune the boiler start

* With testu ok

* Add testus for valve and climate

* Add testus in pipelines

* With pip 3

* With more pytest options

* Ass coverage tests

* Add coverage report in github

* Release 5.3.0

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-01-21 00:31:16 +01:00
John Kozyrakis
3da271b671 Set the last regulation timestamp only when regulation is sent to thermostats (#351)
Fixes a small issue where `_last_regulation_change` is being set to `now` even though the new temperature is not sent to the thermostats (because `abs(dtemp) < self._auto_regulation_dtemp:`)
2024-01-20 06:46:23 +01:00
Jean-Marc Collin
e8bb465b43 Try to fix issue #334 - loop when underlying is late to update 2024-01-13 11:30:11 +00:00
Jean-Marc Collin
d7ec6770c4 Update version manifest.json 2024-01-12 12:20:35 +01:00
Jean-Marc Collin
51428aa875 Issue #324 - (re) 2024-01-09 20:25:10 +00:00
Jean-Marc Collin
6ea6fe8542 Issue #324 - don't use window auto detection is sensor is given 2024-01-09 20:24:45 +00:00
misa1515
a18d10fa3f Update sk.json (#322)
* Update sk.json

* Update sk.json
2024-01-09 08:14:02 +01:00
Jean-Marc Collin
7d4ee40b4d Update TOC 2024-01-06 09:41:02 +00:00
Jean-Marc Collin
1aaf9c8c8e Add troubleshoot "heaters hets when target is overseeded" 2024-01-06 09:38:40 +00:00
Jean-Marc Collin
ae93a8b97c Issue template improvement. 2024-01-05 17:28:57 +00:00
Jean-Marc Collin
cbe98ae20c Issue #313 - improve description with central configuration 2024-01-05 07:56:44 +00:00
Jean-Marc Collin
bfcc854c3e Issue #314 - rename central_mode 2024-01-05 06:47:48 +00:00
Jean-Marc Collin
683aa050f3 Issue #314 - part 1 (update documentation) 2024-01-05 06:37:25 +00:00
Jean-Marc Collin
7476e7fa64 Feature 158 central mode (#309)
* Normal algo working and testu ok

* Fix interaction with window

* FIX complex scenario

* pylint warning

* Release

* Issue #306

* Issue #306

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-01-03 17:52:34 +01:00
Jean-Marc Collin
c222feda1a Issue #295 - No floating point value for target temp 2024-01-01 16:55:41 +00:00
Jean-Marc Collin
d05df021ab Beer from Lajull 2023-12-24 09:07:59 +00:00
Jean-Marc Collin
27a267139f FIX #159 - Doesn't send target temp if VTherm is off 2023-12-20 19:06:34 +00:00
Jean-Marc Collin
707f40d406 FIX issue #284 - preset not saved 2023-12-20 18:54:35 +00:00
Jean-Marc Collin
a01f5770d9 FIX issue #272 and #24ç - min and max values depending of the underlying 2023-12-19 19:39:33 +00:00
Jean-Marc Collin
04d0b28f1d Issue #280 - enable to use central config for window configuration 2023-12-18 21:39:58 +00:00
Jean-Marc Collin
30c3418f1b Issue #281 - cannot use central config at first integration installation 2023-12-18 20:54:39 +00:00
Jean-Marc Collin
efb8ce257d Beers from @Mexx62, @Someone 2023-12-18 20:16:37 +00:00
Jean-Marc Collin
8f934a3298 Feature 239 creates central config panel (#276)
* Add central config into ConfigFlow

* Test manual of confif_flow ok

* Ignore central confic in instanciate entities

* Init data in base_thermostat ok

* With central configuration testu ok

* All testu ok

* With fixture for init_vtherm_api and init_central_config

* Add reload VTherms when central configuration is changed

* Update strings.json and replace security by safety in README.

* UPdate README with release 5.0

* FIX missing Presets central configuration initialisation

* FIX frost_away_temp translation missing

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2023-12-17 16:16:44 +01:00
Jean-Marc Collin
5a468fe2b9 Add beers and presence sensor for multiple persons 2023-12-13 11:22:26 +00:00
59 changed files with 8459 additions and 1565 deletions

View File

@@ -1,5 +1,8 @@
default_config: default_config:
# ffmeg
ffmpeg:
logger: logger:
default: info default: info
logs: logs:
@@ -25,6 +28,8 @@ versatile_thermostat:
max_alpha: 0.6 max_alpha: 0.6
halflife_sec: 301 halflife_sec: 301
precision: 3 precision: 3
safety_mode:
check_outdoor_sensor: false
input_number: input_number:
fake_temperature_sensor1: fake_temperature_sensor1:
@@ -59,10 +64,17 @@ input_number:
unit_of_measurement: kW unit_of_measurement: kW
fake_valve1: fake_valve1:
name: The valve 1 name: The valve 1
min: 0 min: 10
max: 100 max: 90
icon: mdi:pipe-valve icon: mdi:pipe-valve
unit_of_measurement: percentage unit_of_measurement: percentage
fake_boiler_temperature:
name: Central thermostat temp
min: 0
max: 30
icon: mdi:thermostat
unit_of_measurement: °C
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.
@@ -158,6 +170,7 @@ climate:
target_sensor: input_number.fake_temperature_sensor1 target_sensor: input_number.fake_temperature_sensor1
recorder: recorder:
commit_interval: 1
include: include:
domains: domains:
- input_boolean - input_boolean
@@ -165,6 +178,7 @@ recorder:
- switch - switch
- climate - climate
- sensor - sensor
- binary_sensor
template: template:
- binary_sensor: - binary_sensor:

View File

@@ -30,12 +30,9 @@
"waderyan.gitblame", "waderyan.gitblame",
"keesschollaart.vscode-home-assistant", "keesschollaart.vscode-home-assistant",
"vscode.markdown-math", "vscode.markdown-math",
"yzhang.markdown-all-in-one" "yzhang.markdown-all-in-one",
"github.vscode-github-actions"
], ],
// "mounts": [
// "source=${localWorkspaceFolder}/.devcontainer/configuration.yaml,target=${localWorkspaceFolder}/config/www/community/,type=bind,consistency=cached",
// "source=${localWorkspaceFolder}/custom_components,target=/home/vscode/core/config/custom_components,type=bind,consistency=cached"
// ],
"settings": { "settings": {
"files.eol": "\n", "files.eol": "\n",
"editor.tabSize": 4, "editor.tabSize": 4,

View File

@@ -4,6 +4,8 @@ about: Create a report to help us improve
--- ---
<!-- 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.
If you have a simple question or you are not sure this is an issue, don't open an issue but open a new discussion [here](https://github.com/jmcollin78/versatile_thermostat/discussions). If you have a simple question or you are not sure this is an issue, don't open an issue but open a new discussion [here](https://github.com/jmcollin78/versatile_thermostat/discussions).
@@ -13,7 +15,7 @@ Check also in the [Troubleshooting](#troubleshooting) paragrah of the README if
Issues not containing the minimum requirements will be closed: Issues not containing the minimum requirements will be closed:
- Issues without a description (using the header is not good enough) will be closed. - Issues without a description (using the header is not good enough) will be closed.
- Issues without configuration will be closed - Issues that don't follow this template could be closed
--> -->

49
.github/workflows/testus.yaml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: Run Tests
on:
push:
branches:
- main
pull_request:
jobs:
testu:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.11
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip3 install -r requirements_test.txt
- name: Run Tests
run: |
pytest \
-qq \
--timeout=9 \
--durations=10 \
-n auto \
-o console_output_style=count \
-p no:sugar \
tests
- name: Coverage
run: |
coverage run -m pytest tests/
coverage report
- name: Generate HTML Coverage Report
run: coverage html
# - name: Deploy to GitHub Pages
# uses: peaceiris/actions-gh-pages@v3
# with:
# github_token: ${{ secrets.GITHUB_TOKEN }}
# publish_dir: ./htmlcov

3
.gitignore vendored
View File

@@ -110,3 +110,6 @@ __pycache__
config/** config/**
custom_components/hacs custom_components/hacs
custom_components/localtuya custom_components/localtuya
.coverage
htmlcov

View File

@@ -14,7 +14,8 @@
"python.testing.pytestEnabled": true, "python.testing.pytestEnabled": true,
"python.analysis.extraPaths": [ "python.analysis.extraPaths": [
// "/home/vscode/core", // "/home/vscode/core",
"/workspaces/versatile_thermostat/custom_components/versatile_thermostat" "/workspaces/versatile_thermostat/custom_components/versatile_thermostat",
"/home/vscode/.local/lib/python3.11/site-packages/homeassistant"
], ],
"python.formatting.provider": "none" "python.formatting.provider": "none"
} }

6
.vscode/tasks.json vendored
View File

@@ -13,6 +13,12 @@
"command": "./container restart", "command": "./container restart",
"problemMatcher": [] "problemMatcher": []
}, },
{
"label": "Start coverage",
"type": "shell",
"command": "./container coverage",
"problemMatcher": []
},
{ {
"label": "Home Assistant translations update", "label": "Home Assistant translations update",
"type": "shell", "type": "shell",

View File

@@ -8,7 +8,7 @@
> ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) Cette intégration de thermostat vise à simplifier considérablement vos automatisations autour de la gestion du chauffage. Parce que tous les événements autour du chauffage classiques sont gérés nativement par le thermostat (personne à la maison ?, activité détectée dans une pièce ?, fenêtre ouverte ?, délestage de courant ?), vous n'avez pas à vous encombrer de scripts et d'automatismes compliqués pour gérer vos climats. ;-). > ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) Cette intégration de thermostat vise à simplifier considérablement vos automatisations autour de la gestion du chauffage. Parce que tous les événements autour du chauffage classiques sont gérés nativement par le thermostat (personne à la maison ?, activité détectée dans une pièce ?, fenêtre ouverte ?, délestage de courant ?), vous n'avez pas à vous encombrer de scripts et d'automatismes compliqués pour gérer vos climats. ;-).
- [Changements majeurs dans la version 4.0.0](#changements-majeurs-dans-la-version-400) - [Changements majeurs dans la version 5.0](#changements-majeurs-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)
@@ -17,6 +17,7 @@
- [HACS installation (recommendé)](#hacs-installation-recommendé) - [HACS installation (recommendé)](#hacs-installation-recommendé)
- [Installation manuelle](#installation-manuelle) - [Installation manuelle](#installation-manuelle)
- [Configuration](#configuration) - [Configuration](#configuration)
- [Création d'un nouveau Versatile Thermostat](#création-dun-nouveau-versatile-thermostat)
- [Choix des attributs de base](#choix-des-attributs-de-base) - [Choix des attributs de base](#choix-des-attributs-de-base)
- [Sélectionnez des entités pilotées](#sélectionnez-des-entités-pilotées) - [Sélectionnez des entités pilotées](#sélectionnez-des-entités-pilotées)
- [Pour un thermostat de type ```thermostat_over_switch```](#pour-un-thermostat-de-type-thermostat_over_switch) - [Pour un thermostat de type ```thermostat_over_switch```](#pour-un-thermostat-de-type-thermostat_over_switch)
@@ -34,6 +35,12 @@
- [Configurer la gestion de la puissance](#configurer-la-gestion-de-la-puissance) - [Configurer la gestion de la puissance](#configurer-la-gestion-de-la-puissance)
- [Configurer la présence ou l'occupation](#configurer-la-présence-ou-loccupation) - [Configurer la présence ou l'occupation](#configurer-la-présence-ou-loccupation)
- [Configuration avancée](#configuration-avancée) - [Configuration avancée](#configuration-avancée)
- [Le contrôle centralisé](#le-contrôle-centralisé)
- [Le contrôle d'une chaudière centrale](#le-contrôle-dune-chaudière-centrale)
- [Configuration](#configuration-1)
- [Comment trouver le bon service ?](#comment-trouver-le-bon-service-)
- [Les évènements](#les-évènements)
- [Avertissement](#avertissement)
- [Synthèse des paramètres](#synthèse-des-paramètres) - [Synthèse des paramètres](#synthèse-des-paramètres)
- [Exemples de réglage](#exemples-de-réglage) - [Exemples de réglage](#exemples-de-réglage)
- [Chauffage électrique](#chauffage-électrique) - [Chauffage électrique](#chauffage-électrique)
@@ -63,23 +70,32 @@
- [Utilisation d'un Heatzy](#utilisation-dun-heatzy) - [Utilisation d'un Heatzy](#utilisation-dun-heatzy)
- [Utilisation d'un radiateur avec un fil pilote](#utilisation-dun-radiateur-avec-un-fil-pilote) - [Utilisation d'un radiateur avec un fil pilote](#utilisation-dun-radiateur-avec-un-fil-pilote)
- [Seul le premier radiateur chauffe](#seul-le-premier-radiateur-chauffe) - [Seul le premier radiateur chauffe](#seul-le-premier-radiateur-chauffe)
- [Le radiateur chauffe alors que la température de consigne est dépassée ou ne chauffe pas alors que la température de la pièce est bien en-dessous de la consigne](#le-radiateur-chauffe-alors-que-la-température-de-consigne-est-dépassée-ou-ne-chauffe-pas-alors-que-la-température-de-la-pièce-est-bien-en-dessous-de-la-consigne)
- [Type `over_switch` ou `over_valve`](#type-over_switch-ou-over_valve)
- [Type `over_climate`](#type-over_climate)
- [Régler les paramètres de détection d'ouverture de fenêtre en mode auto](#régler-les-paramètres-de-détection-douverture-de-fenêtre-en-mode-auto) - [Régler les paramètres de détection d'ouverture de fenêtre en mode auto](#régler-les-paramètres-de-détection-douverture-de-fenêtre-en-mode-auto)
- [Pourquoi mon Versatile Thermostat se met en Securite ?](#pourquoi-mon-versatile-thermostat-se-met-en-securite-) - [Pourquoi mon Versatile Thermostat se met en Securite ?](#pourquoi-mon-versatile-thermostat-se-met-en-securite-)
- [Comment détecter le mode sécurité ?](#comment-détecter-le-mode-sécurité-) - [Comment détecter le mode sécurité ?](#comment-détecter-le-mode-sécurité-)
- [Comment être averti lorsque cela se produit ?](#comment-être-averti-lorsque-cela-se-produit-) - [Comment être averti lorsque cela se produit ?](#comment-être-averti-lorsque-cela-se-produit-)
- [Comment réparer ?](#comment-réparer-) - [Comment réparer ?](#comment-réparer-)
- [Utilisation d'un groupe de personnes comme capteur de présence](#utilisation-dun-groupe-de-personnes-comme-capteur-de-présence)
Ce composant personnalisé pour Home Assistant est une mise à niveau et est une réécriture complète du composant "Awesome thermostat" (voir [Github](https://github.com/dadge/awesome_thermostat)) avec l'ajout de fonctionnalités. Ce composant personnalisé pour Home Assistant est une mise à niveau et est une réécriture complète du composant "Awesome thermostat" (voir [Github](https://github.com/dadge/awesome_thermostat)) avec l'ajout de fonctionnalités.
> ![Nouveau](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/new-icon.png?raw=true) _*Nouveautés*_ > ![Nouveau](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/new-icon.png?raw=true) _*Nouveautés*_
> * **Release 5.4** : Ajout du pas de température [#311](https://github.com/jmcollin78/versatile_thermostat/issues/311). 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)
> * **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>
<summary>Autres versions</summary>
> * **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).
> * **Release 4.0** : Ajout de la prise en charge de la **Versatile Thermostat UI Card**. Voir [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card). Ajout d'un mode de régulation **Slow** pour les appareils de chauffage à latence lente [#168](https://github.com/jmcollin78/versatile_thermostat/issues/168). Changement de la façon dont **la puissance est calculée** dans le cas de VTherm avec des équipements multi-sous-jacents [#146](https://github.com/jmcollin78/versatile_thermostat/issues/146). Ajout de la prise en charge de AC et Heat pour VTherm via un interrupteur également [#144](https://github.com/jmcollin78/versatile_thermostat/pull/144) > * **Release 4.0** : Ajout de la prise en charge de la **Versatile Thermostat UI Card**. Voir [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card). Ajout d'un mode de régulation **Slow** pour les appareils de chauffage à latence lente [#168](https://github.com/jmcollin78/versatile_thermostat/issues/168). Changement de la façon dont **la puissance est calculée** dans le cas de VTherm avec des équipements multi-sous-jacents [#146](https://github.com/jmcollin78/versatile_thermostat/issues/146). Ajout de la prise en charge de AC et Heat pour VTherm via un interrupteur également [#144](https://github.com/jmcollin78/versatile_thermostat/pull/144)
<details>
<summary>Autres versions</summary>
> * **Release 3.8**: Ajout d'une **fonction d'auto-régulation** pour les thermostats `over climate` dont la régulation est faite par le climate sous-jacent. Cf. [L'auto-régulation](#lauto-régulation) et [#129](https://github.com/jmcollin78/versatile_thermostat/issues/129). Ajout de la **possibilité d'inverser la commande** pour un thermostat `over switch` pour adresser les installations avec fil pilote et diode [#124](https://github.com/jmcollin78/versatile_thermostat/issues/124). > * **Release 3.8**: Ajout d'une **fonction d'auto-régulation** pour les thermostats `over climate` dont la régulation est faite par le climate sous-jacent. Cf. [L'auto-régulation](#lauto-régulation) et [#129](https://github.com/jmcollin78/versatile_thermostat/issues/129). Ajout de la **possibilité d'inverser la commande** pour un thermostat `over switch` pour adresser les installations avec fil pilote et diode [#124](https://github.com/jmcollin78/versatile_thermostat/issues/124).
> * **Release 3.7**: Ajout du type de **Versatile Thermostat `over valve`** pour piloter une vanne TRV directement ou tout autre équipement type gradateur pour le chauffage. La régulation se fait alors directement en agissant sur le pourcentage d'ouverture de l'entité sous-jacente : 0 la vanne est coupée, 100 : la vanne est ouverte à fond. Cf. [#131](https://github.com/jmcollin78/versatile_thermostat/issues/131). Ajout d'une fonction permettant le bypass de la détection d'ouverture [#138](https://github.com/jmcollin78/versatile_thermostat/issues/138). Ajout de la langue Slovaque > * **Release 3.7**: Ajout du type de **Versatile Thermostat `over valve`** pour piloter une vanne TRV directement ou tout autre équipement type gradateur pour le chauffage. La régulation se fait alors directement en agissant sur le pourcentage d'ouverture de l'entité sous-jacente : 0 la vanne est coupée, 100 : la vanne est ouverte à fond. Cf. [#131](https://github.com/jmcollin78/versatile_thermostat/issues/131). Ajout d'une fonction permettant le bypass de la détection d'ouverture [#138](https://github.com/jmcollin78/versatile_thermostat/issues/138). Ajout de la langue Slovaque
> * **Release 3.6**: Ajout du paramètre `motion_off_delay` pour améliorer la gestion de des mouvements [#116](https://github.com/jmcollin78/versatile_thermostat/issues/116), [#128](https://github.com/jmcollin78/versatile_thermostat/issues/128). Ajout du mode AC (air conditionné) pour un VTherm over switch. Préparation du projet Github pour faciliter les contributions [#127](https://github.com/jmcollin78/versatile_thermostat/issues/127) > * **Release 3.6**: Ajout du paramètre `motion_off_delay` pour améliorer la gestion de des mouvements [#116](https://github.com/jmcollin78/versatile_thermostat/issues/116), [#128](https://github.com/jmcollin78/versatile_thermostat/issues/128). Ajout du mode AC (air conditionné) pour un VTherm over switch. Préparation du projet Github pour faciliter les contributions [#127](https://github.com/jmcollin78/versatile_thermostat/issues/127)
@@ -94,12 +110,25 @@ 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>
# Changements majeurs dans la version 4.0.0 # Changements majeurs dans la version 5.0
1. La puissance de l'appareil doit maintenant être la puissance totale de tous les appareils controlée par le VTherm. Cela permet d'avoir des équipements hétérogènes de puissance différente. Dans le cas de plusieurs appareils contrôlés par un seul VTherm, vous devrez éditer et changer la valeur `device_power`. Vous devez configurer la puissance totale de tous les appareils. ![Nouveau](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/new-icon.png?raw=true)
2. Le seuil de détection automatique des ouvertures doit être spécifié en °/heure et pas plus en °/min. Pour conserver les mêmes paramètres il faut multiplier la valeur configurée par 60.
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",
2. Saisir les attributs de cette configuration centrale
Pour l'utiliser ensuite dans les autres VTherms, vous devez les reconfigurer et à chaque fois que c'est possible cocher la case "Utiliser la configuration centrale". Cette case à cocher apparait dans tous les groupes d'attributs qui peuvent avoir recours à la configuration centrale : attributs principaux, TPI, ouvertures, mouvement, puissance, présence et paramètres avancés.
Les attributs configurable dans la configuration centrale est listée ici : [Synthèse des paramètres](#synthèse-des-paramètres).
Lors d'un changement sur la configuration centrale, tous les VTherms seront rechargés pour tenir compte de ces changements.
En conséquence toute la phase de paramètrage d'un VTherm a été profondemment modifiée pour pouvoir utiliser la configuration centrale ou surcharger les valeurs de la configuration centrale par des valeurs propre au VTherm en cours de configuration.
**Note :** les copies d'écran de la configuration d'un VTherm n'ont pas été mises à jour.
# Merci pour la bière [buymecoffee](https://www.buymeacoffee.com/jmcollin78) # Merci pour la bière [buymecoffee](https://www.buymeacoffee.com/jmcollin78)
Un grand merci à @salabur, @pvince83, @bergoglio, @EPicLURcher, @ecolorado66, @Kriss1670, @maia, @f.maymil, @moutte69, @Jerome, @Gunnar M, @Greg.o, @John Burgess, @abyssmal pour les bières. Ca fait très plaisir et ça m'encourage à continuer ! Un grand merci à @salabur, @pvince83, @bergoglio, @EPicLURcher, @ecolorado66, @Kriss1670, @maia, @f.maymil, @moutte69, @Jerome, @Gunnar M, @Greg.o, @John Burgess, @abyssmal, @capinfo26, @Helge, @MattG @Mexx62, @Someone, @Lajull, @giopeco pour les bières. Ca fait très plaisir et ça m'encourage à continuer !
# Quand l'utiliser et ne pas l'utiliser # Quand l'utiliser et ne pas l'utiliser
@@ -122,6 +151,7 @@ Certains thermostat de type TRV sont réputés incompatibles avec le Versatile T
2. Les thermostats « Homematic » (et éventuellement Homematic IP) sont connus pour rencontrer des problèmes avec le Versatile Thermostat en raison des limitations du protocole RF sous-jacent. Ce problème se produit particulièrement lorsque vous essayez de contrôler plusieurs thermostats Homematic à la fois dans une seule instance de VTherm. Afin de réduire la charge du cycle de service, vous pouvez par ex. regroupez les thermostats avec des procédures spécifiques à Homematic (par exemple en utilisant un thermostat mural) et laissez Versatile Thermostat contrôler uniquement le thermostat mural directement. Une autre option consiste à contrôler un seul thermostat et à propager les changements de mode CVC et de température par un automatisme, 2. Les thermostats « Homematic » (et éventuellement Homematic IP) sont connus pour rencontrer des problèmes avec le Versatile Thermostat en raison des limitations du protocole RF sous-jacent. Ce problème se produit particulièrement lorsque vous essayez de contrôler plusieurs thermostats Homematic à la fois dans une seule instance de VTherm. Afin de réduire la charge du cycle de service, vous pouvez par ex. regroupez les thermostats avec des procédures spécifiques à Homematic (par exemple en utilisant un thermostat mural) et laissez Versatile Thermostat contrôler uniquement le thermostat mural directement. Une autre option consiste à contrôler un seul thermostat et à propager les changements de mode CVC et de température par un automatisme,
3. les thermostats de type Heatzy qui ne supportent pas les commandes de type set_temperature 3. les thermostats de type Heatzy qui ne supportent pas les commandes de type set_temperature
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 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.
# Pourquoi une nouvelle implémentation du thermostat ? # Pourquoi une nouvelle implémentation du thermostat ?
@@ -135,7 +165,9 @@ Ce composant nommé __Versatile thermostat__ gère les cas d'utilisation suivant
- Ajouter une **gestion de délestage** ou une régulation pour ne pas dépasser une puissance totale définie. Lorsque la puissance maximale est dépassée, un préréglage caché de « puissance » est défini sur l'entité climatique. Lorsque la puissance passe en dessous du maximum, le préréglage précédent est restauré. - Ajouter une **gestion de délestage** ou une régulation pour ne pas dépasser une puissance totale définie. Lorsque la puissance maximale est dépassée, un préréglage caché de « puissance » est défini sur l'entité climatique. Lorsque la puissance passe en dessous du maximum, le préréglage précédent est restauré.
- La **gestion de la présence à domicile**. Cette fonctionnalité vous permet de modifier dynamiquement la température du préréglage en tenant compte d'un capteur de présence de votre maison. - La **gestion de la présence à domicile**. Cette fonctionnalité vous permet de modifier dynamiquement la température du préréglage en tenant compte d'un capteur de présence de votre maison.
- Des **services pour interagir avec le thermostat** à partir d'autres intégrations : vous pouvez forcer la présence / la non-présence à l'aide d'un service, et vous pouvez modifier dynamiquement la température des préréglages et changer les paramètres de sécurité. - Des **services pour interagir avec le thermostat** à partir d'autres intégrations : vous pouvez forcer la présence / la non-présence à l'aide d'un service, et vous pouvez modifier dynamiquement la température des préréglages et changer les paramètres de sécurité.
- Ajouter des capteurs pour voir les états internes du thermostat. - Ajouter des capteurs pour voir les états internes du thermostat,
- Contrôle centralisé de tous les Versatile Thermostat pour les stopper tous, les passer tous en hors-gel, les forcer en mode Chauffage (l'hiver), les forcer en mode Climatisation (l'été).
- Contrôle d'une chaudière centrale et des VTherm qui doivent contrôler cette chaudière.
# Comment installer cet incroyable Thermostat Versatile ? # Comment installer cet incroyable Thermostat Versatile ?
@@ -160,8 +192,17 @@ Ce composant nommé __Versatile thermostat__ gère les cas d'utilisation suivant
# Configuration # Configuration
Note: aucune configuration dans configuration.yaml n'est nécessaire car toute la configuration est effectuée via l'interface graphique standard lors de l'ajout de l'intégration. -- VTherm = Versatile Thermostat dans la suite de ce document --
> ![Astuce](/images/tips.png?raw=true) _*Notes*_
>
> Trois façons de configurer les VTherms sont disponibles :
> 1. Chaque Versatile Thermostat est entièrement configurée de manière indépendante. Choisissez cette option si vous ne souhaitez avoir aucune configuration ou gestion centrale.
> 2. Certains aspects sont configurés de manière centralisée. Cela permet par ex. définir la température min/max, la détection de fenêtre ouverte,… au niveau d'une instance centrale et unique. Pour chaque VTherm que vous configurez, vous pouvez alors choisir d'utiliser la configuration centrale ou de la remplacer par des paramètres personnalisés.
> 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`.
## 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
![image](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/add-an-integration.png?raw=true) ![image](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/add-an-integration.png?raw=true)
@@ -172,7 +213,9 @@ Suivez ensuite les étapes de configuration comme suit :
## Choix des attributs de base ## Choix des attributs de base
![image](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/config-main.png?raw=true) ![image](/images/config-main0.png?raw=true)
![image](/images/config-main.png?raw=true)
Donnez les principaux attributs obligatoires : Donnez les principaux attributs obligatoires :
1. un nom (sera le nom de l'intégration et aussi le nom de l'entité climate) 1. un nom (sera le nom de l'intégration et aussi le nom de l'entité climate)
@@ -182,7 +225,8 @@ Donnez les principaux attributs obligatoires :
6. une durée de cycle en minutes. A chaque cycle, le radiateur s'allumera puis s'éteindra pendant une durée calculée afin d'atteindre la température ciblée (voir [preset](#configure-the-preset-temperature) ci-dessous). En mode ```over_climate```, le cycle ne sert qu'à faire des controles de base mais ne régule pas directement la température. C'est le ```climate``` sous-jacent qui le fait, 6. une durée de cycle en minutes. A chaque cycle, le radiateur s'allumera puis s'éteindra pendant une durée calculée afin d'atteindre la température ciblée (voir [preset](#configure-the-preset-temperature) ci-dessous). En mode ```over_climate```, le cycle ne sert qu'à faire des controles de base mais ne régule pas directement la température. C'est le ```climate``` sous-jacent qui le fait,
7. les températures minimales et maximales du thermostat, 7. les températures minimales et maximales du thermostat,
8. une puissance de l'équipement ce qui va activer les capteurs de puissance et énergie consommée par l'appareil, 8. une puissance de l'équipement ce qui va activer les capteurs de puissance et énergie consommée par l'appareil,
9. la liste des fonctionnalités qui seront utilisées pour ce thermostat. En fonction de vos choix, les écrans de configuration suivants s'afficheront ou pas. 9. la possibilité de controler le thermostat de façon centralisée. Cf [controle centralisé](#le-contrôle-centralisé),
10. la liste des fonctionnalités qui seront utilisées pour ce thermostat. En fonction de vos choix, les écrans de configuration suivants s'afficheront ou pas.
> ![Astuce](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_ > ![Astuce](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_
> 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**,
@@ -444,7 +488,7 @@ Si vous avez choisi la fonctionnalité ```Avec détection de la puissance```, cl
![image](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/config-power.png?raw=true) ![image](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/config-power.png?raw=true)
Cette fonction vous permet de réguler la consommation électrique de vos radiateurs. Connue sous le nom de délestage, cette fonction vous permet de limiter la consommation électrique de votre appareil de chauffage si des conditions de surpuissance sont détectées. Donnez un **capteur à la consommation électrique actuelle de votre maison**, un **capteur à la puissance max** qu'il ne faut pas dépasser, la **consommation électrique de votre chauffage** (en étape 1 de la configuration) et l'algorithme ne démarrera pas un radiateur si la puissance maximale sera dépassée après le démarrage du radiateur. Cette fonction vous permet de réguler la consommation électrique de vos radiateurs. Connue sous le nom de délestage, cette fonction vous permet de limiter la consommation électrique de votre appareil de chauffage si des conditions de surpuissance sont détectées. Donnez un **capteur à la consommation électrique actuelle de votre maison**, un **capteur à la puissance max** qu'il ne faut pas dépasser, la **consommation électrique totale des équipements du VTherm** (en étape 1 de la configuration) et l'algorithme ne démarrera pas un radiateur si la puissance maximale sera dépassée après le démarrage du radiateur.
Notez que toutes les valeurs de puissance doivent avoir les mêmes unités (kW ou W par exemple). Notez que toutes les valeurs de puissance doivent avoir les mêmes unités (kW ou W par exemple).
Cela vous permet de modifier la puissance maximale au fil du temps à l'aide d'un planificateur ou de ce que vous voulez. Cela vous permet de modifier la puissance maximale au fil du temps à l'aide d'un planificateur ou de ce que vous voulez.
@@ -470,6 +514,8 @@ Pour cela, vous devez configurer :
Si le mode AC est utilisé, vous pourrez aussi configurer les températures lorsque l'équipement en mode climatisation. Si le mode AC est utilisé, vous pourrez aussi configurer les températures lorsque l'équipement en mode climatisation.
ATTENTION : les groupes de personnes ne fonctionnent pas en tant que capteur de présence. Ils ne sont pas reconnus comme un capteur de présence. Vous devez utiliser, un template comme décrit ici [Utilisation d'un groupe de personnes comme capteur de présence](#utilisation-dun-groupe-de-personnes-comme-capteur-de-présence).
> ![Astuce](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_ > ![Astuce](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_
> 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.
@@ -489,80 +535,212 @@ 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.
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:
...
safety_mode:
check_outdoor_sensor: false
```
Par défaut, le thermomètre extérieur peut déclencher une mise en sécurité si il n'envoit plus de valeur.
Voir [exemple de réglages](#examples-tuning) pour avoir des exemples de réglage communs Voir [exemple de réglages](#examples-tuning) pour avoir des exemples de réglage communs
> ![Astuce](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_ > ![Astuce](/images/tips.png?raw=true) _*Notes*_
> 1. Lorsque le capteur de température viendra à la vie et renverra les températures, le préréglage sera restauré à sa valeur précédente, > 1. Lorsque le capteur de température viendra à la vie et renverra les températures, le préréglage sera restauré à sa valeur précédente,
> 2. Attention, deux températures sont nécessaires : la température interne et la température externe et chacune doit donner la température, sinon le thermostat sera en préréglage "security", > 2. Attention, deux températures sont nécessaires : la température interne et la température externe et chacune doit donner la température, sinon le thermostat sera en préréglage "security",
> 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.
## Le contrôle centralisé
Depuis la release 5.2, si vous avez défini une configuration centralisée, vous avez une nouvelle entité nommée `select.central_mode` qui permet de piloter tous les VTherms avec une seule action. Pour qu'un VTherm soit contrôlable de façon centralisée, il faut que son attribut de configuration nommé `use_central_mode` soit vrai.
Cette entité se présente sous la forme d'une liste de choix qui contient les choix suivants :
1. `Auto` : le mode 'normal' dans lequel chaque VTherm se comporte comme dans les versions précédentes,
2. `Stooped` : tous les VTherms sont mis à l'arrêt (`hvac_off`),
3. `Heat only` : tous les VTherms sont mis en mode chauffage lorsque ce mode est supporté par le VTherm, sinon il est stoppé,
3. `Cool only` : tous les VTherms sont mis en mode climatisation lorsque ce mode est supporté par le VTherm, sinon il est stoppé,
4. `Frost protection` : tous les VTherms sont mis en preset hors-gel lorsque ce preset est supporté par le VTherm, sinon il est stoppé.
Il est donc possible de contrôler tous les VTherms (que ceux que l'on désigne explicitement) avec un seul contrôle.
Exemple de rendu :
![central_mode](/images/central_mode.png?raw=true)
## Le contrôle d'une chaudière centrale
Depuis la release 5.3, vous avez la possibilité de contrôler une chaudière centralisée. A partir du moment où il est possible de déclencher ou stopper cette chaudière depuis Home Assistant, alors Versatile Thermostat va pouvoir la commander directement.
Le principe mis en place est globalement le suivant :
1. une nouvelle entité de type `binary_sensor` et nommée par défaut `binary_sensor.central_boiler` est ajoutée,
2. dans la configuration des VTherms vous indiquez si le VTherm doit contrôler la chaudière. En effet, dans une installation hétérogène, certains VTherm doivent commander la chaudière et d'autres non. Vous devez donc indiquer dans chaque configuration de VTherm si il contrôle la chaudière ou pas,
3. le `binary_sensor.central_boiler` écoute les changements d'états des équipements des VTherm marqués comme contrôlant la chaudière,
4. dès que le nombre d'équipements pilotés par le VTherm demandant du chauffage (ie son `hvac_action` passe à `Heating`) dépasse un seuil paramétrable, alors le `binary_sensor.central_boiler` passe à `on` et **si un service d'activation a été configuré, alors ce service est appelé**,
5. si le nombre d'équipements nécessitant du chauffage repasse en dessous du seuil, alors le `binary_sensor.central_boiler` passe à `off` et si **un service de désactivation a été configuré, alors ce service est appelé**,
6. vous avez accès à deux entités :
- une de type `number` nommé par défaut `number.boiler_activation_threshold`, donne le seuil de déclenchement. Ce seuil est en nombre d'équipements (radiateurs) qui demande du chauffage.
- une de type `sensor` nommé par défaut `sensor.nb_device_active_for_boiler`, donne le nombre d'équipements qui demande du chauffage. Par exemple, un VTherm ayant 4 vannes dont 3 demandes du chauffage fera passé ce capteur à 3. Seuls les équipements des VTherms qui sont marqués pour contrôler la chaudière centrale sont comptabilisés.
Vous avez donc en permanence, les informations qui permettent de piloter et régler le déclenchement de la chaudière.
Toutes ces entités sont rattachés au service de configuration centrale :
![Les entités pilotant la chaudière](/images/entitites-central-boiler.png?raw=true)
### Configuration
Pour configurer cette fonction, vous devez avoir une configuration centralisée (cf. [Configuration](#configuration)) et cochez la case 'Ajouter une chuadière centrale' :
![Ajout d'une chaudière centrale](/images/config-central-boiler-1.png?raw=true)
Sur la page suivante vous pouvez donner la configuration des services à appeler lors de l'allumage / extinction de la chaudière :
![Ajout d'une chaudière centrale](/images/config-central-boiler-2.png?raw=true)
Les services se configurent comme indiqués dans la page :
1. le format général est `entity_id/service_id[/attribut:valeur]` (où `/attribut:valeur` est facultatif),
2. `entity_id` est le nom de l'entité qui commande la chaudière sous la forme `domain.entity_name`. Par exemple: `switch.chaudiere` pour les chaudière commandée par un switch ou `climate.chaudière` pour une chaudière commandée par un thermostat ou tout autre entité qui permet le contrôle de la chaudière (il n'y a pas de limitation). On peut aussi commuter des entrées (`helpers`) comme des `input_boolean` ou `input_number`.
3. `service_id` est le nom du service à appeler sous la forme `domain.service_name`. Par exemple: `switch.turn_on`, `switch.turn_off`, `climate.set_temperature`, `climate.set_hvac_mode` sont des exemples valides.
4. pour certain service vous aurez besoin d'un paramètre. Cela peut être le 'Mode CVC' `climate.set_hvac_mode` ou la température cible pour `climate.set_temperature`. Ce paramètre doit être configuré sous la forme `attribut:valeur` en fin de chaine.
Exemples (à ajuster à votre cas) :
- `climate.chaudiere/climate.set_hvac_mode/hvac_mode:heat` : pour allumer le thermostat de la chaudière en mode chauffage,
- `climate.chaudiere/climate.set_hvac_mode/hvac_mode:off` : pour stopper le thermostat de la chaudière,
- `switch.pompe_chaudiere/switch.turn_on` : pour allumer le swicth qui alimente la pompe de la chaudière,
- `switch.pompe_chaudiere/switch.turn_off` : pour allumer le swicth qui alimente la pompe de la chaudière,
- ...
### Comment trouver le bon service ?
Pour trouver le services a utiliser, le mieux est d'aller dans "Outils de développement / Services", chercher le service a appelé, l'entité à commander et l'éventuel paramètre à donner.
Cliquez sur 'Appeler le service'. Si votre chaudière s'allume vous avez la bonne configuration. Passez alors en mode Yaml et recopiez les paramètres.
Exemple:
Sous "Outils de développement / Service" :
![Configuration du service](/images/dev-tools-turnon-boiler-1.png?raw=true)
En mode yaml :
![Configuration du service](/images/dev-tools-turnon-boiler-2.png?raw=true)
Le service à configurer est alors le suivant: `climate.empty_thermostast/climate.set_hvac_mode/hvac_mode:heat` (notez la suppression du blanc dans `hvac_mode:heat`)
Faite alors de même pour le service d'extinction et vous êtes parés.
### Les évènements
A chaque allumage ou extinction réussie de la chaudière un évènement est envoyé par Versatile Thermostat. Il peut avantageusement être capté par une automatisation, par exemple pour notifier un changement.
Les évènements ressemblent à ça :
Un évènement d'allumage :
```
event_type: versatile_thermostat_central_boiler_event
data:
central_boiler: true
entity_id: binary_sensor.central_boiler
name: Central boiler
state_attributes: null
origin: LOCAL
time_fired: "2024-01-14T11:33:52.342026+00:00"
context:
id: 01HM3VZRJP3WYYWPNSDAFARW1T
parent_id: null
user_id: null
```
Un évènement d'extinction :
```
event_type: versatile_thermostat_central_boiler_event
data:
central_boiler: false
entity_id: binary_sensor.central_boiler
name: Central boiler
state_attributes: null
origin: LOCAL
time_fired: "2024-01-14T11:43:52.342026+00:00"
context:
id: 01HM3VZRJP3WYYWPNSDAFBRW1T
parent_id: null
user_id: null
```
### Avertissement
> ![Astuce](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*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.
## Synthèse des paramètres ## Synthèse des paramètres
| Paramètre | Libellé | "over switch" | "over climate" | over valve | | Paramètre | Libellé | "over switch" | "over climate" | "over valve" | "configuration centrale" |
| ----------------------------------------- | --------------------------------------------------------------------------------- | ------------- | ------------------- | ---------- | | ----------------------------------------- | --------------------------------------------------------------------------------- | ------------- | ------------------- | ------------ | ------------------------ |
| ``name`` | Nom | X | X | X | | ``name`` | Nom | X | X | X | - |
| ``thermostat_type`` | Type de thermostat | X | X | X | | ``thermostat_type`` | Type de thermostat | X | X | X | - |
| ``temperature_sensor_entity_id`` | Temperature sensor entity id | X | X (auto-regulation) | X | | ``temperature_sensor_entity_id`` | Temperature sensor entity id | X | X (auto-regulation) | X | - |
| ``external_temperature_sensor_entity_id`` | Température de l'exterieur sensor entity id | X | X (auto-regulation) | X | | ``external_temperature_sensor_entity_id`` | Température de l'exterieur sensor entity id | X | X (auto-regulation) | X | X |
| ``cycle_min`` | Durée du cycle (minutes) | X | X | X | | ``cycle_min`` | Durée du cycle (minutes) | X | X | X | - |
| ``temp_min`` | Température minimale permise | X | X | X | | ``temp_min`` | Température minimale permise | X | X | X | X |
| ``temp_max`` | Température maximale permise | X | X | X | | ``temp_max`` | Température maximale permise | X | X | X | X |
| ``device_power`` | Puissance de l'équipement | X | X | X | | ``device_power`` | Puissance de l'équipement | X | X | X | - |
| ``use_window_feature`` | Avec détection des ouvertures | X | X | X | | ``use_central_mode`` | Autorisation du contrôle centralisé | X | X | X | - |
| ``use_motion_feature`` | Avec détection de mouvement | X | X | X | | ``use_window_feature`` | Avec détection des ouvertures | X | X | X | - |
| ``use_power_feature`` | Avec gestion de la puissance | X | X | X | | ``use_motion_feature`` | Avec détection de mouvement | X | X | X | - |
| ``use_presence_feature`` | Avec détection de présence | X | X | X | | ``use_power_feature`` | Avec gestion de la puissance | X | X | X | - |
| ``heater_entity1_id`` | 1er radiateur | X | - | - | | ``use_presence_feature`` | Avec détection de présence | X | X | X | - |
| ``heater_entity2_id`` | 2ème radiateur | X | - | - | | ``heater_entity1_id`` | 1er radiateur | X | - | - | - |
| ``heater_entity3_id`` | 3ème radiateur | X | - | - | | ``heater_entity2_id`` | 2ème radiateur | X | - | - | - |
| ``heater_entity4_id`` | 4ème radiateur | X | - | - | | ``heater_entity3_id`` | 3ème radiateur | X | - | - | - |
| ``proportional_function`` | Algorithme | X | - | - | | ``heater_entity4_id`` | 4ème radiateur | X | - | - | - |
| ``climate_entity1_id`` | Thermostat sous-jacent | - | X | - | | ``proportional_function`` | Algorithme | X | - | - | - |
| ``climate_entity2_id`` | 2ème thermostat sous-jacent | - | X | - | | ``climate_entity1_id`` | Thermostat sous-jacent | - | X | - | - |
| ``climate_entity3_id`` | 3ème thermostat sous-jacent | - | X | - | | ``climate_entity2_id`` | 2ème thermostat sous-jacent | - | X | - | - |
| ``climate_entity4_id`` | 4ème thermostat sous-jacent | - | X | - | | ``climate_entity3_id`` | 3ème thermostat sous-jacent | - | X | - | - |
| ``valve_entity1_id`` | Vanne sous-jacente | - | - | X | | ``climate_entity4_id`` | 4ème thermostat sous-jacent | - | X | - | - |
| ``valve_entity2_id`` | 2ème vanne sous-jacente | - | - | X | | ``valve_entity1_id`` | Vanne sous-jacente | - | - | X | - |
| ``valve_entity3_id`` | 3ème vanne sous-jacente | - | - | X | | ``valve_entity2_id`` | 2ème vanne sous-jacente | - | - | X | - |
| ``valve_entity4_id`` | 4ème vanne sous-jacente | - | - | X | | ``valve_entity3_id`` | 3ème vanne sous-jacente | - | - | X | - |
| ``ac_mode`` | utilisation de l'air conditionné (AC) ? | X | X | X | | ``valve_entity4_id`` | 4ème vanne sous-jacente | - | - | X | - |
| ``tpi_coef_int`` | Coefficient à utiliser pour le delta de température interne | X | - | X | | ``ac_mode`` | utilisation de l'air conditionné (AC) ? | X | X | X | - |
| ``tpi_coef_ext`` | Coefficient à utiliser pour le delta de température externe | X | - | X | | ``tpi_coef_int`` | Coefficient à utiliser pour le delta de température interne | X | - | X | X |
| ``eco_temp`` | Température en preset Eco | X | X | X | | ``tpi_coef_ext`` | Coefficient à utiliser pour le delta de température externe | X | - | X | X |
| ``comfort_temp`` | Température en preset Confort | X | X | X | | ``frost_tp`` | Température en preset Hors-gel | X | X | X | X |
| ``boost_temp`` | Température en preset Boost | X | X | X | | ``eco_temp`` | Température en preset Eco | X | X | X | X |
| ``eco_ac_temp`` | Température en preset Eco en mode AC | X | X | X | | ``comfort_temp`` | Température en preset Confort | X | X | X | X |
| ``comfort_ac_temp`` | Température en preset Confort en mode AC | X | X | X | | ``boost_temp`` | Température en preset Boost | X | X | X | X |
| ``boost_ac_temp`` | Température en preset Boost en mode AC | X | X | X | | ``eco_ac_temp`` | Température en preset Eco en mode AC | X | X | X | X |
| ``window_sensor_entity_id`` | Détecteur d'ouverture (entity id) | X | X | X | | ``comfort_ac_temp`` | Température en preset Confort en mode AC | X | X | X | X |
| ``window_delay`` | Délai avant extinction (secondes) | X | X | X | | ``boost_ac_temp`` | Température en preset Boost en mode AC | X | X | X | X |
| ``window_auto_open_threshold`` | Seuil haut de chute de température pour la détection automatique (en °/min) | X | X | X | | ``window_sensor_entity_id`` | Détecteur d'ouverture (entity id) | X | X | X | - |
| ``window_auto_close_threshold`` | Seuil bas de chute de température pour la fin de détection automatique (en °/min) | X | X | X | | ``window_delay`` | Délai avant extinction (secondes) | X | X | X | X |
| ``window_auto_max_duration`` | Durée maximum d'une extinction automatique (en min) | X | X | X | | ``window_auto_open_threshold`` | Seuil haut de chute de température pour la détection automatique (en °/min) | X | X | X | X |
| ``motion_sensor_entity_id`` | Détecteur de mouvement entity id | X | X | X | | ``window_auto_close_threshold`` | Seuil bas de chute de température pour la fin de détection automatique (en °/min) | X | X | X | X |
| ``motion_delay`` | Délai avant prise en compte du mouvement (seconds) | X | X | X | | ``window_auto_max_duration`` | Durée maximum d'une extinction automatique (en min) | X | X | X | X |
| ``motion_off_delay`` | Délai avant prise en compte de la fin de mouvement (seconds) | X | X | X | | ``motion_sensor_entity_id`` | Détecteur de mouvement entity id | X | X | X | - |
| ``motion_preset`` | Preset à utiliser si mouvement détecté | X | X | X | | ``motion_delay`` | Délai avant prise en compte du mouvement (seconds) | X | X | X | - |
| ``no_motion_preset`` | Preset à utiliser si pas de mouvement détecté | X | X | X | | ``motion_off_delay`` | Délai avant prise en compte de la fin de mouvement (seconds) | X | X | X | X |
| ``power_sensor_entity_id`` | Capteur de puissance totale (entity id) | X | X | X | | ``motion_preset`` | Preset à utiliser si mouvement détecté | X | X | X | X |
| ``max_power_sensor_entity_id`` | Capteur de puissance Max (entity id) | X | X | X | | ``no_motion_preset`` | Preset à utiliser si pas de mouvement détecté | X | X | X | X |
| ``power_temp`` | Température si délestaqe | X | X | X | | ``power_sensor_entity_id`` | Capteur de puissance totale (entity id) | X | X | X | X |
| ``presence_sensor_entity_id`` | Capteur de présence entity id (true si quelqu'un est présent) | X | X | X | | ``max_power_sensor_entity_id`` | Capteur de puissance Max (entity id) | X | X | X | X |
| ``eco_away_temp`` | Température en preset Eco en cas d'absence | X | X | X | | ``power_temp`` | Température si délestaqe | X | X | X | X |
| ``comfort_away_temp`` | Température en preset Comfort en cas d'absence | X | X | X | | ``presence_sensor_entity_id`` | Capteur de présence entity id (true si quelqu'un est présent) | X | X | X | - |
| ``boost_away_temp`` | Température en preset Boost en cas d'absence | X | X | X | | ``frost_ay_temp`` | Température en preset Hors-gel en cas d'absence | X | X | X | X |
| ``eco_ac_away_temp`` | Température en preset Eco en cas d'absence en mode AC | X | X | X | | ``eco_away_temp`` | Température en preset Eco en cas d'absence | X | X | X | X |
| ``comfort_ac_away_temp`` | Température en preset Comfort en cas d'absence en mode AC | X | X | X | | ``comfort_away_temp`` | Température en preset Comfort en cas d'absence | X | X | X | X |
| ``boost_ac_away_temp`` | Température en preset Boost en cas d'absence en mode AC | X | X | X | | ``boost_away_temp`` | Température en preset Boost en cas d'absence | X | X | X | X |
| ``minimal_activation_delay`` | Délai minimal d'activation | X | - | - | | ``eco_ac_away_temp`` | Température en preset Eco en cas d'absence en mode AC | X | X | X | X |
| ``security_delay_min`` | Délai maximal entre 2 mesures de températures | X | - | X | | ``comfort_ac_away_temp`` | Température en preset Comfort en cas d'absence en mode AC | X | X | X | X |
| ``security_min_on_percent`` | Pourcentage minimal de puissance pour passer en mode sécurité | X | - | X | | ``boost_ac_away_temp`` | Température en preset Boost en cas d'absence en mode AC | X | X | X | X |
| ``auto_regulation_mode`` | Le mode d'auto-régulation | - | X | - | | ``minimal_activation_delay`` | Délai minimal d'activation | X | - | - | X |
| ``auto_regulation_dtemp`` | La seuil d'auto-régulation | - | X | - | | ``security_delay_min`` | Délai maximal entre 2 mesures de températures | X | - | X | X |
| ``auto_regulation_period_min`` | La période minimale d'auto-régulation | - | X | - | | ``security_min_on_percent`` | Pourcentage minimal de puissance pour passer en mode sécurité | X | - | X | X |
| ``inverse_switch_command`` | Inverse la commande du switch (pour switch avec fil pilote) | X | - | - | | ``auto_regulation_mode`` | Le mode d'auto-régulation | - | X | - | - |
| ``auto_regulation_dtemp`` | La seuil d'auto-régulation | - | X | - | - |
| ``auto_regulation_period_min`` | La période minimale d'auto-régulation | - | X | - | - |
| ``inverse_switch_command`` | Inverse la commande du switch (pour switch avec fil pilote) | X | - | - | - |
| ``auto_fan_mode`` | Mode de ventilation automatique | - | X | - | - |
| ``add_central_boiler_control`` | Ajout du controle d'une chaudière centrale | - | - | - | 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 |
| ``used_by_controls_central_boiler`` | Indique si le VTherm contrôle la chaudière centrale | X | X | X | - |
# Exemples de réglage # Exemples de réglage
@@ -596,11 +774,13 @@ Versatile Thermostat vous permet d'être notifié lorsqu'un évènement de ce ty
## Mes presets ## Mes presets
Ceci est juste un exemple de la façon dont j'utilise le préréglage. A vous de vous adapter à votre configuration mais cela peut être utile pour comprendre son fonctionnement. Ceci est juste un exemple de la façon dont j'utilise le préréglage. A vous de vous adapter à votre configuration mais cela peut être utile pour comprendre son fonctionnement.
``Hors gel`` : 10 °C
``Éco`` : 17 °C ``Éco`` : 17 °C
``Confort`` : 19 °C ``Confort`` : 19 °C
``Boost`` : 20 °C ``Boost`` : 20 °C
Lorsque la présence est désactivée : Lorsque la présence est désactivée :
``Hors gel`` : 10 °C
``Éco`` : 16,5 °C ``Éco`` : 16,5 °C
``Confort`` : 17 °C ``Confort`` : 17 °C
``Boost`` : 18 °C ``Boost`` : 18 °C
@@ -756,6 +936,7 @@ Les évènements notifiés sont les suivants:
- ``versatile_thermostat_temperature_event`` : une ou les deux mesures de température d'un thermostat n'ont pas été mis à jour depuis plus de `security_delay_min`` minutes - ``versatile_thermostat_temperature_event`` : une ou les deux mesures de température d'un thermostat n'ont pas été mis à jour depuis plus de `security_delay_min`` minutes
- ``versatile_thermostat_hvac_mode_event`` : le thermostat est allumé ou éteint. Cet évènement est aussi diffusé au démarrage du thermostat - ``versatile_thermostat_hvac_mode_event`` : le thermostat est allumé ou éteint. Cet évènement est aussi diffusé au démarrage du thermostat
- ``versatile_thermostat_preset_event`` : un nouveau preset est sélectionné sur le thermostat. Cet évènement est aussi diffusé au démarrage du thermostat - ``versatile_thermostat_preset_event`` : un nouveau preset est sélectionné sur le thermostat. Cet évènement est aussi diffusé au démarrage du thermostat
- ``versatile_thermostat_central_boiler_event`` : un évènement indiquant un changement dans l'état de la chaudière.
Si vous avez bien suivi, lorsqu'un thermostat passe en mode sécurité, 3 évènements sont déclenchés : Si vous avez bien suivi, lorsqu'un thermostat passe en mode sécurité, 3 évènements sont déclenchés :
1. ``versatile_thermostat_temperature_event`` pour indiquer qu'un thermomètre ne répond plus, 1. ``versatile_thermostat_temperature_event`` pour indiquer qu'un thermomètre ne répond plus,
@@ -813,6 +994,9 @@ Les attributs personnalisés sont les suivants :
| ``valve_open_percent`` | Le pourcentage d'ouverture de la vanne | | ``valve_open_percent`` | Le pourcentage d'ouverture de la vanne |
| ``regulated_target_temperature`` | La température de consigne calculée par l'auto-régulation | | ``regulated_target_temperature`` | La température de consigne calculée par l'auto-régulation |
| ``is_inversed`` | True si la commande est inversée (fil pilote avec diode) | | ``is_inversed`` | True si la commande est inversée (fil pilote avec diode) |
| ``is_controlled_by_central_mode`` | True si le VTherm peut être controlé de façon centrale |
| ``last_central_mode`` | Le dernier mode central utilisé (None si le VTherm n'est pas controlé en central) |
| ``is_used_by_central_boiler`` | Indique si le VTherm peut contrôler la chaudière centrale |
# Quelques résultats # Quelques résultats
@@ -986,6 +1170,10 @@ Remplacez les valeurs entre [[ ]] par les votres.
step: day step: day
``` ```
Exemple de courbes obtenues avec Plotly :
![image](/images/plotly-curves.png?raw=true)
## Et toujours de mieux en mieux avec l'AappDaemon NOTIFIER pour notifier les évènements ## Et toujours de mieux en mieux avec l'AappDaemon NOTIFIER pour notifier les évènements
Cette automatisation utilise l'excellente App Daemon nommée NOTIFIER développée par Horizon Domotique que vous trouverez en démonstration [ici](https://www.youtube.com/watch?v=chJylIK0ASo&ab_channel=HorizonDomotique) et le code est [ici](https://github.com/jlpouffier/home-assistant-config/blob/master/appdaemon/apps/notifier.py). Elle permet de notifier les utilisateurs du logement lorsqu'un des évènements touchant à la sécurité survient sur un des Versatile Thermostats. Cette automatisation utilise l'excellente App Daemon nommée NOTIFIER développée par Horizon Domotique que vous trouverez en démonstration [ici](https://www.youtube.com/watch?v=chJylIK0ASo&ab_channel=HorizonDomotique) et le code est [ici](https://github.com/jlpouffier/home-assistant-config/blob/master/appdaemon/apps/notifier.py). Elle permet de notifier les utilisateurs du logement lorsqu'un des évènements touchant à la sécurité survient sur un des Versatile Thermostats.
@@ -1134,6 +1322,19 @@ Exemple :
En mode `over_switch` si plusieurs radiateurs sont configurés pour un même VTherm, l'alllumage va se faire de façon séquentiel pour lisser au plus possible les pics de consommation. En mode `over_switch` si plusieurs radiateurs sont configurés pour un même VTherm, l'alllumage va se faire de façon séquentiel pour lisser au plus possible les pics de consommation.
Cela est tout à fait normal et voulu. C'est décrit ici : [Pour un thermostat de type ```thermostat_over_switch```](#pour-un-thermostat-de-type-thermostat_over_switch) Cela est tout à fait normal et voulu. C'est décrit ici : [Pour un thermostat de type ```thermostat_over_switch```](#pour-un-thermostat-de-type-thermostat_over_switch)
## Le radiateur chauffe alors que la température de consigne est dépassée ou ne chauffe pas alors que la température de la pièce est bien en-dessous de la consigne
### Type `over_switch` ou `over_valve`
Avec un VTherm de type `over_switch` ou `over_valve`, ce défaut montre juste que les paramètres de l'algorithme TPI sont mal réglés. Voir [Algorithme TPI](#algorithme-tpi) pour optimiser les réglages.
### Type `over_climate`
Avec un VTherm de type `over_climate`, la régulation est faite par le `climate` sous-jacent directement et VTherm se contente de lui transmettre les consignes. Donc si le radiateur chauffe alors que la température de consigne est dépassée, c'est certainement que sa mesure de température interne est biaisée. Ca arrive très souvent avec les TRV et les clims réversibles qui ont un capteur de température interne, soit trop près de l'élément de chauffe (donc trop froid l'hiver).
Exemple de discussion autour de ces sujets: [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348), [#316](https://github.com/jmcollin78/versatile_thermostat/issues/316), [#312](https://github.com/jmcollin78/versatile_thermostat/discussions/312), [#278](https://github.com/jmcollin78/versatile_thermostat/discussions/278)
Pour s'en sortir, VTherm est équipé d'une fonction nommée auto-régulation qui permet d'adapter la consigne envoyée au sous-jacent jusqu'à ce que la consigne soit respectée. Cette fonction permet de compenser le biais de mesure des thermomètres internes. Si le biais est important la régulation doit être importante. Voir [L'auto-régulation](#lauto-régulation) pour configurer l'auto-régulation.
## Régler les paramètres de détection d'ouverture de fenêtre en mode auto ## Régler les paramètres de détection d'ouverture de fenêtre en mode auto
Si vous n'arrivez pas à régler la fonction de détection des ouvertures en mode auto (cf. [auto](#le-mode-auto)), vous pouvez essayer de modifier les paramètres de l'algorithme de lissage de la température. Si vous n'arrivez pas à régler la fonction de détection des ouvertures en mode auto (cf. [auto](#le-mode-auto)), vous pouvez essayer de modifier les paramètres de l'algorithme de lissage de la température.
@@ -1201,6 +1402,27 @@ Cela va dépendre de la cause du problème :
3. Certains capteurs de température, n'envoie pas de mesure si la température n'a pas changée. Donc en cas de température très stable pendant longtemps, le mode sécurité peut se déclencher. Ce n'est pas très grave puisqu'il s'enlève dès que le VTherm reçoit à nouveau une température. Sur certain thermomètre (TuYA par exemple), on peut forcer le délai max entre 2 mesures. Il conviendra de mettre un délai max < `security_delay_min`, 3. Certains capteurs de température, n'envoie pas de mesure si la température n'a pas changée. Donc en cas de température très stable pendant longtemps, le mode sécurité peut se déclencher. Ce n'est pas très grave puisqu'il s'enlève dès que le VTherm reçoit à nouveau une température. Sur certain thermomètre (TuYA par exemple), on peut forcer le délai max entre 2 mesures. Il conviendra de mettre un délai max < `security_delay_min`,
4. Dès que la température sera a nouveau reçue le mode sécurité s'enlèvera et les valeurs précédentes de preset, température cible et mode seront restaurées. 4. Dès que la température sera a nouveau reçue le mode sécurité s'enlèvera et les valeurs précédentes de preset, température cible et mode seront restaurées.
## Utilisation d'un groupe de personnes comme capteur de présence
Malheureusement, les groupes de personnes ne sont pas reconnus comme des capteurs de présence. On ne peut donc pas les utiliser directement dans VTherm.
Le contournement est de créer un template de binary_sensor avec le code suivant :
Fichier `template.yaml` :
```
- binary_sensor:
- name: maison_occupee
unique_id: maison_occupee
state: "{{is_state('person.person1', 'home') or is_state('person.person2', 'home') or is_state('input_boolean.force_presence', 'on')}}"
device_class: occupancy
```
Vous noterez dans cet exemple, l'utilisation d'un input_boolean nommé force_presence qui permet de forcer le capteur à `True` et ainsi de forcer les VTherm qui l'utilise avec présence active. Ca permet par exemple, de forcer un pré-chauffage du logement lors du départ du travail, ou lorsqu'une personne non reconnue nativement dans HA est présente.
Fichier `configuration.yaml`:
```
...
template: !include templates.yaml
...
```
*** ***

470
README.md
View File

@@ -8,7 +8,7 @@
> ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) This thermostat integration aims to drastically simplify your automations around climate management. Because all classical events in climate are natively handled by the thermostat (nobody at home ?, activity detected in a room ?, window open ?, power shedding ?), you don't have to build over complicated scripts and automations to manage your climates ;-). > ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) This thermostat integration aims to drastically simplify your automations around climate management. Because all classical events in climate are natively handled by the thermostat (nobody at home ?, activity detected in a room ?, window open ?, power shedding ?), you don't have to build over complicated scripts and automations to manage your climates ;-).
- [Breaking changes in 4.0.0](#breaking-changes-in-400) - [Major changes in version 5.0](#major-changes-in-version-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)
@@ -17,6 +17,7 @@
- [HACS installation (recommended)](#hacs-installation-recommended) - [HACS installation (recommended)](#hacs-installation-recommended)
- [Manual installation](#manual-installation) - [Manual installation](#manual-installation)
- [Configuration](#configuration) - [Configuration](#configuration)
- [Creation of a new Versatile Thermostat](#creation-of-a-new-versatile-thermostat)
- [Minimal configuration update](#minimal-configuration-update) - [Minimal configuration update](#minimal-configuration-update)
- [Select the driven entity](#select-the-driven-entity) - [Select the driven entity](#select-the-driven-entity)
- [For a ```thermostat_over_switch``` type thermostat](#for-a-thermostat_over_switch-type-thermostat) - [For a ```thermostat_over_switch``` type thermostat](#for-a-thermostat_over_switch-type-thermostat)
@@ -32,7 +33,14 @@
- [Auto mode](#auto-mode) - [Auto mode](#auto-mode)
- [Configure the activity mode or motion detection](#configure-the-activity-mode-or-motion-detection) - [Configure the activity mode or motion detection](#configure-the-activity-mode-or-motion-detection)
- [Configure the power management](#configure-the-power-management) - [Configure the power management](#configure-the-power-management)
- [Configure presence or occupancy](#configure-presence-or-occupancy)
- [Advanced configuration](#advanced-configuration) - [Advanced configuration](#advanced-configuration)
- [Centralized control](#centralized-control)
- [Control of a central boiler](#control-of-a-central-boiler)
- [Setup](#setup)
- [How to find the right service?](#how-to-find-the-right-service)
- [The events](#the-events)
- [Warning](#warning)
- [Parameters synthesis](#parameters-synthesis) - [Parameters synthesis](#parameters-synthesis)
- [Examples tuning](#examples-tuning) - [Examples tuning](#examples-tuning)
- [Electrical heater](#electrical-heater) - [Electrical heater](#electrical-heater)
@@ -46,7 +54,7 @@
- [Services](#services) - [Services](#services)
- [Force the presence / occupancy](#force-the-presence--occupancy) - [Force the presence / occupancy](#force-the-presence--occupancy)
- [Change the temperature of presets](#change-the-temperature-of-presets) - [Change the temperature of presets](#change-the-temperature-of-presets)
- [Change security settings](#change-security-settings) - [Change safety settings](#change-safety-settings)
- [ByPass Window Check](#bypass-window-check) - [ByPass Window Check](#bypass-window-check)
- [Events](#events) - [Events](#events)
- [Custom attributes](#custom-attributes) - [Custom attributes](#custom-attributes)
@@ -62,25 +70,34 @@
- [Using a Heatzy](#using-a-heatzy) - [Using a Heatzy](#using-a-heatzy)
- [Using a Heatsink with a Pilot Wire](#using-a-heatsink-with-a-pilot-wire) - [Using a Heatsink with a Pilot Wire](#using-a-heatsink-with-a-pilot-wire)
- [Only the first radiator heats](#only-the-first-radiator-heats) - [Only the first radiator heats](#only-the-first-radiator-heats)
- [The radiator heats up even though the setpoint temperature is exceeded or does not heat up even though the room temperature is well below the setpoint](#the-radiator-heats-up-even-though-the-setpoint-temperature-is-exceeded-or-does-not-heat-up-even-though-the-room-temperature-is-well-below-the-setpoint)
- [Type `over_switch` or `over_valve`](#type-over_switch-or-over_valve)
- [Type `over_climate`](#type-over_climate)
- [Adjust window opening detection parameters in auto mode](#adjust-window-opening-detection-parameters-in-auto-mode) - [Adjust window opening detection parameters in auto mode](#adjust-window-opening-detection-parameters-in-auto-mode)
- [Why does my Versatile Thermostat go into Safety?](#why-does-my-versatile-thermostat-go-into-safety) - [Why does my Versatile Thermostat go into Safety?](#why-does-my-versatile-thermostat-go-into-safety)
- [How to detect security mode?](#how-to-detect-security-mode) - [How to detect safety mode?](#how-to-detect-safety-mode)
- [How can I be notified when this happens?](#how-can-i-be-notified-when-this-happens) - [How can I be notified when this happens?](#how-can-i-be-notified-when-this-happens)
- [How to repair?](#how-to-repair) - [How to repair?](#how-to-repair)
- [Using a group of people as a presence sensor](#using-a-group-of-people-as-a-presence-sensor)
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.
>![New](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/new-icon.png?raw=true) _*News*_ >![New](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/new-icon.png?raw=true) _*News*_
> * **Release 5.4**: Added a temperature step [#311](https://github.com/jmcollin78/versatile_thermostat/issues/311). Added some regulation thresholdfor `over_valve` VTherm in order to avoid drowing the battery of TRV devices [#338](https://github.com/jmcollin78/versatile_thermostat/issues/338).
> * **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.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).
<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).
> * **Release 4.0**: Added the support of **Versatile Thermostat UI Card**. See [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card). Added a **Slow** regulation mode for slow latency heating devices [#168](https://github.com/jmcollin78/versatile_thermostat/issues/168). Change the way **the power is calculated** in case of VTherm with multi-underlying equipements [#146](https://github.com/jmcollin78/versatile_thermostat/issues/146). Added the support of AC and Heat for VTherm over switch alse [#144](https://github.com/jmcollin78/versatile_thermostat/pull/144) > * **Release 4.0**: Added the support of **Versatile Thermostat UI Card**. See [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card). Added a **Slow** regulation mode for slow latency heating devices [#168](https://github.com/jmcollin78/versatile_thermostat/issues/168). Change the way **the power is calculated** in case of VTherm with multi-underlying equipements [#146](https://github.com/jmcollin78/versatile_thermostat/issues/146). Added the support of AC and Heat for VTherm over switch alse [#144](https://github.com/jmcollin78/versatile_thermostat/pull/144)
> * **Release 3.8**: Added a **self-regulation function** for `over climate` thermostats whose regulation is done by the underlying climate. See [Self-regulation](#self-regulation) and [#129](https://github.com/jmcollin78/versatile_thermostat/issues/129). Added the possibility of **inverting the command** for an `over switch` thermostat to address installations with pilot wire and diode [#124](https://github.com/jmcollin78/versatile_thermostat/issues/124). > * **Release 3.8**: Added a **self-regulation function** for `over climate` thermostats whose regulation is done by the underlying climate. See [Self-regulation](#self-regulation) and [#129](https://github.com/jmcollin78/versatile_thermostat/issues/129). Added the possibility of **inverting the command** for an `over switch` thermostat to address installations with pilot wire and diode [#124](https://github.com/jmcollin78/versatile_thermostat/issues/124).
> * **Release 3.7**: Addition of the **Versatile Thermostat type `over valve`** to control a TRV valve directly or any other dimmer type equipment for heating. Regulation is then done directly by acting on the opening percentage of the underlying entity: 0 the valve is cut off, 100: the valve is fully opened. See [#131](https://github.com/jmcollin78/versatile_thermostat/issues/131). Added a function allowing the bypass of opening detection [#138](https://github.com/jmcollin78/versatile_thermostat/issues/138). Added Slovak language > * **Release 3.7**: Addition of the **Versatile Thermostat type `over valve`** to control a TRV valve directly or any other dimmer type equipment for heating. Regulation is then done directly by acting on the opening percentage of the underlying entity: 0 the valve is cut off, 100: the valve is fully opened. See [#131](https://github.com/jmcollin78/versatile_thermostat/issues/131). Added a function allowing the bypass of opening detection [#138](https://github.com/jmcollin78/versatile_thermostat/issues/138). Added Slovak language
<details>
<summary>Others releases</summary>
> * **Release 3.6**: Added the `motion_off_delay` parameter to improve motion management [#116](https://github.com/jmcollin78/versatile_thermostat/issues/116), [#128](https://github.com/jmcollin78/versatile_thermostat/issues/128). Added AC (air conditioning) mode for a VTherm over switch. Preparing the Github project to facilitate contributions [#127](https://github.com/jmcollin78/versatile_thermostat/issues/127) > * **Release 3.6**: Added the `motion_off_delay` parameter to improve motion management [#116](https://github.com/jmcollin78/versatile_thermostat/issues/116), [#128](https://github.com/jmcollin78/versatile_thermostat/issues/128). Added AC (air conditioning) mode for a VTherm over switch. Preparing the Github project to facilitate contributions [#127](https://github.com/jmcollin78/versatile_thermostat/issues/127)
> * **Release 3.5**: Multiple thermostats when using "thermostat over another thermostat" mode [#113](https://github.com/jmcollin78/versatile_thermostat/issues/113) > * **Release 3.5**: Multiple thermostats when using "thermostat over another thermostat" mode [#113](https://github.com/jmcollin78/versatile_thermostat/issues/113)
> * **Release 3.4**: bug fixes and expose preset temperatures for AC mode [#103](https://github.com/jmcollin78/versatile_thermostat/issues/103) > * **Release 3.4**: bug fixes and expose preset temperatures for AC mode [#103](https://github.com/jmcollin78/versatile_thermostat/issues/103)
@@ -93,12 +110,25 @@ 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>
# Breaking changes in 4.0.0 # Major changes in version 5.0
1. The power of the device should now be the total power of all controler devices by the VTherm. This allow to have eterogeneous equipment with different power. In case of multi-devices controlled by a single VTherm you will have to edit and change the `device_power` value. Set the total power of all devices. ![New](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/new-icon.png?raw=true)
2. The threshold for auto window auto detection should be specified in °/hour and no more in °/min. To keep the same parameters you have to multiply the configured value by 60.
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”,
2. Enter the attributes of this central configuration
To then use it in the other VTherms, you must reconfigure them and whenever possible check the "Use central configuration" box. This check box appears in all attribute groups that can use central configuration: main attributes, TPI, openings, movement, power, presence and advanced parameters.
The configurable attributes in the central configuration are listed here: [Parameter summary](#parameter-synthesis).
When changing the central configuration, all VTherms will be reloaded to take these changes into account.
Consequently, the entire configuration phase of a VTherm has been profoundly modified to be able to use the central configuration or overload the values of the central configuration with values specific to the VTherm being configured.
**Note:** the VTherm configuration screenshots have not been updated.
# Thanks for the beer [buymecoffee](https://www.buymeacoffee.com/jmcollin78) # Thanks for the beer [buymecoffee](https://www.buymeacoffee.com/jmcollin78)
Many thanks to @salabur, @pvince83, @bergoglio, @EPicLURcher, @ecolorado66, @Kriss1670, @maia, @f.maymil, @moutte69, @Jerome, @Gunnar M, @Greg.o, @John Burgess, @abyssmal for the beers. It's very nice and encourages me to continue! Many thanks to @salabur, @pvince83, @bergoglio, @EPicLURcher, @ecolorado66, @Kriss1670, @maia, @f.maymil, @moutte69, @Jerome, @Gunnar M, @Greg.o, @John Burgess, @abyssmal, @capinfo26, @Helge, @MattG, @MattG, @Mexx62, @Someone, @Lajull, @giopeco for the beers. It's very nice and encourages me to continue!
# When to use / not use # When to use / not use
This thermostat can control 3 types of equipment: This thermostat can control 3 types of equipment:
@@ -121,6 +151,7 @@ Some TRV type thermostats are known to be incompatible with the Versatile Thermo
2. "Homematic" (and possible Homematic IP) thermostats are known to have problems with Versatile Thermostats because of limitations of the underlying RF protocol. This problem especially occurs when trying to control several Homematic thermostats at once in one Versatile Thermostat instance. In order to reduce duty cycle load, you may e.g. group thermostats with Homematic-specific procedures (e.g. using a wall thermostat) and let Versatile Thermostat only control the wall thermostat directly. Another option is to control only one thermostat and propagate the changes in HVAC mode and temperature by an automation. 2. "Homematic" (and possible Homematic IP) thermostats are known to have problems with Versatile Thermostats because of limitations of the underlying RF protocol. This problem especially occurs when trying to control several Homematic thermostats at once in one Versatile Thermostat instance. In order to reduce duty cycle load, you may e.g. group thermostats with Homematic-specific procedures (e.g. using a wall thermostat) and let Versatile Thermostat only control the wall thermostat directly. Another option is to control only one thermostat and propagate the changes in HVAC mode and temperature by an automation.
3. Thermostat of type Heatzy which doesn't supports the set_temperature command. 3. Thermostat of type Heatzy which doesn't supports the set_temperature command.
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 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.
# Why another thermostat implementation ? # Why another thermostat implementation ?
@@ -133,8 +164,10 @@ This component named __Versatile thermostat__ manage the following use cases :
- Use a **TPI (Time Proportional Interval) algorithm** thank's to [[Argonaute](https://forum.hacf.fr/u/argonaute/summary)] algorithm , - Use a **TPI (Time Proportional Interval) algorithm** thank's to [[Argonaute](https://forum.hacf.fr/u/argonaute/summary)] algorithm ,
- Add **power shedding management** or regulation to avoid exceeding a defined total power. When max power is exceeded, a hidden 'power' preset is set on the climate entity. When power goes below the max, the previous preset is restored. - Add **power shedding management** or regulation to avoid exceeding a defined total power. When max power is exceeded, a hidden 'power' preset is set on the climate entity. When power goes below the max, the previous preset is restored.
- Add **home presence management**. This feature allows you to dynamically change the temperature of preset considering a occupancy sensor of your home. - Add **home presence management**. This feature allows you to dynamically change the temperature of preset considering a occupancy sensor of your home.
- Add **services to interact with the thermostat** from others integration: you can force the presence / un-presence using a service, and you can dynamically change the temperature of the presets and change dynamically the security parameters. - Add **services to interact with the thermostat** from others integration: you can force the presence / un-presence using a service, and you can dynamically change the temperature of the presets and change dynamically the safety parameters.
- Add sensors to see the internal states of the thermostat - Add sensors to see the internal states of the thermostat,
- Centralized control of all Versatile Thermostats to stop them all, switch them all to frost protection, force them into Heating mode (winter), force them into Cooling mode (summer).
- Control of a central boiler and the VTherms which must control this boiler.
# How to install this incredible Versatile Thermostat ? # How to install this incredible Versatile Thermostat ?
@@ -159,8 +192,17 @@ This component named __Versatile thermostat__ manage the following use cases :
# Configuration # Configuration
Note: no configuration in configuration.yaml is needed because all configuration is done through the standard GUI when adding the integration. -- VTherm = Versatile Thermostat in the remainder of this document --
> ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_
>
> Three ways to configure VTherms are available:
> 1. Each Versatile Thermostat is completely independently configured. Choose this option if you do not want to have any central configuration or management.
> 2. Some aspects are configured centrally. This allows e.g. define the min/max temperature, open window detection, etc. at the level of a single central instance. For each VTherm you configure, you can then choose to use the central configuration or override it with custom settings.
> 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`.
## Creation of a new Versatile Thermostat
Click on Add integration button in the integration page Click on Add integration button in the integration page
![image](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/add-an-integration.png?raw=true) ![image](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/add-an-integration.png?raw=true)
@@ -169,7 +211,10 @@ The configuration can be change through the same interface. Simply select the th
Then follow the configurations steps as follow: Then follow the configurations steps as follow:
## Minimal configuration update ## Minimal configuration update
![image](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/config-main.png?raw=true)
![image](/images/config-main0.png?raw=true)
![image](/images/config-main.png?raw=true)
Give the main mandatory attributes: Give the main mandatory attributes:
1. a name (will be the name of the integration and also the name of the climate entity) 1. a name (will be the name of the integration and also the name of the climate entity)
@@ -179,7 +224,8 @@ Give the main mandatory attributes:
6. a cycle duration in minutes. On each cycle, the heater will cycle on and then off for a calculated time to reach the target temperature (see [preset](#configure-the-preset-temperature) below). In ```over_climate``` mode, the cycle is only used to carry out basic controls but does not directly regulate the temperature. It's the underlying climate that does it, 6. a cycle duration in minutes. On each cycle, the heater will cycle on and then off for a calculated time to reach the target temperature (see [preset](#configure-the-preset-temperature) below). In ```over_climate``` mode, the cycle is only used to carry out basic controls but does not directly regulate the temperature. It's the underlying climate that does it,
7. minimum and maximum thermostat temperatures, 7. minimum and maximum thermostat temperatures,
8. the power of the l'équipement which will activate the power and energy sensors of the device, 8. the power of the l'équipement which will activate the power and energy sensors of the device,
9. the list of features that will be used for this thermostat. Depending on your choices, the following configuration screens will appear or not. 9. the possibility of controlling the thermostat centrally. Cf [centralized control](#centralized-control),
10. the list of features that will be used for this thermostat. Depending on your choices, the following configuration screens will appear or not.
> ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_ > ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_
> 1. 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**,
@@ -355,7 +401,7 @@ The preset mode allows you to pre-configurate targeted temperature. Used in conj
> 1. Changing manually the target temperature, set the preset to None (no preset). This way you can always set a target temperature even if no preset are available. > 1. Changing manually the target temperature, set the preset to None (no preset). This way you can always set a target temperature even if no preset are available.
> 2. standard ``Away`` preset is a hidden preset which is not directly selectable. Versatile Thermostat uses the presence management or movement management to set automatically and dynamically the target temperature depending on a presence in the home or an activity in the room. See [presence management](#configure-the-presence-management). > 2. standard ``Away`` preset is a hidden preset which is not directly selectable. Versatile Thermostat uses the presence management or movement management to set automatically and dynamically the target temperature depending on a presence in the home or an activity in the room. See [presence management](#configure-the-presence-management).
> 3. if you uses the power shedding management, you will see a hidden preset named ``power``. The heater preset is set to ``power`` when overpowering conditions are encountered and shedding is active for this heater. See [power management](#configure-the-power-management). > 3. if you uses the power shedding management, you will see a hidden preset named ``power``. The heater preset is set to ``power`` when overpowering conditions are encountered and shedding is active for this heater. See [power management](#configure-the-power-management).
> 4. if you uses the advanced configuration you will see the preset set to ``security`` 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
## Configure the doors/windows turning on/off the thermostats ## Configure the doors/windows turning on/off the thermostats
@@ -427,7 +473,7 @@ For this to work, the climate thermostat should be in ``Activity`` preset mode.
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:
![image](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/config-power.png?raw=true) ![image](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/config-power.png?raw=true)
This feature allows you to regulate the power consumption of your radiators. Known as shedding, this feature allows you to limit the electrical power consumption of your heater if overpowering conditions are detected. Give a **sensor to the current power consumption of your house**, a **sensor to the max power** that should not be exceeded, the **power consumption of your heater** (in the first step of the configuration) and the algorithm will not start a radiator if the max power will be exceeded after radiator starts. This feature allows you to regulate the power consumption of your radiators. Known as shedding, this feature allows you to limit the electrical power consumption of your heater if overpowering conditions are detected. Give a **sensor to the current power consumption of your house**, a **sensor to the max power** that should not be exceeded, the **power consumption of your heaters linked to the VTherm** (in the first step of the configuration) and the algorithm will not start a radiator if the max power will be exceeded after radiator starts.
Note that all power values should have the same units (kW or W for example). Note that all power values should have the same units (kW or W for example).
@@ -440,22 +486,25 @@ This allows you to change the max power along time using a Scheduler or whatever
> 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.
If you choose the ```Presence management``` feature, this feature allows you to dynamically changes the temperature of all configured Versatile thermostat's presets when nobody is at home or when someone comes back home. For this, you have to configure the temperature that will be used for each preset when presence is off. When the occupancy sensor turns to off, those tempoeratures will be used. When it turns on again the "normal" temperature configured for the preset is used. See [preset management](#configure-the-preset-temperature). ## Configure presence or occupancy
To configure presence fills this form: If selected on the first page, this feature allows you to dynamically change the temperature of all configured thermostat presets when no one is home or when someone comes home. To do this, you must configure the temperature that will be used for each preset when presence is disabled. When the presence sensor turns off, these temperatures will be used. When it turns back on, the "normal" temperature configured for the preset is used. See [preset management](#configure-the-preset-temperature).
To configure presence, complete this form:
![image](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/config-presence.png?raw=true) ![image](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/config-presence.png?raw=true)
For this you need to configure: To do this, you must configure:
1. A **occupancy sensor** which state should be 'on' or 'home' if someone is present or 'off' or 'not_home' else, 1. An **occupancy sensor** whose state must be 'on' or 'home' if someone is present or 'off' or 'not_home' otherwise,
2. The **temperature used in Eco** preset when absent, 2. The **temperature used in Eco** predefined in the event of absence,
3. The **temperature used in Comfort** preset when absent, 3. The **temperature used in Comfort** preset in case of absence,
4. The **temperature used in Boost** preset when absent 4. The **temperature used in Boost** preset in case of absence
Si le mode AC est utilisé, vous pourrez aussi configurer les températures lorsque l'équipement en mode climatisation. If AC mode is used, you will also be able to configure temperatures when the equipment is in cooling mode.
> ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_ ATTENTION: groups of people do not function as a presence sensor. They are not recognized as a presence sensor. You must use a template as described here [Using a group of people as a presence sensor](#using-a-group-of-people-as-a-presence-sensor).
> 1. the switch of temperature is immediate and is reflected on the front component. The calculation will take the new target temperature into account at the next cycle calculation,
> 2. you can use direct person.xxxx sensor or group of sensors of Home Assistant. The presence sensor handles ``on`` or ``home`` states as present and ``off`` or ``not_home`` state as absent. > ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_
> 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.
## Advanced configuration ## Advanced configuration
Those parameters allows to fine tune the thermostat. Those parameters allows to fine tune the thermostat.
@@ -465,88 +514,221 @@ The advanced configuration form is the following:
The first delay (minimal_activation_delay_sec) in sec in the minimum delay acceptable for turning on the heater. When calculation gives a power on delay below this value, the heater will stays off. The first delay (minimal_activation_delay_sec) in sec in the minimum delay acceptable for turning on the heater. When calculation gives a power on delay below this value, the heater will stays off.
The second delay (security_delay_min) is the maximal delay between two temperature measure before setting the preset to ``security`` and turning off the thermostat. If the temperature sensor is no more giving temperature measures, the thermostat and heater will turns off after this delay and the preset of the thermostat will be set to ``security``. This is useful to avoid overheating is the battery of your temperature sensor is too low. The second delay (security_delay_min) is the maximal delay between two temperature measure before setting the preset to ``safety`` and turning off the thermostat. If the temperature sensor is no more giving temperature measures, the thermostat and heater will turns off after this delay and the preset of the thermostat will be set to ``safety``. This is useful to avoid overheating is the battery of your temperature sensor is too low.
The third parameter (``security_min_on_percent``) is the minimum value of ``on_percent`` below which the security preset will not be activated. This parameter makes it possible not to put a thermostat in safety, if the controlled radiator does not heat sufficiently. The third parameter (``security_min_on_percent``) is the minimum value of ``on_percent`` below which the safety preset will not be activated. This parameter makes it possible not to put a thermostat in safety, if the controlled radiator does not heat sufficiently.
Setting this parameter to ``0.00`` will trigger the security preset regardless of the last heating setpoint, conversely ``1.00`` will never trigger the security preset (which amounts to disabling the function). Setting this parameter to ``0.00`` will trigger the safety preset regardless of the last heating setpoint, conversely ``1.00`` will never trigger the safety preset (which amounts to disabling the function).
The fourth parameter (``security_default_on_percent``) is the ``on_percent`` value that will be used when the thermostat enters ``security`` mode. If you put ``0`` then the thermostat will be cut off when it goes into ``security`` mode, putting 0.2% for example allows you to keep a little heating (20% in this case), even in mode ``security``. 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.
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:
...
safety_mode:
check_outdoor_sensor: false
```
By default, the outdoor thermometer can trigger a trip if it no longer sends a value.
See [example tuning](#examples-tuning) for common tuning examples See [example tuning](#examples-tuning) for common tuning examples
>![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_ >![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_
> 1. When the temperature sensor comes to life and returns the temperatures, the preset will be restored to its previous value, > 1. When the temperature sensor comes to life and returns the temperatures, the preset will be restored to its previous value,
> 2. Attention, two temperatures are needed: internal temperature and external temperature and each must give the temperature, otherwise the thermostat will be in "security" preset, > 2. Attention, two temperatures are needed: internal temperature and external temperature and each must give the temperature, otherwise the thermostat will be in "safety" preset,
> 3. A service is available that allows you to set the 3 security parameters. This can be used to adapt the security function to your use. > 3. A service is available that allows you to set the 3 safety parameters. This can be used to adapt the safety function to your use.
> 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 security feature. > 5. Thermostat of type ``thermostat_over_climate`` are not concerned by the safety feature.
## Centralized control
Since release 5.2, if you have defined a centralized configuration, you have a new entity named `select.central_mode` which allows you to control all VTherms with a single action. For a VTherm to be centrally controllable, its configuration attribute named `use_central_mode` must be true.
This entity is presented in the form of a list of choices which contains the following choices:
1. `Auto`: the 'normal' mode in which each VTherm behaves as in previous versions,
2. `Stooped`: all VTherms are turned off (`hvac_off`),
3. `Heat only`: all VTherms are put in heating mode when this mode is supported by the VTherm, otherwise it is stopped,
3. `Cool only`: all VTherms are put in cooling mode when this mode is supported by the VTherm, otherwise it is stopped,
4. `Frost protection`: all VTherms are put in frost protection preset when this preset is supported by the VTherm, otherwise it is stopped.
It is therefore possible to control all VTherms (only those explicitly designated) with a single control.
Example rendering:
![central_mode](/images/central_mode.png?raw=true)
## 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.
The principle put in place is generally as follows:
1. a new entity of type `binary_sensor` and named by default `binary_sensor.central_boiler` is added,
2. in the VTherms configuration you indicate whether the VTherm should control the boiler. Indeed, in a heterogeneous installation, some VTherm must control the boiler and others not. You must therefore indicate in each VTherm configuration whether it controls the boiler or not,
3. the `binary_sensor.central_boiler` listens for changes in state of VTherm equipment marked as controlling the boiler,
4. as soon as the number of devices controlled by the VTherm requesting heating (ie its `hvac_action` goes to `Heating`) exceeds a configurable threshold, then the `binary_sensor.central_boiler` goes to `on` and **if a activation service has been configured, then this service is called**,
5. if the number of devices requiring heating falls below the threshold again, then the `binary_sensor.central_boiler` goes to `off` and if **a deactivation service has been configured, then this service is called**,
6. you have access to two entities:
- one of type `number` named by default `number.boiler_activation_threshold`, gives the trigger threshold. This threshold is in number of equipment (radiators) which requires heating.
- one of type `sensor` named by default `sensor.nb_device_active_for_boiler`, gives the number of devices requiring heating. For example, a VTherm having 4 valves including 3 heating requests will increase this sensor to 3. Only VTherm equipment that is marked to control the central boiler is counted.
You therefore always have the information which allows you to control and adjust the activation of the boiler.
All these entities are attached to the central configuration service:
![The entities controlling the boiler](/images/entitites-central-boiler.png?raw=true)
### Setup
To configure this function, you must have a centralized configuration (see [Configuration](#configuration)) and check the 'Add a central boiler' box:
![Adding a central boiler](/images/config-central-boiler-1.png?raw=true)
On the following page you can configure the services to be called when switching the boiler on/off:
![Adding a central boiler](/images/config-central-boiler-2.png?raw=true)
The services are configured as indicated on the page:
1. the general format is `entity_id/service_id[/attribute:value]` (where `/attribute:value` is optional),
2. `entity_id` is the name of the entity that controls the boiler in the form `domain.entity_name`. For example: `switch.boiler` for boilers controlled by a switch or `climate.boiler` for a boiler controlled by a thermostat or any other entity which allows control of the boiler (there is no limitation). We can also switch inputs (`helpers`) like `input_boolean` or `input_number`.
3. `service_id` is the name of the service to call in the form `domain.service_name`. For example: `switch.turn_on`, `switch.turn_off`, `climate.set_temperature`, `climate.set_hvac_mode` are valid examples.
4. For some service you will need a parameter. This can be the 'HVAC Mode' `climate.set_hvac_mode` or the target temperature for `climate.set_temperature`. This parameter must be configured in the form `attribute:value` at the end of the string.
Examples (to be adjusted to your case):
- `climate.chaudiere/climate.set_hvac_mode/hvac_mode:heat`: to turn on the boiler thermostat in heating mode,
- `climate.chaudiere/climate.set_hvac_mode/hvac_mode:off`: to stop the boiler thermostat,
- `switch.pompe_chaudiere/switch.turn_on`: to turn on the switch which powers the boiler pump,
- `switch.pompe_chaudiere/switch.turn_off`: to turn on the switch which powers the boiler pump,
- ...
### How to find the right service?
To find the service to use, the best is to go to "Development tools / Services", look for the service called, the entity to order and the possible parameter to give.
Click on 'Call Service'. If your boiler lights up you have the correct configuration. Then switch to Yaml mode and copy the parameters.
Example:
Under "Development Tools / Service":
![Service configuration](/images/dev-tools-turnon-boiler-1.png?raw=true)
In yaml mode:
![Service configuration](/images/dev-tools-turnon-boiler-2.png?raw=true)
The service to configure is then the following: `climate.empty_thermostast/climate.set_hvac_mode/hvac_mode:heat` (note the removal of the blank in `hvac_mode:heat`)
Then do the same for the extinguishing service and you are all set.
### The events
Each time the boiler is successfully switched on or off, an event is sent by Versatile Thermostat. It can advantageously be captured by automation, for example to notify a change.
The events look like this:
An ignition event:
```
event_type: versatile_thermostat_central_boiler_event
data:
central_boiler: true
entity_id: binary_sensor.central_boiler
name: Central boiler
state_attributes: null
origin: LOCAL
time_fired: "2024-01-14T11:33:52.342026+00:00"
context:
id: 01HM3VZRJP3WYYWPNSDAFARW1T
parent_id: null
user_id: null
```
An extinction event:
```
event_type: versatile_thermostat_central_boiler_event
data:
central_boiler: false
entity_id: binary_sensor.central_boiler
name: Central boiler
state_attributes: null
origin: LOCAL
time_fired: "2024-01-14T11:43:52.342026+00:00"
context:
id: 01HM3VZRJP3WYYWPNSDAFBRW1T
parent_id: null
user_id: null
```
### Warning
> ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*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.
## Parameters synthesis ## Parameters synthesis
| Paramètre | Libellé | "over switch" | "over climate" | "over valve" | | Paramètre | Libellé | "over switch" | "over climate" | "over valve" | "central configuration" |
| ----------------------------------------- | ----------------------------------------------------------------------------- | ------------- | ------------------- | ------------ | | ----------------------------------------- | ----------------------------------------------------------------------------- | ------------- | ------------------- | ------------ | ----------------------- |
| ``name`` | Name | X | X | X | | ``name`` | Name | X | X | X | - |
| ``thermostat_type`` | Thermostat type | X | X | X | | ``thermostat_type`` | Thermostat type | X | X | X | - |
| ``temperature_sensor_entity_id`` | Temperature sensor entity id | X | X (self-regulation) | X | | ``temperature_sensor_entity_id`` | Temperature sensor entity id | X | X (self-regulation) | X | - |
| ``external_temperature_sensor_entity_id`` | External temperature sensor entity id | X | X (self-regulation) | X | | ``external_temperature_sensor_entity_id`` | External temperature sensor entity id | X | X (self-regulation) | X | X |
| ``cycle_min`` | Cycle duration (minutes) | X | X | X | | ``cycle_min`` | Cycle duration (minutes) | X | X | X | - |
| ``temp_min`` | Minimal temperature allowed | X | X | X | | ``temp_min`` | Minimal temperature allowed | X | X | X | X |
| ``temp_max`` | Maximal temperature allowed | X | X | X | | ``temp_max`` | Maximal temperature allowed | X | X | X | X |
| ``device_power`` | Device power | X | X | X | | ``device_power`` | Total device power | X | X | X | - |
| ``use_window_feature`` | Use window detection | X | X | X | | ``use_central_mode`` | Allow the centralized control | X | X | X | - |
| ``use_motion_feature`` | Use motion detection | X | X | X | | ``use_window_feature`` | Use window detection | X | X | X | - |
| ``use_power_feature`` | Use power management | X | X | X | | ``use_motion_feature`` | Use motion detection | X | X | X | - |
| ``use_presence_feature`` | Use presence detection | X | X | X | | ``use_power_feature`` | Use power management | X | X | X | - |
| ``heater_entity1_id`` | 1rst heater switch | X | - | - | | ``use_presence_feature`` | Use presence detection | X | X | X | - |
| ``heater_entity2_id`` | 2nd heater switch | X | - | - | | ``heater_entity1_id`` | 1rst heater switch | X | - | - | - |
| ``heater_entity3_id`` | 3rd heater switch | X | - | - | | ``heater_entity2_id`` | 2nd heater switch | X | - | - | - |
| ``heater_entity4_id`` | 4th heater switch | X | - | - | | ``heater_entity3_id`` | 3rd heater switch | X | - | - | - |
| ``proportional_function`` | Algorithm | X | - | X | | ``heater_entity4_id`` | 4th heater switch | X | - | - | - |
| ``climate_entity1_id`` | 1rst underlying climate | - | X | - | | ``proportional_function`` | Algorithm | X | - | X | - |
| ``climate_entity2_id`` | 2nd underlying climate | - | X | - | | ``climate_entity1_id`` | 1rst underlying climate | - | X | - | - |
| ``climate_entity3_id`` | 3rd underlying climate | - | X | - | | ``climate_entity2_id`` | 2nd underlying climate | - | X | - | - |
| ``climate_entity4_id`` | 4th underlying climate | - | X | - | | ``climate_entity3_id`` | 3rd underlying climate | - | X | - | - |
| ``valve_entity1_id`` | 1rst underlying valve | - | - | X | | ``climate_entity4_id`` | 4th underlying climate | - | X | - | - |
| ``valve_entity2_id`` | 2nd underlying valve | - | - | X | | ``valve_entity1_id`` | 1rst underlying valve | - | - | X | - |
| ``valve_entity3_id`` | 3rd underlying valve | - | - | X | | ``valve_entity2_id`` | 2nd underlying valve | - | - | X | - |
| ``valve_entity4_id`` | 4th underlying valve | - | - | X | | ``valve_entity3_id`` | 3rd underlying valve | - | - | X | - |
| ``ac_mode`` | Use the Air Conditioning (AC) mode | X | X | X | | ``valve_entity4_id`` | 4th underlying valve | - | - | X | - |
| ``tpi_coef_int`` | Coefficient to use for internal temperature delta | X | - | X | | ``ac_mode`` | Use the Air Conditioning (AC) mode | X | X | X | - |
| ``tpi_coef_ext`` | Coefficient to use for external temperature delta | X | - | X | | ``tpi_coef_int`` | Coefficient to use for internal temperature delta | X | - | X | X |
| ``eco_temp`` | Temperature in Eco preset | X | X | X | | ``tpi_coef_ext`` | Coefficient to use for external temperature delta | X | - | X | X |
| ``comfort_temp`` | Temperature in Comfort preset | X | X | X | | ``frost_temp`` | Temperature in frost protection preset | X | X | X | X |
| ``boost_temp`` | Temperature in Boost preset | X | X | X | | ``eco_temp`` | Temperature in Eco preset | X | X | X | X |
| ``eco_ac_temp`` | Temperature in Eco preset for AC mode | X | X | X | | ``comfort_temp`` | Temperature in Comfort preset | X | X | X | X |
| ``comfort_ac_temp`` | Temperature in Comfort preset for AC mode | X | X | X | | ``boost_temp`` | Temperature in Boost preset | X | X | X | X |
| ``boost_ac_temp`` | Temperature in Boost preset for AC mode | X | X | X | | ``eco_ac_temp`` | Temperature in Eco preset for AC mode | X | X | X | X |
| ``window_sensor_entity_id`` | Window sensor entity id | X | X | X | | ``comfort_ac_temp`` | Temperature in Comfort preset for AC mode | X | X | X | X |
| ``window_delay`` | Window sensor delay (seconds) | X | X | X | | ``boost_ac_temp`` | Temperature in Boost preset for AC mode | X | X | X | X |
| ``window_auto_open_threshold`` | Temperature decrease threshold for automatic window open detection (in °/min) | X | X | X | | ``window_sensor_entity_id`` | Window sensor entity id | X | X | X | - |
| ``window_auto_close_threshold`` | Temperature increase threshold for end of automatic detection (in °/min) | X | X | X | | ``window_delay`` | Window sensor delay (seconds) | X | X | X | X |
| ``window_auto_max_duration`` | Maximum duration of automatic window open detection (in min) | X | X | X | | ``window_auto_open_threshold`` | Temperature decrease threshold for automatic window open detection (in °/min) | X | X | X | X |
| ``motion_sensor_entity_id`` | Motion sensor entity id | X | X | X | | ``window_auto_close_threshold`` | Temperature increase threshold for end of automatic detection (in °/min) | X | X | X | X |
| ``motion_delay`` | Delay before considering the motion (seconds) | X | X | X | | ``window_auto_max_duration`` | Maximum duration of automatic window open detection (in min) | X | X | X | X |
| ``motion_off_delay`` | Delay before considering the end of motion (seconds) | X | X | X | | ``motion_sensor_entity_id`` | Motion sensor entity id | X | X | X | - |
| ``motion_preset`` | Preset to use when motion is detected | X | X | X | | ``motion_delay`` | Delay before considering the motion (seconds) | X | X | X | X |
| ``no_motion_preset`` | Preset to use when no motion is detected | X | X | X | | ``motion_off_delay`` | Delay before considering the end of motion (seconds) | X | X | X | X |
| ``power_sensor_entity_id`` | Power sensor entity id | X | X | X | | ``motion_preset`` | Preset to use when motion is detected | X | X | X | X |
| ``max_power_sensor_entity_id`` | Max power sensor entity id | X | X | X | | ``no_motion_preset`` | Preset to use when no motion is detected | X | X | X | X |
| ``power_temp`` | Temperature for Power shedding | X | X | X | | ``power_sensor_entity_id`` | Power sensor entity id | X | X | X | X |
| ``presence_sensor_entity_id`` | Presence sensor entity id | X | X | X | | ``max_power_sensor_entity_id`` | Max power sensor entity id | X | X | X | X |
| ``eco_away_temp`` | Temperature in Eco preset when no presence | X | X | X | | ``power_temp`` | Temperature for Power shedding | X | X | X | X |
| ``comfort_away_temp`` | Temperature in Comfort preset when no presence | X | X | X | | ``presence_sensor_entity_id`` | Presence sensor entity id | X | X | X | X |
| ``boost_away_temp`` | Temperature in Boost preset when no presence | X | X | X | | ``frost_away_temp`` | Temperature in Frost protection preset when no presence | X | X | X | X |
| ``eco_ac_away_temp`` | Temperature in Eco preset when no presence in AC mode | X | X | X | | ``eco_away_temp`` | Temperature in Eco preset when no presence | X | X | X | X |
| ``comfort_ac_away_temp`` | Temperature in Comfort preset when no presence in AC mode | X | X | X | | ``comfort_away_temp`` | Temperature in Comfort preset when no presence | X | X | X | X |
| ``boost_ac_away_temp`` | Temperature in Boost preset when no presence in AC mode | X | X | X | | ``boost_away_temp`` | Temperature in Boost preset when no presence | X | X | X | X |
| ``minimal_activation_delay`` | Minimal activation delay | X | - | X | | ``eco_ac_away_temp`` | Temperature in Eco preset when no presence in AC mode | X | X | X | X |
| ``security_delay_min`` | Security delay (in minutes) | X | - | X | | ``comfort_ac_away_temp`` | Temperature in Comfort preset when no presence in AC mode | X | X | X | X |
| ``security_min_on_percent`` | Minimal power percent to enable security mode | X | - | X | | ``boost_ac_away_temp`` | Temperature in Boost preset when no presence in AC mode | X | X | X | X |
| ``security_default_on_percent`` | Power percent to use in security mode | X | - | X | | ``minimal_activation_delay`` | Minimal activation delay | X | - | X | X |
| ``auto_regulation_mode`` | Le mode d'auto-régulation | - | X | - | | ``security_delay_min`` | Safety delay (in minutes) | X | - | X | X |
| ``auto_regulation_dtemp`` | La seuil d'auto-régulation | - | X | - | | ``security_min_on_percent`` | Minimal power percent to enable safety mode | X | - | X | X |
| ``auto_regulation_period_min`` | La période minimale d'auto-régulation | - | X | - | | ``security_default_on_percent`` | Power percent to use in safety mode | X | - | X | X |
| ``inverse_switch_command`` | Inverse the switch command (for pilot wire switch) | X | - | - | | ``auto_regulation_mode`` | Le mode d'auto-régulation | - | X | - | - |
| ``auto_regulation_dtemp`` | La seuil d'auto-régulation | - | X | - | - |
| ``auto_regulation_period_min`` | La période minimale d'auto-régulation | - | X | - | - |
| ``inverse_switch_command`` | Inverse the switch command (for pilot wire switch) | X | - | - | - |
| ``auto_fan_mode`` | Auto fan mode | - | X | - | - |
| ``add_central_boiler_control`` | Add the control of a central boiler | - | - | - | X |
| ``central_boiler_activation_service`` | Activation 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 | - |
# Examples tuning # Examples tuning
@@ -560,8 +742,8 @@ See [example tuning](#examples-tuning) for common tuning examples
## Temperature sensor will battery ## Temperature sensor will battery
- security_delay_min: 60 min (because these sensors are lazy) - security_delay_min: 60 min (because these sensors are lazy)
- security_min_on_percent: 0.5 (50% - we go to the ``security`` preset if the radiator was heating more than 50% of the time) - security_min_on_percent: 0.5 (50% - we go to the ``safety`` preset if the radiator was heating more than 50% of the time)
- security_default_on_percent: 0.1 (10% - in preset ``security``, we keep a heating background 20% of the time) - security_default_on_percent: 0.1 (10% - in preset ``safety``, we keep a heating background 20% of the time)
These settings should be understood as follows: These settings should be understood as follows:
@@ -575,16 +757,18 @@ Versatile Thermostat allows you to be notified when an event of this type occurs
## Reactive temperature sensor (on mains) ## Reactive temperature sensor (on mains)
- security_delay_min: 15min - security_delay_min: 15min
- security_min_on_percent: 0.7 (70% - we go to the ``security`` preset if the radiator was heating more than 70% of the time) - security_min_on_percent: 0.7 (70% - we go to the ``safety`` preset if the radiator was heating more than 70% of the time)
- security_default_on_percent: 0.25 (25% - in preset ``security``, we keep a heating background 25% of the time) - security_default_on_percent: 0.25 (25% - in preset ``safety``, we keep a heating background 25% of the time)
## My preset configuration ## My preset configuration
This is just an example of how I use the preset. It up to you to adapt to your configuration but it can be useful to understand how it works. This is just an example of how I use the preset. It up to you to adapt to your configuration but it can be useful to understand how it works.
``Frost``: 10 °C
``Eco``: 17 °C ``Eco``: 17 °C
``Comfort``: 19 °C ``Comfort``: 19 °C
``Boost``: 20 °C ``Boost``: 20 °C
When presence if off: When presence if off:
``Frost``: 10 °C
``Eco``: 16.5 °C ``Eco``: 16.5 °C
``Comfort``: 17 °C ``Comfort``: 17 °C
``Boost``: 18 °C ``Boost``: 18 °C
@@ -633,7 +817,7 @@ In order, there are:
8. load shedding status, 8. load shedding status,
9. cycle power percentage (TPI only), 9. cycle power percentage (TPI only),
10. presence status (if presence management is configured), 10. presence status (if presence management is configured),
11. security status, 11. safety status,
12. opening status (if opening management is configured), 12. opening status (if opening management is configured),
13. motion status (if motion management is configured), 13. motion status (if motion management is configured),
14. the valve opening percentage (for the `over_valve` type) 14. the valve opening percentage (for the `over_valve` type)
@@ -702,11 +886,11 @@ target:
> ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_ > ![Tip](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/tips.png?raw=true) _*Notes*_
- after a restart the preset are resetted to the configured temperature. If you want your change to be permanent you should modify the temperature preset into the confguration of the integration. - after a restart the preset are resetted to the configured temperature. If you want your change to be permanent you should modify the temperature preset into the confguration of the integration.
## Change security settings ## Change safety settings
This service is used to dynamically modify the security parameters described here [Advanced configuration](#configuration-avanced). This service is used to dynamically modify the safety parameters described here [Advanced configuration](#configuration-avanced).
If the thermostat is in ``security`` mode the new settings are applied immediately. If the thermostat is in ``safety`` mode the new settings are applied immediately.
To change the security settings use the following code: To change the safety settings use the following code:
``` ```
service : thermostat_polyvalent.set_security service : thermostat_polyvalent.set_security
data: data:
@@ -733,15 +917,16 @@ target:
Significant thermostat events are notified via the message bus. Significant thermostat events are notified via the message bus.
The notified events are as follows: The notified events are as follows:
- ``versatile_thermostat_security_event``: a thermostat enters or exits the ``security`` preset - ``versatile_thermostat_security_event``: a thermostat enters or exits the ``safety`` preset
- ``versatile_thermostat_power_event``: a thermostat enters or exits the ``power`` preset - ``versatile_thermostat_power_event``: a thermostat enters or exits the ``power`` preset
- ``versatile_thermostat_temperature_event``: one or both temperature measurements of a thermostat have not been updated for more than ``security_delay_min`` minutes - ``versatile_thermostat_temperature_event``: one or both temperature measurements of a thermostat have not been updated for more than ``security_delay_min`` minutes
- ``versatile_thermostat_hvac_mode_event``: the thermostat is on or off. This event is also broadcast when the thermostat starts up - ``versatile_thermostat_hvac_mode_event``: the thermostat is on or off. This event is also broadcast when the thermostat starts up
- ``versatile_thermostat_preset_event``: a new preset is selected on the thermostat. This event is also broadcast when the thermostat starts up - ``versatile_thermostat_preset_event``: a new preset is selected on the thermostat. This event is also broadcast when the thermostat starts up
- ``versatile_thermostat_central_boiler_event``: an event indicating a change in the state of the central boiler.
If you have followed correctly, when a thermostat goes into safety mode, 3 events are triggered: If you have followed correctly, when a thermostat goes into safety mode, 3 events are triggered:
1. ``versatile_thermostat_temperature_event`` to indicate that a thermometer has become unresponsive, 1. ``versatile_thermostat_temperature_event`` to indicate that a thermometer has become unresponsive,
2. ``versatile_thermostat_preset_event`` to indicate the switch to ```security``` preset, 2. ``versatile_thermostat_preset_event`` to indicate the switch to ```safety``` preset,
3. ``versatile_thermostat_hvac_mode_event`` to indicate the possible extinction of the thermostat 3. ``versatile_thermostat_hvac_mode_event`` to indicate the possible extinction of the thermostat
Each event carries the key values of the event (temperatures, current preset, current power, etc.) as well as the states of the thermostat. Each event carries the key values of the event (temperatures, current preset, current power, etc.) as well as the states of the thermostat.
@@ -782,12 +967,12 @@ Custom attributes are the following:
| ``motion_state`` | The last known state of the motion sensor. None if motion is not configured | | ``motion_state`` | The last known state of the motion sensor. None if motion is not configured |
| ``overpowering_state`` | The last known state of the overpowering sensor. None if power management is not configured | | ``overpowering_state`` | The last known state of the overpowering sensor. None if power management is not configured |
| ``presence_state`` | The last known state of the presence sensor. None if presence management is not configured | | ``presence_state`` | The last known state of the presence sensor. None if presence management is not configured |
| ``security_delay_min`` | The delay before setting the security mode when temperature sensor are off | | ``security_delay_min`` | The delay before setting the safety mode when temperature sensor are off |
| ``security_min_on_percent`` | The minimal on_percent below which security preset won't be trigger | | ``security_min_on_percent`` | The minimal on_percent below which safety preset won't be trigger |
| ``security_default_on_percent`` | The on_percent used when thermostat is in ``security`` | | ``security_default_on_percent`` | The on_percent used when thermostat is in ``safety`` |
| ``last_temperature_datetime`` | The date and time in ISO8866 format of the last internal temperature reception | | ``last_temperature_datetime`` | The date and time in ISO8866 format of the last internal temperature reception |
| ``last_ext_temperature_datetime`` | The date and time in ISO8866 format of the last external temperature reception | | ``last_ext_temperature_datetime`` | The date and time in ISO8866 format of the last external temperature reception |
| ``security_state`` | The security state. true or false | | ``security_state`` | The safety state. true or false |
| ``minimal_activation_delay_sec`` | The minimal activation delay in seconds | | ``minimal_activation_delay_sec`` | The minimal activation delay in seconds |
| ``last_update_datetime`` | The date and time in ISO8866 format of this state | | ``last_update_datetime`` | The date and time in ISO8866 format of this state |
| ``friendly_name`` | The name of the thermostat | | ``friendly_name`` | The name of the thermostat |
@@ -795,6 +980,9 @@ Custom attributes are the following:
| ``valve_open_percent`` | The opening percentage of the valve | | ``valve_open_percent`` | The opening percentage of the valve |
| ``regulated_target_temperature`` | The self-regulated target temperature calculated | | ``regulated_target_temperature`` | The self-regulated target temperature calculated |
| ``is_inversed`` | True if the command is inversed (pilot wire with diode) | | ``is_inversed`` | True if the command is inversed (pilot wire with diode) |
| ``is_controlled_by_central_mode`` | True if the VTherm can be centrally controlled |
| ``last_central_mode`` | The last central mode used (None if the VTherm is not centrally controlled) |
| ``is_used_by_central_boiler`` | Indicate if the VTherm can control the central boiler |
# Some results # Some results
@@ -967,6 +1155,11 @@ Replace values in [[ ]] by yours.
step: day step: day
``` ```
Example of graph obtained with Plotly :
![image](/images/plotly-curves.png?raw=true)
## 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.
@@ -1112,7 +1305,19 @@ Example :
## Only the first radiator heats ## Only the first radiator heats
In `over_switch` mode if several radiators are configured for the same VTherm, switching on will be done sequentially to smooth out consumption peaks as much as possible. In `over_switch` mode if several radiators are configured for the same VTherm, switching on will be done sequentially to smooth out consumption peaks as much as possible.
This is completely normal and desired. It is described here: [For a thermostat of type ``thermostat_over_switch```](#for-a-thermostat-of-type-thermostat_over_switch) This is completely normal and desired. It is described here: [For a thermostat of type ``thermostat_over_switch```](#for-a-thermostat-of-type-thermostat_over_switch)v
## The radiator heats up even though the setpoint temperature is exceeded or does not heat up even though the room temperature is well below the setpoint
### Type `over_switch` or `over_valve`
With a VTherm of type `over_switch` or `over_valve`, this fault just shows that the parameters of the TPI algorithm are incorrectly set. See [TPI Algorithm](#tpi-algorithm) to optimize the settings.
### Type `over_climate`
With an `over_climate` type VTherm, the regulation is done by the underlying `climate` directly and VTherm simply transmits the instructions to it. So if the radiator heats up when the set temperature is exceeded, it is certainly because its internal temperature measurement is biased. This happens very often with TRVs and reversible air conditioning units which have an internal temperature sensor, or too close to the heating element (therefore too cold in winter).
Example of discussion around these topics: [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348), [#316](https://github.com/jmcollin78/versatile_thermostat/issues/316), [#312](https://github.com/jmcollin78/versatile_thermostat/discussions/312 ), [#278](https://github.com/jmcollin78/versatile_thermostat/discussions/278)
To get around this, VTherm is equipped with a function called self-regulation which allows the instruction sent to the underlying to be adapted until the target temperature is respected. This function compensates for the measurement bias of internal thermometers. If the bias is important the regulation must be important. See [Self-regulation](#self-regulation) to configure self-regulation.
## Adjust window opening detection parameters in auto mode ## Adjust window opening detection parameters in auto mode
@@ -1141,15 +1346,15 @@ As the algorithm is based on temperature measurements, if they are no longer rec
All these parameters are adjusted on the last page of the VTherm configuration: “Advanced parameters”. All these parameters are adjusted on the last page of the VTherm configuration: “Advanced parameters”.
### How to detect security mode? ### How to detect safety mode?
The first symptom is an abnormally low temperature with a slow and regular heating time in each cycle. The first symptom is an abnormally low temperature with a slow and regular heating time in each cycle.
Example: Example:
[security mode](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/security-mode-symptome1.png?raw=true) [safety mode](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/security-mode-symptome1.png?raw=true)
If you installed the [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card), the VTherm in question will have this shape: If you installed the [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card), the VTherm in question will have this shape:
[security mode UI Card](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/security-mode-symptome2.png?raw=true) [safety mode UI Card](https://github.com/jmcollin78/versatile_thermostat/blob/main/images/security-mode-symptome2.png?raw=true)
You can also check in the VTherm attributes the dates of receipt of the different dates. Attributes are available in Development Tools / Reports. You can also check in the VTherm attributes the dates of receipt of the different dates. Attributes are available in Development Tools / Reports.
@@ -1164,13 +1369,13 @@ security_delay_min: 60
``` ```
We see that : We see that :
1. the VTherm is in security mode (`security_state: true`), 1. the VTherm is in safety mode (`security_state: true`),
2. the current time is 06/12/2023 at 18:43:28 (`last_update_datetime: "2023-12-06T18:43:28.351103+01:00"`), 2. the current time is 06/12/2023 at 18:43:28 (`last_update_datetime: "2023-12-06T18:43:28.351103+01:00"`),
3. the last received time of indoor temperature is 06/12/2023 at 18:43:28 (`last_temperature_datetime: "2023-12-06T18:43:28.346010+01:00"`). So she's on time, 3. the last received time of indoor temperature is 06/12/2023 at 18:43:28 (`last_temperature_datetime: "2023-12-06T18:43:28.346010+01:00"`). So she's on time,
4. the last reception time of the outdoor temperature is 06/12/2023 at 1:04:35 p.m. (`last_ext_temperature_datetime: "2023-12-06T13:04:35.164367+01:00`). external time which is more than 5 hours late and which caused the switch to security mode, because the threshold is limited to 60 min (`security_delay_min: 60`) 4. the last reception time of the outdoor temperature is 06/12/2023 at 1:04:35 p.m. (`last_ext_temperature_datetime: "2023-12-06T13:04:35.164367+01:00`). external time which is more than 5 hours late and which caused the switch to safety mode, because the threshold is limited to 60 min (`security_delay_min: 60`)
### How can I be notified when this happens? ### How can I be notified when this happens?
To be notified, the VTherm sends an event as soon as it happens and one at the end of the security alert. You can capture these events in an automation and send a notification for example, flash a light, trigger a siren, etc. It's up to you. To be notified, the VTherm sends an event as soon as it happens and one at the end of the safety alert. You can capture these events in an automation and send a notification for example, flash a light, trigger a siren, etc. It's up to you.
To manipulate the events generated by VTherm, cf. [Events](#events). To manipulate the events generated by VTherm, cf. [Events](#events).
@@ -1179,8 +1384,29 @@ This will depend on the cause of the problem:
1. If a sensor is faulty, it must be repaired (replace batteries, change it, check the Weather integration which gives the outside temperature, etc.), 1. If a sensor is faulty, it must be repaired (replace batteries, change it, check the Weather integration which gives the outside temperature, etc.),
2. If the `security_delay_min` parameter is too small, it risks generating a lot of false alerts. A correct value is around 60 min, especially if you have battery-powered temperature sensors. 2. If the `security_delay_min` parameter is too small, it risks generating a lot of false alerts. A correct value is around 60 min, especially if you have battery-powered temperature sensors.
3. Some temperature sensors do not send a measurement if the temperature has not changed. So in the event of a very stable temperature for a long time, the safety mode may be triggered. This is not very serious since it is removed as soon as the VTherm receives a temperature again. On certain thermometers (TuYA for example), you can force the maximum delay between 2 measurements. It will be appropriate to set a max delay < `security_delay_min`, 3. Some temperature sensors do not send a measurement if the temperature has not changed. So in the event of a very stable temperature for a long time, the safety mode may be triggered. This is not very serious since it is removed as soon as the VTherm receives a temperature again. On certain thermometers (TuYA for example), you can force the maximum delay between 2 measurements. It will be appropriate to set a max delay < `security_delay_min`,
4. As soon as the temperature is received again the security mode will be removed and the previous values of preset, target temperature and mode will be restored. 4. As soon as the temperature is received again the safety mode will be removed and the previous values of preset, target temperature and mode will be restored.
## Using a group of people as a presence sensor
Unfortunately, groups of people are not recognized as presence sensors. We cannot therefore use them directly in VTherm.
The workaround is to create a binary_sensor template with the following code:
`template.yaml` file:
```
- binary_sensor:
- name: occupied_house
unique_id: occupied_house
state: "{{is_state('person.person1', 'home') or is_state('person.person2', 'home') or is_state('input_boolean.force_presence', 'on')}}"
device_class: occupancy
```
You will note in this example, the use of an input_boolean named force_presence which allows you to force the sensor to `True` and thus force the VTherm which uses it with active presence. This allows, for example, to force pre-heating of the home when leaving work, or when a person not natively recognized in HA is present.
`configuration.yaml` file:
```
...
template: !include templates.yaml
...
```
*** ***

View File

@@ -43,4 +43,13 @@ case $1 in
pwd pwd
./scripts/starts_ha.sh ./scripts/starts_ha.sh
;; ;;
coverage)
rm -rf htmlcov/*
echo "Starting coverage tests"
coverage run -m pytest tests/
echo "Starting coverage report"
coverage report
echo "Starting coverage html"
coverage html
;;
esac esac

View File

@@ -3,10 +3,13 @@ from __future__ import annotations
from typing import Dict from typing import Dict
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.const import SERVICE_RELOAD
from homeassistant.config_entries import ConfigEntry, ConfigType from homeassistant.config_entries import ConfigEntry, ConfigType
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@@ -21,6 +24,9 @@ from .const import (
CONF_AUTO_REGULATION_SLOW, CONF_AUTO_REGULATION_SLOW,
CONF_AUTO_REGULATION_EXPERT, CONF_AUTO_REGULATION_EXPERT,
CONF_SHORT_EMA_PARAMS, CONF_SHORT_EMA_PARAMS,
CONF_SAFETY_MODE,
CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_THERMOSTAT_TYPE,
) )
from .vtherm_api import VersatileThermostatAPI from .vtherm_api import VersatileThermostatAPI
@@ -42,12 +48,17 @@ EMA_PARAM_SCHEMA = {
vol.Required("precision"): cv.positive_int, vol.Required("precision"): cv.positive_int,
} }
SAFETY_MODE_PARAM_SCHEMA = {
vol.Required("check_outdoor_sensor"): bool,
}
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{ {
DOMAIN: vol.Schema( DOMAIN: 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),
} }
), ),
}, },
@@ -65,6 +76,10 @@ async def async_setup(
config.get(DOMAIN), config.get(DOMAIN),
) )
async def _handle_reload(_):
"""The reload callback"""
await reload_all_vtherm(hass)
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})
# L'argument config contient votre fichier configuration.yaml # L'argument config contient votre fichier configuration.yaml
@@ -76,9 +91,31 @@ async def async_setup(
else: else:
_LOGGER.info("No global config from configuration.yaml available") _LOGGER.info("No global config from configuration.yaml available")
hass.helpers.service.async_register_admin_service(
DOMAIN,
SERVICE_RELOAD,
_handle_reload,
)
return True return True
async def reload_all_vtherm(hass):
"""Handle reload service call."""
_LOGGER.info("Service %s.reload called: reloading integration", DOMAIN)
current_entries = hass.config_entries.async_entries(DOMAIN)
reload_tasks = [
hass.config_entries.async_reload(entry.entry_id) for entry in current_entries
]
await asyncio.gather(*reload_tasks)
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass)
if api:
await api.reload_central_boiler_entities_list()
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Versatile Thermostat from a config entry.""" """Set up Versatile Thermostat from a config entry."""
@@ -96,12 +133,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
await api.reload_central_boiler_entities_list()
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."""
await hass.config_entries.async_reload(entry.entry_id) if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CENTRAL_CONFIG:
await reload_all_vtherm(hass)
else:
await hass.config_entries.async_reload(entry.entry_id)
# Reload the central boiler list of entities
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass)
if api is not None:
await api.reload_central_boiler_entities_list()
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@@ -111,6 +157,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
if api: if api:
api.remove_entry(entry) api.remove_entry(entry)
await api.reload_central_boiler_entities_list()
return unload_ok return unload_ok

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,20 @@
""" Implements the VersatileThermostat binary sensors component """ """ Implements the VersatileThermostat binary sensors component """
# pylint: disable=unused-argument, line-too-long
import logging import logging
from homeassistant.core import HomeAssistant, callback, Event from homeassistant.core import (
HomeAssistant,
callback,
Event,
CoreState,
HomeAssistantError,
)
from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.const import STATE_ON, STATE_OFF, EVENT_HOMEASSISTANT_START
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorEntity, BinarySensorEntity,
@@ -13,13 +24,26 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .commons import VersatileThermostatBaseEntity from .vtherm_api import VersatileThermostatAPI
from .commons import (
VersatileThermostatBaseEntity,
check_and_extract_service_configuration,
)
from .const import ( from .const import (
DOMAIN,
DEVICE_MANUFACTURER,
CONF_NAME, CONF_NAME,
CONF_USE_POWER_FEATURE, CONF_USE_POWER_FEATURE,
CONF_USE_PRESENCE_FEATURE, CONF_USE_PRESENCE_FEATURE,
CONF_USE_MOTION_FEATURE, CONF_USE_MOTION_FEATURE,
CONF_USE_WINDOW_FEATURE, CONF_USE_WINDOW_FEATURE,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_CENTRAL_BOILER_ACTIVATION_SRV,
CONF_CENTRAL_BOILER_DEACTIVATION_SRV,
overrides,
EventType,
send_vtherm_event,
) )
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -37,16 +61,25 @@ 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)
entities = [SecurityBinarySensor(hass, unique_id, name, entry.data),WindowByPassBinarySensor(hass, unique_id, name, entry.data)] if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
if entry.data.get(CONF_USE_MOTION_FEATURE): entities = [
entities.append(MotionBinarySensor(hass, unique_id, name, entry.data)) CentralBoilerBinarySensor(hass, unique_id, name, entry.data),
if entry.data.get(CONF_USE_WINDOW_FEATURE): ]
entities.append(WindowBinarySensor(hass, unique_id, name, entry.data)) else:
if entry.data.get(CONF_USE_PRESENCE_FEATURE): entities = [
entities.append(PresenceBinarySensor(hass, unique_id, name, entry.data)) SecurityBinarySensor(hass, unique_id, name, entry.data),
if entry.data.get(CONF_USE_POWER_FEATURE): WindowByPassBinarySensor(hass, unique_id, name, entry.data),
entities.append(OverpoweringBinarySensor(hass, unique_id, name, entry.data)) ]
if entry.data.get(CONF_USE_MOTION_FEATURE):
entities.append(MotionBinarySensor(hass, unique_id, name, entry.data))
if entry.data.get(CONF_USE_WINDOW_FEATURE):
entities.append(WindowBinarySensor(hass, unique_id, name, entry.data))
if entry.data.get(CONF_USE_PRESENCE_FEATURE):
entities.append(PresenceBinarySensor(hass, unique_id, name, entry.data))
if entry.data.get(CONF_USE_POWER_FEATURE):
entities.append(OverpoweringBinarySensor(hass, unique_id, name, entry.data))
async_add_entities(entities, True) async_add_entities(entities, True)
@@ -55,8 +88,12 @@ class SecurityBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
"""Representation of a BinarySensor which exposes the security state""" """Representation of a BinarySensor which exposes the security state"""
def __init__( def __init__(
self, hass: HomeAssistant, unique_id, name, entry_infos self,
) -> None: # pylint: disable=unused-argument hass: HomeAssistant,
unique_id,
name, # pylint: disable=unused-argument
entry_infos,
) -> 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, entry_infos.get(CONF_NAME))
self._attr_name = "Security state" self._attr_name = "Security state"
@@ -90,8 +127,12 @@ class OverpoweringBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity
"""Representation of a BinarySensor which exposes the overpowering state""" """Representation of a BinarySensor which exposes the overpowering state"""
def __init__( def __init__(
self, hass: HomeAssistant, unique_id, name, entry_infos self,
) -> None: # pylint: disable=unused-argument hass: HomeAssistant,
unique_id,
name, # pylint: disable=unused-argument
entry_infos,
) -> None:
"""Initialize the OverpoweringState Binary sensor""" """Initialize the OverpoweringState Binary sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME)) super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Overpowering state" self._attr_name = "Overpowering state"
@@ -125,8 +166,12 @@ class WindowBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
"""Representation of a BinarySensor which exposes the window state""" """Representation of a BinarySensor which exposes the window state"""
def __init__( def __init__(
self, hass: HomeAssistant, unique_id, name, entry_infos self,
) -> None: # pylint: disable=unused-argument hass: HomeAssistant,
unique_id,
name, # pylint: disable=unused-argument
entry_infos,
) -> None:
"""Initialize the WindowState Binary sensor""" """Initialize the WindowState Binary sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME)) super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Window state" self._attr_name = "Window state"
@@ -171,8 +216,12 @@ class MotionBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
"""Representation of a BinarySensor which exposes the motion state""" """Representation of a BinarySensor which exposes the motion state"""
def __init__( def __init__(
self, hass: HomeAssistant, unique_id, name, entry_infos self,
) -> None: # pylint: disable=unused-argument hass: HomeAssistant,
unique_id,
name, # pylint: disable=unused-argument
entry_infos,
) -> None:
"""Initialize the MotionState Binary sensor""" """Initialize the MotionState Binary sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME)) super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Motion state" self._attr_name = "Motion state"
@@ -207,8 +256,12 @@ class PresenceBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
"""Representation of a BinarySensor which exposes the presence state""" """Representation of a BinarySensor which exposes the presence state"""
def __init__( def __init__(
self, hass: HomeAssistant, unique_id, name, entry_infos self,
) -> None: # pylint: disable=unused-argument hass: HomeAssistant,
unique_id,
name, # pylint: disable=unused-argument
entry_infos,
) -> None:
"""Initialize the PresenceState Binary sensor""" """Initialize the PresenceState Binary sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME)) super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Presence state" self._attr_name = "Presence state"
@@ -239,13 +292,17 @@ class PresenceBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
else: else:
return "mdi:nature-people" return "mdi:nature-people"
#PR - Adding Window ByPass
class WindowByPassBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity): class WindowByPassBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
"""Representation of a BinarySensor which exposes the Window ByPass state""" """Representation of a BinarySensor which exposes the Window ByPass state"""
def __init__( def __init__(
self, hass: HomeAssistant, unique_id, name, entry_infos self,
) -> None: # pylint: disable=unused-argument hass: HomeAssistant,
unique_id,
name, # pylint: disable=unused-argument
entry_infos,
) -> None:
"""Initialize the WindowByPass Binary sensor""" """Initialize the WindowByPass Binary sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME)) super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Window bypass" self._attr_name = "Window bypass"
@@ -272,4 +329,162 @@ class WindowByPassBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity
if self._attr_is_on: if self._attr_is_on:
return "mdi:window-shutter-cog" return "mdi:window-shutter-cog"
else: else:
return "mdi:window-shutter-auto" return "mdi:window-shutter-auto"
class CentralBoilerBinarySensor(BinarySensorEntity):
"""Representation of a BinarySensor which exposes the Central Boiler state"""
def __init__(
self,
hass: HomeAssistant,
unique_id,
name, # pylint: disable=unused-argument
entry_infos,
) -> None:
"""Initialize the CentralBoiler Binary sensor"""
self._config_id = unique_id
self._attr_name = "Central boiler"
self._attr_unique_id = "central_boiler_state"
self._attr_is_on = False
self._device_name = entry_infos.get(CONF_NAME)
self._entities = []
self._hass = hass
self._service_activate = check_and_extract_service_configuration(
entry_infos.get(CONF_CENTRAL_BOILER_ACTIVATION_SRV)
)
self._service_deactivate = check_and_extract_service_configuration(
entry_infos.get(CONF_CENTRAL_BOILER_DEACTIVATION_SRV)
)
@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
return DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, self._config_id)},
name=self._device_name,
manufacturer=DEVICE_MANUFACTURER,
model=DOMAIN,
)
@property
def device_class(self) -> BinarySensorDeviceClass | None:
return BinarySensorDeviceClass.RUNNING
@property
def icon(self) -> str | None:
if self._attr_is_on:
return "mdi:water-boiler"
else:
return "mdi:water-boiler-off"
@overrides
async def async_added_to_hass(self) -> None:
await super().async_added_to_hass()
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self._hass)
api.register_central_boiler(self)
@callback
async def _async_startup_internal(*_):
_LOGGER.debug("%s - Calling async_startup_internal", self)
await self.listen_nb_active_vtherm_entity()
if self.hass.state == CoreState.running:
await _async_startup_internal()
else:
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, _async_startup_internal
)
async def listen_nb_active_vtherm_entity(self):
"""Initialize the listening of state change of VTherms"""
# Listen to all VTherm state change
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self._hass)
if (
api.nb_active_device_for_boiler_entity
and api.nb_active_device_for_boiler_threshold_entity
):
listener_cancel = async_track_state_change_event(
self._hass,
[
api.nb_active_device_for_boiler_entity.entity_id,
api.nb_active_device_for_boiler_threshold_entity.entity_id,
],
self.calculate_central_boiler_state,
)
_LOGGER.debug(
"%s - entity to get the nb of active VTherm is %s",
self,
api.nb_active_device_for_boiler_entity.entity_id,
)
self.async_on_remove(listener_cancel)
else:
_LOGGER.debug("%s - no VTherm could controls the central boiler", self)
await self.calculate_central_boiler_state(None)
async def calculate_central_boiler_state(self, _):
"""Calculate the central boiler state depending on all VTherm that
controls this central boiler"""
_LOGGER.debug("%s - calculating the new central boiler state", self)
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self._hass)
if (
api.nb_active_device_for_boiler is None
or api.nb_active_device_for_boiler_threshold is None
):
_LOGGER.warning(
"%s - the entities to calculate the boiler state are not initialized. Boiler state cannot be calculated",
self,
)
return False
active = (
api.nb_active_device_for_boiler >= api.nb_active_device_for_boiler_threshold
)
if self._attr_is_on != active:
try:
if active:
await self.call_service(self._service_activate)
_LOGGER.info("%s - central boiler have been turned on", self)
else:
await self.call_service(self._service_deactivate)
_LOGGER.info("%s - central boiler have been turned off", self)
self._attr_is_on = active
send_vtherm_event(
hass=self._hass,
event_type=EventType.CENTRAL_BOILER_EVENT,
entity=self,
data={"central_boiler": active},
)
self.async_write_ha_state()
except HomeAssistantError as err:
_LOGGER.error(
"%s - Impossible to activate/deactivat boiler due to error %s."
"Central boiler will not being controled by VTherm."
"Please check your service configuration. Cf. README.",
self,
err,
)
async def call_service(self, service_config: dict):
"""Make a call to a service if correctly configured"""
if not service_config:
return
await self._hass.services.async_call(
service_config["service_domain"],
service_config["service_name"],
service_data=service_config["data"],
target={
"entity_id": service_config["entity_id"],
},
)
def __str__(self):
return f"VersatileThermostat-{self.name}"

View File

@@ -1,5 +1,3 @@
# pylint: disable=line-too-long
# pylint: disable=invalid-name
""" Implements the VersatileThermostat climate component """ """ Implements the VersatileThermostat climate component """
import logging import logging
@@ -37,6 +35,7 @@ from .const import (
CONF_THERMOSTAT_SWITCH, CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE, CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_VALVE, CONF_THERMOSTAT_VALVE,
CONF_THERMOSTAT_CENTRAL_CONFIG,
) )
from .thermostat_switch import ThermostatOverSwitch from .thermostat_switch import ThermostatOverSwitch
@@ -64,7 +63,11 @@ async def async_setup_entry(
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)
if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
return
# Instantiate the right base class # Instantiate the right base class
entity = None
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:

View File

@@ -1,4 +1,6 @@
""" Some usefull commons class """ """ Some usefull commons class """
# pylint: disable=line-too-long
import logging import logging
from datetime import timedelta, datetime from datetime import timedelta, datetime
from homeassistant.core import HomeAssistant, callback, Event from homeassistant.core import HomeAssistant, callback, Event
@@ -10,41 +12,148 @@ from homeassistant.helpers.event import async_track_state_change_event, async_ca
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from .base_thermostat import BaseThermostat from .base_thermostat import BaseThermostat
from .const import DOMAIN, DEVICE_MANUFACTURER 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"""
return dt_util.get_time_zone(hass.config.time_zone) return dt_util.get_time_zone(hass.config.time_zone)
class NowClass: class NowClass:
""" For testing purpose only""" """For testing purpose only"""
@staticmethod @staticmethod
def get_now(hass: HomeAssistant) -> datetime: def get_now(hass: HomeAssistant) -> datetime:
""" A test function to get the now. """A test function to get the now.
For testing purpose this method can be overriden to get a specific For testing purpose this method can be overriden to get a specific
timestamp. timestamp.
""" """
return datetime.now( get_tz(hass)) return datetime.now(get_tz(hass))
def round_to_nearest(n:float, x: float)->float:
""" Round a number to the nearest x (which should be decimal but not null)
Example:
nombre1 = 3.2
nombre2 = 4.7
x = 0.3
nombre_arrondi1 = round_to_nearest(nombre1, x) def round_to_nearest(n: float, x: float) -> float:
nombre_arrondi2 = round_to_nearest(nombre2, x) """Round a number to the nearest x (which should be decimal but not null)
Example:
nombre1 = 3.2
nombre2 = 4.7
x = 0.3
print(nombre_arrondi1) # Output: 3.3 nombre_arrondi1 = round_to_nearest(nombre1, x)
print(nombre_arrondi2) # Output: 4.6 nombre_arrondi2 = round_to_nearest(nombre2, x)
print(nombre_arrondi1) # Output: 3.3
print(nombre_arrondi2) # Output: 4.6
""" """
assert x > 0 assert x > 0
return round(n * (1/x)) / (1/x) return round(n * (1 / x)) / (1 / x)
def check_and_extract_service_configuration(service_config) -> dict:
"""Raise a ServiceConfigurationError. In return you have a dict formatted like follows.
Example if you call with 'climate.central_boiler/climate.set_temperature/temperature:10':
{
"service_domain": "climate",
"service_name": "set_temperature",
"entity_id": "climate.central_boiler",
"entity_domain": "climate",
"entity_name": "central_boiler",
"data": {
"temperature": "10"
},
"attribute_name": "temperature",
"attribute_value: "10"
}
For this example 'switch.central_boiler/switch.turn_off' you will have this:
{
"service_domain": "switch",
"service_name": "turn_off",
"entity_id": "switch.central_boiler",
"entity_domain": "switch",
"entity_name": "central_boiler",
"data": { },
}
All values are striped (white space are removed) and are string
"""
ret = {}
if service_config is None:
return ret
parties = service_config.split("/")
if len(parties) < 2:
raise ServiceConfigurationError(
f"Incorrect service configuration. Service {service_config} should be formatted with: 'entity_name/service_name[/data]'. See README for more information."
)
entity_id = parties[0]
service_name = parties[1]
service_infos = service_name.split(".")
if len(service_infos) != 2:
raise ServiceConfigurationError(
f"Incorrect service configuration. The service {service_config} should be formatted like: 'domain.service_name' (ex: 'switch.turn_on'). See README for more information."
)
ret.update(
{
"service_domain": service_infos[0].strip(),
"service_name": service_infos[1].strip(),
}
)
entity_infos = entity_id.split(".")
if len(entity_infos) != 2:
raise ServiceConfigurationError(
f"Incorrect service configuration. The entity_id {entity_id} should be formatted like: 'domain.entity_name' (ex: 'switch.central_boiler_switch'). See README for more information."
)
ret.update(
{
"entity_domain": entity_infos[0].strip(),
"entity_name": entity_infos[1].strip(),
"entity_id": entity_id.strip(),
}
)
if len(parties) == 3:
data = parties[2]
if len(data) > 0:
data_infos = None
data_infos = data.split(":")
if (
len(data_infos) != 2
or len(data_infos[0]) <= 0
or len(data_infos[1]) <= 0
):
raise ServiceConfigurationError(
f"Incorrect service configuration. The data {data} should be formatted like: 'attribute:value' (ex: 'value:25'). See README for more information."
)
ret.update(
{
"attribute_name": data_infos[0].strip(),
"attribute_value": data_infos[1].strip(),
"data": {data_infos[0].strip(): data_infos[1].strip()},
}
)
else:
raise ServiceConfigurationError(
f"Incorrect service configuration. The data {data} should be formatted like: 'attribute:value' (ex: 'value:25'). See README for more information."
)
else:
ret.update({"data": {}})
_LOGGER.debug(
"check_and_extract_service_configuration(%s) gives '%s'", service_config, ret
)
return ret
class VersatileThermostatBaseEntity(Entity): class VersatileThermostatBaseEntity(Entity):
"""A base class for all entities""" """A base class for all entities"""
@@ -130,7 +239,9 @@ class VersatileThermostatBaseEntity(Entity):
await try_find_climate(None) await try_find_climate(None)
@callback @callback
async def async_my_climate_changed(self, event: Event): # pylint: disable=unused-argument async def async_my_climate_changed(
self, event: Event
): # pylint: disable=unused-argument
"""Called when my climate have change """Called when my climate have change
This method aims to be overriden to take the status change This method aims to be overriden to take the status change
""" """

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,346 @@
""" All the schemas for ConfigFlow validation"""
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import selector
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
from homeassistant.components.input_boolean import (
DOMAIN as INPUT_BOOLEAN_DOMAIN,
)
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.input_number import (
DOMAIN as INPUT_NUMBER_DOMAIN,
)
from homeassistant.components.person import DOMAIN as PERSON_DOMAIN
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
STEP_USER_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(
CONF_THERMOSTAT_TYPE, default=CONF_THERMOSTAT_SWITCH
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=CONF_THERMOSTAT_TYPES,
translation_key="thermostat_type",
mode="list",
)
)
}
)
STEP_MAIN_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_TEMP_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SENSOR_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Required(CONF_CYCLE_MIN, default=5): cv.positive_int,
vol.Optional(CONF_DEVICE_POWER, default="1"): vol.Coerce(float),
vol.Optional(CONF_USE_CENTRAL_MODE, default=True): cv.boolean,
vol.Required(CONF_USE_MAIN_CENTRAL_CONFIG, default=True): cv.boolean,
vol.Optional(CONF_USE_WINDOW_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_MOTION_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_POWER_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_PRESENCE_FEATURE, default=False): cv.boolean,
vol.Required(CONF_USED_BY_CENTRAL_BOILER, default=False): cv.boolean,
}
)
STEP_CENTRAL_MAIN_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_EXTERNAL_TEMP_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SENSOR_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Required(CONF_TEMP_MIN, default=7): vol.Coerce(float),
vol.Required(CONF_TEMP_MAX, default=35): vol.Coerce(float),
vol.Required(CONF_STEP_TEMPERATURE, default=0.1): vol.Coerce(float),
vol.Required(CONF_ADD_CENTRAL_BOILER_CONTROL, default=False): cv.boolean,
}
)
STEP_CENTRAL_SPEC_MAIN_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_EXTERNAL_TEMP_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SENSOR_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Required(CONF_TEMP_MIN, default=7): vol.Coerce(float),
vol.Required(CONF_TEMP_MAX, default=35): vol.Coerce(float),
vol.Required(CONF_STEP_TEMPERATURE, default=0.1): vol.Coerce(float),
}
)
STEP_CENTRAL_BOILER_SCHEMA = vol.Schema(
{
vol.Optional(CONF_CENTRAL_BOILER_ACTIVATION_SRV, default=""): str,
vol.Optional(CONF_CENTRAL_BOILER_DEACTIVATION_SRV, default=""): str,
}
)
STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_HEATER): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
),
vol.Optional(CONF_HEATER_2): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
),
vol.Optional(CONF_HEATER_3): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
),
vol.Optional(CONF_HEATER_4): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
),
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
[
PROPORTIONAL_FUNCTION_TPI,
]
),
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
vol.Optional(CONF_INVERSE_SWITCH, default=False): cv.boolean,
}
)
STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_CLIMATE): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
),
vol.Optional(CONF_CLIMATE_2): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
),
vol.Optional(CONF_CLIMATE_3): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
),
vol.Optional(CONF_CLIMATE_4): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
),
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
vol.Optional(
CONF_AUTO_REGULATION_MODE, default=CONF_AUTO_REGULATION_NONE
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=CONF_AUTO_REGULATION_MODES,
translation_key="auto_regulation_mode",
mode="dropdown",
)
),
vol.Optional(CONF_AUTO_REGULATION_DTEMP, default=0.5): vol.Coerce(float),
vol.Optional(CONF_AUTO_REGULATION_PERIOD_MIN, default=5): cv.positive_int,
vol.Optional(
CONF_AUTO_FAN_MODE, default=CONF_AUTO_FAN_HIGH
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=CONF_AUTO_FAN_MODES,
translation_key="auto_fan_mode",
mode="dropdown",
)
),
}
)
STEP_THERMOSTAT_VALVE = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_VALVE): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Optional(CONF_VALVE_2): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Optional(CONF_VALVE_3): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Optional(CONF_VALVE_4): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
[
PROPORTIONAL_FUNCTION_TPI,
]
),
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
vol.Optional(CONF_AUTO_REGULATION_DTEMP, default=10): vol.Coerce(float),
vol.Optional(CONF_AUTO_REGULATION_PERIOD_MIN, default=5): cv.positive_int,
}
)
STEP_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_USE_TPI_CENTRAL_CONFIG, default=True): cv.boolean,
}
)
STEP_CENTRAL_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_TPI_COEF_INT, default=0.6): vol.Coerce(float),
vol.Required(CONF_TPI_COEF_EXT, default=0.01): vol.Coerce(float),
}
)
STEP_PRESETS_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_USE_PRESETS_CENTRAL_CONFIG, default=True): cv.boolean,
}
)
STEP_CENTRAL_PRESETS_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{vol.Optional(v, default=0): vol.Coerce(float) for (k, v) in CONF_PRESETS.items()}
)
STEP_CENTRAL_PRESETS_WITH_AC_DATA_SCHEMA = (
vol.Schema( # pylint: disable=invalid-name # pylint: disable=invalid-name
{
vol.Optional(v, default=0): vol.Coerce(float)
for (k, v) in CONF_PRESETS_WITH_AC.items()
}
)
)
STEP_WINDOW_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Optional(CONF_WINDOW_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[BINARY_SENSOR_DOMAIN, INPUT_BOOLEAN_DOMAIN]
),
),
vol.Required(CONF_USE_WINDOW_CENTRAL_CONFIG, default=True): cv.boolean,
}
)
STEP_CENTRAL_WINDOW_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Optional(CONF_WINDOW_DELAY, default=30): cv.positive_int,
vol.Optional(CONF_WINDOW_AUTO_OPEN_THRESHOLD, default=3): vol.Coerce(float),
vol.Optional(CONF_WINDOW_AUTO_CLOSE_THRESHOLD, default=0): vol.Coerce(float),
vol.Optional(CONF_WINDOW_AUTO_MAX_DURATION, default=30): cv.positive_int,
vol.Optional(
CONF_WINDOW_ACTION, default=CONF_WINDOW_TURN_OFF
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=CONF_WINDOW_ACTIONS,
translation_key="window_action",
mode="dropdown",
)
),
}
)
STEP_CENTRAL_WINDOW_WO_AUTO_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Optional(CONF_WINDOW_DELAY, default=30): cv.positive_int,
vol.Optional(
CONF_WINDOW_ACTION, default=CONF_WINDOW_TURN_OFF
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=CONF_WINDOW_ACTIONS,
translation_key="window_action",
mode="dropdown",
)
),
}
)
STEP_MOTION_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Optional(CONF_MOTION_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[BINARY_SENSOR_DOMAIN, INPUT_BOOLEAN_DOMAIN]
),
),
vol.Required(CONF_USE_MOTION_CENTRAL_CONFIG, default=True): cv.boolean,
}
)
STEP_CENTRAL_MOTION_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Optional(CONF_MOTION_DELAY, default=30): cv.positive_int,
vol.Optional(CONF_MOTION_OFF_DELAY, default=300): cv.positive_int,
vol.Optional(CONF_MOTION_PRESET, default="comfort"): selector.SelectSelector(
selector.SelectSelectorConfig(
options=CONF_PRESETS_SELECTIONABLE,
translation_key="presets",
mode="dropdown",
)
),
vol.Optional(CONF_NO_MOTION_PRESET, default="eco"): selector.SelectSelector(
selector.SelectSelectorConfig(
options=CONF_PRESETS_SELECTIONABLE,
translation_key="presets",
mode="dropdown",
)
),
}
)
STEP_CENTRAL_POWER_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Optional(CONF_POWER_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SENSOR_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Optional(CONF_MAX_POWER_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SENSOR_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Optional(CONF_PRESET_POWER, default="13"): vol.Coerce(float),
}
)
STEP_POWER_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_USE_POWER_CENTRAL_CONFIG, default=True): cv.boolean,
}
)
STEP_CENTRAL_PRESENCE_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Optional(v, default=17): vol.Coerce(float)
for (k, v) in CONF_PRESETS_AWAY.items()
}
)
STEP_CENTRAL_PRESENCE_WITH_AC_DATA_SCHEMA = { # pylint: disable=invalid-name
vol.Optional(v, default=17): vol.Coerce(float)
for (k, v) in CONF_PRESETS_AWAY_WITH_AC.items()
}
STEP_PRESENCE_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Optional(CONF_PRESENCE_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[
PERSON_DOMAIN,
BINARY_SENSOR_DOMAIN,
INPUT_BOOLEAN_DOMAIN,
]
),
),
vol.Required(CONF_USE_PRESENCE_CENTRAL_CONFIG, default=True): cv.boolean,
}
)
STEP_CENTRAL_ADVANCED_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_MINIMAL_ACTIVATION_DELAY, default=10): cv.positive_int,
vol.Required(CONF_SECURITY_DELAY_MIN, default=60): cv.positive_int,
vol.Required(
CONF_SECURITY_MIN_ON_PERCENT,
default=DEFAULT_SECURITY_MIN_ON_PERCENT,
): vol.Coerce(float),
vol.Required(
CONF_SECURITY_DEFAULT_ON_PERCENT,
default=DEFAULT_SECURITY_DEFAULT_ON_PERCENT,
): vol.Coerce(float),
}
)
STEP_ADVANCED_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_USE_ADVANCED_CENTRAL_CONFIG, default=True): cv.boolean,
}
)

View File

@@ -1,6 +1,8 @@
# pylint: disable=line-too-long # pylint: disable=line-too-long
"""Constants for the Versatile Thermostat integration.""" """Constants for the Versatile Thermostat integration."""
import logging
from enum import Enum from enum import Enum
from homeassistant.const import CONF_NAME, Platform from homeassistant.const import CONF_NAME, Platform
@@ -18,6 +20,8 @@ from .prop_algorithm import (
PROPORTIONAL_FUNCTION_TPI, PROPORTIONAL_FUNCTION_TPI,
) )
_LOGGER = logging.getLogger(__name__)
PRESET_AC_SUFFIX = "_ac" PRESET_AC_SUFFIX = "_ac"
PRESET_ECO_AC = PRESET_ECO + PRESET_AC_SUFFIX PRESET_ECO_AC = PRESET_ECO + PRESET_AC_SUFFIX
PRESET_COMFORT_AC = PRESET_COMFORT + PRESET_AC_SUFFIX PRESET_COMFORT_AC = PRESET_COMFORT + PRESET_AC_SUFFIX
@@ -35,7 +39,13 @@ HIDDEN_PRESETS = [PRESET_POWER, PRESET_SECURITY]
DOMAIN = "versatile_thermostat" DOMAIN = "versatile_thermostat"
PLATFORMS: list[Platform] = [Platform.CLIMATE, Platform.BINARY_SENSOR, Platform.SENSOR] PLATFORMS: list[Platform] = [
Platform.NUMBER,
Platform.SELECT,
Platform.CLIMATE,
Platform.SENSOR,
Platform.BINARY_SENSOR,
]
CONF_HEATER = "heater_entity_id" CONF_HEATER = "heater_entity_id"
CONF_HEATER_2 = "heater_entity2_id" CONF_HEATER_2 = "heater_entity2_id"
@@ -66,6 +76,7 @@ CONF_SECURITY_DELAY_MIN = "security_delay_min"
CONF_SECURITY_MIN_ON_PERCENT = "security_min_on_percent" CONF_SECURITY_MIN_ON_PERCENT = "security_min_on_percent"
CONF_SECURITY_DEFAULT_ON_PERCENT = "security_default_on_percent" CONF_SECURITY_DEFAULT_ON_PERCENT = "security_default_on_percent"
CONF_THERMOSTAT_TYPE = "thermostat_type" CONF_THERMOSTAT_TYPE = "thermostat_type"
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"
@@ -95,13 +106,35 @@ CONF_AUTO_REGULATION_EXPERT = "auto_regulation_expert"
CONF_AUTO_REGULATION_DTEMP = "auto_regulation_dtemp" CONF_AUTO_REGULATION_DTEMP = "auto_regulation_dtemp"
CONF_AUTO_REGULATION_PERIOD_MIN = "auto_regulation_periode_min" CONF_AUTO_REGULATION_PERIOD_MIN = "auto_regulation_periode_min"
CONF_INVERSE_SWITCH = "inverse_switch_command" CONF_INVERSE_SWITCH = "inverse_switch_command"
CONF_SHORT_EMA_PARAMS = "short_ema_params"
CONF_AUTO_FAN_MODE = "auto_fan_mode" CONF_AUTO_FAN_MODE = "auto_fan_mode"
CONF_AUTO_FAN_NONE = "auto_fan_none" CONF_AUTO_FAN_NONE = "auto_fan_none"
CONF_AUTO_FAN_LOW = "auto_fan_low" CONF_AUTO_FAN_LOW = "auto_fan_low"
CONF_AUTO_FAN_MEDIUM = "auto_fan_medium" 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"
# Global params into configuration.yaml
CONF_SHORT_EMA_PARAMS = "short_ema_params"
CONF_SAFETY_MODE = "safety_mode"
CONF_USE_MAIN_CENTRAL_CONFIG = "use_main_central_config"
CONF_USE_TPI_CENTRAL_CONFIG = "use_tpi_central_config"
CONF_USE_WINDOW_CENTRAL_CONFIG = "use_window_central_config"
CONF_USE_MOTION_CENTRAL_CONFIG = "use_motion_central_config"
CONF_USE_POWER_CENTRAL_CONFIG = "use_power_central_config"
CONF_USE_PRESENCE_CENTRAL_CONFIG = "use_presence_central_config"
CONF_USE_PRESETS_CENTRAL_CONFIG = "use_presets_central_config"
CONF_USE_ADVANCED_CENTRAL_CONFIG = "use_advanced_central_config"
CONF_USE_CENTRAL_MODE = "use_central_mode"
CONF_ADD_CENTRAL_BOILER_CONTROL = "add_central_boiler_control"
CONF_CENTRAL_BOILER_ACTIVATION_SRV = "central_boiler_activation_service"
CONF_CENTRAL_BOILER_DEACTIVATION_SRV = "central_boiler_deactivation_service"
CONF_USED_BY_CENTRAL_BOILER = "used_by_controls_central_boiler"
CONF_WINDOW_ACTION = "window_action"
DEFAULT_SHORT_EMA_PARAMS = { DEFAULT_SHORT_EMA_PARAMS = {
"max_alpha": 0.5, "max_alpha": 0.5,
@@ -224,6 +257,21 @@ ALL_CONF = (
CONF_AUTO_REGULATION_PERIOD_MIN, CONF_AUTO_REGULATION_PERIOD_MIN,
CONF_INVERSE_SWITCH, CONF_INVERSE_SWITCH,
CONF_AUTO_FAN_MODE, CONF_AUTO_FAN_MODE,
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_CENTRAL_MODE,
CONF_ADD_CENTRAL_BOILER_CONTROL,
CONF_USED_BY_CENTRAL_BOILER,
CONF_CENTRAL_BOILER_ACTIVATION_SRV,
CONF_CENTRAL_BOILER_DEACTIVATION_SRV,
CONF_WINDOW_ACTION,
CONF_STEP_TEMPERATURE,
] ]
+ CONF_PRESETS_VALUES + CONF_PRESETS_VALUES
+ CONF_PRESETS_AWAY_VALUES + CONF_PRESETS_AWAY_VALUES
@@ -245,6 +293,7 @@ CONF_AUTO_REGULATION_MODES = [
] ]
CONF_THERMOSTAT_TYPES = [ CONF_THERMOSTAT_TYPES = [
CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_THERMOSTAT_SWITCH, CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE, CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_VALVE, CONF_THERMOSTAT_VALVE,
@@ -258,6 +307,18 @@ CONF_AUTO_FAN_MODES = [
CONF_AUTO_FAN_TURBO, CONF_AUTO_FAN_TURBO,
] ]
CONF_WINDOW_TURN_OFF = "window_turn_off"
CONF_WINDOW_FAN_ONLY = "window_fan_only"
CONF_WINDOW_FROST_TEMP = "window_frost_temp"
CONF_WINDOW_ECO_TEMP = "window_eco_temp"
CONF_WINDOW_ACTIONS = [
CONF_WINDOW_TURN_OFF,
CONF_WINDOW_FAN_ONLY,
CONF_WINDOW_FROST_TEMP,
CONF_WINDOW_ECO_TEMP,
]
SUPPORT_FLAGS = ClimateEntityFeature.TARGET_TEMPERATURE SUPPORT_FLAGS = ClimateEntityFeature.TARGET_TEMPERATURE
SERVICE_SET_PRESENCE = "set_presence" SERVICE_SET_PRESENCE = "set_presence"
@@ -276,6 +337,21 @@ ATTR_MEAN_POWER_CYCLE = "mean_cycle_power"
AUTO_FAN_DTEMP_THRESHOLD = 2 AUTO_FAN_DTEMP_THRESHOLD = 2
AUTO_FAN_DEACTIVATED_MODES = ["mute", "auto", "low"] AUTO_FAN_DEACTIVATED_MODES = ["mute", "auto", "low"]
CENTRAL_CONFIG_NAME = "Central configuration"
CENTRAL_MODE_AUTO = "Auto"
CENTRAL_MODE_STOPPED = "Stopped"
CENTRAL_MODE_HEAT_ONLY = "Heat only"
CENTRAL_MODE_COOL_ONLY = "Cool only"
CENTRAL_MODE_FROST_PROTECTION = "Frost protection"
CENTRAL_MODES = [
CENTRAL_MODE_AUTO,
CENTRAL_MODE_STOPPED,
CENTRAL_MODE_HEAT_ONLY,
CENTRAL_MODE_COOL_ONLY,
CENTRAL_MODE_FROST_PROTECTION,
]
# A special regulation parameter suggested by @Maia here: https://github.com/jmcollin78/versatile_thermostat/discussions/154 # A special regulation parameter suggested by @Maia here: https://github.com/jmcollin78/versatile_thermostat/discussions/154
class RegulationParamSlow: class RegulationParamSlow:
@@ -351,10 +427,20 @@ class EventType(Enum):
POWER_EVENT: str = "versatile_thermostat_power_event" POWER_EVENT: str = "versatile_thermostat_power_event"
TEMPERATURE_EVENT: str = "versatile_thermostat_temperature_event" TEMPERATURE_EVENT: str = "versatile_thermostat_temperature_event"
HVAC_MODE_EVENT: str = "versatile_thermostat_hvac_mode_event" HVAC_MODE_EVENT: str = "versatile_thermostat_hvac_mode_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"
def send_vtherm_event(hass, event_type: EventType, entity, data: dict):
"""Send an event"""
_LOGGER.info("%s - Sending event %s with data: %s", entity, event_type, data)
data["entity_id"] = entity.entity_id
data["name"] = entity.name
data["state_attributes"] = entity.state_attributes
hass.bus.fire(event_type.value, data)
class UnknownEntity(HomeAssistantError): class UnknownEntity(HomeAssistantError):
"""Error to indicate there is an unknown entity_id given.""" """Error to indicate there is an unknown entity_id given."""
@@ -363,6 +449,14 @@ class WindowOpenDetectionMethod(HomeAssistantError):
"""Error to indicate there is an error in the window open detection method given.""" """Error to indicate there is an error in the window open detection method given."""
class NoCentralConfig(HomeAssistantError):
"""Error to indicate that we try to use a central configuration but no VTherm of type CENTRAL CONFIGURATION has been found"""
class ServiceConfigurationError(HomeAssistantError):
"""Error in the service configuration to control the central boiler"""
class overrides: # pylint: disable=invalid-name class overrides: # pylint: disable=invalid-name
"""An annotation to inform overrides""" """An annotation to inform overrides"""

View File

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

View File

@@ -0,0 +1,117 @@
# pylint: disable=unused-argument
""" Implements the VersatileThermostat select component """
import logging
# from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.core import HomeAssistant, CoreState # , callback
from homeassistant.components.number import NumberEntity, NumberMode
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
from .const import (
DOMAIN,
DEVICE_MANUFACTURER,
CONF_NAME,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_ADD_CENTRAL_BOILER_CONTROL,
overrides,
)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the VersatileThermostat selects with config flow."""
_LOGGER.debug(
"Calling async_setup_entry entry=%s, data=%s", entry.entry_id, entry.data
)
unique_id = entry.entry_id
name = entry.data.get(CONF_NAME)
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
is_central_boiler = entry.data.get(CONF_ADD_CENTRAL_BOILER_CONTROL)
if vt_type != CONF_THERMOSTAT_CENTRAL_CONFIG or not is_central_boiler:
return
entities = [
ActivateBoilerThresholdNumber(hass, unique_id, name, entry.data),
]
async_add_entities(entities, True)
class ActivateBoilerThresholdNumber(NumberEntity, RestoreEntity):
"""Representation of the threshold of the number of VTherm
which should be active to activate the boiler"""
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the energy sensor"""
self._hass = hass
self._config_id = unique_id
self._device_name = entry_infos.get(CONF_NAME)
self._attr_name = "Boiler Activation threshold"
self._attr_unique_id = "boiler_activation_threshold"
self._attr_value = self._attr_native_value = 1 # default value
self._attr_native_min_value = 1
self._attr_native_max_value = 9
self._attr_step = 1 # default value
self._attr_mode = NumberMode.AUTO
@property
def icon(self) -> str | None:
if isinstance(self._attr_native_value, int):
val = int(self._attr_native_value)
return f"mdi:numeric-{val}-box-outline"
else:
return "mdi:numeric-0-box-outline"
@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
return DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, self._config_id)},
name=self._device_name,
manufacturer=DEVICE_MANUFACTURER,
model=DOMAIN,
)
@overrides
async def async_added_to_hass(self) -> None:
await super().async_added_to_hass()
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self._hass)
api.register_central_boiler_activation_number_threshold(self)
old_state: CoreState = await self.async_get_last_state()
_LOGGER.debug(
"%s - Calling async_added_to_hass old_state is %s", self, old_state
)
if old_state is not None:
self._attr_value = self._attr_native_value = int(float(old_state.state))
@overrides
def set_native_value(self, value: float) -> None:
"""Change the value"""
int_value = int(value)
old_value = int(self._attr_native_value)
if int_value == old_value:
return
self._attr_value = self._attr_native_value = int_value
def __str__(self):
return f"VersatileThermostat-{self.name}"

View File

@@ -25,7 +25,7 @@ class PropAlgorithm:
) -> None: ) -> None:
"""Initialisation of the Proportional Algorithm""" """Initialisation of the Proportional Algorithm"""
_LOGGER.debug( _LOGGER.debug(
"Creation new PropAlgorithm function_type: %s, tpi_coef_int: %s, tpi_coef_ext: %s, cycle_min:%d, minimal_activation_delay:%d", "Creation new PropAlgorithm function_type: %s, tpi_coef_int: %s, tpi_coef_ext: %s, cycle_min:%d, minimal_activation_delay:%d", # pylint: disable=line-too-long
function_type, function_type,
tpi_coef_int, tpi_coef_int,
tpi_coef_ext, tpi_coef_ext,
@@ -140,27 +140,27 @@ class PropAlgorithm:
self._off_time_sec = self._cycle_min * 60 - self._on_time_sec self._off_time_sec = self._cycle_min * 60 - self._on_time_sec
def set_security(self, default_on_percent: float): def set_security(self, default_on_percent: float):
"""Set a default value for on_percent (used for security mode)""" """Set a default value for on_percent (used for safety mode)"""
self._security = True self._security = True
self._default_on_percent = default_on_percent self._default_on_percent = default_on_percent
self._calculate_internal() self._calculate_internal()
def unset_security(self): def unset_security(self):
"""Unset the security mode""" """Unset the safety mode"""
self._security = False self._security = False
self._calculate_internal() self._calculate_internal()
@property @property
def on_percent(self) -> float: def on_percent(self) -> float:
"""Returns the percentage the heater must be ON """Returns the percentage the heater must be ON
In security mode this value is overriden with the _default_on_percent In safety mode this value is overriden with the _default_on_percent
(1 means the heater will be always on, 0 never on)""" # pylint: disable=line-too-long (1 means the heater will be always on, 0 never on)""" # pylint: disable=line-too-long
return round(self._on_percent, 2) return round(self._on_percent, 2)
@property @property
def calculated_on_percent(self) -> float: def calculated_on_percent(self) -> float:
"""Returns the calculated percentage the heater must be ON """Returns the calculated percentage the heater must be ON
Calculated means NOT overriden even in security mode Calculated means NOT overriden even in safety mode
(1 means the heater will be always on, 0 never on)""" # pylint: disable=line-too-long (1 means the heater will be always on, 0 never on)""" # pylint: disable=line-too-long
return round(self._calculated_on_percent, 2) return round(self._calculated_on_percent, 2)

View File

@@ -0,0 +1,138 @@
# pylint: disable=unused-argument
""" Implements the VersatileThermostat select component """
import logging
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.core import HomeAssistant, CoreState, callback
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
from homeassistant.components.select import SelectEntity
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_component import EntityComponent
from custom_components.versatile_thermostat.base_thermostat import (
BaseThermostat,
ConfigData,
)
from .const import (
DOMAIN,
DEVICE_MANUFACTURER,
CONF_NAME,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_CENTRAL_CONFIG,
CENTRAL_MODE_AUTO,
CENTRAL_MODES,
overrides,
)
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the VersatileThermostat selects with config flow."""
_LOGGER.debug(
"Calling async_setup_entry entry=%s, data=%s", entry.entry_id, entry.data
)
unique_id = entry.entry_id
name = entry.data.get(CONF_NAME)
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
if vt_type != CONF_THERMOSTAT_CENTRAL_CONFIG:
return
entities = [
CentralModeSelect(hass, unique_id, name, entry.data),
]
async_add_entities(entities, True)
class CentralModeSelect(SelectEntity, RestoreEntity):
"""Representation of the central mode choice"""
def __init__(
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigData
):
"""Initialize the energy sensor"""
self._config_id = unique_id
self._device_name = entry_infos.get(CONF_NAME)
self._attr_name = "Central Mode"
self._attr_unique_id = "central_mode"
self._attr_options = CENTRAL_MODES
self._attr_current_option = CENTRAL_MODE_AUTO
@property
def icon(self) -> str:
return "mdi:form-select"
@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
return DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, self._config_id)},
name=self._device_name,
manufacturer=DEVICE_MANUFACTURER,
model=DOMAIN,
)
@overrides
async def async_added_to_hass(self) -> None:
await super().async_added_to_hass()
old_state = await self.async_get_last_state()
_LOGGER.debug(
"%s - Calling async_added_to_hass old_state is %s", self, old_state
)
if old_state is not None:
self._attr_current_option = old_state.state
@callback
async def _async_startup_internal(*_):
_LOGGER.debug("%s - Calling async_startup_internal", self)
await self.notify_central_mode_change()
if self.hass.state == CoreState.running:
await _async_startup_internal()
else:
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, _async_startup_internal
)
@overrides
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
old_option = self._attr_current_option
if option == old_option:
return
if option in CENTRAL_MODES:
self._attr_current_option = option
await self.notify_central_mode_change(old_central_mode=old_option)
async def notify_central_mode_change(self, old_central_mode: str | None = None):
"""Notify all VTherm that the central_mode have change"""
# Update all VTherm states
component: EntityComponent[ClimateEntity] = self.hass.data[CLIMATE_DOMAIN]
for entity in component.entities:
if isinstance(entity, BaseThermostat):
_LOGGER.debug(
"Changing the central_mode. We have find %s to update",
entity.name,
)
await entity.check_central_mode(
self._attr_current_option, old_central_mode
)
def __str__(self) -> str:
return f"VersatileThermostat-{self.name}"

View File

@@ -3,9 +3,15 @@
import logging import logging
import math import math
from homeassistant.core import HomeAssistant, callback, Event from homeassistant.core import HomeAssistant, callback, Event, CoreState
from homeassistant.const import UnitOfTime, UnitOfPower, UnitOfEnergy, PERCENTAGE from homeassistant.const import (
UnitOfTime,
UnitOfPower,
UnitOfEnergy,
PERCENTAGE,
EVENT_HOMEASSISTANT_START,
)
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorEntity, SensorEntity,
@@ -16,9 +22,24 @@ from homeassistant.components.sensor import (
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.components.climate import (
ClimateEntity,
DOMAIN as CLIMATE_DOMAIN,
HVACAction,
HVACMode,
)
from .base_thermostat import BaseThermostat
from .vtherm_api import VersatileThermostatAPI
from .commons import VersatileThermostatBaseEntity from .commons import VersatileThermostatBaseEntity
from .const import ( from .const import (
DOMAIN,
DEVICE_MANUFACTURER,
CONF_NAME, CONF_NAME,
CONF_DEVICE_POWER, CONF_DEVICE_POWER,
CONF_PROP_FUNCTION, CONF_PROP_FUNCTION,
@@ -27,6 +48,9 @@ from .const import (
CONF_THERMOSTAT_VALVE, CONF_THERMOSTAT_VALVE,
CONF_THERMOSTAT_CLIMATE, CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_ADD_CENTRAL_BOILER_CONTROL,
overrides,
) )
THRESHOLD_WATT_KILO = 100 THRESHOLD_WATT_KILO = 100
@@ -46,33 +70,45 @@ 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)
entities = [ entities = None
LastTemperatureSensor(hass, unique_id, name, entry.data),
LastExtTemperatureSensor(hass, unique_id, name, entry.data),
TemperatureSlopeSensor(hass, unique_id, name, entry.data),
EMATemperatureSensor(hass, unique_id, name, entry.data),
]
if entry.data.get(CONF_DEVICE_POWER):
entities.append(EnergySensor(hass, unique_id, name, entry.data))
if entry.data.get(CONF_THERMOSTAT_TYPE) in [
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_VALVE,
]:
entities.append(MeanPowerSensor(hass, unique_id, name, entry.data))
if entry.data.get(CONF_PROP_FUNCTION) == PROPORTIONAL_FUNCTION_TPI: if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
entities.append(OnPercentSensor(hass, unique_id, name, entry.data)) if entry.data.get(CONF_ADD_CENTRAL_BOILER_CONTROL):
entities.append(OnTimeSensor(hass, unique_id, name, entry.data)) entities = [
entities.append(OffTimeSensor(hass, unique_id, name, entry.data)) NbActiveDeviceForBoilerSensor(hass, unique_id, name, entry.data)
]
async_add_entities(entities, True)
else:
entities = [
LastTemperatureSensor(hass, unique_id, name, entry.data),
LastExtTemperatureSensor(hass, unique_id, name, entry.data),
TemperatureSlopeSensor(hass, unique_id, name, entry.data),
EMATemperatureSensor(hass, unique_id, name, entry.data),
]
if entry.data.get(CONF_DEVICE_POWER):
entities.append(EnergySensor(hass, unique_id, name, entry.data))
if entry.data.get(CONF_THERMOSTAT_TYPE) in [
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_VALVE,
]:
entities.append(MeanPowerSensor(hass, unique_id, name, entry.data))
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE: if entry.data.get(CONF_PROP_FUNCTION) == PROPORTIONAL_FUNCTION_TPI:
entities.append(ValveOpenPercentSensor(hass, unique_id, name, entry.data)) entities.append(OnPercentSensor(hass, unique_id, name, entry.data))
entities.append(OnTimeSensor(hass, unique_id, name, entry.data))
entities.append(OffTimeSensor(hass, unique_id, name, entry.data))
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE: if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE:
entities.append(RegulatedTemperatureSensor(hass, unique_id, name, entry.data)) entities.append(ValveOpenPercentSensor(hass, unique_id, name, entry.data))
async_add_entities(entities, True) if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE:
entities.append(
RegulatedTemperatureSensor(hass, unique_id, name, entry.data)
)
async_add_entities(entities, True)
class EnergySensor(VersatileThermostatBaseEntity, SensorEntity): class EnergySensor(VersatileThermostatBaseEntity, SensorEntity):
@@ -248,7 +284,7 @@ class ValveOpenPercentSensor(VersatileThermostatBaseEntity, SensorEntity):
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None: def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the energy sensor""" """Initialize the energy sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME)) super().__init__(hass, unique_id, entry_infos.get(CONF_NAME))
self._attr_name = "Vave open percent" self._attr_name = "Valve open percent"
self._attr_unique_id = f"{self._device_name}_valve_open_percent" self._attr_unique_id = f"{self._device_name}_valve_open_percent"
@callback @callback
@@ -592,3 +628,116 @@ class EMATemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
def suggested_display_precision(self) -> int | None: def suggested_display_precision(self) -> int | None:
"""Return the suggested number of decimal digits for display.""" """Return the suggested number of decimal digits for display."""
return 2 return 2
class NbActiveDeviceForBoilerSensor(SensorEntity):
"""Representation of the threshold of the number of VTherm
which should be active to activate the boiler"""
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None:
"""Initialize the energy sensor"""
self._hass = hass
self._config_id = unique_id
self._device_name = entry_infos.get(CONF_NAME)
self._attr_name = "Nb device active for boiler"
self._attr_unique_id = "nb_device_active_boiler"
self._attr_value = self._attr_native_value = None # default value
self._entities = []
@property
def icon(self) -> str | None:
return "mdi:heat-wave"
@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
return DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
identifiers={(DOMAIN, self._config_id)},
name=self._device_name,
manufacturer=DEVICE_MANUFACTURER,
model=DOMAIN,
)
@property
def state_class(self) -> SensorStateClass | None:
return SensorStateClass.MEASUREMENT
@property
def suggested_display_precision(self) -> int | None:
"""Return the suggested number of decimal digits for display."""
return 0
@overrides
async def async_added_to_hass(self) -> None:
await super().async_added_to_hass()
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self._hass)
api.register_nb_device_active_boiler(self)
@callback
async def _async_startup_internal(*_):
_LOGGER.debug("%s - Calling async_startup_internal", self)
await self.listen_vtherms_entities()
if self.hass.state == CoreState.running:
await _async_startup_internal()
else:
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, _async_startup_internal
)
async def listen_vtherms_entities(self):
"""Initialize the listening of state change of VTherms"""
# Listen to all VTherm state change
self._entities = []
underlying_entities_id = []
component: EntityComponent[ClimateEntity] = self.hass.data[CLIMATE_DOMAIN]
for entity in component.entities:
if isinstance(entity, BaseThermostat) and entity.is_used_by_central_boiler:
self._entities.append(entity)
for under in entity.underlying_entities:
underlying_entities_id.append(under.entity_id)
if len(underlying_entities_id) > 0:
# Arme l'écoute de la première entité
listener_cancel = async_track_state_change_event(
self._hass,
underlying_entities_id,
self.calculate_nb_active_devices,
)
_LOGGER.info(
"%s - the underlyings that could controls the central boiler are %s",
self,
underlying_entities_id,
)
self.async_on_remove(listener_cancel)
else:
_LOGGER.debug("%s - no VTherm could controls the central boiler", self)
await self.calculate_nb_active_devices(None)
async def calculate_nb_active_devices(self, _):
"""Calculate the number of active VTherm that have an
influence on central boiler"""
_LOGGER.debug("%s - calculating the number of active VTherm", self)
nb_active = 0
for entity in self._entities:
_LOGGER.debug(
"Examining the hvac_action of %s",
entity.name,
)
if (
entity.hvac_mode == HVACMode.HEAT
and entity.hvac_action == HVACAction.HEATING
):
for under in entity.underlying_entities:
nb_active += 1 if under.is_device_active else 0
self._attr_native_value = nb_active
self.async_write_ha_state()
def __str__(self):
return f"VersatileThermostat-{self.name}"

View File

@@ -77,8 +77,8 @@ set_preset_temperature:
mode: slider mode: slider
set_security: set_security:
name: Set security name: Set safety
description: Change the security parameters description: Change the safety parameters
target: target:
entity: entity:
integration: versatile_thermostat integration: versatile_thermostat
@@ -97,7 +97,7 @@ set_security:
mode: box mode: box
min_on_percent: min_on_percent:
name: Minimal on_percent name: Minimal on_percent
description: Minimal heating percent value for security preset activation description: Minimal heating percent value for safety preset activation
required: false required: false
advanced: false advanced: false
example: "0.5" example: "0.5"
@@ -110,8 +110,8 @@ set_security:
unit_of_measurement: "%" unit_of_measurement: "%"
mode: slider mode: slider
default_on_percent: default_on_percent:
name: on_percent used in security mode name: on_percent used in safety mode
description: The default heating percent value in security preset description: The default heating percent value in safety preset
required: false required: false
advanced: false advanced: false
example: "0.1" example: "0.1"

View File

@@ -4,21 +4,38 @@
"flow_title": "Versatile Thermostat configuration", "flow_title": "Versatile Thermostat configuration",
"step": { "step": {
"user": { "user": {
"title": "Type of Versatile Thermostat",
"data": {
"thermostat_type": "Thermostat type"
},
"data_description": {
"thermostat_type": "Only one central configuration type is possible"
}
},
"main": {
"title": "Add new Versatile Thermostat", "title": "Add new Versatile Thermostat",
"description": "Main mandatory attributes", "description": "Main mandatory attributes",
"data": { "data": {
"name": "Name", "name": "Name",
"thermostat_type": "Thermostat type", "thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Temperature sensor entity id", "temperature_sensor_entity_id": "Temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id", "external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)", "cycle_min": "Cycle duration (minutes)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimal temperature allowed",
"temp_max": "Maximal temperature allowed", "temp_max": "Maximal temperature allowed",
"step_temperature": "Temperature step",
"device_power": "Device power", "device_power": "Device power",
"use_central_mode": "Enable the control by central entity (need central config). Check to enable the control of the VTherm with the select central_mode entities.",
"use_window_feature": "Use window detection", "use_window_feature": "Use window detection",
"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_main_central_config": "Use central main configuration. Check to use the central main configuration. Uncheck to use a specific main configuration for this VTherm",
"add_central_boiler_control": "Add 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 need heating, the boiler will be turned on. If no VTherm needs heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the next configuration page",
"used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler"
},
"data_description": {
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
} }
}, },
"type": { "type": {
@@ -61,7 +78,7 @@
"valve_entity3_id": "3rd valve number entity id", "valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th 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 ° 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",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command", "inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" "auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
@@ -71,8 +88,14 @@
"title": "TPI", "title": "TPI",
"description": "Time Proportional Integral attributes", "description": "Time Proportional Integral attributes",
"data": { "data": {
"tpi_coef_int": "coef_int",
"tpi_coef_ext": "coef_ext",
"use_tpi_central_config": "Use central TPI configuration"
},
"data_description": {
"tpi_coef_int": "Coefficient to use for internal temperature delta", "tpi_coef_int": "Coefficient to use for internal temperature delta",
"tpi_coef_ext": "Coefficient to use for external temperature delta" "tpi_coef_ext": "Coefficient to use for external temperature delta",
"use_tpi_central_config": "Check to use the central TPI configuration. Uncheck to use a specific TPI configuration for this VTherm"
} }
}, },
"presets": { "presets": {
@@ -85,7 +108,8 @@
"frost_temp": "Frost protection preset", "frost_temp": "Frost protection preset",
"eco_ac_temp": "Eco preset for AC mode", "eco_ac_temp": "Eco preset for AC mode",
"comfort_ac_temp": "Comfort preset for AC mode", "comfort_ac_temp": "Comfort preset for AC mode",
"boost_ac_temp": "Boost preset for AC mode" "boost_ac_temp": "Boost preset for AC mode",
"use_presets_central_config": "Use central presets configuration"
}, },
"data_description": { "data_description": {
"eco_temp": "Temperature in Eco preset", "eco_temp": "Temperature in Eco preset",
@@ -94,57 +118,71 @@
"frost_temp": "Temperature in Frost protection preset", "frost_temp": "Temperature in Frost protection preset",
"eco_ac_temp": "Temperature in Eco preset for AC mode", "eco_ac_temp": "Temperature in Eco preset for AC mode",
"comfort_ac_temp": "Temperature in Comfort preset for AC mode", "comfort_ac_temp": "Temperature in Comfort preset for AC mode",
"boost_ac_temp": "Temperature in Boost preset for AC mode" "boost_ac_temp": "Temperature in Boost preset for AC mode",
"use_presets_central_config": "Check to use the central presets configuration. Uncheck to use a specific presets configuration for this VTherm"
} }
}, },
"window": { "window": {
"title": "Window management", "title": "Window management",
"description": "Open window management.\nLeave corresponding entity_id empty if not used\nYou can also configure automatic window open detection based on temperature decrease", "description": "Open window management.\nYou can also configure automatic window open detection based on temperature decrease",
"data": { "data": {
"window_sensor_entity_id": "Window sensor entity id", "window_sensor_entity_id": "Window sensor entity id",
"window_delay": "Window sensor delay (seconds)", "window_delay": "Window sensor delay (seconds)",
"window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)", "window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)",
"window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)", "window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)",
"window_auto_max_duration": "Maximum duration of automatic window open detection (in min)" "window_auto_max_duration": "Maximum duration of automatic window open detection (in min)",
"use_window_central_config": "Use central window configuration",
"window_action": "Action"
}, },
"data_description": { "data_description": {
"window_sensor_entity_id": "Leave empty if no window sensor should be used", "window_sensor_entity_id": "Leave empty if no window sensor should be used and to use the automatic detection",
"window_delay": "The delay in seconds before sensor detection is taken into account", "window_delay": "The delay in seconds before sensor detection is taken into account",
"window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used", "window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used",
"window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used", "window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used",
"window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used" "window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used",
"use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm",
"window_action": "Action to do if window is deteted as open"
} }
}, },
"motion": { "motion": {
"title": "Motion management", "title": "Motion management",
"description": "Motion sensor management. Preset can switch automatically depending on motion detection\nLeave corresponding entity_id empty if not used.\nmotion_preset and no_motion_preset should be set to the corresponding preset name", "description": "Motion sensor management. Preset can switch automatically depending on motion detection\nmotion_preset and no_motion_preset should be set to the corresponding preset name",
"data": { "data": {
"motion_sensor_entity_id": "Motion sensor entity id", "motion_sensor_entity_id": "Motion sensor entity id",
"motion_delay": "Activation delay", "motion_delay": "Activation delay",
"motion_off_delay": "Deactivation delay", "motion_off_delay": "Deactivation delay",
"motion_preset": "Motion preset", "motion_preset": "Motion preset",
"no_motion_preset": "No motion preset" "no_motion_preset": "No motion preset",
"use_motion_central_config": "Use central motion configuration"
}, },
"data_description": { "data_description": {
"motion_sensor_entity_id": "The entity id of the motion sensor", "motion_sensor_entity_id": "The entity id of the motion sensor",
"motion_delay": "Motion activation delay (seconds)", "motion_delay": "Motion activation delay (seconds)",
"motion_off_delay": "Motion deactivation delay (seconds)", "motion_off_delay": "Motion deactivation delay (seconds)",
"motion_preset": "Preset to use when motion is detected", "motion_preset": "Preset to use when motion is detected",
"no_motion_preset": "Preset to use when no motion is detected" "no_motion_preset": "Preset to use when no motion is detected",
"use_motion_central_config": "Check to use the central motion configuration. Uncheck to use a specific motion configuration for this VTherm"
} }
}, },
"power": { "power": {
"title": "Power management", "title": "Power management",
"description": "Power management attributes.\nGives the power and max power sensor of your home.\nThen specify the power consumption of the heater when on.\nAll sensors and device power should have the same unit (kW or W).\nLeave corresponding entity_id empty if not used.", "description": "Power management attributes.\nGives the power and max power sensor of your home.\nThen specify the power consumption of the heater when on.\nAll sensors and device power should have the same unit (kW or W).",
"data": { "data": {
"power_sensor_entity_id": "Power",
"max_power_sensor_entity_id": "Max power",
"power_temp": "Shedding temperature",
"use_power_central_config": "Use central power configuration"
},
"data_description": {
"power_sensor_entity_id": "Power sensor entity id", "power_sensor_entity_id": "Power sensor entity id",
"max_power_sensor_entity_id": "Max power sensor entity id", "max_power_sensor_entity_id": "Max power sensor entity id",
"power_temp": "Temperature for Power shedding" "power_temp": "Temperature for Power shedding",
"use_power_central_config": "Check to use the central power configuration. Uncheck to use a specific power configuration for this VTherm"
} }
}, },
"presence": { "presence": {
"title": "Presence management", "title": "Presence management",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present).\nThen specify either the preset to use when presence sensor is false or the offset in temperature to apply.\nIf preset is given, the offset will not be used.\nLeave corresponding entity_id empty if not used.", "description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present) and give the corresponding temperature preset setting.",
"data": { "data": {
"presence_sensor_entity_id": "Presence sensor", "presence_sensor_entity_id": "Presence sensor",
"eco_away_temp": "Eco preset", "eco_away_temp": "Eco preset",
@@ -153,7 +191,8 @@
"frost_away_temp": "Frost protection preset", "frost_away_temp": "Frost protection preset",
"eco_ac_away_temp": "Eco preset in AC mode", "eco_ac_away_temp": "Eco preset in AC mode",
"comfort_ac_away_temp": "Comfort preset in AC mode", "comfort_ac_away_temp": "Comfort preset in AC mode",
"boost_ac_away_temp": "Boost pres et in AC mode" "boost_ac_away_temp": "Boost pres et in AC mode",
"use_presence_central_config": "Use central presence configuration"
}, },
"data_description": { "data_description": {
"presence_sensor_entity_id": "Presence sensor entity id", "presence_sensor_entity_id": "Presence sensor entity id",
@@ -163,7 +202,8 @@
"frost_away_temp": "Temperature in Frost protection preset when no presence", "frost_away_temp": "Temperature in Frost protection preset when no presence",
"eco_ac_away_temp": "Temperature in Eco preset when no presence in AC mode", "eco_ac_away_temp": "Temperature in Eco preset when no presence in AC mode",
"comfort_ac_away_temp": "Temperature in Comfort preset when no presence in AC mode", "comfort_ac_away_temp": "Temperature in Comfort preset when no presence in AC mode",
"boost_ac_away_temp": "Temperature in Boost preset when no presence in AC mode" "boost_ac_away_temp": "Temperature in Boost preset when no presence in AC mode",
"use_presence_central_config": "Check to use the central presence configuration. Uncheck to use a specific presence configuration for this VTherm"
} }
}, },
"advanced": { "advanced": {
@@ -171,22 +211,25 @@
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThese parameters can lead to very poor temperature control or bad power regulation.", "description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThese parameters can lead to very poor temperature control or bad power regulation.",
"data": { "data": {
"minimal_activation_delay": "Minimal activation delay", "minimal_activation_delay": "Minimal activation delay",
"security_delay_min": "Security delay (in minutes)", "security_delay_min": "Safety delay (in minutes)",
"security_min_on_percent": "Minimal power percent to enable security mode", "security_min_on_percent": "Minimal power percent to enable safety mode",
"security_default_on_percent": "Power percent to use in security mode" "security_default_on_percent": "Power percent to use in safety mode",
"use_advanced_central_config": "Use central advanced configuration"
}, },
"data_description": { "data_description": {
"minimal_activation_delay": "Delay in seconds under which the equipment will not be activated", "minimal_activation_delay": "Delay in seconds under which the equipment will not be activated",
"security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a security off state", "security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state",
"security_min_on_percent": "Minimal heating percent value for security preset activation. Below this amount of power percent the thermostat won't go into security preset", "security_min_on_percent": "Minimal heating percent value for safety preset activation. Below this amount of power percent the thermostat won't go into safety preset",
"security_default_on_percent": "The default heating power percent value in security preset. Set to 0 to switch off heater in security 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"
} }
} }
}, },
"error": { "error": {
"unknown": "Unexpected error", "unknown": "Unexpected error",
"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."
}, },
"abort": { "abort": {
"already_configured": "Device is already configured" "already_configured": "Device is already configured"
@@ -196,25 +239,42 @@
"flow_title": "Versatile Thermostat configuration", "flow_title": "Versatile Thermostat configuration",
"step": { "step": {
"user": { "user": {
"title": "Add new Versatile Thermostat", "title": "Type - {name}",
"data": {
"thermostat_type": "Thermostat type"
},
"data_description": {
"thermostat_type": "Only one central configuration type is possible"
}
},
"main": {
"title": "Main - {name}",
"description": "Main mandatory attributes", "description": "Main mandatory attributes",
"data": { "data": {
"name": "Name", "name": "Name",
"thermostat_type": "Thermostat type", "thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Temperature sensor entity id", "temperature_sensor_entity_id": "Room temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id", "external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)", "cycle_min": "Cycle duration (minutes)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimal temperature allowed",
"temp_max": "Maximal temperature allowed", "temp_max": "Maximal temperature allowed",
"device_power": "Device power (kW)", "step_temperature": "Temperature step",
"device_power": "Device power",
"use_central_mode": "Enable the control by central entity (need central config). Check to enable the control of the VTherm with the select central_mode entities.",
"use_window_feature": "Use window detection", "use_window_feature": "Use window detection",
"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_main_central_config": "Use central main configuration. Check to use the central main configuration. Uncheck to use a specific main configuration for this VTherm",
"add_central_boiler_control": "Add 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 need heating, the boiler will be turned on. If no VTherm needs heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the next configuration page",
"used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler"
},
"data_description": {
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
} }
}, },
"type": { "type": {
"title": "Linked entities", "title": "Entities - {name}",
"description": "Linked entities attributes", "description": "Linked entities attributes",
"data": { "data": {
"heater_entity_id": "1st heater switch", "heater_entity_id": "1st heater switch",
@@ -253,22 +313,28 @@
"valve_entity3_id": "3rd valve number entity id", "valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th 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 ° 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",
"inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command", "inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" "auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
} }
}, },
"tpi": { "tpi": {
"title": "TPI", "title": "TPI - {name}",
"description": "Time Proportional Integral attributes", "description": "Time Proportional Integral attributes",
"data": { "data": {
"tpi_coef_int": "coef_int",
"tpi_coef_ext": "coef_ext",
"use_tpi_central_config": "Use central TPI configuration"
},
"data_description": {
"tpi_coef_int": "Coefficient to use for internal temperature delta", "tpi_coef_int": "Coefficient to use for internal temperature delta",
"tpi_coef_ext": "Coefficient to use for external temperature delta" "tpi_coef_ext": "Coefficient to use for external temperature delta",
"use_tpi_central_config": "Check to use the central TPI configuration. Uncheck to use a specific TPI configuration for this VTherm"
} }
}, },
"presets": { "presets": {
"title": "Presets", "title": "Presets - {name}",
"description": "For each preset set the target temperature (0 to ignore preset)", "description": "For each preset set the target temperature (0 to ignore preset)",
"data": { "data": {
"eco_temp": "Eco preset", "eco_temp": "Eco preset",
@@ -277,7 +343,8 @@
"frost_temp": "Frost protection preset", "frost_temp": "Frost protection preset",
"eco_ac_temp": "Eco preset for AC mode", "eco_ac_temp": "Eco preset for AC mode",
"comfort_ac_temp": "Comfort preset for AC mode", "comfort_ac_temp": "Comfort preset for AC mode",
"boost_ac_temp": "Boost preset for AC mode" "boost_ac_temp": "Boost preset for AC mode",
"use_presets_central_config": "Use central presets configuration"
}, },
"data_description": { "data_description": {
"eco_temp": "Temperature in Eco preset", "eco_temp": "Temperature in Eco preset",
@@ -286,88 +353,119 @@
"frost_temp": "Temperature in Frost protection preset", "frost_temp": "Temperature in Frost protection preset",
"eco_ac_temp": "Temperature in Eco preset for AC mode", "eco_ac_temp": "Temperature in Eco preset for AC mode",
"comfort_ac_temp": "Temperature in Comfort preset for AC mode", "comfort_ac_temp": "Temperature in Comfort preset for AC mode",
"boost_ac_temp": "Temperature in Boost preset for AC mode" "boost_ac_temp": "Temperature in Boost preset for AC mode",
"use_presets_central_config": "Check to use the central presets configuration. Uncheck to use a specific presets configuration for this VTherm"
} }
}, },
"window": { "window": {
"title": "Window management", "title": "Window - {name}",
"description": "Open window management.\nLeave corresponding entity_id empty if not used\nYou can also configure automatic window open detection based on temperature decrease", "description": "Open window management.\nYou can also configure automatic window open detection based on temperature decrease",
"data": { "data": {
"window_sensor_entity_id": "Window sensor entity id", "window_sensor_entity_id": "Window sensor entity id",
"window_delay": "Window sensor delay (seconds)", "window_delay": "Window sensor delay (seconds)",
"window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)", "window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)",
"window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)", "window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)",
"window_auto_max_duration": "Maximum duration of automatic window open detection (in min)" "window_auto_max_duration": "Maximum duration of automatic window open detection (in min)",
"use_window_central_config": "Use central window configuration",
"window_action": "Action"
}, },
"data_description": { "data_description": {
"window_sensor_entity_id": "Leave empty if no window sensor should be used", "window_sensor_entity_id": "Leave empty if no window sensor should be used and to use the automatic detection",
"window_delay": "The delay in seconds before sensor detection is taken into account", "window_delay": "The delay in seconds before sensor detection is taken into account",
"window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used", "window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used",
"window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used", "window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used",
"window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used" "window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used",
"use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm",
"window_action": "Action to do if window is deteted as open"
} }
}, },
"motion": { "motion": {
"title": "Motion management", "title": "Motion - {name}",
"description": "Motion sensor management. Preset can switch automatically depending of a motion detection\nLeave corresponding entity_id empty if not used.\nmotion_preset and no_motion_preset should be set to the corresponding preset name", "description": "Motion management. Preset can switch automatically depending of a motion detection\nmotion_preset and no_motion_preset should be set to the corresponding preset name",
"data": { "data": {
"motion_sensor_entity_id": "Motion sensor entity id", "motion_sensor_entity_id": "Motion sensor entity id",
"motion_delay": "Activation delay", "motion_delay": "Activation delay",
"motion_off_delay": "Deactivation delay", "motion_off_delay": "Deactivation delay",
"motion_preset": "Motion preset", "motion_preset": "Motion preset",
"no_motion_preset": "No motion preset" "no_motion_preset": "No motion preset",
"use_motion_central_config": "Use central motion configuration"
}, },
"data_description": { "data_description": {
"motion_sensor_entity_id": "The entity id of the motion sensor", "motion_sensor_entity_id": "The entity id of the motion sensor",
"motion_delay": "Motion activation delay (seconds)", "motion_delay": "Motion activation delay (seconds)",
"motion_off_delay": "Motion deactivation delay (seconds)", "motion_off_delay": "Motion deactivation delay (seconds)",
"motion_preset": "Preset to use when motion is detected", "motion_preset": "Preset to use when motion is detected",
"no_motion_preset": "Preset to use when no motion is detected" "no_motion_preset": "Preset to use when no motion is detected",
"use_motion_central_config": "Check to use the central motion configuration. Uncheck to use a specific motion configuration for this VTherm"
} }
}, },
"power": { "power": {
"title": "Power management", "title": "Power - {name}",
"description": "Power management attributes.\nGives the power and max power sensor of your home.\nThen specify the power consumption of the heater when on.\nAll sensors and device power should have the same unit (kW or W).\nLeave corresponding entity_id empty if not used.", "description": "Power management attributes.\nGives the power and max power sensor of your home.\nThen specify the power consumption of the heater when on.\nAll sensors and device power should have the same unit (kW or W).",
"data": { "data": {
"power_sensor_entity_id": "Power",
"max_power_sensor_entity_id": "Max power",
"power_temp": "Shedding temperature",
"use_power_central_config": "Use central power configuration"
},
"data_description": {
"power_sensor_entity_id": "Power sensor entity id", "power_sensor_entity_id": "Power sensor entity id",
"max_power_sensor_entity_id": "Max power sensor entity id", "max_power_sensor_entity_id": "Max power sensor entity id",
"power_temp": "Temperature for Power shedding" "power_temp": "Temperature for Power shedding",
"use_power_central_config": "Check to use the central power configuration. Uncheck to use a specific power configuration for this VTherm"
} }
}, },
"presence": { "presence": {
"title": "Presence management", "title": "Presence - {name}",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present).\nThen specify either the preset to use when presence sensor is false or the offset in temperature to apply.\nIf preset is given, the offset will not be used.\nLeave corresponding entity_id empty if not used.", "description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present) and give the corresponding temperature preset setting.",
"data": { "data": {
"presence_sensor_entity_id": "Presence sensor",
"eco_away_temp": "Eco away preset",
"comfort_away_temp": "Comfort away preset",
"boost_away_temp": "Boost away preset",
"frost_away_temp": "Frost protection preset",
"eco_ac_away_temp": "Eco away preset in AC mode",
"comfort_ac_away_temp": "Comfort away preset in AC mode",
"boost_ac_away_temp": "Boost away preset in AC mode",
"use_presence_central_config": "Use central presence configuration"
},
"data_description": {
"presence_sensor_entity_id": "Presence sensor entity id", "presence_sensor_entity_id": "Presence sensor entity id",
"eco_away_temp": "Temperature in Eco preset when no presence", "eco_away_temp": "Temperature in Eco preset when no presence",
"comfort_away_temp": "Temperature in Comfort preset when no presence", "comfort_away_temp": "Temperature in Comfort preset when no presence",
"boost_away_temp": "Temperature in Boost preset when no presence", "boost_away_temp": "Temperature in Boost preset when no presence",
"frost_away_temp": "Temperature in Frost protection preset when no presence",
"eco_ac_away_temp": "Temperature in Eco preset when no presence in AC mode", "eco_ac_away_temp": "Temperature in Eco preset when no presence in AC mode",
"comfort_ac_away_temp": "Temperature in Comfort preset when no presence in AC mode", "comfort_ac_away_temp": "Temperature in Comfort preset when no presence in AC mode",
"boost_ac_away_temp": "Temperature in Boost preset when no presence in AC mode" "boost_ac_away_temp": "Temperature in Boost preset when no presence in AC mode",
"use_presence_central_config": "Check to use the central presence configuration. Uncheck to use a specific presence configuration for this VTherm"
} }
}, },
"advanced": { "advanced": {
"title": "Advanced parameters", "title": "Advanced - {name}",
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThese parameters can lead to very poor temperature control or bad power regulation.", "description": "Advanced parameters. Leave the default values if you don't know what you are doing.\nThese parameters can lead to very poor temperature control or bad power regulation.",
"data": { "data": {
"minimal_activation_delay": "Minimal activation delay", "minimal_activation_delay": "Minimal activation delay",
"security_delay_min": "Security delay (in minutes)", "security_delay_min": "Safety delay (in minutes)",
"security_min_on_percent": "Minimal power percent to enable security mode", "security_min_on_percent": "Minimal power percent to enable safety mode",
"security_default_on_percent": "Power percent to use in security mode" "security_default_on_percent": "Power percent to use in safety mode",
"use_advanced_central_config": "Use central advanced configuration"
}, },
"data_description": { "data_description": {
"minimal_activation_delay": "Delay in seconds under which the equipment will not be activated", "minimal_activation_delay": "Delay in seconds under which the equipment will not be activated",
"security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a security off state", "security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state",
"security_min_on_percent": "Minimal heating percent value for security preset activation. Below this amount of power percent the thermostat won't go into security preset", "security_min_on_percent": "Minimal heating percent value for safety preset activation. Below this amount of power percent the thermostat won't go into safety preset",
"security_default_on_percent": "The default heating power percent value in security preset. Set to 0 to switch off heater in security 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"
} }
} }
}, },
"error": { "error": {
"unknown": "Unexpected error", "unknown": "Unexpected error",
"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.",
"service_configuration_format": "The format of the service configuration is wrong"
}, },
"abort": { "abort": {
"already_configured": "Device is already configured" "already_configured": "Device is already configured"
@@ -376,6 +474,7 @@
"selector": { "selector": {
"thermostat_type": { "thermostat_type": {
"options": { "options": {
"thermostat_central_config": "Central configuration",
"thermostat_over_switch": "Thermostat over a switch", "thermostat_over_switch": "Thermostat over a switch",
"thermostat_over_climate": "Thermostat over a climate", "thermostat_over_climate": "Thermostat over a climate",
"thermostat_over_valve": "Thermostat over a valve" "thermostat_over_valve": "Thermostat over a valve"
@@ -399,6 +498,22 @@
"auto_fan_high": "High", "auto_fan_high": "High",
"auto_fan_turbo": "Turbo" "auto_fan_turbo": "Turbo"
} }
},
"window_action": {
"options": {
"window_turn_off": "Turn off",
"window_fan_only": "Fan only",
"window_frost_temp": "Frost protect",
"window_eco_temp": "Eco"
}
},
"presets": {
"options": {
"frost": "Frost protect",
"eco": "Eco",
"comfort": "Comfort",
"boost": "Boost"
}
} }
}, },
"entity": { "entity": {
@@ -408,7 +523,7 @@
"preset_mode": { "preset_mode": {
"state": { "state": {
"power": "Shedding", "power": "Shedding",
"security": "Security", "security": "Safety",
"none": "Manual" "none": "Manual"
} }
} }

View File

@@ -3,12 +3,13 @@
import logging import logging
from datetime import timedelta, datetime from datetime import timedelta, datetime
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, State, callback
from homeassistant.helpers.event import ( from homeassistant.helpers.event import (
async_track_state_change_event, async_track_state_change_event,
async_track_time_interval, async_track_time_interval,
EventStateChangedData,
) )
from homeassistant.helpers.typing import EventType as HASSEventType
from homeassistant.components.climate import ( from homeassistant.components.climate import (
HVACAction, HVACAction,
HVACMode, HVACMode,
@@ -16,7 +17,7 @@ from homeassistant.components.climate import (
) )
from .commons import NowClass, round_to_nearest from .commons import NowClass, round_to_nearest
from .base_thermostat import BaseThermostat from .base_thermostat import BaseThermostat, ConfigData
from .pi_algorithm import PITemperatureRegulator from .pi_algorithm import PITemperatureRegulator
from .const import ( from .const import (
@@ -59,19 +60,19 @@ _LOGGER = logging.getLogger(__name__)
class ThermostatOverClimate(BaseThermostat): class ThermostatOverClimate(BaseThermostat):
"""Representation of a base class for a Versatile Thermostat over a climate""" """Representation of a base class for a Versatile Thermostat over a climate"""
_auto_regulation_mode: str = None _auto_regulation_mode: str | None = None
_regulation_algo = None _regulation_algo = None
_regulated_target_temp: float = None _regulated_target_temp: float | None = None
_auto_regulation_dtemp: float = None _auto_regulation_dtemp: float | None = None
_auto_regulation_period_min: int = None _auto_regulation_period_min: int | None = None
_last_regulation_change: datetime = None _last_regulation_change: datetime | None = None
# The fan mode configured in configEntry # The fan mode configured in configEntry
_auto_fan_mode: str = None _auto_fan_mode: str | None = None
# The current fan mode (could be change by service call) # The current fan mode (could be change by service call)
_current_auto_fan_mode: str = None _current_auto_fan_mode: str | None = None
# The fan_mode name depending of the current_mode # The fan_mode name depending of the current_mode
_auto_activated_fan_mode: str = None _auto_activated_fan_mode: str | None = None
_auto_deactivated_fan_mode: str = None _auto_deactivated_fan_mode: str | None = None
_entity_component_unrecorded_attributes = ( _entity_component_unrecorded_attributes = (
BaseThermostat._entity_component_unrecorded_attributes.union( BaseThermostat._entity_component_unrecorded_attributes.union(
@@ -94,7 +95,9 @@ class ThermostatOverClimate(BaseThermostat):
) )
) )
def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None: def __init__(
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigData
):
"""Initialize the thermostat over switch.""" """Initialize the thermostat over switch."""
# super.__init__ calls post_init at the end. So it must be called after regulation initialization # super.__init__ calls post_init at the end. So it must be called after regulation initialization
super().__init__(hass, unique_id, name, entry_infos) super().__init__(hass, unique_id, name, entry_infos)
@@ -127,7 +130,7 @@ class ThermostatOverClimate(BaseThermostat):
return HVACAction.OFF return HVACAction.OFF
@overrides @overrides
async def _async_internal_set_temperature(self, temperature): 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"""
await super()._async_internal_set_temperature(temperature) await super()._async_internal_set_temperature(temperature)
@@ -136,6 +139,11 @@ class ThermostatOverClimate(BaseThermostat):
async def _send_regulated_temperature(self, force=False): async def _send_regulated_temperature(self, force=False):
"""Sends the regulated temperature to all underlying""" """Sends the regulated temperature to all underlying"""
if self.hvac_mode == HVACMode.OFF:
_LOGGER.debug("%s - don't send regulated temperature cause VTherm is off ")
return
_LOGGER.info( _LOGGER.info(
"%s - Calling ThermostatClimate._send_regulated_temperature force=%s", "%s - Calling ThermostatClimate._send_regulated_temperature force=%s",
self, self,
@@ -157,7 +165,6 @@ class ThermostatOverClimate(BaseThermostat):
self._regulated_target_temp = self.target_temperature self._regulated_target_temp = self.target_temperature
_LOGGER.info("%s - regulation calculation will be done", self) _LOGGER.info("%s - regulation calculation will be done", self)
self._last_regulation_change = now
new_regulated_temp = round_to_nearest( new_regulated_temp = round_to_nearest(
self._regulation_algo.calculate_regulated_temperature( self._regulation_algo.calculate_regulated_temperature(
@@ -183,6 +190,7 @@ class ThermostatOverClimate(BaseThermostat):
new_regulated_temp, new_regulated_temp,
) )
self._last_regulation_change = now
for under in self._underlyings: for under in self._underlyings:
await under.set_temperature( await under.set_temperature(
self.regulated_target_temp, self._attr_max_temp, self._attr_min_temp self.regulated_target_temp, self._attr_max_temp, self._attr_min_temp
@@ -234,49 +242,49 @@ class ThermostatOverClimate(BaseThermostat):
await self.async_set_fan_mode(self._auto_deactivated_fan_mode) await self.async_set_fan_mode(self._auto_deactivated_fan_mode)
@overrides @overrides
def post_init(self, entry_infos): def post_init(self, config_entry: ConfigData):
"""Initialize the Thermostat""" """Initialize the Thermostat"""
super().post_init(entry_infos) super().post_init(config_entry)
for climate in [ for climate in [
CONF_CLIMATE, CONF_CLIMATE,
CONF_CLIMATE_2, CONF_CLIMATE_2,
CONF_CLIMATE_3, CONF_CLIMATE_3,
CONF_CLIMATE_4, CONF_CLIMATE_4,
]: ]:
if entry_infos.get(climate): if config_entry.get(climate):
self._underlyings.append( self._underlyings.append(
UnderlyingClimate( UnderlyingClimate(
hass=self._hass, hass=self._hass,
thermostat=self, thermostat=self,
climate_entity_id=entry_infos.get(climate), climate_entity_id=config_entry.get(climate),
) )
) )
self.choose_auto_regulation_mode( self.choose_auto_regulation_mode(
entry_infos.get(CONF_AUTO_REGULATION_MODE) config_entry.get(CONF_AUTO_REGULATION_MODE)
if entry_infos.get(CONF_AUTO_REGULATION_MODE) is not None if config_entry.get(CONF_AUTO_REGULATION_MODE) is not None
else CONF_AUTO_REGULATION_NONE else CONF_AUTO_REGULATION_NONE
) )
self._auto_regulation_dtemp = ( self._auto_regulation_dtemp = (
entry_infos.get(CONF_AUTO_REGULATION_DTEMP) config_entry.get(CONF_AUTO_REGULATION_DTEMP)
if entry_infos.get(CONF_AUTO_REGULATION_DTEMP) is not None if config_entry.get(CONF_AUTO_REGULATION_DTEMP) is not None
else 0.5 else 0.5
) )
self._auto_regulation_period_min = ( self._auto_regulation_period_min = (
entry_infos.get(CONF_AUTO_REGULATION_PERIOD_MIN) config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN)
if entry_infos.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None if config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None
else 5 else 5
) )
self._auto_fan_mode = ( self._auto_fan_mode = (
entry_infos.get(CONF_AUTO_FAN_MODE) config_entry.get(CONF_AUTO_FAN_MODE)
if entry_infos.get(CONF_AUTO_FAN_MODE) is not None if config_entry.get(CONF_AUTO_FAN_MODE) is not None
else CONF_AUTO_FAN_NONE else CONF_AUTO_FAN_NONE
) )
def choose_auto_regulation_mode(self, auto_regulation_mode): 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
if self._auto_regulation_mode == CONF_AUTO_REGULATION_LIGHT: if self._auto_regulation_mode == CONF_AUTO_REGULATION_LIGHT:
@@ -352,7 +360,7 @@ class ThermostatOverClimate(BaseThermostat):
self.target_temperature, 0, 0, 0, 0, 0.1, 0 self.target_temperature, 0, 0, 0, 0, 0.1, 0
) )
def choose_auto_fan_mode(self, auto_fan_mode): def choose_auto_fan_mode(self, auto_fan_mode: str):
"""Choose the correct fan mode depending of the underlying capacities and the configuration""" """Choose the correct fan mode depending of the underlying capacities and the configuration"""
self._current_auto_fan_mode = auto_fan_mode self._current_auto_fan_mode = auto_fan_mode
@@ -364,7 +372,7 @@ class ThermostatOverClimate(BaseThermostat):
self._auto_activated_fan_mode = self._auto_deactivated_fan_mode = None self._auto_activated_fan_mode = self._auto_deactivated_fan_mode = None
return return
def find_fan_mode(fan_modes, fan_mode) -> str: def find_fan_mode(fan_modes: list[str], fan_mode: str) -> str | None:
"""Return the fan_mode if it exist of None if not""" """Return the fan_mode if it exist of None if not"""
try: try:
return fan_mode if fan_modes.index(fan_mode) >= 0 else None return fan_mode if fan_modes.index(fan_mode) >= 0 else None
@@ -422,10 +430,11 @@ class ThermostatOverClimate(BaseThermostat):
) )
# init auto_regulation_mode # init auto_regulation_mode
self.choose_auto_regulation_mode(self._auto_regulation_mode) # Issue 325 - do only once (in post_init and not here)
# self.choose_auto_regulation_mode(self._auto_regulation_mode)
@overrides @overrides
def restore_specific_previous_state(self, old_state): def restore_specific_previous_state(self, old_state: State):
"""Restore my specific attributes from previous state""" """Restore my specific attributes from previous state"""
old_error = old_state.attributes.get("regulation_accumulated_error") old_error = old_state.attributes.get("regulation_accumulated_error")
if old_error: if old_error:
@@ -521,7 +530,11 @@ class ThermostatOverClimate(BaseThermostat):
return return
added_energy = 0 added_energy = 0
if self.is_over_climate and self._underlying_climate_delta_t is not None: if (
self.is_over_climate
and self._underlying_climate_delta_t is not None
and self._device_power
):
added_energy = self._device_power * self._underlying_climate_delta_t added_energy = self._device_power * self._underlying_climate_delta_t
self._total_energy += added_energy self._total_energy += added_energy
@@ -533,7 +546,7 @@ class ThermostatOverClimate(BaseThermostat):
) )
@callback @callback
async def _async_climate_changed(self, event): async def _async_climate_changed(self, event: HASSEventType[EventStateChangedData]):
"""Handle unerdlying climate state changes. """Handle unerdlying climate state changes.
This method takes the underlying values and update the VTherm with them. This method takes the underlying values and update the VTherm with them.
To avoid loops (issues #121 #101 #95 #99), we discard the event if it is received To avoid loops (issues #121 #101 #95 #99), we discard the event if it is received
@@ -543,7 +556,7 @@ class ThermostatOverClimate(BaseThermostat):
which is important for feedaback and which cannot generates loops. which is important for feedaback and which cannot generates loops.
""" """
async def end_climate_changed(changes): async def end_climate_changed(changes: bool):
"""To end the event management""" """To end the event management"""
if changes: if changes:
self.async_write_ha_state() self.async_write_ha_state()
@@ -596,8 +609,9 @@ class ThermostatOverClimate(BaseThermostat):
# new_hvac_mode = HVACMode.OFF # new_hvac_mode = HVACMode.OFF
_LOGGER.info( _LOGGER.info(
"%s - Underlying climate changed. Event.new_hvac_mode is %s, current_hvac_mode=%s, new_hvac_action=%s, old_hvac_action=%s", "%s - Underlying climate %s changed. Event.new_hvac_mode is %s, current_hvac_mode=%s, new_hvac_action=%s, old_hvac_action=%s",
self, self,
new_state.entity_id,
new_hvac_mode, new_hvac_mode,
self._hvac_mode, self._hvac_mode,
new_hvac_action, new_hvac_action,
@@ -653,7 +667,7 @@ class ThermostatOverClimate(BaseThermostat):
) )
changes = True changes = True
# Issue #120 - Some TRV are chaning target temperature a very long time (6 sec) after the change. # Issue #120 - Some TRV are changing target temperature a very long time (6 sec) after the change.
# In that case a loop is possible if a user change multiple times during this 6 sec. # In that case a loop is possible if a user change multiple times during this 6 sec.
if new_state_date_updated and self._last_change_time: if new_state_date_updated and self._last_change_time:
delta = (new_state_date_updated - self._last_change_time).total_seconds() delta = (new_state_date_updated - self._last_change_time).total_seconds()
@@ -679,12 +693,31 @@ class ThermostatOverClimate(BaseThermostat):
] ]
and self._hvac_mode != new_hvac_mode and self._hvac_mode != new_hvac_mode
): ):
changes = True
self._hvac_mode = new_hvac_mode
# Update all underlyings state # Update all underlyings state
# Issue #334 - if all underlyings are not aligned with the same hvac_mode don't change the underlying and wait they are aligned
if self.is_over_climate: if self.is_over_climate:
for under in self._underlyings:
if (
under.entity_id != new_state.entity_id
and under.hvac_mode != self._hvac_mode
):
_LOGGER.info(
"%s - the underlying's hvac_mode %s is not aligned with VTherm hvac_mode %s. So we don't diffuse the change to all other underlyings to avoid loops",
under,
under.hvac_mode,
self._hvac_mode,
)
return
_LOGGER.debug(
"%s - All underlyings have the same hvac_mode, so VTherm will send the new hvac mode %s",
self,
new_hvac_mode,
)
for under in self._underlyings: for under in self._underlyings:
await under.set_hvac_mode(new_hvac_mode) await under.set_hvac_mode(new_hvac_mode)
changes = True
self._hvac_mode = new_hvac_mode
# A quick win to known if it has change by using the self._attr_fan_mode and not only underlying[0].fan_mode # A quick win to known if it has change by using the self._attr_fan_mode and not only underlying[0].fan_mode
if new_fan_mode != self._attr_fan_mode: if new_fan_mode != self._attr_fan_mode:
@@ -716,7 +749,7 @@ class ThermostatOverClimate(BaseThermostat):
await end_climate_changed(changes) await end_climate_changed(changes)
@overrides @overrides
async def async_control_heating(self, force=False, _=None): 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, _)
@@ -728,27 +761,27 @@ class ThermostatOverClimate(BaseThermostat):
return ret return ret
@property @property
def auto_regulation_mode(self): def auto_regulation_mode(self) -> str | None:
"""Get the regulation mode""" """Get the regulation mode"""
return self._auto_regulation_mode return self._auto_regulation_mode
@property @property
def auto_fan_mode(self): def auto_fan_mode(self) -> str | None:
"""Get the auto fan mode""" """Get the auto fan mode"""
return self._auto_fan_mode return self._auto_fan_mode
@property @property
def regulated_target_temp(self): def regulated_target_temp(self) -> float | None:
"""Get the regulated target temperature""" """Get the regulated target temperature"""
return self._regulated_target_temp return self._regulated_target_temp
@property @property
def is_regulated(self): def is_regulated(self) -> bool:
"""Check if the ThermostatOverClimate is regulated""" """Check if the ThermostatOverClimate is regulated"""
return self.auto_regulation_mode != CONF_AUTO_REGULATION_NONE return self.auto_regulation_mode != CONF_AUTO_REGULATION_NONE
@property @property
def hvac_modes(self): def hvac_modes(self) -> list[HVACMode]:
"""List of available operation modes.""" """List of available operation modes."""
if self.underlying_entity(0): if self.underlying_entity(0):
return self.underlying_entity(0).hvac_modes return self.underlying_entity(0).hvac_modes
@@ -821,13 +854,14 @@ class ThermostatOverClimate(BaseThermostat):
return self._support_flags return self._support_flags
@property # We keep the step configured for the VTherm and not the step of the underlying
def target_temperature_step(self) -> float | None: # @property
"""Return the supported step of target temperature.""" # def target_temperature_step(self) -> float | None:
if self.underlying_entity(0): # """Return the supported step of target temperature."""
return self.underlying_entity(0).target_temperature_step # if self.underlying_entity(0):
# return self.underlying_entity(0).target_temperature_step
return None #
# return None
@property @property
def target_temperature_high(self) -> float | None: def target_temperature_high(self) -> float | None:
@@ -914,7 +948,7 @@ class ThermostatOverClimate(BaseThermostat):
await under.async_turn_aux_heat_off() await under.async_turn_aux_heat_off()
@overrides @overrides
async def async_set_fan_mode(self, fan_mode): async def async_set_fan_mode(self, fan_mode: str):
"""Set new target fan mode.""" """Set new target fan mode."""
_LOGGER.info("%s - Set fan mode: %s", self, fan_mode) _LOGGER.info("%s - Set fan mode: %s", self, fan_mode)
if fan_mode is None: if fan_mode is None:
@@ -947,7 +981,7 @@ class ThermostatOverClimate(BaseThermostat):
self._swing_mode = swing_mode self._swing_mode = swing_mode
self.async_write_ha_state() self.async_write_ha_state()
async def service_set_auto_regulation_mode(self, auto_regulation_mode): async def service_set_auto_regulation_mode(self, auto_regulation_mode: str):
"""Called by a service call: """Called by a service call:
service: versatile_thermostat.set_auto_regulation_mode service: versatile_thermostat.set_auto_regulation_mode
data: data:
@@ -976,7 +1010,7 @@ class ThermostatOverClimate(BaseThermostat):
await self._send_regulated_temperature() await self._send_regulated_temperature()
self.update_custom_attributes() self.update_custom_attributes()
async def service_set_auto_fan_mode(self, auto_fan_mode): async def service_set_auto_fan_mode(self, auto_fan_mode: str):
"""Called by a service call: """Called by a service call:
service: versatile_thermostat.set_auto_fan_mode service: versatile_thermostat.set_auto_fan_mode
data: data:

View File

@@ -3,7 +3,11 @@
""" A climate over switch classe """ """ A climate over switch classe """
import logging import logging
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.event import (
async_track_state_change_event,
EventStateChangedData,
)
from homeassistant.helpers.typing import EventType as HASSEventType
from homeassistant.components.climate import HVACMode from homeassistant.components.climate import HVACMode
from .const import ( from .const import (
@@ -15,7 +19,7 @@ from .const import (
overrides, overrides,
) )
from .base_thermostat import BaseThermostat from .base_thermostat import BaseThermostat, ConfigData
from .underlyings import UnderlyingSwitch from .underlyings import UnderlyingSwitch
from .prop_algorithm import PropAlgorithm from .prop_algorithm import PropAlgorithm
@@ -48,10 +52,10 @@ class ThermostatOverSwitch(BaseThermostat):
) )
# useless for now # useless for now
# def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None: # def __init__(self, hass: HomeAssistant, unique_id, name, config_entry) -> None:
# """Initialize the thermostat over switch.""" # """Initialize the thermostat over switch."""
# super().__init__(hass, unique_id, name, entry_infos) # super().__init__(hass, unique_id, name, config_entry)
_is_inversed: bool = None _is_inversed: bool | None = None
@property @property
def is_over_switch(self) -> bool: def is_over_switch(self) -> bool:
@@ -72,10 +76,10 @@ class ThermostatOverSwitch(BaseThermostat):
return None return None
@overrides @overrides
def post_init(self, entry_infos): def post_init(self, config_entry: ConfigData):
"""Initialize the Thermostat""" """Initialize the Thermostat"""
super().post_init(entry_infos) super().post_init(config_entry)
self._prop_algorithm = PropAlgorithm( self._prop_algorithm = PropAlgorithm(
self._proportional_function, self._proportional_function,
@@ -85,13 +89,13 @@ class ThermostatOverSwitch(BaseThermostat):
self._minimal_activation_delay, self._minimal_activation_delay,
) )
lst_switches = [entry_infos.get(CONF_HEATER)] lst_switches = [config_entry.get(CONF_HEATER)]
if entry_infos.get(CONF_HEATER_2): if config_entry.get(CONF_HEATER_2):
lst_switches.append(entry_infos.get(CONF_HEATER_2)) lst_switches.append(config_entry.get(CONF_HEATER_2))
if entry_infos.get(CONF_HEATER_3): if config_entry.get(CONF_HEATER_3):
lst_switches.append(entry_infos.get(CONF_HEATER_3)) lst_switches.append(config_entry.get(CONF_HEATER_3))
if entry_infos.get(CONF_HEATER_4): if config_entry.get(CONF_HEATER_4):
lst_switches.append(entry_infos.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):
@@ -104,7 +108,7 @@ class ThermostatOverSwitch(BaseThermostat):
) )
) )
self._is_inversed = entry_infos.get(CONF_INVERSE_SWITCH) is True self._is_inversed = config_entry.get(CONF_INVERSE_SWITCH) is True
self._should_relaunch_control_heating = False self._should_relaunch_control_heating = False
@overrides @overrides
@@ -200,7 +204,7 @@ class ThermostatOverSwitch(BaseThermostat):
) )
@callback @callback
def _async_switch_changed(self, event): def _async_switch_changed(self, event: HASSEventType[EventStateChangedData]):
"""Handle heater switch state changes.""" """Handle heater switch state changes."""
new_state = event.data.get("new_state") new_state = event.data.get("new_state")
old_state = event.data.get("old_state") old_state = event.data.get("old_state")
@@ -208,5 +212,6 @@ class ThermostatOverSwitch(BaseThermostat):
return return
if old_state is None: if old_state is None:
self.hass.create_task(self._check_initial_state()) self.hass.create_task(self._check_initial_state())
self.async_write_ha_state() self.async_write_ha_state()
self.update_custom_attributes() self.update_custom_attributes()

View File

@@ -1,54 +1,104 @@
# pylint: disable=line-too-long # pylint: disable=line-too-long
""" A climate over switch classe """ """ A climate over switch classe """
import logging import logging
from datetime import timedelta from datetime import timedelta, datetime
from homeassistant.helpers.event import async_track_state_change_event, async_track_time_interval from homeassistant.helpers.event import (
from homeassistant.core import callback async_track_state_change_event,
async_track_time_interval,
EventStateChangedData,
)
from homeassistant.helpers.typing import EventType as HASSEventType
from homeassistant.core import HomeAssistant, callback
from homeassistant.components.climate import HVACMode from homeassistant.components.climate import HVACMode
from .base_thermostat import BaseThermostat from .base_thermostat import BaseThermostat, ConfigData
from .prop_algorithm import PropAlgorithm from .prop_algorithm import PropAlgorithm
from .const import CONF_VALVE, CONF_VALVE_2, CONF_VALVE_3, CONF_VALVE_4, overrides from .const import (
CONF_VALVE,
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
# This is not really self-regulation but regulation here
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
overrides,
)
from .underlyings import UnderlyingValve from .underlyings import UnderlyingValve
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class ThermostatOverValve(BaseThermostat): class ThermostatOverValve(BaseThermostat):
"""Representation of a class for a Versatile Thermostat over a Valve""" """Representation of a class for a Versatile Thermostat over a Valve"""
_entity_component_unrecorded_attributes = BaseThermostat._entity_component_unrecorded_attributes.union(frozenset( _entity_component_unrecorded_attributes = (
{ BaseThermostat._entity_component_unrecorded_attributes.union(
"is_over_valve", "underlying_valve_0", "underlying_valve_1", frozenset(
"underlying_valve_2", "underlying_valve_3", "on_time_sec", "off_time_sec", {
"cycle_min", "function", "tpi_coef_int", "tpi_coef_ext" "is_over_valve",
})) "underlying_valve_0",
"underlying_valve_1",
"underlying_valve_2",
"underlying_valve_3",
"on_time_sec",
"off_time_sec",
"cycle_min",
"function",
"tpi_coef_int",
"tpi_coef_ext",
"auto_regulation_dpercent",
"auto_regulation_period_min",
"last_calculation_timestamp",
}
)
)
)
# Useless for now def __init__(
# def __init__(self, hass: HomeAssistant, unique_id, name, entry_infos) -> None: self, hass: HomeAssistant, unique_id: str, name: str, config_entry: ConfigData
# """Initialize the thermostat over switch.""" ):
# super().__init__(hass, unique_id, name, entry_infos) """Initialize the thermostat over switch."""
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
# Call to super must be done after initialization because it calls post_init at the end
super().__init__(hass, unique_id, name, config_entry)
@property @property
def is_over_valve(self) -> bool: def is_over_valve(self) -> bool:
""" True if the Thermostat is over_valve""" """True if the Thermostat is over_valve"""
return True return True
@property @property
def valve_open_percent(self) -> int: def valve_open_percent(self) -> int:
""" Gives the percentage of valve needed""" """Gives the percentage of valve needed"""
if self._hvac_mode == HVACMode.OFF: if self._hvac_mode == HVACMode.OFF:
return 0 return 0
else: else:
return round(max(0, min(self.proportional_algorithm.on_percent, 1)) * 100) return self._valve_open_percent
@overrides @overrides
def post_init(self, entry_infos): def post_init(self, config_entry: ConfigData):
""" Initialize the Thermostat""" """Initialize the Thermostat"""
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
)
super().post_init(entry_infos)
self._prop_algorithm = PropAlgorithm( self._prop_algorithm = PropAlgorithm(
self._proportional_function, self._proportional_function,
self._tpi_coef_int, self._tpi_coef_int,
@@ -57,21 +107,17 @@ class ThermostatOverValve(BaseThermostat):
self._minimal_activation_delay, self._minimal_activation_delay,
) )
lst_valves = [entry_infos.get(CONF_VALVE)] lst_valves = [config_entry.get(CONF_VALVE)]
if entry_infos.get(CONF_VALVE_2): if config_entry.get(CONF_VALVE_2):
lst_valves.append(entry_infos.get(CONF_VALVE_2)) lst_valves.append(config_entry.get(CONF_VALVE_2))
if entry_infos.get(CONF_VALVE_3): if config_entry.get(CONF_VALVE_3):
lst_valves.append(entry_infos.get(CONF_VALVE_3)) lst_valves.append(config_entry.get(CONF_VALVE_3))
if entry_infos.get(CONF_VALVE_4): if config_entry.get(CONF_VALVE_4):
lst_valves.append(entry_infos.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(
UnderlyingValve( UnderlyingValve(hass=self._hass, thermostat=self, valve_entity_id=valve)
hass=self._hass,
thermostat=self,
valve_entity_id=valve
)
) )
self._should_relaunch_control_heating = False self._should_relaunch_control_heating = False
@@ -89,7 +135,7 @@ class ThermostatOverValve(BaseThermostat):
async_track_state_change_event( async_track_state_change_event(
self.hass, [valve.entity_id], self._async_valve_changed self.hass, [valve.entity_id], self._async_valve_changed
) )
) )
# Start the control_heating # Start the control_heating
# starts a cycle # starts a cycle
@@ -102,34 +148,39 @@ class ThermostatOverValve(BaseThermostat):
) )
@callback @callback
async def _async_valve_changed(self, event): async def _async_valve_changed(self, event: HASSEventType[EventStateChangedData]):
"""Handle unerdlying valve state changes. """Handle unerdlying valve state changes.
This method just log the change. It changes nothing to avoid loops. This method just log the change. It changes nothing to avoid loops.
""" """
new_state = event.data.get("new_state") new_state = event.data.get("new_state")
_LOGGER.debug("%s - _async_valve_changed new_state is %s", self, new_state.state) _LOGGER.debug(
"%s - _async_valve_changed new_state is %s", self, new_state.state
)
@overrides @overrides
def update_custom_attributes(self): def update_custom_attributes(self):
""" Custom attributes """ """Custom attributes"""
super().update_custom_attributes() super().update_custom_attributes()
self._attr_extra_state_attributes["valve_open_percent"] = self.valve_open_percent self._attr_extra_state_attributes[
"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._attr_extra_state_attributes["underlying_valve_0"] = self._underlyings[
self._underlyings[0].entity_id) 0
].entity_id
self._attr_extra_state_attributes["underlying_valve_1"] = ( self._attr_extra_state_attributes["underlying_valve_1"] = (
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
) )
self._attr_extra_state_attributes["underlying_valve_2"] = ( self._attr_extra_state_attributes["underlying_valve_2"] = (
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
) )
self._attr_extra_state_attributes["underlying_valve_3"] = ( self._attr_extra_state_attributes["underlying_valve_3"] = (
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None 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"
] = self._prop_algorithm.on_percent ] = self._prop_algorithm.on_percent
self._attr_extra_state_attributes[ self._attr_extra_state_attributes[
"on_time_sec" "on_time_sec"
] = self._prop_algorithm.on_time_sec ] = self._prop_algorithm.on_time_sec
@@ -140,6 +191,17 @@ class ThermostatOverValve(BaseThermostat):
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[
"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() self.async_write_ha_state()
_LOGGER.debug( _LOGGER.debug(
@@ -153,7 +215,21 @@ class ThermostatOverValve(BaseThermostat):
"""A utility function to force the calculation of a the algo and """A utility function to force the calculation of a the algo and
update the custom attributes and write the state update the custom attributes and write the state
""" """
_LOGGER.debug("%s - recalculate all", self) _LOGGER.debug("%s - recalculate the open percent", self)
# 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._prop_algorithm.calculate(
self._target_temp, self._target_temp,
self._cur_temp, self._cur_temp,
@@ -161,11 +237,34 @@ class ThermostatOverValve(BaseThermostat):
self._hvac_mode == HVACMode.COOL, self._hvac_mode == HVACMode.COOL,
) )
for under in self._underlyings: new_valve_percent = round(
under.set_valve_open_percent( max(0, min(self.proportional_algorithm.on_percent, 1)) * 100
self._prop_algorithm.on_percent )
dpercent = new_valve_percent - self.valve_open_percent
if (
dpercent >= -1 * self._auto_regulation_dpercent
and 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:
under.set_valve_open_percent()
self._last_calculation_timestamp = now
self.update_custom_attributes() self.update_custom_attributes()
self.async_write_ha_state() self.async_write_ha_state()
@@ -185,4 +284,4 @@ class ThermostatOverValve(BaseThermostat):
self, self,
added_energy, added_energy,
self._total_energy, self._total_energy,
) )

View File

@@ -4,21 +4,38 @@
"flow_title": "Versatile Thermostat configuration", "flow_title": "Versatile Thermostat configuration",
"step": { "step": {
"user": { "user": {
"title": "Type of Versatile Thermostat",
"data": {
"thermostat_type": "Thermostat type"
},
"data_description": {
"thermostat_type": "Only one central configuration type is possible"
}
},
"main": {
"title": "Add new Versatile Thermostat", "title": "Add new Versatile Thermostat",
"description": "Main mandatory attributes", "description": "Main mandatory attributes",
"data": { "data": {
"name": "Name", "name": "Name",
"thermostat_type": "Thermostat type", "thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Temperature sensor entity id", "temperature_sensor_entity_id": "Temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id", "external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)", "cycle_min": "Cycle duration (minutes)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimal temperature allowed",
"temp_max": "Maximal temperature allowed", "temp_max": "Maximal temperature allowed",
"step_temperature": "Temperature step",
"device_power": "Device power", "device_power": "Device power",
"use_central_mode": "Enable the control by central entity (need central config). Check to enable the control of the VTherm with the select central_mode entities.",
"use_window_feature": "Use window detection", "use_window_feature": "Use window detection",
"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_main_central_config": "Use central main configuration. Check to use the central main configuration. Uncheck to use a specific main configuration for this VTherm",
"add_central_boiler_control": "Add 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 need heating, the boiler will be turned on. If no VTherm needs heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the next configuration page",
"used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler"
},
"data_description": {
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
} }
}, },
"type": { "type": {
@@ -61,7 +78,7 @@
"valve_entity3_id": "3rd valve number entity id", "valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th 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 ° 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",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command", "inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" "auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
@@ -71,8 +88,14 @@
"title": "TPI", "title": "TPI",
"description": "Time Proportional Integral attributes", "description": "Time Proportional Integral attributes",
"data": { "data": {
"tpi_coef_int": "coef_int",
"tpi_coef_ext": "coef_ext",
"use_tpi_central_config": "Use central TPI configuration"
},
"data_description": {
"tpi_coef_int": "Coefficient to use for internal temperature delta", "tpi_coef_int": "Coefficient to use for internal temperature delta",
"tpi_coef_ext": "Coefficient to use for external temperature delta" "tpi_coef_ext": "Coefficient to use for external temperature delta",
"use_tpi_central_config": "Check to use the central TPI configuration. Uncheck to use a specific TPI configuration for this VTherm"
} }
}, },
"presets": { "presets": {
@@ -85,7 +108,8 @@
"frost_temp": "Frost protection preset", "frost_temp": "Frost protection preset",
"eco_ac_temp": "Eco preset for AC mode", "eco_ac_temp": "Eco preset for AC mode",
"comfort_ac_temp": "Comfort preset for AC mode", "comfort_ac_temp": "Comfort preset for AC mode",
"boost_ac_temp": "Boost preset for AC mode" "boost_ac_temp": "Boost preset for AC mode",
"use_presets_central_config": "Use central presets configuration"
}, },
"data_description": { "data_description": {
"eco_temp": "Temperature in Eco preset", "eco_temp": "Temperature in Eco preset",
@@ -94,57 +118,71 @@
"frost_temp": "Temperature in Frost protection preset", "frost_temp": "Temperature in Frost protection preset",
"eco_ac_temp": "Temperature in Eco preset for AC mode", "eco_ac_temp": "Temperature in Eco preset for AC mode",
"comfort_ac_temp": "Temperature in Comfort preset for AC mode", "comfort_ac_temp": "Temperature in Comfort preset for AC mode",
"boost_ac_temp": "Temperature in Boost preset for AC mode" "boost_ac_temp": "Temperature in Boost preset for AC mode",
"use_presets_central_config": "Check to use the central presets configuration. Uncheck to use a specific presets configuration for this VTherm"
} }
}, },
"window": { "window": {
"title": "Window management", "title": "Window management",
"description": "Open window management.\nLeave corresponding entity_id empty if not used\nYou can also configure automatic window open detection based on temperature decrease", "description": "Open window management.\nYou can also configure automatic window open detection based on temperature decrease",
"data": { "data": {
"window_sensor_entity_id": "Window sensor entity id", "window_sensor_entity_id": "Window sensor entity id",
"window_delay": "Window sensor delay (seconds)", "window_delay": "Window sensor delay (seconds)",
"window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)", "window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)",
"window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)", "window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)",
"window_auto_max_duration": "Maximum duration of automatic window open detection (in min)" "window_auto_max_duration": "Maximum duration of automatic window open detection (in min)",
"use_window_central_config": "Use central window configuration",
"window_action": "Action"
}, },
"data_description": { "data_description": {
"window_sensor_entity_id": "Leave empty if no window sensor should be used", "window_sensor_entity_id": "Leave empty if no window sensor should be used and to use the automatic detection",
"window_delay": "The delay in seconds before sensor detection is taken into account", "window_delay": "The delay in seconds before sensor detection is taken into account",
"window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used", "window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used",
"window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used", "window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used",
"window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used" "window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used",
"use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm",
"window_action": "Action to do if window is deteted as open"
} }
}, },
"motion": { "motion": {
"title": "Motion management", "title": "Motion management",
"description": "Motion sensor management. Preset can switch automatically depending on motion detection\nLeave corresponding entity_id empty if not used.\nmotion_preset and no_motion_preset should be set to the corresponding preset name", "description": "Motion sensor management. Preset can switch automatically depending on motion detection\nmotion_preset and no_motion_preset should be set to the corresponding preset name",
"data": { "data": {
"motion_sensor_entity_id": "Motion sensor entity id", "motion_sensor_entity_id": "Motion sensor entity id",
"motion_delay": "Activation delay", "motion_delay": "Activation delay",
"motion_off_delay": "Deactivation delay", "motion_off_delay": "Deactivation delay",
"motion_preset": "Motion preset", "motion_preset": "Motion preset",
"no_motion_preset": "No motion preset" "no_motion_preset": "No motion preset",
"use_motion_central_config": "Use central motion configuration"
}, },
"data_description": { "data_description": {
"motion_sensor_entity_id": "The entity id of the motion sensor", "motion_sensor_entity_id": "The entity id of the motion sensor",
"motion_delay": "Motion activation delay (seconds)", "motion_delay": "Motion activation delay (seconds)",
"motion_off_delay": "Motion deactivation delay (seconds)", "motion_off_delay": "Motion deactivation delay (seconds)",
"motion_preset": "Preset to use when motion is detected", "motion_preset": "Preset to use when motion is detected",
"no_motion_preset": "Preset to use when no motion is detected" "no_motion_preset": "Preset to use when no motion is detected",
"use_motion_central_config": "Check to use the central motion configuration. Uncheck to use a specific motion configuration for this VTherm"
} }
}, },
"power": { "power": {
"title": "Power management", "title": "Power management",
"description": "Power management attributes.\nGives the power and max power sensor of your home.\nThen specify the power consumption of the heater when on.\nAll sensors and device power should have the same unit (kW or W).\nLeave corresponding entity_id empty if not used.", "description": "Power management attributes.\nGives the power and max power sensor of your home.\nThen specify the power consumption of the heater when on.\nAll sensors and device power should have the same unit (kW or W).",
"data": { "data": {
"power_sensor_entity_id": "Power",
"max_power_sensor_entity_id": "Max power",
"power_temp": "Shedding temperature",
"use_power_central_config": "Use central power configuration"
},
"data_description": {
"power_sensor_entity_id": "Power sensor entity id", "power_sensor_entity_id": "Power sensor entity id",
"max_power_sensor_entity_id": "Max power sensor entity id", "max_power_sensor_entity_id": "Max power sensor entity id",
"power_temp": "Temperature for Power shedding" "power_temp": "Temperature for Power shedding",
"use_power_central_config": "Check to use the central power configuration. Uncheck to use a specific power configuration for this VTherm"
} }
}, },
"presence": { "presence": {
"title": "Presence management", "title": "Presence management",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present).\nThen specify either the preset to use when presence sensor is false or the offset in temperature to apply.\nIf preset is given, the offset will not be used.\nLeave corresponding entity_id empty if not used.", "description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present) and give the corresponding temperature preset setting.",
"data": { "data": {
"presence_sensor_entity_id": "Presence sensor", "presence_sensor_entity_id": "Presence sensor",
"eco_away_temp": "Eco preset", "eco_away_temp": "Eco preset",
@@ -153,7 +191,8 @@
"frost_away_temp": "Frost protection preset", "frost_away_temp": "Frost protection preset",
"eco_ac_away_temp": "Eco preset in AC mode", "eco_ac_away_temp": "Eco preset in AC mode",
"comfort_ac_away_temp": "Comfort preset in AC mode", "comfort_ac_away_temp": "Comfort preset in AC mode",
"boost_ac_away_temp": "Boost pres et in AC mode" "boost_ac_away_temp": "Boost pres et in AC mode",
"use_presence_central_config": "Use central presence configuration"
}, },
"data_description": { "data_description": {
"presence_sensor_entity_id": "Presence sensor entity id", "presence_sensor_entity_id": "Presence sensor entity id",
@@ -163,7 +202,8 @@
"frost_away_temp": "Temperature in Frost protection preset when no presence", "frost_away_temp": "Temperature in Frost protection preset when no presence",
"eco_ac_away_temp": "Temperature in Eco preset when no presence in AC mode", "eco_ac_away_temp": "Temperature in Eco preset when no presence in AC mode",
"comfort_ac_away_temp": "Temperature in Comfort preset when no presence in AC mode", "comfort_ac_away_temp": "Temperature in Comfort preset when no presence in AC mode",
"boost_ac_away_temp": "Temperature in Boost preset when no presence in AC mode" "boost_ac_away_temp": "Temperature in Boost preset when no presence in AC mode",
"use_presence_central_config": "Check to use the central presence configuration. Uncheck to use a specific presence configuration for this VTherm"
} }
}, },
"advanced": { "advanced": {
@@ -171,22 +211,25 @@
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThese parameters can lead to very poor temperature control or bad power regulation.", "description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThese parameters can lead to very poor temperature control or bad power regulation.",
"data": { "data": {
"minimal_activation_delay": "Minimal activation delay", "minimal_activation_delay": "Minimal activation delay",
"security_delay_min": "Security delay (in minutes)", "security_delay_min": "Safety delay (in minutes)",
"security_min_on_percent": "Minimal power percent to enable security mode", "security_min_on_percent": "Minimal power percent to enable safety mode",
"security_default_on_percent": "Power percent to use in security mode" "security_default_on_percent": "Power percent to use in safety mode",
"use_advanced_central_config": "Use central advanced configuration"
}, },
"data_description": { "data_description": {
"minimal_activation_delay": "Delay in seconds under which the equipment will not be activated", "minimal_activation_delay": "Delay in seconds under which the equipment will not be activated",
"security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a security off state", "security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state",
"security_min_on_percent": "Minimal heating percent value for security preset activation. Below this amount of power percent the thermostat won't go into security preset", "security_min_on_percent": "Minimal heating percent value for safety preset activation. Below this amount of power percent the thermostat won't go into safety preset",
"security_default_on_percent": "The default heating power percent value in security preset. Set to 0 to switch off heater in security 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"
} }
} }
}, },
"error": { "error": {
"unknown": "Unexpected error", "unknown": "Unexpected error",
"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."
}, },
"abort": { "abort": {
"already_configured": "Device is already configured" "already_configured": "Device is already configured"
@@ -196,25 +239,42 @@
"flow_title": "Versatile Thermostat configuration", "flow_title": "Versatile Thermostat configuration",
"step": { "step": {
"user": { "user": {
"title": "Add new Versatile Thermostat", "title": "Type - {name}",
"data": {
"thermostat_type": "Thermostat type"
},
"data_description": {
"thermostat_type": "Only one central configuration type is possible"
}
},
"main": {
"title": "Main - {name}",
"description": "Main mandatory attributes", "description": "Main mandatory attributes",
"data": { "data": {
"name": "Name", "name": "Name",
"thermostat_type": "Thermostat type", "thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Temperature sensor entity id", "temperature_sensor_entity_id": "Room temperature sensor entity id",
"external_temperature_sensor_entity_id": "External temperature sensor entity id", "external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)", "cycle_min": "Cycle duration (minutes)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimal temperature allowed",
"temp_max": "Maximal temperature allowed", "temp_max": "Maximal temperature allowed",
"device_power": "Device power (kW)", "step_temperature": "Temperature step",
"device_power": "Device power",
"use_central_mode": "Enable the control by central entity (need central config). Check to enable the control of the VTherm with the select central_mode entities.",
"use_window_feature": "Use window detection", "use_window_feature": "Use window detection",
"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_main_central_config": "Use central main configuration. Check to use the central main configuration. Uncheck to use a specific main configuration for this VTherm",
"add_central_boiler_control": "Add 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 need heating, the boiler will be turned on. If no VTherm needs heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the next configuration page",
"used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler"
},
"data_description": {
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
} }
}, },
"type": { "type": {
"title": "Linked entities", "title": "Entities - {name}",
"description": "Linked entities attributes", "description": "Linked entities attributes",
"data": { "data": {
"heater_entity_id": "1st heater switch", "heater_entity_id": "1st heater switch",
@@ -253,22 +313,28 @@
"valve_entity3_id": "3rd valve number entity id", "valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th 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 ° 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",
"inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command", "inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" "auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary"
} }
}, },
"tpi": { "tpi": {
"title": "TPI", "title": "TPI - {name}",
"description": "Time Proportional Integral attributes", "description": "Time Proportional Integral attributes",
"data": { "data": {
"tpi_coef_int": "coef_int",
"tpi_coef_ext": "coef_ext",
"use_tpi_central_config": "Use central TPI configuration"
},
"data_description": {
"tpi_coef_int": "Coefficient to use for internal temperature delta", "tpi_coef_int": "Coefficient to use for internal temperature delta",
"tpi_coef_ext": "Coefficient to use for external temperature delta" "tpi_coef_ext": "Coefficient to use for external temperature delta",
"use_tpi_central_config": "Check to use the central TPI configuration. Uncheck to use a specific TPI configuration for this VTherm"
} }
}, },
"presets": { "presets": {
"title": "Presets", "title": "Presets - {name}",
"description": "For each preset set the target temperature (0 to ignore preset)", "description": "For each preset set the target temperature (0 to ignore preset)",
"data": { "data": {
"eco_temp": "Eco preset", "eco_temp": "Eco preset",
@@ -277,7 +343,8 @@
"frost_temp": "Frost protection preset", "frost_temp": "Frost protection preset",
"eco_ac_temp": "Eco preset for AC mode", "eco_ac_temp": "Eco preset for AC mode",
"comfort_ac_temp": "Comfort preset for AC mode", "comfort_ac_temp": "Comfort preset for AC mode",
"boost_ac_temp": "Boost preset for AC mode" "boost_ac_temp": "Boost preset for AC mode",
"use_presets_central_config": "Use central presets configuration"
}, },
"data_description": { "data_description": {
"eco_temp": "Temperature in Eco preset", "eco_temp": "Temperature in Eco preset",
@@ -286,88 +353,119 @@
"frost_temp": "Temperature in Frost protection preset", "frost_temp": "Temperature in Frost protection preset",
"eco_ac_temp": "Temperature in Eco preset for AC mode", "eco_ac_temp": "Temperature in Eco preset for AC mode",
"comfort_ac_temp": "Temperature in Comfort preset for AC mode", "comfort_ac_temp": "Temperature in Comfort preset for AC mode",
"boost_ac_temp": "Temperature in Boost preset for AC mode" "boost_ac_temp": "Temperature in Boost preset for AC mode",
"use_presets_central_config": "Check to use the central presets configuration. Uncheck to use a specific presets configuration for this VTherm"
} }
}, },
"window": { "window": {
"title": "Window management", "title": "Window - {name}",
"description": "Open window management.\nLeave corresponding entity_id empty if not used\nYou can also configure automatic window open detection based on temperature decrease", "description": "Open window management.\nYou can also configure automatic window open detection based on temperature decrease",
"data": { "data": {
"window_sensor_entity_id": "Window sensor entity id", "window_sensor_entity_id": "Window sensor entity id",
"window_delay": "Window sensor delay (seconds)", "window_delay": "Window sensor delay (seconds)",
"window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)", "window_auto_open_threshold": "Temperature decrease threshold for automatic window open detection (in °/hours)",
"window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)", "window_auto_close_threshold": "Temperature increase threshold for end of automatic detection (in °/hours)",
"window_auto_max_duration": "Maximum duration of automatic window open detection (in min)" "window_auto_max_duration": "Maximum duration of automatic window open detection (in min)",
"use_window_central_config": "Use central window configuration",
"window_action": "Action"
}, },
"data_description": { "data_description": {
"window_sensor_entity_id": "Leave empty if no window sensor should be used", "window_sensor_entity_id": "Leave empty if no window sensor should be used and to use the automatic detection",
"window_delay": "The delay in seconds before sensor detection is taken into account", "window_delay": "The delay in seconds before sensor detection is taken into account",
"window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used", "window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used",
"window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used", "window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used",
"window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used" "window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used",
"use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm",
"window_action": "Action to do if window is deteted as open"
} }
}, },
"motion": { "motion": {
"title": "Motion management", "title": "Motion - {name}",
"description": "Motion sensor management. Preset can switch automatically depending of a motion detection\nLeave corresponding entity_id empty if not used.\nmotion_preset and no_motion_preset should be set to the corresponding preset name", "description": "Motion management. Preset can switch automatically depending of a motion detection\nmotion_preset and no_motion_preset should be set to the corresponding preset name",
"data": { "data": {
"motion_sensor_entity_id": "Motion sensor entity id", "motion_sensor_entity_id": "Motion sensor entity id",
"motion_delay": "Activation delay", "motion_delay": "Activation delay",
"motion_off_delay": "Deactivation delay", "motion_off_delay": "Deactivation delay",
"motion_preset": "Motion preset", "motion_preset": "Motion preset",
"no_motion_preset": "No motion preset" "no_motion_preset": "No motion preset",
"use_motion_central_config": "Use central motion configuration"
}, },
"data_description": { "data_description": {
"motion_sensor_entity_id": "The entity id of the motion sensor", "motion_sensor_entity_id": "The entity id of the motion sensor",
"motion_delay": "Motion activation delay (seconds)", "motion_delay": "Motion activation delay (seconds)",
"motion_off_delay": "Motion deactivation delay (seconds)", "motion_off_delay": "Motion deactivation delay (seconds)",
"motion_preset": "Preset to use when motion is detected", "motion_preset": "Preset to use when motion is detected",
"no_motion_preset": "Preset to use when no motion is detected" "no_motion_preset": "Preset to use when no motion is detected",
"use_motion_central_config": "Check to use the central motion configuration. Uncheck to use a specific motion configuration for this VTherm"
} }
}, },
"power": { "power": {
"title": "Power management", "title": "Power - {name}",
"description": "Power management attributes.\nGives the power and max power sensor of your home.\nThen specify the power consumption of the heater when on.\nAll sensors and device power should have the same unit (kW or W).\nLeave corresponding entity_id empty if not used.", "description": "Power management attributes.\nGives the power and max power sensor of your home.\nThen specify the power consumption of the heater when on.\nAll sensors and device power should have the same unit (kW or W).",
"data": { "data": {
"power_sensor_entity_id": "Power",
"max_power_sensor_entity_id": "Max power",
"power_temp": "Shedding temperature",
"use_power_central_config": "Use central power configuration"
},
"data_description": {
"power_sensor_entity_id": "Power sensor entity id", "power_sensor_entity_id": "Power sensor entity id",
"max_power_sensor_entity_id": "Max power sensor entity id", "max_power_sensor_entity_id": "Max power sensor entity id",
"power_temp": "Temperature for Power shedding" "power_temp": "Temperature for Power shedding",
"use_power_central_config": "Check to use the central power configuration. Uncheck to use a specific power configuration for this VTherm"
} }
}, },
"presence": { "presence": {
"title": "Presence management", "title": "Presence - {name}",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present).\nThen specify either the preset to use when presence sensor is false or the offset in temperature to apply.\nIf preset is given, the offset will not be used.\nLeave corresponding entity_id empty if not used.", "description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present) and give the corresponding temperature preset setting.",
"data": { "data": {
"presence_sensor_entity_id": "Presence sensor",
"eco_away_temp": "Eco away preset",
"comfort_away_temp": "Comfort away preset",
"boost_away_temp": "Boost away preset",
"frost_away_temp": "Frost protection preset",
"eco_ac_away_temp": "Eco away preset in AC mode",
"comfort_ac_away_temp": "Comfort away preset in AC mode",
"boost_ac_away_temp": "Boost away preset in AC mode",
"use_presence_central_config": "Use central presence configuration"
},
"data_description": {
"presence_sensor_entity_id": "Presence sensor entity id", "presence_sensor_entity_id": "Presence sensor entity id",
"eco_away_temp": "Temperature in Eco preset when no presence", "eco_away_temp": "Temperature in Eco preset when no presence",
"comfort_away_temp": "Temperature in Comfort preset when no presence", "comfort_away_temp": "Temperature in Comfort preset when no presence",
"boost_away_temp": "Temperature in Boost preset when no presence", "boost_away_temp": "Temperature in Boost preset when no presence",
"frost_away_temp": "Temperature in Frost protection preset when no presence",
"eco_ac_away_temp": "Temperature in Eco preset when no presence in AC mode", "eco_ac_away_temp": "Temperature in Eco preset when no presence in AC mode",
"comfort_ac_away_temp": "Temperature in Comfort preset when no presence in AC mode", "comfort_ac_away_temp": "Temperature in Comfort preset when no presence in AC mode",
"boost_ac_away_temp": "Temperature in Boost preset when no presence in AC mode" "boost_ac_away_temp": "Temperature in Boost preset when no presence in AC mode",
"use_presence_central_config": "Check to use the central presence configuration. Uncheck to use a specific presence configuration for this VTherm"
} }
}, },
"advanced": { "advanced": {
"title": "Advanced parameters", "title": "Advanced - {name}",
"description": "Configuration of advanced parameters. Leave the default values if you don't know what you are doing.\nThese parameters can lead to very poor temperature control or bad power regulation.", "description": "Advanced parameters. Leave the default values if you don't know what you are doing.\nThese parameters can lead to very poor temperature control or bad power regulation.",
"data": { "data": {
"minimal_activation_delay": "Minimal activation delay", "minimal_activation_delay": "Minimal activation delay",
"security_delay_min": "Security delay (in minutes)", "security_delay_min": "Safety delay (in minutes)",
"security_min_on_percent": "Minimal power percent to enable security mode", "security_min_on_percent": "Minimal power percent to enable safety mode",
"security_default_on_percent": "Power percent to use in security mode" "security_default_on_percent": "Power percent to use in safety mode",
"use_advanced_central_config": "Use central advanced configuration"
}, },
"data_description": { "data_description": {
"minimal_activation_delay": "Delay in seconds under which the equipment will not be activated", "minimal_activation_delay": "Delay in seconds under which the equipment will not be activated",
"security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a security off state", "security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state",
"security_min_on_percent": "Minimal heating percent value for security preset activation. Below this amount of power percent the thermostat won't go into security preset", "security_min_on_percent": "Minimal heating percent value for safety preset activation. Below this amount of power percent the thermostat won't go into safety preset",
"security_default_on_percent": "The default heating power percent value in security preset. Set to 0 to switch off heater in security 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"
} }
} }
}, },
"error": { "error": {
"unknown": "Unexpected error", "unknown": "Unexpected error",
"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.",
"service_configuration_format": "The format of the service configuration is wrong"
}, },
"abort": { "abort": {
"already_configured": "Device is already configured" "already_configured": "Device is already configured"
@@ -376,6 +474,7 @@
"selector": { "selector": {
"thermostat_type": { "thermostat_type": {
"options": { "options": {
"thermostat_central_config": "Central configuration",
"thermostat_over_switch": "Thermostat over a switch", "thermostat_over_switch": "Thermostat over a switch",
"thermostat_over_climate": "Thermostat over a climate", "thermostat_over_climate": "Thermostat over a climate",
"thermostat_over_valve": "Thermostat over a valve" "thermostat_over_valve": "Thermostat over a valve"
@@ -399,6 +498,22 @@
"auto_fan_high": "High", "auto_fan_high": "High",
"auto_fan_turbo": "Turbo" "auto_fan_turbo": "Turbo"
} }
},
"window_action": {
"options": {
"window_turn_off": "Turn off",
"window_fan_only": "Fan only",
"window_frost_temp": "Frost protect",
"window_eco_temp": "Eco"
}
},
"presets": {
"options": {
"frost": "Frost protect",
"eco": "Eco",
"comfort": "Comfort",
"boost": "Boost"
}
} }
}, },
"entity": { "entity": {
@@ -408,7 +523,7 @@
"preset_mode": { "preset_mode": {
"state": { "state": {
"power": "Shedding", "power": "Shedding",
"security": "Security", "security": "Safety",
"none": "Manual" "none": "Manual"
} }
} }

View File

@@ -4,6 +4,15 @@
"flow_title": "Versatile Thermostat configuration", "flow_title": "Versatile Thermostat configuration",
"step": { "step": {
"user": { "user": {
"title": "Type du nouveau Versatile Thermostat",
"data": {
"thermostat_type": "Type de thermostat"
},
"data_description": {
"thermostat_type": "Un seul thermostat de type Configuration centrale est possible."
}
},
"main": {
"title": "Ajout d'un nouveau thermostat", "title": "Ajout d'un nouveau thermostat",
"description": "Principaux attributs obligatoires", "description": "Principaux attributs obligatoires",
"data": { "data": {
@@ -14,11 +23,19 @@
"cycle_min": "Durée du cycle (minutes)", "cycle_min": "Durée du cycle (minutes)",
"temp_min": "Température minimale permise", "temp_min": "Température minimale permise",
"temp_max": "Température maximale permise", "temp_max": "Température maximale permise",
"step_temperature": "Pas de température",
"device_power": "Puissance de l'équipement", "device_power": "Puissance de l'équipement",
"use_central_mode": "Autoriser le controle par une entity centrale ('nécessite une config. centrale`). Cochez pour autoriser le contrôle du VTherm par la liste déroulante 'central_mode' de l'entité configuration centrale.",
"use_window_feature": "Avec détection des ouvertures", "use_window_feature": "Avec détection des ouvertures",
"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_main_central_config": "Utiliser la configuration centrale. Cochez pour utiliser la configuration centrale. Décochez et saisissez les attributs pour utiliser une configuration spécifique.",
"add_central_boiler_control": "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.",
"used_by_controls_central_boiler": "Utilisé par la chaudière centrale. Cochez si ce VTherm doit contrôler la chaudière centrale."
},
"data_description": {
"external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure."
} }
}, },
"type": { "type": {
@@ -61,7 +78,7 @@
"valve_entity3_id": "Entity id de la 3ème valve", "valve_entity3_id": "Entity id de la 3ème valve",
"valve_entity4_id": "Entity id de la 4è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 ° au-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",
"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"
@@ -71,8 +88,14 @@
"title": "TPI", "title": "TPI",
"description": "Attributs de l'algo Time Proportional Integral", "description": "Attributs de l'algo Time Proportional Integral",
"data": { "data": {
"tpi_coef_int": "coeff_int : Coefficient à utiliser pour le delta de température interne", "tpi_coef_int": "coeff_int",
"tpi_coef_ext": "coeff_ext : Coefficient à utiliser pour le delta de température externe" "tpi_coef_ext": "coeff_ext",
"use_tpi_central_config": "Utiliser la configuration TPI centrale"
},
"data_description": {
"tpi_coef_int": "Coefficient à utiliser pour le delta de température interne",
"tpi_coef_ext": "Coefficient à utiliser pour le delta de température externe",
"use_tpi_central_config": "Cochez pour utiliser la configuration TPI centrale. Décochez et saisissez les attributs pour utiliser une configuration TPI spécifique"
} }
}, },
"presets": { "presets": {
@@ -85,7 +108,8 @@
"frost_temp": "Preset Hors-gel", "frost_temp": "Preset Hors-gel",
"eco_ac_temp": "Preset Eco en mode AC", "eco_ac_temp": "Preset Eco en mode AC",
"comfort_ac_temp": "Preset Comfort en mode AC", "comfort_ac_temp": "Preset Comfort en mode AC",
"boost_ac_temp": "Preset Boost en mode AC" "boost_ac_temp": "Preset Boost en mode AC",
"use_presets_central_config": "Utiliser la configuration des presets centrale"
}, },
"data_description": { "data_description": {
"eco_temp": "Température en preset Eco", "eco_temp": "Température en preset Eco",
@@ -94,57 +118,71 @@
"frost_temp": "Température en preset Hors-gel", "frost_temp": "Température en preset Hors-gel",
"eco_ac_temp": "Température en preset Eco en mode AC", "eco_ac_temp": "Température en preset Eco en mode AC",
"comfort_ac_temp": "Température en preset Comfort en mode AC", "comfort_ac_temp": "Température en preset Comfort en mode AC",
"boost_ac_temp": "Température en preset Boost en mode AC" "boost_ac_temp": "Température en preset Boost en mode AC",
"use_presets_central_config": "Cochez pour utiliser la configuration des presets centrale. Décochez et saisissez les attributs pour utiliser une configuration des presets spécifique"
} }
}, },
"window": { "window": {
"title": "Gestion d'une ouverture", "title": "Gestion d'une ouverture",
"description": "Coupe le radiateur si l'ouverture est ouverte.\nLaissez l'entity id vide si non utilisé.", "description": "Coupe le radiateur si l'ouverture est ouverte.\nLaissez l'id d'entité vide pour utiliser la détection automatique.",
"data": { "data": {
"window_sensor_entity_id": "Détecteur d'ouverture (entity id)", "window_sensor_entity_id": "Détecteur d'ouverture (entity id)",
"window_delay": "Délai avant extinction (secondes)", "window_delay": "Délai avant extinction (secondes)",
"window_auto_open_threshold": "Seuil haut de chute de température pour la détection automatique (en °/heure)", "window_auto_open_threshold": "Seuil haut de chute de température pour la détection automatique (en °/heure)",
"window_auto_close_threshold": "Seuil bas de chute de température pour la fin de détection automatique (en °/heure)", "window_auto_close_threshold": "Seuil bas de chute de température pour la fin de détection automatique (en °/heure)",
"window_auto_max_duration": "Durée maximum d'une extinction automatique (en min)" "window_auto_max_duration": "Durée maximum d'une extinction automatique (en min)",
"use_window_central_config": "Utiliser la configuration centrale des ouvertures",
"window_action": "Action"
}, },
"data_description": { "data_description": {
"window_sensor_entity_id": "Laissez vide si vous n'avez de détecteur", "window_sensor_entity_id": "Laissez vide si vous n'avez de détecteur et pour utiliser la détection automatique",
"window_delay": "Le délai (en secondes) avant que le changement du détecteur soit pris en compte", "window_delay": "Le délai (en secondes) avant que le changement du détecteur soit pris en compte",
"window_auto_open_threshold": "Valeur recommandée: entre 3 et 10. Laissez vide si vous n'utilisez pas la détection automatique", "window_auto_open_threshold": "Valeur recommandée: entre 3 et 10. Laissez vide si vous n'utilisez pas la détection automatique",
"window_auto_close_threshold": "Valeur recommandée: 0. Laissez vide si vous n'utilisez pas la détection automatique", "window_auto_close_threshold": "Valeur recommandée: 0. Laissez vide si vous n'utilisez pas la détection automatique",
"window_auto_max_duration": "Valeur recommandée: 60 (1 heure). Laissez vide si vous n'utilisez pas la détection automatique" "window_auto_max_duration": "Valeur recommandée: 60 (1 heure). Laissez vide si vous n'utilisez pas la détection automatique",
"use_window_central_config": "Cochez pour utiliser la configuration centrale des ouvertures. Décochez et saisissez les attributs pour utiliser une configuration spécifique des ouvertures",
"window_action": "Action a effectuer si la fenêtre est détectée comme ouverte"
} }
}, },
"motion": { "motion": {
"title": "Gestion de la détection de mouvement", "title": "Gestion de la détection de mouvement",
"description": "Le preset s'ajuste automatiquement si un mouvement est détecté\nLaissez l'entity id vide si non utilisé.\n'Preset mouvement' et 'Preset sans mouvement' doivent être choisis avec les preset à utiliser.", "description": "Le preset s'ajuste automatiquement si un mouvement est détecté\n'Preset mouvement' et 'Preset sans mouvement' doivent être choisis avec les preset à utiliser.",
"data": { "data": {
"motion_sensor_entity_id": "Détecteur de mouvement", "motion_sensor_entity_id": "Détecteur de mouvement",
"motion_delay": "Délai d'activation", "motion_delay": "Délai d'activation",
"motion_off_delay": "Délai de désactivation", "motion_off_delay": "Délai de désactivation",
"motion_preset": "Preset si mouvement", "motion_preset": "Preset si mouvement",
"no_motion_preset": "Preset si pas de mouvement" "no_motion_preset": "Preset sans mouvement",
"use_motion_central_config": "Utiliser la condfiguration centrale du mouvement"
}, },
"data_description": { "data_description": {
"motion_sensor_entity_id": "Détecteur de mouvement entity id", "motion_sensor_entity_id": "Id d'entité du détecteur de mouvement",
"motion_delay": "Délai avant activation lorsqu'un mouvement est détecté (secondss)", "motion_delay": "Délai avant activation lorsqu'un mouvement est détecté (secondss)",
"motion_off_delai": "Délai avant désactivation lorsqu'aucun mouvement n'est détecté (secondes)", "motion_off_delai": "Délai avant désactivation lorsqu'aucun mouvement n'est détecté (secondes)",
"motion_preset": "Preset à utiliser si mouvement détecté", "motion_preset": "Preset à utiliser si mouvement détecté",
"no_motion_preset": "Preset à utiliser si pas de mouvement détecté" "no_motion_preset": "Preset à utiliser si pas de mouvement détecté",
"use_motion_central_config": "Cochez pour utiliser la configuration centrale du mouvement. Décochez et saisissez les attributs pour utiliser une configuration spécifique du mouvement"
} }
}, },
"power": { "power": {
"title": "Gestion de l'énergie", "title": "Gestion de la puissance",
"description": "Sélectionne automatiquement le preset 'power' si la puissance consommée est supérieure à un maximum.\nDonnez les entity id des capteurs qui mesurent la puissance totale et la puissance max autorisée.\nEnsuite donnez la puissance de l'équipement.\nTous les capteurs et la puissance consommée par l'équipement doivent avoir la même unité de mesure (kW ou W).", "description": "Sélectionne automatiquement le preset 'power' si la puissance consommée est supérieure à un maximum.\nDonnez les entity id des capteurs qui mesurent la puissance totale et la puissance max autorisée.\nEnsuite donnez la puissance de l'équipement.\nTous les capteurs et la puissance consommée par l'équipement doivent avoir la même unité de mesure (kW ou W).",
"data": { "data": {
"power_sensor_entity_id": "Capteur de puissance totale (entity id)", "power_sensor_entity_id": "Capteur de puissance totale (entity id)",
"max_power_sensor_entity_id": "Capteur de puissance Max (entity id)", "max_power_sensor_entity_id": "Capteur de puissance Max (entity id)",
"power_temp": "Température si délestaqe" "power_temp": "Température si délestaqe",
"use_power_central_config": "Utiliser la configuration centrale de la puissance"
},
"data_description": {
"power_sensor_entity_id": "Entity id du capteur de puissance totale du logement",
"max_power_sensor_entity_id": "Entity id du capteur de puissance Max autorisée avant délestage",
"power_temp": "Température cible si délestaqe",
"use_power_central_config": "Cochez pour utiliser la configuration centrale de la puissance. Décochez et saisissez les attributs pour utiliser une configuration spécifique de la puissance"
} }
}, },
"presence": { "presence": {
"title": "Gestion de la présence", "title": "Gestion de la présence",
"description": "Donnez un capteur de présence (true si quelqu'un est présent).\nEnsuite spécifiez soit un preset à utiliser, soit un offset de température à appliquer lorsque personne n'est présent.\nSi le préset est utilisé, l'offset ne sera pas pris en compte.\nLaissez l'entity id vide si la gestion de la présence est non utilisée.", "description": "Donnez un capteur de présence (true si quelqu'un est présent) et les températures cibles à utiliser en cas d'absence.",
"data": { "data": {
"presence_sensor_entity_id": "Capteur de présence", "presence_sensor_entity_id": "Capteur de présence",
"eco_away_temp": "preset Eco", "eco_away_temp": "preset Eco",
@@ -153,17 +191,19 @@
"frost_away_temp": "preset Hors-gel", "frost_away_temp": "preset Hors-gel",
"eco_ac_away_temp": "preset Eco en mode AC", "eco_ac_away_temp": "preset Eco en mode AC",
"comfort_ac_away_temp": "preset Comfort en mode AC", "comfort_ac_away_temp": "preset Comfort en mode AC",
"boost_ac_away_temp": "preset Boost en mode AC" "boost_ac_away_temp": "preset Boost en mode AC",
"use_presence_central_config": "Utiliser la configuration centrale de la présence"
}, },
"data_description": { "data_description": {
"presence_sensor_entity_id": "Capteur de présence entity id (true si quelqu'un est présent)", "presence_sensor_entity_id": "Id d'entité du capteur de présence",
"eco_away_temp": "Température en preset Eco en cas d'absence", "eco_away_temp": "Température en preset Eco en cas d'absence",
"comfort_away_temp": "Température en preset Comfort en cas d'absence", "comfort_away_temp": "Température en preset Comfort en cas d'absence",
"boost_away_temp": "Température en preset Boost en cas d'absence", "boost_away_temp": "Température en preset Boost en cas d'absence",
"frost_away_temp": "Température en preset Hors-gel en cas d'absence", "frost_away_temp": "Température en preset Hors-gel en cas d'absence",
"eco_ac_away_temp": "Température en preset Eco en cas d'absence en mode AC", "eco_ac_away_temp": "Température en preset Eco en cas d'absence en mode AC",
"comfort_ac_away_temp": "Température en preset Comfort en cas d'absence en mode AC", "comfort_ac_away_temp": "Température en preset Comfort en cas d'absence en mode AC",
"boost_ac_away_temp": "Température en preset Boost en cas d'absence en mode AC" "boost_ac_away_temp": "Température en preset Boost en cas d'absence en mode AC",
"use_presence_central_config": "Cochez pour utiliser la configuration centrale de la présence. Décochez et saisissez les attributs pour utiliser une configuration spécifique de la présence"
} }
}, },
"advanced": { "advanced": {
@@ -173,20 +213,35 @@
"minimal_activation_delay": "Délai minimal d'activation", "minimal_activation_delay": "Délai minimal d'activation",
"security_delay_min": "Délai maximal entre 2 mesures de températures", "security_delay_min": "Délai maximal entre 2 mesures de températures",
"security_min_on_percent": "Pourcentage minimal de puissance", "security_min_on_percent": "Pourcentage minimal de puissance",
"security_default_on_percent": "Pourcentage de puissance a utiliser en mode securité" "security_default_on_percent": "Pourcentage de puissance a utiliser en mode securité",
"use_advanced_central_config": "Utiliser la configuration centrale avancée"
}, },
"data_description": { "data_description": {
"minimal_activation_delay": "Délai en seondes en-dessous duquel l'équipement ne sera pas activé", "minimal_activation_delay": "Délai en seondes en-dessous duquel l'équipement ne sera pas activé",
"security_delay_min": "Délai maximal autorisé en minutes entre 2 mesures de températures. Au-dessus de ce délai, le thermostat se mettra en position de sécurité", "security_delay_min": "Délai maximal autorisé en minutes entre 2 mesures de températures. Au-dessus de ce délai, le thermostat se mettra en position de sécurité",
"security_min_on_percent": "Seuil minimal de pourcentage de chauffage en-dessous duquel le préréglage sécurité ne sera jamais activé", "security_min_on_percent": "Seuil minimal de pourcentage de chauffage en-dessous duquel le préréglage sécurité ne sera jamais activé",
"security_default_on_percent": "Valeur par défaut pour le pourcentage de chauffage en mode sécurité. Mettre 0 pour éteindre le radiateur en mode sécurité" "security_default_on_percent": "Valeur par défaut pour le pourcentage de chauffage en mode sécurité. Mettre 0 pour éteindre le radiateur en mode sécurité",
"use_advanced_central_config": "Cochez pour utiliser la configuration centrale avancée. Décochez et saisissez les attributs pour utiliser une configuration spécifique avancée"
}
},
"central_boiler": {
"title": "Contrôle de la chaudière centrale",
"description": "Donnez les services à appeler pour allumer/éteindre la chaudière centrale. Laissez vide, si aucun appel de service ne doit être effectué (dans ce cas, vous devrez gérer vous même l'allumage/extinction de votre chaudière centrale). Le service a appelé doit être formatté comme suit: `entity_id/service_name[/attribut:valeur]` (/attribut:valeur est facultatif)\nPar exemple:\n- pour allumer un switch: `switch.controle_chaudiere/switch.turn_on`\n- pour éteindre un switch: `switch.controle_chaudiere/switch.turn_off`\n- pour programmer la chaudière sur 25° et ainsi forcer son allumage: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- pour envoyer 10° à la chaudière et ainsi forcer son extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
"data": {
"central_boiler_activation_service": "Commande pour allumer",
"central_boiler_deactivation_service": "Commande pour éteindre"
},
"data_description": {
"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]"
} }
} }
}, },
"error": { "error": {
"unknown": "Erreur inattendue", "unknown": "Erreur inattendue",
"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."
}, },
"abort": { "abort": {
"already_configured": "Le device est déjà configuré" "already_configured": "Le device est déjà configuré"
@@ -196,26 +251,42 @@
"flow_title": "Versatile Thermostat configuration", "flow_title": "Versatile Thermostat configuration",
"step": { "step": {
"user": { "user": {
"title": "Ajout d'un nouveau thermostat", "title": "Type - {name}",
"data": {
"thermostat_type": "Type de thermostat"
},
"data_description": {
"thermostat_type": "Un seul thermostat de type Configuration centrale est possible."
}
},
"main": {
"title": "Attributs - {name}",
"description": "Principaux attributs obligatoires", "description": "Principaux attributs obligatoires",
"data": { "data": {
"name": "Nom", "name": "Nom",
"thermostat_over_switch": "Thermostat sur un switch", "thermostat_type": "Type de thermostat",
"thermostat_over_climate": "Thermostat sur un autre thermostat",
"temperature_sensor_entity_id": "Température sensor entity id", "temperature_sensor_entity_id": "Température sensor entity id",
"external_temperature_sensor_entity_id": "Temperature exterieure sensor entity id", "external_temperature_sensor_entity_id": "Température exterieure sensor entity id",
"cycle_min": "Durée du cycle (minutes)", "cycle_min": "Durée du cycle (minutes)",
"temp_min": "Température minimale permise", "temp_min": "Température minimale permise",
"temp_max": "Température maximale permise", "temp_max": "Température maximale permise",
"step_temperature": "Pas de température",
"device_power": "Puissance de l'équipement", "device_power": "Puissance de l'équipement",
"use_central_mode": "Autoriser le controle par une entity centrale ('nécessite une config. centrale`). Cochez pour autoriser le contrôle du VTherm par la liste déroulante 'central_mode' de l'entité configuration centrale.",
"use_window_feature": "Avec détection des ouvertures", "use_window_feature": "Avec détection des ouvertures",
"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_main_central_config": "Utiliser la configuration centrale. Cochez pour utiliser la configuration centrale. Décochez et saisissez les attributs pour utiliser une configuration spécifique.",
"add_central_boiler_control": "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.",
"used_by_controls_central_boiler": "Utilisé par la chaudière centrale. Cochez si ce VTherm doit contrôler la chaudière centrale."
},
"data_description": {
"external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure. N'est pas utilisé si la configuration centrale est utilisée."
} }
}, },
"type": { "type": {
"title": "Entité(s) liée(s)", "title": "Entité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", "heater_entity_id": "1er radiateur",
@@ -254,14 +325,14 @@
"valve_entity3_id": "Entity id de la 3ème valve", "valve_entity3_id": "Entity id de la 3ème valve",
"valve_entity4_id": "Entity id de la 4ème valve", "valve_entity4_id": "Entity id de la 4ème valve",
"auto_regulation_mode": "Ajustement automatique de la consigne", "auto_regulation_mode": "Ajustement automatique de la consigne",
"auto_regulation_dtemp": "Le seuil en ° au-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",
"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"
} }
}, },
"tpi": { "tpi": {
"title": "TPI", "title": "TPI - {name}",
"description": "Attributs de l'algo Time Proportional Integral", "description": "Attributs de l'algo Time Proportional Integral",
"data": { "data": {
"tpi_coef_int": "coeff_int : Coefficient à utiliser pour le delta de température interne", "tpi_coef_int": "coeff_int : Coefficient à utiliser pour le delta de température interne",
@@ -269,8 +340,8 @@
} }
}, },
"presets": { "presets": {
"title": "Presets", "title": "Pre-réglages - {name}",
"description": "Pour chaque preset, donnez la température cible (0 pour ignorer le preset)", "description": "Réglage des presets. Donnez la température cible (0 pour ignorer le preset)",
"data": { "data": {
"eco_temp": "Preset Eco", "eco_temp": "Preset Eco",
"comfort_temp": "Preset Comfort", "comfort_temp": "Preset Comfort",
@@ -278,7 +349,8 @@
"frost_temp": "Preset Hors-gel", "frost_temp": "Preset Hors-gel",
"eco_ac_temp": "Preset Eco en mode AC", "eco_ac_temp": "Preset Eco en mode AC",
"comfort_ac_temp": "Preset Comfort en mode AC", "comfort_ac_temp": "Preset Comfort en mode AC",
"boost_ac_temp": "Preset Boost en mode AC" "boost_ac_temp": "Preset Boost en mode AC",
"use_presets_central_config": "Utiliser la configuration centrale des presets"
}, },
"data_description": { "data_description": {
"eco_temp": "Température en preset Eco", "eco_temp": "Température en preset Eco",
@@ -287,57 +359,71 @@
"frost_temp": "Température en preset Hors-gel", "frost_temp": "Température en preset Hors-gel",
"eco_ac_temp": "Température en preset Eco en mode AC", "eco_ac_temp": "Température en preset Eco en mode AC",
"comfort_ac_temp": "Température en preset Comfort en mode AC", "comfort_ac_temp": "Température en preset Comfort en mode AC",
"boost_ac_temp": "Température en preset Boost en mode AC" "boost_ac_temp": "Température en preset Boost en mode AC",
"use_presets_central_config": "Cochez pour utiliser la configuration centrale des presets. Décochez et saisissez les attributs pour utiliser une configuration des presets spécifique"
} }
}, },
"window": { "window": {
"title": "Gestion d'une ouverture", "title": "Ouverture - {name}",
"description": "Coupe le radiateur si l'ouverture est ouverte.\nLaissez l'entity id vide si non utilisé.", "description": "Coupe le radiateur si l'ouverture est ouverte.\nLaissez l'id d'entité vide pour utiliser la détection automatique.",
"data": { "data": {
"window_sensor_entity_id": "Détecteur d'ouverture (entity id)", "window_sensor_entity_id": "Détecteur d'ouverture (entity id)",
"window_delay": "Délai avant extinction (secondes)", "window_delay": "Délai avant extinction (secondes)",
"window_auto_open_threshold": "Seuil haut de chute de température pour la détection automatique (en °/heure)", "window_auto_open_threshold": "Seuil haut de chute de température pour la détection automatique (en °/heure)",
"window_auto_close_threshold": "Seuil bas de chute de température pour la fin de détection automatique (en °/heure)", "window_auto_close_threshold": "Seuil bas de chute de température pour la fin de détection automatique (en °/heure)",
"window_auto_max_duration": "Durée maximum d'une extinction automatique (en min)" "window_auto_max_duration": "Durée maximum d'une extinction automatique (en min)",
"use_window_central_config": "Utiliser la configuration centrale des ouvertures",
"window_action": "Action"
}, },
"data_description": { "data_description": {
"window_sensor_entity_id": "Laissez vide si vous n'avez de détecteur", "window_sensor_entity_id": "Laissez vide si vous n'avez de détecteur et pour utiliser la détection automatique",
"window_delay": "Le délai (en secondes) avant que le changement du détecteur soit pris en compte", "window_delay": "Le délai (en secondes) avant que le changement du détecteur soit pris en compte",
"window_auto_open_threshold": "Valeur recommandée: entre 3 et 10. Laissez vide si vous n'utilisez pas la détection automatique", "window_auto_open_threshold": "Valeur recommandée: entre 3 et 10. Laissez vide si vous n'utilisez pas la détection automatique",
"window_auto_close_threshold": "Valeur recommandée: 0. Laissez vide si vous n'utilisez pas la détection automatique", "window_auto_close_threshold": "Valeur recommandée: 0. Laissez vide si vous n'utilisez pas la détection automatique",
"window_auto_max_duration": "Valeur recommandée: 60 (1 heure). Laissez vide si vous n'utilisez pas la détection automatique" "window_auto_max_duration": "Valeur recommandée: 60 (1 heure). Laissez vide si vous n'utilisez pas la détection automatique",
"use_window_central_config": "Cochez pour utiliser la configuration centrale des ouvertures. Décochez et saisissez les attributs pour utiliser une configuration spécifique des ouvertures",
"window_action": "Action a effectuer si la fenêtre est détectée comme ouverte"
} }
}, },
"motion": { "motion": {
"title": "Gestion de la détection de mouvement", "title": "Mouvement - {name}",
"description": "Le preset s'ajuste automatiquement si un mouvement est détecté\nLaissez l'entity id vide si non utilisé.\n'Preset mouvement' et 'Preset sans mouvement' doivent être choisis avec les preset à utiliser.", "description": "Gestion du mouvement. Le preset s'ajuste automatiquement si un mouvement est détecté\n'Preset mouvement' et 'Preset sans mouvement' doivent être choisis avec les preset à utiliser.",
"data": { "data": {
"motion_sensor_entity_id": "Détecteur de mouvement", "motion_sensor_entity_id": "Détecteur de mouvement",
"motion_delay": "Délai d'activation", "motion_delay": "Délai d'activation",
"motion_off_delay": "Délai de désactivation", "motion_off_delay": "Délai de désactivation",
"motion_preset": "Preset si mouvement", "motion_preset": "Preset si mouvement",
"no_motion_preset": "Preset si pas de mouvement" "no_motion_preset": "Preset sans mouvement",
"use_motion_central_config": "Utiliser la condfiguration centrale du mouvement"
}, },
"data_description": { "data_description": {
"motion_sensor_entity_id": "Détecteur de mouvement entity id", "motion_sensor_entity_id": "Id d'entité du détecteur de mouvement",
"motion_delay": "Délai avant activation lorsqu'un mouvement est détecté (secondss)", "motion_delay": "Délai avant activation lorsqu'un mouvement est détecté (secondss)",
"motion_off_delai": "Délai avant désactivation lorsqu'aucun mouvement n'est détecté (secondes)", "motion_off_delai": "Délai avant désactivation lorsqu'aucun mouvement n'est détecté (secondes)",
"motion_preset": "Preset à utiliser si mouvement détecté", "motion_preset": "Preset à utiliser si mouvement détecté",
"no_motion_preset": "Preset à utiliser si pas de mouvement détecté" "no_motion_preset": "Preset à utiliser si pas de mouvement détecté",
"use_motion_central_config": "Cochez pour utiliser la configuration centrale du mouvement. Décochez et saisissez les attributs pour utiliser une configuration spécifique du mouvement"
} }
}, },
"power": { "power": {
"title": "Gestion de l'énergie", "title": "Puissance - {name}",
"description": "Sélectionne automatiquement le preset 'power' si la puissance consommée est supérieure à un maximum.\nDonnez les entity id des capteurs qui mesurent la puissance totale et la puissance max autorisée.\nEnsuite donnez la puissance de l'équipement.\nTous les capteurs et la puissance consommée par l'équipement doivent avoir la même unité de mesure (kW ou W).", "description": "Gestion de la puissance. Sélectionne automatiquement le preset 'power' si la puissance consommée est supérieure à un maximum. Tous les capteurs et la puissance consommée par l'équipement doivent avoir la même unité de mesure (kW ou W).",
"data": { "data": {
"power_sensor_entity_id": "Capteur de puissance totale (entity id)", "power_sensor_entity_id": "Puissance totale",
"max_power_sensor_entity_id": "Capteur de puissance Max (entity id)", "max_power_sensor_entity_id": "Capteur de puissance Max (entity id)",
"power_temp": "Température si délestaqe" "power_temp": "Température si délestaqe",
"use_power_central_config": "Utiliser la configuration centrale de la puissance"
},
"data_description": {
"power_sensor_entity_id": "Entity id du capteur de puissance totale du logement",
"max_power_sensor_entity_id": "Entity id du capteur de puissance Max autorisée avant délestage",
"power_temp": "Température cible si délestaqe",
"use_power_central_config": "Cochez pour utiliser la configuration centrale de la puissance. Décochez et saisissez les attributs pour utiliser une configuration spécifique de la puissance"
} }
}, },
"presence": { "presence": {
"title": "Gestion de la présence", "title": "Présence - {name}",
"description": "Donnez un capteur de présence (true si quelqu'un est présent).\nEnsuite spécifiez soit un preset à utiliser, soit un offset de température à appliquer lorsque personne n'est présent.\nSi le préset est utilisé, l'offset ne sera pas pris en compte.\nLaissez l'entity id vide si la gestion de la présence est non utilisée.", "description": "Donnez un capteur de présence (true si quelqu'un est présent) et les températures cibles à utiliser en cas d'absence.",
"data": { "data": {
"presence_sensor_entity_id": "Capteur de présence", "presence_sensor_entity_id": "Capteur de présence",
"eco_away_temp": "preset Eco", "eco_away_temp": "preset Eco",
@@ -346,40 +432,58 @@
"frost_away_temp": "preset Hors-gel", "frost_away_temp": "preset Hors-gel",
"eco_ac_away_temp": "preset Eco en mode AC", "eco_ac_away_temp": "preset Eco en mode AC",
"comfort_ac_away_temp": "preset Comfort en mode AC", "comfort_ac_away_temp": "preset Comfort en mode AC",
"boost_ac_away_temp": "preset Boost en mode AC" "boost_ac_away_temp": "preset Boost en mode AC",
"use_presence_central_config": "Utiliser la configuration centrale de la présence"
}, },
"data_description": { "data_description": {
"presence_sensor_entity_id": "Capteur de présence entity id (true si quelqu'un est présent)", "presence_sensor_entity_id": "Id d'entité du capteur de présence",
"eco_away_temp": "Température en preset Eco en cas d'absence", "eco_away_temp": "Température en preset Eco en cas d'absence",
"comfort_away_temp": "Température en preset Comfort en cas d'absence", "comfort_away_temp": "Température en preset Comfort en cas d'absence",
"boost_away_temp": "Température en preset Boost en cas d'absence", "boost_away_temp": "Température en preset Boost en cas d'absence",
"frost_away_temp": "Température en preset Hors-gel en cas d'absence", "frost_away_temp": "Température en preset Hors-gel en cas d'absence",
"eco_ac_away_temp": "Température en preset Eco en cas d'absence en mode AC", "eco_ac_away_temp": "Température en preset Eco en cas d'absence en mode AC",
"comfort_ac_away_temp": "Température en preset Comfort en cas d'absence en mode AC", "comfort_ac_away_temp": "Température en preset Comfort en cas d'absence en mode AC",
"boost_ac_away_temp": "Température en preset Boost en cas d'absence en mode AC" "boost_ac_away_temp": "Température en preset Boost en cas d'absence en mode AC",
"use_presence_central_config": "Cochez pour utiliser la configuration centrale de la présence. Décochez et saisissez les attributs pour utiliser une configuration spécifique de la présence"
} }
}, },
"advanced": { "advanced": {
"title": "Parameters avancés", "title": "Avancés - {name}",
"description": "Configuration des paramètres avancés. Laissez les valeurs par défaut si vous ne savez pas ce que vous faites.\nCes paramètres peuvent induire des mauvais comportements du thermostat.", "description": "Paramètres avancés. Laissez les valeurs par défaut si vous ne savez pas ce que vous faites.\nCes paramètres peuvent induire des mauvais comportements du thermostat.",
"data": { "data": {
"minimal_activation_delay": "Délai minimal d'activation", "minimal_activation_delay": "Délai minimal d'activation",
"security_delay_min": "Délai maximal entre 2 mesures de températures", "security_delay_min": "Délai maximal entre 2 mesures de températures",
"security_min_on_percent": "Pourcentage minimal de puissance", "security_min_on_percent": "Pourcentage minimal de puissance",
"security_default_on_percent": "Pourcentage de puissance a utiliser en mode securité" "security_default_on_percent": "Pourcentage de puissance a utiliser en mode securité",
"use_advanced_central_config": "Utiliser la configuration centrale avancée"
}, },
"data_description": { "data_description": {
"minimal_activation_delay": "Délai en seondes en-dessous duquel l'équipement ne sera pas activé", "minimal_activation_delay": "Délai en seondes en-dessous duquel l'équipement ne sera pas activé",
"security_delay_min": "Délai maximal autorisé en minutes entre 2 mesures de températures. Au-dessus de ce délai, le thermostat se mettra en position de sécurité", "security_delay_min": "Délai maximal autorisé en minutes entre 2 mesures de températures. Au-dessus de ce délai, le thermostat se mettra en position de sécurité",
"security_min_on_percent": "Seuil minimal de pourcentage de chauffage en-dessous duquel le préréglage sécurité ne sera jamais activé", "security_min_on_percent": "Seuil minimal de pourcentage de chauffage en-dessous duquel le préréglage sécurité ne sera jamais activé",
"security_default_on_percent": "Valeur par défaut pour le pourcentage de chauffage en mode sécurité. Mettre 0 pour éteindre le radiateur en mode sécurité" "security_default_on_percent": "Valeur par défaut pour le pourcentage de chauffage en mode sécurité. Mettre 0 pour éteindre le radiateur en mode sécurité",
"use_advanced_central_config": "Cochez pour utiliser la configuration centrale avancée. Décochez et saisissez les attributs pour utiliser une configuration spécifique avancée"
}
},
"central_boiler": {
"title": "Contrôle de la chaudière centrale - {name}",
"description": "Donnez les services à appeler pour allumer/éteindre la chaudière centrale. Laissez vide, si aucun appel de service ne doit être effectué (dans ce cas, vous devrez gérer vous même l'allumage/extinction de votre chaudière centrale). Le service a appelé doit être formatté comme suit: `entity_id/service_name[/attribut:valeur]` (/attribut:valeur est facultatif)\nPar exemple:\n- pour allumer un switch: `switch.controle_chaudiere/switch.turn_on`\n- pour éteindre un switch: `switch.controle_chaudiere/switch.turn_off`\n- pour programmer la chaudière sur 25° et ainsi forcer son allumage: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- pour envoyer 10° à la chaudière et ainsi forcer son extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
"data": {
"central_boiler_activation_service": "Commande pour allumer",
"central_boiler_deactivation_service": "Commande pour éteindre"
},
"data_description": {
"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]"
} }
} }
}, },
"error": { "error": {
"unknown": "Erreur inattendue", "unknown": "Erreur inattendue",
"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.",
"service_configuration_format": "Mauvais format de la configuration du service"
}, },
"abort": { "abort": {
"already_configured": "Le device est déjà configuré" "already_configured": "Le device est déjà configuré"
@@ -388,6 +492,7 @@
"selector": { "selector": {
"thermostat_type": { "thermostat_type": {
"options": { "options": {
"thermostat_central_config": "Configuration centrale",
"thermostat_over_switch": "Thermostat sur un switch", "thermostat_over_switch": "Thermostat sur un switch",
"thermostat_over_climate": "Thermostat sur un autre thermostat", "thermostat_over_climate": "Thermostat sur un autre thermostat",
"thermostat_over_valve": "Thermostat sur une valve" "thermostat_over_valve": "Thermostat sur une valve"
@@ -411,6 +516,22 @@
"auto_fan_high": "Forte", "auto_fan_high": "Forte",
"auto_fan_turbo": "Turbo" "auto_fan_turbo": "Turbo"
} }
},
"window_action": {
"options": {
"window_turn_off": "Eteindre",
"window_fan_only": "Ventilateur seul",
"window_frost_temp": "Hors gel",
"window_eco_temp": "Eco"
}
},
"presets": {
"options": {
"frost": "Hors-gel",
"eco": "Eco",
"comfort": "Confort",
"boost": "Renforcé (boost)"
}
} }
}, },
"entity": { "entity": {

View File

@@ -4,6 +4,15 @@
"flow_title": "Všestranná konfigurácia termostatu", "flow_title": "Všestranná konfigurácia termostatu",
"step": { "step": {
"user": { "user": {
"title": "Typ všestranného termostatu",
"data": {
"thermostat_type": "Typ termostatu"
},
"data_description": {
"thermostat_type": "Len jeden centrálny typ konfigurácie je možný"
}
},
"main": {
"title": "Pridajte nový všestranný termostat", "title": "Pridajte nový všestranný termostat",
"description": "Hlavné povinné atribúty", "description": "Hlavné povinné atribúty",
"data": { "data": {
@@ -15,10 +24,17 @@
"temp_min": "Minimálna povolená teplota", "temp_min": "Minimálna povolená teplota",
"temp_max": "Maximálna povolená teplota", "temp_max": "Maximálna povolená teplota",
"device_power": "Napájanie zariadenia", "device_power": "Napájanie zariadenia",
"use_central_mode": "Povoliť ovládanie centrálnou entitou (potrebná centrálna konfigurácia)",
"use_window_feature": "Použite detekciu okien", "use_window_feature": "Použite detekciu okien",
"use_motion_feature": "Použite detekciu pohybu", "use_motion_feature": "Použite detekciu pohybu",
"use_power_feature": "Použite správu napájania", "use_power_feature": "Použite správu napájania",
"use_presence_feature": "Použite detekciu prítomnosti" "use_presence_feature": "Použite detekciu prítomnosti",
"use_main_central_config": "Použite centrálnu hlavnú konfiguráciu"
},
"data_description": {
"use_central_mode": "Zaškrtnutím povolíte ovládanie VTherm pomocou vybraných entít central_mode",
"use_main_central_config": "Začiarknite, ak chcete použiť centrálnu hlavnú konfiguráciu. Zrušte začiarknutie, ak chcete použiť špecifickú hlavnú konfiguráciu pre tento VTherm",
"external_temperature_sensor_entity_id": "ID entity snímača vonkajšej teploty. Nepoužíva sa, ak je zvolená centrálna konfigurácia"
} }
}, },
"type": { "type": {
@@ -39,11 +55,11 @@
"valve_entity2_id": "2. ventil číslo", "valve_entity2_id": "2. ventil číslo",
"valve_entity3_id": "3. ventil číslo", "valve_entity3_id": "3. ventil číslo",
"valve_entity4_id": "4. ventil číslo", "valve_entity4_id": "4. ventil číslo",
"auto_regulation_mode": "Self-regulation", "auto_regulation_mode": "Samoregulácia",
"auto_regulation_dtemp": "Regulation threshold", "auto_regulation_dtemp": "Regulačný prah",
"auto_regulation_periode_min": "Regulation minimal period", "auto_regulation_periode_min": "Regulačné minimálne obdobie",
"inverse_switch_command": "Inverse switch command", "inverse_switch_command": "Inverzný prepínací príkaz",
"auto_fan_mode": " Auto fan mode" "auto_fan_mode": "Režim automatického ventilátora"
}, },
"data_description": { "data_description": {
"heater_entity_id": "ID entity povinného ohrievača", "heater_entity_id": "ID entity povinného ohrievača",
@@ -60,11 +76,11 @@
"valve_entity2_id": "2. ventil číslo entity id", "valve_entity2_id": "2. ventil číslo entity id",
"valve_entity3_id": "3. ventil číslo entity id", "valve_entity3_id": "3. ventil číslo entity id",
"valve_entity4_id": "4. ventil číslo entity id", "valve_entity4_id": "4. ventil číslo entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature", "auto_regulation_mode": "Automatické nastavenie cieľovej teploty",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send", "auto_regulation_dtemp": "Hranica v °, pod ktorou sa zmena teploty neodošle",
"auto_regulation_periode_min": "Duration in minutes between two regulation update", "auto_regulation_periode_min": "Trvanie v minútach medzi dvoma aktualizáciami predpisov",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command", "inverse_switch_command": "V prípade spínača s pilotným vodičom a diódou možno budete musieť príkaz invertovať",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" "auto_fan_mode": "Automaticky aktivujte ventilátor, keď je potrebné veľké vykurovanie/chladenie"
} }
}, },
"tpi": { "tpi": {
@@ -72,7 +88,13 @@
"description": "Časovo proporcionálne integrálne atribúty", "description": "Časovo proporcionálne integrálne atribúty",
"data": { "data": {
"tpi_coef_int": "Koeficient na použitie pre vnútornú teplotnú deltu", "tpi_coef_int": "Koeficient na použitie pre vnútornú teplotnú deltu",
"tpi_coef_ext": "Koeficient na použitie pre deltu vonkajšej teploty" "tpi_coef_ext": "Koeficient na použitie pre deltu vonkajšej teploty",
"use_tpi_central_config": "Použite centrálnu konfiguráciu TPI"
},
"data_description": {
"tpi_coef_int": "Koeficient na použitie pre vnútornú teplotnú deltu",
"tpi_coef_ext": "Koeficient na použitie pre deltu vonkajšej teploty",
"use_tpi_central_config": "Začiarknite, ak chcete použiť centrálnu konfiguráciu TPI. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu TPI pre tento VTherm"
} }
}, },
"presets": { "presets": {
@@ -85,7 +107,18 @@
"frost_temp": "Teplota v prednastavení Frost protection", "frost_temp": "Teplota v prednastavení Frost protection",
"eco_ac_temp": "Teplota v režime Eco prednastavená pre režim AC", "eco_ac_temp": "Teplota v režime Eco prednastavená pre režim AC",
"comfort_ac_temp": "Teplota v režime Comfort je prednastavená pre režim AC", "comfort_ac_temp": "Teplota v režime Comfort je prednastavená pre režim AC",
"boost_ac_temp": "Prednastavená teplota v režime Boost pre režim AC" "boost_ac_temp": "Prednastavená teplota v režime Boost pre režim AC",
"use_presets_central_config": "Použite konfiguráciu centrálnych predvolieb"
},
"data_description": {
"eco_temp": "Teplota v predvoľbe Eco",
"comfort_temp": "Prednastavená teplota v komfortnom režime",
"boost_temp": "Teplota v prednastavení Boost",
"frost_temp": "Teplota v prednastavenej ochrane proti mrazu",
"eco_ac_temp": "Teplota v režime Eco prednastavená pre režim AC",
"comfort_ac_temp": "Teplota v režime Comfort je prednastavená pre režim AC",
"boost_ac_temp": "Prednastavená teplota v režime Boost pre režim AC",
"use_presets_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálnych predvolieb. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu predvolieb pre tento VTherm"
} }
}, },
"window": { "window": {
@@ -96,14 +129,16 @@
"window_delay": "Oneskorenie snímača okna (sekundy)", "window_delay": "Oneskorenie snímača okna (sekundy)",
"window_auto_open_threshold": "Prah poklesu teploty pre automatickú detekciu otvoreného okna (v °/hodina)", "window_auto_open_threshold": "Prah poklesu teploty pre automatickú detekciu otvoreného okna (v °/hodina)",
"window_auto_close_threshold": "Prahová hodnota zvýšenia teploty pre koniec automatickej detekcie (v °/hodina)", "window_auto_close_threshold": "Prahová hodnota zvýšenia teploty pre koniec automatickej detekcie (v °/hodina)",
"window_auto_max_duration": "Maximálne trvanie automatickej detekcie otvoreného okna (v min)" "window_auto_max_duration": "Maximálne trvanie automatickej detekcie otvoreného okna (v min)",
"use_window_central_config": "Použite centrálnu konfiguráciu okna"
}, },
"data_description": { "data_description": {
"window_sensor_entity_id": "Nechajte prázdne, ak nemáte použiť žiadny okenný senzor", "window_sensor_entity_id": "Nechajte prázdne, ak nemáte použiť žiadny okenný senzor",
"window_delay": "Zohľadňuje sa oneskorenie v sekundách pred detekciou snímača", "window_delay": "Zohľadňuje sa oneskorenie v sekundách pred detekciou snímača",
"window_auto_open_threshold": "Odporúčaná hodnota: medzi 3 a 10. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne", "window_auto_open_threshold": "Odporúčaná hodnota: medzi 3 a 10. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne",
"window_auto_close_threshold": "Odporúčaná hodnota: 0. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne", "window_auto_close_threshold": "Odporúčaná hodnota: 0. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne",
"window_auto_max_duration": "Odporúčaná hodnota: 60 (jedna hodina). Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne" "window_auto_max_duration": "Odporúčaná hodnota: 60 (jedna hodina). Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne",
"use_window_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálneho okna. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu okna pre tento VTherm"
} }
}, },
"motion": { "motion": {
@@ -114,14 +149,16 @@
"motion_delay": "Oneskorenie aktivácie", "motion_delay": "Oneskorenie aktivácie",
"motion_off_delay": "Oneskorenie deaktivácie", "motion_off_delay": "Oneskorenie deaktivácie",
"motion_preset": "Prednastavený pohyb", "motion_preset": "Prednastavený pohyb",
"no_motion_preset": "Žiadna predvoľba pohybu" "no_motion_preset": "Žiadna predvoľba pohybu",
"use_motion_central_config": "Použite centrálnu konfiguráciu pohybu"
}, },
"data_description": { "data_description": {
"motion_sensor_entity_id": "ID entity snímača pohybu", "motion_sensor_entity_id": "ID entity snímača pohybu",
"motion_delay": "Oneskorenie aktivácie pohybu (sekundy)", "motion_delay": "Oneskorenie aktivácie pohybu (sekundy)",
"motion_off_delay": "Oneskorenie deaktivácie pohybu (sekundy)", "motion_off_delay": "Oneskorenie deaktivácie pohybu (sekundy)",
"motion_preset": "Prednastavené na použitie pri detekcii pohybu", "motion_preset": "Prednastavené na použitie pri detekcii pohybu",
"no_motion_preset": "Prednastavené na použitie, keď nie je detekovaný žiadny pohyb" "no_motion_preset": "Prednastavené na použitie, keď nie je detekovaný žiadny pohyb",
"use_motion_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálneho pohybu. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu pohybu pre tento VTherm"
} }
}, },
"power": { "power": {
@@ -130,7 +167,14 @@
"data": { "data": {
"power_sensor_entity_id": "ID entity snímača výkonu", "power_sensor_entity_id": "ID entity snímača výkonu",
"max_power_sensor_entity_id": "ID entity snímača maximálneho výkonu", "max_power_sensor_entity_id": "ID entity snímača maximálneho výkonu",
"power_temp": "Teplota pre zníženie výkonu" "power_temp": "Teplota pre zníženie výkonu",
"use_power_central_config": "Použite centrálnu konfiguráciu napájania"
},
"data_description": {
"power_sensor_entity_id": "ID entity snímača výkonu",
"max_power_sensor_entity_id": "ID entity snímača maximálneho výkonu",
"power_temp": "Teplota pre zníženie výkonu",
"use_power_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálneho napájania. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu napájania pre tento VTherm"
} }
}, },
"presence": { "presence": {
@@ -144,7 +188,19 @@
"frost_away_temp": "Prednastavená teplota v režime Frost protection, keď nie je prítomný", "frost_away_temp": "Prednastavená teplota v režime Frost protection, keď nie je prítomný",
"eco_ac_away_temp": "Teplota v prednastavenej Eco, keď nie je prítomná v režime AC", "eco_ac_away_temp": "Teplota v prednastavenej Eco, keď nie je prítomná v režime AC",
"comfort_ac_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný v režime AC", "comfort_ac_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný v režime AC",
"boost_ac_away_temp": "Teplota v prednastavenom Boost, keď nie je prítomný v režime AC" "boost_ac_away_temp": "Teplota v prednastavenom Boost, keď nie je prítomný v režime AC",
"use_presence_central_config": "Použite centrálnu konfiguráciu prítomnosti"
},
"data_description": {
"presence_sensor_entity_id": "ID entity senzora prítomnosti",
"eco_away_temp": "Teplota v prednastavenej Eco, keď nie je žiadna prítomnosť",
"comfort_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný",
"boost_away_temp": "Prednastavená teplota v režime Boost, keď nie je prítomný",
"frost_away_temp": "Teplota v Prednastavená ochrana pred mrazom, keď nie je prítomný",
"eco_ac_away_temp": "Teplota v prednastavenej Eco, keď nie je prítomná v režime AC",
"comfort_ac_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný v režime AC",
"boost_ac_away_temp": "Teplota v prednastavenom Boost, keď nie je prítomný v režime AC",
"use_presence_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálnej prítomnosti. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu prítomnosti pre tento VTherm"
} }
}, },
"advanced": { "advanced": {
@@ -154,20 +210,23 @@
"minimal_activation_delay": "Minimálne oneskorenie aktivácie", "minimal_activation_delay": "Minimálne oneskorenie aktivácie",
"security_delay_min": "Bezpečnostné oneskorenie (v minútach)", "security_delay_min": "Bezpečnostné oneskorenie (v minútach)",
"security_min_on_percent": "Minimálne percento výkonu na aktiváciu bezpečnostného režimu", "security_min_on_percent": "Minimálne percento výkonu na aktiváciu bezpečnostného režimu",
"security_default_on_percent": "Percento výkonu na použitie v bezpečnostnom režime" "security_default_on_percent": "Percento výkonu na použitie v bezpečnostnom režime",
"use_advanced_central_config": "Použite centrálnu rozšírenú konfiguráciu"
}, },
"data_description": { "data_description": {
"minimal_activation_delay": "Oneskorenie v sekundách, pri ktorom sa zariadenie neaktivuje", "minimal_activation_delay": "Oneskorenie v sekundách, pri ktorom sa zariadenie neaktivuje",
"security_delay_min": "Maximálne povolené oneskorenie v minútach medzi dvoma meraniami teploty. Po uplynutí tohto oneskorenia sa termostat prepne do bezpečnostného vypnutého stavu", "security_delay_min": "Maximálne povolené oneskorenie v minútach medzi dvoma meraniami teploty. Po uplynutí tohto oneskorenia sa termostat prepne do bezpečnostného vypnutého stavu",
"security_min_on_percent": "Minimálna percentuálna hodnota ohrevu pre aktiváciu prednastavenej bezpečnosti. Pod týmto percentom výkonu termostat neprejde do prednastavenia zabezpečenia", "security_min_on_percent": "Minimálna percentuálna hodnota ohrevu pre aktiváciu prednastavenej bezpečnosti. Pod týmto percentom výkonu termostat neprejde do prednastavenia zabezpečenia",
"security_default_on_percent": "Predvolená percentuálna hodnota vykurovacieho výkonu v bezpečnostnej predvoľbe. Nastavte na 0, ak chcete vypnúť ohrievač v zabezpečenom stave" "security_default_on_percent": "Predvolená percentuálna hodnota vykurovacieho výkonu v bezpečnostnej predvoľbe. Nastavte na 0, ak chcete vypnúť ohrievač v zabezpečenom stave",
"use_advanced_central_config": "Začiarknite, ak chcete použiť centrálnu rozšírenú konfiguráciu. Zrušte začiarknutie, ak chcete použiť špecifickú rozšírenú konfiguráciu pre tento VTherm"
} }
} }
}, },
"error": { "error": {
"unknown": "Neočakávaná chyba", "unknown": "Neočakávaná chyba",
"unknown_entity": "Neznáme ID entity", "unknown_entity": "Neznáme ID entity",
"window_open_detection_method": "Mala by sa použiť iba jedna metóda detekcie otvoreného okna. Použite senzor alebo automatickú detekciu cez teplotný prah, ale nie oboje" "window_open_detection_method": "Mala by sa použiť iba jedna metóda detekcie otvoreného okna. Použite senzor alebo automatickú detekciu cez teplotný prah, ale nie oboje",
"no_central_config": "Nemôžete zaškrtnúť „použiť centrálnu konfiguráciu“, pretože sa nenašla žiadna centrálna konfigurácia. Aby ste ho mohli používať, musíte si vytvoriť všestranný termostat typu „Central Configuration“."
}, },
"abort": { "abort": {
"already_configured": "Zariadenie je už nakonfigurované" "already_configured": "Zariadenie je už nakonfigurované"
@@ -177,7 +236,16 @@
"flow_title": "Všestranná konfigurácia termostatu", "flow_title": "Všestranná konfigurácia termostatu",
"step": { "step": {
"user": { "user": {
"title": "Pridajte nový všestranný termostat", "title": "Typ - {name}",
"data": {
"thermostat_type": "Typ termostatu"
},
"data_description": {
"thermostat_type": "Je možný len jeden centrálny typ konfigurácie"
}
},
"main": {
"title": "Hlavný - {name}",
"description": "Hlavné povinné atribúty", "description": "Hlavné povinné atribúty",
"data": { "data": {
"name": "Názov", "name": "Názov",
@@ -188,14 +256,21 @@
"temp_min": "Minimálna povolená teplota", "temp_min": "Minimálna povolená teplota",
"temp_max": "Maximálna povolená teplota", "temp_max": "Maximálna povolená teplota",
"device_power": "Výkon zariadenia (kW)", "device_power": "Výkon zariadenia (kW)",
"use_central_mode": "Povoliť ovládanie centrálnou entitou (potrebná centrálna konfigurácia)",
"use_window_feature": "Použite detekciu okien", "use_window_feature": "Použite detekciu okien",
"use_motion_feature": "Použite detekciu pohybu", "use_motion_feature": "Použite detekciu pohybu",
"use_power_feature": "Použite správu napájania", "use_power_feature": "Použite správu napájania",
"use_presence_feature": "Použite detekciu prítomnosti" "use_presence_feature": "Použite detekciu prítomnosti",
"use_main_central_config": "Použite centrálnu hlavnú konfiguráciu"
},
"data_description": {
"use_central_mode": "Zaškrtnutím povolíte ovládanie VTherm pomocou vybraných entít central_mode",
"use_main_central_config": "Začiarknite, ak chcete použiť centrálnu hlavnú konfiguráciu. Ak chcete použiť špecifickú konfiguráciu pre tento VTherm, zrušte začiarknutie",
"external_temperature_sensor_entity_id": "ID entity snímača vonkajšej teploty. Nepoužíva sa, ak je zvolená centrálna konfigurácia"
} }
}, },
"type": { "type": {
"title": "Prepojené entity", "title": "Prepojené entity - {name}",
"description": "Atribúty prepojených entít", "description": "Atribúty prepojených entít",
"data": { "data": {
"heater_entity_id": "Spínač ohrievača", "heater_entity_id": "Spínač ohrievača",
@@ -212,11 +287,11 @@
"valve_entity2_id": "2. ventil číslo", "valve_entity2_id": "2. ventil číslo",
"valve_entity3_id": "3. ventil číslo", "valve_entity3_id": "3. ventil číslo",
"valve_entity4_id": "4. ventil číslo", "valve_entity4_id": "4. ventil číslo",
"auto_regulation_mode": "Self-regulation", "auto_regulation_mode": "Samoregulácia",
"auto_regulation_dtemp": "Regulation threshold", "auto_regulation_dtemp": "Regulačný prah",
"auto_regulation_periode_min": "Regulation minimal period", "auto_regulation_periode_min": "Regulačné minimálne obdobie",
"inverse_switch_command": "Inverse switch command", "inverse_switch_command": "Inverzný prepínací príkaz",
"auto_fan_mode": " Auto fan mode" "auto_fan_mode": "Režim automatického ventilátora"
}, },
"data_description": { "data_description": {
"heater_entity_id": "ID entity povinného ohrievača", "heater_entity_id": "ID entity povinného ohrievača",
@@ -233,23 +308,29 @@
"valve_entity2_id": "2. ventil číslo entity id", "valve_entity2_id": "2. ventil číslo entity id",
"valve_entity3_id": "3. ventil číslo entity id", "valve_entity3_id": "3. ventil číslo entity id",
"valve_entity4_id": "4. ventil číslo entity id", "valve_entity4_id": "4. ventil číslo entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature", "auto_regulation_mode": "Automatické nastavenie cieľovej teploty",
"auto_regulation_dtemp": "The threshold in ° under which the temperature change will not be send", "auto_regulation_dtemp": "Hranica v °, pod ktorou sa zmena teploty neodošle",
"auto_regulation_periode_min": "Duration in minutes between two regulation update", "auto_regulation_periode_min": "Trvanie v minútach medzi dvoma aktualizáciami predpisov",
"inverse_switch_command": "For switch with pilot wire and diode you may need to inverse the command", "inverse_switch_command": "V prípade spínača s pilotným vodičom a diódou možno budete musieť príkaz invertovať",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" "auto_fan_mode": "Automaticky aktivujte ventilátor, keď je potrebné veľké vykurovanie/chladenie"
} }
}, },
"tpi": { "tpi": {
"title": "TPI", "title": "TPI - {name}",
"description": "Časovo proporcionálne integrálne atribúty", "description": "Časovo proporcionálne integrálne atribúty",
"data": { "data": {
"tpi_coef_int": "Koeficient na použitie pre vnútornú teplotnú deltu", "tpi_coef_int": "Koeficient na použitie pre vnútornú teplotnú deltu",
"tpi_coef_ext": "Koeficient na použitie pre vonkajšiu teplotnú deltu" "tpi_coef_ext": "Koeficient na použitie pre vonkajšiu teplotnú deltu",
"use_tpi_central_config": "Použite centrálnu konfiguráciu TPI"
},
"data_description": {
"tpi_coef_int": "Koeficient na použitie pre vnútornú teplotnú deltu",
"tpi_coef_ext": "Koeficient na použitie pre deltu vonkajšej teploty",
"use_tpi_central_config": "Začiarknite, ak chcete použiť centrálnu konfiguráciu TPI. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu TPI pre tento VTherm"
} }
}, },
"presets": { "presets": {
"title": "Predvoľby", "title": "Predvoľby - {name}",
"description": "Pre každú predvoľbu zadajte cieľovú teplotu (0, ak chcete predvoľbu ignorovať)", "description": "Pre každú predvoľbu zadajte cieľovú teplotu (0, ak chcete predvoľbu ignorovať)",
"data": { "data": {
"eco_temp": "Teplota v predvoľbe Eco", "eco_temp": "Teplota v predvoľbe Eco",
@@ -258,52 +339,74 @@
"frost_temp": "Teplota v prednastavení Frost protection", "frost_temp": "Teplota v prednastavení Frost protection",
"eco_ac_temp": "Teplota v režime Eco prednastavená pre režim AC", "eco_ac_temp": "Teplota v režime Eco prednastavená pre režim AC",
"comfort_ac_temp": "Teplota v režime Comfort je prednastavená pre režim AC", "comfort_ac_temp": "Teplota v režime Comfort je prednastavená pre režim AC",
"boost_ac_temp": "Prednastavená teplota v režime Boost pre režim AC" "boost_ac_temp": "Prednastavená teplota v režime Boost pre režim AC",
"use_presets_central_config": "Použite konfiguráciu centrálnych predvolieb"
},
"data_description": {
"eco_temp": "Teplota v predvoľbe Eco",
"comfort_temp": "Prednastavená teplota v komfortnom režime",
"boost_temp": "Teplota v prednastavení Boost",
"frost_temp": "Teplota v prednastavenej ochrane proti mrazu",
"eco_ac_temp": "Teplota v režime Eco prednastavená pre režim AC",
"comfort_ac_temp": "Teplota v režime Comfort je prednastavená pre režim AC",
"boost_ac_temp": "Prednastavená teplota v režime Boost pre režim AC",
"use_presets_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálnych predvolieb. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu predvolieb pre tento VTherm"
} }
}, },
"window": { "window": {
"title": "Správa okien", "title": "Správa okien - {name}",
"description": "Otvoriť správu okien.\nAk sa príslušné entity_id nepoužíva, ponechajte prázdne\nMôžete tiež nakonfigurovať automatickú detekciu otvoreného okna na základe poklesu teploty", "description": "Otvoriť správu okien.\nAk sa príslušné entity_id nepoužíva, ponechajte prázdne\nMôžete tiež nakonfigurovať automatickú detekciu otvoreného okna na základe poklesu teploty",
"data": { "data": {
"window_sensor_entity_id": "ID entity snímača okna", "window_sensor_entity_id": "ID entity snímača okna",
"window_delay": "Oneskorenie snímača okna (sekundy)", "window_delay": "Oneskorenie snímača okna (sekundy)",
"window_auto_open_threshold": "Prah poklesu teploty pre automatickú detekciu otvoreného okna (v °/hodina)", "window_auto_open_threshold": "Prah poklesu teploty pre automatickú detekciu otvoreného okna (v °/hodina)",
"window_auto_close_threshold": "Prahová hodnota zvýšenia teploty pre koniec automatickej detekcie (v °/hodina)", "window_auto_close_threshold": "Prahová hodnota zvýšenia teploty pre koniec automatickej detekcie (v °/hodina)",
"window_auto_max_duration": "Maximálne trvanie automatickej detekcie otvoreného okna (v min)" "window_auto_max_duration": "Maximálne trvanie automatickej detekcie otvoreného okna (v min)",
"use_window_central_config": "Použite centrálnu konfiguráciu okna"
}, },
"data_description": { "data_description": {
"window_sensor_entity_id": "Nechajte prázdne, ak nemáte použiť žiadny okenný senzor", "window_sensor_entity_id": "Nechajte prázdne, ak nemáte použiť žiadny okenný senzor",
"window_delay": "Zohľadňuje sa oneskorenie v sekundách pred detekciou snímača", "window_delay": "Zohľadňuje sa oneskorenie v sekundách pred detekciou snímača",
"window_auto_open_threshold": "Odporúčaná hodnota: medzi 3 a 10. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne", "window_auto_open_threshold": "Odporúčaná hodnota: medzi 3 a 10. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne",
"window_auto_close_threshold": "Odporúčaná hodnota: 0. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne", "window_auto_close_threshold": "Odporúčaná hodnota: 0. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne",
"window_auto_max_duration": "Odporúčaná hodnota: 60 (jedna hodina). Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne" "window_auto_max_duration": "Odporúčaná hodnota: 60 (jedna hodina). Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne",
"use_window_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálneho okna. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu okna pre tento VTherm"
} }
}, },
"motion": { "motion": {
"title": "Riadenie pohybu", "title": "Riadenie pohybu - {name}",
"description": "Správa snímača pohybu. Predvoľba sa môže automaticky prepínať v závislosti od detekcie pohybu\nAk sa nepoužíva, ponechajte zodpovedajúce entity_id prázdne.\nmotion_preset a no_motion_preset by mali byť nastavené na zodpovedajúci názov predvoľby", "description": "Správa snímača pohybu. Predvoľba sa môže automaticky prepínať v závislosti od detekcie pohybu\nAk sa nepoužíva, ponechajte zodpovedajúce entity_id prázdne.\nmotion_preset a no_motion_preset by mali byť nastavené na zodpovedajúci názov predvoľby",
"data": { "data": {
"motion_sensor_entity_id": "ID entity snímača pohybu", "motion_sensor_entity_id": "ID entity snímača pohybu",
"motion_delay": "Oneskorenie aktivácie", "motion_delay": "Oneskorenie aktivácie",
"motion_off_delay": "Oneskorenie deaktivácie", "motion_off_delay": "Oneskorenie deaktivácie",
"motion_preset": "Prednastavený pohyb", "motion_preset": "Prednastavený pohyb",
"no_motion_preset": "Žiadna predvoľba pohybu" "no_motion_preset": "Žiadna predvoľba pohybu",
"use_motion_central_config": "Použite centrálnu konfiguráciu pohybu"
}, },
"data_description": { "data_description": {
"motion_sensor_entity_id": "ID entity snímača pohybu", "motion_sensor_entity_id": "ID entity snímača pohybu",
"motion_delay": "Oneskorenie aktivácie pohybu (sekundy)", "motion_delay": "Oneskorenie aktivácie pohybu (sekundy)",
"motion_off_delay": "Oneskorenie deaktivácie pohybu (sekundy)", "motion_off_delay": "Oneskorenie deaktivácie pohybu (sekundy)",
"motion_preset": "Prednastavené na použitie pri detekcii pohybu", "motion_preset": "Prednastavené na použitie pri detekcii pohybu",
"no_motion_preset": "Prednastavené na použitie, keď nie je detekovaný žiadny pohyb" "no_motion_preset": "Prednastavené na použitie, keď nie je detekovaný žiadny pohyb",
"use_motion_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálneho pohybu. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu pohybu pre tento VTherm"
} }
}, },
"power": { "power": {
"title": "Správa napájania", "title": "Správa napájania - {name}",
"description": "Atribúty správy napájania.\nPoskytuje senzor výkonu a maximálneho výkonu vášho domova.\nPotom zadajte spotrebu energie ohrievača, keď je zapnutý.\nVšetky senzory a výkon zariadenia by mali mať rovnakú jednotku (kW alebo W).\nPonechajte zodpovedajúce entity_id prázdne ak sa nepoužíva.", "description": "Atribúty správy napájania.\nPoskytuje senzor výkonu a maximálneho výkonu vášho domova.\nPotom zadajte spotrebu energie ohrievača, keď je zapnutý.\nVšetky senzory a výkon zariadenia by mali mať rovnakú jednotku (kW alebo W).\nPonechajte zodpovedajúce entity_id prázdne ak sa nepoužíva.",
"data": { "data": {
"power_sensor_entity_id": "ID entity snímača výkonu", "power_sensor_entity_id": "ID entity snímača výkonu",
"max_power_sensor_entity_id": "ID entity snímača maximálneho výkonu", "max_power_sensor_entity_id": "ID entity snímača maximálneho výkonu",
"power_temp": "Teplota pre zníženie výkonu" "power_temp": "Teplota pre zníženie výkonu",
"use_power_central_config": "Použite centrálnu konfiguráciu napájania"
},
"data_description": {
"power_sensor_entity_id": "ID entity snímača výkonu",
"max_power_sensor_entity_id": "ID entity snímača maximálneho výkonu",
"power_temp": "Teplota pre zníženie výkonu",
"use_power_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálneho napájania. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu napájania pre tento VTherm"
} }
}, },
"presence": { "presence": {
@@ -317,30 +420,45 @@
"frost_away_temp": "Prednastavená teplota v režime Frost protection, keď nie je prítomný", "frost_away_temp": "Prednastavená teplota v režime Frost protection, keď nie je prítomný",
"eco_ac_away_temp": "Teplota v prednastavenej Eco, keď nie je prítomná v režime AC", "eco_ac_away_temp": "Teplota v prednastavenej Eco, keď nie je prítomná v režime AC",
"comfort_ac_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný v režime AC", "comfort_ac_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný v režime AC",
"boost_ac_away_temp": "Teplota v prednastavenom Boost, keď nie je prítomný v režime AC" "boost_ac_away_temp": "Teplota v prednastavenom Boost, keď nie je prítomný v režime AC",
"use_presence_central_config": "Použite centrálnu konfiguráciu prítomnosti"
},
"data_description": {
"presence_sensor_entity_id": "ID entity senzora prítomnosti",
"eco_away_temp": "Teplota v prednastavenej Eco, keď nie je žiadna prítomnosť",
"comfort_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný",
"boost_away_temp": "Prednastavená teplota v režime Boost, keď nie je prítomný",
"frost_away_temp": "Teplota v Prednastavená ochrana pred mrazom, keď nie je prítomný",
"eco_ac_away_temp": "Teplota v prednastavenej Eco, keď nie je prítomná v režime AC",
"comfort_ac_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný v režime AC",
"boost_ac_away_temp": "Teplota v prednastavenom Boost, keď nie je prítomný v režime AC",
"use_presence_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálnej prítomnosti. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu prítomnosti pre tento VTherm"
} }
}, },
"advanced": { "advanced": {
"title": "Pokročilé parametre", "title": "Pokročilé parametre - {name}",
"description": "Konfigurácia pokročilých parametrov. Ak neviete, čo robíte, ponechajte predvolené hodnoty.\nTento parameter môže viesť k veľmi zlej regulácii teploty alebo výkonu.", "description": "Konfigurácia pokročilých parametrov. Ak neviete, čo robíte, ponechajte predvolené hodnoty.\nTento parameter môže viesť k veľmi zlej regulácii teploty alebo výkonu.",
"data": { "data": {
"minimal_activation_delay": "Minimálne oneskorenie aktivácie", "minimal_activation_delay": "Minimálne oneskorenie aktivácie",
"security_delay_min": "Bezpečnostné oneskorenie (v minútach)", "security_delay_min": "Bezpečnostné oneskorenie (v minútach)",
"security_min_on_percent": "Minimálne percento výkonu pre bezpečnostný režim", "security_min_on_percent": "Minimálne percento výkonu pre bezpečnostný režim",
"security_default_on_percent": "Percento výkonu na použitie v bezpečnostnom režime" "security_default_on_percent": "Percento výkonu na použitie v bezpečnostnom režime",
"use_advanced_central_config": "Použite centrálnu rozšírenú konfiguráciu"
}, },
"data_description": { "data_description": {
"minimal_activation_delay": "Oneskorenie v sekundách, pri ktorom sa zariadenie neaktivuje", "minimal_activation_delay": "Oneskorenie v sekundách, pri ktorom sa zariadenie neaktivuje",
"security_delay_min": "Maximálne povolené oneskorenie v minútach medzi dvoma meraniami teploty. Po uplynutí tohto oneskorenia sa termostat prepne do bezpečnostného vypnutého stavu", "security_delay_min": "Maximálne povolené oneskorenie v minútach medzi dvoma meraniami teploty. Po uplynutí tohto oneskorenia sa termostat prepne do bezpečnostného vypnutého stavu",
"security_min_on_percent": "Minimálna percentuálna hodnota ohrevu pre aktiváciu prednastavenej bezpečnosti. Pod týmto percentom výkonu termostat neprejde do prednastavenia zabezpečenia", "security_min_on_percent": "Minimálna percentuálna hodnota ohrevu pre aktiváciu prednastavenej bezpečnosti. Pod týmto percentom výkonu termostat neprejde do prednastavenia zabezpečenia",
"security_default_on_percent": "Predvolená percentuálna hodnota vykurovacieho výkonu v bezpečnostnej predvoľbe. Nastavte na 0, ak chcete vypnúť ohrievač v zabezpečenom stave" "security_default_on_percent": "Predvolená percentuálna hodnota vykurovacieho výkonu v bezpečnostnej predvoľbe. Nastavte na 0, ak chcete vypnúť ohrievač v zabezpečenom stave",
"use_advanced_central_config": "Začiarknite, ak chcete použiť centrálnu rozšírenú konfiguráciu. Zrušte začiarknutie, ak chcete použiť špecifickú rozšírenú konfiguráciu pre tento VTherm"
} }
} }
}, },
"error": { "error": {
"unknown": "Neočakávaná chyba", "unknown": "Neočakávaná chyba",
"unknown_entity": "Neznáme ID entity", "unknown_entity": "Neznáme ID entity",
"window_open_detection_method": "Mala by sa použiť iba jedna metóda detekcie otvoreného okna. Použite senzor alebo automatickú detekciu cez teplotný prah, ale nie oboje" "window_open_detection_method": "Mala by sa použiť iba jedna metóda detekcie otvoreného okna. Použite senzor alebo automatickú detekciu cez teplotný prah, ale nie oboje",
"no_central_config": "Nemôžete zaškrtnúť „použiť centrálnu konfiguráciu“, pretože sa nenašla žiadna centrálna konfigurácia. Aby ste ho mohli používať, musíte si vytvoriť všestranný termostat typu „Central Configuration“."
}, },
"abort": { "abort": {
"already_configured": "Zariadenie je už nakonfigurované" "already_configured": "Zariadenie je už nakonfigurované"
@@ -349,27 +467,28 @@
"selector": { "selector": {
"thermostat_type": { "thermostat_type": {
"options": { "options": {
"thermostat_central_config": "Centrálna konfigurácia",
"thermostat_over_switch": "Termostat nad spínačom", "thermostat_over_switch": "Termostat nad spínačom",
"thermostat_over_climate": "Termostat nad iným termostatom", "thermostat_over_climate": "Termostat nad iným termostatom",
"thermostat_over_valve": "Thermostat over a valve" "thermostat_over_valve": "Termostat nad ventilom"
} }
}, },
"auto_regulation_mode": { "auto_regulation_mode": {
"options": { "options": {
"auto_regulation_slow": "Slow", "auto_regulation_slow": "Pomalé",
"auto_regulation_strong": "Strong", "auto_regulation_strong": "Silné",
"auto_regulation_medium": "Medium", "auto_regulation_medium": "Stredné",
"auto_regulation_light": "Light", "auto_regulation_light": "Jemné",
"auto_regulation_expert": "Expert", "auto_regulation_expert": "Expert",
"auto_regulation_none": "No auto-regulation" "auto_regulation_none": "Nie auto-regulácia"
} }
}, },
"auto_fan_mode": { "auto_fan_mode": {
"options": { "options": {
"auto_fan_none": "No auto-fan", "auto_fan_none": "Žiadny automatický ventilátor",
"auto_fan_low": "Low", "auto_fan_low": "Nízky",
"auto_fan_medium": "Medium", "auto_fan_medium": "Stredný",
"auto_fan_high": "High", "auto_fan_high": "Vysoký",
"auto_fan_turbo": "Turbo" "auto_fan_turbo": "Turbo"
} }
} }
@@ -389,4 +508,4 @@
} }
} }
} }
} }

View File

@@ -6,6 +6,7 @@ from typing import Any
from enum import StrEnum from enum import StrEnum
from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, UnitOfTemperature from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, UnitOfTemperature
from homeassistant.core import State
from homeassistant.exceptions import ServiceNotFound from homeassistant.exceptions import ServiceNotFound
@@ -111,18 +112,18 @@ class UnderlyingEntity:
# This should be the correct way to handle turn_off and turn_on but this breaks the unit test # This should be the correct way to handle turn_off and turn_on but this breaks the unit test
# will an not understandable error: TypeError: object MagicMock can't be used in 'await' expression # will an not understandable error: TypeError: object MagicMock can't be used in 'await' expression
async def turn_off(self): async def turn_off(self):
""" Turn off the underlying equipement. """Turn off the underlying equipement.
Need to be overriden""" Need to be overriden"""
return NotImplementedError return NotImplementedError
async def turn_on(self): async def turn_on(self):
""" Turn off the underlying equipement. """Turn off the underlying equipement.
Need to be overriden""" Need to be overriden"""
return NotImplementedError return NotImplementedError
@property @property
def is_inversed(self): def is_inversed(self):
""" Tells if the switch command should be inversed""" """Tells if the switch command should be inversed"""
return False return False
def remove_entity(self): def remove_entity(self):
@@ -132,16 +133,17 @@ class UnderlyingEntity:
async def check_initial_state(self, hvac_mode: HVACMode): async def check_initial_state(self, hvac_mode: HVACMode):
"""Prevent the underlying to be on but thermostat is off""" """Prevent the underlying to be on but thermostat is off"""
if hvac_mode == HVACMode.OFF and self.is_device_active: if hvac_mode == HVACMode.OFF and self.is_device_active:
_LOGGER.warning( _LOGGER.info(
"%s - The hvac mode is OFF, but the underlying device is ON. Turning off device %s", "%s - The hvac mode is OFF, but the underlying device is ON. Turning off device %s",
self, self,
self._entity_id, self._entity_id,
) )
await self.set_hvac_mode(hvac_mode) await self.set_hvac_mode(hvac_mode)
elif hvac_mode != HVACMode.OFF and not self.is_device_active: elif hvac_mode != HVACMode.OFF and not self.is_device_active:
_LOGGER.warning( _LOGGER.info(
"%s - The hvac mode is ON, but the underlying device is not ON. Turning on device %s", "%s - The hvac mode is %s, but the underlying device is not ON. Turning on device %s if needed",
self, self,
hvac_mode,
self._entity_id, self._entity_id,
) )
await self.set_hvac_mode(hvac_mode) await self.set_hvac_mode(hvac_mode)
@@ -164,7 +166,11 @@ class UnderlyingEntity:
"""Starting cycle for switch""" """Starting cycle for switch"""
def _cancel_cycle(self): def _cancel_cycle(self):
""" Stops an eventual cycle """ """Stops an eventual cycle"""
def cap_sent_value(self, value) -> float:
"""capping of the value send to the underlying eqt"""
return value
class UnderlyingSwitch(UnderlyingEntity): class UnderlyingSwitch(UnderlyingEntity):
@@ -205,7 +211,7 @@ class UnderlyingSwitch(UnderlyingEntity):
@overrides @overrides
@property @property
def is_inversed(self): def is_inversed(self):
""" Tells if the switch command should be inversed""" """Tells if the switch command should be inversed"""
return self._thermostat.is_inversed return self._thermostat.is_inversed
# @overrides this breaks some unit tests TypeError: object MagicMock can't be used in 'await' expression # @overrides this breaks some unit tests TypeError: object MagicMock can't be used in 'await' expression
@@ -227,14 +233,16 @@ class UnderlyingSwitch(UnderlyingEntity):
def is_device_active(self): def is_device_active(self):
"""If the toggleable device is currently active.""" """If the toggleable device is currently active."""
real_state = self._hass.states.is_state(self._entity_id, STATE_ON) real_state = self._hass.states.is_state(self._entity_id, STATE_ON)
return (self.is_inversed and not real_state) or (not self.is_inversed and real_state) return (self.is_inversed and not real_state) or (
not self.is_inversed and real_state
)
# @overrides this breaks some unit tests TypeError: object MagicMock can't be used in 'await' expression # @overrides this breaks some unit tests TypeError: object MagicMock can't be used in 'await' expression
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 entity %s", self, self._entity_id) _LOGGER.debug("%s - Stopping underlying entity %s", self, self._entity_id)
command = SERVICE_TURN_OFF if not self.is_inversed else SERVICE_TURN_ON command = SERVICE_TURN_OFF if not self.is_inversed else SERVICE_TURN_ON
domain = self._entity_id.split('.')[0] domain = self._entity_id.split(".")[0]
# This may fails if called after shutdown # This may fails if called after shutdown
try: try:
data = {ATTR_ENTITY_ID: self._entity_id} data = {ATTR_ENTITY_ID: self._entity_id}
@@ -250,7 +258,7 @@ class UnderlyingSwitch(UnderlyingEntity):
"""Turn heater toggleable device on.""" """Turn heater toggleable device on."""
_LOGGER.debug("%s - Starting underlying entity %s", self, self._entity_id) _LOGGER.debug("%s - Starting underlying entity %s", self, self._entity_id)
command = SERVICE_TURN_ON if not self.is_inversed else SERVICE_TURN_OFF command = SERVICE_TURN_ON if not self.is_inversed else SERVICE_TURN_OFF
domain = self._entity_id.split('.')[0] domain = self._entity_id.split(".")[0]
try: try:
data = {ATTR_ENTITY_ID: self._entity_id} data = {ATTR_ENTITY_ID: self._entity_id}
await self._hass.services.async_call( await self._hass.services.async_call(
@@ -261,7 +269,6 @@ class UnderlyingSwitch(UnderlyingEntity):
except ServiceNotFound as err: except ServiceNotFound as err:
_LOGGER.error(err) _LOGGER.error(err)
@overrides @overrides
async def start_cycle( async def start_cycle(
self, self,
@@ -348,8 +355,8 @@ class UnderlyingSwitch(UnderlyingEntity):
if await self._thermostat.check_overpowering(): if await self._thermostat.check_overpowering():
_LOGGER.debug("%s - End of cycle (3)", self) _LOGGER.debug("%s - End of cycle (3)", self)
return return
# Security mode could have change the on_time percent # safety mode could have change the on_time percent
await self._thermostat.check_security() await self._thermostat.check_safety()
time = self._on_time_sec time = self._on_time_sec
action_label = "start" action_label = "start"
@@ -477,6 +484,14 @@ class UnderlyingClimate(UnderlyingEntity):
if not self.is_initialized: if not self.is_initialized:
return False return False
if self._underlying_climate.hvac_mode == hvac_mode:
_LOGGER.debug(
"%s - hvac_mode is already is requested state %s. Do not send any command",
self,
self._underlying_climate.hvac_mode,
)
return False
data = {ATTR_ENTITY_ID: self._entity_id, "hvac_mode": hvac_mode} data = {ATTR_ENTITY_ID: self._entity_id, "hvac_mode": hvac_mode}
await self._hass.services.async_call( await self._hass.services.async_call(
CLIMATE_DOMAIN, CLIMATE_DOMAIN,
@@ -490,10 +505,14 @@ class UnderlyingClimate(UnderlyingEntity):
def is_device_active(self): def is_device_active(self):
"""If the toggleable device is currently active.""" """If the toggleable device is currently active."""
if self.is_initialized: if self.is_initialized:
return self._underlying_climate.hvac_mode != HVACMode.OFF and self._underlying_climate.hvac_action not in [ return (
HVACAction.IDLE, self._underlying_climate.hvac_mode != HVACMode.OFF
HVACAction.OFF, and self._underlying_climate.hvac_action
] not in [
HVACAction.IDLE,
HVACAction.OFF,
]
)
else: else:
return None return None
@@ -550,7 +569,7 @@ class UnderlyingClimate(UnderlyingEntity):
return return
data = { data = {
ATTR_ENTITY_ID: self._entity_id, ATTR_ENTITY_ID: self._entity_id,
"temperature": temperature, "temperature": self.cap_sent_value(temperature),
"target_temp_high": max_temp, "target_temp_high": max_temp,
"target_temp_low": min_temp, "target_temp_low": min_temp,
} }
@@ -664,6 +683,40 @@ class UnderlyingClimate(UnderlyingEntity):
return None return None
return self._underlying_climate.turn_aux_heat_off() return self._underlying_climate.turn_aux_heat_off()
@overrides
def cap_sent_value(self, value) -> float:
"""Try to adapt the target temp value to the min_temp / max_temp found
in the underlying entity (if any)"""
if not self.is_initialized:
return value
# Gets the min_temp and max_temp
if (
self._underlying_climate.min_temp is not None
and self._underlying_climate is not None
):
min_val = self._underlying_climate.min_temp
max_val = self._underlying_climate.max_temp
new_value = max(min_val, min(value, max_val))
else:
_LOGGER.debug("%s - no min and max attributes on underlying", self)
new_value = value
if new_value != value:
_LOGGER.info(
"%s - Target temp have been updated due min, max of the underlying entity. new_value=%.0f value=%.0f min=%.0f max=%.0f",
self,
new_value,
value,
min_val,
max_val,
)
return new_value
class UnderlyingValve(UnderlyingEntity): class UnderlyingValve(UnderlyingEntity):
"""Represent a underlying switch""" """Represent a underlying switch"""
@@ -672,10 +725,7 @@ class UnderlyingValve(UnderlyingEntity):
_percent_open: int _percent_open: int
def __init__( def __init__(
self, self, hass: HomeAssistant, thermostat: Any, valve_entity_id: str
hass: HomeAssistant,
thermostat: Any,
valve_entity_id: str
) -> None: ) -> None:
"""Initialize the underlying switch""" """Initialize the underlying switch"""
@@ -689,13 +739,14 @@ class UnderlyingValve(UnderlyingEntity):
self._should_relaunch_control_heating = False self._should_relaunch_control_heating = False
self._hvac_mode = None self._hvac_mode = None
self._percent_open = self._thermostat.valve_open_percent self._percent_open = self._thermostat.valve_open_percent
self._valve_entity_id = valve_entity_id
async def send_percent_open(self): async def send_percent_open(self):
""" Send the percent open to the underlying valve """ """Send the percent open to the underlying valve"""
# This may fails if called after shutdown # This may fails if called after shutdown
try: try:
data = { ATTR_ENTITY_ID: self._entity_id, "value": self._percent_open } data = {ATTR_ENTITY_ID: self._entity_id, "value": self._percent_open}
domain = self._entity_id.split('.')[0] domain = self._entity_id.split(".")[0]
await self._hass.services.async_call( await self._hass.services.async_call(
domain, domain,
SERVICE_SET_VALUE, SERVICE_SET_VALUE,
@@ -707,19 +758,25 @@ class UnderlyingValve(UnderlyingEntity):
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)
self._percent_open = 0 # Issue 341
if self.is_device_active: is_active = self.is_device_active
self._percent_open = self.cap_sent_value(0)
if is_active:
await self.send_percent_open() await self.send_percent_open()
async def turn_on(self): async def turn_on(self):
"""Nothing to do for Valve because it cannot be turned off""" """Nothing to do for Valve because it cannot be turned on"""
self.set_valve_open_percent()
async def set_hvac_mode(self, hvac_mode: HVACMode) -> bool: async def set_hvac_mode(self, hvac_mode: HVACMode) -> bool:
"""Set the HVACmode. Returns true if something have change""" """Set the HVACmode. Returns true if something have change"""
if hvac_mode == HVACMode.OFF: if hvac_mode == HVACMode.OFF and self.is_device_active:
await self.turn_off() await self.turn_off()
if hvac_mode != HVACMode.OFF and not self.is_device_active:
await self.turn_on()
if self._hvac_mode != hvac_mode: if self._hvac_mode != hvac_mode:
self._hvac_mode = hvac_mode self._hvac_mode = hvac_mode
return True return True
@@ -734,7 +791,7 @@ class UnderlyingValve(UnderlyingEntity):
# To test if real device is open but this is causing some side effect # To test if real device is open but this is causing some side effect
# because the activation can be deferred - # because the activation can be deferred -
# or float(self._hass.states.get(self._entity_id).state) > 0 # or float(self._hass.states.get(self._entity_id).state) > 0
except Exception: # pylint: disable=broad-exception-caught except Exception: # pylint: disable=broad-exception-caught
return False return False
@overrides @overrides
@@ -748,11 +805,43 @@ class UnderlyingValve(UnderlyingEntity):
): ):
"""We use this function to change the on_percent""" """We use this function to change the on_percent"""
if force: if force:
self._percent_open = self.cap_sent_value(self._percent_open)
await self.send_percent_open() await self.send_percent_open()
def set_valve_open_percent(self, percent): @overrides
""" Update the valve open percent """ def cap_sent_value(self, value) -> float:
caped_val = self._thermostat.valve_open_percent """Try to adapt the open_percent value to the min / max found
in the underlying entity (if any)"""
# Gets the last number state
valve_state: State = self._hass.states.get(self._valve_entity_id)
if valve_state is None:
return value
if "min" in valve_state.attributes and "max" in valve_state.attributes:
min_val = valve_state.attributes["min"]
max_val = valve_state.attributes["max"]
new_value = round(max(min_val, min(value, max_val)))
else:
_LOGGER.debug("%s - no min and max attributes on underlying", self)
new_value = value
if new_value != value:
_LOGGER.info(
"%s - Valve open percent have been updated due min, max of the underlying entity. new_value=%.0f value=%.0f min=%.0f max=%.0f",
self,
new_value,
value,
min_val,
max_val,
)
return new_value
def set_valve_open_percent(self):
"""Update the valve open percent"""
caped_val = self.cap_sent_value(self._thermostat.valve_open_percent)
if self._percent_open == caped_val: if self._percent_open == caped_val:
# No changes # No changes
return return
@@ -760,7 +849,9 @@ class UnderlyingValve(UnderlyingEntity):
self._percent_open = caped_val self._percent_open = caped_val
# Send the new command to valve via a service call # Send the new command to valve via a service call
_LOGGER.info("%s - Setting valve ouverture percent to %s", self, self._percent_open) _LOGGER.info(
"%s - Setting valve ouverture percent to %s", self, self._percent_open
)
# Send the change to the valve, in background # Send the change to the valve, in background
self._hass.create_task(self.send_percent_open()) self._hass.create_task(self.send_percent_open())

View File

@@ -3,7 +3,14 @@ import logging
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from .const import DOMAIN, CONF_AUTO_REGULATION_EXPERT, CONF_SHORT_EMA_PARAMS from .const import (
DOMAIN,
CONF_AUTO_REGULATION_EXPERT,
CONF_SHORT_EMA_PARAMS,
CONF_SAFETY_MODE,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_CENTRAL_CONFIG,
)
VTHERM_API_NAME = "vtherm_api" VTHERM_API_NAME = "vtherm_api"
@@ -13,41 +20,65 @@ _LOGGER = logging.getLogger(__name__)
class VersatileThermostatAPI(dict): class VersatileThermostatAPI(dict):
"""The VersatileThermostatAPI""" """The VersatileThermostatAPI"""
_hass: HomeAssistant _hass: HomeAssistant = None
# _entries: Dict(str, ConfigEntry)
@classmethod @classmethod
def get_vtherm_api(cls, hass: HomeAssistant): def get_vtherm_api(cls, hass=None):
"""Get the eventual VTherm API class instance""" """Get the eventual VTherm API class instance or
ret = hass.data.get(DOMAIN).get(VTHERM_API_NAME) instantiate it if it doesn't exists"""
if hass is not None:
VersatileThermostatAPI._hass = hass
if VersatileThermostatAPI._hass is None:
return None
domain = VersatileThermostatAPI._hass.data.get(DOMAIN)
if not domain:
VersatileThermostatAPI._hass.data.setdefault(DOMAIN, {})
ret = VersatileThermostatAPI._hass.data.get(DOMAIN).get(VTHERM_API_NAME)
if ret is None: if ret is None:
ret = VersatileThermostatAPI(hass) ret = VersatileThermostatAPI()
hass.data[DOMAIN][VTHERM_API_NAME] = ret VersatileThermostatAPI._hass.data[DOMAIN][VTHERM_API_NAME] = ret
return ret return ret
def __init__(self, hass: HomeAssistant) -> None: def __init__(self) -> None:
_LOGGER.debug("building a VersatileThermostatAPI") _LOGGER.debug("building a VersatileThermostatAPI")
super().__init__() super().__init__()
self._hass = hass
self._expert_params = None self._expert_params = None
self._short_ema_params = None self._short_ema_params = None
self._safety_mode = None
self._central_boiler_entity = None
self._threshold_number_entity = None
self._nb_active_number_entity = None
def find_central_configuration(self):
"""Search for a central configuration"""
for config_entry in VersatileThermostatAPI._hass.config_entries.async_entries(
DOMAIN
):
if (
config_entry.data.get(CONF_THERMOSTAT_TYPE)
== CONF_THERMOSTAT_CENTRAL_CONFIG
):
central_config = config_entry
return central_config
return None
def add_entry(self, entry: ConfigEntry): def add_entry(self, entry: ConfigEntry):
"""Add a new entry""" """Add a new entry"""
_LOGGER.debug("Add the entry %s", entry.entry_id) _LOGGER.debug("Add the entry %s", entry.entry_id)
# self._entries[entry.entry_id] = entry
# Add the entry in hass.data # Add the entry in hass.data
self._hass.data[DOMAIN][entry.entry_id] = entry VersatileThermostatAPI._hass.data[DOMAIN][entry.entry_id] = entry
def remove_entry(self, entry: ConfigEntry): def remove_entry(self, entry: ConfigEntry):
"""Remove an entry""" """Remove an entry"""
_LOGGER.debug("Remove the entry %s", entry.entry_id) _LOGGER.debug("Remove the entry %s", entry.entry_id)
# self._entries.pop(entry.entry_id) VersatileThermostatAPI._hass.data[DOMAIN].pop(entry.entry_id)
self._hass.data[DOMAIN].pop(entry.entry_id)
# If not more entries are preset, remove the API # If not more entries are preset, remove the API
if len(self) == 0: if len(self) == 0:
_LOGGER.debug("No more entries-> Remove the API from DOMAIN") _LOGGER.debug("No more entries-> Remove the API from DOMAIN")
self._hass.data.pop(DOMAIN) VersatileThermostatAPI._hass.data.pop(DOMAIN)
def set_global_config(self, config): def set_global_config(self, config):
"""Read the global configuration from configuration.yaml file""" """Read the global configuration from configuration.yaml file"""
@@ -61,6 +92,30 @@ class VersatileThermostatAPI(dict):
if self._short_ema_params: if self._short_ema_params:
_LOGGER.debug("We have found short ema params %s", self._short_ema_params) _LOGGER.debug("We have found short ema params %s", self._short_ema_params)
self._safety_mode = config.get(CONF_SAFETY_MODE)
if self._safety_mode:
_LOGGER.debug("We have found safet_mode params %s", self._safety_mode)
def register_central_boiler(self, central_boiler_entity):
"""Register the central boiler entity. This is used by the CentralBoilerBinarySensor
class to register itself at creation"""
self._central_boiler_entity = central_boiler_entity
def register_central_boiler_activation_number_threshold(
self, threshold_number_entity
):
"""register the two number entities needed for boiler activation"""
self._threshold_number_entity = threshold_number_entity
def register_nb_device_active_boiler(self, nb_active_number_entity):
"""register the two number entities needed for boiler activation"""
self._nb_active_number_entity = nb_active_number_entity
async def reload_central_boiler_entities_list(self):
"""Reload the central boiler list of entities if a central boiler is used"""
if self._nb_active_number_entity is not None:
await self._nb_active_number_entity.listen_vtherms_entities()
@property @property
def self_regulation_expert(self): def self_regulation_expert(self):
"""Get the self regulation params""" """Get the self regulation params"""
@@ -68,10 +123,49 @@ class VersatileThermostatAPI(dict):
@property @property
def short_ema_params(self): def short_ema_params(self):
"""Get the self regulation params""" """Get the short EMA params in expert mode"""
return self._short_ema_params return self._short_ema_params
@property
def safety_mode(self):
"""Get the safety_mode params"""
return self._safety_mode
@property
def central_boiler_entity(self):
"""Get the central boiler binary_sensor entity"""
return self._central_boiler_entity
@property
def nb_active_device_for_boiler(self):
"""Returns the number of active VTherm which have an
influence on boiler"""
if self._nb_active_number_entity is None:
return None
else:
return self._nb_active_number_entity.native_value
@property
def nb_active_device_for_boiler_entity(self):
"""Returns the number of active VTherm entity which have an
influence on boiler"""
return self._nb_active_number_entity
@property
def nb_active_device_for_boiler_threshold_entity(self):
"""Returns the number of active VTherm entity which have an
influence on boiler"""
return self._threshold_number_entity
@property
def nb_active_device_for_boiler_threshold(self):
"""Returns the number of active VTherm entity which have an
influence on boiler"""
if self._threshold_number_entity is None:
return None
return int(self._threshold_number_entity.native_value)
@property @property
def hass(self): def hass(self):
"""Get the HomeAssistant object""" """Get the HomeAssistant object"""
return self._hass return VersatileThermostatAPI._hass

BIN
images/central_mode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 45 KiB

BIN
images/config-main0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
images/plotly-curves.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

7
pyrightconfig.json Normal file
View File

@@ -0,0 +1,7 @@
{
"include": [
"custom_components/versatile_thermostat/**",
"homeassistant/**"
],
"reportShadowedImports": false
}

View File

@@ -2,5 +2,7 @@
-r requirements_dev.txt -r requirements_dev.txt
aiodiscover aiodiscover
ulid_transform ulid_transform
pytest
coverage
pytest-asyncio pytest-asyncio
pytest-homeassistant-custom-component pytest-homeassistant-custom-component

View File

@@ -19,6 +19,14 @@ from homeassistant.components.climate import (
ClimateEntityFeature, ClimateEntityFeature,
) )
from homeassistant.components.switch import (
SwitchEntity,
)
from homeassistant.components.number import (
NumberEntity,
)
from pytest_homeassistant_custom_component.common import MockConfigEntry from pytest_homeassistant_custom_component.common import MockConfigEntry
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
@@ -29,13 +37,19 @@ from custom_components.versatile_thermostat.commons import ( # pylint: disable=
NowClass, NowClass,
) )
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
from .const import ( # pylint: disable=unused-import from .const import ( # pylint: disable=unused-import
MOCK_TH_OVER_SWITCH_USER_CONFIG, MOCK_TH_OVER_SWITCH_USER_CONFIG,
MOCK_TH_OVER_4SWITCH_USER_CONFIG, MOCK_TH_OVER_4SWITCH_USER_CONFIG,
MOCK_TH_OVER_CLIMATE_USER_CONFIG, MOCK_TH_OVER_CLIMATE_USER_CONFIG,
MOCK_TH_OVER_SWITCH_MAIN_CONFIG,
MOCK_TH_OVER_SWITCH_CENTRAL_MAIN_CONFIG,
MOCK_TH_OVER_SWITCH_TYPE_CONFIG, MOCK_TH_OVER_SWITCH_TYPE_CONFIG,
MOCK_TH_OVER_SWITCH_AC_TYPE_CONFIG, MOCK_TH_OVER_SWITCH_AC_TYPE_CONFIG,
MOCK_TH_OVER_4SWITCH_TYPE_CONFIG, MOCK_TH_OVER_4SWITCH_TYPE_CONFIG,
MOCK_TH_OVER_CLIMATE_MAIN_CONFIG,
MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG,
MOCK_TH_OVER_CLIMATE_TYPE_CONFIG, MOCK_TH_OVER_CLIMATE_TYPE_CONFIG,
MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG, MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG,
MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG, MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG,
@@ -54,10 +68,14 @@ from .const import ( # pylint: disable=unused-import
PRESET_NONE, PRESET_NONE,
PRESET_ECO, PRESET_ECO,
PRESET_ACTIVITY, PRESET_ACTIVITY,
overrides,
) )
FULL_SWITCH_CONFIG = ( FULL_SWITCH_CONFIG = (
MOCK_TH_OVER_SWITCH_USER_CONFIG MOCK_TH_OVER_SWITCH_USER_CONFIG
| MOCK_TH_OVER_SWITCH_MAIN_CONFIG
| MOCK_TH_OVER_SWITCH_CENTRAL_MAIN_CONFIG
| MOCK_TH_OVER_SWITCH_TYPE_CONFIG | MOCK_TH_OVER_SWITCH_TYPE_CONFIG
| MOCK_TH_OVER_SWITCH_TPI_CONFIG | MOCK_TH_OVER_SWITCH_TPI_CONFIG
| MOCK_PRESETS_CONFIG | MOCK_PRESETS_CONFIG
@@ -70,6 +88,8 @@ FULL_SWITCH_CONFIG = (
FULL_SWITCH_AC_CONFIG = ( FULL_SWITCH_AC_CONFIG = (
MOCK_TH_OVER_SWITCH_USER_CONFIG MOCK_TH_OVER_SWITCH_USER_CONFIG
| MOCK_TH_OVER_SWITCH_MAIN_CONFIG
| MOCK_TH_OVER_SWITCH_CENTRAL_MAIN_CONFIG
| MOCK_TH_OVER_SWITCH_AC_TYPE_CONFIG | MOCK_TH_OVER_SWITCH_AC_TYPE_CONFIG
| MOCK_TH_OVER_SWITCH_TPI_CONFIG | MOCK_TH_OVER_SWITCH_TPI_CONFIG
| MOCK_PRESETS_AC_CONFIG | MOCK_PRESETS_AC_CONFIG
@@ -83,6 +103,8 @@ FULL_SWITCH_AC_CONFIG = (
PARTIAL_CLIMATE_CONFIG = ( PARTIAL_CLIMATE_CONFIG = (
MOCK_TH_OVER_CLIMATE_USER_CONFIG MOCK_TH_OVER_CLIMATE_USER_CONFIG
| MOCK_TH_OVER_CLIMATE_MAIN_CONFIG
| MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG
| MOCK_TH_OVER_CLIMATE_TYPE_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_CONFIG
| MOCK_PRESETS_CONFIG | MOCK_PRESETS_CONFIG
| MOCK_ADVANCED_CONFIG | MOCK_ADVANCED_CONFIG
@@ -90,6 +112,8 @@ PARTIAL_CLIMATE_CONFIG = (
PARTIAL_CLIMATE_NOT_REGULATED_CONFIG = ( PARTIAL_CLIMATE_NOT_REGULATED_CONFIG = (
MOCK_TH_OVER_CLIMATE_USER_CONFIG MOCK_TH_OVER_CLIMATE_USER_CONFIG
| MOCK_TH_OVER_CLIMATE_MAIN_CONFIG
| MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG
| MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG
| MOCK_PRESETS_CONFIG | MOCK_PRESETS_CONFIG
| MOCK_ADVANCED_CONFIG | MOCK_ADVANCED_CONFIG
@@ -98,6 +122,8 @@ PARTIAL_CLIMATE_NOT_REGULATED_CONFIG = (
PARTIAL_CLIMATE_AC_CONFIG = ( PARTIAL_CLIMATE_AC_CONFIG = (
MOCK_TH_OVER_CLIMATE_USER_CONFIG MOCK_TH_OVER_CLIMATE_USER_CONFIG
| MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG
| MOCK_TH_OVER_CLIMATE_MAIN_CONFIG
| MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG
| MOCK_PRESETS_CONFIG | MOCK_PRESETS_CONFIG
| MOCK_ADVANCED_CONFIG | MOCK_ADVANCED_CONFIG
) )
@@ -114,6 +140,91 @@ FULL_4SWITCH_CONFIG = (
| MOCK_ADVANCED_CONFIG | MOCK_ADVANCED_CONFIG
) )
FULL_CENTRAL_CONFIG = {
CONF_NAME: CENTRAL_CONFIG_NAME,
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
CONF_STEP_TEMPERATURE: 0.1,
CONF_TPI_COEF_INT: 0.5,
CONF_TPI_COEF_EXT: 0.02,
"frost_temp": 10,
"eco_temp": 17.1,
"comfort_temp": 0,
"boost_temp": 19.1,
"eco_ac_temp": 25.1,
"comfort_ac_temp": 23.1,
"boost_ac_temp": 21.1,
"frost_away_temp": 15.1,
"eco_away_temp": 15.2,
"comfort_away_temp": 0,
"boost_away_temp": 15.4,
"eco_ac_away_temp": 30.5,
"comfort_ac_away_temp": 0,
"boost_ac_away_temp": 30.7,
CONF_WINDOW_DELAY: 15,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 4,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 1,
CONF_WINDOW_AUTO_MAX_DURATION: 31,
CONF_MOTION_DELAY: 31,
CONF_MOTION_OFF_DELAY: 301,
CONF_MOTION_PRESET: "boost",
CONF_NO_MOTION_PRESET: "frost",
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_max_power_sensor",
CONF_PRESET_POWER: 14,
CONF_MINIMAL_ACTIVATION_DELAY: 11,
CONF_SECURITY_DELAY_MIN: 61,
CONF_SECURITY_MIN_ON_PERCENT: 0.5,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.2,
CONF_ADD_CENTRAL_BOILER_CONTROL: False,
}
FULL_CENTRAL_CONFIG_WITH_BOILER = {
CONF_NAME: CENTRAL_CONFIG_NAME,
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
CONF_STEP_TEMPERATURE: 0.1,
CONF_TPI_COEF_INT: 0.5,
CONF_TPI_COEF_EXT: 0.02,
"frost_temp": 10,
"eco_temp": 17.1,
"comfort_temp": 0,
"boost_temp": 19.1,
"eco_ac_temp": 25.1,
"comfort_ac_temp": 23.1,
"boost_ac_temp": 21.1,
"frost_away_temp": 15.1,
"eco_away_temp": 15.2,
"comfort_away_temp": 0,
"boost_away_temp": 15.4,
"eco_ac_away_temp": 30.5,
"comfort_ac_away_temp": 0,
"boost_ac_away_temp": 30.7,
CONF_WINDOW_DELAY: 15,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 4,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 1,
CONF_WINDOW_AUTO_MAX_DURATION: 31,
CONF_MOTION_DELAY: 31,
CONF_MOTION_OFF_DELAY: 301,
CONF_MOTION_PRESET: "boost",
CONF_NO_MOTION_PRESET: "frost",
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_max_power_sensor",
CONF_PRESET_POWER: 14,
CONF_MINIMAL_ACTIVATION_DELAY: 11,
CONF_SECURITY_DELAY_MIN: 61,
CONF_SECURITY_MIN_ON_PERCENT: 0.5,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.2,
CONF_ADD_CENTRAL_BOILER_CONTROL: True,
CONF_CENTRAL_BOILER_ACTIVATION_SRV: "switch.pompe_chaudiere/switch.turn_on",
CONF_CENTRAL_BOILER_DEACTIVATION_SRV: "switch.pompe_chaudiere/switch.turn_off",
}
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -129,6 +240,7 @@ class MockClimate(ClimateEntity):
hvac_mode: HVACMode = HVACMode.OFF, hvac_mode: HVACMode = HVACMode.OFF,
hvac_action: HVACAction = HVACAction.OFF, hvac_action: HVACAction = HVACAction.OFF,
fan_modes: list[str] = None, fan_modes: list[str] = None,
hvac_modes: list[str] = None,
) -> None: ) -> None:
"""Initialize the thermostat.""" """Initialize the thermostat."""
@@ -144,14 +256,24 @@ class MockClimate(ClimateEntity):
HVACAction.OFF if hvac_mode == HVACMode.OFF else HVACAction.HEATING HVACAction.OFF if hvac_mode == HVACMode.OFF else HVACAction.HEATING
) )
self._attr_hvac_mode = hvac_mode self._attr_hvac_mode = hvac_mode
self._attr_hvac_modes = [HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT] self._attr_hvac_modes = (
hvac_modes
if hvac_modes is not None
else [HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT]
)
self._attr_temperature_unit = UnitOfTemperature.CELSIUS self._attr_temperature_unit = UnitOfTemperature.CELSIUS
self._attr_target_temperature = 20 self._attr_target_temperature = 20
self._attr_current_temperature = 15 self._attr_current_temperature = 15
self._attr_hvac_action = hvac_action self._attr_hvac_action = hvac_action
self._attr_target_temperature_step = 0.2
self._fan_modes = fan_modes if fan_modes else None self._fan_modes = fan_modes if fan_modes else None
self._attr_fan_mode = None self._attr_fan_mode = None
@property
def name(self) -> str:
"""The name"""
return self._name
@property @property
def hvac_action(self): def hvac_action(self):
"""The hvac action of the mock climate""" """The hvac action of the mock climate"""
@@ -183,6 +305,10 @@ class MockClimate(ClimateEntity):
"""The hvac mode""" """The hvac mode"""
self._attr_hvac_mode = hvac_mode self._attr_hvac_mode = hvac_mode
def set_hvac_mode(self, hvac_mode):
"""The hvac mode"""
self._attr_hvac_mode = hvac_mode
def set_hvac_action(self, hvac_action: HVACAction): def set_hvac_action(self, hvac_action: HVACAction):
"""Set the HVACaction""" """Set the HVACaction"""
self._attr_hvac_action = hvac_action self._attr_hvac_action = hvac_action
@@ -280,6 +406,74 @@ class MagicMockClimate(MagicMock):
def supported_features(self): # pylint: disable=missing-function-docstring def supported_features(self): # pylint: disable=missing-function-docstring
return ClimateEntityFeature.TARGET_TEMPERATURE return ClimateEntityFeature.TARGET_TEMPERATURE
@property
def min_temp(self): # pylint: disable=missing-function-docstring
return 15
@property
def max_temp(self): # pylint: disable=missing-function-docstring
return 19
class MockSwitch(SwitchEntity):
"""A fake switch to be used instead real switch"""
def __init__( # pylint: disable=unused-argument, dangerous-default-value
self, hass: HomeAssistant, unique_id, name, entry_infos={}
):
"""Init the switch"""
super().__init__()
self.hass = hass
self.platform = "switch"
self.entity_id = self.platform + "." + unique_id
self._name = name
self._attr_is_on = False
@property
def name(self) -> str:
"""The name"""
return self._name
@overrides
def turn_on(self, **kwargs: Any):
"""Turns the switch on and notify the state change"""
self._attr_is_on = True
# self.async_write_ha_state()
@overrides
def turn_off(self, **kwargs: Any):
"""Turns the switch on and notify the state change"""
self._attr_is_on = False
# self.async_write_ha_state()
class MockNumber(NumberEntity):
"""A fake switch to be used instead real switch"""
def __init__( # pylint: disable=unused-argument, dangerous-default-value
self, hass: HomeAssistant, unique_id, name, entry_infos={}
):
"""Init the switch"""
super().__init__()
self.hass = hass
self.platform = "number"
self.entity_id = self.platform + "." + unique_id
self._name = name
self._attr_native_value = 0
self._attr_native_min_value = 0
@property
def name(self) -> str:
"""The name"""
return self._name
@overrides
def set_native_value(self, value: float):
"""Change the value"""
self._attr_native_value = value
async def create_thermostat( async def create_thermostat(
hass: HomeAssistant, entry: MockConfigEntry, entity_id: str hass: HomeAssistant, entry: MockConfigEntry, entity_id: str
@@ -292,16 +486,30 @@ async def create_thermostat(
await hass.config_entries.async_setup(entry.entry_id) await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.LOADED assert entry.state is ConfigEntryState.LOADED
# def find_my_entity(entity_id) -> ClimateEntity:
# """Find my new entity"""
# component: EntityComponent[ClimateEntity] = hass.data[CLIMATE_DOMAIN]
# for entity in component.entities:
# if entity.entity_id == entity_id:
# return entity
return search_entity(hass, entity_id, CLIMATE_DOMAIN) return search_entity(hass, entity_id, CLIMATE_DOMAIN)
async def create_central_config( # pylint: disable=dangerous-default-value
hass: HomeAssistant, entry: MockConfigEntry = FULL_CENTRAL_CONFIG
):
"""Creates a Central Configuration from entry given in argument"""
central_config_entry = MockConfigEntry(
domain=DOMAIN,
title="TheCentralConfigMockName",
unique_id="centralConfigUniqueId",
data=entry,
)
central_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(central_config_entry.entry_id)
assert central_config_entry.state is ConfigEntryState.LOADED
# Test that VTherm API find the CentralConfig
api = VersatileThermostatAPI.get_vtherm_api(hass)
central_configuration = api.find_central_configuration()
assert central_configuration is not None
def search_entity(hass: HomeAssistant, entity_id, domain) -> Entity: def search_entity(hass: HomeAssistant, entity_id, domain) -> Entity:
"""Search and return the entity in the domain""" """Search and return the entity in the domain"""
component = hass.data[domain] component = hass.data[domain]
@@ -311,6 +519,12 @@ def search_entity(hass: HomeAssistant, entity_id, domain) -> Entity:
return None return None
def count_entities(hass: HomeAssistant, entity_id, domain) -> Entity:
"""Search and return the entity in the domain"""
component = hass.data[domain]
return len(list(component.entities)) if component.entities else 0
async def send_temperature_change_event( async def send_temperature_change_event(
entity: BaseThermostat, new_temp, date, sleep=True entity: BaseThermostat, new_temp, date, sleep=True
): ):
@@ -332,10 +546,12 @@ async def send_temperature_change_event(
) )
}, },
) )
await entity._async_temperature_changed(temp_event) dearm_window_auto = await entity._async_temperature_changed(temp_event)
if sleep: if sleep:
await asyncio.sleep(0.1) await asyncio.sleep(0.1)
return dearm_window_auto
async def send_ext_temperature_change_event( async def send_ext_temperature_change_event(
entity: BaseThermostat, new_temp, date, sleep=True entity: BaseThermostat, new_temp, date, sleep=True
@@ -523,6 +739,7 @@ async def send_climate_change_event(
old_hvac_action: HVACAction, old_hvac_action: HVACAction,
date, date,
sleep=True, sleep=True,
underlying_entity_id: str = None,
): ):
"""Sending a new climate event simulating a change on the underlying climate state""" """Sending a new climate event simulating a change on the underlying climate state"""
_LOGGER.info( _LOGGER.info(
@@ -534,18 +751,23 @@ async def send_climate_change_event(
date, date,
entity, entity,
) )
send_from_entity_id = (
underlying_entity_id if underlying_entity_id is not None else entity.entity_id
)
climate_event = Event( climate_event = Event(
EVENT_STATE_CHANGED, EVENT_STATE_CHANGED,
{ {
"new_state": State( "new_state": State(
entity_id=entity.entity_id, entity_id=send_from_entity_id,
state=new_hvac_mode, state=new_hvac_mode,
attributes={"hvac_action": new_hvac_action}, attributes={"hvac_action": new_hvac_action},
last_changed=date, last_changed=date,
last_updated=date, last_updated=date,
), ),
"old_state": State( "old_state": State(
entity_id=entity.entity_id, entity_id=send_from_entity_id,
state=old_hvac_mode, state=old_hvac_mode,
attributes={"hvac_action": old_hvac_action}, attributes={"hvac_action": old_hvac_action},
last_changed=date, last_changed=date,

View File

@@ -26,15 +26,24 @@ from custom_components.versatile_thermostat.config_flow import (
VersatileThermostatBaseConfigFlow, VersatileThermostatBaseConfigFlow,
) )
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from .commons import (
create_central_config,
FULL_CENTRAL_CONFIG,
FULL_CENTRAL_CONFIG_WITH_BOILER,
)
pytest_plugins = "pytest_homeassistant_custom_component" # pylint: disable=invalid-name pytest_plugins = "pytest_homeassistant_custom_component" # pylint: disable=invalid-name
# This fixture enables loading custom integrations in all tests. # This fixture enables loading custom integrations in all tests.
# Remove to enable selective use of this fixture # Remove to enable selective use of this fixture
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def auto_enable_custom_integrations(enable_custom_integrations): # pylint: disable=unused-argument def auto_enable_custom_integrations(
enable_custom_integrations,
): # pylint: disable=unused-argument
"""Enable all integration in tests""" """Enable all integration in tests"""
yield yield
@@ -108,3 +117,30 @@ def skip_send_event_fixture():
"""Skip the send_event in BaseThermostat""" """Skip the send_event in BaseThermostat"""
with patch.object(BaseThermostat, "send_event"): with patch.object(BaseThermostat, "send_event"):
yield yield
@pytest.fixture(name="init_vtherm_api")
def init_vtherm_api_fixture(hass):
"""Initialize the VTherm API"""
VersatileThermostatAPI.get_vtherm_api(hass)
yield
@pytest.fixture(name="init_central_config")
async def init_central_config_fixture(
hass, init_vtherm_api
): # pylint: disable=unused-argument
"""Initialize the VTherm API"""
await create_central_config(hass, FULL_CENTRAL_CONFIG)
yield
@pytest.fixture(name="init_central_config_with_boiler_fixture")
async def init_central_config_with_boiler_fixture(
hass, init_vtherm_api
): # pylint: disable=unused-argument
"""Initialize the VTherm API"""
await create_central_config(hass, FULL_CENTRAL_CONFIG_WITH_BOILER)
yield

View File

@@ -6,74 +6,23 @@ from homeassistant.components.climate.const import ( # pylint: disable=unused-i
PRESET_NONE, PRESET_NONE,
PRESET_ACTIVITY, PRESET_ACTIVITY,
) )
from custom_components.versatile_thermostat.const import (
CONF_NAME, from custom_components.versatile_thermostat.const import * # pylint: disable=wildcard-import, unused-wildcard-import
CONF_HEATER,
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_TYPE,
CONF_AC_MODE,
CONF_TEMP_SENSOR,
CONF_EXTERNAL_TEMP_SENSOR,
CONF_CYCLE_MIN,
CONF_TEMP_MAX,
CONF_TEMP_MIN,
CONF_PROP_FUNCTION,
PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT,
CONF_TPI_COEF_EXT,
CONF_MINIMAL_ACTIVATION_DELAY,
CONF_SECURITY_DELAY_MIN,
CONF_SECURITY_MIN_ON_PERCENT,
CONF_SECURITY_DEFAULT_ON_PERCENT,
CONF_USE_WINDOW_FEATURE,
CONF_USE_MOTION_FEATURE,
CONF_USE_POWER_FEATURE,
CONF_USE_PRESENCE_FEATURE,
CONF_WINDOW_SENSOR,
CONF_WINDOW_DELAY,
CONF_WINDOW_AUTO_OPEN_THRESHOLD,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD,
CONF_WINDOW_AUTO_MAX_DURATION,
CONF_MOTION_SENSOR,
CONF_MOTION_DELAY,
CONF_MOTION_OFF_DELAY,
CONF_MOTION_PRESET,
CONF_NO_MOTION_PRESET,
CONF_POWER_SENSOR,
CONF_MAX_POWER_SENSOR,
CONF_DEVICE_POWER,
CONF_PRESET_POWER,
CONF_PRESENCE_SENSOR,
PRESET_AWAY_SUFFIX,
CONF_CLIMATE,
CONF_AUTO_REGULATION_MODE,
CONF_AUTO_REGULATION_STRONG,
CONF_AUTO_REGULATION_NONE,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
CONF_INVERSE_SWITCH,
CONF_AUTO_FAN_HIGH,
CONF_AUTO_FAN_MODE,
PRESET_FROST_PROTECTION,
)
MOCK_TH_OVER_SWITCH_USER_CONFIG = { MOCK_TH_OVER_SWITCH_USER_CONFIG = {
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH, CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
}
MOCK_TH_OVER_SWITCH_MAIN_CONFIG = {
CONF_NAME: "TheOverSwitchMockName",
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5, CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
CONF_DEVICE_POWER: 1, CONF_DEVICE_POWER: 1,
CONF_USE_WINDOW_FEATURE: True, CONF_USE_WINDOW_FEATURE: True,
CONF_USE_MOTION_FEATURE: True, CONF_USE_MOTION_FEATURE: True,
CONF_USE_POWER_FEATURE: True, CONF_USE_POWER_FEATURE: True,
CONF_USE_PRESENCE_FEATURE: True, CONF_USE_PRESENCE_FEATURE: True,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
} }
MOCK_TH_OVER_4SWITCH_USER_CONFIG = { MOCK_TH_OVER_4SWITCH_USER_CONFIG = {
@@ -84,6 +33,7 @@ MOCK_TH_OVER_4SWITCH_USER_CONFIG = {
CONF_CYCLE_MIN: 8, CONF_CYCLE_MIN: 8,
CONF_TEMP_MIN: 15, CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30, CONF_TEMP_MAX: 30,
CONF_STEP_TEMPERATURE: 0.1,
CONF_DEVICE_POWER: 1, CONF_DEVICE_POWER: 1,
CONF_USE_WINDOW_FEATURE: True, CONF_USE_WINDOW_FEATURE: True,
CONF_USE_MOTION_FEATURE: True, CONF_USE_MOTION_FEATURE: True,
@@ -92,14 +42,33 @@ MOCK_TH_OVER_4SWITCH_USER_CONFIG = {
} }
MOCK_TH_OVER_CLIMATE_USER_CONFIG = { MOCK_TH_OVER_CLIMATE_USER_CONFIG = {
CONF_NAME: "TheOverClimateMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE, CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
}
MOCK_TH_OVER_CLIMATE_MAIN_CONFIG = {
CONF_NAME: "TheOverClimateMockName",
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5, CONF_CYCLE_MIN: 5,
CONF_DEVICE_POWER: 1,
CONF_USE_MAIN_CENTRAL_CONFIG: False,
CONF_USE_CENTRAL_MODE: True
# Keep default values which are False
}
MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG = {
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_TEMP_MIN: 15, CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30, CONF_TEMP_MAX: 30,
CONF_DEVICE_POWER: 1 CONF_STEP_TEMPERATURE: 0.1,
# Keep default values which are False
}
MOCK_TH_OVER_SWITCH_CENTRAL_MAIN_CONFIG = {
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
CONF_STEP_TEMPERATURE: 0.1,
# Keep default values which are False # Keep default values which are False
} }
@@ -174,6 +143,11 @@ MOCK_PRESETS_AC_CONFIG = {
MOCK_WINDOW_CONFIG = { MOCK_WINDOW_CONFIG = {
CONF_WINDOW_SENSOR: "binary_sensor.window_sensor", CONF_WINDOW_SENSOR: "binary_sensor.window_sensor",
# Not used normally only for tests to avoid rewrite all tests
CONF_WINDOW_DELAY: 10,
}
MOCK_WINDOW_DELAY_CONFIG = {
CONF_WINDOW_DELAY: 10, CONF_WINDOW_DELAY: 10,
} }
@@ -181,6 +155,7 @@ MOCK_WINDOW_AUTO_CONFIG = {
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 1.0, CONF_WINDOW_AUTO_OPEN_THRESHOLD: 1.0,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.0, CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.0,
CONF_WINDOW_AUTO_MAX_DURATION: 5.0, CONF_WINDOW_AUTO_MAX_DURATION: 5.0,
CONF_WINDOW_ACTION: CONF_WINDOW_FAN_ONLY,
} }
MOCK_MOTION_CONFIG = { MOCK_MOTION_CONFIG = {
@@ -228,3 +203,14 @@ MOCK_DEFAULT_FEATURE_CONFIG = {
CONF_USE_POWER_FEATURE: False, CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False, CONF_USE_PRESENCE_FEATURE: False,
} }
MOCK_DEFAULT_CENTRAL_CONFIG = {
CONF_USE_MAIN_CENTRAL_CONFIG: False,
CONF_USE_TPI_CENTRAL_CONFIG: False,
CONF_USE_PRESETS_CENTRAL_CONFIG: False,
CONF_USE_WINDOW_CENTRAL_CONFIG: False,
CONF_USE_MOTION_CENTRAL_CONFIG: False,
CONF_USE_POWER_CENTRAL_CONFIG: False,
CONF_USE_PRESENCE_CENTRAL_CONFIG: False,
CONF_USE_ADVANCED_CENTRAL_CONFIG: False,
}

View File

@@ -71,8 +71,8 @@ async def test_over_climate_regulation(
assert entity.name == "TheOverClimateMockName" assert entity.name == "TheOverClimateMockName"
assert entity.is_over_climate is True assert entity.is_over_climate is True
assert entity.is_regulated is True assert entity.is_regulated is True
assert entity.hvac_action is HVACAction.OFF
assert entity.hvac_mode is HVACMode.OFF assert entity.hvac_mode is HVACMode.OFF
assert entity.hvac_action is HVACAction.OFF
assert entity.target_temperature == entity.min_temp assert entity.target_temperature == entity.min_temp
assert entity.preset_modes == [ assert entity.preset_modes == [
PRESET_NONE, PRESET_NONE,

View File

@@ -6,6 +6,10 @@ from datetime import datetime, timedelta
import logging import logging
from homeassistant.components.climate import (
SERVICE_SET_TEMPERATURE,
)
from .commons import * from .commons import *
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
@@ -354,7 +358,7 @@ async def test_bug_82(
skip_turn_on_off_heater, skip_turn_on_off_heater,
skip_send_event, skip_send_event,
): ):
"""Test that when a underlying climate is not available the VTherm doesn't go into security mode""" """Test that when a underlying climate is not available the VTherm doesn't go into safety mode"""
tz = get_tz(hass) # pylint: disable=invalid-name tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz) now: datetime = datetime.now(tz=tz)
@@ -423,7 +427,7 @@ async def test_bug_82(
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()])
# Force security mode # Force safety mode
assert entity._last_ext_temperature_measure is not None assert entity._last_ext_temperature_measure is not None
assert entity._last_temperature_measure is not None assert entity._last_temperature_measure is not None
assert ( assert (
@@ -568,3 +572,146 @@ async def test_bug_101(
) )
assert entity.target_temperature == 12.75 assert entity.target_temperature == 12.75
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_bug_272(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
):
"""Test that it not possible to set the target temperature under the min_temp setting"""
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverClimateMockName",
unique_id="uniqueId",
# default value are min 15°, max 30°, step 0.1
data=PARTIAL_CLIMATE_CONFIG, # 5 minutes security delay
)
# Min_temp is 15 and max_temp is 19
fake_underlying_climate = MagicMockClimate()
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,
), patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call:
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.LOADED
def find_my_entity(entity_id) -> ClimateEntity:
"""Find my new entity"""
component: EntityComponent[ClimateEntity] = hass.data[CLIMATE_DOMAIN]
for entity in component.entities:
if entity.entity_id == entity_id:
return entity
entity = find_my_entity("climate.theoverclimatemockname")
assert entity
assert entity.name == "TheOverClimateMockName"
assert entity.is_over_climate is True
assert entity.hvac_mode is HVACMode.OFF
# The VTherm value and not the underlying value
assert entity.target_temperature_step == 0.1
assert entity.target_temperature == entity.min_temp
assert entity.is_regulated is True
assert mock_service_call.call_count == 0
# Set the hvac_mode to HEAT
await entity.async_set_hvac_mode(HVACMode.HEAT)
# In the accepted interval
await entity.async_set_temperature(temperature=17.5)
# MagicMock climate is already HEAT by default. So there is no SET_HAVC_MODE call
assert mock_service_call.call_count == 1
mock_service_call.assert_has_calls(
[
# call.async_call(
# "climate",
# SERVICE_SET_HVAC_MODE,
# {"entity_id": "climate.mock_climate", "hvac_mode": HVACMode.HEAT},
# ),
call.async_call(
"climate",
SERVICE_SET_TEMPERATURE,
{
"entity_id": "climate.mock_climate",
"temperature": 17.5,
"target_temp_high": 30,
"target_temp_low": 15,
},
),
]
)
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
# Set room temperature to something very cold
event_timestamp = now + timedelta(minutes=1)
await send_temperature_change_event(entity, 13, event_timestamp)
await send_ext_temperature_change_event(entity, 9, event_timestamp)
# Not in the accepted interval (15-19)
await entity.async_set_temperature(temperature=10)
assert mock_service_call.call_count == 1
mock_service_call.assert_has_calls(
[
call.async_call(
"climate",
SERVICE_SET_TEMPERATURE,
{
"entity_id": "climate.mock_climate",
"temperature": 15, # the minimum acceptable
"target_temp_high": 30,
"target_temp_low": 15,
},
),
]
)
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
# Set room temperature to something very cold
event_timestamp = now + timedelta(minutes=1)
await send_temperature_change_event(entity, 13, event_timestamp)
await send_ext_temperature_change_event(entity, 9, event_timestamp)
# In the accepted interval
await entity.async_set_temperature(temperature=20.8)
assert mock_service_call.call_count == 1
mock_service_call.assert_has_calls(
[
call.async_call(
"climate",
SERVICE_SET_TEMPERATURE,
{
"entity_id": "climate.mock_climate",
"temperature": 19, # the maximum acceptable
"target_temp_high": 30,
"target_temp_low": 15,
},
),
]
)

View File

@@ -0,0 +1,838 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long
""" Test the central_configuration """
import asyncio
from datetime import datetime
from unittest.mock import patch, call
from homeassistant.core import HomeAssistant
from homeassistant.config_entries import ConfigEntryState
from pytest_homeassistant_custom_component.common import MockConfigEntry
from custom_components.versatile_thermostat.thermostat_switch import (
ThermostatOverSwitch,
)
from custom_components.versatile_thermostat.thermostat_valve import (
ThermostatOverValve,
)
from custom_components.versatile_thermostat.thermostat_climate import (
ThermostatOverClimate,
)
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
from custom_components.versatile_thermostat.binary_sensor import (
CentralBoilerBinarySensor,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
# @pytest.mark.parametrize("expected_lingering_tasks", [True])
# @pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_add_a_central_config_with_boiler(
hass: HomeAssistant,
skip_hass_states_is_state,
):
"""Tests the clean_central_config_doubon of base_thermostat"""
central_config_entry = MockConfigEntry(
domain=DOMAIN,
title="TheCentralConfigMockName",
unique_id="centralConfigUniqueId",
data=FULL_CENTRAL_CONFIG_WITH_BOILER,
)
central_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(central_config_entry.entry_id)
assert central_config_entry.state is ConfigEntryState.LOADED
entity: ThermostatOverClimate = search_entity(
hass, "climate.thecentralconfigmockname", "climate"
)
assert entity is None
assert count_entities(hass, "climate.thecentralconfigmockname", "climate") == 0
# Test that VTherm API find the CentralConfig
api = VersatileThermostatAPI.get_vtherm_api(hass)
central_configuration = api.find_central_configuration()
assert central_configuration is not None
# Test that VTherm API have any central boiler entities
assert api.nb_active_device_for_boiler_entity is not None
assert api.nb_active_device_for_boiler == 0
assert api.nb_active_device_for_boiler_threshold_entity is not None
assert api.nb_active_device_for_boiler_threshold is 1 # the default value is 1
async def test_update_central_boiler_state_simple(
hass: HomeAssistant,
# skip_hass_states_is_state,
init_central_config_with_boiler_fixture,
):
"""Test that the central boiler state behavoir"""
api = VersatileThermostatAPI.get_vtherm_api(hass)
switch1 = MockSwitch(hass, "switch1", "theSwitch1")
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 8,
CONF_TEMP_MAX: 18,
"frost_temp": 10,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 21,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: switch1.entity_id,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_INVERSE_SWITCH: False,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_TPI_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
CONF_USED_BY_CENTRAL_BOILER: True,
},
)
entity: ThermostatOverSwitch = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
assert entity.name == "TheOverSwitchMockName"
assert entity.is_over_switch
assert entity.underlying_entities[0].entity_id == "switch.switch1"
assert api.nb_active_device_for_boiler_threshold == 1
assert api.nb_active_device_for_boiler == 0
# Force the VTherm to heat
await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST)
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
await send_temperature_change_event(entity, 10, now)
assert entity.hvac_mode == HVACMode.HEAT
boiler_binary_sensor: CentralBoilerBinarySensor = search_entity(
hass, "binary_sensor.central_boiler", "binary_sensor"
)
assert boiler_binary_sensor is not None
assert boiler_binary_sensor.state == STATE_OFF
# 1. start a heater
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.binary_sensor.send_vtherm_event"
) as mock_send_event:
await switch1.async_turn_on()
switch1.async_write_ha_state()
# Wait for state event propagation
await asyncio.sleep(0.1)
assert entity.hvac_action == HVACAction.HEATING
assert mock_service_call.call_count >= 1
# Sometimes this test fails
# mock_service_call.assert_has_calls(
# [
# call.service_call(
# "switch",
# "turn_on",
# service_data={},
# target={"entity_id": "switch.pompe_chaudiere"},
# ),
# ]
# )
assert mock_send_event.call_count >= 1
mock_send_event.assert_has_calls(
[
call.send_vtherm_event(
hass=hass,
event_type=EventType.CENTRAL_BOILER_EVENT,
entity=api.central_boiler_entity,
data={"central_boiler": True},
)
]
)
assert api.nb_active_device_for_boiler == 1
assert boiler_binary_sensor.state == STATE_ON
# 2. stop a heater
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.binary_sensor.send_vtherm_event"
) as mock_send_event:
await switch1.async_turn_off()
switch1.async_write_ha_state()
# Wait for state event propagation
await asyncio.sleep(0.1)
assert entity.hvac_action == HVACAction.IDLE
assert mock_service_call.call_count >= 1
mock_service_call.assert_has_calls(
[
call(
"switch",
"turn_off",
service_data={},
target={"entity_id": "switch.pompe_chaudiere"},
)
]
)
assert mock_send_event.call_count >= 1
mock_send_event.assert_has_calls(
[
call.send_vtherm_event(
hass=hass,
event_type=EventType.CENTRAL_BOILER_EVENT,
entity=api.central_boiler_entity,
data={"central_boiler": False},
)
]
)
assert api.nb_active_device_for_boiler == 0
assert boiler_binary_sensor.state == STATE_OFF
entity.remove_thermostat()
async def test_update_central_boiler_state_multiple(
hass: HomeAssistant,
# skip_hass_states_is_state,
init_central_config_with_boiler_fixture,
):
"""Test that the central boiler state behavoir"""
api = VersatileThermostatAPI.get_vtherm_api(hass)
switch1 = MockSwitch(hass, "switch1", "theSwitch1")
switch2 = MockSwitch(hass, "switch2", "theSwitch2")
switch3 = MockSwitch(hass, "switch3", "theSwitch3")
switch4 = MockSwitch(hass, "switch4", "theSwitch4")
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 8,
CONF_TEMP_MAX: 18,
"frost_temp": 10,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 21,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: switch1.entity_id,
CONF_HEATER_2: switch2.entity_id,
CONF_HEATER_3: switch3.entity_id,
CONF_HEATER_4: switch4.entity_id,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_INVERSE_SWITCH: False,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_TPI_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
CONF_USED_BY_CENTRAL_BOILER: True,
},
)
entity: ThermostatOverSwitch = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
assert entity.name == "TheOverSwitchMockName"
assert entity.is_over_switch
assert entity.underlying_entities[0].entity_id == "switch.switch1"
assert entity.underlying_entities[1].entity_id == "switch.switch2"
assert entity.underlying_entities[2].entity_id == "switch.switch3"
assert entity.underlying_entities[3].entity_id == "switch.switch4"
assert api.nb_active_device_for_boiler_threshold == 1
assert api.nb_active_device_for_boiler == 0
# Force the VTherm to heat
await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST)
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
await send_temperature_change_event(entity, 10, now)
assert entity.hvac_mode == HVACMode.HEAT
# 0. set threshold to 3
api.nb_active_device_for_boiler_threshold_entity.set_native_value(3)
assert api.nb_active_device_for_boiler_threshold == 3
boiler_binary_sensor: CentralBoilerBinarySensor = search_entity(
hass, "binary_sensor.central_boiler", "binary_sensor"
)
assert boiler_binary_sensor is not None
assert boiler_binary_sensor.state == STATE_OFF
# 1. start a first heater
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.binary_sensor.send_vtherm_event"
) as mock_send_event:
await switch1.async_turn_on()
switch1.async_write_ha_state()
# Wait for state event propagation
await asyncio.sleep(0.1)
assert entity.hvac_action == HVACAction.HEATING
assert mock_service_call.call_count == 1
# No switch of the boiler
mock_service_call.assert_has_calls(
[
call.service_call(
"switch",
"turn_on",
{"entity_id": "switch.switch1"},
),
]
)
assert mock_send_event.call_count == 0
assert api.nb_active_device_for_boiler == 1
assert boiler_binary_sensor.state == STATE_OFF
# 2. start a 2nd heater
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.binary_sensor.send_vtherm_event"
) as mock_send_event:
await switch2.async_turn_on()
switch2.async_write_ha_state()
# Wait for state event propagation
await asyncio.sleep(0.1)
assert entity.hvac_action == HVACAction.HEATING
# Only the first heater is started by the algo
assert mock_service_call.call_count == 1
# No switch of the boiler. Caution: each time a underlying heater state change itself,
# the cycle restarts. So it is always the first heater that is started
mock_service_call.assert_has_calls(
[
call.service_call(
"switch",
"turn_on",
{"entity_id": "switch.switch1"},
),
]
)
assert mock_send_event.call_count == 0
assert api.nb_active_device_for_boiler == 2
assert boiler_binary_sensor.state == STATE_OFF
# 3. start a 3rd heater
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.binary_sensor.send_vtherm_event"
) as mock_send_event:
await switch3.async_turn_on()
switch3.async_write_ha_state()
# Wait for state event propagation
await asyncio.sleep(0.1)
assert entity.hvac_action == HVACAction.HEATING
# Only the first heater is started by the algo
assert mock_service_call.call_count == 2
# No switch of the boiler. Caution: each time a underlying heater state change itself,
# the cycle restarts. So it is always the first heater that is started
mock_service_call.assert_has_calls(
[
call.service_call(
"switch",
"turn_on",
service_data={},
target={"entity_id": "switch.pompe_chaudiere"},
),
call.service_call(
"switch",
"turn_on",
{"entity_id": "switch.switch1"},
),
],
any_order=True,
)
assert mock_send_event.call_count == 1
mock_send_event.assert_has_calls(
[
call.send_vtherm_event(
hass=hass,
event_type=EventType.CENTRAL_BOILER_EVENT,
entity=api.central_boiler_entity,
data={"central_boiler": True},
)
]
)
assert api.nb_active_device_for_boiler == 3
assert boiler_binary_sensor.state == STATE_ON
# 4. start a 4th heater
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.binary_sensor.send_vtherm_event"
) as mock_send_event:
await switch4.async_turn_on()
switch4.async_write_ha_state()
# Wait for state event propagation
await asyncio.sleep(0.1)
assert entity.hvac_action == HVACAction.HEATING
# Only the first heater is started by the algo
assert mock_service_call.call_count == 1
# No switch of the boiler. Caution: each time a underlying heater state change itself,
# the cycle restarts. So it is always the first heater that is started
mock_service_call.assert_has_calls(
[
call.service_call(
"switch",
"turn_on",
{"entity_id": "switch.switch1"},
),
]
)
assert mock_send_event.call_count == 0
assert api.nb_active_device_for_boiler == 4
assert boiler_binary_sensor.state == STATE_ON
# 5. stop a heater
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.binary_sensor.send_vtherm_event"
) as mock_send_event:
await switch1.async_turn_off()
switch1.async_write_ha_state()
# Wait for state event propagation
await asyncio.sleep(0.1)
assert entity.hvac_action == HVACAction.HEATING
assert mock_service_call.call_count == 0
assert mock_send_event.call_count == 0
assert api.nb_active_device_for_boiler == 3
assert boiler_binary_sensor.state == STATE_ON
# 6. stop a 2nd heater
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.binary_sensor.send_vtherm_event"
) as mock_send_event:
await switch4.async_turn_off()
switch4.async_write_ha_state()
# Wait for state event propagation
await asyncio.sleep(0.1)
assert entity.hvac_action == HVACAction.HEATING
assert mock_service_call.call_count >= 1
mock_service_call.assert_has_calls(
[
call(
"switch",
"turn_off",
service_data={},
target={"entity_id": "switch.pompe_chaudiere"},
)
]
)
assert mock_send_event.call_count >= 1
mock_send_event.assert_has_calls(
[
call.send_vtherm_event(
hass=hass,
event_type=EventType.CENTRAL_BOILER_EVENT,
entity=api.central_boiler_entity,
data={"central_boiler": False},
)
]
)
assert api.nb_active_device_for_boiler == 2
assert boiler_binary_sensor.state == STATE_OFF
entity.remove_thermostat()
async def test_update_central_boiler_state_simple_valve(
hass: HomeAssistant,
# skip_hass_states_is_state,
init_central_config_with_boiler_fixture,
):
"""Test that the central boiler state behavoir"""
api = VersatileThermostatAPI.get_vtherm_api(hass)
valve1 = MockNumber(hass, "valve1", "theValve1")
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverValveMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverValveMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_VALVE,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 8,
CONF_TEMP_MAX: 18,
"frost_temp": 10,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 21,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_VALVE: valve1.entity_id,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_INVERSE_SWITCH: False,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_TPI_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
CONF_USED_BY_CENTRAL_BOILER: True,
},
)
entity: ThermostatOverValve = await create_thermostat(
hass, entry, "climate.theovervalvemockname"
)
assert entity
assert entity.name == "TheOverValveMockName"
assert entity.is_over_valve
assert entity.underlying_entities[0].entity_id == "number.valve1"
assert api.nb_active_device_for_boiler_threshold == 1
assert api.nb_active_device_for_boiler == 0
# Force the VTherm to heat
await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST)
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
assert entity.hvac_mode == HVACMode.HEAT
boiler_binary_sensor: CentralBoilerBinarySensor = search_entity(
hass, "binary_sensor.central_boiler", "binary_sensor"
)
assert boiler_binary_sensor is not None
assert boiler_binary_sensor.state == STATE_OFF
# 1. start a valve
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.binary_sensor.send_vtherm_event"
) as mock_send_event:
await send_temperature_change_event(entity, 10, now)
# we have to simulate the valve also else the test don't work
valve1.set_native_value(10)
valve1.async_write_ha_state()
# Wait for state event propagation
await asyncio.sleep(0.1)
assert entity.hvac_action == HVACAction.HEATING
assert mock_service_call.call_count >= 1
mock_service_call.assert_has_calls(
[
call.service_call(
"switch",
"turn_on",
service_data={},
target={"entity_id": "switch.pompe_chaudiere"},
),
]
)
assert mock_send_event.call_count >= 1
mock_send_event.assert_has_calls(
[
call.send_vtherm_event(
hass=hass,
event_type=EventType.CENTRAL_BOILER_EVENT,
entity=api.central_boiler_entity,
data={"central_boiler": True},
)
]
)
assert api.nb_active_device_for_boiler == 1
assert boiler_binary_sensor.state == STATE_ON
# 2. stop a heater
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.binary_sensor.send_vtherm_event"
) as mock_send_event:
await send_temperature_change_event(entity, 25, now)
# Change the valve value to 0
valve1.set_native_value(0)
valve1.async_write_ha_state()
# Wait for state event propagation
await asyncio.sleep(0.1)
assert entity.hvac_action == HVACAction.IDLE
assert mock_service_call.call_count >= 1
mock_service_call.assert_has_calls(
[
call(
"switch",
"turn_off",
service_data={},
target={"entity_id": "switch.pompe_chaudiere"},
)
]
)
assert mock_send_event.call_count >= 1
mock_send_event.assert_has_calls(
[
call.send_vtherm_event(
hass=hass,
event_type=EventType.CENTRAL_BOILER_EVENT,
entity=api.central_boiler_entity,
data={"central_boiler": False},
)
]
)
assert api.nb_active_device_for_boiler == 0
assert boiler_binary_sensor.state == STATE_OFF
entity.remove_thermostat()
async def test_update_central_boiler_state_simple_climate(
hass: HomeAssistant,
# skip_hass_states_is_state,
init_central_config_with_boiler_fixture,
):
"""Test that the central boiler state behavoir"""
api = VersatileThermostatAPI.get_vtherm_api(hass)
climate1 = MockClimate(hass, "climate1", "theClimate1")
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverClimateMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverClimateMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 8,
CONF_TEMP_MAX: 18,
"frost_temp": 10,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 21,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: climate1.entity_id,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
CONF_USED_BY_CENTRAL_BOILER: True,
},
)
with patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
return_value=climate1,
):
entity: ThermostatOverValve = await create_thermostat(
hass, entry, "climate.theoverclimatemockname"
)
assert entity
assert entity.name == "TheOverClimateMockName"
assert entity.is_over_climate
assert entity.underlying_entities[0].entity_id == "climate.climate1"
assert api.nb_active_device_for_boiler_threshold == 1
assert api.nb_active_device_for_boiler == 0
# Force the VTherm to heat
await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST)
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
assert entity.hvac_mode == HVACMode.HEAT
boiler_binary_sensor: CentralBoilerBinarySensor = search_entity(
hass, "binary_sensor.central_boiler", "binary_sensor"
)
assert boiler_binary_sensor is not None
assert boiler_binary_sensor.state == STATE_OFF
# 1. start a climate
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.binary_sensor.send_vtherm_event"
) as mock_send_event:
await send_temperature_change_event(entity, 10, now)
# we have to simulate the climate also else the test don't work
climate1.set_hvac_mode(HVACMode.HEAT)
climate1.set_hvac_action(HVACAction.HEATING)
climate1.async_write_ha_state()
# Wait for state event propagation
await asyncio.sleep(0.5)
assert entity.hvac_action == HVACAction.HEATING
assert mock_service_call.call_count >= 1
mock_service_call.assert_has_calls(
[
call.service_call(
"switch",
"turn_on",
service_data={},
target={"entity_id": "switch.pompe_chaudiere"},
),
]
)
assert mock_send_event.call_count >= 1
mock_send_event.assert_has_calls(
[
call.send_vtherm_event(
hass=hass,
event_type=EventType.CENTRAL_BOILER_EVENT,
entity=api.central_boiler_entity,
data={"central_boiler": True},
)
]
)
assert api.nb_active_device_for_boiler == 1
assert boiler_binary_sensor.state == STATE_ON
# 2. stop a climate
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.binary_sensor.send_vtherm_event"
) as mock_send_event:
await send_temperature_change_event(entity, 25, now)
climate1.set_hvac_mode(HVACMode.HEAT)
climate1.set_hvac_action(HVACAction.IDLE)
climate1.async_write_ha_state()
# Wait for state event propagation
await asyncio.sleep(0.5)
assert entity.hvac_action == HVACAction.IDLE
assert mock_service_call.call_count >= 1
mock_service_call.assert_has_calls(
[
call(
"switch",
"turn_off",
service_data={},
target={"entity_id": "switch.pompe_chaudiere"},
)
]
)
assert mock_send_event.call_count >= 1
mock_send_event.assert_has_calls(
[
call.send_vtherm_event(
hass=hass,
event_type=EventType.CENTRAL_BOILER_EVENT,
entity=api.central_boiler_entity,
data={"central_boiler": False},
)
]
)
assert api.nb_active_device_for_boiler == 0
assert boiler_binary_sensor.state == STATE_OFF
entity.remove_thermostat()

View File

@@ -0,0 +1,462 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long
""" Test the central_configuration """
from unittest.mock import patch # , call
# from datetime import datetime # , timedelta
from homeassistant import data_entry_flow
from homeassistant.core import HomeAssistant
# from homeassistant.components.climate import HVACAction, HVACMode
from homeassistant.config_entries import ConfigEntryState, SOURCE_USER
# from homeassistant.helpers.entity_component import EntityComponent
# from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
from pytest_homeassistant_custom_component.common import MockConfigEntry
# from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from custom_components.versatile_thermostat.thermostat_climate import (
ThermostatOverClimate,
)
from custom_components.versatile_thermostat.thermostat_switch import (
ThermostatOverSwitch,
)
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
# @pytest.mark.parametrize("expected_lingering_tasks", [True])
# @pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_add_a_central_config(hass: HomeAssistant, skip_hass_states_is_state):
"""Tests the clean_central_config_doubon of base_thermostat"""
central_config_entry = MockConfigEntry(
domain=DOMAIN,
title="TheCentralConfigMockName",
unique_id="centralConfigUniqueId",
data={
CONF_NAME: CENTRAL_CONFIG_NAME,
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_central_ext_temp_sensor",
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
CONF_TPI_COEF_INT: 0.5,
CONF_TPI_COEF_EXT: 0.02,
"frost_temp": 10,
"eco_temp": 17.1,
"comfort_temp": 18.1,
"boost_temp": 19.1,
"eco_ac_temp": 25.1,
"comfort_ac_temp": 23.1,
"boost_ac_temp": 21.1,
"frost_away_temp": 15.1,
"eco_away_temp": 15.2,
"comfort_away_temp": 15.3,
"boost_away_temp": 15.4,
"eco_ac_away_temp": 30.5,
"comfort_ac_away_temp": 30.6,
"boost_ac_away_temp": 30.7,
CONF_WINDOW_DELAY: 15,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 4,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 1,
CONF_WINDOW_AUTO_MAX_DURATION: 31,
CONF_MOTION_DELAY: 31,
CONF_MOTION_OFF_DELAY: 301,
CONF_MOTION_PRESET: "boost",
CONF_NO_MOTION_PRESET: "frost",
CONF_POWER_SENSOR: "sensor.mock_central_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_central_max_power_sensor",
CONF_PRESET_POWER: 14,
CONF_MINIMAL_ACTIVATION_DELAY: 11,
CONF_SECURITY_DELAY_MIN: 61,
CONF_SECURITY_MIN_ON_PERCENT: 0.5,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.2,
CONF_ADD_CENTRAL_BOILER_CONTROL: False,
},
)
central_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(central_config_entry.entry_id)
assert central_config_entry.state is ConfigEntryState.LOADED
entity: ThermostatOverClimate = search_entity(
hass, "climate.thecentralconfigmockname", "climate"
)
assert entity is None
assert count_entities(hass, "climate.thecentralconfigmockname", "climate") == 0
# Test that VTherm API find the CentralConfig
api = VersatileThermostatAPI.get_vtherm_api(hass)
central_configuration = api.find_central_configuration()
assert central_configuration is not None
# Test that VTherm API doesn't have any central boiler entities
assert api.nb_active_device_for_boiler_entity is None
assert api.nb_active_device_for_boiler is None
assert api.nb_active_device_for_boiler_threshold_entity is None
assert api.nb_active_device_for_boiler_threshold is None
# @pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_minimal_over_switch_wo_central_config(
hass: HomeAssistant, skip_hass_states_is_state, init_vtherm_api
):
"""Tests that a VTherm without any central_configuration is working with its own attributes"""
# Add a Switch VTherm
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 8,
CONF_TEMP_MAX: 18,
CONF_STEP_TEMPERATURE: 0.3,
"frost_temp": 10,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 21,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1,
# CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1,
# CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
# CONF_WINDOW_AUTO_MAX_DURATION: 0, # Should be 0 for test
CONF_INVERSE_SWITCH: True,
# CONF_USE_MAIN_CENTRAL_CONFIG: False,
# CONF_USE_TPI_CENTRAL_CONFIG: False,
# CONF_USE_WINDOW_CENTRAL_CONFIG: False,
# CONF_USE_MOTION_CENTRAL_CONFIG: False,
# CONF_USE_POWER_CENTRAL_CONFIG: False,
# CONF_USE_PRESENCE_CENTRAL_CONFIG: False,
# CONF_USE_PRESETS_CENTRAL_CONFIG: False,
# CONF_USE_ADVANCED_CENTRAL_CONFIG: False,
},
)
with patch("homeassistant.core.ServiceRegistry.async_call"):
entity: ThermostatOverSwitch = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
assert entity.name == "TheOverSwitchMockName"
assert entity.is_over_switch
assert entity._temp_sensor_entity_id == "sensor.mock_temp_sensor"
assert entity._ext_temp_sensor_entity_id == "sensor.mock_ext_temp_sensor"
assert entity._cycle_min == 5
assert entity.min_temp == 8
assert entity.max_temp == 18
assert entity.target_temperature_step == 0.3
assert entity.preset_modes == ["none", "frost", "eco", "comfort", "boost"]
assert entity.is_window_auto_enabled is False
assert entity.nb_underlying_entities == 1
assert entity.underlying_entity_id(0) == "switch.mock_switch"
assert entity.proportional_algorithm is not None
assert entity.proportional_algorithm._tpi_coef_int == 0.3
assert entity.proportional_algorithm._tpi_coef_ext == 0.01
assert entity.proportional_algorithm._minimal_activation_delay == 30
assert entity._security_delay_min == 5
assert entity._security_min_on_percent == 0.3
assert entity._security_default_on_percent == 0.1
assert entity.is_inversed
entity.remove_thermostat()
# @pytest.mark.parametrize("expected_lingering_tasks", [True])
# @pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_full_over_switch_wo_central_config(
hass: HomeAssistant, skip_hass_states_is_state, init_vtherm_api
):
"""Tests that a VTherm without any central_configuration is working with its own attributes"""
# Add a Switch VTherm
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 8,
CONF_TEMP_MAX: 18,
CONF_STEP_TEMPERATURE: 0.3,
"frost_temp": 10,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 21,
"frost_away_temp": 13,
"eco_away_temp": 13,
"comfort_away_temp": 13,
"boost_away_temp": 13,
CONF_USE_WINDOW_FEATURE: True,
CONF_USE_MOTION_FEATURE: True,
CONF_USE_POWER_FEATURE: True,
CONF_USE_PRESENCE_FEATURE: True,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_INVERSE_SWITCH: False,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 30,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 3,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_MAX_DURATION: 5,
CONF_MOTION_DELAY: 10,
CONF_MOTION_OFF_DELAY: 29,
CONF_MOTION_PRESET: "comfort",
CONF_NO_MOTION_PRESET: "eco",
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_max_power_sensor",
CONF_PRESENCE_SENSOR: "binary_sensor.mock_presence_sensor",
CONF_USE_MAIN_CENTRAL_CONFIG: False,
CONF_USE_TPI_CENTRAL_CONFIG: False,
CONF_USE_WINDOW_CENTRAL_CONFIG: False,
CONF_USE_MOTION_CENTRAL_CONFIG: False,
CONF_USE_POWER_CENTRAL_CONFIG: False,
CONF_USE_PRESENCE_CENTRAL_CONFIG: False,
CONF_USE_PRESETS_CENTRAL_CONFIG: False,
CONF_USE_ADVANCED_CENTRAL_CONFIG: False,
},
)
with patch("homeassistant.core.ServiceRegistry.async_call"):
entity: ThermostatOverSwitch = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
assert entity.name == "TheOverSwitchMockName"
assert entity.is_over_switch
assert entity._temp_sensor_entity_id == "sensor.mock_temp_sensor"
assert entity._ext_temp_sensor_entity_id == "sensor.mock_ext_temp_sensor"
assert entity._cycle_min == 5
assert entity.min_temp == 8
assert entity.max_temp == 18
assert entity.target_temperature_step == 0.3
assert entity.preset_modes == [
"none",
"frost",
"eco",
"comfort",
"boost",
"activity",
]
assert entity.nb_underlying_entities == 1
assert entity.underlying_entity_id(0) == "switch.mock_switch"
assert entity.proportional_algorithm is not None
assert entity.proportional_algorithm._tpi_coef_int == 0.3
assert entity.proportional_algorithm._tpi_coef_ext == 0.01
assert entity.proportional_algorithm._minimal_activation_delay == 30
assert entity._security_delay_min == 5
assert entity._security_min_on_percent == 0.3
assert entity._security_default_on_percent == 0.1
assert entity.is_inversed is False
assert entity.is_window_auto_enabled is False # we have an entity_id
assert entity._window_sensor_entity_id == "binary_sensor.mock_window_sensor"
assert entity._window_delay_sec == 30
assert entity._window_auto_close_threshold == 0.1
assert entity._window_auto_open_threshold == 3
assert entity._window_auto_max_duration == 5
assert entity._motion_sensor_entity_id == "binary_sensor.mock_motion_sensor"
assert entity._motion_delay_sec == 10
assert entity._motion_off_delay_sec == 29
assert entity._motion_preset == "comfort"
assert entity._no_motion_preset == "eco"
assert entity._power_sensor_entity_id == "sensor.mock_power_sensor"
assert entity._max_power_sensor_entity_id == "sensor.mock_max_power_sensor"
assert entity._presence_sensor_entity_id == "binary_sensor.mock_presence_sensor"
entity.remove_thermostat()
# @pytest.mark.parametrize("expected_lingering_tasks", [True])
# @pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_full_over_switch_with_central_config(
hass: HomeAssistant, skip_hass_states_is_state, init_central_config
):
"""Tests that a VTherm with central_configuration is working with the central_config attributes"""
# Add a Switch VTherm
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 8,
CONF_TEMP_MAX: 18,
CONF_STEP_TEMPERATURE: 0.3,
"frost_temp": 10,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 21,
CONF_USE_WINDOW_FEATURE: True,
CONF_USE_MOTION_FEATURE: True,
CONF_USE_POWER_FEATURE: True,
CONF_USE_PRESENCE_FEATURE: True,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_INVERSE_SWITCH: False,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 30,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 3,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_MAX_DURATION: 5,
CONF_MOTION_DELAY: 10,
CONF_MOTION_OFF_DELAY: 29,
CONF_MOTION_PRESET: "comfort",
CONF_NO_MOTION_PRESET: "eco",
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
CONF_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_max_power_sensor",
CONF_PRESENCE_SENSOR: "binary_sensor.mock_presence_sensor",
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_TPI_CENTRAL_CONFIG: True,
CONF_USE_WINDOW_CENTRAL_CONFIG: True,
CONF_USE_MOTION_CENTRAL_CONFIG: True,
CONF_USE_POWER_CENTRAL_CONFIG: True,
CONF_USE_PRESENCE_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
},
)
with patch("homeassistant.core.ServiceRegistry.async_call"):
entity: ThermostatOverSwitch = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
assert entity.name == "TheOverSwitchMockName"
assert entity.is_over_switch
assert entity._temp_sensor_entity_id == "sensor.mock_temp_sensor"
assert entity._ext_temp_sensor_entity_id == "sensor.mock_ext_temp_sensor"
assert entity._cycle_min == 5
assert entity.min_temp == 15
assert entity.max_temp == 30
assert entity.target_temperature_step == 0.1
assert entity.preset_modes == [
"none",
"frost",
"eco",
"boost",
"activity",
]
assert entity.nb_underlying_entities == 1
assert entity.underlying_entity_id(0) == "switch.mock_switch"
assert entity.proportional_algorithm is not None
assert entity.proportional_algorithm._tpi_coef_int == 0.5
assert entity.proportional_algorithm._tpi_coef_ext == 0.02
assert entity.proportional_algorithm._minimal_activation_delay == 11
assert entity._security_delay_min == 61
assert entity._security_min_on_percent == 0.5
assert entity._security_default_on_percent == 0.2
assert entity.is_inversed is False
# We have an entity so window auto is not enabled
assert entity.is_window_auto_enabled is False
assert entity._window_sensor_entity_id == "binary_sensor.mock_window_sensor"
assert entity._window_delay_sec == 15
assert entity._window_auto_close_threshold == 1
assert entity._window_auto_open_threshold == 4
assert entity._window_auto_max_duration == 31
assert entity._motion_sensor_entity_id == "binary_sensor.mock_motion_sensor"
assert entity._motion_delay_sec == 31
assert entity._motion_off_delay_sec == 301
assert entity._motion_preset == "boost"
assert entity._no_motion_preset == "frost"
assert entity._power_sensor_entity_id == "sensor.mock_power_sensor"
assert entity._max_power_sensor_entity_id == "sensor.mock_max_power_sensor"
assert entity._presence_sensor_entity_id == "binary_sensor.mock_presence_sensor"
entity.remove_thermostat()
# @pytest.mark.parametrize("expected_lingering_tasks", [True])
# @pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_over_switch_with_central_config_but_no_central_config(
hass: HomeAssistant, skip_hass_states_get, init_vtherm_api
):
"""Tests that a VTherm with a central_configuration flag but no central config. Should lead to an error"""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == SOURCE_USER
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "main"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_NAME: "TheOverSwitchMockName",
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_DEVICE_POWER: 1,
CONF_USE_WINDOW_FEATURE: True,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
# in case of error we stays in main
assert result["step_id"] == "main"
assert result["errors"] == {"use_main_central_config": "no_central_config"}

1040
tests/test_central_mode.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -13,13 +13,8 @@ from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
@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_show_form(hass: HomeAssistant) -> None: async def test_show_form(hass: HomeAssistant, init_vtherm_api) -> None:
"""Test that the form is served with no input""" """Test that the form is served with no input"""
# Init the API
# hass.data["custom_components"] = None
# loader.async_get_custom_components(hass)
# BaseThermostatAPI(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
@@ -33,9 +28,9 @@ async def test_show_form(hass: HomeAssistant) -> None:
# Disable this test which don't work anymore (kill the pytest !) # Disable this test which don't work anymore (kill the pytest !)
@pytest.mark.skip @pytest.mark.skip
async def test_user_config_flow_over_switch( async def test_user_config_flow_over_switch(
hass: HomeAssistant, skip_hass_states_get hass: HomeAssistant, skip_hass_states_get, init_central_config
): # pylint: disable=unused-argument ): # pylint: disable=unused-argument
"""Test the config flow with all thermostat_over_switch features""" """Test the config flow with all thermostat_over_switch features and central config on"""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
@@ -47,6 +42,14 @@ async def test_user_config_flow_over_switch(
result["flow_id"], user_input=MOCK_TH_OVER_SWITCH_USER_CONFIG result["flow_id"], user_input=MOCK_TH_OVER_SWITCH_USER_CONFIG
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "main"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_TH_OVER_SWITCH_MAIN_CONFIG
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "type" assert result["step_id"] == "type"
assert result["errors"] == {} assert result["errors"] == {}
@@ -60,7 +63,7 @@ async def test_user_config_flow_over_switch(
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_TH_OVER_SWITCH_TPI_CONFIG result["flow_id"], user_input={CONF_USE_TPI_CENTRAL_CONFIG: True}
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
@@ -68,7 +71,7 @@ async def test_user_config_flow_over_switch(
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_PRESETS_CONFIG result["flow_id"], user_input={CONF_USE_PRESETS_CENTRAL_CONFIG: True}
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
@@ -76,7 +79,11 @@ async def test_user_config_flow_over_switch(
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_WINDOW_CONFIG result["flow_id"],
user_input={
CONF_WINDOW_SENSOR: "binary_sensor.window_sensor",
CONF_USE_WINDOW_CENTRAL_CONFIG: True,
},
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
@@ -84,7 +91,11 @@ async def test_user_config_flow_over_switch(
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_MOTION_CONFIG result["flow_id"],
user_input={
CONF_MOTION_SENSOR: "input_boolean.motion_sensor",
CONF_USE_MOTION_CENTRAL_CONFIG: True,
},
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
@@ -92,7 +103,7 @@ async def test_user_config_flow_over_switch(
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_POWER_CONFIG result["flow_id"], user_input={CONF_USE_POWER_CENTRAL_CONFIG: True}
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
@@ -100,7 +111,11 @@ async def test_user_config_flow_over_switch(
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_PRESENCE_CONFIG result["flow_id"],
user_input={
CONF_PRESENCE_SENSOR: "person.presence_sensor",
CONF_USE_PRESENCE_CENTRAL_CONFIG: True,
},
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
@@ -108,21 +123,29 @@ async def test_user_config_flow_over_switch(
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_ADVANCED_CONFIG result["flow_id"], user_input={CONF_USE_ADVANCED_CENTRAL_CONFIG: True}
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert ( assert result["data"] == (
result["data"] MOCK_TH_OVER_SWITCH_USER_CONFIG
== MOCK_TH_OVER_SWITCH_USER_CONFIG | MOCK_TH_OVER_SWITCH_MAIN_CONFIG
| MOCK_TH_OVER_SWITCH_TYPE_CONFIG | MOCK_TH_OVER_SWITCH_TYPE_CONFIG
| MOCK_TH_OVER_SWITCH_TPI_CONFIG | {CONF_WINDOW_SENSOR: "binary_sensor.window_sensor"}
| MOCK_PRESETS_CONFIG | {CONF_MOTION_SENSOR: "input_boolean.motion_sensor"}
| MOCK_WINDOW_CONFIG | {CONF_PRESENCE_SENSOR: "person.presence_sensor"}
| MOCK_MOTION_CONFIG | {
| MOCK_POWER_CONFIG CONF_USE_MAIN_CENTRAL_CONFIG: True,
| MOCK_PRESENCE_CONFIG CONF_USE_TPI_CENTRAL_CONFIG: True,
| MOCK_ADVANCED_CONFIG CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_WINDOW_CENTRAL_CONFIG: True,
CONF_USE_MOTION_CENTRAL_CONFIG: True,
CONF_USE_POWER_CENTRAL_CONFIG: True,
CONF_USE_PRESENCE_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
CONF_USE_CENTRAL_MODE: True,
CONF_USED_BY_CENTRAL_BOILER: False,
}
) )
assert result["result"] assert result["result"]
assert result["result"].domain == DOMAIN assert result["result"].domain == DOMAIN
@@ -137,6 +160,8 @@ async def test_user_config_flow_over_climate(
hass: HomeAssistant, skip_hass_states_get hass: HomeAssistant, skip_hass_states_get
): # pylint: disable=unused-argument ): # pylint: disable=unused-argument
"""Test the config flow with all thermostat_over_climate features and no additional features""" """Test the config flow with all thermostat_over_climate features and no additional features"""
await create_central_config(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
@@ -148,6 +173,22 @@ async def test_user_config_flow_over_climate(
result["flow_id"], user_input=MOCK_TH_OVER_CLIMATE_USER_CONFIG result["flow_id"], user_input=MOCK_TH_OVER_CLIMATE_USER_CONFIG
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "main"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_TH_OVER_CLIMATE_MAIN_CONFIG
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "main"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "type" assert result["step_id"] == "type"
assert result["errors"] == {} assert result["errors"] == {}
@@ -160,6 +201,14 @@ async def test_user_config_flow_over_climate(
assert result["step_id"] == "presets" assert result["step_id"] == "presets"
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_USE_PRESETS_CENTRAL_CONFIG: False}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "presets"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_PRESETS_CONFIG result["flow_id"], user_input=MOCK_PRESETS_CONFIG
) )
@@ -168,19 +217,32 @@ async def test_user_config_flow_over_climate(
assert result["step_id"] == "advanced" assert result["step_id"] == "advanced"
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_USE_ADVANCED_CENTRAL_CONFIG: False}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "advanced"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_ADVANCED_CONFIG result["flow_id"], user_input=MOCK_ADVANCED_CONFIG
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert ( assert result[
result["data"] "data"
== MOCK_TH_OVER_CLIMATE_USER_CONFIG ] == MOCK_TH_OVER_CLIMATE_USER_CONFIG | MOCK_TH_OVER_CLIMATE_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_CONFIG | MOCK_PRESETS_CONFIG | MOCK_ADVANCED_CONFIG | MOCK_DEFAULT_FEATURE_CONFIG | {
| MOCK_TH_OVER_CLIMATE_TYPE_CONFIG CONF_USE_MAIN_CENTRAL_CONFIG: False,
| MOCK_PRESETS_CONFIG CONF_USE_TPI_CENTRAL_CONFIG: False,
| MOCK_ADVANCED_CONFIG CONF_USE_PRESETS_CENTRAL_CONFIG: False,
| MOCK_DEFAULT_FEATURE_CONFIG CONF_USE_WINDOW_CENTRAL_CONFIG: False,
) CONF_USE_MOTION_CENTRAL_CONFIG: False,
CONF_USE_POWER_CENTRAL_CONFIG: False,
CONF_USE_PRESENCE_CENTRAL_CONFIG: False,
CONF_USE_ADVANCED_CENTRAL_CONFIG: False,
CONF_USED_BY_CENTRAL_BOILER: False,
}
assert result["result"] assert result["result"]
assert result["result"].domain == DOMAIN assert result["result"].domain == DOMAIN
assert result["result"].version == 1 assert result["result"].version == 1
@@ -196,6 +258,8 @@ async def test_user_config_flow_window_auto_ok(
skip_control_heating, # pylint: disable=unused-argument skip_control_heating, # pylint: disable=unused-argument
): ):
"""Test the config flow with only window auto feature""" """Test the config flow with only window auto feature"""
await create_central_config(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
@@ -206,18 +270,27 @@ async def test_user_config_flow_window_auto_ok(
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={ user_input={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH, CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "main"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_NAME: "TheOverSwitchMockName",
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5, CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
CONF_DEVICE_POWER: 1, CONF_DEVICE_POWER: 1,
CONF_USE_WINDOW_FEATURE: True, CONF_USE_WINDOW_FEATURE: True,
CONF_USE_MOTION_FEATURE: False, CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False, CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False, CONF_USE_PRESENCE_FEATURE: False,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USED_BY_CENTRAL_BOILER: True,
}, },
) )
@@ -233,6 +306,14 @@ async def test_user_config_flow_window_auto_ok(
assert result["step_id"] == "tpi" assert result["step_id"] == "tpi"
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_USE_TPI_CENTRAL_CONFIG: False}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "tpi"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_TH_OVER_SWITCH_TPI_CONFIG result["flow_id"], user_input=MOCK_TH_OVER_SWITCH_TPI_CONFIG
) )
@@ -242,7 +323,16 @@ async def test_user_config_flow_window_auto_ok(
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_PRESETS_CONFIG result["flow_id"], user_input={CONF_USE_PRESETS_CENTRAL_CONFIG: True}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "window"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_USE_WINDOW_CENTRAL_CONFIG: False},
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
@@ -259,27 +349,36 @@ async def test_user_config_flow_window_auto_ok(
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_ADVANCED_CONFIG result["flow_id"], user_input={CONF_USE_ADVANCED_CENTRAL_CONFIG: True}
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert ( assert result["data"] == {
result["data"] CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
== MOCK_TH_OVER_SWITCH_USER_CONFIG CONF_NAME: "TheOverSwitchMockName",
| { CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_USE_MOTION_FEATURE: False, CONF_CYCLE_MIN: 5,
CONF_USE_POWER_FEATURE: False, CONF_DEVICE_POWER: 1,
CONF_USE_PRESENCE_FEATURE: False, CONF_USE_WINDOW_FEATURE: True,
CONF_WINDOW_DELAY: 30, # the default value is added CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False, CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False, CONF_USE_PRESENCE_FEATURE: False,
} CONF_USE_MOTION_FEATURE: False,
| MOCK_TH_OVER_SWITCH_TYPE_CONFIG CONF_USE_POWER_FEATURE: False,
| MOCK_TH_OVER_SWITCH_TPI_CONFIG CONF_USE_PRESENCE_FEATURE: False,
| MOCK_PRESETS_CONFIG CONF_WINDOW_DELAY: 30, # the default value is added
| MOCK_WINDOW_AUTO_CONFIG CONF_USE_CENTRAL_MODE: True, # True is the defaulf value
| MOCK_ADVANCED_CONFIG } | MOCK_TH_OVER_SWITCH_TYPE_CONFIG | MOCK_TH_OVER_SWITCH_TPI_CONFIG | MOCK_WINDOW_AUTO_CONFIG | {
) CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_TPI_CENTRAL_CONFIG: False,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_WINDOW_CENTRAL_CONFIG: False,
CONF_USE_MOTION_CENTRAL_CONFIG: False,
CONF_USE_POWER_CENTRAL_CONFIG: False,
CONF_USE_PRESENCE_CENTRAL_CONFIG: False,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
CONF_USED_BY_CENTRAL_BOILER: True,
}
assert result["result"] assert result["result"]
assert result["result"].domain == DOMAIN assert result["result"].domain == DOMAIN
assert result["result"].version == 1 assert result["result"].version == 1
@@ -293,6 +392,9 @@ async def test_user_config_flow_window_auto_ko(
hass: HomeAssistant, skip_hass_states_get # pylint: disable=unused-argument hass: HomeAssistant, skip_hass_states_get # pylint: disable=unused-argument
): ):
"""Test the config flow with window auto and window features -> not allowed""" """Test the config flow with window auto and window features -> not allowed"""
await create_central_config(hass)
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER} DOMAIN, context={"source": SOURCE_USER}
) )
@@ -303,18 +405,26 @@ async def test_user_config_flow_window_auto_ko(
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input={ user_input={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH, CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "main"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_NAME: "TheOverSwitchMockName",
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5, CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
CONF_DEVICE_POWER: 1, CONF_DEVICE_POWER: 1,
CONF_USE_WINDOW_FEATURE: True, CONF_USE_WINDOW_FEATURE: True,
CONF_USE_MOTION_FEATURE: False, CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False, CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False, CONF_USE_PRESENCE_FEATURE: False,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
}, },
) )
@@ -330,6 +440,14 @@ async def test_user_config_flow_window_auto_ko(
assert result["step_id"] == "tpi" assert result["step_id"] == "tpi"
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input={CONF_USE_TPI_CENTRAL_CONFIG: False}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "tpi"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_TH_OVER_SWITCH_TPI_CONFIG result["flow_id"], user_input=MOCK_TH_OVER_SWITCH_TPI_CONFIG
) )
@@ -339,7 +457,7 @@ async def test_user_config_flow_window_auto_ko(
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_PRESETS_CONFIG result["flow_id"], user_input={CONF_USE_PRESETS_CENTRAL_CONFIG: True}
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
@@ -348,15 +466,29 @@ async def test_user_config_flow_window_auto_ko(
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
user_input=MOCK_WINDOW_AUTO_CONFIG | MOCK_WINDOW_CONFIG, user_input={
CONF_WINDOW_SENSOR: "binary_sensor.window_sensor",
CONF_USE_WINDOW_CENTRAL_CONFIG: False,
},
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
# We should stay on window with an error
assert result["step_id"] == "window" assert result["step_id"] == "window"
assert result["errors"] == { assert result["errors"] == {}
"window_sensor_entity_id": "window_open_detection_method"
} result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=MOCK_WINDOW_DELAY_CONFIG,
)
# Since issue #280 we cannot have the error because we only display the
# MOCK_WINDOW_DELAY_CONFIG form if we have a sensor configured
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
# We should stay on window with an error
assert result["errors"] == {}
# "window_sensor_entity_id": "window_open_detection_method"
# }
assert result["step_id"] == "advanced"
@pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize("expected_lingering_tasks", [True])
@@ -368,19 +500,24 @@ async def test_user_config_flow_over_4_switches(
): ):
"""Test the config flow with 4 switchs thermostat_over_switch features""" """Test the config flow with 4 switchs thermostat_over_switch features"""
SOURCE_CONFIG = { # pylint: disable=wildcard-import, invalid-name await create_central_config(hass)
CONF_NAME: "TheOver4SwitchMockName",
SOURCE_CONFIG = {
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH, CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
}
MAIN_CONFIG = { # pylint: disable=wildcard-import, invalid-name
CONF_NAME: "TheOver4SwitchMockName",
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor", CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5, CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
CONF_DEVICE_POWER: 1, CONF_DEVICE_POWER: 1,
CONF_USE_WINDOW_FEATURE: False, CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False, CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False, CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False, CONF_USE_PRESENCE_FEATURE: False,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_CENTRAL_MODE: False,
CONF_USED_BY_CENTRAL_BOILER: False,
} }
TYPE_CONFIG = { # pylint: disable=wildcard-import, invalid-name TYPE_CONFIG = { # pylint: disable=wildcard-import, invalid-name
@@ -390,6 +527,7 @@ async def test_user_config_flow_over_4_switches(
CONF_HEATER_4: "switch.mock_switch4", CONF_HEATER_4: "switch.mock_switch4",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI, CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AC_MODE: False, CONF_AC_MODE: False,
CONF_INVERSE_SWITCH: False,
} }
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@@ -404,6 +542,15 @@ async def test_user_config_flow_over_4_switches(
user_input=SOURCE_CONFIG, user_input=SOURCE_CONFIG,
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "main"
assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=MAIN_CONFIG,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "type" assert result["step_id"] == "type"
assert result["errors"] == {} assert result["errors"] == {}
@@ -418,7 +565,7 @@ async def test_user_config_flow_over_4_switches(
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_TH_OVER_SWITCH_TPI_CONFIG result["flow_id"], user_input={CONF_USE_TPI_CENTRAL_CONFIG: True}
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
@@ -426,7 +573,7 @@ async def test_user_config_flow_over_4_switches(
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_PRESETS_CONFIG result["flow_id"], user_input={CONF_USE_PRESETS_CENTRAL_CONFIG: True}
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
@@ -434,15 +581,25 @@ async def test_user_config_flow_over_4_switches(
assert result["errors"] == {} assert result["errors"] == {}
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=MOCK_ADVANCED_CONFIG result["flow_id"], user_input={CONF_USE_ADVANCED_CENTRAL_CONFIG: True}
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result[ assert result["data"] == (
"data" SOURCE_CONFIG
] == SOURCE_CONFIG | TYPE_CONFIG | MOCK_TH_OVER_SWITCH_TPI_CONFIG | MOCK_PRESETS_CONFIG | MOCK_ADVANCED_CONFIG | { | MAIN_CONFIG
CONF_INVERSE_SWITCH: False | TYPE_CONFIG
} | {
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_TPI_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_WINDOW_CENTRAL_CONFIG: False,
CONF_USE_MOTION_CENTRAL_CONFIG: False,
CONF_USE_POWER_CENTRAL_CONFIG: False,
CONF_USE_PRESENCE_CENTRAL_CONFIG: False,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
}
)
assert result["result"] assert result["result"]
assert result["result"].domain == DOMAIN assert result["result"].domain == DOMAIN
assert result["result"].version == 1 assert result["result"].version == 1

View File

@@ -1,15 +1,18 @@
# pylint: disable=unused-argument, line-too-long, protected-access # pylint: disable=unused-argument, line-too-long, protected-access
""" Test the Window management """ """ Test the Window management """
import asyncio # import asyncio
import logging import logging
from unittest.mock import patch, call from unittest.mock import patch, call
from datetime import datetime, timedelta from datetime import datetime, timedelta
from custom_components.versatile_thermostat.thermostat_switch import ThermostatOverSwitch from custom_components.versatile_thermostat.thermostat_switch import (
ThermostatOverSwitch,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
@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_inverted_switch(hass: HomeAssistant, skip_hass_states_is_state): async def test_inverted_switch(hass: HomeAssistant, skip_hass_states_is_state):
@@ -44,14 +47,14 @@ async def test_inverted_switch(hass: HomeAssistant, skip_hass_states_is_state):
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1, CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1, CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_MAX_DURATION: 0, # Should be 0 for test CONF_WINDOW_AUTO_MAX_DURATION: 0, # Should be 0 for test
CONF_INVERSE_SWITCH: True CONF_INVERSE_SWITCH: True,
}, },
) )
with patch( with patch(
"homeassistant.core.ServiceRegistry.async_call" "homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch( ) as mock_service_call, patch(
"homeassistant.core.StateMachine.is_state", return_value=True # switch is On "homeassistant.core.StateMachine.is_state", return_value=True # switch is On
): ):
entity: ThermostatOverSwitch = await create_thermostat( entity: ThermostatOverSwitch = await create_thermostat(
hass, entry, "climate.theoverswitchmockname" hass, entry, "climate.theoverswitchmockname"
@@ -80,7 +83,7 @@ async def test_inverted_switch(hass: HomeAssistant, skip_hass_states_is_state):
), patch( ), patch(
"homeassistant.core.ServiceRegistry.async_call" "homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch( ) as mock_service_call, patch(
"homeassistant.core.StateMachine.is_state", return_value=True # switch is Off "homeassistant.core.StateMachine.is_state", return_value=True # switch is Off
): ):
event_timestamp = now - timedelta(minutes=4) event_timestamp = now - timedelta(minutes=4)
await send_temperature_change_event(entity, 19, event_timestamp) await send_temperature_change_event(entity, 19, event_timestamp)
@@ -90,9 +93,13 @@ async def test_inverted_switch(hass: HomeAssistant, skip_hass_states_is_state):
# not updated cause mocked assert entity.is_device_active is True # not updated cause mocked assert entity.is_device_active is True
assert mock_service_call.call_count == 1 assert mock_service_call.call_count == 1
mock_service_call.assert_has_calls([ mock_service_call.assert_has_calls(
call.async_call('switch', SERVICE_TURN_OFF, {'entity_id': 'switch.mock_switch'}), [
]) call.async_call(
"switch", SERVICE_TURN_OFF, {"entity_id": "switch.mock_switch"}
),
]
)
# 2. Make the temperature up to deactivate the switch # 2. Make the temperature up to deactivate the switch
with patch( with patch(
@@ -100,7 +107,8 @@ async def test_inverted_switch(hass: HomeAssistant, skip_hass_states_is_state):
), patch( ), patch(
"homeassistant.core.ServiceRegistry.async_call" "homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch( ) as mock_service_call, patch(
"homeassistant.core.StateMachine.is_state", return_value=False # switch is On -> it should turned off "homeassistant.core.StateMachine.is_state",
return_value=False, # switch is On -> it should turned off
): ):
event_timestamp = now - timedelta(minutes=3) event_timestamp = now - timedelta(minutes=3)
await send_temperature_change_event(entity, 25, event_timestamp) await send_temperature_change_event(entity, 25, event_timestamp)
@@ -114,11 +122,13 @@ async def test_inverted_switch(hass: HomeAssistant, skip_hass_states_is_state):
await entity._underlyings[0].turn_off() await entity._underlyings[0].turn_off()
assert mock_service_call.call_count == 1 assert mock_service_call.call_count == 1
mock_service_call.assert_has_calls([ mock_service_call.assert_has_calls(
call.async_call('switch', SERVICE_TURN_ON, {'entity_id': 'switch.mock_switch'}), [
]) call.async_call(
"switch", SERVICE_TURN_ON, {"entity_id": "switch.mock_switch"}
),
]
)
# Clean the entity # Clean the entity
entity.remove_thermostat() entity.remove_thermostat()

View File

@@ -472,7 +472,7 @@ async def test_multiple_climates_underlying_changes(
skip_hass_states_is_state, skip_hass_states_is_state,
skip_send_event, skip_send_event,
): # pylint: disable=unused-argument ): # pylint: disable=unused-argument
"""Test that when multiple switch are configured the activation of one underlying """Test that when multiple climate are configured the activation of one underlying
climate activate the others""" climate activate the others"""
tz = get_tz(hass) # pylint: disable=invalid-name tz = get_tz(hass) # pylint: disable=invalid-name
@@ -541,11 +541,15 @@ async def test_multiple_climates_underlying_changes(
assert entity.is_device_active is False # pylint: disable=protected-access assert entity.is_device_active is False # pylint: disable=protected-access
# Stop heating on one underlying climate # Stop heating on one underlying climate
# All underlying supposed to be aligned with the hvac_mode now
with patch( with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating" "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
), patch( ), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode" "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
) as mock_underlying_set_hvac_mode: ) as mock_underlying_set_hvac_mode, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.hvac_mode",
HVACMode.HEAT,
):
# Wait 11 sec so that the event will not be discarded # Wait 11 sec so that the event will not be discarded
event_timestamp = now + timedelta(seconds=11) event_timestamp = now + timedelta(seconds=11)
await send_climate_change_event( await send_climate_change_event(
@@ -555,6 +559,7 @@ async def test_multiple_climates_underlying_changes(
HVACAction.OFF, HVACAction.OFF,
HVACAction.HEATING, HVACAction.HEATING,
event_timestamp, event_timestamp,
underlying_entity_id="switch.mock_climate3",
) )
# Should be call for all Switch # Should be call for all Switch
@@ -577,6 +582,9 @@ async def test_multiple_climates_underlying_changes(
# a function but a property # a function but a property
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.hvac_action", "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.hvac_action",
HVACAction.IDLE, HVACAction.IDLE,
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.hvac_mode",
HVACMode.OFF,
): ):
# Wait 11 sec so that the event will not be discarded # Wait 11 sec so that the event will not be discarded
event_timestamp = now + timedelta(seconds=11) event_timestamp = now + timedelta(seconds=11)
@@ -601,6 +609,113 @@ async def test_multiple_climates_underlying_changes(
assert entity.is_device_active is False # pylint: disable=protected-access assert entity.is_device_active is False # pylint: disable=protected-access
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_multiple_climates_underlying_changes_not_aligned(
hass: HomeAssistant,
skip_hass_states_is_state,
skip_send_event,
): # pylint: disable=unused-argument
"""Test that when multiple climate are configured the activation of one underlying
climate don't activate the others if their havc_mode are not aligned"""
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOver4ClimateMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOver4ClimateMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 8,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "switch.mock_climate1",
CONF_CLIMATE_2: "switch.mock_climate2",
CONF_CLIMATE_3: "switch.mock_climate3",
CONF_CLIMATE_4: "switch.mock_climate4",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
},
)
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theover4climatemockname"
)
assert entity
assert entity.is_over_climate is True
assert entity.nb_underlying_entities == 4
# start heating, in boost mode. We block the control_heating to avoid running a cycle
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
) as mock_underlying_set_hvac_mode:
await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.target_temperature == 19
assert entity.window_state is STATE_OFF
event_timestamp = now - timedelta(minutes=4)
await send_temperature_change_event(entity, 15, event_timestamp)
# Should be call for all Switch
assert mock_underlying_set_hvac_mode.call_count == 4
mock_underlying_set_hvac_mode.assert_has_calls(
[
call.set_hvac_mode(HVACMode.HEAT),
]
)
# Stop heating on one underlying climate
# All underlying supposed to be aligned with the hvac_mode now
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.async_control_heating"
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
) as mock_underlying_set_hvac_mode, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.hvac_mode",
HVACMode.COOL,
):
# Wait 11 sec so that the event will not be discarded
event_timestamp = now + timedelta(seconds=11)
await send_climate_change_event(
entity,
HVACMode.OFF,
HVACMode.HEAT,
HVACAction.OFF,
HVACAction.HEATING,
event_timestamp,
underlying_entity_id="switch.mock_climate3",
)
# Should be call for all Switch
assert mock_underlying_set_hvac_mode.call_count == 0
# mock_underlying_set_hvac_mode.assert_has_calls(
# [
# call.set_hvac_mode(HVACMode.OFF),
# ]
# )
# No change
assert entity.hvac_mode == HVACMode.HEAT
@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_multiple_switch_power_management( async def test_multiple_switch_power_management(

View File

@@ -288,7 +288,7 @@ async def test_security_feature_back_on_percent(
assert entity.security_state is False assert entity.security_state is False
assert mock_send_event.call_count == 0 assert mock_send_event.call_count == 0
# 3. Set security mode with a preset change # 3. Set safety mode with a preset change
with patch( with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch( ) as mock_send_event, patch(
@@ -400,7 +400,7 @@ async def test_security_over_climate(
skip_turn_on_off_heater, skip_turn_on_off_heater,
skip_send_event, skip_send_event,
): ):
"""Test that when a underlying climate is not available the VTherm doesn't go into security mode""" """Test that when a underlying climate is not available the VTherm doesn't go into safety mode"""
tz = get_tz(hass) # pylint: disable=invalid-name tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz) now: datetime = datetime.now(tz=tz)
@@ -471,7 +471,7 @@ async def test_security_over_climate(
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()])
# Force security mode # Force safety mode
assert entity._last_ext_temperature_measure is not None assert entity._last_ext_temperature_measure is not None
assert entity._last_temperature_measure is not None assert entity._last_temperature_measure is not None
assert ( assert (
@@ -505,7 +505,7 @@ async def test_security_over_climate(
event_timestamp = now - timedelta(minutes=6) event_timestamp = now - timedelta(minutes=6)
await send_temperature_change_event(entity, 15, event_timestamp) await send_temperature_change_event(entity, 15, event_timestamp)
# Should stay False because a climate is never in security mode # Should stay False because a climate is never in safety mode
assert entity.security_state is False assert entity.security_state is False
assert entity.preset_mode == "none" assert entity.preset_mode == "none"
assert entity._saved_preset_mode == "none" assert entity._saved_preset_mode == "none"

View File

@@ -129,7 +129,7 @@ async def test_over_switch_ac_full_start(
assert entity.hvac_mode is HVACMode.OFF assert entity.hvac_mode is HVACMode.OFF
assert entity.hvac_action is HVACAction.OFF assert entity.hvac_action is HVACAction.OFF
assert entity.target_temperature == 16 # eco_ac_away assert entity.target_temperature == 27 # eco_ac_away (no change)
# Close a window # Close a window
with patch("homeassistant.helpers.condition.state", return_value=True): with patch("homeassistant.helpers.condition.state", return_value=True):

View File

@@ -1,10 +1,10 @@
# pylint: disable=line-too-long # pylint: disable=line-too-long, disable=protected-access
""" Test the normal start of a Switch AC Thermostat """ """ Test the normal start of a Switch AC Thermostat """
from unittest.mock import patch, call from unittest.mock import patch, call
from datetime import datetime, timedelta from datetime import datetime, timedelta
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, State
from homeassistant.components.climate import HVACAction, HVACMode from homeassistant.components.climate import HVACAction, HVACMode
from homeassistant.config_entries import ConfigEntryState from homeassistant.config_entries import ConfigEntryState
@@ -214,20 +214,60 @@ async def test_over_valve_full_start(
assert entity.hvac_action == HVACAction.HEATING assert entity.hvac_action == HVACAction.HEATING
# Change internal temperature # Change internal temperature
expected_state = State(
entity_id="number.mock_valve", state="0", attributes={"min": 10, "max": 50}
)
with patch( with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch( ) as mock_send_event, patch(
"homeassistant.core.ServiceRegistry.async_call" "homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch( ) as mock_service_call, patch(
"homeassistant.core.StateMachine.get", return_value=0 "homeassistant.core.StateMachine.get", return_value=expected_state
): ):
event_timestamp = now - timedelta(minutes=3) event_timestamp = now - timedelta(minutes=3)
await send_temperature_change_event(entity, 20, datetime.now()) await send_temperature_change_event(entity, 20, datetime.now())
assert entity.valve_open_percent == 0 assert entity.valve_open_percent == 0
assert entity.is_device_active is False assert entity.is_device_active is True # Should be 0 but in fact 10 is send
assert entity.hvac_action == HVACAction.IDLE assert (
entity.hvac_action == HVACAction.HEATING
) # Should be IDLE but heating due to 10
assert mock_service_call.call_count == 1
# The VTherm valve is 0, but the underlying have received 10 which is the min
mock_service_call.assert_has_calls(
[
call.async_call(
"number",
"set_value",
{"entity_id": "number.mock_valve", "value": 10},
)
]
)
await send_temperature_change_event(entity, 17, datetime.now()) await send_temperature_change_event(entity, 17, datetime.now())
assert mock_service_call.call_count == 2
# The VTherm valve is 0, but the underlying have received 10 which is the min
mock_service_call.assert_has_calls(
[
call.async_call(
"number",
"set_value",
{
"entity_id": "number.mock_valve",
"value": 10,
}, # the min allowed value
),
call.async_call(
"number",
"set_value",
{
"entity_id": "number.mock_valve",
"value": 50,
}, # the max allowed value
),
]
)
# switch to Eco # switch to Eco
await entity.async_set_preset_mode(PRESET_ECO) await entity.async_set_preset_mode(PRESET_ECO)
assert entity.preset_mode is PRESET_ECO assert entity.preset_mode is PRESET_ECO
@@ -243,6 +283,18 @@ async def test_over_valve_full_start(
assert entity.is_device_active is True assert entity.is_device_active is True
assert entity.hvac_action == HVACAction.HEATING assert entity.hvac_action == HVACAction.HEATING
# Test window open/close (with a normal min/max so that is_device_active is False when open_percent is 0)
expected_state = State(
entity_id="number.mock_valve", state="0", attributes={"min": 0, "max": 99}
)
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"homeassistant.core.StateMachine.get", return_value=expected_state
):
# Open a window # Open a window
with patch("homeassistant.helpers.condition.state", return_value=True): with patch("homeassistant.helpers.condition.state", return_value=True):
event_timestamp = now - timedelta(minutes=1) event_timestamp = now - timedelta(minutes=1)
@@ -269,9 +321,235 @@ async def test_over_valve_full_start(
await try_condition(None) await try_condition(None)
assert entity.hvac_mode is HVACMode.HEAT assert entity.hvac_mode is HVACMode.HEAT
assert ( assert entity.hvac_action is HVACAction.HEATING
entity.hvac_action is HVACAction.OFF
or entity.hvac_action is HVACAction.IDLE
)
assert entity.target_temperature == 17.1 # eco assert entity.target_temperature == 17.1 # eco
assert entity.valve_open_percent == 10 assert entity.valve_open_percent == 10
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_over_valve_regulation(
hass: HomeAssistant, skip_hass_states_is_state
): # pylint: disable=unused-argument
"""Test the normal full start of a thermostat in thermostat_over_switch type"""
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverValveMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverValveMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_VALVE,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_VALVE: "number.mock_valve",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
PRESET_FROST_PROTECTION + "_temp": 7,
PRESET_ECO + "_temp": 17,
PRESET_COMFORT + "_temp": 19,
PRESET_BOOST + "_temp": 21,
CONF_USE_WINDOW_FEATURE: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 60,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
# only send new valve open percent if dtemp is > 30%
CONF_AUTO_REGULATION_DTEMP: 5,
# only send new valve open percent last mesure was more than 5 min ago
CONF_AUTO_REGULATION_PERIOD_MIN: 5,
},
)
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
# 1. prepare the Valve at now
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
entity: ThermostatOverValve = await create_thermostat(
hass, entry, "climate.theovervalvemockname"
)
assert entity
assert isinstance(entity, ThermostatOverValve)
assert entity.name == "TheOverValveMockName"
assert entity.is_over_valve is True
assert entity._auto_regulation_dpercent == 5
assert entity._auto_regulation_period_min == 5
assert entity.target_temperature == entity.min_temp
assert entity._prop_algorithm is not None
# 2. Set the HVACMode to HEAT, with manual preset and target_temp to 18 before receiving temperature
# at now +1
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
now = now + timedelta(minutes=1)
entity._set_now(now)
# Select a hvacmode, presence and preset
await entity.async_set_hvac_mode(HVACMode.HEAT)
assert entity.hvac_mode is HVACMode.HEAT
# No heating now
assert entity.valve_open_percent == 0
assert entity.hvac_action == HVACAction.IDLE
assert mock_send_event.call_count == 1
mock_send_event.assert_has_calls(
[
call.send_event(
EventType.HVAC_MODE_EVENT,
{"hvac_mode": HVACMode.HEAT},
),
]
)
# 3. Set the preset
# at now +1
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
now = now + timedelta(minutes=1)
entity._set_now(now)
# set preset
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.preset_mode == PRESET_BOOST
assert entity.target_temperature == 21
# the preset have changed
assert mock_send_event.call_count == 1
mock_send_event.assert_has_calls(
[
call.send_event(
EventType.PRESET_EVENT,
{"preset": PRESET_BOOST},
),
]
)
await entity.async_set_hvac_mode(HVACMode.HEAT)
assert entity.hvac_mode is HVACMode.HEAT
# Still no heating because we don't have temperature
assert entity.valve_open_percent == 0
assert entity.hvac_action == HVACAction.IDLE
# 4. Set temperature and external temperature
# at now + 1 (but the _last_calculation_timestamp is still not send)
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"homeassistant.core.StateMachine.get",
return_value=State(entity_id="number.mock_valve", state="90"),
):
# Change temperature
now = now + timedelta(minutes=1)
entity._set_now(now)
await send_temperature_change_event(entity, 18, now)
assert entity.valve_open_percent == 90
assert entity.is_device_active is True
assert entity.hvac_action == HVACAction.HEATING
assert mock_service_call.call_count == 1
mock_service_call.assert_has_calls(
[
call.async_call(
"number",
"set_value",
{"entity_id": "number.mock_valve", "value": 90},
),
]
)
assert mock_send_event.call_count == 0
# 5. Set external temperature
# at now + 1
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"homeassistant.core.StateMachine.get",
return_value=State(entity_id="number.mock_valve", state="90"),
):
# Change external temperature
now = now + timedelta(minutes=1)
entity._set_now(now)
await send_ext_temperature_change_event(entity, 10, now)
# Should not have change due to regulation (period_min !)
assert entity.valve_open_percent == 90
assert entity.is_device_active is True
assert entity.hvac_action == HVACAction.HEATING
assert mock_service_call.call_count == 0
assert mock_send_event.call_count == 0
# 6. Set temperature
# at now + 5 (to avoid the period_min threshold)
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"homeassistant.core.StateMachine.get",
return_value=State(entity_id="number.mock_valve", state="90"),
):
# Change external temperature
now = now + timedelta(minutes=5)
entity._set_now(now)
await send_ext_temperature_change_event(entity, 15, now)
# Should have change this time to 96
assert entity.valve_open_percent == 96
assert entity.is_device_active is True
assert entity.hvac_action == HVACAction.HEATING
assert mock_service_call.call_count == 1
mock_service_call.assert_has_calls(
[
call.async_call(
"number",
"set_value",
{"entity_id": "number.mock_valve", "value": 96},
),
]
)
assert mock_send_event.call_count == 0
# 7. Set small temperature update to test dtemp threshold
# at now + 5
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"homeassistant.core.StateMachine.get",
return_value=State(entity_id="number.mock_valve", state="96"),
):
# Change external temperature
now = now + timedelta(minutes=5)
entity._set_now(now)
# this generate a delta percent of -3
await send_temperature_change_event(entity, 18.1, now)
# Should not have due to dtemp
assert entity.valve_open_percent == 96
assert entity.is_device_active is True
assert entity.hvac_action == HVACAction.HEATING
assert mock_service_call.call_count == 0
assert mock_send_event.call_count == 0

View File

@@ -1,4 +1,4 @@
# pylint: disable=unused-argument, line-too-long, protected-access # pylint: disable=unused-argument, line-too-long, protected-access, too-many-lines
""" Test the Window management """ """ Test the Window management """
import asyncio import asyncio
import logging import logging
@@ -6,6 +6,9 @@ from unittest.mock import patch, call, PropertyMock
from datetime import datetime, timedelta from datetime import datetime, timedelta
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from custom_components.versatile_thermostat.thermostat_climate import (
ThermostatOverClimate,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG) logging.getLogger().setLevel(logging.DEBUG)
@@ -46,6 +49,7 @@ async def test_window_management_time_not_enough(
CONF_SECURITY_MIN_ON_PERCENT: 0.3, CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor", CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
CONF_WINDOW_ACTION: CONF_WINDOW_TURN_OFF,
}, },
) )
@@ -134,6 +138,7 @@ async def test_window_management_time_enough(
CONF_SECURITY_MIN_ON_PERCENT: 0.3, CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor", CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
CONF_WINDOW_ACTION: CONF_WINDOW_TURN_OFF,
}, },
) )
@@ -242,7 +247,7 @@ async def test_window_management_time_enough(
@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_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state): async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
"""Test the Window management""" """Test the auto Window management with fast slope down"""
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
@@ -295,6 +300,7 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
assert entity.target_temperature == 21 assert entity.target_temperature == 21
assert entity.window_state is STATE_OFF assert entity.window_state is STATE_OFF
assert entity.is_window_auto_enabled is True
# Initialize the slope algo with 2 measurements # Initialize the slope algo with 2 measurements
event_timestamp = now + timedelta(minutes=1) event_timestamp = now + timedelta(minutes=1)
@@ -431,6 +437,125 @@ async def test_window_auto_fast(hass: HomeAssistant, skip_hass_states_is_state):
entity.remove_thermostat() entity.remove_thermostat()
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_window_auto_fast_and_sensor(
hass: HomeAssistant, skip_hass_states_is_state
):
"""Test that the auto-window detection algorithm is deactivated if a window sensor is provided"""
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 21,
CONF_USE_WINDOW_FEATURE: True,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_SENSOR: "binary_sensor.fake_window_sensor",
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_MAX_DURATION: 10, # Should be 0 for test
},
)
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
tz = get_tz(hass) # pylint: disable=invalid-name
now = datetime.now(tz)
tpi_algo = entity._prop_algorithm
assert tpi_algo
await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.target_temperature == 21
assert entity.window_state is STATE_OFF
assert entity.is_window_auto_enabled is False
# Initialize the slope algo with 2 measurements
event_timestamp = now + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp)
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp)
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp)
# Make the temperature down
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
) as mock_heater_on, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
return_value=True,
):
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp)
# The heater don't turns on
assert mock_send_event.call_count == 0
assert entity.is_device_active is True
assert entity.last_temperature_slope == 0.0
assert entity._window_auto_algo.is_window_open_detected() is False
assert entity._window_auto_algo.is_window_close_detected() is False
assert entity.hvac_mode is HVACMode.HEAT
# send one degre down in one minute
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
) as mock_heater_on, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
return_value=True,
):
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 18, event_timestamp)
assert mock_send_event.call_count == 0
assert mock_heater_on.call_count == 0 # no change in heater
assert mock_heater_off.call_count == 0 # no change in heater
assert entity.last_temperature_slope == -6.24
# The window open should be detected (but not used)
# because we need to calculate the slope anyway, we have the algorithm running
assert entity._window_auto_algo.is_window_open_detected() is True
assert entity._window_auto_algo.is_window_close_detected() is False
assert entity.window_auto_state == STATE_OFF
assert entity.hvac_mode is HVACMode.HEAT
# Clean the entity
entity.remove_thermostat()
@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_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_state): async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_state):
@@ -461,7 +586,7 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
CONF_SECURITY_MIN_ON_PERCENT: 0.3, CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6, CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6, CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6,
CONF_WINDOW_AUTO_MAX_DURATION: 0, # Should be 0 for test CONF_WINDOW_AUTO_MAX_DURATION: 1, # 0 will deactivate window auto detection
}, },
) )
@@ -484,8 +609,9 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
assert entity.target_temperature == 21 assert entity.target_temperature == 21
assert entity.window_state is STATE_OFF assert entity.window_state is STATE_OFF
assert entity.is_window_auto_enabled is True
# Initialize the slope algo with 2 measurements # 1. Initialize the slope algo with 2 measurements
event_timestamp = now + timedelta(minutes=1) event_timestamp = now + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp) await send_temperature_change_event(entity, 19, event_timestamp)
event_timestamp = event_timestamp + timedelta(minutes=1) event_timestamp = event_timestamp + timedelta(minutes=1)
@@ -493,7 +619,7 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
event_timestamp = event_timestamp + timedelta(minutes=1) event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp) await send_temperature_change_event(entity, 19, event_timestamp)
# Make the temperature down # 2. Make the temperature down
with patch( with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch( ) as mock_send_event, patch(
@@ -513,7 +639,7 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
assert entity._window_auto_algo.is_window_close_detected() is False assert entity._window_auto_algo.is_window_close_detected() is False
assert entity.hvac_mode is HVACMode.HEAT assert entity.hvac_mode is HVACMode.HEAT
# send one degre down in one minute # 3. send one degre down in one minute
with patch( with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch( ) as mock_send_event, patch(
@@ -549,12 +675,14 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
assert entity.window_auto_state == STATE_ON assert entity.window_auto_state == STATE_ON
assert entity.hvac_mode is HVACMode.OFF assert entity.hvac_mode is HVACMode.OFF
# This is to avoid that the slope stayx under 6, else we will reactivate the window immediatly # 4. This is to avoid that the slope stay under 6, else we will reactivate the window immediatly
event_timestamp = event_timestamp + timedelta(minutes=1) event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp, sleep=False) dearm_window_auto = await send_temperature_change_event(
entity, 19, event_timestamp, sleep=False
)
assert entity.last_temperature_slope > -6.0 assert entity.last_temperature_slope > -6.0
# Waits for automatic disable # 5. Waits for automatic disable
with patch( with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event" "custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch( ) as mock_send_event, patch(
@@ -563,7 +691,8 @@ async def test_window_auto_auto_stop(hass: HomeAssistant, skip_hass_states_is_st
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active", "custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
return_value=False, return_value=False,
): ):
await asyncio.sleep(0.3) # simulate the expiration of the delay
await dearm_window_auto(None)
assert entity.hvac_mode is HVACMode.HEAT assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST assert entity.preset_mode is PRESET_BOOST
@@ -698,7 +827,6 @@ async def test_window_auto_no_on_percent(
entity.remove_thermostat() entity.remove_thermostat()
# PR - Adding Window Bypass
@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_window_bypass(hass: HomeAssistant, skip_hass_states_is_state): async def test_window_bypass(hass: HomeAssistant, skip_hass_states_is_state):
@@ -751,6 +879,7 @@ async def test_window_bypass(hass: HomeAssistant, skip_hass_states_is_state):
assert entity.target_temperature == 19 assert entity.target_temperature == 19
assert entity.window_state is STATE_OFF assert entity.window_state is STATE_OFF
assert entity.is_window_auto_enabled is False
# change temperature to force turning on the heater # change temperature to force turning on the heater
with patch( with patch(
@@ -867,7 +996,7 @@ async def test_window_auto_bypass(hass: HomeAssistant, skip_hass_states_is_state
CONF_SECURITY_MIN_ON_PERCENT: 0.3, CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6, CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6, CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6,
CONF_WINDOW_AUTO_MAX_DURATION: 0, # Should be 0 for test CONF_WINDOW_AUTO_MAX_DURATION: 1, # Should be > 0 to activate window_auto
}, },
) )
@@ -890,6 +1019,7 @@ async def test_window_auto_bypass(hass: HomeAssistant, skip_hass_states_is_state
assert entity.target_temperature == 21 assert entity.target_temperature == 21
assert entity.window_state is STATE_OFF assert entity.window_state is STATE_OFF
assert entity.is_window_auto_enabled
# Initialize the slope algo with 2 measurements # Initialize the slope algo with 2 measurements
event_timestamp = now + timedelta(minutes=1) event_timestamp = now + timedelta(minutes=1)
@@ -1081,3 +1211,694 @@ async def test_window_bypass_reactivate(hass: HomeAssistant, skip_hass_states_is
# Clean the entity # Clean the entity
entity.remove_thermostat() entity.remove_thermostat()
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_window_action_fan_only(hass: HomeAssistant, skip_hass_states_is_state):
"""Test the Window management with the fan_only option"""
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverClimateMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverClimateMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: True,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 1,
CONF_WINDOW_ACTION: CONF_WINDOW_FAN_ONLY,
# CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6,
# CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6,
# CONF_WINDOW_AUTO_MAX_DURATION: 1, # 0 will deactivate window auto detection
},
)
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
fake_underlying_climate = MockClimate(
hass=hass,
unique_id="mockUniqueId",
name="MockClimateName",
hvac_modes=[HVACMode.HEAT, HVACMode.COOL, HVACMode.FAN_ONLY],
)
# 1. intialize climate entity
with patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
return_value=fake_underlying_climate,
):
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.LOADED
entity: ThermostatOverClimate = search_entity(
hass, "climate.theoverclimatemockname", "climate"
)
assert entity
assert entity.is_over_climate is True
assert entity.window_action == CONF_WINDOW_FAN_ONLY
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 == 18
assert entity.window_state is STATE_OFF
# 2. Open the window, condition of time is satisfied, check the thermostat and heater turns off
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"homeassistant.helpers.condition.state", return_value=True
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
) as mock_underlying_set_hvac_mode:
event_timestamp = now - timedelta(minutes=2)
try_window_condition = await send_window_change_event(
entity, True, False, event_timestamp
)
await try_window_condition(None)
assert mock_send_event.call_count == 1
mock_send_event.assert_has_calls(
[
call.send_event(
EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.FAN_ONLY}
)
]
)
# The underlying should be in OFF hvac_mode
assert mock_underlying_set_hvac_mode.call_count == 1
mock_underlying_set_hvac_mode.assert_has_calls(
[
call.set_hvac_mode(HVACMode.FAN_ONLY),
]
)
assert entity.window_state == STATE_ON
# The underlying should be in FAN_ONLY hvac_mode
assert entity.hvac_mode is HVACMode.FAN_ONLY
assert entity._saved_hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_COMFORT
# 3. Close the window
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"homeassistant.helpers.condition.state", return_value=True
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
) as mock_underlying_set_hvac_mode:
event_timestamp = now - timedelta(minutes=1)
try_function = await send_window_change_event(
entity, False, True, event_timestamp, sleep=False
)
await try_function(None)
# Wait for initial delay of heater
await asyncio.sleep(0.3)
assert entity.window_state == STATE_OFF
assert mock_send_event.call_count == 1
mock_send_event.assert_has_calls(
[
call.send_event(
EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.HEAT}
),
],
any_order=False,
)
# The underlying should be in OFF hvac_mode
assert mock_underlying_set_hvac_mode.call_count == 1
mock_underlying_set_hvac_mode.assert_has_calls(
[
call.set_hvac_mode(HVACMode.HEAT),
]
)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_COMFORT
# Clean the entity
entity.remove_thermostat()
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_window_action_fan_only_ko(
hass: HomeAssistant, skip_hass_states_is_state
):
"""Test the Window management with the fan_only option but the underlyings doesn't have the FAN_ONLY mode
So the VTherm switch to OFF which is the fallback mode"""
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverClimateMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverClimateMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_CLIMATE,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 19,
CONF_USE_WINDOW_FEATURE: True,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 1,
CONF_WINDOW_ACTION: CONF_WINDOW_FAN_ONLY,
# CONF_WINDOW_AUTO_OPEN_THRESHOLD: 6,
# CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 6,
# CONF_WINDOW_AUTO_MAX_DURATION: 1, # 0 will deactivate window auto detection
},
)
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
fake_underlying_climate = MockClimate(
hass=hass,
unique_id="mockUniqueId",
name="MockClimateName",
hvac_modes=[HVACMode.HEAT, HVACMode.COOL, HVACMode.AUTO],
)
# 1. intialize climate entity
with patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
return_value=fake_underlying_climate,
):
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.LOADED
entity: ThermostatOverClimate = search_entity(
hass, "climate.theoverclimatemockname", "climate"
)
assert entity
assert entity.is_over_climate is True
assert entity.window_action == CONF_WINDOW_FAN_ONLY
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 == 18
assert entity.window_state is STATE_OFF
# 2. Open the window, condition of time is satisfied, check the thermostat and heater turns off
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"homeassistant.helpers.condition.state", return_value=True
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
) as mock_underlying_set_hvac_mode:
event_timestamp = now - timedelta(minutes=2)
try_window_condition = await send_window_change_event(
entity, True, False, event_timestamp
)
await try_window_condition(None)
assert mock_send_event.call_count == 1
mock_send_event.assert_has_calls(
[call.send_event(EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.OFF})]
)
assert entity.window_state == STATE_ON
assert entity.hvac_mode is HVACMode.OFF
# The underlying should be in OFF hvac_mode
assert mock_underlying_set_hvac_mode.call_count == 1
mock_underlying_set_hvac_mode.assert_has_calls(
[
call.set_hvac_mode(HVACMode.OFF),
]
)
assert entity._saved_hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_COMFORT
# 3. Close the window
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"homeassistant.helpers.condition.state", return_value=True
), patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingClimate.set_hvac_mode"
) as mock_underlying_set_hvac_mode:
event_timestamp = now - timedelta(minutes=1)
try_function = await send_window_change_event(
entity, False, True, event_timestamp, sleep=False
)
await try_function(None)
# Wait for initial delay of heater
await asyncio.sleep(0.3)
assert entity.window_state == STATE_OFF
assert mock_send_event.call_count == 1
mock_send_event.assert_has_calls(
[
call.send_event(
EventType.HVAC_MODE_EVENT, {"hvac_mode": HVACMode.HEAT}
),
],
any_order=False,
)
# The underlying should be in OFF hvac_mode
assert mock_underlying_set_hvac_mode.call_count == 1
mock_underlying_set_hvac_mode.assert_has_calls(
[
call.set_hvac_mode(HVACMode.HEAT),
]
)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_COMFORT
# Clean the entity
entity.remove_thermostat()
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_window_action_eco_temp(hass: HomeAssistant, skip_hass_states_is_state):
"""Test the Window management with the eco_temp option"""
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 21,
CONF_USE_WINDOW_FEATURE: True,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_MAX_DURATION: 10, # Should be 0 for test
CONF_WINDOW_ACTION: CONF_WINDOW_ECO_TEMP,
},
)
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
tz = get_tz(hass) # pylint: disable=invalid-name
now = datetime.now(tz)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
assert entity.target_temperature == 21
assert entity.window_state is STATE_OFF
assert entity.is_window_auto_enabled is True
# 1. Initialize the slope algo with 2 measurements
event_timestamp = now + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp)
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp)
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp)
# 2. Make the temperature down
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
) as mock_heater_on, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
return_value=True,
):
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp)
# The heater turns on
assert mock_send_event.call_count == 0
assert entity.is_device_active is True
assert entity.hvac_mode is HVACMode.HEAT
assert entity.window_state is STATE_OFF
assert entity.window_auto_state is STATE_OFF
# 3. send one degre down in one minute
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
) as mock_heater_on, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
return_value=True,
):
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 18, event_timestamp)
# The heater turns on
assert mock_send_event.call_count == 1
assert mock_heater_on.call_count == 0
assert mock_heater_off.call_count == 0
assert entity.last_temperature_slope == -6.24
assert entity.window_auto_state == STATE_ON
assert entity.window_state == STATE_OFF
# No change on HVACMode
assert entity.hvac_mode is HVACMode.HEAT
# No change on preset
assert entity.preset_mode is PRESET_BOOST
# The eco temp
assert entity.target_temperature == 17
mock_send_event.assert_has_calls(
[
call.send_event(
EventType.WINDOW_AUTO_EVENT,
{"type": "start", "cause": "slope alert", "curve_slope": -6.24},
),
],
any_order=True,
)
# 4. send another 0.1 degre in one minute -> no change
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
) as mock_heater_on, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
new_callable=PropertyMock,
return_value=False,
):
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 17.9, event_timestamp)
# The heater turns on
assert mock_send_event.call_count == 0
assert mock_heater_on.call_count == 0
assert mock_heater_off.call_count == 0
assert round(entity.last_temperature_slope, 3) == -7.49
assert entity.window_auto_state == STATE_ON
assert entity.hvac_mode is HVACMode.HEAT
# No change on preset
assert entity.preset_mode is PRESET_BOOST
# The eco temp
assert entity.target_temperature == 17
# 5. send another plus 1.1 degre in one minute -> restore state
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
) as mock_heater_on, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
new_callable=PropertyMock,
return_value=False,
):
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp)
# The heater turns on
assert mock_send_event.call_count == 1
mock_send_event.assert_has_calls(
[
call.send_event(
EventType.WINDOW_AUTO_EVENT,
{
"type": "end",
"cause": "end of slope alert",
"curve_slope": 0.42,
},
),
],
any_order=True,
)
assert mock_heater_on.call_count == 0
assert mock_heater_off.call_count == 0
assert entity.last_temperature_slope == 0.42
assert entity.window_auto_state == STATE_OFF
assert entity.hvac_mode is HVACMode.HEAT
# No change on preset
assert entity.preset_mode is PRESET_BOOST
# The eco temp
assert entity.target_temperature == 21
# Clean the entity
entity.remove_thermostat()
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_window_action_frost_temp(hass: HomeAssistant, skip_hass_states_is_state):
"""Test the Window management with the frost_temp option"""
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
unique_id="uniqueId",
data={
CONF_NAME: "TheOverSwitchMockName",
CONF_THERMOSTAT_TYPE: CONF_THERMOSTAT_SWITCH,
CONF_TEMP_SENSOR: "sensor.mock_temp_sensor",
CONF_EXTERNAL_TEMP_SENSOR: "sensor.mock_ext_temp_sensor",
CONF_CYCLE_MIN: 5,
CONF_TEMP_MIN: 15,
CONF_TEMP_MAX: 30,
"eco_temp": 17,
"comfort_temp": 18,
"boost_temp": 21,
"frost_temp": 10,
CONF_USE_WINDOW_FEATURE: True,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: False,
CONF_USE_PRESENCE_FEATURE: False,
CONF_HEATER: "switch.mock_switch",
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_TPI_COEF_INT: 0.3,
CONF_TPI_COEF_EXT: 0.01,
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 0.1,
CONF_WINDOW_AUTO_MAX_DURATION: 10, # Should be 0 for test
CONF_WINDOW_ACTION: CONF_WINDOW_FROST_TEMP,
},
)
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
)
assert entity
tz = get_tz(hass) # pylint: disable=invalid-name
now = datetime.now(tz)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_BOOST)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is None
assert entity.target_temperature == 21
assert entity.window_state is STATE_OFF
assert entity.is_window_auto_enabled is True
# 1. Initialize the slope algo with 2 measurements
event_timestamp = now + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp)
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp)
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp)
# 2. Make the temperature down
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
) as mock_heater_on, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
return_value=True,
):
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp)
# The heater turns on
assert mock_send_event.call_count == 0
assert entity.is_device_active is True
assert entity.hvac_mode is HVACMode.HEAT
assert entity.window_state is STATE_OFF
assert entity.window_auto_state is STATE_OFF
# 3. send one degre down in one minute
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
) as mock_heater_on, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
return_value=True,
):
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 18, event_timestamp)
# The heater turns on
assert mock_send_event.call_count == 1
assert mock_heater_on.call_count == 0
assert mock_heater_off.call_count == 0
assert entity.last_temperature_slope == -6.24
assert entity.window_auto_state == STATE_ON
assert entity.window_state == STATE_OFF
# No change on HVACMode
assert entity.hvac_mode is HVACMode.HEAT
# No change on preset
assert entity.preset_mode is PRESET_BOOST
# The eco temp
assert entity.target_temperature == 10
mock_send_event.assert_has_calls(
[
call.send_event(
EventType.WINDOW_AUTO_EVENT,
{"type": "start", "cause": "slope alert", "curve_slope": -6.24},
),
],
any_order=True,
)
# 4. send another 0.1 degre in one minute -> no change
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
) as mock_heater_on, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
new_callable=PropertyMock,
return_value=False,
):
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 17.9, event_timestamp)
# The heater turns on
assert mock_send_event.call_count == 0
assert mock_heater_on.call_count == 0
assert mock_heater_off.call_count == 0
assert round(entity.last_temperature_slope, 3) == -7.49
assert entity.window_auto_state == STATE_ON
assert entity.hvac_mode is HVACMode.HEAT
# No change on preset
assert entity.preset_mode is PRESET_BOOST
# The eco temp
assert entity.target_temperature == 10
# 5. send another plus 1.1 degre in one minute -> restore state
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_on"
) as mock_heater_on, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.turn_off"
) as mock_heater_off, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
new_callable=PropertyMock,
return_value=False,
):
event_timestamp = event_timestamp + timedelta(minutes=1)
await send_temperature_change_event(entity, 19, event_timestamp)
# The heater turns on
assert mock_send_event.call_count == 1
mock_send_event.assert_has_calls(
[
call.send_event(
EventType.WINDOW_AUTO_EVENT,
{
"type": "end",
"cause": "end of slope alert",
"curve_slope": 0.42,
},
),
],
any_order=True,
)
assert mock_heater_on.call_count == 0
assert mock_heater_off.call_count == 0
assert entity.last_temperature_slope == 0.42
assert entity.window_auto_state == STATE_OFF
assert entity.hvac_mode is HVACMode.HEAT
# No change on preset
assert entity.preset_mode is PRESET_BOOST
# The eco temp
assert entity.target_temperature == 21
# Clean the entity
entity.remove_thermostat()