Compare commits

...

53 Commits

Author SHA1 Message Date
Jean-Marc Collin
cc7b7f43b9 Fix test and default value for minimal_deactivation_delay 2025-02-12 14:38:50 +00:00
Jean-Marc Collin
2a125db73f Fix fr.json translations for minimal_deactivation_delay 2025-02-12 13:46:16 +00:00
Tomasz Madycki
bb4d3a59a9 Add minimal_deactivation_delay (#905)
* Add minimal_deactivation_delay

In some cases users may want to keep the equipment on if it will deactivate for a short time.
This could help reduce wear on gas boilers.

* fix(prop_algorithm): correct the log message for minimal_deactivation_delay

* fix(translations): correct the translations for minimal_deactivation_delay
2025-02-12 14:41:13 +01:00
Jean-Marc Collin
15a48105b0 Add quickstart over_climate 2025-02-10 17:44:25 +00:00
Jean-Marc Collin
ea0d66f0c6 Vack to HA 2025.1.4. It doesn't work on Github. 2025-02-10 16:53:28 +00:00
adi90x
641801084d Correct window bypass (#899) 2025-02-10 17:44:28 +01:00
adi90x
1576e9c189 Add bypass to the window open check and correct description (#897) 2025-02-10 17:42:06 +01:00
adi90x
313fa26160 Save & Recover Window Bypass state (#898)
* Remove windows_bypass state from unrecorded

* Recover bypass_state
2025-02-10 17:29:53 +01:00
Jean-Marc Collin
832ea78a12 FIX select_option 2025-02-10 08:33:40 +01:00
Jean-Marc Collin
5f167af331 Fix select_option 2025-02-10 08:33:06 +01:00
Jean-Marc Collin
c20e641ac1 Release 7.2.0- Heating is inverted on over_switch with inverted commands ? (#891)
* Release 7.2.0- Heating is inverted on `over_switch` with inverted commands ?
Fixes #889

* Release

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-02-08 11:56:24 +01:00
Jean-Marc Collin
87064882a3 Fix warning 2025-02-08 09:39:51 +00:00
Jean-Marc Collin
1c0a7cb4e8 homeassistant==2025.2.1 2025-02-08 09:35:06 +00:00
Jean-Marc Collin
b1f2fcd66d Fix frost_protection for Nodon 2025-02-05 18:47:08 +00:00
Jean-Marc Collin
a0cdaa8a7f Issue #872 (#886)
* ConfigFlow (translation to change)

* +1

* With implemn in thermostat_switch (not finished)

* GUI fixe

* +1

* Select Working

* Test input in config_flow ok

* documentation

* Add github copilot
Add first test ok for UnderlyingSwitch

* All tests ok

* Fix warnings

* Fix All tests ok

* Translations

* safety

* Add quick-start documentation

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-02-04 14:35:41 +01:00
Jean-Marc Collin
ebc2d8b6ac Release 7.1.6 2025-01-25 10:57:47 +00:00
Jean-Marc Collin
cf4cc32b13 Typos 2025-01-25 10:56:42 +00:00
Jean-Marc Collin
2523fc74c2 Update over-climate.md
Add precision on "use internal device temp"
2025-01-25 11:41:11 +01:00
Jean-Marc Collin
87de91c2c5 Update over-climate.md
Add precision to "use internal device temp"
2025-01-25 11:40:10 +01:00
Jean-Marc Collin
a3b6f66f1b Issue #846 - swicth Eco preset after Window close event (#868)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-25 11:09:34 +01:00
Jean-Marc Collin
8cf09e5254 Update self-regulation.md 2025-01-25 09:22:13 +01:00
Jean-Marc Collin
0b67226666 Update self-regulation.md 2025-01-25 09:21:24 +01:00
Jean-Marc Collin
812bb19e10 Update self-regulation.md
Precision on min opening values
2025-01-25 09:18:28 +01:00
Eric Scherlinger
be3012af71 Fixes on FR translation (#851) 2025-01-21 21:02:51 +01:00
Jean-Marc Collin
05fe2055e2 [Feature Request] - Pwoer shedding should prevent all VTherm to start at the same time (#845)
Fixes #844

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-19 20:45:37 +01:00
Jean-Marc Collin
8743872c09 Add config.yml into ISSUE_TEMPLATE 2025-01-19 16:30:16 +00:00
Jean-Marc Collin
c4a1631d29 [Feature Request] - Resend state to underlying device is device switch from Unknown state. (#841)
Fixes #829

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-18 18:02:40 +01:00
Jean-Marc Collin
d1ef83f422 hvac_off_reason could stay at auto_start_stop when the VTherm is 'on' (hvac_mode=Heat) (#840)
Fixes #828

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-18 16:34:44 +01:00
Jean-Marc Collin
42ac2b0f98 #823 - window configuration don't hold on VTherm configuration (#839)
* Improve documentation

* Fix should be not possible to validate a VTherm without underlyings

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-18 13:01:31 +01:00
Jean-Marc Collin
e71d8dba86 Issue #820 - improve power shedding algorithm (#821)
* Issue #820 - improve power shedding algorithm

* Fix testu

* Release

* Fix cannot turn-off a VTherm in overpowering mode

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-15 07:48:45 +01:00
Jean-Marc Collin
3e96247b63 Add messages 2025-01-14 09:15:14 +00:00
Jean-Marc Collin
05e31358a4 Documentation 2025-01-12 17:06:28 +00:00
Jean-Marc Collin
3af0318c2f Fix number selection for TPI in ConfigFlow 2025-01-12 16:22:01 +00:00
Jean-Marc Collin
12b67ba3e0 issue #804 - addition 2025-01-12 11:13:04 +00:00
Jean-Marc Collin
1d675f22c7 Issue #779 - error in ValveOpenPercent sensor at startup (#814)
* issue #779 - error in ValveOpenPercent sensor at startup

* fix log

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-12 11:26:32 +01:00
Jean-Marc Collin
3fd9ffe93d Issue #804 - cannot set preset when follow is activated (#812)
* Issue #804 - cannot set preset when follow is activated

* Release

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-12 11:04:39 +01:00
Jean-Marc Collin
43d8e5eb3c issue #807 - send temperature only if in the feature (#811)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-12 09:51:36 +01:00
Jean-Marc Collin
f8050e2ed7 typo 2025-01-11 21:24:47 +00:00
Jean-Marc Collin
13443402d0 Issue #805 - improve valve regulation config texts (#810)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-11 22:08:31 +01:00
Jean-Marc Collin
4d7bc1b5b3 Issue_791-add-window-turn-on-delay (#809)
* HA 2025.1.2

* Developped all tests ok

* Integration tests

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-11 21:06:13 +01:00
Jean-Marc Collin
2b164d3dab Change step for coef_ext 2025-01-08 21:55:56 +00:00
Jean-Marc Collin
0333c403f8 Add power unit precision 2025-01-08 10:47:51 +01:00
Jean-Marc Collin
ae1a86f484 Add power unit precision 2025-01-08 10:46:25 +01:00
Jean-Marc Collin
2db574da42 issue #759 - Frost temperature setpoint doesn't update with presence (#794)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-08 09:01:25 +01:00
Jean-Marc Collin
d236cc8fbb issue #789 - auto-start-stop precision in documentation 2025-01-08 07:26:41 +00:00
Jean-Marc Collin
a637c2841c issue #790 - hvac-off-reason is not restored after restart (#793)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-08 08:22:09 +01:00
Jean-Marc Collin
ee3b803db1 Release number 2025-01-05 17:11:30 +00:00
Jean-Marc Collin
22b2b965c1 Issue_766-enhance_power_management (#778)
* First implem + tests (not finished)

* With tests of calculate_shedding ok

* Commit for rebase

* All tests ok for central_feature_power_manager

* All tests not ok

* All tests ok

* integrattion tests - Do startup works

* enhance the overpowering algo if current_power > max_power

* Change shedding calculation delay to 20 sec (vs 60 sec)

* Integration tests ok

* Fix overpowering is set even if other heater have on_percent = 0

* Fix too much shedding in over_climate

* Add logs

* Add temporal filter for calculate_shedding
Add restore overpowering state at startup

* Fix restore overpowering_state

* Removes poweer_entity_id from vtherm non central config

* Release

* Add Sonoff TRVZB in creation.md

* Add comment on Sonoff TRVZB Closing degree

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-05 18:10:18 +01:00
Jean-Marc Collin
9c8a965dba Update creation.md 2025-01-05 15:32:01 +01:00
zepala
68e05bef31 Change percent_open calculation method (#772)
* Change percent_open calculation method

Modified 'percent_open' calculation method :

  - Before : If < min_opening_degree then =min_opening_degree
  - Now : Each % step is calculated (100 - min_opening_degree)/100

* Code aesthetics

* Code aesthetics

* Updated test_overclimate_valve.py
2025-01-04 08:33:04 +01:00
Fabio Coatti
e727c0628e (chore) Lint base_* files (#769)
Removes typos and linter warnings for lines too long.
Line lenght is 88, as per black default.
2025-01-03 08:20:23 +01:00
Jean-Marc Collin
9e52c843bc Issue-739-refactor-to-modularize (#742)
* Refactor Presence Feature

* Add PresenceFeatureManager ok

* Python 3.13

* Fix presence test

* Refactor power feature

* Add Motion manager. All tests ok

* Tests ok. But tests are not complete

* All tests Window Feature Manager ok.

* All windows tests ok

* Fix all testus with feature_window_manager ok

* Add test_auto_start_stop feature manager. All tests ok

* Add safety feature_safety_manager
Rename config attribute from security_ to safety_

* Documentation and release

* Add safety manager direct tests

* Typo

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2025-01-01 16:30:18 +01:00
Romain Van Vooren
d2a94d33e8 Add installation link (#762) 2024-12-31 19:35:26 +01:00
122 changed files with 8927 additions and 6601 deletions

View File

@@ -1,2 +1,2 @@
FROM mcr.microsoft.com/devcontainers/python:1-3.12
RUN apt update && apt install -y ffmpeg
FROM mcr.microsoft.com/devcontainers/python:1-3.13
RUN apt update && apt install -y ffmpeg libturbojpeg0-dev

View File

@@ -8,12 +8,13 @@ recorder:
domains:
- input_boolean
- input_number
- input_select
- switch
- climate
- sensor
- binary_sensor
- number
- input_select
- select
- versatile_thermostat
logger:
@@ -190,6 +191,9 @@ input_boolean:
fake_valve_sonoff_trvzb2:
name: Valve Sonoff TRVZB2
icon: mdi:valve
fake_inversed_heater:
name: Inversed Heater
icon: mdi:radiator-off
climate:
- platform: generic_thermostat
@@ -243,6 +247,11 @@ climate:
heater: input_boolean.fake_valve_sonoff_trvzb2
target_sensor: input_number.fake_temperature_sensor1
ac_mode: false
- platform: generic_thermostat
name: Underlying switch climate
heater: input_boolean.fake_heater_switch2
target_sensor: input_number.fake_temperature_sensor1
ac_mode: false
input_datetime:
fake_last_seen:
@@ -308,6 +317,21 @@ switch:
option: comfort-2
target:
entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode
- platform: template
switches:
fake_inversed_switch:
friendly_name: "A fake inversed switch"
value_template: "{{ is_state('input_boolean.fake_inversed_heater', 'on') }}"
turn_on:
action: input_boolean.turn_on
data: {}
target:
entity_id: input_boolean.fake_inversed_heater
turn_off:
action: input_boolean.turn_off
data: {}
target:
entity_id: input_boolean.fake_inversed_heater
frontend:
extra_module_url:

View File

@@ -38,6 +38,8 @@
"github.vscode-github-actions",
"azuretools.vscode-docker",
"huizhou.githd",
"github.copilot",
"github.copilot-chat"
],
"settings": {
"files.eol": "\n",
@@ -54,6 +56,7 @@
"python.analysis.autoSearchPaths": true,
"pylint.lintOnChange": false,
"python.formatting.provider": "black",
"python.formatting.blackArgs": ["--line-length", "180"],
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"editor.formatOnPaste": false,
"editor.formatOnSave": true,

2
.devcontainer/pytest.ini Normal file
View File

@@ -0,0 +1,2 @@
[pytest]
asyncio_default_fixture_loop_scope = function

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -4,7 +4,10 @@ about: Create a report to help us improve
---
> Please read carefuly this instructions and fill this form before writing an issue. It helps me to help you.
# Read this carefully
> Please read carefully this instructions and fill this form before writing an issue. It helps me to help you.
> If you choose to not follow this template, you accept to have no answer from the author. The tag on the issue 'Template not respected' means you don't respect this template. Potentially, you will not have a relevant answer.
<!-- 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. -->
@@ -12,7 +15,7 @@ about: Create a report to help us improve
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).
Check also in the [Troubleshooting](#troubleshooting) paragrah of the README if the aswer is not already given.
Check also in the [Troubleshooting] paragrah of the README if the aswer is not already given.
Issues not containing the minimum requirements will be closed:
@@ -69,10 +72,10 @@ motion_state: 'off'
overpowering_state: false
presence_state: 'on'
window_auto_state: false
window_bypass_state: false
security_delay_min: 2
security_min_on_percent: 0.5
security_default_on_percent: 0.1
is_window_bypass: false
safety_delay_min: 2
safety_min_on_percent: 0.5
safety_default_on_percent: 0.1
last_temperature_datetime: '2023-11-05T00:48:54.873157+01:00'
last_ext_temperature_datetime: '2023-11-05T00:48:53.240122+01:00'
security_state: true

2
.pylintrc Normal file
View File

@@ -0,0 +1,2 @@
[FORMAT]
max-line-length=180

View File

@@ -13,15 +13,13 @@ Ce composant personnalisé pour Home Assistant est une mise à niveau et une ré
# Quoi de neuf ?
![Nouveau](images/new-icon.png)
> * **Release 6.8**:
> * **Release 7.2**:
>
> Ajout d'une nouvelle méthode de régulation pour les Versatile Thermostat de type `over_climate`. Cette méthode nommée 'Contrôle direct de la vanne' permet de contrôler directement la vanne d'un TRV et éventuellement un décalage pour calibrer le thermomètre interne de votre TRV. Cette nouvelle méthode a été testée avec des Sonoff TRVZB et généralisée pour d'autre type de TRV pour lesquels la vanne est directement commandable via des entités de type `number`.
> - Prise en compte native des équipements pilotable via une entité de type `select` (ou `input_select`) ou `climate` pour des _VTherm_ de type `over_switch`. Cette évolution rend obsolète, la création de switch virtuels pour l'intégration des Nodon ou Heaty ou eCosy ... etc. Plus d'informations [ici](documentation/fr/over-switch.md#la-personnalisation-des-commandes).
>
> Plus d'informations [ici](documentation/fr/over-climate.md) et [ici](documentation/fr/self-regulation.md).
> - Lien vers la documentation : cette version 7.2 expérimente des liens vers la documentation depuis les pages de configuration. Le lien est accessible via l'icone [![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/over-switch.md#configuration). Elle est expérimentée sur certaines pages de la configuration.
>
> * **Refonte de la documentation**:
>
> Avec toutes les évolutions réalisées depuis le début de l'intégration, la documentation nécessitait une profonde re-organisation, c'est chose faite sur cette version. Tous vos retours sur cette nouvelle organisation seront les bienvenus.
> - Ajout d'un chapitre dans la documentation nommé 'Démarrage rapide' permettant de mettre en oeuvre rapidement un _VTherm_ en fonction de votre équipement. La page est [ici](documentation/quick-start.md)
# 🍻 Merci pour les bières [buymecoffee](https://www.buymeacoffee.com/jmcollin78) 🍻
@@ -35,31 +33,44 @@ Un grand merci à tous mes fournisseurs de bières pour leurs dons et leurs enco
_AC_ : Air conditionné. Un équipement est AC si il fait du froid. Les températures sont alors inversées : Eco est plus chaud que Confort qui est plus chaud que Boost. Les algorithmes tiennent compte de cette information.
_EMA_ : Exponential Moving Average. Utilisé pour lisser les mesures de températures de capteur. Elle correspond à une moyenne glissante de la température de la pièce. Elle est utilisée pour calculer la pente de la courbe de température (slope) qui serait trop instable sur la courbe brute.
_slope_ : la pente de la courbe de température. Elle est mesurée en °(C ou K)/h. Elle est positive si la température augmente et négative si elle diminue. Cette pente est calculée sur l'_EMA_
_PAC_ : Pompe à chaleur
_HA_ : Home Assistant
_sous-jacent_ : l'équipement controlé par _VTherm_
# Documentation
La documentation est maintenant découpée en plusieurs pages pour faciliter la lecture et la recherche d'informations :
1. [présentation](documentation/fr/presentation.md),
2. [choisir un type de VTherm](documentation/fr/creation.md),
3. [les attributs de base](documentation/fr/base-attributes.md)
3. [configurer un VTherm sur un `switch`](documentation/fr/over-switch.md)
3. [configurer un VTherm sur un `climate`](documentation/fr/over-climate.md)
3. [configurer un VTherm sur une vanne](documentation/fr/over-valve.md)
4. [les pré-régages (preset)](documentation/fr/feature-presets.md)
5. [la gestion des ouvertures](documentation/fr/feature-window.md)
6. [la gestion de la présence](documentation/fr/feature-presence.md)
7. [la gestion de mouvement](documentation/fr/feature-motion.md)
8. [la gestion de la puissance](documentation/fr/feature-power.md)
9. [l'auto start and stop](documentation/fr/feature-auto-start-stop.md)
10. [la contrôle centralisé de tous vos VTherms](documentation/fr/feature-central-mode.md)
11. [la commande du chauffage central](documentation/fr/feature-central-boiler.md)
12. [aspects avancés, mode sécurité](documentation/fr/feature-advanced.md)
12. [l'auto-régulation](documentation/fr/self-regulation.md)
13. [exemples de réglages](documentation/fr/tuning-examples.md)
14. [les différents algorithmes](documentation/fr/algorithms.md)
15. [documentation de référence](documentation/fr/reference.md)
16. [exemple de réglages](documentation/fr/tuning-examples.md)
17. [dépannage](documentation/fr/troubleshooting.md)
18. [notes de version](documentation/fr/releases.md)
2. [Installation](documentation/fr/installation.md),
3. [Démarrage rapide](documentation/fr/quick-start.md)
4. [choisir un type de VTherm](documentation/fr/creation.md),
5. [les attributs de base](documentation/fr/base-attributes.md)
6. [configurer un VTherm sur un `switch`](documentation/fr/over-switch.md)
7. [configurer un VTherm sur un `climate`](documentation/fr/over-climate.md)
8. [configurer un VTherm sur une vanne](documentation/fr/over-valve.md)
9. [les pré-régages (preset)](documentation/fr/feature-presets.md)
10. [la gestion des ouvertures](documentation/fr/feature-window.md)
11. [la gestion de la présence](documentation/fr/feature-presence.md)
12. [la gestion de mouvement](documentation/fr/feature-motion.md)
13. [la gestion de la puissance](documentation/fr/feature-power.md)
14. [l'auto start and stop](documentation/fr/feature-auto-start-stop.md)
15. [la contrôle centralisé de tous vos VTherms](documentation/fr/feature-central-mode.md)
16. [la commande du chauffage central](documentation/fr/feature-central-boiler.md)
17. [aspects avancés, mode sécurité](documentation/fr/feature-advanced.md)
18. [l'auto-régulation](documentation/fr/self-regulation.md)
19. [exemples de réglages](documentation/fr/tuning-examples.md)
20. [les différents algorithmes](documentation/fr/algorithms.md)
21. [documentation de référence](documentation/fr/reference.md)
22. [exemple de réglages](documentation/fr/tuning-examples.md)
23. [dépannage](documentation/fr/troubleshooting.md)
24. [notes de version](documentation/fr/releases.md)
# Quelques résultats

View File

@@ -13,15 +13,13 @@ This custom component for Home Assistant is an upgrade and a complete rewrite of
# What's new?
![New](images/new-icon.png)
> * **Release 6.8**:
# What's New?
![New](images/new-icon.png)
> * **Release 7.2**:
>
> Added a new regulation method for `over_climate` type Versatile Thermostats. This method, called 'Direct control of valve', allows direct control of a TRV valve and optionally a calibration offset for the internal thermometer of your TRV. This new method has been tested with Sonoff TRVZB and generalized for other TRV types whose valves can be directly controlled via `number` entities.
> - Native support for devices controlled via a `select` (or `input_select`) or `climate` entity for _VTherm_ of type `over_switch`. This update makes the creation of virtual switches obsolete for integrating Nodon, Heaty, eCosy, etc. More information [here](documentation/en/over-switch.md#command-customization).
>
> More information [here](documentation/en/over-climate.md) and [here](documentation/en/self-regulation.md).
>
> * **Documentation overhaul**:
>
> With all the developments since the start of the integration, the documentation needed a major reorganization, which has been completed in this version. All feedback on this new organization is welcome.
> - Documentation links: Version 7.2 introduces experimental links to the documentation from the configuration pages. The link is accessible via the icon [![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/over-switch.md#configuration). This feature is currently tested on some configuration pages.
# 🍻 Thanks for the beers [buymecoffee](https://www.buymeacoffee.com/jmcollin78) 🍻
A big thank you to all my beer sponsors for their donations and encouragements. It means a lot to me and motivates me to keep going! If this integration has saved you money, buy me a beer in return; I would greatly appreciate it!
@@ -34,31 +32,43 @@ A big thank you to all my beer sponsors for their donations and encouragements.
_AC_: Air Conditioning. An AC device cools instead of heats. Temperatures are reversed: Eco is warmer than Comfort, which is warmer than Boost. The algorithms take this information into account.
_EMA_: Exponential Moving Average. Used to smooth sensor temperature measurements. It represents a moving average of the room's temperature and is used to calculate the slope of the temperature curve, which would be too unstable on the raw data.
_slope_: The slope of the temperature curve, measured in ° (C or K)/h. It is positive when the temperature increases and negative when it decreases. This slope is calculated based on the _EMA_.
_PAC_: Heat pump
_HA_: Home Assistant
_underlying_: the device controlled by _VTherm_
# Documentation
The documentation is now divided into several pages for easier reading and searching:
1. [Introduction](documentation/en/presentation.md),
2. [Choosing a VTherm type](documentation/en/creation.md),
3. [Basic attributes](documentation/en/base-attributes.md)
3. [Configuring a VTherm on a `switch`](documentation/en/over-switch.md)
3. [Configuring a VTherm on a `climate`](documentation/en/over-climate.md)
3. [Configuring a VTherm on a valve](documentation/en/over-valve.md)
4. [Presets](documentation/en/feature-presets.md)
5. [Window management](documentation/en/feature-window.md)
6. [Presence management](documentation/en/feature-presence.md)
7. [Motion management](documentation/en/feature-motion.md)
8. [Power management](documentation/en/feature-power.md)
9. [Auto start and stop](documentation/en/feature-auto-start-stop.md)
10. [Centralized control of all VTherms](documentation/en/feature-central-mode.md)
11. [Central heating control](documentation/en/feature-central-boiler.md)
12. [Advanced aspects, security mode](documentation/en/feature-advanced.md)
12. [Self-regulation](documentation/en/self-regulation.md)
13. [Tuning examples](documentation/en/tuning-examples.md)
14. [Algorithms](documentation/en/algorithms.md)
15. [Reference documentation](documentation/en/reference.md)
16. [Tuning examples](documentation/en/tuning-examples.md)
17. [Troubleshooting](documentation/en/troubleshooting.md)
18. [Release notes](documentation/en/releases.md)
2. [Installation](documentation/en/installation.md),
3. [Démarrage rapide](documentation/en/quick-start.md)
4. [Choosing a VTherm type](documentation/en/creation.md),
5. [Basic attributes](documentation/en/base-attributes.md)
6. [Configuring a VTherm on a `switch`](documentation/en/over-switch.md)
7. [Configuring a VTherm on a `climate`](documentation/en/over-climate.md)
8. [Configuring a VTherm on a valve](documentation/en/over-valve.md)
9. [Presets](documentation/en/feature-presets.md)
10. [Window management](documentation/en/feature-window.md)
11. [Presence management](documentation/en/feature-presence.md)
12. [Motion management](documentation/en/feature-motion.md)
13. [Power management](documentation/en/feature-power.md)
14. [Auto start and stop](documentation/en/feature-auto-start-stop.md)
15. [Centralized control of all VTherms](documentation/en/feature-central-mode.md)
16. [Central heating control](documentation/en/feature-central-boiler.md)
17. [Advanced aspects, security mode](documentation/en/feature-advanced.md)
18. [Self-regulation](documentation/en/self-regulation.md)
19. [Tuning examples](documentation/en/tuning-examples.md)
20. [Algorithms](documentation/en/algorithms.md)
21. [Reference documentation](documentation/en/reference.md)
22. [Tuning examples](documentation/en/tuning-examples.md)
23. [Troubleshooting](documentation/en/troubleshooting.md)
24. [Release notes](documentation/en/releases.md)
# Some results
@@ -103,4 +113,4 @@ If you wish to contribute, please read the [contribution guidelines](CONTRIBUTIN
[license-shield]: https://img.shields.io/github/license/jmcollin78/versatile_thermostat.svg?style=for-the-badge
[maintenance-shield]: https://img.shields.io/badge/maintainer-Joakim%20Sørensen%20%40ludeeus-blue.svg?style=for-the-badge
[releases-shield]: https://img.shields.io/github/release/jmcollin78/versatile_thermostat.svg?style=for-the-badge
[releases]: https://github.com/jmcollin78/versatile_thermostat/releases
[releases]: https://github.com/jmcollin78/versatile_thermostat/releases

View File

@@ -29,6 +29,9 @@ from .const import (
CONF_AUTO_REGULATION_EXPERT,
CONF_SHORT_EMA_PARAMS,
CONF_SAFETY_MODE,
CONF_SAFETY_DELAY_MIN,
CONF_SAFETY_MIN_ON_PERCENT,
CONF_SAFETY_DEFAULT_ON_PERCENT,
CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_THERMOSTAT_TYPE,
CONF_USE_WINDOW_FEATURE,
@@ -291,6 +294,20 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
]:
new.pop(key, None)
# Migration 2.0 to 2.1 -> rename security parameters into safety
if config_entry.version == CONFIG_VERSION and config_entry.minor_version == 0:
for key in [
"security_delay_min",
"security_min_on_percent",
"security_default_on_percent",
]:
new_key = key.replace("security_", "safety_")
old_value = config_entry.data.get(key, None)
if old_value is not None:
new[new_key] = old_value
new.pop(key, None)
hass.config_entries.async_update_entry(
config_entry,
data=new,

View File

@@ -0,0 +1,119 @@
""" A base class for all VTherm entities"""
import logging
from datetime import timedelta
from homeassistant.core import HomeAssistant, callback, Event
from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import DOMAIN as CLIMATE_DOMAIN
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.helpers.event import async_track_state_change_event, async_call_later
from .const import DOMAIN, DEVICE_MANUFACTURER
from .base_thermostat import BaseThermostat
_LOGGER = logging.getLogger(__name__)
class VersatileThermostatBaseEntity(Entity):
"""A base class for all entities"""
_my_climate: BaseThermostat
hass: HomeAssistant
_config_id: str
_device_name: str
def __init__(self, hass: HomeAssistant, config_id, device_name) -> None:
"""The CTOR"""
self.hass = hass
self._config_id = config_id
self._device_name = device_name
self._my_climate = None
self._cancel_call = None
self._attr_has_entity_name = True
@property
def should_poll(self) -> bool:
"""Do not poll for those entities"""
return False
@property
def my_climate(self) -> BaseThermostat | None:
"""Returns my climate if found"""
if not self._my_climate:
self._my_climate = self.find_my_versatile_thermostat()
if self._my_climate:
# Only the first time
self.my_climate_is_initialized()
return self._my_climate
@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,
)
def find_my_versatile_thermostat(self) -> BaseThermostat:
"""Find the underlying climate entity"""
try:
component: EntityComponent[ClimateEntity] = self.hass.data[CLIMATE_DOMAIN]
for entity in component.entities:
# _LOGGER.debug("Device_info is %s", entity.device_info)
if entity.device_info == self.device_info:
_LOGGER.debug("Found %s!", entity)
return entity
except KeyError:
pass
return None
@callback
async def async_added_to_hass(self):
"""Listen to my climate state change"""
# Check delay condition
async def try_find_climate(_):
_LOGGER.debug(
"%s - Calling VersatileThermostatBaseEntity.async_added_to_hass", self
)
mcl = self.my_climate
if mcl:
if self._cancel_call:
self._cancel_call()
self._cancel_call = None
self.async_on_remove(
async_track_state_change_event(
self.hass,
[mcl.entity_id],
self.async_my_climate_changed,
)
)
else:
_LOGGER.debug("%s - no entity to listen. Try later", self)
self._cancel_call = async_call_later(
self.hass, timedelta(seconds=1), try_find_climate
)
await try_find_climate(None)
@callback
def my_climate_is_initialized(self):
"""Called when the associated climate is initialized"""
return
@callback
async def async_my_climate_changed(
self, event: Event
): # pylint: disable=unused-argument
"""Called when my climate have change
This method aims to be overridden to take the status change
"""
return

View File

@@ -0,0 +1,62 @@
""" Implements a base Feature Manager for Versatile Thermostat """
import logging
from typing import Any
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .commons import ConfigData
from .config_schema import * # pylint: disable=wildcard-import, unused-wildcard-import
_LOGGER = logging.getLogger(__name__)
class BaseFeatureManager:
"""A base class for all feature"""
def __init__(self, vtherm: Any, hass: HomeAssistant, name: str = None):
"""Init of a featureManager"""
self._vtherm = vtherm
self._name = vtherm.name if vtherm else name
self._active_listener: list[CALLBACK_TYPE] = []
self._hass = hass
def post_init(self, entry_infos: ConfigData):
"""Initialize the attributes of the FeatureManager"""
raise NotImplementedError()
async def start_listening(self):
"""Start listening the underlying entity"""
raise NotImplementedError()
def stop_listening(self) -> bool:
"""stop listening to the sensor"""
while self._active_listener:
self._active_listener.pop()()
self._active_listener = []
async def refresh_state(self):
"""Refresh the state and return True if a change have been made"""
return False
def add_listener(self, func: CALLBACK_TYPE) -> None:
"""Add a listener to the list of active listener"""
self._active_listener.append(func)
@property
def is_configured(self) -> bool:
"""True if the FeatureManager is fully configured"""
raise NotImplementedError()
@property
def name(self) -> str:
"""The name"""
return self._name
@property
def hass(self) -> HomeAssistant:
"""The HA instance"""
return self._hass

File diff suppressed because it is too large Load Diff

View File

@@ -25,10 +25,8 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .vtherm_api import VersatileThermostatAPI
from .commons import (
VersatileThermostatBaseEntity,
check_and_extract_service_configuration,
)
from .commons import check_and_extract_service_configuration
from .base_entity import VersatileThermostatBaseEntity
from .const import (
DOMAIN,
DEVICE_MANUFACTURER,
@@ -111,7 +109,7 @@ class SecurityBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on
self._attr_is_on = self.my_climate.security_state is True
self._attr_is_on = self.my_climate.safety_manager.is_safety_detected
if old_state != self._attr_is_on:
self.async_write_ha_state()
return
@@ -150,7 +148,7 @@ class OverpoweringBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on
self._attr_is_on = self.my_climate.overpowering_state is True
self._attr_is_on = self.my_climate.overpowering_state is STATE_ON
if old_state != self._attr_is_on:
self.async_write_ha_state()
return
@@ -319,8 +317,8 @@ class WindowByPassBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on
if self.my_climate.window_bypass_state in [True, False]:
self._attr_is_on = self.my_climate.window_bypass_state
if self.my_climate.is_window_bypass in [True, False]:
self._attr_is_on = self.my_climate.is_window_bypass
if old_state != self._attr_is_on:
self.async_write_ha_state()
return

View File

@@ -0,0 +1,324 @@
""" Implements a central Power Feature Manager for Versatile Thermostat """
import logging
from typing import Any
from functools import cmp_to_key
from datetime import timedelta
from homeassistant.const import STATE_OFF
from homeassistant.core import HomeAssistant, Event, callback
from homeassistant.helpers.event import (
async_track_state_change_event,
EventStateChangedData,
async_call_later,
)
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components.climate import (
ClimateEntity,
DOMAIN as CLIMATE_DOMAIN,
)
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .commons import ConfigData
from .base_manager import BaseFeatureManager
# circular dependency
# from .base_thermostat import BaseThermostat
MIN_DTEMP_SECS = 20
_LOGGER = logging.getLogger(__name__)
class CentralFeaturePowerManager(BaseFeatureManager):
"""A central Power feature manager"""
def __init__(self, hass: HomeAssistant, vtherm_api: Any):
"""Init of a featureManager"""
super().__init__(None, hass, "centralPowerManager")
self._hass: HomeAssistant = hass
self._vtherm_api = vtherm_api # no type due to circular reference
self._is_configured: bool = False
self._power_sensor_entity_id: str = None
self._max_power_sensor_entity_id: str = None
self._current_power: float = None
self._current_max_power: float = None
self._power_temp: float = None
self._cancel_calculate_shedding_call = None
self._started_vtherm_total_power: float = None
# Not used now
self._last_shedding_date = None
def post_init(self, entry_infos: ConfigData):
"""Gets the configuration parameters"""
central_config = self._vtherm_api.find_central_configuration()
if not central_config:
_LOGGER.info("No central configuration is found. Power management will be deactivated")
return
self._power_sensor_entity_id = entry_infos.get(CONF_POWER_SENSOR)
self._max_power_sensor_entity_id = entry_infos.get(CONF_MAX_POWER_SENSOR)
self._power_temp = entry_infos.get(CONF_PRESET_POWER)
self._is_configured = False
self._current_power = None
self._current_max_power = None
if (
entry_infos.get(CONF_USE_POWER_FEATURE, False)
and self._max_power_sensor_entity_id
and self._power_sensor_entity_id
and self._power_temp
):
self._is_configured = True
self._started_vtherm_total_power = 0
else:
_LOGGER.info("Power management is not fully configured and will be deactivated")
async def start_listening(self):
"""Start listening the power sensor"""
if not self._is_configured:
return
self.stop_listening()
self.add_listener(
async_track_state_change_event(
self.hass,
[self._power_sensor_entity_id],
self._power_sensor_changed,
)
)
self.add_listener(
async_track_state_change_event(
self.hass,
[self._max_power_sensor_entity_id],
self._max_power_sensor_changed,
)
)
@callback
async def _power_sensor_changed(self, event: Event[EventStateChangedData]):
"""Handle power changes."""
_LOGGER.debug("Receive new Power event")
_LOGGER.debug(event)
self._started_vtherm_total_power = 0
await self.refresh_state()
@callback
async def _max_power_sensor_changed(self, event: Event[EventStateChangedData]):
"""Handle power max changes."""
_LOGGER.debug("Receive new Power Max event")
_LOGGER.debug(event)
await self.refresh_state()
@overrides
async def refresh_state(self) -> bool:
"""Tries to get the last state from sensor
Returns True if a change has been made"""
async def _calculate_shedding_internal(_):
_LOGGER.debug("Do the shedding calculation")
await self.calculate_shedding()
if self._cancel_calculate_shedding_call:
self._cancel_calculate_shedding_call()
self._cancel_calculate_shedding_call = None
if not self._is_configured:
return False
# Retrieve current power
new_power = get_safe_float(self._hass, self._power_sensor_entity_id)
power_changed = new_power is not None and self._current_power != new_power
if power_changed:
self._current_power = new_power
_LOGGER.debug("New current power has been retrieved: %.3f", self._current_power)
# Retrieve max power
new_max_power = get_safe_float(self._hass, self._max_power_sensor_entity_id)
max_power_changed = new_max_power is not None and self._current_max_power != new_max_power
if max_power_changed:
self._current_max_power = new_max_power
_LOGGER.debug("New current max power has been retrieved: %.3f", self._current_max_power)
# Schedule shedding calculation if there's any change
if power_changed or max_power_changed:
if not self._cancel_calculate_shedding_call:
self._cancel_calculate_shedding_call = async_call_later(self.hass, timedelta(seconds=MIN_DTEMP_SECS), _calculate_shedding_internal)
return True
return False
# For testing purpose only, do an immediate shedding calculation
async def _do_immediate_shedding(self):
"""Do an immmediate shedding calculation if a timer was programmed.
Else, do nothing"""
if self._cancel_calculate_shedding_call:
self._cancel_calculate_shedding_call()
self._cancel_calculate_shedding_call = None
await self.calculate_shedding()
async def calculate_shedding(self):
"""Do the shedding calculation and set/unset VTherm into overpowering state"""
if not self.is_configured or self.current_max_power is None or self.current_power is None:
return
_LOGGER.debug("-------- Start of calculate_shedding")
# Find all VTherms
available_power = self.current_max_power - self.current_power
vtherms_sorted = self.find_all_vtherm_with_power_management_sorted_by_dtemp()
# shedding only
if available_power < 0:
_LOGGER.debug(
"The available power is is < 0 (%s). Set overpowering only for list: %s",
available_power,
vtherms_sorted,
)
# we will set overpowering for the nearest target temp first
total_power_gain = 0
for vtherm in vtherms_sorted:
if vtherm.is_device_active and not vtherm.power_manager.is_overpowering_detected:
device_power = vtherm.power_manager.device_power
total_power_gain += device_power
_LOGGER.info("vtherm %s should be in overpowering state (device_power=%.2f)", vtherm.name, device_power)
await vtherm.power_manager.set_overpowering(True, device_power)
_LOGGER.debug("after vtherm %s total_power_gain=%s, available_power=%s", vtherm.name, total_power_gain, available_power)
if total_power_gain >= -available_power:
_LOGGER.debug("We have found enough vtherm to set to overpowering")
break
# unshedding only
else:
vtherms_sorted.reverse()
_LOGGER.debug("The available power is is > 0 (%s). Do a complete shedding/un-shedding calculation for list: %s", available_power, vtherms_sorted)
total_power_added = 0
for vtherm in vtherms_sorted:
# We want to do always unshedding in order to initialize the state
# so we cannot use is_overpowering_detected which test also UNKNOWN and UNAVAILABLE
if vtherm.power_manager.overpowering_state == STATE_OFF:
continue
power_consumption_max = device_power = vtherm.power_manager.device_power
# calculate the power_consumption_max
if vtherm.on_percent is not None:
power_consumption_max = max(
device_power / vtherm.nb_underlying_entities,
device_power * vtherm.on_percent,
)
_LOGGER.debug("vtherm %s power_consumption_max is %s (device_power=%s, overclimate=%s)", vtherm.name, power_consumption_max, device_power, vtherm.is_over_climate)
# or not ... is for initializing the overpowering state if not already done
if total_power_added + power_consumption_max < available_power or not vtherm.power_manager.is_overpowering_detected:
# we count the unshedding only if the VTherm was in shedding
if vtherm.power_manager.is_overpowering_detected:
_LOGGER.info("vtherm %s should not be in overpowering state (power_consumption_max=%.2f)", vtherm.name, power_consumption_max)
total_power_added += power_consumption_max
await vtherm.power_manager.set_overpowering(False)
if total_power_added >= available_power:
_LOGGER.debug("We have found enough vtherm to set to non-overpowering")
break
_LOGGER.debug("after vtherm %s total_power_added=%s, available_power=%s", vtherm.name, total_power_added, available_power)
self._last_shedding_date = self._vtherm_api.now
_LOGGER.debug("-------- End of calculate_shedding")
def get_climate_components_entities(self) -> list:
"""Get all VTherms entitites"""
vtherms = []
component: EntityComponent[ClimateEntity] = self._hass.data.get(
CLIMATE_DOMAIN, None
)
if component:
for entity in component.entities:
# A little hack to test if the climate is a VTherm. Cannot use isinstance
# due to circular dependency of BaseThermostat
if (
entity.device_info
and entity.device_info.get("model", None) == DOMAIN
):
vtherms.append(entity)
return vtherms
def find_all_vtherm_with_power_management_sorted_by_dtemp(
self,
) -> list:
"""Returns all the VTherms with power management activated"""
entities = self.get_climate_components_entities()
vtherms = [
vtherm
for vtherm in entities
if vtherm.power_manager.is_configured and vtherm.is_on
]
# sort the result with the min temp difference first. A and B should be BaseThermostat class
def cmp_temps(a, b) -> int:
diff_a = float("inf")
diff_b = float("inf")
a_target = a.target_temperature if not a.power_manager.is_overpowering_detected else a.saved_target_temp
b_target = b.target_temperature if not b.power_manager.is_overpowering_detected else b.saved_target_temp
if a.current_temperature is not None and a_target is not None:
diff_a = a_target - a.current_temperature
if b.current_temperature is not None and b_target is not None:
diff_b = b_target - b.current_temperature
if diff_a == diff_b:
return 0
return 1 if diff_a > diff_b else -1
vtherms.sort(key=cmp_to_key(cmp_temps))
return vtherms
def add_started_vtherm_total_power(self, started_power: float):
"""Add the power into the _started_vtherm_total_power which holds all VTherm started after
the last power measurement"""
self._started_vtherm_total_power += started_power
_LOGGER.debug("%s - started_vtherm_total_power is now %s", self, self._started_vtherm_total_power)
@property
def is_configured(self) -> bool:
"""True if the FeatureManager is fully configured"""
return self._is_configured
@property
def current_power(self) -> float | None:
"""Return the current power from sensor"""
return self._current_power
@property
def current_max_power(self) -> float | None:
"""Return the current power from sensor"""
return self._current_max_power
@property
def power_temperature(self) -> float | None:
"""Return the power temperature"""
return self._power_temp
@property
def power_sensor_entity_id(self) -> float | None:
"""Return the power sensor entity id"""
return self._power_sensor_entity_id
@property
def max_power_sensor_entity_id(self) -> float | None:
"""Return the max power sensor entity id"""
return self._max_power_sensor_entity_id
@property
def started_vtherm_total_power(self) -> float | None:
"""Return the started_vtherm_total_power"""
return self._started_vtherm_total_power
def __str__(self):
return "CentralPowerManager"

View File

@@ -28,6 +28,7 @@ from .thermostat_switch import ThermostatOverSwitch
from .thermostat_climate import ThermostatOverClimate
from .thermostat_valve import ThermostatOverValve
from .thermostat_climate_valve import ThermostatOverClimateValve
from .vtherm_api import VersatileThermostatAPI
_LOGGER = logging.getLogger(__name__)
@@ -51,6 +52,9 @@ async def async_setup_entry(
)
if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
# Initialize the central power manager
vtherm_api = VersatileThermostatAPI.get_vtherm_api(hass)
vtherm_api.central_power_manager.post_init(entry.data)
return
# Instantiate the right base class
@@ -97,13 +101,13 @@ async def async_setup_entry(
)
platform.async_register_entity_service(
SERVICE_SET_SECURITY,
SERVICE_SET_SAFETY,
{
vol.Optional("delay_min"): cv.positive_int,
vol.Optional("min_on_percent"): vol.Coerce(float),
vol.Optional("default_on_percent"): vol.Coerce(float),
},
"service_set_security",
"SERVICE_SET_SAFETY",
)
platform.async_register_entity_service(

View File

@@ -3,17 +3,15 @@
# pylint: disable=line-too-long
import logging
from datetime import timedelta
from homeassistant.core import HomeAssistant, callback, Event
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.helpers.event import async_track_state_change_event, async_call_later
import warnings
from types import MappingProxyType
from typing import Any, TypeVar
from .const import ServiceConfigurationError
from .underlyings import UnderlyingEntity
from .base_thermostat import BaseThermostat
from .const import DOMAIN, DEVICE_MANUFACTURER, ServiceConfigurationError
ConfigData = MappingProxyType[str, Any]
T = TypeVar("T", bound=UnderlyingEntity)
_LOGGER = logging.getLogger(__name__)
@@ -137,102 +135,18 @@ def check_and_extract_service_configuration(service_config) -> dict:
return ret
class VersatileThermostatBaseEntity(Entity):
"""A base class for all entities"""
def deprecated(message):
"""A decorator to indicate that the method/attribut is deprecated"""
_my_climate: BaseThermostat
hass: HomeAssistant
_config_id: str
_device_name: str
def __init__(self, hass: HomeAssistant, config_id, device_name) -> None:
"""The CTOR"""
self.hass = hass
self._config_id = config_id
self._device_name = device_name
self._my_climate = None
self._cancel_call = None
self._attr_has_entity_name = True
@property
def should_poll(self) -> bool:
"""Do not poll for those entities"""
return False
@property
def my_climate(self) -> BaseThermostat | None:
"""Returns my climate if found"""
if not self._my_climate:
self._my_climate = self.find_my_versatile_thermostat()
if self._my_climate:
# Only the first time
self.my_climate_is_initialized()
return self._my_climate
@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,
)
def find_my_versatile_thermostat(self) -> BaseThermostat:
"""Find the underlying climate entity"""
try:
component: EntityComponent[ClimateEntity] = self.hass.data[CLIMATE_DOMAIN]
for entity in component.entities:
# _LOGGER.debug("Device_info is %s", entity.device_info)
if entity.device_info == self.device_info:
_LOGGER.debug("Found %s!", entity)
return entity
except KeyError:
pass
return None
@callback
async def async_added_to_hass(self):
"""Listen to my climate state change"""
# Check delay condition
async def try_find_climate(_):
_LOGGER.debug(
"%s - Calling VersatileThermostatBaseEntity.async_added_to_hass", self
def decorator(func):
def wrapper(*args, **kwargs):
warnings.warn(
f"{func.__name__} is deprecated: {message}",
DeprecationWarning,
stacklevel=2,
)
mcl = self.my_climate
if mcl:
if self._cancel_call:
self._cancel_call()
self._cancel_call = None
self.async_on_remove(
async_track_state_change_event(
self.hass,
[mcl.entity_id],
self.async_my_climate_changed,
)
)
else:
_LOGGER.debug("%s - no entity to listen. Try later", self)
self._cancel_call = async_call_later(
self.hass, timedelta(seconds=1), try_find_climate
)
return func(*args, **kwargs)
await try_find_climate(None)
return wrapper
@callback
def my_climate_is_initialized(self):
"""Called when the associated climate is initialized"""
return
@callback
async def async_my_climate_changed(
self, event: Event
): # pylint: disable=unused-argument
"""Called when my climate have change
This method aims to be overriden to take the status change
"""
return
return decorator

View File

@@ -4,13 +4,12 @@
from __future__ import annotations
from typing import Any
import re
import logging
import copy
from collections.abc import Mapping
from collections.abc import Mapping # pylint: disable=import-error
import voluptuous as vol
from homeassistant.exceptions import HomeAssistantError
from homeassistant.core import callback
from homeassistant.config_entries import (
ConfigEntry,
@@ -90,11 +89,10 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
CONF_USE_MOTION_FEATURE, False
) and (self._infos.get(CONF_MOTION_SENSOR) is not None or is_central_config)
self._infos[CONF_USE_POWER_FEATURE] = self._infos.get(
CONF_USE_POWER_CENTRAL_CONFIG, False
) or (
self._infos.get(CONF_POWER_SENSOR) is not None
and self._infos.get(CONF_MAX_POWER_SENSOR) is not None
self._infos[CONF_USE_POWER_FEATURE] = (
self._infos.get(CONF_USE_POWER_CENTRAL_CONFIG, False)
or self._infos.get(CONF_USE_POWER_FEATURE, False)
or (is_central_config and self._infos.get(CONF_POWER_SENSOR) is not None and self._infos.get(CONF_MAX_POWER_SENSOR) is not None)
)
self._infos[CONF_USE_PRESENCE_FEATURE] = (
self._infos.get(CONF_USE_PRESENCE_CENTRAL_CONFIG, False)
@@ -184,7 +182,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
Data has the keys from STEP_*_DATA_SCHEMA with values provided by the user.
"""
# check the heater_entity_id
# check the entity_ids
for conf in [
CONF_UNDERLYING_LIST,
CONF_TEMP_SENSOR,
@@ -274,6 +272,34 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
CONF_MIN_OPENING_DEGREES
) from exc
# Check the VSWITCH configuration. There should be the same number of vswitch_on (resp. vswitch_off) than the number of underlying entity
if self._infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_SWITCH and step_id == "type":
if not self.check_vswitch_configuration(data):
raise VirtualSwitchConfigurationIncorrect(CONF_VSWITCH_ON_CMD_LIST)
def check_vswitch_configuration(self, data) -> bool:
"""Check the Virtual switch configuration and return True if the configuration is correct"""
nb_under = len(data.get(CONF_UNDERLYING_LIST, []))
# check format of each command_on
for command in data.get(CONF_VSWITCH_ON_CMD_LIST, []) + data.get(CONF_VSWITCH_OFF_CMD_LIST, []):
pattern = r"^(?P<command>[a-zA-Z0-9_]+)(?:/(?P<argument>[a-zA-Z0-9_]+)(?::(?P<value>[a-zA-Z0-9_]+))?)?$"
if not re.match(pattern, command):
return False
nb_command_on = len(data.get(CONF_VSWITCH_ON_CMD_LIST, []))
nb_command_off = len(data.get(CONF_VSWITCH_OFF_CMD_LIST, []))
if (nb_command_on == nb_under or nb_command_on == 0) and (nb_command_off == nb_under or nb_command_off == 0):
# There is enough command_on and off
# Check if one under is not a switch (which have default command).
if any(
not thermostat_type.startswith(SWITCH_DOMAIN) and not thermostat_type.startswith(INPUT_BOOLEAN_DOMAIN) for thermostat_type in data.get(CONF_UNDERLYING_LIST, [])
):
if nb_command_on != nb_under or nb_command_off != nb_under:
return False
return True
return False
def check_config_complete(self, infos) -> bool:
"""True if the config is now complete (ie all mandatory attributes are set)"""
is_central_config = (
@@ -319,9 +345,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
):
return False
if infos.get(CONF_UNDERLYING_LIST, None) is not None and not infos.get(
CONF_UNDERLYING_LIST, None
):
# checks that at least one underlying is set but not it central configuration
if len(infos.get(CONF_UNDERLYING_LIST, [])) < 1:
return False
if (
@@ -330,14 +355,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
):
return False
if (
infos.get(CONF_USE_POWER_FEATURE, False) is True
and infos.get(CONF_USE_POWER_CENTRAL_CONFIG, False) is False
and (
infos.get(CONF_POWER_SENSOR, None) is None
or infos.get(CONF_MAX_POWER_SENSOR, None) is None
)
):
if infos.get(CONF_USE_POWER_FEATURE, False) is True and infos.get(CONF_USE_POWER_CENTRAL_CONFIG, False) is False and infos.get(CONF_PRESET_POWER, None) is None:
return False
if (
@@ -353,6 +371,12 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
):
return False
if (
infos.get(CONF_USE_ADVANCED_CENTRAL_CONFIG, False) is False
and infos.get(CONF_MINIMAL_DEACTIVATION_DELAY, -1) == -1
):
return False
if (
infos.get(CONF_PROP_FUNCTION, None) == PROPORTIONAL_FUNCTION_TPI
and infos.get(CONF_USE_TPI_CENTRAL_CONFIG, False) is False
@@ -416,6 +440,8 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
errors["base"] = "valve_regulation_nb_entities_incorrect"
except ValveRegulationMinOpeningDegreesIncorrect as err:
errors[str(err)] = "min_opening_degrees_format"
except VirtualSwitchConfigurationIncorrect as err:
errors["base"] = "vswitch_configuration_incorrect"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
@@ -815,7 +841,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
"""Handle the specific power flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_spec_power user_input=%s", user_input)
schema = STEP_CENTRAL_POWER_DATA_SCHEMA
schema = STEP_NON_CENTRAL_POWER_DATA_SCHEMA
self._infos[COMES_FROM] = "async_step_spec_power"
@@ -909,10 +935,42 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
return await self.generic_step("advanced", schema, user_input, next_step)
async def async_step_finalize(self, _):
"""Should be implemented by Leaf classes"""
raise HomeAssistantError(
"async_finalize not implemented on VersatileThermostat sub-class"
)
"""Finalize the creation. Should be overriden by underlyings"""
if not self._infos[CONF_USE_WINDOW_FEATURE]:
self._infos[CONF_USE_WINDOW_CENTRAL_CONFIG] = False
if CONF_WINDOW_SENSOR in self._infos:
del self._infos[CONF_WINDOW_SENSOR]
if CONF_WINDOW_AUTO_CLOSE_THRESHOLD in self._infos:
del self._infos[CONF_WINDOW_AUTO_CLOSE_THRESHOLD]
if CONF_WINDOW_AUTO_OPEN_THRESHOLD in self._infos:
del self._infos[CONF_WINDOW_AUTO_OPEN_THRESHOLD]
if CONF_WINDOW_AUTO_MAX_DURATION in self._infos:
del self._infos[CONF_WINDOW_AUTO_MAX_DURATION]
if not self._infos[CONF_USE_MOTION_FEATURE]:
self._infos[CONF_USE_MOTION_CENTRAL_CONFIG] = False
if CONF_MOTION_SENSOR in self._infos:
del self._infos[CONF_MOTION_SENSOR]
if not self._infos[CONF_USE_POWER_FEATURE]:
self._infos[CONF_USE_POWER_CENTRAL_CONFIG] = False
if CONF_POWER_SENSOR in self._infos:
del self._infos[CONF_POWER_SENSOR]
if CONF_MAX_POWER_SENSOR in self._infos:
del self._infos[CONF_MAX_POWER_SENSOR]
if not self._infos[CONF_USE_PRESENCE_FEATURE]:
self._infos[CONF_USE_PRESENCE_CENTRAL_CONFIG] = False
if CONF_PRESENCE_SENSOR in self._infos:
del self._infos[CONF_PRESENCE_SENSOR]
if not self._infos[CONF_USE_CENTRAL_BOILER_FEATURE]:
if CONF_CENTRAL_BOILER_ACTIVATION_SRV in self._infos:
del self._infos[CONF_CENTRAL_BOILER_ACTIVATION_SRV]
if CONF_CENTRAL_BOILER_DEACTIVATION_SRV in self._infos:
del self._infos[CONF_CENTRAL_BOILER_DEACTIVATION_SRV]
if not self._infos[CONF_USE_AUTO_START_STOP_FEATURE]:
self._infos[CONF_AUTO_START_STOP_LEVEL] = AUTO_START_STOP_LEVEL_NONE
# Removes temporary value
if COMES_FROM in self._infos:
del self._infos[COMES_FROM]
class VersatileThermostatConfigFlow( # pylint: disable=abstract-method
@@ -936,9 +994,8 @@ class VersatileThermostatConfigFlow( # pylint: disable=abstract-method
async def async_step_finalize(self, _):
"""Finalization of the ConfigEntry creation"""
_LOGGER.debug("ConfigFlow.async_finalize")
# Removes temporary value
if COMES_FROM in self._infos:
del self._infos[COMES_FROM]
await super().async_step_finalize(_)
return self.async_create_entry(title=self._infos[CONF_NAME], data=self._infos)
@@ -976,37 +1033,13 @@ class VersatileThermostatOptionsFlowHandler(
async def async_step_finalize(self, _):
"""Finalization of the ConfigEntry creation"""
if not self._infos[CONF_USE_WINDOW_FEATURE]:
self._infos[CONF_USE_WINDOW_CENTRAL_CONFIG] = False
self._infos[CONF_WINDOW_SENSOR] = None
self._infos[CONF_WINDOW_AUTO_CLOSE_THRESHOLD] = None
self._infos[CONF_WINDOW_AUTO_OPEN_THRESHOLD] = None
self._infos[CONF_WINDOW_AUTO_MAX_DURATION] = None
if not self._infos[CONF_USE_MOTION_FEATURE]:
self._infos[CONF_USE_MOTION_CENTRAL_CONFIG] = False
self._infos[CONF_MOTION_SENSOR] = None
if not self._infos[CONF_USE_POWER_FEATURE]:
self._infos[CONF_USE_POWER_CENTRAL_CONFIG] = False
self._infos[CONF_POWER_SENSOR] = None
self._infos[CONF_MAX_POWER_SENSOR] = None
if not self._infos[CONF_USE_PRESENCE_FEATURE]:
self._infos[CONF_USE_PRESENCE_CENTRAL_CONFIG] = False
self._infos[CONF_PRESENCE_SENSOR] = None
if not self._infos[CONF_USE_CENTRAL_BOILER_FEATURE]:
self._infos[CONF_CENTRAL_BOILER_ACTIVATION_SRV] = None
self._infos[CONF_CENTRAL_BOILER_DEACTIVATION_SRV] = None
if not self._infos[CONF_USE_AUTO_START_STOP_FEATURE]:
self._infos[CONF_AUTO_START_STOP_LEVEL] = AUTO_START_STOP_LEVEL_NONE
_LOGGER.info(
"Recreating entry %s due to configuration change. New config is now: %s",
self.config_entry.entry_id,
self._infos,
)
# Removes temporary value
if COMES_FROM in self._infos:
del self._infos[COMES_FROM]
await super().async_step_finalize(_)
self.hass.config_entries.async_update_entry(self.config_entry, data=self._infos)
return self.async_create_entry(title=None, data=None)

View File

@@ -16,6 +16,14 @@ from homeassistant.components.input_number import (
DOMAIN as INPUT_NUMBER_DOMAIN,
)
from homeassistant.components.select import (
DOMAIN as SELECT_DOMAIN,
)
from homeassistant.components.input_select import (
DOMAIN as INPUT_SELECT_DOMAIN,
)
from homeassistant.components.input_datetime import (
DOMAIN as INPUT_DATETIME_DOMAIN,
)
@@ -120,9 +128,7 @@ STEP_CENTRAL_BOILER_SCHEMA = vol.Schema(
STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_UNDERLYING_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN], multiple=True
),
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN, SELECT_DOMAIN, INPUT_SELECT_DOMAIN, CLIMATE_DOMAIN], multiple=True),
),
vol.Optional(CONF_HEATER_KEEP_ALIVE): cv.positive_int,
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
@@ -132,6 +138,10 @@ STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
),
vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
vol.Optional(CONF_INVERSE_SWITCH, default=False): cv.boolean,
vol.Optional("on_command_text"): vol.In([]),
vol.Optional(CONF_VSWITCH_ON_CMD_LIST): selector.TextSelector(selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT, multiple=True)),
vol.Optional("off_command_text"): vol.In([]),
vol.Optional(CONF_VSWITCH_OFF_CMD_LIST): selector.TextSelector(selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT, multiple=True)),
}
)
@@ -231,16 +241,8 @@ STEP_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
STEP_CENTRAL_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_TPI_COEF_INT, default=0.6): selector.NumberSelector(
selector.NumberSelectorConfig(
min=0.0, max=1.0, step=0.01, mode=selector.NumberSelectorMode.BOX
)
),
vol.Required(CONF_TPI_COEF_EXT, default=0.01): selector.NumberSelector(
selector.NumberSelectorConfig(
min=0.0, max=1.0, step=0.01, mode=selector.NumberSelectorMode.BOX
)
),
vol.Required(CONF_TPI_COEF_INT, default=0.6): selector.NumberSelector(selector.NumberSelectorConfig(min=0.0, max=1.0, step=0.01, mode=selector.NumberSelectorMode.BOX)),
vol.Required(CONF_TPI_COEF_EXT, default=0.01): selector.NumberSelector(selector.NumberSelectorConfig(min=0.0, max=1.0, step=0.001, mode=selector.NumberSelectorMode.BOX)),
}
)
@@ -265,12 +267,11 @@ STEP_WINDOW_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
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_OFF_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(
vol.Optional(CONF_WINDOW_ACTION, default=CONF_WINDOW_TURN_OFF): selector.SelectSelector(
selector.SelectSelectorConfig(
options=CONF_WINDOW_ACTIONS,
translation_key="window_action",
@@ -283,9 +284,8 @@ STEP_CENTRAL_WINDOW_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
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(
vol.Optional(CONF_WINDOW_OFF_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",
@@ -339,6 +339,12 @@ STEP_CENTRAL_POWER_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
}
)
STEP_NON_CENTRAL_POWER_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
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,
@@ -368,14 +374,15 @@ STEP_PRESENCE_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
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_MINIMAL_DEACTIVATION_DELAY, default=0): cv.positive_int,
vol.Required(CONF_SAFETY_DELAY_MIN, default=60): cv.positive_int,
vol.Required(
CONF_SECURITY_MIN_ON_PERCENT,
default=DEFAULT_SECURITY_MIN_ON_PERCENT,
CONF_SAFETY_MIN_ON_PERCENT,
default=DEFAULT_SAFETY_MIN_ON_PERCENT,
): vol.Coerce(float),
vol.Required(
CONF_SECURITY_DEFAULT_ON_PERCENT,
default=DEFAULT_SECURITY_DEFAULT_ON_PERCENT,
CONF_SAFETY_DEFAULT_ON_PERCENT,
default=DEFAULT_SAFETY_DEFAULT_ON_PERCENT,
): vol.Coerce(float),
}
)

View File

@@ -4,6 +4,7 @@
import logging
import math
from typing import Literal
from datetime import datetime
from enum import Enum
@@ -28,7 +29,7 @@ from .prop_algorithm import (
_LOGGER = logging.getLogger(__name__)
CONFIG_VERSION = 2
CONFIG_MINOR_VERSION = 0
CONFIG_MINOR_VERSION = 1
PRESET_TEMP_SUFFIX = "_temp"
PRESET_AC_SUFFIX = "_ac"
@@ -41,10 +42,10 @@ DEVICE_MANUFACTURER = "JMCOLLIN"
DEVICE_MODEL = "Versatile Thermostat"
PRESET_POWER = "power"
PRESET_SECURITY = "security"
PRESET_SAFETY = "security"
PRESET_FROST_PROTECTION = "frost"
HIDDEN_PRESETS = [PRESET_POWER, PRESET_SECURITY]
HIDDEN_PRESETS = [PRESET_POWER, PRESET_SAFETY]
DOMAIN = "versatile_thermostat"
@@ -72,6 +73,7 @@ CONF_DEVICE_POWER = "device_power"
CONF_CYCLE_MIN = "cycle_min"
CONF_PROP_FUNCTION = "proportional_function"
CONF_WINDOW_DELAY = "window_delay"
CONF_WINDOW_OFF_DELAY = "window_off_delay"
CONF_MOTION_DELAY = "motion_delay"
CONF_MOTION_OFF_DELAY = "motion_off_delay"
CONF_MOTION_PRESET = "motion_preset"
@@ -81,11 +83,12 @@ CONF_TPI_COEF_EXT = "tpi_coef_ext"
CONF_PRESENCE_SENSOR = "presence_sensor_entity_id"
CONF_PRESET_POWER = "power_temp"
CONF_MINIMAL_ACTIVATION_DELAY = "minimal_activation_delay"
CONF_MINIMAL_DEACTIVATION_DELAY = "minimal_deactivation_delay"
CONF_TEMP_MIN = "temp_min"
CONF_TEMP_MAX = "temp_max"
CONF_SECURITY_DELAY_MIN = "security_delay_min"
CONF_SECURITY_MIN_ON_PERCENT = "security_min_on_percent"
CONF_SECURITY_DEFAULT_ON_PERCENT = "security_default_on_percent"
CONF_SAFETY_DELAY_MIN = "safety_delay_min"
CONF_SAFETY_MIN_ON_PERCENT = "safety_min_on_percent"
CONF_SAFETY_DEFAULT_ON_PERCENT = "safety_default_on_percent"
CONF_THERMOSTAT_TYPE = "thermostat_type"
CONF_THERMOSTAT_CENTRAL_CONFIG = "thermostat_central_config"
CONF_THERMOSTAT_SWITCH = "thermostat_over_switch"
@@ -125,6 +128,9 @@ CONF_OPENING_DEGREE_LIST = "opening_degree_entity_ids"
CONF_CLOSING_DEGREE_LIST = "closing_degree_entity_ids"
CONF_MIN_OPENING_DEGREES = "min_opening_degrees"
CONF_VSWITCH_ON_CMD_LIST = "vswitch_on_command"
CONF_VSWITCH_OFF_CMD_LIST = "vswitch_off_command"
# Deprecated
CONF_HEATER = "heater_entity_id"
CONF_HEATER_2 = "heater_entity2_id"
@@ -269,6 +275,7 @@ ALL_CONF = (
CONF_MAX_POWER_SENSOR,
CONF_WINDOW_SENSOR,
CONF_WINDOW_DELAY,
CONF_WINDOW_OFF_DELAY,
CONF_WINDOW_AUTO_OPEN_THRESHOLD,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD,
CONF_WINDOW_AUTO_MAX_DURATION,
@@ -283,11 +290,12 @@ ALL_CONF = (
CONF_TPI_COEF_EXT,
CONF_PRESENCE_SENSOR,
CONF_MINIMAL_ACTIVATION_DELAY,
CONF_MINIMAL_DEACTIVATION_DELAY,
CONF_TEMP_MIN,
CONF_TEMP_MAX,
CONF_SECURITY_DELAY_MIN,
CONF_SECURITY_MIN_ON_PERCENT,
CONF_SECURITY_DEFAULT_ON_PERCENT,
CONF_SAFETY_DELAY_MIN,
CONF_SAFETY_MIN_ON_PERCENT,
CONF_SAFETY_DEFAULT_ON_PERCENT,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
@@ -373,13 +381,13 @@ SUPPORT_FLAGS = (
SERVICE_SET_PRESENCE = "set_presence"
SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"
SERVICE_SET_SECURITY = "set_security"
SERVICE_SET_SAFETY = "set_safety"
SERVICE_SET_WINDOW_BYPASS = "set_window_bypass"
SERVICE_SET_AUTO_REGULATION_MODE = "set_auto_regulation_mode"
SERVICE_SET_AUTO_FAN_MODE = "set_auto_fan_mode"
DEFAULT_SECURITY_MIN_ON_PERCENT = 0.5
DEFAULT_SECURITY_DEFAULT_ON_PERCENT = 0.1
DEFAULT_SAFETY_MIN_ON_PERCENT = 0.5
DEFAULT_SAFETY_DEFAULT_ON_PERCENT = 0.1
ATTR_TOTAL_ENERGY = "total_energy"
ATTR_MEAN_POWER_CYCLE = "mean_cycle_power"
@@ -502,6 +510,8 @@ def get_safe_float(hass, entity_id: str):
if (
entity_id is None
or not (state := hass.states.get(entity_id))
or state.state is None
or state.state == "None"
or state.state == "unknown"
or state.state == "unavailable"
):
@@ -557,6 +567,10 @@ class ValveRegulationMinOpeningDegreesIncorrect(HomeAssistantError):
"""Error to indicate that the minimal opening degrees is not a list of int separated by coma"""
class VirtualSwitchConfigurationIncorrect(HomeAssistantError):
"""Error when a virtual switch is not configured correctly"""
class overrides: # pylint: disable=invalid-name
"""An annotation to inform overrides"""

View File

@@ -0,0 +1,240 @@
""" Implements the Auto-start/stop Feature Manager """
# pylint: disable=line-too-long
import logging
from typing import Any
from homeassistant.core import (
HomeAssistant,
)
from homeassistant.components.climate import HVACMode
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .commons import ConfigData
from .base_manager import BaseFeatureManager
from .auto_start_stop_algorithm import (
AutoStartStopDetectionAlgorithm,
AUTO_START_STOP_ACTION_OFF,
AUTO_START_STOP_ACTION_ON,
)
_LOGGER = logging.getLogger(__name__)
class FeatureAutoStartStopManager(BaseFeatureManager):
"""The implementation of the AutoStartStop feature"""
unrecorded_attributes = frozenset(
{
"auto_start_stop_level",
"auto_start_stop_dtmin",
"auto_start_stop_enable",
"auto_start_stop_accumulated_error",
"auto_start_stop_accumulated_error_threshold",
"auto_start_stop_last_switch_date",
}
)
def __init__(self, vtherm: Any, hass: HomeAssistant):
"""Init of a featureManager"""
super().__init__(vtherm, hass)
self._auto_start_stop_level: TYPE_AUTO_START_STOP_LEVELS = (
AUTO_START_STOP_LEVEL_NONE
)
self._auto_start_stop_algo: AutoStartStopDetectionAlgorithm | None = None
self._is_configured: bool = False
self._is_auto_start_stop_enabled: bool = False
@overrides
def post_init(self, entry_infos: ConfigData):
"""Reinit of the manager"""
use_auto_start_stop = entry_infos.get(CONF_USE_AUTO_START_STOP_FEATURE, False)
if use_auto_start_stop:
self._auto_start_stop_level = (
entry_infos.get(CONF_AUTO_START_STOP_LEVEL, None)
or AUTO_START_STOP_LEVEL_NONE
)
self._is_configured = True
else:
self._auto_start_stop_level = AUTO_START_STOP_LEVEL_NONE
self._is_configured = False
# Instanciate the auto start stop algo
self._auto_start_stop_algo = AutoStartStopDetectionAlgorithm(
self._auto_start_stop_level, self.name
)
# Fix an eventual incoherent state
if self._vtherm.is_on and self._vtherm.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP:
self._vtherm.hvac_off_reason = None
@overrides
async def start_listening(self):
"""Start listening the underlying entity"""
@overrides
def stop_listening(self):
"""Stop listening and remove the eventual timer still running"""
@overrides
async def refresh_state(self) -> bool:
"""Check the auto-start-stop and an eventual action
Return False if we should stop the control_heating method"""
if not self._is_configured or not self._is_auto_start_stop_enabled:
_LOGGER.debug("%s - auto start/stop is disabled (or not configured)", self)
return True
slope = (
self._vtherm.last_temperature_slope or 0
) / 60 # to have the slope in °/min
action = self._auto_start_stop_algo.calculate_action(
self._vtherm.hvac_mode,
self._vtherm.saved_hvac_mode,
self._vtherm.target_temperature,
self._vtherm.current_temperature,
slope,
self._vtherm.now,
)
_LOGGER.debug("%s - auto_start_stop action is %s", self, action)
if action == AUTO_START_STOP_ACTION_OFF and self._vtherm.is_on:
_LOGGER.info(
"%s - Turning OFF the Vtherm due to auto-start-stop conditions",
self,
)
self._vtherm.set_hvac_off_reason(HVAC_OFF_REASON_AUTO_START_STOP)
await self._vtherm.async_turn_off()
# Send an event
self._vtherm.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "stop",
"name": self.name,
"cause": "Auto stop conditions reached",
"hvac_mode": self._vtherm.hvac_mode,
"saved_hvac_mode": self._vtherm.saved_hvac_mode,
"target_temperature": self._vtherm.target_temperature,
"current_temperature": self._vtherm.current_temperature,
"temperature_slope": round(slope, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
# Stop here
return False
elif (
action == AUTO_START_STOP_ACTION_ON
and self._vtherm.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
):
_LOGGER.info(
"%s - Turning ON the Vtherm due to auto-start-stop conditions", self
)
await self._vtherm.async_turn_on()
# Send an event
self._vtherm.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "start",
"name": self.name,
"cause": "Auto start conditions reached",
"hvac_mode": self._vtherm.hvac_mode,
"saved_hvac_mode": self._vtherm.saved_hvac_mode,
"target_temperature": self._vtherm.target_temperature,
"current_temperature": self._vtherm.current_temperature,
"temperature_slope": round(slope, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
self._vtherm.update_custom_attributes()
return True
def set_auto_start_stop_enable(self, is_enabled: bool):
"""Enable/Disable the auto-start/stop feature"""
self._is_auto_start_stop_enabled = is_enabled
if (
self._vtherm.hvac_mode == HVACMode.OFF
and self._vtherm.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
):
_LOGGER.debug(
"%s - the vtherm is off cause auto-start/stop and enable have been set to false -> starts the VTherm"
)
self.hass.create_task(self._vtherm.async_turn_on())
# Send an event
self._vtherm.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "start",
"name": self.name,
"cause": "Auto start stop disabled",
"hvac_mode": self._vtherm.hvac_mode,
"saved_hvac_mode": self._vtherm.saved_hvac_mode,
"target_temperature": self._vtherm.target_temperature,
"current_temperature": self._vtherm.current_temperature,
"temperature_slope": round(
self._vtherm.last_temperature_slope or 0, 3
),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
self._vtherm.update_custom_attributes()
def add_custom_attributes(self, extra_state_attributes: dict[str, Any]):
"""Add some custom attributes"""
extra_state_attributes.update(
{
"is_auto_start_stop_configured": self.is_configured,
}
)
if self.is_configured:
extra_state_attributes.update(
{
"auto_start_stop_enable": self.auto_start_stop_enable,
"auto_start_stop_level": self._auto_start_stop_algo.level,
"auto_start_stop_dtmin": self._auto_start_stop_algo.dt_min,
"auto_start_stop_accumulated_error": self._auto_start_stop_algo.accumulated_error,
"auto_start_stop_accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
"auto_start_stop_last_switch_date": self._auto_start_stop_algo.last_switch_date,
}
)
@overrides
@property
def is_configured(self) -> bool:
"""Return True of the aiuto-start/stop feature is configured"""
return self._is_configured
@property
def auto_start_stop_level(self) -> TYPE_AUTO_START_STOP_LEVELS:
"""Return the auto start/stop level."""
return self._auto_start_stop_level
@property
def auto_start_stop_enable(self) -> bool:
"""Returns the auto_start_stop_enable"""
return self._is_auto_start_stop_enabled
@property
def is_auto_stopped(self) -> bool:
"""Returns the is vtherm is stopped and reason is AUTO_START_STOP"""
return (
self._vtherm.hvac_mode == HVACMode.OFF
and self._vtherm.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
)
def __str__(self):
return f"AutoStartStopManager-{self.name}"

View File

@@ -0,0 +1,343 @@
""" Implements the Motion Feature Manager """
# pylint: disable=line-too-long
import logging
from typing import Any
from datetime import timedelta
from homeassistant.const import (
STATE_ON,
STATE_OFF,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import (
HomeAssistant,
callback,
Event,
)
from homeassistant.helpers.event import (
async_track_state_change_event,
EventStateChangedData,
async_call_later,
)
from homeassistant.components.climate import (
PRESET_ACTIVITY,
)
from homeassistant.exceptions import ConditionError
from homeassistant.helpers import condition
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .commons import ConfigData
from .base_manager import BaseFeatureManager
_LOGGER = logging.getLogger(__name__)
class FeatureMotionManager(BaseFeatureManager):
"""The implementation of the Motion feature"""
unrecorded_attributes = frozenset(
{
"motion_sensor_entity_id",
"is_motion_configured",
"motion_delay_sec",
"motion_off_delay_sec",
"motion_preset",
"no_motion_preset",
}
)
def __init__(self, vtherm: Any, hass: HomeAssistant):
"""Init of a featureManager"""
super().__init__(vtherm, hass)
self._motion_state: str = STATE_UNAVAILABLE
self._motion_sensor_entity_id: str = None
self._motion_delay_sec: int | None = 0
self._motion_off_delay_sec: int | None = 0
self._motion_preset: str | None = None
self._no_motion_preset: str | None = None
self._is_configured: bool = False
self._motion_call_cancel: callable = None
@overrides
def post_init(self, entry_infos: ConfigData):
"""Reinit of the manager"""
self.dearm_motion_timer()
self._motion_sensor_entity_id = entry_infos.get(CONF_MOTION_SENSOR, None)
self._motion_delay_sec = entry_infos.get(CONF_MOTION_DELAY, 0)
self._motion_off_delay_sec = entry_infos.get(CONF_MOTION_OFF_DELAY, None)
if not self._motion_off_delay_sec:
self._motion_off_delay_sec = self._motion_delay_sec
self._motion_preset = entry_infos.get(CONF_MOTION_PRESET)
self._no_motion_preset = entry_infos.get(CONF_NO_MOTION_PRESET)
if (
self._motion_sensor_entity_id is not None
and self._motion_preset is not None
and self._no_motion_preset is not None
):
self._is_configured = True
self._motion_state = STATE_UNKNOWN
@overrides
async def start_listening(self):
"""Start listening the underlying entity"""
if self._is_configured:
self.stop_listening()
self.add_listener(
async_track_state_change_event(
self.hass,
[self._motion_sensor_entity_id],
self._motion_sensor_changed,
)
)
@overrides
def stop_listening(self):
"""Stop listening and remove the eventual timer still running"""
self.dearm_motion_timer()
super().stop_listening()
def dearm_motion_timer(self):
"""Dearm the eventual motion time running"""
if self._motion_call_cancel:
self._motion_call_cancel()
self._motion_call_cancel = None
@overrides
async def refresh_state(self) -> bool:
"""Tries to get the last state from sensor
Returns True if a change has been made"""
ret = False
if self._is_configured:
motion_state = self.hass.states.get(self._motion_sensor_entity_id)
if motion_state and motion_state.state not in (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
):
_LOGGER.debug(
"%s - Motion state have been retrieved: %s",
self,
self._motion_state,
)
# recalculate the right target_temp in activity mode
ret = await self.update_motion_state(motion_state.state, False)
return ret
@callback
async def _motion_sensor_changed(self, event: Event[EventStateChangedData]):
"""Handle motion sensor changes."""
new_state = event.data.get("new_state")
_LOGGER.info(
"%s - Motion changed. Event.new_state is %s, _attr_preset_mode=%s, activity=%s",
self,
new_state,
self._vtherm.preset_mode,
PRESET_ACTIVITY,
)
if new_state is None or new_state.state not in (STATE_OFF, STATE_ON):
return
# Check delay condition
async def try_motion_condition(_):
self.dearm_motion_timer()
try:
delay = (
self._motion_delay_sec
if new_state.state == STATE_ON
else self._motion_off_delay_sec
)
long_enough = condition.state(
self.hass,
self._motion_sensor_entity_id,
new_state.state,
timedelta(seconds=delay),
)
except ConditionError:
long_enough = False
if not long_enough:
_LOGGER.debug(
"Motion delay condition is not satisfied (the sensor have change its state during the delay). Check motion sensor state"
)
# Get sensor current state
motion_state = self.hass.states.get(self._motion_sensor_entity_id)
_LOGGER.debug(
"%s - motion_state=%s, new_state.state=%s",
self,
motion_state.state,
new_state.state,
)
if (
motion_state.state == new_state.state
and new_state.state == STATE_ON
):
_LOGGER.debug(
"%s - the motion sensor is finally 'on' after the delay", self
)
long_enough = True
else:
long_enough = False
if long_enough:
_LOGGER.debug("%s - Motion delay condition is satisfied", self)
await self.update_motion_state(new_state.state)
else:
await self.update_motion_state(
STATE_ON if new_state.state == STATE_OFF else STATE_OFF
)
im_on = self._motion_state == STATE_ON
delay_running = self._motion_call_cancel is not None
event_on = new_state.state == STATE_ON
def arm():
"""Arm the timer"""
delay = (
self._motion_delay_sec
if new_state.state == STATE_ON
else self._motion_off_delay_sec
)
self._motion_call_cancel = async_call_later(
self.hass, timedelta(seconds=delay), try_motion_condition
)
# if I'm off
if not im_on:
if event_on and not delay_running:
_LOGGER.debug(
"%s - Arm delay cause i'm off and event is on and no delay is running",
self,
)
arm()
return try_motion_condition
# Ignore the event
_LOGGER.debug("%s - Event ignored cause i'm already off", self)
return None
else: # I'm On
if not event_on and not delay_running:
_LOGGER.info("%s - Arm delay cause i'm on and event is off", self)
arm()
return try_motion_condition
if event_on and delay_running:
_LOGGER.debug(
"%s - Desarm off delay cause i'm on and event is on and a delay is running",
self,
)
self.dearm_motion_timer()
return None
# Ignore the event
_LOGGER.debug("%s - Event ignored cause i'm already on", self)
return None
async def update_motion_state(
self, new_state: str = None, recalculate: bool = True
) -> bool:
"""Update the value of the motion sensor and update the VTherm state accordingly
Return true if a change has been made"""
_LOGGER.info("%s - Updating motion state. New state is %s", self, new_state)
old_motion_state = self._motion_state
if new_state is not None:
self._motion_state = STATE_ON if new_state == STATE_ON else STATE_OFF
if self._vtherm.preset_mode == PRESET_ACTIVITY:
new_preset = self.get_current_motion_preset()
_LOGGER.info(
"%s - Motion condition have changes. New preset temp will be %s",
self,
new_preset,
)
# We do not change the preset which is kept to ACTIVITY but only the target_temperature
# We take the motion into account
new_temp = self._vtherm.find_preset_temp(new_preset)
old_temp = self._vtherm.target_temperature
if new_temp != old_temp:
await self._vtherm.change_target_temperature(new_temp)
if new_temp != old_temp and recalculate:
self._vtherm.recalculate()
await self._vtherm.async_control_heating(force=True)
return old_motion_state != self._motion_state
def get_current_motion_preset(self) -> str:
"""Calculate and return the current motion preset"""
return (
self._motion_preset
if self._motion_state == STATE_ON
else self._no_motion_preset
)
def add_custom_attributes(self, extra_state_attributes: dict[str, Any]):
"""Add some custom attributes"""
extra_state_attributes.update(
{
"motion_sensor_entity_id": self._motion_sensor_entity_id,
"motion_state": self._motion_state,
"is_motion_configured": self._is_configured,
"motion_delay_sec": self._motion_delay_sec,
"motion_off_delay_sec": self._motion_off_delay_sec,
"motion_preset": self._motion_preset,
"no_motion_preset": self._no_motion_preset,
}
)
@overrides
@property
def is_configured(self) -> bool:
"""Return True of the motion is configured"""
return self._is_configured
@property
def motion_state(self) -> str | None:
"""Return the current motion state STATE_ON or STATE_OFF
or STATE_UNAVAILABLE if not configured"""
if not self._is_configured:
return STATE_UNAVAILABLE
return self._motion_state
@property
def is_motion_detected(self) -> bool:
"""Return true if the motion is configured and motion sensor is OFF"""
return self._is_configured and self._motion_state in [
STATE_ON,
]
@property
def motion_sensor_entity_id(self) -> bool:
"""Return true if the motion is configured and motion sensor is OFF"""
return self._motion_sensor_entity_id
@property
def motion_delay_sec(self) -> bool:
"""Return the motion delay"""
return self._motion_delay_sec
@property
def motion_off_delay_sec(self) -> bool:
"""Return motion delay off"""
return self._motion_off_delay_sec
@property
def motion_preset(self) -> bool:
"""Return motion preset"""
return self._motion_preset
@property
def no_motion_preset(self) -> bool:
"""Return no motion preset"""
return self._no_motion_preset
def __str__(self):
return f"MotionManager-{self.name}"

View File

@@ -0,0 +1,268 @@
""" Implements the Power Feature Manager """
# pylint: disable=line-too-long
import logging
from typing import Any
from homeassistant.const import (
STATE_ON,
STATE_OFF,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import (
HomeAssistant,
)
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .commons import ConfigData
from .base_manager import BaseFeatureManager
from .vtherm_api import VersatileThermostatAPI
_LOGGER = logging.getLogger(__name__)
class FeaturePowerManager(BaseFeatureManager):
"""The implementation of the Power feature"""
unrecorded_attributes = frozenset(
{
"power_sensor_entity_id",
"max_power_sensor_entity_id",
"is_power_configured",
"device_power",
"power_temp",
"current_power",
"current_max_power",
}
)
def __init__(self, vtherm: Any, hass: HomeAssistant):
"""Init of a featureManager"""
super().__init__(vtherm, hass)
self._power_temp = None
self._overpowering_state = STATE_UNAVAILABLE
self._is_configured: bool = False
self._device_power: float = 0
self._use_power_feature: bool = False
@overrides
def post_init(self, entry_infos: ConfigData):
"""Reinit of the manager"""
# Power management
self._power_temp = entry_infos.get(CONF_PRESET_POWER)
self._device_power = entry_infos.get(CONF_DEVICE_POWER) or 0
self._use_power_feature = entry_infos.get(CONF_USE_POWER_FEATURE, False)
self._is_configured = False
@overrides
async def start_listening(self):
"""Start listening the underlying entity. There is nothing to listen"""
central_power_configuration = (
VersatileThermostatAPI.get_vtherm_api().central_power_manager.is_configured
)
if self._use_power_feature and self._device_power and central_power_configuration:
self._is_configured = True
# Try to restore _overpowering_state from previous state
old_state = await self._vtherm.async_get_last_state()
self._overpowering_state = STATE_ON if old_state and old_state.attributes and old_state.attributes.get("overpowering_state") == STATE_ON else STATE_UNKNOWN
else:
if self._use_power_feature:
if not central_power_configuration:
_LOGGER.warning(
"%s - Power management is not fully configured. You have to configure the central configuration power",
self,
)
else:
_LOGGER.warning(
"%s - Power management is not fully configured. You have to configure the power feature of the VTherm",
self,
)
def add_custom_attributes(self, extra_state_attributes: dict[str, Any]):
"""Add some custom attributes"""
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
extra_state_attributes.update(
{
"power_sensor_entity_id": vtherm_api.central_power_manager.power_sensor_entity_id,
"max_power_sensor_entity_id": vtherm_api.central_power_manager.max_power_sensor_entity_id,
"overpowering_state": self._overpowering_state,
"is_power_configured": self._is_configured,
"device_power": self._device_power,
"power_temp": self._power_temp,
"current_power": vtherm_api.central_power_manager.current_power,
"current_max_power": vtherm_api.central_power_manager.current_max_power,
"mean_cycle_power": self.mean_cycle_power,
}
)
async def check_power_available(self) -> bool:
"""Check if the Vtherm can be started considering overpowering.
Returns True if no overpowering conditions are found.
If True the vtherm power is written into the temporay vtherm started
"""
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
if (
not self._is_configured
or not vtherm_api.central_power_manager.is_configured
):
return True
current_power = vtherm_api.central_power_manager.current_power
current_max_power = vtherm_api.central_power_manager.current_max_power
started_vtherm_total_power = vtherm_api.central_power_manager.started_vtherm_total_power
if (
current_power is None
or current_max_power is None
or self._device_power is None
):
_LOGGER.warning(
"%s - power not valued. check_power_available not available", self
)
return True
_LOGGER.debug(
"%s - overpowering check: power=%.3f, max_power=%.3f heater power=%.3f",
self,
current_power,
current_max_power,
self._device_power,
)
# issue 407 - power_consumption_max is power we need to add. If already active we don't need to add more power
if self._vtherm.is_device_active:
power_consumption_max = 0
else:
if self._vtherm.is_over_climate:
power_consumption_max = self._device_power
else:
power_consumption_max = max(
self._device_power / self._vtherm.nb_underlying_entities,
self._device_power * self._vtherm.proportional_algorithm.on_percent,
)
ret = (current_power + started_vtherm_total_power + power_consumption_max) < current_max_power
if not ret:
_LOGGER.info(
"%s - there is not enough power available power=%.3f, max_power=%.3f heater power=%.3f",
self,
current_power,
current_max_power,
self._device_power,
)
else:
# Adds the current_power_max to the started vtherm total power
vtherm_api.central_power_manager.add_started_vtherm_total_power(power_consumption_max)
return ret
async def set_overpowering(self, overpowering: bool, power_consumption_max=0):
"""Force the overpowering state for the VTherm"""
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
current_power = vtherm_api.central_power_manager.current_power
current_max_power = vtherm_api.central_power_manager.current_max_power
if overpowering and not self.is_overpowering_detected:
_LOGGER.warning(
"%s - overpowering is detected. Heater preset will be set to 'power'",
self,
)
self._overpowering_state = STATE_ON
if self._vtherm.is_over_climate:
self._vtherm.save_hvac_mode()
self._vtherm.save_preset_mode()
await self._vtherm.async_underlying_entity_turn_off()
await self._vtherm.async_set_preset_mode_internal(PRESET_POWER, force=True)
self._vtherm.send_event(
EventType.POWER_EVENT,
{
"type": "start",
"current_power": current_power,
"device_power": self._device_power,
"current_max_power": current_max_power,
"current_power_consumption": power_consumption_max,
},
)
elif not overpowering and self.is_overpowering_detected:
_LOGGER.warning(
"%s - end of overpowering is detected. Heater preset will be restored to '%s'",
self,
self._vtherm._saved_preset_mode, # pylint: disable=protected-access
)
self._overpowering_state = STATE_OFF
# restore state
if self._vtherm.is_over_climate:
await self._vtherm.restore_hvac_mode()
await self._vtherm.restore_preset_mode()
# restart cycle
await self._vtherm.async_control_heating(force=True)
self._vtherm.send_event(
EventType.POWER_EVENT,
{
"type": "end",
"current_power": current_power,
"device_power": self._device_power,
"current_max_power": current_max_power,
},
)
elif not overpowering and self._overpowering_state != STATE_OFF:
# just set to not overpowering the state which was not set
self._overpowering_state = STATE_OFF
else:
# Nothing to do (already in the right state)
return
self._vtherm.update_custom_attributes()
@overrides
@property
def is_configured(self) -> bool:
"""Return True of the presence is configured"""
return self._is_configured
@property
def overpowering_state(self) -> str | None:
"""Return the current overpowering state STATE_ON or STATE_OFF
or STATE_UNAVAILABLE if not configured"""
if not self._is_configured:
return STATE_UNAVAILABLE
return self._overpowering_state
@property
def is_overpowering_detected(self) -> str | None:
"""Return True if the Vtherm is in overpowering state"""
return self._overpowering_state == STATE_ON
@property
def power_temperature(self) -> bool:
"""Return the power temperature"""
return self._power_temp
@property
def device_power(self) -> bool:
"""Return the device power"""
return self._device_power
@property
def mean_cycle_power(self) -> float | None:
"""Returns the mean power consumption during the cycle"""
if not self._device_power or not self._vtherm.proportional_algorithm:
return None
return float(
self._device_power * self._vtherm.proportional_algorithm.on_percent
)
def __str__(self):
return f"PowerManager-{self.name}"

View File

@@ -0,0 +1,200 @@
""" Implements the Presence Feature Manager """
# pylint: disable=line-too-long
import logging
from typing import Any
from homeassistant.const import (
STATE_ON,
STATE_OFF,
STATE_HOME,
STATE_NOT_HOME,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import (
HomeAssistant,
callback,
Event,
)
from homeassistant.helpers.event import (
async_track_state_change_event,
EventStateChangedData,
)
from homeassistant.components.climate import PRESET_ACTIVITY, PRESET_BOOST, PRESET_COMFORT, PRESET_ECO
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .commons import ConfigData
from .base_manager import BaseFeatureManager
_LOGGER = logging.getLogger(__name__)
class FeaturePresenceManager(BaseFeatureManager):
"""The implementation of the Presence feature"""
unrecorded_attributes = frozenset(
{
"presence_sensor_entity_id",
"is_presence_configured",
}
)
def __init__(self, vtherm: Any, hass: HomeAssistant):
"""Init of a featureManager"""
super().__init__(vtherm, hass)
self._presence_state: str = STATE_UNAVAILABLE
self._presence_sensor_entity_id: str = None
self._is_configured: bool = False
@overrides
def post_init(self, entry_infos: ConfigData):
"""Reinit of the manager"""
self._presence_sensor_entity_id = entry_infos.get(CONF_PRESENCE_SENSOR)
if (
entry_infos.get(CONF_USE_PRESENCE_FEATURE, False)
and self._presence_sensor_entity_id is not None
):
self._is_configured = True
self._presence_state = STATE_UNKNOWN
@overrides
async def start_listening(self):
"""Start listening the underlying entity"""
if self._is_configured:
self.stop_listening()
self.add_listener(
async_track_state_change_event(
self.hass,
[self._presence_sensor_entity_id],
self._presence_sensor_changed,
)
)
@overrides
async def refresh_state(self) -> bool:
"""Tries to get the last state from sensor
Returns True if a change has been made"""
ret = False
if self._is_configured:
# try to acquire presence entity state
presence_state = self.hass.states.get(self._presence_sensor_entity_id)
if presence_state and presence_state.state not in (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
):
ret = await self.update_presence(presence_state.state)
_LOGGER.debug(
"%s - Presence have been retrieved: %s",
self,
presence_state.state,
)
return ret
@callback
async def _presence_sensor_changed(self, event: Event[EventStateChangedData]):
"""Handle presence changes."""
new_state = event.data.get("new_state")
_LOGGER.info(
"%s - Presence changed. Event.new_state is %s, _attr_preset_mode=%s, activity=%s",
self,
new_state,
self._vtherm.preset_mode,
PRESET_ACTIVITY,
)
if new_state is None:
return
if await self.update_presence(new_state.state):
await self._vtherm.async_control_heating(force=True)
async def update_presence(self, new_state: str) -> bool:
"""Update the value of the presence sensor and update the VTherm state accordingly
Return true if a change has been made"""
_LOGGER.info("%s - Updating presence. New state is %s", self, new_state)
old_presence_state = self._presence_state
self._presence_state = (
STATE_ON if new_state in (STATE_ON, STATE_HOME) else STATE_OFF
)
if self._vtherm.preset_mode in HIDDEN_PRESETS or self._is_configured is False:
_LOGGER.info(
"%s - Ignoring presence change cause in Power or Security preset or presence not configured",
self,
)
return old_presence_state != self._presence_state
if new_state is None or new_state not in (
STATE_OFF,
STATE_ON,
STATE_HOME,
STATE_NOT_HOME,
):
self._presence_state = STATE_UNKNOWN
return old_presence_state != self._presence_state
if self._vtherm.preset_mode not in [
PRESET_BOOST,
PRESET_COMFORT,
PRESET_ECO,
PRESET_ACTIVITY,
PRESET_FROST_PROTECTION,
]:
return old_presence_state != self._presence_state
new_temp = self._vtherm.find_preset_temp(self._vtherm.preset_mode)
if new_temp is not None:
_LOGGER.debug(
"%s - presence change in temperature mode new_temp will be: %.2f",
self,
new_temp,
)
await self._vtherm.change_target_temperature(new_temp)
self._vtherm.recalculate()
return True
return old_presence_state != self._presence_state
def add_custom_attributes(self, extra_state_attributes: dict[str, Any]):
"""Add some custom attributes"""
extra_state_attributes.update(
{
"presence_sensor_entity_id": self._presence_sensor_entity_id,
"presence_state": self._presence_state,
"is_presence_configured": self._is_configured,
}
)
@overrides
@property
def is_configured(self) -> bool:
"""Return True of the presence is configured"""
return self._is_configured
@property
def presence_state(self) -> str | None:
"""Return the current presence state STATE_ON or STATE_OFF
or STATE_UNAVAILABLE if not configured"""
if not self._is_configured:
return STATE_UNAVAILABLE
return self._presence_state
@property
def is_absence_detected(self) -> bool:
"""Return true if the presence is configured and presence sensor is OFF"""
return self._is_configured and self._presence_state in [
STATE_NOT_HOME,
STATE_OFF,
]
@property
def presence_sensor_entity_id(self) -> bool:
"""Return true if the presence is configured and presence sensor is OFF"""
return self._presence_sensor_entity_id
def __str__(self):
return f"PresenceManager-{self.name}"

View File

@@ -0,0 +1,322 @@
# pylint: disable=line-too-long
""" Implements the Safety as a Feature Manager"""
import logging
from typing import Any
from homeassistant.const import (
STATE_ON,
STATE_OFF,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.components.climate import HVACMode, HVACAction
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .commons import ConfigData
from .base_manager import BaseFeatureManager
from .vtherm_api import VersatileThermostatAPI
_LOGGER = logging.getLogger(__name__)
class FeatureSafetyManager(BaseFeatureManager):
"""The implementation of the Safety feature"""
unrecorded_attributes = frozenset(
{
"safety_delay_min",
"safety_min_on_percent",
"safety_default_on_percent",
"is_safety_configured",
}
)
def __init__(self, vtherm: Any, hass: HomeAssistant):
"""Init of a featureManager"""
super().__init__(vtherm, hass)
self._is_configured: bool = False
self._safety_delay_min = None
self._safety_min_on_percent = None
self._safety_default_on_percent = None
self._safety_state = STATE_UNAVAILABLE
@overrides
def post_init(self, entry_infos: ConfigData):
"""Reinit of the manager"""
self._safety_delay_min = entry_infos.get(CONF_SAFETY_DELAY_MIN)
self._safety_min_on_percent = (
entry_infos.get(CONF_SAFETY_MIN_ON_PERCENT)
if entry_infos.get(CONF_SAFETY_MIN_ON_PERCENT) is not None
else DEFAULT_SAFETY_MIN_ON_PERCENT
)
self._safety_default_on_percent = (
entry_infos.get(CONF_SAFETY_DEFAULT_ON_PERCENT)
if entry_infos.get(CONF_SAFETY_DEFAULT_ON_PERCENT) is not None
else DEFAULT_SAFETY_DEFAULT_ON_PERCENT
)
if (
self._safety_delay_min is not None
and self._safety_default_on_percent is not None
and self._safety_default_on_percent is not None
):
self._safety_state = STATE_UNKNOWN
self._is_configured = True
@overrides
async def start_listening(self):
"""Start listening the underlying entity"""
@overrides
def stop_listening(self):
"""Stop listening and remove the eventual timer still running"""
@overrides
async def refresh_state(self) -> bool:
"""Check the safety and an eventual action
Return True is safety should be active"""
if not self._is_configured:
_LOGGER.debug("%s - safety is disabled (or not configured)", self)
return False
now = self._vtherm.now
current_tz = dt_util.get_time_zone(self._hass.config.time_zone)
is_safety_detected = self.is_safety_detected
delta_temp = (
now - self._vtherm.last_temperature_measure.replace(tzinfo=current_tz)
).total_seconds() / 60.0
delta_ext_temp = (
now - self._vtherm.last_ext_temperature_measure.replace(tzinfo=current_tz)
).total_seconds() / 60.0
mode_cond = self._vtherm.hvac_mode != HVACMode.OFF
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api()
is_outdoor_checked = (
not api.safety_mode
or api.safety_mode.get("check_outdoor_sensor") is not False
)
temp_cond: bool = delta_temp > self._safety_delay_min or (
is_outdoor_checked and delta_ext_temp > self._safety_delay_min
)
climate_cond: bool = (
self._vtherm.is_over_climate
and self._vtherm.hvac_action
not in [
HVACAction.COOLING,
HVACAction.IDLE,
]
)
switch_cond: bool = (
not self._vtherm.is_over_climate
and self._vtherm.proportional_algorithm is not None
and self._vtherm.proportional_algorithm.calculated_on_percent
>= self._safety_min_on_percent
)
_LOGGER.debug(
"%s - checking safety delta_temp=%.1f delta_ext_temp=%.1f mod_cond=%s temp_cond=%s climate_cond=%s switch_cond=%s",
self,
delta_temp,
delta_ext_temp,
mode_cond,
temp_cond,
climate_cond,
switch_cond,
)
# Issue 99 - a climate is regulated by the device itself and not by VTherm. So a VTherm should never be in safety !
should_climate_be_in_security = False # temp_cond and climate_cond
should_switch_be_in_security = temp_cond and switch_cond
should_be_in_security = (
should_climate_be_in_security or should_switch_be_in_security
)
should_start_security = (
mode_cond and not is_safety_detected and should_be_in_security
)
# attr_preset_mode is not necessary normaly. It is just here to be sure
should_stop_security = (
is_safety_detected
and not should_be_in_security
and self._vtherm.preset_mode == PRESET_SAFETY
)
# Logging and event
if should_start_security:
if should_climate_be_in_security:
_LOGGER.warning(
"%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and underlying climate is %s. Setting it into safety mode",
self,
self._safety_delay_min,
delta_temp,
delta_ext_temp,
self.hvac_action,
)
elif should_switch_be_in_security:
_LOGGER.warning(
"%s - No temperature received for more than %.1f minutes (dt=%.1f, dext=%.1f) and on_percent (%.2f %%) is over defined value (%.2f %%). Set it into safety mode",
self,
self._safety_delay_min,
delta_temp,
delta_ext_temp,
self._vtherm.proportional_algorithm.on_percent * 100,
self._safety_min_on_percent * 100,
)
self._vtherm.send_event(
EventType.TEMPERATURE_EVENT,
{
"last_temperature_measure": self._vtherm.last_temperature_measure.replace(
tzinfo=current_tz
).isoformat(),
"last_ext_temperature_measure": self._vtherm.last_ext_temperature_measure.replace(
tzinfo=current_tz
).isoformat(),
"current_temp": self._vtherm.current_temperature,
"current_ext_temp": self._vtherm.current_outdoor_temperature,
"target_temp": self._vtherm.target_temperature,
},
)
# Start safety mode
if should_start_security:
self._safety_state = STATE_ON
self._vtherm.save_hvac_mode()
self._vtherm.save_preset_mode()
if self._vtherm.proportional_algorithm:
self._vtherm.proportional_algorithm.set_safety(
self._safety_default_on_percent
)
await self._vtherm.async_set_preset_mode_internal(PRESET_SAFETY)
# Turn off the underlying climate or heater if safety default on_percent is 0
if self._vtherm.is_over_climate or self._safety_default_on_percent <= 0.0:
await self._vtherm.async_set_hvac_mode(HVACMode.OFF, False)
self._vtherm.send_event(
EventType.SECURITY_EVENT,
{
"type": "start",
"last_temperature_measure": self._vtherm.last_temperature_measure.replace(
tzinfo=current_tz
).isoformat(),
"last_ext_temperature_measure": self._vtherm.last_ext_temperature_measure.replace(
tzinfo=current_tz
).isoformat(),
"current_temp": self._vtherm.current_temperature,
"current_ext_temp": self._vtherm.current_outdoor_temperature,
"target_temp": self._vtherm.target_temperature,
},
)
# Stop safety mode
elif should_stop_security:
_LOGGER.warning(
"%s - End of safety mode. restoring hvac_mode to %s and preset_mode to %s",
self,
self._vtherm.saved_hvac_mode,
self._vtherm.saved_preset_mode,
)
self._safety_state = STATE_OFF
if self._vtherm.proportional_algorithm:
self._vtherm.proportional_algorithm.unset_safety()
# Restore hvac_mode if previously saved
if self._vtherm.is_over_climate or self._safety_default_on_percent <= 0.0:
await self._vtherm.restore_hvac_mode(False)
await self._vtherm.restore_preset_mode()
self._vtherm.send_event(
EventType.SECURITY_EVENT,
{
"type": "end",
"last_temperature_measure": self._vtherm.last_temperature_measure.replace(
tzinfo=current_tz
).isoformat(),
"last_ext_temperature_measure": self._vtherm.last_ext_temperature_measure.replace(
tzinfo=current_tz
).isoformat(),
"current_temp": self._vtherm.current_temperature,
"current_ext_temp": self._vtherm.current_outdoor_temperature,
"target_temp": self._vtherm.target_temperature,
},
)
# Initialize the safety_state if not already done
elif not should_be_in_security and self._safety_state in [STATE_UNKNOWN]:
self._safety_state = STATE_OFF
return should_be_in_security
def add_custom_attributes(self, extra_state_attributes: dict[str, Any]):
"""Add some custom attributes"""
extra_state_attributes.update(
{
"is_safety_configured": self._is_configured,
"safety_state": self._safety_state,
}
)
if self._is_configured:
extra_state_attributes.update(
{
"safety_delay_min": self._safety_delay_min,
"safety_min_on_percent": self._safety_min_on_percent,
"safety_default_on_percent": self._safety_default_on_percent,
}
)
@overrides
@property
def is_configured(self) -> bool:
"""Return True of the safety feature is configured"""
return self._is_configured
def set_safety_delay_min(self, safety_delay_min):
"""Set the delay min"""
self._safety_delay_min = safety_delay_min
def set_safety_min_on_percent(self, safety_min_on_percent):
"""Set the min on percent"""
self._safety_min_on_percent = safety_min_on_percent
def set_safety_default_on_percent(self, safety_default_on_percent):
"""Set the default on_percent"""
self._safety_default_on_percent = safety_default_on_percent
@property
def is_safety_detected(self) -> bool:
"""Returns the is vtherm is in safety mode"""
return self._safety_state == STATE_ON
@property
def safety_state(self) -> str:
"""Returns the safety state: STATE_ON, STATE_OFF, STATE_UNKWNON, STATE_UNAVAILABLE"""
return self._safety_state
@property
def safety_delay_min(self) -> bool:
"""Returns the safety delay min"""
return self._safety_delay_min
@property
def safety_min_on_percent(self) -> bool:
"""Returns the safety min on percent"""
return self._safety_min_on_percent
@property
def safety_default_on_percent(self) -> bool:
"""Returns the safety safety_default_on_percent"""
return self._safety_default_on_percent
def __str__(self):
return f"SafetyManager-{self.name}"

View File

@@ -0,0 +1,560 @@
""" Implements the Window Feature Manager """
# pylint: disable=line-too-long
import logging
from typing import Any
from datetime import timedelta
from homeassistant.const import (
STATE_ON,
STATE_OFF,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import (
HomeAssistant,
callback,
Event,
)
from homeassistant.helpers.event import (
async_track_state_change_event,
EventStateChangedData,
async_call_later,
)
from homeassistant.components.climate import HVACMode
from homeassistant.exceptions import ConditionError
from homeassistant.helpers import condition
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .commons import ConfigData
from .base_manager import BaseFeatureManager
from .open_window_algorithm import WindowOpenDetectionAlgorithm
_LOGGER = logging.getLogger(__name__)
class FeatureWindowManager(BaseFeatureManager):
"""The implementation of the Window feature"""
unrecorded_attributes = frozenset(
{
"window_sensor_entity_id",
"is_window_configured",
"window_delay_sec",
"window_off_delay_sec",
"window_auto_configured",
"window_auto_open_threshold",
"window_auto_close_threshold",
"window_auto_max_duration",
"window_action",
}
)
def __init__(self, vtherm: Any, hass: HomeAssistant):
"""Init of a featureManager"""
super().__init__(vtherm, hass)
self._window_sensor_entity_id: str = None
self._window_state: str = STATE_UNAVAILABLE
self._window_auto_open_threshold: float = 0
self._window_auto_close_threshold: float = 0
self._window_auto_max_duration: int = 0
self._window_auto_state: bool = False
self._window_auto_algo: WindowOpenDetectionAlgorithm = None
self._is_window_bypass: bool = False
self._window_action: str = None
self._window_delay_sec: int | None = 0
self._window_off_delay_sec: int | None = 0
self._is_configured: bool = False
self._is_window_auto_configured: bool = False
self._window_call_cancel: callable = None
@overrides
def post_init(self, entry_infos: ConfigData):
"""Reinit of the manager"""
self.dearm_window_timer()
self._window_auto_state = STATE_UNAVAILABLE
self._window_state = STATE_UNAVAILABLE
self._window_sensor_entity_id = entry_infos.get(CONF_WINDOW_SENSOR)
self._window_delay_sec = entry_infos.get(CONF_WINDOW_DELAY)
# default is the WINDOW_ON delay if not configured
self._window_off_delay_sec = entry_infos.get(CONF_WINDOW_OFF_DELAY, self._window_delay_sec)
self._window_action = (
entry_infos.get(CONF_WINDOW_ACTION) or CONF_WINDOW_TURN_OFF
)
self._window_auto_open_threshold = entry_infos.get(
CONF_WINDOW_AUTO_OPEN_THRESHOLD
)
self._window_auto_close_threshold = entry_infos.get(
CONF_WINDOW_AUTO_CLOSE_THRESHOLD
)
self._window_auto_max_duration = entry_infos.get(CONF_WINDOW_AUTO_MAX_DURATION)
use_window_feature = entry_infos.get(CONF_USE_WINDOW_FEATURE, False)
if ( # pylint: disable=too-many-boolean-expressions
use_window_feature
and self._window_sensor_entity_id is None
and self._window_auto_open_threshold is not None
and self._window_auto_open_threshold > 0.0
and self._window_auto_close_threshold is not None
and self._window_auto_max_duration is not None
and self._window_auto_max_duration > 0
and self._window_action is not None
):
self._is_window_auto_configured = True
self._window_auto_state = STATE_UNKNOWN
self._window_auto_algo = WindowOpenDetectionAlgorithm(
alert_threshold=self._window_auto_open_threshold,
end_alert_threshold=self._window_auto_close_threshold,
)
if self._is_window_auto_configured or (
use_window_feature
and self._window_sensor_entity_id is not None
and self._window_delay_sec is not None
and self._window_action is not None
):
self._is_configured = True
self._window_state = STATE_UNKNOWN
@overrides
async def start_listening(self):
"""Start listening the underlying entity"""
#Try to get last window bypass state
old_state = await self._vtherm.async_get_last_state()
self._is_window_bypass = True if old_state and old_state.attributes and old_state.attributes.get("is_window_bypass") == True else False
if self._is_configured:
self.stop_listening()
if self._window_sensor_entity_id:
self.add_listener(
async_track_state_change_event(
self.hass,
[self._window_sensor_entity_id],
self._window_sensor_changed,
)
)
@overrides
def stop_listening(self):
"""Stop listening and remove the eventual timer still running"""
self.dearm_window_timer()
super().stop_listening()
def dearm_window_timer(self):
"""Dearm the eventual motion time running"""
if self._window_call_cancel:
self._window_call_cancel()
self._window_call_cancel = None
@overrides
async def refresh_state(self) -> bool:
"""Tries to get the last state from sensor
Returns True if a change has been made"""
ret = False
if self._is_configured and self._window_sensor_entity_id is not None:
window_state = self.hass.states.get(self._window_sensor_entity_id)
if window_state and window_state.state not in (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
):
_LOGGER.debug(
"%s - Window state have been retrieved: %s",
self,
self._window_state,
)
# recalculate the right target_temp in activity mode
ret = await self.update_window_state(window_state.state)
return ret
@callback
async def _window_sensor_changed(self, event: Event[EventStateChangedData]):
"""Handle window sensor changes."""
new_state = event.data.get("new_state")
old_state = event.data.get("old_state")
_LOGGER.info(
"%s - Window changed. Event.new_state is %s, _hvac_mode=%s, _saved_hvac_mode=%s",
self,
new_state,
self._vtherm.hvac_mode,
self._vtherm.saved_hvac_mode,
)
# Check delay condition
async def try_window_condition(_):
try:
long_enough = condition.state(
self._hass,
self._window_sensor_entity_id,
new_state.state,
timedelta(seconds=delay),
)
except ConditionError:
long_enough = False
if not long_enough:
_LOGGER.debug(
"Window delay condition is not satisfied. Ignore window event"
)
self._window_state = old_state.state or STATE_OFF
return
_LOGGER.debug("%s - Window delay condition is satisfied", self)
if self._window_state == new_state.state:
_LOGGER.debug("%s - no change in window state. Forget the event")
return
_LOGGER.debug("%s - Window ByPass is : %s", self, self._is_window_bypass)
if self._is_window_bypass:
_LOGGER.info(
"%s - Window ByPass is activated. Ignore window event", self
)
# We change tne state but we don't apply the change
self._window_state = new_state.state
else:
await self.update_window_state(new_state.state)
self._vtherm.update_custom_attributes()
delay = self._window_delay_sec if new_state.state == STATE_ON else self._window_off_delay_sec
if new_state is None or old_state is None or new_state.state == old_state.state:
return try_window_condition
self.dearm_window_timer()
self._window_call_cancel = async_call_later(self.hass, timedelta(seconds=delay), try_window_condition)
# For testing purpose we need to access the inner function
return try_window_condition
async def update_window_state(self, new_state: str = None) -> bool:
"""Change the window detection state.
new_state is on if an open window have been detected or off else
return True if the state have changed
"""
if self._window_state == new_state:
return False
if new_state != STATE_ON:
_LOGGER.info(
"%s - Window is closed. Restoring hvac_mode '%s' if stopped by window detection or temperature %s",
self,
self._vtherm.saved_hvac_mode,
self._vtherm.saved_target_temp,
)
if self._window_action in [
CONF_WINDOW_FROST_TEMP,
CONF_WINDOW_ECO_TEMP,
]:
await self._vtherm.restore_target_temp()
# default to TURN_OFF
elif self._window_action in [CONF_WINDOW_TURN_OFF]:
if (
self._vtherm.last_central_mode != CENTRAL_MODE_STOPPED
and self._vtherm.hvac_off_reason == HVAC_OFF_REASON_WINDOW_DETECTION
):
self._vtherm.set_hvac_off_reason(None)
await self._vtherm.restore_hvac_mode(True)
elif self._window_action in [CONF_WINDOW_FAN_ONLY]:
if self._vtherm.last_central_mode != CENTRAL_MODE_STOPPED:
self._vtherm.set_hvac_off_reason(None)
await self._vtherm.restore_hvac_mode(True)
else:
_LOGGER.error(
"%s - undefined window_action %s. Please open a bug in the github of this project with this log",
self,
self._window_action,
)
return False
else:
_LOGGER.info(
"%s - Window is open. Apply window action %s", self, self._window_action
)
if self._window_action == CONF_WINDOW_TURN_OFF and not self._vtherm.is_on:
_LOGGER.debug(
"%s is already off. Forget turning off VTherm due to window detection"
)
self._window_state = new_state
return False
# self._window_state = new_state
if self._vtherm.last_central_mode in [CENTRAL_MODE_AUTO, None]:
if self._window_action in [
CONF_WINDOW_TURN_OFF,
CONF_WINDOW_FAN_ONLY,
]:
self._vtherm.save_hvac_mode()
elif self._window_action in [
CONF_WINDOW_FROST_TEMP,
CONF_WINDOW_ECO_TEMP,
]:
self._vtherm.save_target_temp()
if (
self._window_action == CONF_WINDOW_FAN_ONLY
and HVACMode.FAN_ONLY in self._vtherm.hvac_modes
):
await self._vtherm.async_set_hvac_mode(HVACMode.FAN_ONLY)
elif (
self._window_action == CONF_WINDOW_FROST_TEMP
and self._vtherm.is_preset_configured(PRESET_FROST_PROTECTION)
):
await self._vtherm.change_target_temperature(
self._vtherm.find_preset_temp(PRESET_FROST_PROTECTION)
)
elif (
self._window_action == CONF_WINDOW_ECO_TEMP
and self._vtherm.is_preset_configured(PRESET_ECO)
):
await self._vtherm.change_target_temperature(
self._vtherm.find_preset_temp(PRESET_ECO)
)
else: # default is to turn_off
self._vtherm.set_hvac_off_reason(HVAC_OFF_REASON_WINDOW_DETECTION)
await self._vtherm.async_set_hvac_mode(HVACMode.OFF)
self._window_state = new_state
return True
async def manage_window_auto(self, in_cycle=False) -> callable:
"""The management of the window auto feature
Returns the dearm function used to deactivate the window auto"""
async def dearm_window_auto(_):
"""Callback that will be called after end of WINDOW_AUTO_MAX_DURATION"""
_LOGGER.info("Unset window auto because MAX_DURATION is exceeded")
await deactivate_window_auto(auto=True)
async def deactivate_window_auto(auto=False):
"""Deactivation of the Window auto state"""
_LOGGER.warning(
"%s - End auto detection of open window slope=%.3f", self, slope
)
# Send an event
cause = "max duration expiration" if auto else "end of slope alert"
self._vtherm.send_event(
EventType.WINDOW_AUTO_EVENT,
{"type": "end", "cause": cause, "curve_slope": slope},
)
# Set attributes
self._window_auto_state = STATE_OFF
await self.update_window_state(self._window_auto_state)
# await self.restore_hvac_mode(True)
self.dearm_window_timer()
if not self._window_auto_algo:
return None
if in_cycle:
slope = self._window_auto_algo.check_age_last_measurement(
temperature=self._vtherm.ema_temperature,
datetime_now=self._vtherm.now,
)
else:
slope = self._window_auto_algo.add_temp_measurement(
temperature=self._vtherm.ema_temperature,
datetime_measure=self._vtherm.last_temperature_measure,
)
_LOGGER.debug(
"%s - Window auto is on, check the alert. last slope is %.3f",
self,
slope if slope is not None else 0.0,
)
if self.is_window_bypass or not self._is_window_auto_configured:
_LOGGER.debug(
"%s - Window auto event is ignored because bypass is ON or window auto detection is disabled",
self,
)
return None
if (
self._window_auto_algo.is_window_open_detected()
and self._window_auto_state in [STATE_UNKNOWN, STATE_OFF]
and self._vtherm.hvac_mode != HVACMode.OFF
):
if (
self._vtherm.proportional_algorithm
and self._vtherm.proportional_algorithm.on_percent <= 0.0
):
_LOGGER.info(
"%s - Start auto detection of open window slope=%.3f but no heating detected (on_percent<=0). Forget the event",
self,
slope,
)
return dearm_window_auto
_LOGGER.warning(
"%s - Start auto detection of open window slope=%.3f", self, slope
)
# Send an event
self._vtherm.send_event(
EventType.WINDOW_AUTO_EVENT,
{"type": "start", "cause": "slope alert", "curve_slope": slope},
)
# Set attributes
self._window_auto_state = STATE_ON
await self.update_window_state(self._window_auto_state)
# Arm the end trigger
self.dearm_window_timer()
self._window_call_cancel = async_call_later(
self.hass,
timedelta(minutes=self._window_auto_max_duration),
dearm_window_auto,
)
elif (
self._window_auto_algo.is_window_close_detected()
and self._window_auto_state == STATE_ON
):
await deactivate_window_auto(False)
# For testing purpose we need to return the inner function
return dearm_window_auto
def add_custom_attributes(self, extra_state_attributes: dict[str, Any]):
"""Add some custom attributes"""
extra_state_attributes.update(
{
"window_state": self.window_state,
"window_auto_state": self.window_auto_state,
"window_action": self.window_action,
"is_window_bypass": self._is_window_bypass,
"window_sensor_entity_id": self._window_sensor_entity_id,
"window_delay_sec": self._window_delay_sec,
"window_off_delay_sec": self._window_off_delay_sec,
"is_window_configured": self._is_configured,
"is_window_auto_configured": self._is_window_auto_configured,
"window_auto_open_threshold": self._window_auto_open_threshold,
"window_auto_close_threshold": self._window_auto_close_threshold,
"window_auto_max_duration": self._window_auto_max_duration,
}
)
async def set_window_bypass(self, window_bypass: bool) -> bool:
"""Set the window bypass flag
Return True if state have been changed"""
self._is_window_bypass = window_bypass
if self._window_state == STATE_ON:
if not self._is_window_bypass:
_LOGGER.info(
"%s - Last window state was open & ByPass is now off. Set hvac_mode to '%s'",
self,
HVACMode.OFF,
)
self._vtherm.save_hvac_mode()
await self._vtherm.async_set_hvac_mode(HVACMode.OFF)
if self._is_window_bypass:
_LOGGER.info(
"%s - Last window state was open & ByPass is now on. Set hvac_mode to last available mode",
self,
)
await self._vtherm.restore_hvac_mode(True)
return True
return False
@overrides
@property
def is_configured(self) -> bool:
"""Return True of the window feature is configured"""
return self._is_configured
@property
def is_window_auto_configured(self) -> bool:
"""Return True of the window automatic detection is configured"""
return self._is_window_auto_configured
@property
def window_state(self) -> str | None:
"""Return the current window state STATE_ON or STATE_OFF
or STATE_UNAVAILABLE if not configured"""
if not self._is_configured:
return STATE_UNAVAILABLE
return self._window_state
@property
def window_auto_state(self) -> str | None:
"""Return the current window auto state STATE_ON or STATE_OFF
or STATE_UNAVAILABLE if not configured"""
if not self._is_configured:
return STATE_UNAVAILABLE
return self._window_auto_state
@property
def is_window_bypass(self) -> str | None:
"""Return True if the window bypass is activated"""
if not self._is_configured:
return False
return self._is_window_bypass
@property
def is_window_detected(self) -> bool:
"""Return true if the window is configured and open and bypass is not ON"""
return self._is_configured and (
self._window_state == STATE_ON or self._window_auto_state == STATE_ON
) and not self._is_window_bypass
@property
def window_sensor_entity_id(self) -> bool:
"""Return true if the presence is configured and presence sensor is OFF"""
return self._window_sensor_entity_id
@property
def window_delay_sec(self) -> bool:
"""Return the window on delay"""
return self._window_delay_sec
@property
def window_off_delay_sec(self) -> bool:
"""Return the window off delay"""
return self._window_off_delay_sec
@property
def window_action(self) -> bool:
"""Return the window action"""
return self._window_action
@property
def window_auto_open_threshold(self) -> bool:
"""Return the window_auto_open_threshold"""
return self._window_auto_open_threshold
@property
def window_auto_close_threshold(self) -> bool:
"""Return the window_auto_close_threshold"""
return self._window_auto_close_threshold
@property
def window_auto_max_duration(self) -> bool:
"""Return the window_auto_max_duration"""
return self._window_auto_max_duration
@property
def last_slope(self) -> float:
"""Return the last slope (in °C/hour)"""
if not self._window_auto_algo:
return None
return self._window_auto_algo.last_slope
def __str__(self):
return f"WindowManager-{self.name}"

View File

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

View File

@@ -28,7 +28,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util import slugify
from .vtherm_api import VersatileThermostatAPI
from .commons import VersatileThermostatBaseEntity
from .base_entity import VersatileThermostatBaseEntity
from .const import (
DOMAIN,
@@ -367,9 +367,6 @@ class CentralConfigTemperatureNumber(
@property
def native_unit_of_measurement(self) -> str | None:
"""The unit of measurement"""
# TODO Kelvin ? It seems not because all internal values are stored in
# ° Celsius but only the render in front can be in °K depending on the
# user configuration.
return self.hass.config.units.temperature_unit

View File

@@ -30,18 +30,20 @@ class PropAlgorithm:
tpi_coef_ext,
cycle_min: int,
minimal_activation_delay: int,
minimal_deactivation_delay: int,
vtherm_entity_id: str = None,
max_on_percent: float = None,
) -> None:
"""Initialisation of the Proportional Algorithm"""
_LOGGER.debug(
"%s - 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
"%s - Creation new PropAlgorithm function_type: %s, tpi_coef_int: %s, tpi_coef_ext: %s, cycle_min:%d, minimal_activation_delay:%d, minimal_deactivation_delay:%d", # pylint: disable=line-too-long
vtherm_entity_id,
function_type,
tpi_coef_int,
tpi_coef_ext,
cycle_min,
minimal_activation_delay,
minimal_deactivation_delay,
)
# Issue 506 - check parameters
@@ -51,10 +53,11 @@ class PropAlgorithm:
or not is_number(tpi_coef_ext)
or not is_number(cycle_min)
or not is_number(minimal_activation_delay)
or not is_number(minimal_deactivation_delay)
or function_type != PROPORTIONAL_FUNCTION_TPI
):
_LOGGER.error(
"%s - configuration is wrong. function_type=%s, entity_id is %s, tpi_coef_int is %s, tpi_coef_ext is %s, cycle_min is %s, minimal_activation_delay is %s",
"%s - configuration is wrong. function_type=%s, entity_id is %s, tpi_coef_int is %s, tpi_coef_ext is %s, cycle_min is %s, minimal_activation_delay is %s, minimal_deactivation_delay is %s",
vtherm_entity_id,
function_type,
vtherm_entity_id,
@@ -62,6 +65,7 @@ class PropAlgorithm:
tpi_coef_ext,
cycle_min,
minimal_activation_delay,
minimal_deactivation_delay,
)
raise TypeError(
"TPI parameters are not set correctly. VTherm will not work as expected. Please reconfigure it correctly. See previous log for values"
@@ -73,6 +77,7 @@ class PropAlgorithm:
self._tpi_coef_ext = tpi_coef_ext
self._cycle_min = cycle_min
self._minimal_activation_delay = minimal_activation_delay
self._minimal_deactivation_delay = minimal_deactivation_delay
self._on_percent = 0
self._calculated_on_percent = 0
self._on_time_sec = 0
@@ -187,7 +192,19 @@ class PropAlgorithm:
self._off_time_sec = self._cycle_min * 60 - self._on_time_sec
def set_security(self, default_on_percent: float):
# Do not stop heating when off time less than xx sec
if self._off_time_sec < self._minimal_deactivation_delay:
if self._off_time_sec > 0:
_LOGGER.info(
"%s - Force 100%% heating cycle since the off duration (%f) is shorter than minimal_deactivation_delay (%f)",
self._vtherm_entity_id,
self._off_time_sec,
self._minimal_deactivation_delay,
)
self._on_time_sec = self._cycle_min * 60
self._off_time_sec = 0
def set_safety(self, default_on_percent: float):
"""Set a default value for on_percent (used for safety mode)"""
_LOGGER.info(
"%s - Proportional Algo - set security to ON", self._vtherm_entity_id
@@ -196,7 +213,7 @@ class PropAlgorithm:
self._default_on_percent = default_on_percent
self._calculate_internal()
def unset_security(self):
def unset_safety(self):
"""Unset the safety mode"""
_LOGGER.info(
"%s - Proportional Algo - set security to OFF", self._vtherm_entity_id

View File

@@ -35,7 +35,7 @@ from homeassistant.components.climate import (
from .base_thermostat import BaseThermostat
from .vtherm_api import VersatileThermostatAPI
from .commons import VersatileThermostatBaseEntity
from .base_entity import VersatileThermostatBaseEntity
from .const import (
DOMAIN,
DEVICE_MANUFACTURER,
@@ -165,7 +165,7 @@ class EnergySensor(VersatileThermostatBaseEntity, SensorEntity):
if not self.my_climate:
return None
if self.my_climate.device_power > THRESHOLD_WATT_KILO:
if self.my_climate.power_manager.device_power > THRESHOLD_WATT_KILO:
return UnitOfEnergy.WATT_HOUR
else:
return UnitOfEnergy.KILO_WATT_HOUR
@@ -190,16 +190,17 @@ class MeanPowerSensor(VersatileThermostatBaseEntity, SensorEntity):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
if math.isnan(float(self.my_climate.mean_cycle_power)) or math.isinf(
self.my_climate.mean_cycle_power
):
if math.isnan(
float(self.my_climate.power_manager.mean_cycle_power)
) or math.isinf(self.my_climate.power_manager.mean_cycle_power):
raise ValueError(
f"Sensor has illegal state {self.my_climate.mean_cycle_power}"
f"Sensor has illegal state {self.my_climate.power_manager.mean_cycle_power}"
)
old_state = self._attr_native_value
self._attr_native_value = round(
self.my_climate.mean_cycle_power, self.suggested_display_precision
self.my_climate.power_manager.mean_cycle_power,
self.suggested_display_precision,
)
if old_state != self._attr_native_value:
self.async_write_ha_state()
@@ -222,7 +223,7 @@ class MeanPowerSensor(VersatileThermostatBaseEntity, SensorEntity):
if not self.my_climate:
return None
if self.my_climate.device_power > THRESHOLD_WATT_KILO:
if self.my_climate.power_manager.device_power > THRESHOLD_WATT_KILO:
return UnitOfPower.WATT
else:
return UnitOfPower.KILO_WATT
@@ -302,6 +303,10 @@ class ValveOpenPercentSensor(VersatileThermostatBaseEntity, SensorEntity):
"""Called when my climate have change"""
# _LOGGER.debug("%s - climate state change", self._attr_unique_id)
if not self.my_climate or not hasattr(self.my_climate, "valve_open_percent"):
_LOGGER.warning("%s - my_climate not found or no valve_open_percent property found. This could be normal at startup. Ignore the underlying device change.", self)
return
old_state = self._attr_native_value
self._attr_native_value = self.my_climate.valve_open_percent
if old_state != self._attr_native_value:

View File

@@ -76,7 +76,7 @@ set_preset_temperature:
unit_of_measurement: °
mode: slider
set_security:
set_safety:
name: Set safety
description: Change the safety parameters
target:

View File

@@ -35,7 +35,7 @@
},
"main": {
"title": "Add new Versatile Thermostat",
"description": "Main mandatory attributes",
"description": "Main mandatory attributes&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/base-attributes.md)",
"data": {
"name": "Name",
"thermostat_type": "Thermostat type",
@@ -71,7 +71,7 @@
},
"type": {
"title": "Linked entities",
"description": "Linked entities attributes",
"description": "Linked entities attributes&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/over-switch.md#configuration)",
"data": {
"underlying_entity_ids": "The device(s) to be controlled",
"heater_keep_alive": "Switch keep-alive interval in seconds",
@@ -82,7 +82,11 @@
"auto_regulation_periode_min": "Regulation minimum period",
"auto_regulation_use_device_temp": "Use internal temperature of the underlying",
"inverse_switch_command": "Inverse switch command",
"auto_fan_mode": "Auto fan mode"
"auto_fan_mode": "Auto fan mode",
"on_command_text": "Turn on command customization",
"vswitch_on_command": "Optional turn on commands",
"off_command_text": "Turn off command customization",
"vswitch_off_command": "Optional turn off commands"
},
"data_description": {
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
@@ -94,12 +98,13 @@
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"auto_regulation_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation",
"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",
"on_command_text": "For underlying of type `select` or `climate` you have to customize the commands."
}
},
"tpi": {
"title": "TPI",
"description": "Time Proportional Integral attributes",
"description": "Time Proportional Integral attributes&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/algorithms.md#lalgorithme-tpi)",
"data": {
"tpi_coef_int": "coef_int",
"tpi_coef_ext": "coef_ext",
@@ -113,17 +118,18 @@
},
"presets": {
"title": "Presets",
"description": "Select if the thermostat will use central preset - deselect for the thermostat to have its own presets",
"description": "Select if the thermostat will use central preset - deselect for the thermostat to have its own presets&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-presets.md)",
"data": {
"use_presets_central_config": "Use central presets configuration"
}
},
"window": {
"title": "Window management",
"description": "Open window management.\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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-window.md)",
"data": {
"window_sensor_entity_id": "Window sensor entity id",
"window_delay": "Window sensor delay (seconds)",
"window_delay": "Window sensor 'on' delay (seconds)",
"window_off_delay": "Window sensor 'off' delay (seconds)",
"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_max_duration": "Maximum duration of automatic window open detection (in min)",
@@ -132,7 +138,8 @@
},
"data_description": {
"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 'on' detection is taken into account",
"window_off_delay": "The delay in seconds before sensor 'off' detection is taken into account. Leave it empty to use the same value as window on delay",
"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_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used",
@@ -142,7 +149,7 @@
},
"motion": {
"title": "Motion management",
"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",
"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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-motion.md)",
"data": {
"motion_sensor_entity_id": "Motion sensor entity id",
"motion_delay": "Activation delay",
@@ -162,7 +169,7 @@
},
"power": {
"title": "Power management",
"description": "Power management attributes.\nGives the power and max power sensor of your home.\nSpecify the power consumption of the heater when on.\nAll sensors and device power should use the same unit (kW or W).",
"description": "Power management attributes.\nGives the power and max power sensor of your home.\nSpecify the power consumption of the heater when on.\nAll sensors and device power should use the same unit (kW or W)&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-power.md)",
"data": {
"power_sensor_entity_id": "Power",
"max_power_sensor_entity_id": "Max power",
@@ -178,7 +185,7 @@
},
"presence": {
"title": "Presence management",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present) and give the corresponding temperature preset setting.",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present) and give the corresponding temperature preset setting&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-presence.md)",
"data": {
"presence_sensor_entity_id": "Presence sensor",
"use_presence_central_config": "Use central presence temperature configuration. Deselect to use specific temperature entities"
@@ -189,25 +196,27 @@
},
"advanced": {
"title": "Advanced parameters",
"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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-advanced.md)",
"data": {
"minimal_activation_delay": "Minimum activation delay",
"security_delay_min": "Safety delay (in minutes)",
"security_min_on_percent": "Minimum power percent to enable safety mode",
"security_default_on_percent": "Power percent to use in safety mode",
"minimal_deactivation_delay": "Minimum deactivation delay",
"safety_delay_min": "Safety delay (in minutes)",
"safety_min_on_percent": "Minimum power percent to enable safety mode",
"safety_default_on_percent": "Power percent to use in safety mode",
"use_advanced_central_config": "Use central advanced configuration"
},
"data_description": {
"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 safety off state",
"security_min_on_percent": "Minimum 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 safety preset. Set to 0 to switch off heater in safety preset",
"minimal_deactivation_delay": "Delay in seconds under which the equipment will be kept active",
"safety_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state",
"safety_min_on_percent": "Minimum heating percent value for safety preset activation. Below this amount of power percent the thermostat won't go into safety preset",
"safety_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"
}
},
"central_boiler": {
"title": "Control of the central boiler",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-central-boiler.md)",
"data": {
"central_boiler_activation_service": "Command to turn-on",
"central_boiler_deactivation_service": "Command to turn-off"
@@ -219,7 +228,7 @@
},
"valve_regulation": {
"title": "Self-regulation with valve",
"description": "Configuration for self-regulation with direct control of the valve",
"description": "Configuration for self-regulation with direct control of the valve&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/self-regulation.md#auto-régulation-par-contrôle-direct-de-la-vanne)",
"data": {
"offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities",
@@ -228,11 +237,11 @@
"min_opening_degrees": "Min opening degrees"
},
"data_description": {
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV has the entity for better regulation. There should be one per underlying climate entities",
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
"closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV has the entity for better regulation. There should be one per underlying climate entities",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"min_opening_degrees": "A comma seperated list of minimal opening degrees. Default to 0. Example: 20, 25, 30"
"min_opening_degrees": "Opening degree minimum value for each underlying device, comma separated. Default to 0. Example: 20, 25, 30"
}
}
},
@@ -281,7 +290,7 @@
},
"main": {
"title": "Main - {name}",
"description": "Main mandatory attributes",
"description": "Main mandatory attributes&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/base-attributes.md)",
"data": {
"name": "Name",
"thermostat_type": "Thermostat type",
@@ -317,7 +326,7 @@
},
"type": {
"title": "Entities - {name}",
"description": "Linked entities attributes",
"description": "Linked entities attributes&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/over-switch.md#configuration)",
"data": {
"underlying_entity_ids": "The device(s) to be controlled",
"heater_keep_alive": "Switch keep-alive interval in seconds",
@@ -328,7 +337,11 @@
"auto_regulation_periode_min": "Regulation minimum period",
"auto_regulation_use_device_temp": "Use internal temperature of the underlying",
"inverse_switch_command": "Inverse switch command",
"auto_fan_mode": "Auto fan mode"
"auto_fan_mode": "Auto fan mode",
"on_command_text": "Turn on command customization",
"vswitch_on_command": "Optional turn on commands",
"off_command_text": "Turn off command customization",
"vswitch_off_command": "Optional turn off commands"
},
"data_description": {
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
@@ -339,13 +352,14 @@
"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_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation",
"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"
"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",
"on_command_text": "For underlying of type `select` or `climate` you have to customize the commands."
}
},
"tpi": {
"title": "TPI - {name}",
"description": "Time Proportional Integral attributes",
"description": "Time Proportional Integral attributes&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/algorithms.md#lalgorithme-tpi)",
"data": {
"tpi_coef_int": "coef_int",
"tpi_coef_ext": "coef_ext",
@@ -359,17 +373,18 @@
},
"presets": {
"title": "Presets - {name}",
"description": "Check if the thermostat will use central presets. Uncheck and the thermostat will have its own preset entities",
"description": "Check if the thermostat will use central presets. Uncheck and the thermostat will have its own preset entities&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-presets.md)",
"data": {
"use_presets_central_config": "Use central presets configuration"
}
},
"window": {
"title": "Window - {name}",
"description": "Open window management.\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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-window.md)",
"data": {
"window_sensor_entity_id": "Window sensor entity id",
"window_delay": "Window sensor delay (seconds)",
"window_delay": "Window sensor 'on' delay (seconds)",
"window_off_delay": "Window sensor 'off' delay (seconds)",
"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_max_duration": "Maximum duration of automatic window open detection (in min)",
@@ -378,8 +393,8 @@
},
"data_description": {
"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_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used",
"window_delay": "The delay in seconds before sensor 'on' detection is taken into account",
"window_off_delay": "The delay in seconds before sensor 'off' detection is taken into account. Leave it empty to use the same value as window on delay",
"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",
"use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm",
@@ -388,7 +403,7 @@
},
"motion": {
"title": "Motion - {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",
"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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-motion.md)",
"data": {
"motion_sensor_entity_id": "Motion sensor entity id",
"motion_delay": "Activation delay",
@@ -408,7 +423,7 @@
},
"power": {
"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).",
"description": "Power management attributes.\nGives the power and max power sensor of your home.\nSpecify the power consumption of the heater when on.\nAll sensors and device power should use the same unit (kW or W)&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-power.md)",
"data": {
"power_sensor_entity_id": "Power",
"max_power_sensor_entity_id": "Max power",
@@ -424,7 +439,7 @@
},
"presence": {
"title": "Presence - {name}",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present) and give the corresponding temperature preset setting.",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present) and give the corresponding temperature preset setting&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-presence.md)",
"data": {
"presence_sensor_entity_id": "Presence sensor",
"use_presence_central_config": "Use central presence temperature configuration. Uncheck to use specific temperature entities"
@@ -435,25 +450,27 @@
},
"advanced": {
"title": "Advanced - {name}",
"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.",
"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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-advanced.md)",
"data": {
"minimal_activation_delay": "Minimum activation delay",
"security_delay_min": "Safety delay (in minutes)",
"security_min_on_percent": "Minimum power percent to enable safety mode",
"security_default_on_percent": "Power percent to use in safety mode",
"minimal_deactivation_delay": "Minimum deactivation delay",
"safety_delay_min": "Safety delay (in minutes)",
"safety_min_on_percent": "Minimum power percent to enable safety mode",
"safety_default_on_percent": "Power percent to use in safety mode",
"use_advanced_central_config": "Use central advanced configuration"
},
"data_description": {
"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 safety off state",
"security_min_on_percent": "Minimum 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 safety preset. Set to 0 to switch off heater in safety preset",
"minimal_deactivation_delay": "Delay in seconds under which the equipment will be kept active",
"safety_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state",
"safety_min_on_percent": "Minimum heating percent value for safety preset activation. Below this amount of power percent the thermostat won't go into safety preset",
"safety_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"
}
},
"central_boiler": {
"title": "Control of the central boiler - {name}",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-central-boiler.md)",
"data": {
"central_boiler_activation_service": "Command to turn-on",
"central_boiler_deactivation_service": "Command to turn-off"
@@ -465,7 +482,7 @@
},
"valve_regulation": {
"title": "Self-regulation with valve - {name}",
"description": "Configuration for self-regulation with direct control of the valve",
"description": "Configuration for self-regulation with direct control of the valve&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/self-regulation.md#auto-régulation-par-contrôle-direct-de-la-vanne)",
"data": {
"offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities",
@@ -474,11 +491,11 @@
"min_opening_degrees": "Min opening degrees"
},
"data_description": {
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV has the entity for better regulation. There should be one per underlying climate entities",
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
"closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV has the entity for better regulation. There should be one per underlying climate entities",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"min_opening_degrees": "A comma seperated list of minimal opening degrees. Default to 0. Example: 20, 25, 30"
"min_opening_degrees": "Opening degree minimum value for each underlying device, comma separated. Default to 0. Example: 20, 25, 30"
}
}
},
@@ -556,7 +573,7 @@
"preset_mode": {
"state": {
"power": "Shedding",
"security": "Safety",
"safety": "Safety",
"none": "Manual",
"frost": "Frost"
}

View File

@@ -12,7 +12,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .commons import VersatileThermostatBaseEntity
from .base_entity import VersatileThermostatBaseEntity
from .const import * # pylint: disable=unused-wildcard-import,wildcard-import
@@ -84,8 +84,13 @@ class AutoStartStopEnable(VersatileThermostatBaseEntity, SwitchEntity, RestoreEn
def update_my_state_and_vtherm(self):
"""Update the auto_start_stop_enable flag in my VTherm"""
self.async_write_ha_state()
if self.my_climate is not None:
self.my_climate.set_auto_start_stop_enable(self._attr_is_on)
if (
self.my_climate is not None
and self.my_climate.auto_start_stop_manager is not None
):
self.my_climate.auto_start_stop_manager.set_auto_start_stop_enable(
self._attr_is_on
)
@callback
async def async_turn_on(self, **kwargs: Any) -> None:

View File

@@ -3,7 +3,7 @@
import logging
from datetime import timedelta, datetime
from homeassistant.const import STATE_ON
from homeassistant.const import STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import Event, HomeAssistant, State, callback
from homeassistant.helpers.event import (
async_track_state_change_event,
@@ -24,11 +24,7 @@ from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
from .vtherm_api import VersatileThermostatAPI
from .underlyings import UnderlyingClimate
from .auto_start_stop_algorithm import (
AutoStartStopDetectionAlgorithm,
AUTO_START_STOP_ACTION_OFF,
AUTO_START_STOP_ACTION_ON,
)
from .feature_auto_start_stop_manager import FeatureAutoStartStopManager
_LOGGER = logging.getLogger(__name__)
@@ -55,15 +51,9 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
"auto_activated_fan_mode",
"auto_deactivated_fan_mode",
"auto_regulation_use_device_temp",
"auto_start_stop_level",
"auto_start_stop_dtmin",
"auto_start_stop_enable",
"auto_start_stop_accumulated_error",
"auto_start_stop_accumulated_error_threshold",
"auto_start_stop_last_switch_date",
"follow_underlying_temp_change",
}
)
).union(FeatureAutoStartStopManager.unrecorded_attributes)
)
def __init__(
@@ -83,11 +73,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
# The fan_mode name depending of the current_mode
self._auto_activated_fan_mode: str | None = None
self._auto_deactivated_fan_mode: str | None = None
self._auto_start_stop_level: TYPE_AUTO_START_STOP_LEVELS = (
AUTO_START_STOP_LEVEL_NONE
)
self._auto_start_stop_algo: AutoStartStopDetectionAlgorithm | None = None
self._is_auto_start_stop_enabled: bool = False
self._follow_underlying_temp_change: bool = False
self._last_regulation_change = None # NowClass.get_now(hass)
@@ -99,6 +84,12 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
def post_init(self, config_entry: ConfigData):
"""Initialize the Thermostat"""
self._auto_start_stop_manager: FeatureAutoStartStopManager = (
FeatureAutoStartStopManager(self, self._hass)
)
self.register_manager(self._auto_start_stop_manager)
super().post_init(config_entry)
for climate in config_entry.get(CONF_UNDERLYING_LIST):
@@ -136,19 +127,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
CONF_AUTO_REGULATION_USE_DEVICE_TEMP, False
)
use_auto_start_stop = config_entry.get(CONF_USE_AUTO_START_STOP_FEATURE, False)
if use_auto_start_stop:
self._auto_start_stop_level = config_entry.get(
CONF_AUTO_START_STOP_LEVEL, AUTO_START_STOP_LEVEL_NONE
)
else:
self._auto_start_stop_level = AUTO_START_STOP_LEVEL_NONE
# Instanciate the auto start stop algo
self._auto_start_stop_algo = AutoStartStopDetectionAlgorithm(
self._auto_start_stop_level, self.name
)
@property
def is_over_climate(self) -> bool:
"""True if the Thermostat is over_climate"""
@@ -178,9 +156,9 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
return self.calculate_hvac_action(self._underlyings)
@overrides
async def _async_internal_set_temperature(self, temperature: float):
async def change_target_temperature(self, temperature: float):
"""Set the target temperature and the target temperature of underlying climate if any"""
await super()._async_internal_set_temperature(temperature)
await super().change_target_temperature(temperature)
self._regulation_algo.set_target_temp(self.target_temperature)
# Is necessary cause control_heating method will not force the update.
@@ -538,28 +516,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
self.auto_regulation_use_device_temp
)
self._attr_extra_state_attributes["auto_start_stop_enable"] = (
self.auto_start_stop_enable
)
self._attr_extra_state_attributes["auto_start_stop_level"] = (
self._auto_start_stop_algo.level
)
self._attr_extra_state_attributes["auto_start_stop_dtmin"] = (
self._auto_start_stop_algo.dt_min
)
self._attr_extra_state_attributes["auto_start_stop_accumulated_error"] = (
self._auto_start_stop_algo.accumulated_error
)
self._attr_extra_state_attributes[
"auto_start_stop_accumulated_error_threshold"
] = self._auto_start_stop_algo.accumulated_error_threshold
self._attr_extra_state_attributes["auto_start_stop_last_switch_date"] = (
self._auto_start_stop_algo.last_switch_date
)
self._attr_extra_state_attributes["follow_underlying_temp_change"] = (
self._follow_underlying_temp_change
)
@@ -593,7 +549,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
_LOGGER.info(
"%s - Force resent target temp cause we turn on some over climate"
)
await self._async_internal_set_temperature(self._target_temp)
await self.change_target_temperature(self._target_temp)
@overrides
def incremente_energy(self):
@@ -602,13 +558,14 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
if self.hvac_mode == HVACMode.OFF:
return
device_power = self.power_manager.device_power
added_energy = 0
if (
self.is_over_climate
and self._underlying_climate_delta_t is not None
and self._device_power
and device_power
):
added_energy = self._device_power * self._underlying_climate_delta_t
added_energy = device_power * self._underlying_climate_delta_t
if self._total_energy is None:
self._total_energy = added_energy
@@ -657,7 +614,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
return
# Find the underlying which have change
under = self.find_underlying_by_entity_id(new_state.entity_id)
under: UnderlyingClimate = self.find_underlying_by_entity_id(new_state.entity_id)
if not under:
_LOGGER.warning(
@@ -669,6 +626,16 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
new_hvac_mode = new_state.state
old_state = event.data.get("old_state")
# Issue #829 - refresh underlying command if it comes back to life
if old_state is not None and new_state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN) and old_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
_LOGGER.warning("%s - underlying %s come back to life. New state=%s, old_state=%s. Will refresh its status", self, under.entity_id, new_state.state, old_state.state)
# Force hvac_mode and target temperature
await under.set_hvac_mode(self.hvac_mode)
await self._send_regulated_temperature(force=True)
return
old_hvac_action = (
old_state.attributes.get("hvac_action")
if old_state and old_state.attributes
@@ -876,7 +843,8 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
and under.last_sent_temperature is not None
):
_LOGGER.debug(
"Do temperature check. under.last_sent_temperature is %s, new_target_temp is %s",
"%s - Do temperature check. under.last_sent_temperature is %s, new_target_temp is %s",
self,
under.last_sent_temperature,
new_target_temp,
)
@@ -898,90 +866,17 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
await end_climate_changed(changes)
async def check_auto_start_stop(self):
"""Check the auto-start-stop and an eventual action
Return False if we should stop the control_heating method"""
slope = (self.last_temperature_slope or 0) / 60 # to have the slope in °/min
action = self._auto_start_stop_algo.calculate_action(
self.hvac_mode,
self._saved_hvac_mode,
self.target_temperature,
self.current_temperature,
slope,
self.now,
)
_LOGGER.debug("%s - auto_start_stop action is %s", self, action)
if action == AUTO_START_STOP_ACTION_OFF and self.is_on:
_LOGGER.info(
"%s - Turning OFF the Vtherm due to auto-start-stop conditions",
self,
)
self.set_hvac_off_reason(HVAC_OFF_REASON_AUTO_START_STOP)
await self.async_turn_off()
# Send an event
self.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "stop",
"name": self.name,
"cause": "Auto stop conditions reached",
"hvac_mode": self.hvac_mode,
"saved_hvac_mode": self._saved_hvac_mode,
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": round(slope, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
# Stop here
return False
elif (
action == AUTO_START_STOP_ACTION_ON
and self.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
):
_LOGGER.info(
"%s - Turning ON the Vtherm due to auto-start-stop conditions", self
)
await self.async_turn_on()
# Send an event
self.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "start",
"name": self.name,
"cause": "Auto start conditions reached",
"hvac_mode": self.hvac_mode,
"saved_hvac_mode": self._saved_hvac_mode,
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": round(slope, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
self.update_custom_attributes()
return True
@overrides
async def async_control_heating(self, force=False, _=None) -> bool:
"""The main function used to run the calculation at each cycle"""
ret = await super().async_control_heating(force, _)
# Check if we need to auto start/stop the Vtherm
if self.auto_start_stop_enable:
continu = await self.check_auto_start_stop()
if not continu:
return ret
else:
_LOGGER.debug("%s - auto start/stop is disabled", self)
continu = await self.auto_start_stop_manager.refresh_state()
if not continu:
return ret
# Continue the normal async_control_heating
# Continue the normal async_control_heating
# Send the regulated temperature to the underlyings
await self._send_regulated_temperature()
@@ -991,37 +886,6 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
return ret
def set_auto_start_stop_enable(self, is_enabled: bool):
"""Enable/Disable the auto-start/stop feature"""
self._is_auto_start_stop_enabled = is_enabled
if (
self.hvac_mode == HVACMode.OFF
and self.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
):
_LOGGER.debug(
"%s - the vtherm is off cause auto-start/stop and enable have been set to false -> starts the VTherm"
)
self.hass.create_task(self.async_turn_on())
# Send an event
self.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "start",
"name": self.name,
"cause": "Auto start stop disabled",
"hvac_mode": self.hvac_mode,
"saved_hvac_mode": self._saved_hvac_mode,
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": round(self.last_temperature_slope or 0, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
self.update_custom_attributes()
def set_follow_underlying_temp_change(self, follow: bool):
"""Set the flaf follow the underlying temperature changes"""
self._follow_underlying_temp_change = follow
@@ -1172,21 +1036,16 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
return False
return True
@property
def auto_start_stop_level(self) -> TYPE_AUTO_START_STOP_LEVELS:
"""Return the auto start/stop level."""
return self._auto_start_stop_level
@property
def auto_start_stop_enable(self) -> bool:
"""Returns the auto_start_stop_enable"""
return self._is_auto_start_stop_enabled
@property
def follow_underlying_temp_change(self) -> bool:
"""Get the follow underlying temp change flag"""
return self._follow_underlying_temp_change
@property
def auto_start_stop_manager(self) -> FeatureAutoStartStopManager:
"""Return the auto-start-stop Manager"""
return self._auto_start_stop_manager
@overrides
def init_underlyings(self):
"""Init the underlyings if not already done"""
@@ -1245,7 +1104,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
@overrides
async def async_set_humidity(self, humidity: int):
"""Set new target humidity."""
_LOGGER.info("%s - Set fan mode: %s", self, humidity)
_LOGGER.info("%s - Set humidity: %s", self, humidity)
if humidity is None:
return
for under in self._underlyings:
@@ -1256,7 +1115,7 @@ class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
@overrides
async def async_set_swing_mode(self, swing_mode):
"""Set new target swing operation."""
_LOGGER.info("%s - Set fan mode: %s", self, swing_mode)
_LOGGER.info("%s - Set swing mode: %s", self, swing_mode)
if swing_mode is None:
return
for under in self._underlyings:

View File

@@ -82,6 +82,7 @@ class ThermostatOverClimateValve(ThermostatOverClimate):
self._tpi_coef_ext,
self._cycle_min,
self._minimal_activation_delay,
self._minimal_deactivation_delay,
self.name,
)
@@ -255,6 +256,13 @@ class ThermostatOverClimateValve(ThermostatOverClimate):
self._attr_min_temp,
)
self._last_regulation_change = self.now
self.reset_last_change_time_from_vtherm()
_LOGGER.debug(
"%s - last_regulation_change is now: %s and last_change_from_vtherm is now: %s", self, self._last_regulation_change, self._last_change_time_from_vtherm
) # pylint: disable=protected-access
for under in self._underlyings_valve_regulation:
await under.set_valve_open_percent()
@@ -263,19 +271,6 @@ class ThermostatOverClimateValve(ThermostatOverClimate):
"""True if the Thermostat is regulated by valve"""
return True
@property
def power_percent(self) -> float | None:
"""Get the current on_percent value"""
if self._prop_algorithm:
return round(self._prop_algorithm.on_percent * 100, 0)
else:
return None
# @property
# def hvac_modes(self) -> list[HVACMode]:
# """Get the hvac_modes"""
# return self._hvac_list
@property
def valve_open_percent(self) -> int:
"""Gives the percentage of valve needed"""

View File

@@ -14,6 +14,8 @@ from .const import (
CONF_UNDERLYING_LIST,
CONF_HEATER_KEEP_ALIVE,
CONF_INVERSE_SWITCH,
CONF_VSWITCH_ON_CMD_LIST,
CONF_VSWITCH_OFF_CMD_LIST,
overrides,
)
@@ -26,29 +28,31 @@ _LOGGER = logging.getLogger(__name__)
class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
"""Representation of a base class for a Versatile Thermostat over a switch."""
_entity_component_unrecorded_attributes = (
BaseThermostat._entity_component_unrecorded_attributes.union(
frozenset(
{
"is_over_switch",
"is_inversed",
"underlying_entities",
"on_time_sec",
"off_time_sec",
"cycle_min",
"function",
"tpi_coef_int",
"tpi_coef_ext",
"power_percent",
"calculated_on_percent",
}
)
_entity_component_unrecorded_attributes = BaseThermostat._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
frozenset(
{
"is_over_switch",
"is_inversed",
"underlying_entities",
"on_time_sec",
"off_time_sec",
"cycle_min",
"function",
"tpi_coef_int",
"tpi_coef_ext",
"power_percent",
"calculated_on_percent",
"vswitch_on_commands",
"vswitch_off_commands",
}
)
)
def __init__(self, hass: HomeAssistant, unique_id, name, config_entry) -> None:
"""Initialize the thermostat over switch."""
self._is_inversed: bool | None = None
self._lst_vswitch_on: list[str] = []
self._lst_vswitch_off: list[str] = []
super().__init__(hass, unique_id, name, config_entry)
@property
@@ -61,14 +65,6 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
"""True if the switch is inversed (for pilot wire and diode)"""
return self._is_inversed is True
@property
def power_percent(self) -> float | None:
"""Get the current on_percent value"""
if self._prop_algorithm:
return round(self._prop_algorithm.on_percent * 100, 0)
else:
return None
@overrides
def post_init(self, config_entry: ConfigData):
"""Initialize the Thermostat"""
@@ -81,14 +77,21 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
self._tpi_coef_ext,
self._cycle_min,
self._minimal_activation_delay,
self._minimal_deactivation_delay,
self.name,
max_on_percent=self._max_on_percent,
)
self._is_inversed = config_entry.get(CONF_INVERSE_SWITCH) is True
lst_switches = config_entry.get(CONF_UNDERLYING_LIST)
self._lst_vswitch_on = config_entry.get(CONF_VSWITCH_ON_CMD_LIST, [])
self._lst_vswitch_off = config_entry.get(CONF_VSWITCH_OFF_CMD_LIST, [])
delta_cycle = self._cycle_min * 60 / len(lst_switches)
for idx, switch in enumerate(lst_switches):
vswitch_on = self._lst_vswitch_on[idx] if idx < len(self._lst_vswitch_on) else None
vswitch_off = self._lst_vswitch_off[idx] if idx < len(self._lst_vswitch_off) else None
self._underlyings.append(
UnderlyingSwitch(
hass=self._hass,
@@ -96,10 +99,11 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
switch_entity_id=switch,
initial_delay_sec=idx * delta_cycle,
keep_alive_sec=config_entry.get(CONF_HEATER_KEEP_ALIVE, 0),
vswitch_on=vswitch_on,
vswitch_off=vswitch_off,
)
)
self._is_inversed = config_entry.get(CONF_INVERSE_SWITCH) is True
self._should_relaunch_control_heating = False
@overrides
@@ -152,6 +156,9 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
"calculated_on_percent"
] = self._prop_algorithm.calculated_on_percent
self._attr_extra_state_attributes["vswitch_on_commands"] = self._lst_vswitch_on
self._attr_extra_state_attributes["vswitch_off_commands"] = self._lst_vswitch_off
self.async_write_ha_state()
_LOGGER.debug(
"%s - Calling update_custom_attributes: %s",
@@ -182,8 +189,10 @@ class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
return
added_energy = 0
if not self.is_over_climate and self.mean_cycle_power is not None:
added_energy = self.mean_cycle_power * float(self._cycle_min) / 60.0
if not self.is_over_climate and self.power_manager.mean_cycle_power is not None:
added_energy = (
self.power_manager.mean_cycle_power * float(self._cycle_min) / 60.0
)
if self._total_energy is None:
self._total_energy = added_energy

View File

@@ -96,6 +96,7 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
self._tpi_coef_ext,
self._cycle_min,
self._minimal_activation_delay,
self._minimal_deactivation_delay,
self.name,
max_on_percent=self._max_on_percent,
)
@@ -265,8 +266,10 @@ class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=a
return
added_energy = 0
if not self.is_over_climate and self.mean_cycle_power is not None:
added_energy = self.mean_cycle_power * float(self._cycle_min) / 60.0
if not self.is_over_climate and self.power_manager.mean_cycle_power is not None:
added_energy = (
self.power_manager.mean_cycle_power * float(self._cycle_min) / 60.0
)
if self._total_energy is None:
self._total_energy = added_energy

View File

@@ -152,15 +152,15 @@
"description": "Διαμόρφωση των προχωρημένων παραμέτρων. Αφήστε τις προεπιλεγμένες τιμές αν δεν γνωρίζετε τι κάνετε.\nΑυτές οι παράμετροι μπορούν να οδηγήσουν σε πολύ κακή ρύθμιση θερμοκρασίας ή ενέργειας.",
"data": {
"minimal_activation_delay": "Ελάχιστη καθυστέρηση ενεργοποίησης",
"security_delay_min": "Καθυστέρηση ασφαλείας (σε λεπτά)",
"security_min_on_percent": "Ελάχιστο ποσοστό ισχύος για ενεργοποίηση λειτουργίας ασφαλείας",
"security_default_on_percent": "Ποσοστό ισχύος για χρήση σε λειτουργία ασφαλείας"
"safety_delay_min": "Καθυστέρηση ασφαλείας (σε λεπτά)",
"safety_min_on_percent": "Ελάχιστο ποσοστό ισχύος για ενεργοποίηση λειτουργίας ασφαλείας",
"safety_default_on_percent": "Ποσοστό ισχύος για χρήση σε λειτουργία ασφαλείας"
},
"data_description": {
"minimal_activation_delay": "Καθυστέρηση σε δευτερόλεπτα κάτω από την οποία η συσκευή δεν θα ενεργοποιηθεί",
"security_delay_min": "Μέγιστη επιτρεπτή καθυστέρηση σε λεπτά μεταξύ δύο μετρήσεων θερμοκρασίας. Πέρα από αυτή την καθυστέρηση, ο θερμοστάτης θα μεταβεί σε κατάσταση ασφαλείας",
"security_min_on_percent": "Ελάχιστη τιμή ποσοστού θέρμανσης για την ενεργοποίηση του προεπιλεγμένου ασφάλειας. Κάτω από αυτό το ποσοστό ισχύος το θερμοστάτη δεν θα πάει στο προεπιλεγμένο ασφάλειας.",
"security_default_on_percent": "Η προεπιλεγμένη τιμή ποσοστού ισχύος θέρμανσης στο προεπιλεγμένο ασφάλειας. Ορίστε σε 0 για να απενεργοποιήσετε τη θερμάστρα στο παρόν ασφάλειας."
"safety_delay_min": "Μέγιστη επιτρεπτή καθυστέρηση σε λεπτά μεταξύ δύο μετρήσεων θερμοκρασίας. Πέρα από αυτή την καθυστέρηση, ο θερμοστάτης θα μεταβεί σε κατάσταση ασφαλείας",
"safety_min_on_percent": "Ελάχιστη τιμή ποσοστού θέρμανσης για την ενεργοποίηση του προεπιλεγμένου ασφάλειας. Κάτω από αυτό το ποσοστό ισχύος το θερμοστάτη δεν θα πάει στο προεπιλεγμένο ασφάλειας.",
"safety_default_on_percent": "Η προεπιλεγμένη τιμή ποσοστού ισχύος θέρμανσης στο προεπιλεγμένο ασφάλειας. Ορίστε σε 0 για να απενεργοποιήσετε τη θερμάστρα στο παρόν ασφάλειας."
}
}
},
@@ -325,15 +325,15 @@
"description": "Διαμόρφωση των προηγμένων παραμέτρων. Αφήστε τις προεπιλεγμένες τιμές εάν δεν γνωρίζετε τι κάνετε.\nΑυτές οι παράμετροι μπορούν να οδηγήσουν σε πολύ κακή ρύθμιση θερμοκρασίας ή ενέργειας.",
"data": {
"minimal_activation_delay": "Ελάχιστη καθυστέρηση ενεργοποίησης",
"security_delay_min": "Καθυστέρηση ασφαλείας (σε λεπτά)",
"security_min_on_percent": "Ελάχιστο ποσοστό ισχύος για τη λειτουργία ασφαλείας",
"security_default_on_percent": "Ποσοστό ισχύος που θα χρησιμοποιηθεί στη λειτουργία ασφαλείας"
"safety_delay_min": "Καθυστέρηση ασφαλείας (σε λεπτά)",
"safety_min_on_percent": "Ελάχιστο ποσοστό ισχύος για τη λειτουργία ασφαλείας",
"safety_default_on_percent": "Ποσοστό ισχύος που θα χρησιμοποιηθεί στη λειτουργία ασφαλείας"
},
"data_description": {
"minimal_activation_delay": "Καθυστέρηση σε δευτερόλεπτα κάτω από την οποία ο εξοπλισμός δεν θα ενεργοποιηθεί",
"security_delay_min": "Μέγιστη επιτρεπόμενη καθυστέρηση σε λεπτά μεταξύ δύο μετρήσεων θερμοκρασίας. Πάνω από αυτή την καθυστέρηση, ο θερμοστάτης θα μεταβεί σε κατάσταση ασφαλείας",
"security_min_on_percent": "Ελάχιστη τιμή ποσοστού θέρμανσης για ενεργοποίηση του προεπιλεγμένου ασφαλείας. Κάτω από αυτό το ποσοστό ισχύος, ο θερμοστάτης δεν θα μεταβεί στο προεπιλεγμένο ασφαλείας",
"security_default_on_percent": "Η προεπιλεγμένη τιμή ποσοστού ισχύος θέρμανσης στο προεπιλεγμένο ασφαλείας. Ορίστε σε 0 για να απενεργοποιήσετε τη θερμάστρα στο παρόν ασφαλείας"
"safety_delay_min": "Μέγιστη επιτρεπόμενη καθυστέρηση σε λεπτά μεταξύ δύο μετρήσεων θερμοκρασίας. Πάνω από αυτή την καθυστέρηση, ο θερμοστάτης θα μεταβεί σε κατάσταση ασφαλείας",
"safety_min_on_percent": "Ελάχιστη τιμή ποσοστού θέρμανσης για ενεργοποίηση του προεπιλεγμένου ασφαλείας. Κάτω από αυτό το ποσοστό ισχύος, ο θερμοστάτης δεν θα μεταβεί στο προεπιλεγμένο ασφαλείας",
"safety_default_on_percent": "Η προεπιλεγμένη τιμή ποσοστού ισχύος θέρμανσης στο προεπιλεγμένο ασφαλείας. Ορίστε σε 0 για να απενεργοποιήσετε τη θερμάστρα στο παρόν ασφαλείας"
}
}
},

View File

@@ -35,7 +35,7 @@
},
"main": {
"title": "Add new Versatile Thermostat",
"description": "Main mandatory attributes",
"description": "Main mandatory attributes&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/base-attributes.md)",
"data": {
"name": "Name",
"thermostat_type": "Thermostat type",
@@ -71,7 +71,7 @@
},
"type": {
"title": "Linked entities",
"description": "Linked entities attributes",
"description": "Linked entities attributes&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/over-switch.md#configuration)",
"data": {
"underlying_entity_ids": "The device(s) to be controlled",
"heater_keep_alive": "Switch keep-alive interval in seconds",
@@ -82,7 +82,11 @@
"auto_regulation_periode_min": "Regulation minimum period",
"auto_regulation_use_device_temp": "Use internal temperature of the underlying",
"inverse_switch_command": "Inverse switch command",
"auto_fan_mode": "Auto fan mode"
"auto_fan_mode": "Auto fan mode",
"on_command_text": "Turn on command customization",
"vswitch_on_command": "Optional turn on commands",
"off_command_text": "Turn off command customization",
"vswitch_off_command": "Optional turn off commands"
},
"data_description": {
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
@@ -94,12 +98,13 @@
"auto_regulation_periode_min": "Duration in minutes between two regulation update",
"auto_regulation_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation",
"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",
"on_command_text": "For underlying of type `select` or `climate` you have to customize the commands."
}
},
"tpi": {
"title": "TPI",
"description": "Time Proportional Integral attributes",
"description": "Time Proportional Integral attributes&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/algorithms.md#lalgorithme-tpi)",
"data": {
"tpi_coef_int": "coef_int",
"tpi_coef_ext": "coef_ext",
@@ -113,17 +118,18 @@
},
"presets": {
"title": "Presets",
"description": "Select if the thermostat will use central preset - deselect for the thermostat to have its own presets",
"description": "Select if the thermostat will use central preset - deselect for the thermostat to have its own presets&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-presets.md)",
"data": {
"use_presets_central_config": "Use central presets configuration"
}
},
"window": {
"title": "Window management",
"description": "Open window management.\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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-window.md)",
"data": {
"window_sensor_entity_id": "Window sensor entity id",
"window_delay": "Window sensor delay (seconds)",
"window_delay": "Window sensor 'on' delay (seconds)",
"window_off_delay": "Window sensor 'off' delay (seconds)",
"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_max_duration": "Maximum duration of automatic window open detection (in min)",
@@ -132,7 +138,8 @@
},
"data_description": {
"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 'on' detection is taken into account",
"window_off_delay": "The delay in seconds before sensor 'off' detection is taken into account. Leave it empty to use the same value as window on delay",
"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_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used",
@@ -142,7 +149,7 @@
},
"motion": {
"title": "Motion management",
"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",
"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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-motion.md)",
"data": {
"motion_sensor_entity_id": "Motion sensor entity id",
"motion_delay": "Activation delay",
@@ -162,7 +169,7 @@
},
"power": {
"title": "Power management",
"description": "Power management attributes.\nGives the power and max power sensor of your home.\nSpecify the power consumption of the heater when on.\nAll sensors and device power should use the same unit (kW or W).",
"description": "Power management attributes.\nGives the power and max power sensor of your home.\nSpecify the power consumption of the heater when on.\nAll sensors and device power should use the same unit (kW or W)&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-power.md)",
"data": {
"power_sensor_entity_id": "Power",
"max_power_sensor_entity_id": "Max power",
@@ -178,7 +185,7 @@
},
"presence": {
"title": "Presence management",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present) and give the corresponding temperature preset setting.",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present) and give the corresponding temperature preset setting&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-presence.md)",
"data": {
"presence_sensor_entity_id": "Presence sensor",
"use_presence_central_config": "Use central presence temperature configuration. Deselect to use specific temperature entities"
@@ -189,25 +196,27 @@
},
"advanced": {
"title": "Advanced parameters",
"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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-advanced.md)",
"data": {
"minimal_activation_delay": "Minimum activation delay",
"security_delay_min": "Safety delay (in minutes)",
"security_min_on_percent": "Minimum power percent to enable safety mode",
"security_default_on_percent": "Power percent to use in safety mode",
"minimal_deactivation_delay": "Minimal deactivation delay",
"safety_delay_min": "Safety delay (in minutes)",
"safety_min_on_percent": "Minimum power percent to enable safety mode",
"safety_default_on_percent": "Power percent to use in safety mode",
"use_advanced_central_config": "Use central advanced configuration"
},
"data_description": {
"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 safety off state",
"security_min_on_percent": "Minimum 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 safety preset. Set to 0 to switch off heater in safety preset",
"minimal_deactivation_delay": "Delay in seconds under which the equipment will be kept active",
"safety_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state",
"safety_min_on_percent": "Minimum heating percent value for safety preset activation. Below this amount of power percent the thermostat won't go into safety preset",
"safety_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"
}
},
"central_boiler": {
"title": "Control of the central boiler",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-central-boiler.md)",
"data": {
"central_boiler_activation_service": "Command to turn-on",
"central_boiler_deactivation_service": "Command to turn-off"
@@ -219,7 +228,7 @@
},
"valve_regulation": {
"title": "Self-regulation with valve",
"description": "Configuration for self-regulation with direct control of the valve",
"description": "Configuration for self-regulation with direct control of the valve&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/self-regulation.md#auto-régulation-par-contrôle-direct-de-la-vanne)",
"data": {
"offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities",
@@ -228,11 +237,11 @@
"min_opening_degrees": "Min opening degrees"
},
"data_description": {
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV has the entity for better regulation. There should be one per underlying climate entities",
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
"closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV has the entity for better regulation. There should be one per underlying climate entities",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"min_opening_degrees": "A comma seperated list of minimal opening degrees. Default to 0. Example: 20, 25, 30"
"min_opening_degrees": "Opening degree minimum value for each underlying device, comma separated. Default to 0. Example: 20, 25, 30"
}
}
},
@@ -281,7 +290,7 @@
},
"main": {
"title": "Main - {name}",
"description": "Main mandatory attributes",
"description": "Main mandatory attributes&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/base-attributes.md)",
"data": {
"name": "Name",
"thermostat_type": "Thermostat type",
@@ -317,7 +326,7 @@
},
"type": {
"title": "Entities - {name}",
"description": "Linked entities attributes",
"description": "Linked entities attributes&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/over-switch.md#configuration)",
"data": {
"underlying_entity_ids": "The device(s) to be controlled",
"heater_keep_alive": "Switch keep-alive interval in seconds",
@@ -328,7 +337,11 @@
"auto_regulation_periode_min": "Regulation minimum period",
"auto_regulation_use_device_temp": "Use internal temperature of the underlying",
"inverse_switch_command": "Inverse switch command",
"auto_fan_mode": "Auto fan mode"
"auto_fan_mode": "Auto fan mode",
"on_command_text": "Turn on command customization",
"vswitch_on_command": "Optional turn on commands",
"off_command_text": "Turn off command customization",
"vswitch_off_command": "Optional turn off commands"
},
"data_description": {
"underlying_entity_ids": "The device(s) to be controlled - 1 is required",
@@ -339,13 +352,14 @@
"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_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation",
"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"
"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",
"on_command_text": "For underlying of type `select` or `climate` you have to customize the commands."
}
},
"tpi": {
"title": "TPI - {name}",
"description": "Time Proportional Integral attributes",
"description": "Time Proportional Integral attributes&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/algorithms.md#lalgorithme-tpi)",
"data": {
"tpi_coef_int": "coef_int",
"tpi_coef_ext": "coef_ext",
@@ -359,17 +373,18 @@
},
"presets": {
"title": "Presets - {name}",
"description": "Check if the thermostat will use central presets. Uncheck and the thermostat will have its own preset entities",
"description": "Check if the thermostat will use central presets. Uncheck and the thermostat will have its own preset entities&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-presets.md)",
"data": {
"use_presets_central_config": "Use central presets configuration"
}
},
"window": {
"title": "Window - {name}",
"description": "Open window management.\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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-window.md)",
"data": {
"window_sensor_entity_id": "Window sensor entity id",
"window_delay": "Window sensor delay (seconds)",
"window_delay": "Window sensor 'on' delay (seconds)",
"window_off_delay": "Window sensor 'off' delay (seconds)",
"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_max_duration": "Maximum duration of automatic window open detection (in min)",
@@ -378,8 +393,8 @@
},
"data_description": {
"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_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used",
"window_delay": "The delay in seconds before sensor 'on' detection is taken into account",
"window_off_delay": "The delay in seconds before sensor 'off' detection is taken into account. Leave it empty to use the same value as window on delay",
"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",
"use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm",
@@ -388,7 +403,7 @@
},
"motion": {
"title": "Motion - {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",
"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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-motion.md)",
"data": {
"motion_sensor_entity_id": "Motion sensor entity id",
"motion_delay": "Activation delay",
@@ -408,7 +423,7 @@
},
"power": {
"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).",
"description": "Power management attributes.\nGives the power and max power sensor of your home.\nSpecify the power consumption of the heater when on.\nAll sensors and device power should use the same unit (kW or W)&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-power.md)",
"data": {
"power_sensor_entity_id": "Power",
"max_power_sensor_entity_id": "Max power",
@@ -424,7 +439,7 @@
},
"presence": {
"title": "Presence - {name}",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present) and give the corresponding temperature preset setting.",
"description": "Presence management attributes.\nGives the a presence sensor of your home (true is someone is present) and give the corresponding temperature preset setting&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-presence.md)",
"data": {
"presence_sensor_entity_id": "Presence sensor",
"use_presence_central_config": "Use central presence temperature configuration. Uncheck to use specific temperature entities"
@@ -435,25 +450,25 @@
},
"advanced": {
"title": "Advanced - {name}",
"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.",
"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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/en/feature-advanced.md)",
"data": {
"minimal_activation_delay": "Minimum activation delay",
"security_delay_min": "Safety delay (in minutes)",
"security_min_on_percent": "Minimum power percent to enable safety mode",
"security_default_on_percent": "Power percent to use in safety mode",
"safety_delay_min": "Safety delay (in minutes)",
"safety_min_on_percent": "Minimum power percent to enable safety mode",
"safety_default_on_percent": "Power percent to use in safety mode",
"use_advanced_central_config": "Use central advanced configuration"
},
"data_description": {
"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 safety off state",
"security_min_on_percent": "Minimum 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 safety preset. Set to 0 to switch off heater in safety preset",
"safety_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state",
"safety_min_on_percent": "Minimum heating percent value for safety preset activation. Below this amount of power percent the thermostat won't go into safety preset",
"safety_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"
}
},
"central_boiler": {
"title": "Control of the central boiler - {name}",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-central-boiler.md)",
"data": {
"central_boiler_activation_service": "Command to turn-on",
"central_boiler_deactivation_service": "Command to turn-off"
@@ -465,7 +480,7 @@
},
"valve_regulation": {
"title": "Self-regulation with valve - {name}",
"description": "Configuration for self-regulation with direct control of the valve",
"description": "Configuration for self-regulation with direct control of the valve&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/self-regulation.md#auto-régulation-par-contrôle-direct-de-la-vanne)",
"data": {
"offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities",
@@ -474,11 +489,11 @@
"min_opening_degrees": "Min opening degrees"
},
"data_description": {
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV has the entity for better regulation. There should be one per underlying climate entities",
"opening_degree_entity_ids": "The list of the 'opening degree' entities. There should be one per underlying climate entities",
"closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV have the entity for better regulation. There should be one per underlying climate entities",
"closing_degree_entity_ids": "The list of the 'closing degree' entities. Set it if your TRV has the entity for better regulation. There should be one per underlying climate entities",
"proportional_function": "Algorithm to use (TPI is the only one for now)",
"min_opening_degrees": "A comma seperated list of minimal opening degrees. Default to 0. Example: 20, 25, 30"
"min_opening_degrees": "Opening degree minimum value for each underlying device, comma separated. Default to 0. Example: 20, 25, 30"
}
}
},
@@ -488,7 +503,8 @@
"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",
"valve_regulation_nb_entities_incorrect": "The number of valve entities for valve regulation should be equal to the number of underlyings"
"valve_regulation_nb_entities_incorrect": "The number of valve entities for valve regulation should be equal to the number of underlyings",
"min_opening_degrees_format": "A comma separated list of positive integer is expected. Example: 20, 25, 30"
},
"abort": {
"already_configured": "Device is already configured"
@@ -555,7 +571,7 @@
"preset_mode": {
"state": {
"power": "Shedding",
"security": "Safety",
"safety": "Safety",
"none": "Manual",
"frost": "Frost"
}

View File

@@ -5,6 +5,7 @@
"step": {
"user": {
"title": "Type du nouveau Versatile Thermostat",
"description": "Choisissez le type de thermostat que vous voulez créer&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/creation.md)",
"data": {
"thermostat_type": "Type de thermostat"
},
@@ -35,7 +36,7 @@
},
"main": {
"title": "Ajout d'un nouveau thermostat",
"description": "Principaux attributs obligatoires",
"description": "Principaux attributs obligatoires&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/base-attributes.md)",
"data": {
"name": "Nom",
"thermostat_type": "Type de thermostat",
@@ -71,7 +72,7 @@
},
"type": {
"title": "Entité(s) liée(s)",
"description": "Attributs de(s) l'entité(s) liée(s)",
"description": "Attributs de(s) l'entité(s) liée(s)&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/over-switch.md#configuration)",
"data": {
"underlying_entity_ids": "Les équipements à controller",
"heater_keep_alive": "keep-alive (sec)",
@@ -82,7 +83,11 @@
"auto_regulation_periode_min": "Période minimale de régulation",
"auto_regulation_use_device_temp": "Compenser la température interne du sous-jacent",
"inverse_switch_command": "Inverser la commande",
"auto_fan_mode": " Auto ventilation mode"
"auto_fan_mode": " Auto ventilation mode",
"on_command_text": "Personnalisation des commandes d'allumage",
"vswitch_on_command": "Commande d'allumage (optionnel)",
"off_command_text": "Personnalisation des commandes d'extinction",
"vswitch_off_command": "Commande d'extinction (optionnel)"
},
"data_description": {
"underlying_entity_ids": "La liste des équipements qui seront controlés par ce VTherm",
@@ -94,12 +99,13 @@
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
"auto_regulation_use_device_temp": "Compenser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important",
"on_command_text": "Pour les sous-jacents de type `select` ou `climate` vous devez personnaliser les commandes."
}
},
"tpi": {
"title": "TPI",
"description": "Attributs de l'algo Time Proportional Integral",
"description": "Attributs de l'algo Time Proportional Integral&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/algorithms.md#lalgorithme-tpi)",
"data": {
"tpi_coef_int": "coeff_int",
"tpi_coef_ext": "coeff_ext",
@@ -113,17 +119,18 @@
},
"presets": {
"title": "Pre-réglages",
"description": "Cochez pour que ce thermostat utilise les pré-réglages de la configuration centrale. Décochez pour utiliser des entités de température spécifiques",
"description": "Cochez pour que ce thermostat utilise les pré-réglages de la configuration centrale. Décochez pour utiliser des entités de température spécifiques&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-presets.md)",
"data": {
"use_presets_central_config": "Utiliser la configuration des pré-réglages centrale"
}
},
"window": {
"title": "Gestion d'une ouverture",
"description": "Coupe le radiateur si l'ouverture est ouverte.\nLaissez l'id d'entité vide pour utiliser la détection automatique.",
"description": "Coupe le radiateur si l'ouverture est ouverte.\nLaissez l'id d'entité vide pour utiliser la détection automatique&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-window.md)",
"data": {
"window_sensor_entity_id": "Détecteur d'ouverture (entity id)",
"window_delay": "Délai avant extinction (secondes)",
"window_delay": "Délai de prise en compte à l'ouverture (secondes)",
"window_off_delay": "Délai de prise compte à la fermeture (secondes)",
"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_max_duration": "Durée maximum d'une extinction automatique (en min)",
@@ -132,7 +139,8 @@
},
"data_description": {
"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 lors de la détection d'une ouverture",
"window_off_delay": "Le délai (en secondes) avant que le changement du détecteur soit pris en compte lors de la détection d'une fermeture. Laissez vide pour utiliser le même délai à l'ouveture et à la fermeture",
"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_max_duration": "Valeur recommandée: 60 (1 heure). Laissez vide si vous n'utilisez pas la détection automatique",
@@ -142,7 +150,7 @@
},
"motion": {
"title": "Gestion de la détection de mouvement",
"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.",
"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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-motion.md)",
"data": {
"motion_sensor_entity_id": "Détecteur de mouvement",
"motion_delay": "Délai d'activation",
@@ -153,7 +161,7 @@
},
"data_description": {
"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é (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é",
"no_motion_preset": "Preset à utiliser si pas de mouvement détecté",
@@ -162,7 +170,7 @@
},
"power": {
"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)&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-power.md)",
"data": {
"power_sensor_entity_id": "Capteur de puissance totale (entity id)",
"max_power_sensor_entity_id": "Capteur de puissance Max (entity id)",
@@ -177,8 +185,8 @@
}
},
"presence": {
"title": "Gestion de la présence",
"description": "Donnez un capteur de présence (true si quelqu'un est présent) et les températures cibles à utiliser en cas d'abs.",
"title": "Gestion de la présenc",
"description": "Donnez un capteur de présence (true si quelqu'un est présent) et les températures cibles à utiliser en cas d'absence&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-presence.md)",
"data": {
"presence_sensor_entity_id": "Capteur de présence",
"use_presence_central_config": "Utiliser la configuration centrale des températures en cas d'absence. Décochez pour avoir des entités de température dédiées"
@@ -189,25 +197,27 @@
},
"advanced": {
"title": "Parameters avancés",
"description": "Configuration des paramètres avancés. Laissez les valeurs par défaut si vous ne savez pas ce que vous faites.\nCes paramètres peuvent induire des mauvais comportements du thermostat.",
"description": "Configuration des paramètres avancés. Laissez les valeurs par défaut si vous ne savez pas ce que vous faites.\nCes paramètres peuvent induire des mauvais comportements du thermostat&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-advanced.md)",
"data": {
"minimal_activation_delay": "Délai minimal d'activation",
"security_delay_min": "Délai maximal entre 2 mesures de températures",
"security_min_on_percent": "Pourcentage minimal de puissance",
"security_default_on_percent": "Pourcentage de puissance a utiliser en mode securité",
"minimal_deactivation_delay": "Délai de désactivation minimal",
"safety_delay_min": "Délai maximal entre 2 mesures de températures",
"safety_min_on_percent": "Pourcentage minimal de puissance",
"safety_default_on_percent": "Pourcentage de puissance a utiliser en mode securité",
"use_advanced_central_config": "Utiliser la configuration centrale avancée"
},
"data_description": {
"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_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é",
"minimal_activation_delay": "Délai en secondes en-dessous duquel l'équipement ne sera pas activé",
"minimal_deactivation_delay": "Délai en secondes en-dessous duquel l'équipement se laissé actif",
"safety_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é",
"safety_min_on_percent": "Seuil minimal de pourcentage de chauffage en-dessous duquel le préréglage sécurité ne sera jamais activé",
"safety_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`",
"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`&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-central-boiler.md)",
"data": {
"central_boiler_activation_service": "Commande pour allumer",
"central_boiler_deactivation_service": "Commande pour éteindre"
@@ -219,7 +229,7 @@
},
"valve_regulation": {
"title": "Auto-régulation par vanne",
"description": "Configuration de l'auto-régulation par controle direct de la vanne",
"description": "Configuration de l'auto-régulation par controle direct de la vanne&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/self-regulation.md#auto-régulation-par-contrôle-direct-de-la-vanne)",
"data": {
"offset_calibration_entity_ids": "Entités de 'calibrage du décalage''",
"opening_degree_entity_ids": "Entités 'ouverture de vanne'",
@@ -251,6 +261,7 @@
"step": {
"user": {
"title": "Type - {name}",
"description": "Choisissez le type de thermostat que vous voulez créer&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/creation.md)",
"data": {
"thermostat_type": "Type de thermostat"
},
@@ -281,7 +292,7 @@
},
"main": {
"title": "Attributs - {name}",
"description": "Principaux attributs obligatoires",
"description": "Principaux attributs obligatoires&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/base-attributes.md)",
"data": {
"name": "Nom",
"thermostat_type": "Type de thermostat",
@@ -317,7 +328,7 @@
},
"type": {
"title": "Entité(s) liée(s) - {name}",
"description": "Attributs de(s) l'entité(s) liée(s)",
"description": "Attributs de(s) l'entité(s) liée(s)&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/over-switch.md#configuration)",
"data": {
"underlying_entity_ids": "Les équipements à controller",
"heater_keep_alive": "keep-alive (sec)",
@@ -328,7 +339,11 @@
"auto_regulation_periode_min": "Période minimale de régulation",
"auto_regulation_use_device_temp": "Compenser la température interne du sous-jacent",
"inverse_switch_command": "Inverser la commande",
"auto_fan_mode": " Auto ventilation mode"
"auto_fan_mode": " Auto ventilation mode",
"on_command_text": "Personnalisation des commandes d'allumage",
"vswitch_on_command": "Commande d'allumage (optionnel)",
"off_command_text": "Personnalisation des commandes d'extinction",
"vswitch_off_command": "Commande d'extinction (optionnel)"
},
"data_description": {
"underlying_entity_ids": "La liste des équipements qui seront controlés par ce VTherm",
@@ -340,12 +355,13 @@
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
"auto_regulation_use_device_temp": "Compenser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important",
"on_command_text": "Pour les sous-jacents de type `select` ou `climate`"
}
},
"tpi": {
"title": "TPI - {name}",
"description": "Attributs de l'algo Time Proportional Integral",
"description": "Attributs de l'algo Time Proportional Integral&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/algorithms.md#lalgorithme-tpi)",
"data": {
"tpi_coef_int": "coeff_int : Coefficient à utiliser pour le delta de température interne",
"tpi_coef_ext": "coeff_ext : Coefficient à utiliser pour le delta de température externe"
@@ -353,17 +369,18 @@
},
"presets": {
"title": "Pre-réglages - {name}",
"description": "Cochez pour que ce thermostat utilise les pré-réglages de la configuration centrale. Décochez pour utiliser des entités de température spécifiques",
"description": "Cochez pour que ce thermostat utilise les pré-réglages de la configuration centrale. Décochez pour utiliser des entités de température spécifiques&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-presets.md)",
"data": {
"use_presets_central_config": "Utiliser la configuration des pré-réglages centrale"
}
},
"window": {
"title": "Ouverture - {name}",
"description": "Coupe le radiateur si l'ouverture est ouverte.\nLaissez l'id d'entité vide pour utiliser la détection automatique.",
"description": "Coupe le radiateur si l'ouverture est ouverte.\nLaissez l'id d'entité vide pour utiliser la détection automatique&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-window.md)",
"data": {
"window_sensor_entity_id": "Détecteur d'ouverture (entity id)",
"window_delay": "Délai avant extinction (secondes)",
"window_off_delay": "Délai de prise compte à la fermeture (secondes)",
"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_max_duration": "Durée maximum d'une extinction automatique (en min)",
@@ -373,6 +390,7 @@
"data_description": {
"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_off_delay": "Le délai (en secondes) avant que le changement du détecteur soit pris en compte lors de la détection d'une fermeture. Laissez vide pour utiliser le même délai à l'ouveture et à la fermeture",
"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_max_duration": "Valeur recommandée: 60 (1 heure). Laissez vide si vous n'utilisez pas la détection automatique",
@@ -382,7 +400,7 @@
},
"motion": {
"title": "Mouvement - {name}",
"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.",
"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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-motion.md)",
"data": {
"motion_sensor_entity_id": "Détecteur de mouvement",
"motion_delay": "Délai d'activation",
@@ -402,7 +420,7 @@
},
"power": {
"title": "Puissance - {name}",
"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).",
"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)&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-power.md)",
"data": {
"power_sensor_entity_id": "Puissance totale",
"max_power_sensor_entity_id": "Capteur de puissance Max (entity id)",
@@ -418,7 +436,7 @@
},
"presence": {
"title": "Présence - {name}",
"description": "Donnez un capteur de présence (true si quelqu'un est présent) et les températures cibles à utiliser en cas d'abs.",
"description": "Donnez un capteur de présence (true si quelqu'un est présent) et les températures cibles à utiliser en cas d'absence&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-presence.md)",
"data": {
"presence_sensor_entity_id": "Capteur de présence",
"use_presence_central_config": "Utiliser la configuration centrale des températures en cas d'absence. Décochez pour avoir des entités de température dédiées"
@@ -429,25 +447,27 @@
},
"advanced": {
"title": "Avancés - {name}",
"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.",
"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&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-advanced.md)",
"data": {
"minimal_activation_delay": "Délai minimal d'activation",
"security_delay_min": "Délai maximal entre 2 mesures de températures",
"security_min_on_percent": "Pourcentage minimal de puissance",
"security_default_on_percent": "Pourcentage de puissance a utiliser en mode securité",
"minimal_deactivation_delay": "Délai de désactivation minimal",
"safety_delay_min": "Délai maximal entre 2 mesures de températures",
"safety_min_on_percent": "Pourcentage minimal de puissance",
"safety_default_on_percent": "Pourcentage de puissance a utiliser en mode securité",
"use_advanced_central_config": "Utiliser la configuration centrale avancée"
},
"data_description": {
"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_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é",
"minimal_activation_delay": "Délai en secondes en-dessous duquel l'équipement ne sera pas activé",
"minimal_deactivation_delay": "Délai en secondes en-dessous duquel l'équipement se laissé actif",
"safety_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é",
"safety_min_on_percent": "Seuil minimal de pourcentage de chauffage en-dessous duquel le préréglage sécurité ne sera jamais activé",
"safety_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`",
"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`&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/feature-central-boiler.md)",
"data": {
"central_boiler_activation_service": "Commande pour allumer",
"central_boiler_deactivation_service": "Commande pour éteindre"
@@ -459,7 +479,7 @@
},
"valve_regulation": {
"title": "Auto-régulation par vanne - {name}",
"description": "Configuration de l'auto-régulation par controle direct de la vanne",
"description": "Configuration de l'auto-régulation par controle direct de la vanne&nbsp;[![?](https://img.icons8.com/color/18/help.png)](https://github.com/jmcollin78/versatile_thermostat/blob/main/documentation/fr/self-regulation.md#auto-régulation-par-contrôle-direct-de-la-vanne)",
"data": {
"offset_calibration_entity_ids": "Entités de 'calibrage du décalage''",
"opening_degree_entity_ids": "Entités 'ouverture de vanne'",
@@ -483,7 +503,8 @@
"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",
"valve_regulation_nb_entities_incorrect": "Le nombre d'entités pour la régulation par vanne doit être égal au nombre d'entité sous-jacentes",
"min_opening_degrees_format": "Une liste d'entiers positifs séparés par des ',' est attendu. Exemple : 20, 25, 30"
"min_opening_degrees_format": "Une liste d'entiers positifs séparés par des ',' est attendu. Exemple : 20, 25, 30",
"vswitch_configuration_incorrect": "La configuration de la personnalisation des commandes est incorrecte. Elle est obligatoire pour les sous-jacents non switch et le format doit être 'service_name[/attribut:valeur]'. Plus d'informations dans le README."
},
"abort": {
"already_configured": "Le device est déjà configuré"
@@ -495,7 +516,7 @@
"thermostat_central_config": "Configuration centrale",
"thermostat_over_switch": "Thermostat sur un switch",
"thermostat_over_climate": "Thermostat sur un autre thermostat",
"thermostat_over_valve": "Thermostat sur une valve"
"thermostat_over_valve": "Thermostat sur une vanne"
}
},
"auto_regulation_mode": {
@@ -550,7 +571,7 @@
"preset_mode": {
"state": {
"power": "Délestage",
"security": "Sécurité",
"safety": "Sécurité",
"none": "Manuel",
"frost": "Hors Gel"
}

View File

@@ -143,15 +143,15 @@
"description": "Configurazione avanzata dei parametri. Lasciare i valori predefiniti se non conoscete cosa state modificando.\nQuesti parametri possono determinare una pessima gestione della temperatura e della potenza.",
"data": {
"minimal_activation_delay": "Ritardo minimo di accensione",
"security_delay_min": "Ritardo di sicurezza (in minuti)",
"security_min_on_percent": "Percentuale minima di potenza per la modalità di sicurezza",
"security_default_on_percent": "Percentuale di potenza per la modalità di sicurezza"
"safety_delay_min": "Ritardo di sicurezza (in minuti)",
"safety_min_on_percent": "Percentuale minima di potenza per la modalità di sicurezza",
"safety_default_on_percent": "Percentuale di potenza per la modalità di sicurezza"
},
"data_description": {
"minimal_activation_delay": "Ritardo in secondi al di sotto del quale l'apparecchiatura non verrà attivata",
"security_delay_min": "Ritardo massimo consentito in minuti tra due misure di temperatura. Al di sopra di questo ritardo, il termostato passerà allo stato di sicurezza",
"security_min_on_percent": "Soglia percentuale minima di riscaldamento al di sotto della quale il preset di sicurezza non verrà mai attivato",
"security_default_on_percent": "Valore percentuale predefinito della potenza di riscaldamento nella modalità di sicurezza. Impostare a 0 per spegnere il riscaldatore nella modalità di sicurezza"
"safety_delay_min": "Ritardo massimo consentito in minuti tra due misure di temperatura. Al di sopra di questo ritardo, il termostato passerà allo stato di sicurezza",
"safety_min_on_percent": "Soglia percentuale minima di riscaldamento al di sotto della quale il preset di sicurezza non verrà mai attivato",
"safety_default_on_percent": "Valore percentuale predefinito della potenza di riscaldamento nella modalità di sicurezza. Impostare a 0 per spegnere il riscaldatore nella modalità di sicurezza"
}
}
},
@@ -307,15 +307,15 @@
"description": "Configurazione avanzata dei parametri. Lasciare i valori predefiniti se non conoscete cosa state modificando.\nQuesti parametri possono determinare una pessima gestione della temperatura e della potenza.",
"data": {
"minimal_activation_delay": "Ritardo minimo di accensione",
"security_delay_min": "Ritardo di sicurezza (in minuti)",
"security_min_on_percent": "Percentuale minima di potenza per la modalità di sicurezza",
"security_default_on_percent": "Percentuale di potenza per la modalità di sicurezza"
"safety_delay_min": "Ritardo di sicurezza (in minuti)",
"safety_min_on_percent": "Percentuale minima di potenza per la modalità di sicurezza",
"safety_default_on_percent": "Percentuale di potenza per la modalità di sicurezza"
},
"data_description": {
"minimal_activation_delay": "Ritardo in secondi al di sotto del quale l'apparecchiatura non verrà attivata",
"security_delay_min": "Ritardo massimo consentito in minuti tra due misure di temperatura. Al di sopra di questo ritardo, il termostato passerà allo stato di sicurezza",
"security_min_on_percent": "Soglia percentuale minima di riscaldamento al di sotto della quale il preset di sicurezza non verrà mai attivato",
"security_default_on_percent": "Valore percentuale predefinito della potenza di riscaldamento nella modalità di sicurezza. Impostare a 0 per spegnere il riscaldatore nella modalità di sicurezza"
"safety_delay_min": "Ritardo massimo consentito in minuti tra due misure di temperatura. Al di sopra di questo ritardo, il termostato passerà allo stato di sicurezza",
"safety_min_on_percent": "Soglia percentuale minima di riscaldamento al di sotto della quale il preset di sicurezza non verrà mai attivato",
"safety_default_on_percent": "Valore percentuale predefinito della potenza di riscaldamento nella modalità di sicurezza. Impostare a 0 per spegnere il riscaldatore nella modalità di sicurezza"
}
}
},

View File

@@ -211,16 +211,16 @@
"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": {
"minimal_activation_delay": "Minimálne oneskorenie aktivácie",
"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_default_on_percent": "Percento výkonu na použitie v bezpečnostnom režime",
"safety_delay_min": "Bezpečnostné oneskorenie (v minútach)",
"safety_min_on_percent": "Minimálne percento výkonu na aktiváciu bezpečnostného režimu",
"safety_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": {
"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_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",
"safety_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",
"safety_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",
"safety_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"
}
}
@@ -446,16 +446,16 @@
"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": {
"minimal_activation_delay": "Minimálne oneskorenie aktivácie",
"security_delay_min": "Bezpečnostné oneskorenie (v minútach)",
"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",
"safety_delay_min": "Bezpečnostné oneskorenie (v minútach)",
"safety_min_on_percent": "Minimálne percento výkonu pre bezpečnostný režim",
"safety_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": {
"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_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",
"safety_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",
"safety_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",
"safety_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"
}
}
@@ -578,4 +578,4 @@
}
}
}
}
}

View File

@@ -2,10 +2,12 @@
""" Underlying entities classes """
import logging
from typing import Any
import re
from typing import Any, Dict, Tuple
from enum import StrEnum
from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, STATE_UNAVAILABLE
from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, STATE_OFF, STATE_UNAVAILABLE
from homeassistant.core import State
from homeassistant.exceptions import ServiceNotFound
@@ -190,21 +192,27 @@ class UnderlyingEntity:
"""capping of the value send to the underlying eqt"""
return value
async def turn_off_and_cancel_cycle(self):
"""Turn off and cancel eventual running cycle"""
self._cancel_cycle()
await self.turn_off()
async def check_overpowering(self) -> bool:
"""Check that a underlying can be turned on, else
activate the overpowering state of the VTherm associated.
Returns True if the check is ok (no overpowering needed)"""
if not await self._thermostat.power_manager.check_power_available():
_LOGGER.debug("%s - overpowering is detected", self)
await self._thermostat.power_manager.set_overpowering(True)
return False
return True
class UnderlyingSwitch(UnderlyingEntity):
"""Represent a underlying switch"""
_initialDelaySec: int
_on_time_sec: int
_off_time_sec: int
def __init__(
self,
hass: HomeAssistant,
thermostat: Any,
switch_entity_id: str,
initial_delay_sec: int,
keep_alive_sec: float,
self, hass: HomeAssistant, thermostat: Any, switch_entity_id: str, initial_delay_sec: int, keep_alive_sec: float, vswitch_on: str = None, vswitch_off: str = None
) -> None:
"""Initialize the underlying switch"""
@@ -220,6 +228,14 @@ class UnderlyingSwitch(UnderlyingEntity):
self._on_time_sec = 0
self._off_time_sec = 0
self._keep_alive = IntervalCaller(hass, keep_alive_sec)
self._vswitch_on = vswitch_on.strip() if vswitch_on else None
self._vswitch_off = vswitch_off.strip() if vswitch_off else None
self._domain = self._entity_id.split(".")[0]
# build command
command, data, state_on = self.build_command(use_on=True)
self._on_command = {"command": command, "data": data, "state": state_on}
command, data, state_off = self.build_command(use_on=False)
self._off_command = {"command": command, "data": data, "state": state_off}
@property
def initial_delay_sec(self):
@@ -228,7 +244,7 @@ class UnderlyingSwitch(UnderlyingEntity):
@overrides
@property
def is_inversed(self):
def is_inversed(self) -> bool:
"""Tells if the switch command should be inversed"""
return self._thermostat.is_inversed
@@ -260,10 +276,15 @@ class UnderlyingSwitch(UnderlyingEntity):
@property
def is_device_active(self):
"""If the toggleable device is currently active."""
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
)
# 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
# )
is_on = self._hass.states.is_state(self._entity_id, self._on_command.get("state"))
# if self.is_inversed:
# return not is_on
return is_on
async def _keep_alive_callback(self):
"""Keep alive: Turn on if already turned on, turn off if already turned off."""
@@ -290,18 +311,48 @@ class UnderlyingSwitch(UnderlyingEntity):
)
await (self.turn_on() if self.is_device_active else self.turn_off())
# @overrides this breaks some unit tests TypeError: object MagicMock can't be used in 'await' expression
def build_command(self, use_on: bool) -> Tuple[str, Dict[str, str]]:
"""Build a command and returns a command and a dict as data"""
value = None
data = {ATTR_ENTITY_ID: self._entity_id}
take_on = (use_on and not self.is_inversed) or (not use_on and self.is_inversed)
vswitch = self._vswitch_on if take_on else self._vswitch_off
if vswitch:
pattern = r"^(?P<command>[^\s/]+)(?:/(?P<argument>[^\s:]+)(?::(?P<value>[^\s]+))?)?$"
match = re.match(pattern, vswitch)
if match:
# Extraire les groupes nommés
command = match.group("command")
argument = match.group("argument")
value = match.group("value")
if argument is not None and value is not None:
data.update({argument: value})
else:
raise ValueError(f"Invalid input format: {vswitch}. Must be conform to 'command[/argument[:value]]'")
else:
command = SERVICE_TURN_ON if take_on else SERVICE_TURN_OFF
if value is None:
value = STATE_ON if take_on else STATE_OFF
return command, data, value
async def turn_off(self):
"""Turn heater toggleable device off."""
self._keep_alive.cancel() # Cancel early to avoid a turn_on/turn_off race condition
_LOGGER.debug("%s - Stopping underlying entity %s", self, self._entity_id)
command = SERVICE_TURN_OFF if not self.is_inversed else SERVICE_TURN_ON
domain = self._entity_id.split(".")[0]
command = self._off_command.get("command")
data = self._off_command.get("data")
# This may fails if called after shutdown
try:
try:
data = {ATTR_ENTITY_ID: self._entity_id}
await self._hass.services.async_call(domain, command, data)
_LOGGER.debug("%s - Sending command %s with data=%s", self, command, data)
await self._hass.services.async_call(self._domain, command, data)
self._keep_alive.set_async_action(self._keep_alive_callback)
except Exception:
self._keep_alive.cancel()
@@ -313,13 +364,18 @@ class UnderlyingSwitch(UnderlyingEntity):
"""Turn heater toggleable device on."""
self._keep_alive.cancel() # Cancel early to avoid a turn_on/turn_off race condition
_LOGGER.debug("%s - Starting underlying entity %s", self, self._entity_id)
command = SERVICE_TURN_ON if not self.is_inversed else SERVICE_TURN_OFF
domain = self._entity_id.split(".")[0]
if not await self.check_overpowering():
return False
command = self._on_command.get("command")
data = self._on_command.get("data")
try:
try:
data = {ATTR_ENTITY_ID: self._entity_id}
await self._hass.services.async_call(domain, command, data)
_LOGGER.debug("%s - Sending command %s with data=%s", self, command, data)
await self._hass.services.async_call(self._domain, command, data)
self._keep_alive.set_async_action(self._keep_alive_callback)
return True
except Exception:
self._keep_alive.cancel()
raise
@@ -409,11 +465,8 @@ class UnderlyingSwitch(UnderlyingEntity):
await self.turn_off()
return
if await self._thermostat.check_overpowering():
_LOGGER.debug("%s - End of cycle (3)", self)
return
# safety mode could have change the on_time percent
await self._thermostat.check_safety()
await self._thermostat.safety_manager.refresh_state()
time = self._on_time_sec
action_label = "start"
@@ -426,7 +479,8 @@ class UnderlyingSwitch(UnderlyingEntity):
time // 60,
time % 60,
)
await self.turn_on()
if not await self.turn_on():
return
else:
_LOGGER.debug("%s - No action on heater cause duration is 0", self)
self._async_cancel_cycle = self.call_later(
@@ -551,6 +605,10 @@ class UnderlyingClimate(UnderlyingEntity):
)
return False
# When turning on a climate, check that power is available
if hvac_mode in (HVACMode.HEAT, HVACMode.COOL) and not await self.check_overpowering():
return False
data = {ATTR_ENTITY_ID: self._entity_id, "hvac_mode": hvac_mode}
await self._hass.services.async_call(
CLIMATE_DOMAIN,
@@ -589,7 +647,7 @@ class UnderlyingClimate(UnderlyingEntity):
async def set_humidity(self, humidity: int):
"""Set new target humidity."""
_LOGGER.info("%s - Set fan mode: %s", self, humidity)
_LOGGER.info("%s - Set humidity: %s", self, humidity)
if not self.is_initialized:
return
data = {
@@ -605,7 +663,7 @@ class UnderlyingClimate(UnderlyingEntity):
async def set_swing_mode(self, swing_mode):
"""Set new target swing operation."""
_LOGGER.info("%s - Set fan mode: %s", self, swing_mode)
_LOGGER.info("%s - Set swing mode: %s", self, swing_mode)
if not self.is_initialized:
return
data = {
@@ -626,22 +684,23 @@ class UnderlyingClimate(UnderlyingEntity):
# Issue 508 we have to take care of service set_temperature or set_range
target_temp = self.cap_sent_value(temperature)
if (
ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
in self._underlying_climate.supported_features
):
data = {
ATTR_ENTITY_ID: self._entity_id,
"target_temp_high": target_temp,
"target_temp_low": target_temp,
# issue 518 - we should send also the target temperature, even in TARGET RANGE
"temperature": target_temp,
}
else:
data = {
ATTR_ENTITY_ID: self._entity_id,
"temperature": target_temp,
}
data = {
ATTR_ENTITY_ID: self._entity_id,
}
_LOGGER.info("%s - Set setpoint temperature to: %s", self, target_temp)
# Issue 807 add TARGET_TEMPERATURE only if in the features
if ClimateEntityFeature.TARGET_TEMPERATURE_RANGE in self._underlying_climate.supported_features:
data.update(
{
"target_temp_high": target_temp,
"target_temp_low": target_temp,
}
)
if ClimateEntityFeature.TARGET_TEMPERATURE in self._underlying_climate.supported_features:
data["temperature"] = target_temp
await self._hass.services.async_call(
CLIMATE_DOMAIN,
@@ -650,6 +709,7 @@ class UnderlyingClimate(UnderlyingEntity):
)
self._last_sent_temperature = target_temp
_LOGGER.debug("%s - Last_sent_temperature is now: %s", self, self._last_sent_temperature)
@property
def last_sent_temperature(self) -> float | None:
@@ -1080,10 +1140,17 @@ class UnderlyingValveRegulation(UnderlyingValve):
)
return
# Send opening_degree
if 0 < self._percent_open < self._min_opening_degree:
self._percent_open = self._min_opening_degree
# Caclulate percent_open
if self._percent_open >= 1:
self._percent_open = round(
self._min_opening_degree
+ (self._percent_open
* (100 - self._min_opening_degree) / 100)
)
else:
self._percent_open = 0
# Send opening_degree
await super().send_percent_open()
# Send closing_degree if set

View File

@@ -1,6 +1,7 @@
""" The API of Versatile Thermostat"""
import logging
from datetime import datetime
from homeassistant.core import HomeAssistant
from homeassistant.config_entries import ConfigEntry
@@ -16,8 +17,11 @@ from .const import (
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_MAX_ON_PERCENT,
NowClass,
)
from .central_feature_power_manager import CentralFeaturePowerManager
VTHERM_API_NAME = "vtherm_api"
_LOGGER = logging.getLogger(__name__)
@@ -62,6 +66,12 @@ class VersatileThermostatAPI(dict):
# A dict that will store all Number entities which holds the temperature
self._number_temperatures = dict()
self._max_on_percent = None
self._central_power_manager = CentralFeaturePowerManager(
VersatileThermostatAPI._hass, self
)
# the current time (for testing purpose)
self._now = None
def find_central_configuration(self):
"""Search for a central configuration"""
@@ -125,15 +135,10 @@ class VersatileThermostatAPI(dict):
):
"""register the two number entities needed for boiler activation"""
self._threshold_number_entity = threshold_number_entity
# If sensor and threshold number are initialized, reload the listener
# if self._nb_active_number_entity and self._central_boiler_entity:
# self._hass.async_add_job(self.reload_central_boiler_binary_listener)
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
# if self._threshold_number_entity and self._central_boiler_entity:
# self._hass.async_add_job(self.reload_central_boiler_binary_listener)
def register_temperature_number(
self,
@@ -172,13 +177,6 @@ class VersatileThermostatAPI(dict):
)
if component:
for entity in component.entities:
# if hasattr(entity, "init_presets"):
# if (
# only_use_central is False
# or entity.use_central_config_temperature
# ):
# await entity.init_presets(self.find_central_configuration())
# A little hack to test if the climate is a VTherm. Cannot use isinstance
# due to circular dependency of BaseThermostat
if (
@@ -188,6 +186,10 @@ class VersatileThermostatAPI(dict):
if entry_id is None or entry_id == entity.unique_id:
await entity.async_startup(self.find_central_configuration())
# start listening for the central power manager if not only one vtherm reload
if not entry_id:
await self.central_power_manager.start_listening()
async def init_vtherm_preset_with_central(self):
"""Init all VTherm presets when the VTherm uses central temperature"""
# Initialization of all preset for all VTherm
@@ -301,3 +303,18 @@ class VersatileThermostatAPI(dict):
def hass(self):
"""Get the HomeAssistant object"""
return VersatileThermostatAPI._hass
@property
def central_power_manager(self) -> any:
"""Returns the central power manager"""
return self._central_power_manager
# For testing purpose
def _set_now(self, now: datetime):
"""Set the now timestamp. This is only for tests purpose"""
self._now = now
@property
def now(self) -> datetime:
"""Get now. The local datetime or the overloaded _set_now date"""
return self._now if self._now is not None else NowClass.get_now(self._hass)

View File

@@ -15,7 +15,7 @@ Provide the mandatory main attributes. These attributes are common to all VTherm
1. For `over_switch`: VTherm will turn the radiator on/off, modulating the proportion of time it is on,
2. For `over_valve`: VTherm will calculate a new valve opening level and send it if it has changed,
3. For `over_climate`: The cycle performs basic controls and recalculates the self-regulation coefficients. The cycle may result in a new setpoint sent to underlying devices or a valve opening adjustment in the case of a controllable TRV.
5. The equipment's power, which will activate power and energy consumption sensors for the device. If multiple devices are linked to the same VTherm, specify the total maximum power of all devices here,
5. The equipment's power, which will activate power and energy consumption sensors for the device. If multiple devices are linked to the same VTherm, specify the total maximum power of all devices here. The power unit is not important here. What is important is that all _VTherm_ and all power sensors have the same unit (see: Power shedding feature),
6. The option to use additional parameters from centralized configuration:
1. Outdoor temperature sensor,
2. Minimum/maximum temperature and temperature step size,
@@ -42,4 +42,4 @@ Choose the features you want to use for this VTherm:
> ![Tip](images/tips.png) _*Notes*_
> 1. The list of available functions adapts to your VTherm type.
> 2. When you enable a function, a new menu entry is added to configure it.
> 3. You cannot validate the creation of a VTherm if all parameters for all enabled functions have not been configured.
> 3. You cannot validate the creation of a VTherm if all parameters for all enabled functions have not been configured.

View File

@@ -49,7 +49,7 @@ When your device is controlled by a `climate` entity in Home Assistant and you o
This type also includes advanced self-regulation features to adjust the setpoint sent to the underlying device, helping to achieve the target temperature faster and mitigating poor internal regulation. For example, if the device's internal thermometer is too close to the heating element, it may incorrectly assume the room is warm while the setpoint is far from being achieved in other areas.
Since version 6.8, this VTherm type can also regulate directly by controlling the valve. Ideal for controllable TRVs, this type is recommended if you have such devices.
Since version 6.8, this VTherm type can also regulate directly by controlling the valve. Ideal for controllable TRVs, as Sonoff TRVZB, this type is recommended if you have such devices.
The underlying entities for this VTherm type are exclusively `climate`.

View File

@@ -25,14 +25,18 @@ The advanced configuration form looks like this:
The first delay (`minimal_activation_delay_sec`) in seconds is the minimum acceptable delay to turn on the heating. If the calculated activation time is shorter than this value, the heating remains off. This parameter only applies to _VTherm_ with cyclic triggering `over_switch`. If the activation time is too short, rapid switching will not allow the device to heat up properly.
### Minimum Deactivation Delay
The delay (`minimal_deactivation_delay_sec`) in seconds is the minimum acceptable delay to turn off the heating. If the calculated deactivation time is shorter than this value, the heating remains on.
### Safety Mode
The second delay (`security_delay_min`) is the maximum time between two temperature measurements before the _VTherm_ switches to Safety Mode.
The second delay (`safety_delay_min`) is the maximum time between two temperature measurements before the _VTherm_ switches to Safety Mode.
The third parameter (`security_min_on_percent`) is the minimum `on_percent` below which Safety Mode will not be activated. This setting prevents activating Safety Mode if the controlled radiator does not heat sufficiently. In this case, there is no physical risk to the home, only the risk of overheating or underheating.
The third parameter (`safety_min_on_percent`) is the minimum `on_percent` below which Safety Mode will not be activated. This setting prevents activating Safety Mode if the controlled radiator does not heat sufficiently. In this case, there is no physical risk to the home, only the risk of overheating or underheating.
Setting this parameter to `0.00` will trigger Safety Mode regardless of the last heating setting, whereas `1.00` will never trigger Safety Mode (effectively disabling the feature). This can be useful to adapt the safety mechanism to your specific needs.
The fourth parameter (`security_default_on_percent`) defines the `on_percent` used when the thermostat switches to `security` mode. Setting it to `0` will turn off the thermostat in Safety Mode, while setting it to a value like `0.2` (20%) ensures some heating remains, avoiding a completely frozen home in case of a thermometer failure.
The fourth parameter (`safety_default_on_percent`) defines the `on_percent` used when the thermostat switches to `security` mode. Setting it to `0` will turn off the thermostat in Safety Mode, while setting it to a value like `0.2` (20%) ensures some heating remains, avoiding a completely frozen home in case of a thermometer failure.
It is possible to disable Safety Mode triggered by missing data from the outdoor thermometer. Since the outdoor thermometer usually has a minor impact on regulation (depending on your configuration), it might not be critical if it's unavailable. To do this, add the following lines to your `configuration.yaml`:
@@ -49,5 +53,5 @@ By default, the outdoor thermometer can trigger Safety Mode if it stops sending
> 1. When the temperature sensor resumes reporting, the preset will be restored to its previous value.
> 2. Two temperature sources are required: the indoor and outdoor temperatures. Both must report values, or the thermostat will switch to "security" preset.
> 3. An action is available to adjust the three safety parameters. This can help adapt Safety Mode to your needs.
> 4. For normal use, `security_default_on_percent` should be lower than `security_min_on_percent`.
> 4. For normal use, `safety_default_on_percent` should be lower than `safety_min_on_percent`.
> 5. If you use the Versatile Thermostat UI card (see [here](additions.md#better-with-the-versatile-thermostat-ui-card)), a _VTherm_ in Safety Mode is indicated by a gray overlay showing the faulty thermometer and the time since its last value update: ![safety mode](images/safety-mode-icon.png).

View File

@@ -33,6 +33,8 @@ Once the function is configured, you will now have a new `switch` type entity th
Check the box to allow auto-start and auto-stop, and leave it unchecked to disable the feature.
Note: The auto-start/stop function will only turn a _VTherm_ back on if it was turned off by this function. This prevents unwanted or unexpected activations. Naturally, the off state is preserved even after a Home Assistant restart.
> ![Tip](images/tips.png) _*Notes*_
> 1. The detection algorithm is described [here](algorithms.md#auto-startstop-algorithm).
> 2. Some appliances (boilers, underfloor heating, _PAC_, etc.) may not like being started/stopped too frequently. If that's the case, it might be better to disable the function when you know the appliance will be used. For example, I disable this feature during the day when presence is detected because I know my _PAC_ will turn on often. I enable auto-start/stop at night or when no one is home, as the setpoint is lowered and it rarely triggers.

View File

@@ -1,46 +1,51 @@
# Power Management - Load Shedding
- [Power Management - Load Shedding](#power-management---load-shedding)
- [Configure Power Management](#configure-power-management)
- [Example Use Case:](#example-use-case)
- [Configuring Power Management](#configuring-power-management)
This feature allows you to regulate the electricity consumption of your heaters. Known as load shedding, this feature enables you to limit the electrical consumption of your heating device if overcapacity conditions are detected.
You will need a **sensor for the total instantaneous power consumption** of your home, as well as a **sensor for the maximum allowed power**.
This feature allows you to regulate the electrical consumption of your heaters. Known as load shedding, it lets you limit the electrical consumption of your heating equipment if overconsumption conditions are detected.
You will need a **sensor for the total instantaneous power consumption** of your home and a **sensor for the maximum allowed power**.
The behavior of this feature is basic:
1. when the _VTherm_ is about to turn on a device,
2. it compares the last known value of the power consumption sensor with the last value of the maximum allowed power. If there is a remaining margin greater than or equal to the declared power of the _VTherm_'s devices, then the _VTherm_ and its devices will be turned on. Otherwise, they will remain off until the next cycle.
The behavior of this feature is as follows:
1. When a new measurement of the home's power consumption or the maximum allowed power is received,
2. If the maximum power is exceeded, the central command will shed the load of all active devices starting with those closest to the setpoint. This continues until enough _VTherms_ are shed,
3. If there is available power reserve and some _VTherms_ are shed, the central command will re-enable as many devices as possible, starting with those furthest from the setpoint (at the time they were shed).
4. When a _VTherm_ starts, a check is performed to determine if the declared power is available. If not, the _VTherm_ is put into shed mode.
WARNING: This very basic operation **is not a safety function** but more of an optimization feature to manage consumption at the cost of heating performance. Overloads may occur depending on the frequency of updates from your consumption sensors, and the actual power used by your devices. Therefore, you must always maintain a safety margin.
**WARNING:** This is **not a safety feature** but an optimization function to manage consumption at the expense of some heating degradation. Overconsumption is still possible depending on the frequency of your consumption sensor updates and the actual power used by your equipment. Always maintain a safety margin.
Typical use case:
1. you have an electricity meter limited to 11 kW,
2. you occasionally charge an electric vehicle at 5 kW,
3. that leaves 6 kW for everything else, including heating,
4. you have 1 kW of other equipment running,
5. you have declared a sensor (`input_number`) for the maximum allowed power at 9 kW (= 11 kW - the reserve for other devices - margin)
### Example Use Case:
1. You have an electric meter limited to 11 kW,
2. You occasionally charge an electric vehicle at 5 kW,
3. This leaves 6 kW for everything else, including heating,
4. You have 1 kW of other active devices,
5. You declare a sensor (`input_number`) for the maximum allowed power at 9 kW (= 11 kW - reserved power for other devices - safety margin).
If the vehicle is charging, the total power consumed is 6 kW (5+1), and a _VTherm_ will only turn on if its declared power is 3 kW max (9 kW - 6 kW).
If the vehicle is charging and another _VTherm_ of 2 kW is running, the total power consumed is 8 kW (5+1+2), and a _VTherm_ will only turn on if its declared power is 1 kW max (9 kW - 8 kW). Otherwise, it will wait until the next cycle.
If the vehicle is charging, the total consumed power is 6 kW (5 + 1), and a _VTherm_ will only turn on if its declared power is a maximum of 3 kW (9 kW - 6 kW).
If the vehicle is charging and another _VTherm_ of 2 kW is on, the total consumed power is 8 kW (5 + 1 + 2), and a _VTherm_ will only turn on if its declared power is a maximum of 1 kW (9 kW - 8 kW). Otherwise, it will skip its turn (cycle).
If the vehicle is not charging, the total consumed power is 1 kW, and a _VTherm_ will only turn on if its declared power is a maximum of 8 kW (9 kW - 1 kW).
If the vehicle is not charging, the total power consumed is 1 kW, and a _VTherm_ will only turn on if its declared power is 8 kW max (9 kW - 1 kW).
## Configuring Power Management
## Configure Power Management
If you have chosen the `With power detection` feature, configure it as follows:
In the centralized configuration, if you have selected the `With power detection` feature, configure it as follows:
![image](images/config-power.png)
1. the entity ID of the **instantaneous power consumption sensor** for your home,
2. the entity ID of the **maximum allowed power sensor**,
3. the temperature to apply if load shedding is activated.
1. The entity ID of the **sensor for total instantaneous power consumption** of your home,
2. The entity ID of the **sensor for maximum allowed power**,
3. The temperature to apply if load shedding is activated.
Note that all power values must have the same units (kW or W, for example).
Having a **maximum allowed power sensor** allows you to adjust the maximum power over time using a scheduler or automation.
Ensure that all power values use the same units (e.g., kW or W).
Having a **sensor for maximum allowed power** allows you to modify the maximum power dynamically using a scheduler or automation.
Note that due to centralized load-shedding, it is not possible to override the consumption and maximum consumption sensors on individual _VTherms_. This configuration must be done in the centralized settings. See [Centralized Configuration](./creation.md#centralized-configuration).
> ![Tip](images/tips.png) _*Notes*_
>
> 1. In case of load shedding, the radiator is set to the preset named `power`. This is a hidden preset, and you cannot select it manually.
> 2. Always keep a margin, as the maximum power may briefly be exceeded while waiting for the next cycle calculation, or due to unregulated equipment.
> 3. If you don't want to use this feature, uncheck it in the 'Functions' menu.
> 4. If a _VTherm_ controls multiple devices, the **electrical consumption of your heating** must match the sum of the powers.
> 5. If you are using the Versatile Thermostat UI card (see [here](additions.md#much-better-with-the-versatile-thermostat-ui-card)), load shedding is represented as follows: ![load shedding](images/power-exceeded-icon.png).
> 1. During load shedding, the heater is set to the preset named `power`. This is a hidden preset that cannot be manually selected.
> 2. Always maintain a margin, as the maximum power can briefly be exceeded while waiting for the next cycle's calculation or due to uncontrolled devices.
> 3. If you do not wish to use this feature, uncheck it in the 'Features' menu.
> 4. If a single _VTherm_ controls multiple devices, the **declared heating power consumption** should correspond to the total power of all devices.
> 5. If you use the Versatile Thermostat UI card (see [here](additions.md#better-with-the-versatile-thermostat-ui-card)), load shedding is represented as follows: ![load shedding](images/power-exceeded-icon.png).
> 6. There may be a delay of up to 20 seconds between receiving a new value from the power consumption sensor and triggering load shedding for _VTherms_. This delay prevents overloading Home Assistant if your consumption updates are very frequent.

View File

@@ -5,7 +5,7 @@
## Configure Pre-configured Temperatures
The preset mode allows you to pre-configure the target temperature. Used in conjunction with Scheduler (see [scheduler](additions#the-scheduler-component)), you'll have a powerful and simple way to optimize the temperature relative to the electricity consumption in your home. The managed presets are as follows:
The preset mode allows you to pre-configure the target temperature. Used in conjunction with Scheduler (see [scheduler](additions.md#the-scheduler-component)), you'll have a powerful and simple way to optimize the temperature relative to the electricity consumption in your home. The managed presets are as follows:
- **Eco**: the device is in energy-saving mode
- **Comfort**: the device is in comfort mode
- **Boost**: the device fully opens all valves

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,8 @@ The installation should look like this:
## Configuration
Click on the "Underlying Entities" option from the menu, and you will see this configuration page:
First, configure the main settings common to all _VTherms_ (see [main settings](base-attributes.md)).
Then, click on the "Underlying Entities" option from the menu, and you will see this configuration page:
![image](images/config-linked-entity2.png)
@@ -59,6 +60,8 @@ Of course, your underlying equipment must have ventilation, and it must be contr
### Compensating for the Internal Temperature of the Underlying Equipment
Warning: This option must not be used with direct valve control regulation if a calibration entity has been provided.
Sometimes, the internal thermometer of the underlying equipment (TRV, air conditioner, etc.) is inaccurate to the point that self-regulation is insufficient. This happens when the internal thermometer is placed too close to the heat source. The internal temperature rises much faster than the room temperature, leading to regulation failures.
Example:
1. Room temperature is 18°, setpoint is 20°.
@@ -98,4 +101,4 @@ When this entity is 'On', all temperature or state changes made directly on the
Be careful, if you use this feature, your equipment is now controlled in two ways: _VTherm_ and directly by you. The commands might be contradictory, which could lead to confusion about the equipment's state. _VTherm_ is equipped with a delay mechanism that prevents loops: the user gives a setpoint, which is captured by _VTherm_ and changes the setpoint, ... This delay may cause the change made directly on the equipment to be ignored if these changes are too close together in time.
Some equipment (like Daikin, for example) changes state by itself. If the checkbox is checked, it may turn off the _VTherm_ when that's not what you intended.
That's why it's better not to use it. It generates a lot of confusion and many support requests.
That's why it's better not to use it. It generates a lot of confusion and many support requests.

View File

@@ -3,10 +3,11 @@
- [`over_switch` Type Thermostat](#over_switch-type-thermostat)
- [Prerequisites](#prerequisites)
- [Configuration](#configuration)
- [The Underlying Entities](#the-underlying-entities)
- [The underlying devices](#the-underlying-devices)
- [Keep-Alive](#keep-alive)
- [AC Mode](#ac-mode)
- [Command Inversion](#command-inversion)
- [Command Customization](#command-customization)
## Prerequisites
@@ -18,20 +19,24 @@ The installation should look like this:
1. The user or automation, or the Scheduler, sets a setpoint via a preset or directly using a temperature.
2. Periodically, the internal thermometer (2) or external thermometer (2b) sends the measured temperature. The internal thermometer should be placed in a relevant spot for the user's comfort: ideally in the middle of the living space. Avoid placing it too close to a window or too near the radiator.
3. Based on the setpoint values, the different temperatures, and the TPI algorithm parameters (see [TPI](algorithms.md#lalgorithme-tpi)), VTherm will calculate a percentage of the on-time.
4. It will then regularly command the turning on and off of the underlying `switch` entities.
5. These underlying switch entities will control the physical switch.
4. It will then regularly command the turning on and off of the underlying `switch` (or `select` or `climate`) entities.
5. These underlying entities will control the physical device.
6. The physical switch will turn the radiator on or off.
> The on-time percentage is recalculated each cycle, which is what allows regulating the room temperature.
## Configuration
Click on the "Underlying Entities" option from the menu, and you will see this configuration page:
First, configure the main settings common to all _VTherms_ (see [main settings](base-attributes.md)).
Then, click on the "Underlying Entities" option from the menu, and you will see this configuration page:
![image](images/config-linked-entity.png)
### The Underlying Entities
In the "Equipment to Control" list, you should add the switches that will be controlled by VTherm. Only `switch` or `input_boolean` entities are accepted.
### The underlying devices
In the "list of devices to control," you add the switches that will be controlled by VTherm. Only entities of type `switch`, `input_boolean`, `select`, `input_select`, or `climate` are accepted.
If one of the underlying devices is not a `switch`, then command customization is mandatory. By default, for `switch` entities, the commands are the standard switch on/off commands (`turn_on`, `turn_off`).
The algorithm currently available is TPI. See [algorithm](#algorithm).
If multiple entities are configured, the thermostat staggers the activations to minimize the number of switches on at any given time. This allows for better power distribution, as each radiator will turn on in turn.
@@ -52,4 +57,39 @@ It is possible to choose a `thermostat_over_switch` to control an air conditione
### Command Inversion
If your equipment is controlled by a pilot wire with a diode, you may need to check the "Invert the Command" box. This will set the switch to `On` when you need to turn off the equipment and to `Off` when you need to turn it on. The cycle times will be inverted with this option.
If your equipment is controlled by a pilot wire with a diode, you may need to check the "Invert the Command" box. This will set the switch to `On` when you need to turn off the equipment and to `Off` when you need to turn it on. The cycle times will be inverted with this option.
### Command Customization
This configuration section allows you to customize the on and off commands sent to the underlying device.
These commands are mandatory if one of the underlying devices is not a `switch` (for `switch` entities, standard on/off commands are used).
To customize the commands, click on `Add` at the bottom of the page for both the on and off commands:
![virtual switch](images/config-vswitch1.png)
Then, specify the on and off commands using the format `command[/attribute[:value]]`.
The available commands depend on the type of underlying device:
| Underlying Device Type | Possible On Commands | Possible Off Commands | Applies To |
| --------------------------- | ------------------------------------- | ---------------------------------------------- | ----------------------------- |
| `switch` or `input_boolean` | `turn_on` | `turn_off` | All switches |
| `select` or `input_select` | `select_option/option:comfort` | `select_option/option:frost_protection` | Nodon SIN-4-FP-21 and similar |
| `climate` (hvac_mode) | `set_hvac_mode/hvac_mode:heat` | `set_hvac_mode/hvac_mode:off` | eCosy (via Tuya Local) |
| `climate` (preset) | `set_preset_mode/preset_mode:comfort` | `set_preset_mode/preset_mode:frost_protection` | Heatzy |
Of course, these examples can be adapted to your specific case.
Example for a Nodon SIN-4-FP-21:
![virtual switch Nodon](images/config-vswitch2.png)
Click "Validate" to confirm the modifications.
If the following error occurs:
> The command customization configuration is incorrect. It is required for non-switch underlying devices, and the format must be 'service_name[/attribute:value]'. More details in the README.
This means that one of the entered commands is invalid. The following rules must be followed:
1. Each command must follow the format `command[/attribute[:value]]` (e.g., `select_option/option:comfort` or `turn_on`) without spaces or special characters except `_`.
2. There must be as many commands as there are declared underlying devices, except when all underlying devices are `switch` entities, in which case command customization is not required.
3. If multiple underlying devices are configured, the commands must be in the same order. The number of on commands must equal the number of off commands and the number of underlying devices (in the correct order). It is possible to mix different types of underlying devices. As soon as one underlying device is not a `switch`, all commands for all underlying devices, including `switch` entities, must be configured.

View File

@@ -21,7 +21,8 @@ The installation should be similar to the `over_switch` VTherm setup, except tha
## Configuration
Click on the "Underlying Entities" option from the menu, and you will see this configuration page, you should add the `number` entities that will be controlled by VTherm. Only `number` or `input_number` entities are accepted.
First, configure the main settings common to all _VTherms_ (see [main settings](base-attributes.md)).
Then, click on the "Underlying Entities" option from the menu, and you will see this configuration page, you should add the `number` entities that will be controlled by VTherm. Only `number` or `input_number` entities are accepted.
![image](images/config-linked-entity3.png)

View File

@@ -40,4 +40,5 @@ Some TRV type thermostats are known to be incompatible with Versatile Thermostat
5. TRVs like Aqara SRTS-A01 and MOES TV01-ZB, which lack the `hvac_action` state feedback to determine whether they are heating or not. Therefore, state feedback is inaccurate, but the rest seems functional.
6. Airwell air conditioners with the "Midea AC LAN" integration. If two VTherm commands are too close together, the air conditioner stops itself.
7. Climates based on the Overkiz integration do not work. It seems impossible to turn off or even change the temperature on these systems.
8. Heating systems based on Netatmo perform poorly. Netatmo schedules conflict with _VTherm_ programming. Netatmo devices constantly revert to `Auto` mode, which is poorly managed with _VTherm_. In this mode, _VTherm_ cannot determine whether the system is heating or cooling, making it impossible to select the correct algorithm. Some users have managed to make it work using a virtual switch between _VTherm_ and the underlying system, but stability is not guaranteed. An example is provided in the [troubleshooting](troubleshooting.md) section.

View File

@@ -0,0 +1,100 @@
# Quick Start
This page outlines the steps to quickly set up a basic yet operational _VTherm_. It is structured by equipment type.
- [Quick Start](#quick-start)
- [Nodon SIN-4-FP-21 or similar (pilot wire)](#nodon-sin-4-fp-21-or-similar-pilot-wire)
- [Heatzy, eCosy, or similar (`climate` entity)](#heatzy-ecosy-or-similar-climate-entity)
- [Simple switch such as Aqara T1, Nous B2Z, Sonoff ZBMini, Sonoff POW, ...](#simple-switch-such-as-aqara-t1-nous-b2z-sonoff-zbmini-sonoff-pow-)
- [Sonoff TRVZB or similar (TRV with valve control)](#sonoff-trvzb-or-similar-trv-with-valve-control)
- [Reversible HP Units, Air Conditioning, or Devices Controlled via a `climate` Entity](#reversible-hp-units-air-conditioning-or-devices-controlled-via-a-climate-entity)
- [Next Steps](#next-steps)
- [Call for Contributions](#call-for-contributions)
## Nodon SIN-4-FP-21 or similar (pilot wire)
This module allows controlling a radiator via a pilot wire. It appears in _HA_ as a `select` entity that lets you choose the heating preset to apply.
_VTherm_ will regulate the temperature by periodically changing the preset via customized commands until the setpoint is reached.
For this to work, the preset used for heating control must be higher than the maximum temperature you will need (24°C is a good value).
To integrate it into _VTherm_, you must:
1. Create a _VTherm_ of type `over_switch`. See [creating a _VTherm_](creation.md),
2. Assign it the main attributes (name, room temperature sensor, and outdoor temperature sensor at a minimum). See [main attributes](base-attributes.md),
3. Assign one or more underlying devices to control. The underlying device here is the `select` entity that controls the Nodon. See [underlying devices](over-switch.md),
4. Provide custom on/off commands (mandatory for the Nodon). See [command customization](over-switch.md#command-customization). The custom commands follow the format `select_option/option:<preset>` as indicated in the link.
After completing these four steps, you will have a fully functional _VTherm_ that controls your Nodon or similar device.
## Heatzy, eCosy, or similar (`climate` entity)
This module allows controlling a radiator that appears in _HA_ as a `climate` entity, enabling you to choose the heating preset or mode (Heat / Cool / Off).
_VTherm_ will regulate the temperature by turning the device on/off via customized commands at regular intervals until the setpoint is reached.
To integrate it into _VTherm_, you must:
1. Create a _VTherm_ of type `over_switch`. See [creating a _VTherm_](creation.md),
2. Assign it the main attributes (name, room temperature sensor, and outdoor temperature sensor at a minimum). See [main attributes](base-attributes.md),
3. Assign one or more underlying devices to control. The underlying device here is the `climate` entity that controls the Heatzy or eCosy. See [underlying devices](over-switch.md),
4. Provide custom on/off commands (mandatory). See [command customization](over-switch.md#command-customization). The custom commands follow the format `set_hvac_mode/hvac_mode:<mode>` or `set_preset_mode/preset_mode:<preset>` as indicated in the link.
After completing these four steps, you will have a fully functional _VTherm_ that controls your Heatzy, eCosy, or similar device.
## Simple switch such as Aqara T1, Nous B2Z, Sonoff ZBMini, Sonoff POW, ...
This module allows controlling a radiator via a simple switch. It appears in _HA_ as a `switch` entity that directly turns the radiator on or off.
_VTherm_ will regulate the temperature by periodically turning the `switch` on and off until the setpoint is reached.
To integrate it into _VTherm_, you must:
1. Create a _VTherm_ of type `over_switch`. See [creating a _VTherm_](creation.md),
2. Assign it the main attributes (name, room temperature sensor, and outdoor temperature sensor at a minimum). See [main attributes](base-attributes.md),
3. Assign one or more underlying devices to control. The underlying device here is the `switch` entity that controls the switch. See [underlying devices](over-switch.md).
After completing these three steps, you will have a fully functional _VTherm_ that controls your `switch` or similar device.
## Sonoff TRVZB or similar (TRV with valve control)
This type of _TRV_ device controls the opening of a valve that allows more or less hot water from a boiler or heat pump to flow. It appears in _HA_ as a `climate` entity along with `number` entities that control the valve. These `number` entities may be hidden and need to be explicitly added in some cases.
_VTherm_ will adjust the valve opening degree until the setpoint temperature is reached.
To integrate it into _VTherm_, you must:
1. Create a _VTherm_ of type `over_climate`. See [creating a _VTherm_](creation.md),
2. Assign it the main attributes (name, room temperature sensor, and outdoor temperature sensor at a minimum). See [main attributes](base-attributes.md),
3. Assign one or more underlying devices to control. The underlying device here is the `climate` entity that controls the TRV. See [underlying devices](over-climate.md),
4. Specify the regulation type as `Direct valve control` only. Leave the option `Compensate for underlying temperature` unchecked. See [auto-regulation](over-climate.md#auto-regulation),
5. Provide the `number` entities named `opening_degree` and `calibration_offset`. Do not configure the `closing_degree` entity. See [underlying devices](over-switch.md).
For this to work, the `closing degree` must be set to the maximum (100%). Do not immediately enable the `Follow underlying temperature change` option until you have verified that this basic configuration is working properly.
After completing these five steps, you will have a fully functional _VTherm_ that controls your Sonoff TRVZB or similar device.
## Reversible HP Units, Air Conditioning, or Devices Controlled via a `climate` Entity
Reversible heat pumps (HP) or similar devices are represented in _HA_ as a `climate` entity, allowing you to select the heating preset or mode (Heat / Cool / Off).)
_VTherm_ will regulate the temperature by controlling the target temperature and mode of the device through commands sent to the underlying `climate` entity.
To integrate it into _VTherm_, you need to:
1. Create a _VTherm_ of type `over_climate`. See [creating a _VTherm_](creation.md),
2. Assign it the main attributes (name, room temperature sensor, and outdoor temperature sensor at minimum). See [main attributes](base-attributes.md),
3. Define one or more underlying devices to control. The underlying entity here is the `climate` entity that manages the heat pump or air conditioner. See [underlying devices](over-climate.md),
After these three steps, you will have a fully operational _VTherm_ to control your heat pump, air conditioner, or similar device.
To go further, self-regulation may be necessary depending on how well your device operates. Self-regulation involves _VTherm_ slightly adjusting the target temperature to encourage the device to heat or cool more or less until the desired setpoint is reached. Self-regulation is explained in detail here: [self-regulation](self-regulation.md).
# Next Steps
Once created, you need to configure the preset temperatures. See [presets](feature-presets.md) for a minimal configuration.
You can also (optional but recommended) install the dedicated UI card for your dashboards. (See [VTHerm UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card))
Once this minimal setup is functional—and only once it works correctly—you can add additional features such as presence detection to avoid heating when no one is present. Add them one by one, verifying that _VTherm_ reacts correctly at each step before proceeding to the next.
You can then set up centralized configurations to share settings across all _VTherm_ instances, enable central mode for unified control of all _VTherms_ ([centralized configuration](feature-central-mode.md)), or integrate a central boiler control ([central boiler](feature-central-boiler.md)). This is not an exhaustive list—please refer to the table of contents for a complete list of _VTherm_ features.
# Call for Contributions
This page is open for contributions. Feel free to suggest additional equipment and minimal configuration setups.

View File

@@ -61,8 +61,9 @@
| ``power_temp`` | Temperature during load shedding | X | X | X | X |
| ``presence_sensor_entity_id`` | Presence sensor entity id (true if someone is present) | X | X | X | - |
| ``minimal_activation_delay`` | Minimum activation delay | X | - | - | X |
| ``security_delay_min`` | Maximum delay between two temperature measurements | X | - | X | X |
| ``security_min_on_percent`` | Minimum power percentage to enter security mode | X | - | X | X |
| ``minimal_deactivation_delay`` | Minimum deactivation delay | X | - | - | X |
| ``safety_delay_min`` | Maximum delay between two temperature measurements | X | - | X | X |
| ``safety_min_on_percent`` | Minimum power percentage to enter security mode | X | - | X | X |
| ``auto_regulation_mode`` | Auto-regulation mode | - | X | - | - |
| ``auto_regulation_dtemp`` | Auto-regulation threshold | - | X | - | - |
| ``auto_regulation_period_min`` | Minimum auto-regulation period | - | X | - | - |
@@ -175,7 +176,7 @@ If the thermostat is in ``security`` mode, the new settings are applied immediat
To change the security settings, use the following code:
```yaml
service: versatile_thermostat.set_security
service: versatile_thermostat.set_safety
data:
min_on_percent: "0.5"
default_on_percent: "0.1"
@@ -204,7 +205,7 @@ The following events are notified:
- ``versatile_thermostat_security_event``: the thermostat enters or exits the ``security`` preset
- ``versatile_thermostat_power_event``: the thermostat enters or exits the ``power`` preset
- ``versatile_thermostat_temperature_event``: one or both temperature measurements of the thermostat haven't been updated for more than `security_delay_min`` minutes
- ``versatile_thermostat_temperature_event``: one or both temperature measurements of the thermostat haven't been updated for more than `safety_delay_min`` minutes
- ``versatile_thermostat_hvac_mode_event``: the thermostat is turned on or off. This event is also broadcast at the thermostat's startup
- ``versatile_thermostat_preset_event``: a new preset is selected on the thermostat. This event is also broadcast at the thermostat's startup
- ``versatile_thermostat_central_boiler_event``: an event indicating a change in the boiler's state
@@ -248,17 +249,18 @@ The custom attributes are as follows:
| ``saved_preset_mode`` | The last preset used before automatic preset switching |
| ``saved_target_temp`` | The last temperature used before automatic switching |
| ``window_state`` | The last known state of the window sensor. None if the window is not configured |
| ``window_bypass_state`` | True if the window open detection bypass is enabled |
| ``is_window_bypass`` | True if the window open detection bypass is enabled |
| ``motion_state`` | The last known state of the motion sensor. None if motion detection is not configured |
| ``overpowering_state`` | The last known state of the overpower sensor. None if power management is not configured |
| ``presence_state`` | The last known state of the presence sensor. None if presence detection is not configured |
| ``security_delay_min`` | The delay before activating security mode when one of the two temperature sensors stops sending measurements |
| ``security_min_on_percent`` | The heating percentage below which the thermostat will not switch to security |
| ``security_default_on_percent`` | The heating percentage used when the thermostat is in security mode |
| ``safety_delay_min`` | The delay before activating security mode when one of the two temperature sensors stops sending measurements |
| ``safety_min_on_percent`` | The heating percentage below which the thermostat will not switch to security |
| ``safety_default_on_percent`` | The heating percentage used when the thermostat is in security mode |
| ``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 |
| ``security_state`` | The security state. True or false |
| ``minimal_activation_delay_sec`` | The minimal activation delay in seconds |
| ``minimal_deactivation_delay_sec``| The minimal deactivation delay in seconds |
| ``last_update_datetime`` | The date and time in ISO8866 format of this state |
| ``friendly_name`` | The name of the thermostat |
| ``supported_features`` | A combination of all features supported by this thermostat. See the official climate integration documentation for more information |

View File

@@ -2,6 +2,9 @@
![New](images/new-icon.png)
> * **Release 7.1**:
> - Redesign of the load-shedding function (power management). Load-shedding is now handled centrally (previously, each _VTherm_ was autonomous). This allows for much more efficient management and prioritization of load-shedding on devices that are close to the setpoint. Note that you must have a centralized configuration with power management enabled for this to work. More info [here](./feature-power.md).
> * **Release 6.8**:
> - Added a new regulation method for `over_climate` type Versatile Thermostats. This method, called 'Direct Valve Control', allows direct control of a TRV valve and possibly an offset to calibrate the internal thermometer of your TRV. This new method has been tested with Sonoff TRVZB and extended to other TRV types where the valve can be directly controlled via `number` entities. More information [here](over-climate.md#lauto-régulation) and [here](self-regulation.md#auto-régulation-par-contrôle-direct-de-la-vanne).

View File

@@ -31,13 +31,15 @@ This allows you to configure the valve control entities:
You need to provide:
1. As many valve opening control entities as there are underlying devices, and in the same order. These parameters are mandatory.
2. As many temperature calibration entities as there are underlying devices, and in the same order. These parameters are optional; they must either all be provided or none.
3. As many valve closure control entities as there are underlying devices, and in the same order. These parameters are optional; they must either all be provided or none.
4. A list of minimum opening values for the valve when it needs to be opened. This field is a list of integers. If the valve needs to be opened, it will be opened at a minimum of this opening value. This allows enough water to pass through when it needs to be opened.
3. As many valve closure control entities as there are underlying devices, and in the same order. These parameters are optional; they must either all be provided or none. For Sonoff TRVZB, you should not configure this entity. See the note below.
4. A list of minimum opening values for the valve when it needs to be opened. This field is a list of integers. If the valve needs to be opened, it will be opened at a minimum of this opening value, else it will be set to 0 (to ensure the valve is closed). This allows enough water to pass through when it needs to be opened.
The opening rate calculation algorithm is based on the _TPI_ algorithm described [here](algorithms.md). This is the same algorithm used for _VTherms_ `over_switch` and `over_valve`.
If a valve closure rate entity is configured, it will be set to 100 minus the opening rate to force the valve into a particular state.
Note: for Sonoff TRVZB you should not configure the "closing degree" parameter. This leads to a bug in the TRV and the `hvac_action` is no more working.
### Other self-regulation
In the second case, Versatile Thermostat calculates an offset based on the following information:
@@ -150,4 +152,4 @@ To apply the changes, you must either **restart Home Assistant completely** or j
## Summary of the Auto-Regulation Algorithm
A summary of the auto-regulation algorithm is described [here](algorithms.md#the-auto-regulation-algorithm-without-valve-control)
A summary of the auto-regulation algorithm is described [here](algorithms.md#the-auto-regulation-algorithm-without-valve-control)

View File

@@ -4,6 +4,7 @@
- [Troubleshooting](#troubleshooting)
- [Using a Heatzy](#using-a-heatzy)
- [Using a radiator with a pilot wire (Nodon SIN-4-FP-21)](#using-a-radiator-with-a-pilot-wire-nodon-sin-4-fp-21)
- [Using a Netatmo System](#using-a-netatmo-system)
- [Only the first radiator heats](#only-the-first-radiator-heats)
- [The radiator heats even though the setpoint temperature is exceeded, or it does not heat when the room temperature is well below the setpoint](#the-radiator-heats-even-though-the-setpoint-temperature-is-exceeded-or-it-does-not-heat-when-the-room-temperature-is-well-below-the-setpoint)
- [Type `over_switch` or `over_valve`](#type-over_switch-or-over_valve)
@@ -16,10 +17,15 @@
- [Using a Group of People as a Presence Sensor](#using-a-group-of-people-as-a-presence-sensor)
- [Enable Logs for the Versatile Thermostat](#enable-logs-for-the-versatile-thermostat)
- [VTherm does not track setpoint changes made directly on the underlying device (`over_climate`)](#vtherm-does-not-track-setpoint-changes-made-directly-on-the-underlying-device-over_climate)
- [VTherm Automatically Switches to 'Cooling' or 'Heating' Mode](#vtherm-automatically-switches-to-cooling-or-heating-mode)
## Using a Heatzy
The Heatzy is now natively supported by _VTherm_. See [Quick Start](quick-start.md#heatzy-or-ecosy-or-similar-climate-entity).
This configuration is kept for reference only.
Using a Heatzy or Nodon is possible provided you use a virtual switch with this model:
```yaml
@@ -51,6 +57,12 @@ Using a Heatzy or Nodon is possible provided you use a virtual switch with this
Thanks to @gael for this example.
## Using a radiator with a pilot wire (Nodon SIN-4-FP-21)
The Nodon is now natively supported by _VTherm_. See [Quick Start](quick-start.md#nodon-sin-4-fp-21-or-similar-pilot-wire).
This configuration is kept for reference only.
As with the Heatzy above, you can use a virtual switch that will change the preset of your radiator based on the VTherms on/off state.
Example:
@@ -85,6 +97,17 @@ Example:
Another more complex example is [here](https://github.com/jmcollin78/versatile_thermostat/discussions/431#discussioncomment-11393065)
## Using a Netatmo System
The system based on Netatmo TRVs does not work well with _VTherm_. You can find a discussion about the specific behavior of Netatmo systems (in French) here: [https://forum.hacf.fr/t/vannes-netatmo-et-vtherm/56063](https://forum.hacf.fr/t/vannes-netatmo-et-vtherm/56063).
However, some users have successfully integrated _VTherm_ with Netatmo by incorporating a virtual switch between _VTherm_ and the Netatmo `climate` entity, as follows:
```
TODO
```
## Only the first radiator heats
In `over_switch` mode, if multiple radiators are configured for the same VTherm, the heating will be triggered sequentially to smooth out the consumption peaks as much as possible.
@@ -125,9 +148,9 @@ These parameters are sensitive and quite difficult to adjust. Please only use th
## Why is my Versatile Thermostat going into Safety Mode?
Safety mode is only available for VTherm types `over_switch` and `over_valve`. It occurs when one of the two thermometers (providing either the room temperature or the external temperature) has not sent a value for more than `security_delay_min` minutes, and the radiator had been heating at least `security_min_on_percent`. See [safety mode](feature-advanced.md#safety-mode)
Safety mode is only available for VTherm types `over_switch` and `over_valve`. It occurs when one of the two thermometers (providing either the room temperature or the external temperature) has not sent a value for more than `safety_delay_min` minutes, and the radiator had been heating at least `safety_min_on_percent`. See [safety mode](feature-advanced.md#safety-mode)
Since the algorithm relies on temperature measurements, if they are no longer received by the VTherm, there is a risk of overheating and fire. To prevent this, when the above conditions are detected, heating is limited to the `security_default_on_percent` parameter. This value should therefore be reasonably low (10% is a good value). It helps avoid a fire while preventing the radiator from being completely turned off (risk of freezing).
Since the algorithm relies on temperature measurements, if they are no longer received by the VTherm, there is a risk of overheating and fire. To prevent this, when the above conditions are detected, heating is limited to the `safety_default_on_percent` parameter. This value should therefore be reasonably low (10% is a good value). It helps avoid a fire while preventing the radiator from being completely turned off (risk of freezing).
All these parameters are configured on the last page of the VTherm configuration: "Advanced Settings".
@@ -151,14 +174,14 @@ last_temperature_datetime: "2023-12-06T18:43:28.346010+01:00"
last_ext_temperature_datetime: "2023-12-06T13:04:35.164367+01:00"
last_update_datetime: "2023-12-06T18:43:28.351103+01:00"
...
security_delay_min: 60
safety_delay_min: 60
```
We can see that:
1. The VTherm is indeed 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"`),
3. The last reception time of the room temperature is 06/12/2023 at 18:43:28 (`last_temperature_datetime: "2023-12-06T18:43:28.346010+01:00"`), so it's recent,
4. The last reception time of the external temperature is 06/12/2023 at 13:04:35 (`last_ext_temperature_datetime: "2023-12-06T13:04:35.164367+01:00"`). The external temperature is over 5 hours late, which triggered the safety mode, as the threshold is set to 60 minutes (`security_delay_min: 60`).
4. The last reception time of the external temperature is 06/12/2023 at 13:04:35 (`last_ext_temperature_datetime: "2023-12-06T13:04:35.164367+01:00"`). The external temperature is over 5 hours late, which triggered the safety mode, as the threshold is set to 60 minutes (`safety_delay_min: 60`).
### How to Be Notified When This Happens?
The VTherm sends an event as soon as this happens and again at the end of the safety alert. You can capture these events in an automation and send a notification, blink a light, trigger a siren, etc. It's up to you.
@@ -168,8 +191,8 @@ For handling events generated by VTherm, see [Events](#events).
### How to Fix It?
It depends on the cause of the problem:
1. If a sensor is faulty, it should be repaired (replace batteries, change it, check the weather integration that provides the external temperature, etc.),
2. If the `security_delay_min` parameter is too small, it may generate many false alerts. A correct value is around 60 minutes, especially if you have battery-powered temperature sensors. See [my settings](tuning-examples.md#battery-powered-temperature-sensor),
3. Some temperature sensors don't send measurements if the temperature hasn't changed. So if the temperature stays very stable for a long time, safety mode can trigger. This is not a big issue since it will deactivate once the VTherm receives a new temperature. On some thermometers (e.g., TuYA or Zigbee), you can force a max delay between two measurements. The max delay should be set to a value lower than `security_delay_min`,
2. If the `safety_delay_min` parameter is too small, it may generate many false alerts. A correct value is around 60 minutes, especially if you have battery-powered temperature sensors. See [my settings](tuning-examples.md#battery-powered-temperature-sensor),
3. Some temperature sensors don't send measurements if the temperature hasn't changed. So if the temperature stays very stable for a long time, safety mode can trigger. This is not a big issue since it will deactivate once the VTherm receives a new temperature. On some thermometers (e.g., TuYA or Zigbee), you can force a max delay between two measurements. The max delay should be set to a value lower than `safety_delay_min`,
4. As soon as the temperature is received again, safety mode will turn off, and the previous preset, target temperature, and mode values will be restored.
5. If the external temperature sensor is faulty, you can disable safety mode triggering as it has a minimal impact on the results. To do so, see [here](feature-advanced.md#safety-mode).
@@ -213,4 +236,10 @@ Be careful, in debug mode, Versatile Thermostat is very verbose and can quickly
## VTherm does not track setpoint changes made directly on the underlying device (`over_climate`)
See the details of this feature [here](over-climate.md#track-underlying-temperature-changes).
See the details of this feature [here](over-climate.md#track-underlying-temperature-changes).
## VTherm Automatically Switches to 'Cooling' or 'Heating' Mode
Some reversible heat pumps have modes that allow the heat pump to decide whether to heat or cool. These modes are labeled as 'Auto' or 'Heat_cool' depending on the brand. These two modes should not be used with _VTherm_ because _VTherm_'s algorithms require explicit knowledge of whether the system is in heating or cooling mode, which these modes do not provide.
You should only use the following modes: `Heat`, `Cool`, `Off`, or optionally `Fan` (although `Fan` has no practical purpose with _VTherm_).

View File

@@ -18,9 +18,9 @@
## Battery-Powered Temperature Sensor
These sensors are often sluggish and do not always send temperature readings when the temperature is stable. Therefore, the settings should be loose to avoid false positives.
- security_delay_min: 60 minutes (because these sensors are sluggish)
- security_min_on_percent: 0.7 (70% - the system goes into security mode if the heater was on more than 70% of the time)
- security_default_on_percent: 0.4 (40% - in security mode, we maintain 40% heating time to avoid getting too cold)
- safety_delay_min: 60 minutes (because these sensors are sluggish)
- safety_min_on_percent: 0.7 (70% - the system goes into security mode if the heater was on more than 70% of the time)
- safety_default_on_percent: 0.4 (40% - in security mode, we maintain 40% heating time to avoid getting too cold)
These settings should be understood as follows:
@@ -35,9 +35,9 @@ Versatile Thermostat allows you to be notified when such an event occurs. Set up
## Reactive Temperature Sensor (plugged in)
A powered thermometer is supposed to be very regular in sending temperature readings. If it doesn't send anything for 15 minutes, it most likely has an issue, and we can react faster without the risk of a false positive.
- security_delay_min: 15 minutes
- security_min_on_percent: 0.5 (50% - the system goes into ``security`` preset if the heater was on more than 50% of the time)
- security_default_on_percent: 0.25 (25% - in ``security`` preset, we keep 25% heating time)
- safety_delay_min: 15 minutes
- safety_min_on_percent: 0.5 (50% - the system goes into ``security`` preset if the heater was on more than 50% of the time)
- safety_default_on_percent: 0.25 (25% - in ``security`` preset, we keep 25% heating time)
## My Presets
This is just an example of how I use the preset. You can adapt it to your configuration, but it may be useful to understand its functionality.

View File

@@ -5,6 +5,7 @@
- [Composant Scheduler !](#composant-scheduler-)
- [Courbes de régulattion avec Plotly](#courbes-de-régulattion-avec-plotly)
- [Les notification avec l'AappDaemon NOTIFIER](#les-notification-avec-laappdaemon-notifier)
- [Une très belle carte (merci @Jeffodilo)](#une-très-belle-carte-merci-jeffodilo)
## Versatile Thermostat UI Card
@@ -213,4 +214,275 @@ action:
mode: queued
max: 30
```
</details>
## Une très belle carte (merci @Jeffodilo)
@Jeffodilo a réalisé et partagé le code d'une très belle carte adaptée au TRV :
![carte Jeffodilo](images/card-trv-jeffodilo.png)
Cette carte nutilise pas card_mod, elle utilise les cartes custom assez courante suivantes
On garde une partie de linterface UI, sauf pour lhorizontale de la 2ème ligne
- custom:vertical-stack-in-card
- custom:stack-in-card
- custom:mini-graph-card
- custom:mushroom-template-card
- custom:button-card
L'original est ici (En Français): [forum HACF](https://forum.hacf.fr/t/carte-mise-en-forme-vanne-avec-thermostant-versatile/56132)
Evidemment, vous devez l'adapter à votre code.
Le code:
<details>
```yaml
type: vertical-stack
cards:
- type: heading
icon: mdi:bed-double
heading: Parents
heading_style: title
- type: custom:vertical-stack-in-card
cards:
- type: custom:mini-graph-card
entities:
- entity: sensor.sonde_parents_temperature
name: Température
state_adaptive_color: true
- entity: climate.valve_parents
name: Temp
attribute: current_temperature
unit: °C
state_adaptive_color: true
show_graph: false
show_state: true
hour24: true
hours_to_show: 24
points_per_hour: 2
font_size: 50
show:
name: false
icon: false
legend: false
labels: true
extrema: false
color_thresholds:
- color: "#33ccff"
value: 19
- color: "#00ffff"
value: 19.5
- color: "#33ffcc"
value: 20
- color: "#00ff99"
value: 20.5
- color: "#ffff99"
value: 21
- color: "#ffff33"
value: 21.5
- color: "#ff9933"
value: 22
- color: "#cc6633"
value: 24
- color: "#ff6000"
value: 26
- type: custom:stack-in-card
mode: horizontal
cards:
- type: custom:mushroom-template-card
secondary: ""
layout: horizontal
tap_action:
action: more-info
entity: sensor.sonde_parents_temperature
fill_container: false
multiline_secondary: false
primary: >-
{% if is_state_attr('climate.versatile_parents','hvac_action',
'idle') %}
🗜️ {{ states('number.valve_parents_valve_opening_degree', with_unit=True,)}} |🔋{{ states('sensor.valve_parents_battery') }} % | Inactif
{% elif is_state_attr('climate.versatile_parents','hvac_action',
'heating') %}
🗜️ {{ states('number.valve_parents_valve_opening_degree', with_unit=True,)}} |🔋{{ states('sensor.valve_parents_battery') }} % | Chauffe
{% else %} 🗜️ {{
states('number.valve_parents_valve_opening_degree',
with_unit=True,)}} | 🔋{{ states('sensor.valve_parents_battery')
}} % | Off {% endif %}
icon: ""
- type: horizontal-stack
cards:
- type: custom:button-card
name: Conf.
entity: climate.versatile_parents
show_state: false
show_icon: true
show_name: false
icon: mdi:fire
size: 80%
styles:
icon:
- color: |
[[[
if (states['climate.versatile_parents']) {
if (states['climate.versatile_parents'].attributes.preset_mode == 'comfort')
return 'darkorange';
else
return 'white'; }
]]]
name:
- color: white
- font-size: 60%
card:
- height: 40px
- width: 30px
tap_action:
action: perform-action
perform_action: climate.set_preset_mode
target:
entity_id:
- climate.versatile_parents
data:
preset_mode: comfort
- type: custom:button-card
name: Eco
entity: climate.versatile_parents
show_state: false
show_icon: true
show_name: false
icon: mdi:leaf
size: 80%
styles:
icon:
- color: |
[[[
if (states['climate.versatile_parents']) {
if (states['climate.versatile_parents'].attributes.preset_mode == 'eco')
return 'lightgreen';
else
return 'white'; }
]]]
name:
- color: white
- font-size: 60%
card:
- height: 40px
- width: 30px
tap_action:
action: perform-action
perform_action: climate.set_preset_mode
target:
entity_id:
- climate.versatile_parents
data:
preset_mode: eco
- type: custom:button-card
name: Manu
entity: climate.versatile_parents
show_state: false
show_icon: true
show_name: false
icon: mdi:hand-back-left
size: 80%
styles:
icon:
- color: |
[[[
if (states['climate.versatile_parents']) {
if (states['climate.versatile_parents'].attributes.preset_mode == 'none')
return 'indianred';
else
return 'white'; }
]]]
name:
- color: white
- font-size: 60%
card:
- height: 40px
- width: 30px
tap_action:
action: perform-action
perform_action: climate.set_preset_mode
target:
entity_id:
- climate.versatile_parents
data:
preset_mode: none
- type: custom:button-card
name: Abs.
entity: climate.versatile_parents
show_state: false
show_icon: true
show_name: false
icon: mdi:snowflake
size: 80%
styles:
icon:
- color: |
[[[
if (states['climate.versatile_parents']) {
if (states['climate.versatile_parents'].attributes.preset_mode == 'frost')
return 'skyblue';
else
return 'white'; }
]]]
name:
- color: white
- font-size: 60%
card:
- height: 40px
- width: 30px
tap_action:
action: perform-action
perform_action: climate.set_preset_mode
target:
entity_id:
- climate.versatile_parents
data:
preset_mode: frost
- type: custom:button-card
name: Boost
entity: climate.versatile_parents
show_state: false
show_icon: true
show_name: false
icon: mdi:rocket-launch
size: 80%
styles:
icon:
- color: |
[[[
if (states['climate.versatile_parents']) {
if (states['climate.versatile_parents'].attributes.preset_mode == 'boost')
return 'red';
else
return 'white'; }
]]]
name:
- color: white
- font-size: 60%
card:
- height: 40px
- width: 30px
tap_action:
action: perform-action
perform_action: climate.set_preset_mode
target:
entity_id:
- climate.versatile_parents
data:
preset_mode: boost
- type: custom:mushroom-climate-card
entity: climate.versatile_parents
show_temperature_control: true
hvac_modes: []
tap_action:
action: more-info
primary_info: state
icon: mdi:radiator
secondary_info: last-updated
fill_container: false
layout: horizontal
```
</details>

View File

@@ -15,7 +15,7 @@ Donnez les principaux attributs obligatoires. Ces attributs sont communs à tous
1. `over_switch` : VTherm allumera/éteindra le radiateur en modulant la proportion de temps allumé,
2. `over_valve` : VTherm calculera une nouvelle ouverture de la vanne et lui enverra si elle a changée,
3. `over_climate` : le cycle permet d'effectuer les contrôles de base et recalcule les coefficients de l'auto-régulation. Le cycle peut déboucher sur une nouvelle consigne envoyée au sous-jacents ou sur une modification d'ouverture de la vanne dans le cas d'un _TRV_ dont la vanne est commandable.
7. une puissance de l'équipement ce qui va activer les capteurs de puissance et énergie consommée par l'appareil. Si plusieurs équipements sont reliés au même VTherm, il faut indiquer ici le total des puissances max des équipements,
7. une puissance de l'équipement ce qui va activer les capteurs de puissance et énergie consommée par l'appareil. Si plusieurs équipements sont reliés au même VTherm, il faut indiquer ici le total des puissances max des équipements. L'unité n'est pas importante. Ce qui est important c'est toutes les puissances de tous les _VTherms_ soient dans la même unité ainsi que les éventuels capteurs de puissance (cf. la fonction de délestage),
8. la possibilité d'utiliser des paramètres complémentaires venant de la configuration centralisée :
1. capteur de température extérieure,
2. température minimale / maximale et pas de température

View File

@@ -50,7 +50,7 @@ Les entités sous-jacentes sont donc des `switchs` ou des `input_boolean`.
Lorsque votre équipement est contrôlé par une entité de type `climate` dans Home Assistant et que vous n'avez que ça à disposition, vous devez utiliser ce type de VTherm. Dans ce cas, le VTherm va simplement commander la température de consigne du `climate` sous-jacent.
Ce type est aussi équipé de fonction d' auto-régulations avancées permettant de moduler la consigne donnée aux sous-jacent pour atteindre plus vite la consigne et de s'affranchir de la régulation interne de ces équipements qui est parfois mauvaise. C'est le cas, si le thermomètre interne de l'équipement est trop proche du corps de chauffe. L'équipement peut croire qu'il fait chaud alors qu'au bout de la pièce, la consigne n'est pas du tout atteinte.
Depuis la version 6.8, ce type de VTherm permet aussi de réguler avec une action directe sur la vanne. Idéal pour les _TRV_ pour lesquels la vanne est commandable, ce type est recommandé si vous êtes équipés.
Depuis la version 6.8, ce type de VTherm permet aussi de réguler avec une action directe sur la vanne. Idéal pour les _TRV_ pour lesquels la vanne est commandable, comme les Sonoff TRVZB, ce type est recommandé si vous êtes équipés.
Les entités sous-jacentes de ce type de VTherm sont donc des `climate` exclusivement.

View File

@@ -27,12 +27,12 @@ Le premier délai (`minimal_activation_delay_sec`) en secondes est le délai min
### La mise en sécurité
Le deuxième délai (`security_delay_min`) est le délai maximal entre deux mesures de température avant de passer le _VTherm_ en mode sécurité.
Le deuxième délai (`safety_delay_min`) est le délai maximal entre deux mesures de température avant de passer le _VTherm_ en mode sécurité.
Le troisième paramètre (`security_min_on_percent`) est la valeur minimal de `on_percent` en dessous de laquelle le préréglage sécurité ne sera pas activé. Ce paramètre permet de ne pas mettre en sécurité un thermostat, si le radiateur piloté ne chauffe pas suffisament. En effet, il n'y a pas de risque physique pour le logement dans ce cas mais juste un risque de surchauffe ou de sous-chauffe.
Le troisième paramètre (`safety_min_on_percent`) est la valeur minimal de `on_percent` en dessous de laquelle le préréglage sécurité ne sera pas activé. Ce paramètre permet de ne pas mettre en sécurité un thermostat, si le radiateur piloté ne chauffe pas suffisament. En effet, il n'y a pas de risque physique pour le logement dans ce cas mais juste un risque de surchauffe ou de sous-chauffe.
Mettre ce paramètre à ``0.00`` déclenchera le préréglage sécurité quelque soit la dernière consigne de chauffage, à l'inverse ``1.00`` ne déclenchera jamais le préréglage sécurité ( ce qui revient à désactiver la fonction). Ce peut ê
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 (`safety_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.
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` :
```yaml
@@ -47,5 +47,5 @@ Par défaut, le thermomètre extérieur peut déclencher une mise en sécurité
> 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",
> 3. Une action 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 ``safety_default_on_percent`` doit être inférieur à ``safety_min_on_percent``,
> 5. Si vous utilisez la carte Verstatile Thermostat UI (cf. [ici](additions.md#bien-mieux-avec-le-versatile-thermostat-ui-card)), un _Vtherm_ en mode sécurité est signalé par un voile grisatre qui donne le thermomètre en défaut et depuis combien de temps le thermomètre n'a pas remonté de valeur : ![mode sécurité](images/safety-mode-icon.png).

View File

@@ -34,6 +34,8 @@ Une fois la fonction paramétrée, vous aurez maintenant une nouvelle entité de
Cochez la pour autoriser le démarrage et extinction automatique et laissez là décocher si vous voulez désactiver la fonction auto-start/stop.
A noter : la fonction auto-start/stop ne rallumera un _VTherm_ que si celui-ci a été éteint par cette fonction. Ca évite des allumages intempestifs non désirés. Evidement l'état d'extinction est résistant à un redémarrage de Home Assistant.
> ![Astuce](images/tips.png) _*Notes*_
> 1. L'algorithme de détection est décrit [ici](algorithms.md#lalgorithme-de-la-fonction-dauto-startstop).

View File

@@ -6,11 +6,13 @@
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.
Vous aurez besoin d'un **capteur de la puissance totale instantanée consommée** de votre logement ainsi que d'un **capteur donnant la puissance maximale autorisée**.
Le comportement de cette fonction est basique :
1. lorsque le _VTherm_ va allumer un équipement,
2. il compare la dernière valeur connue du capteur de puissance consommée avec la dernière valeur de la puissance maximale autorisée. Si il reste une marge supérieure égale à la puissance déclarée des équipements du _VTherm_ alors le VTherm et ses équipements seront allumés. Sinon ils resteront éteints jusqu'au prochain cycle.
Le comportement de cette fonction est le suivant :
1. lorsqu'une nouvelle mesure de la puissance consommée du logement ou de la puissance maximale autorisée est reçue,
2. si la puissance max est dépassée, la commande centrale va mettre en délestage tous les équipements actifs en commençant par ceux qui sont le plus près de la consigne. Il fait ça jusqu'à ce que suffisament de _VTherm_ soient délestés,
3. si une réserve de puissance est disponible et que des _VTherms_ sont délestés, alors la commande centrale va délester autant d'équipements que possible en commençant par les plus loin de la consigne (au moment où il a été mis en délestage),
4. au démarrage d'un _VTherm_, une vérification est effectuée pour savoir si la puissance déclarée est disponible. Si non, le _VTherm_ est passé en délestage.
ATTENTION: ce fonctionnement très basique **n'est pas une fonction de sécurité** mais plus une fonction permettant une optimisation de la consommation au prix d'une dégradation du chauffage. Des dépassements sont possibles selon la fréquence de remontée de vos capteurs de consommation, la puissance réellement utilisée par votre équipements. Vous devez donc toujours garder une marge de sécurité.
ATTENTION: ce fonctionnement **n'est pas une fonction de sécurité** mais plus une fonction permettant une optimisation de la consommation au prix d'une dégradation du chauffage. Des dépassements sont possibles selon la fréquence de remontée de vos capteurs de consommation, la puissance réellement utilisée par votre équipements. Vous devez donc toujours garder une marge de sécurité.
Cas d'usage type:
1. vous avez un compteur électrique limité à 11 kW,
@@ -26,7 +28,7 @@ Si le vehicle n'est pas en charge, la puissance totale consommé est de 1 kW, un
## Configurer la gestion de la puissance
Si vous avez choisi la fonctionnalité `Avec détection de la puissance`, vous la configurez de la façon suivante :
Dans la configuration centralisée, si vous avez choisi la fonctionnalité `Avec détection de la puissance`, vous la configurez de la façon suivante :
![image](images/config-power.png)
@@ -37,10 +39,13 @@ Si vous avez choisi la fonctionnalité `Avec détection de la puissance`, vous l
Notez que toutes les valeurs de puissance doivent avoir les mêmes unités (kW ou W par exemple).
Le fait d'avoir un **capteur de puissance maximale autorisée**, vous permet de modifier la puissance maximale au fil du temps à l'aide d'un planificateur ou d'une automatisation.
A noter, dû à la centralisation du délestage, il n'est pas possible de sur-charger les capteurs de consommation et de consommation maximale sur les _VTherms_. Cette configuration se fait forcément dans la configuration centralisée. Cf. [Configuration centralisée](./creation.md#configuration-centralisée)
> ![Astuce](images/tips.png) _*Notes*_
>
> 1. En cas de délestage, le radiateur est réglé sur le préréglage nommé `power`. Il s'agit d'un préréglage caché, vous ne pouvez pas le sélectionner manuellement.
> 2. Gardez toujours une marge, car la puissance max peut être brièvement dépassée en attendant le calcul du prochain cycle typiquement ou par des équipements non régulés.
> 3. Si vous ne souhaitez pas utiliser cette fonctionnalité, décochez la dans le menu 'Fonctions'.
> 4. Si une _VTherm_ controlez plusieurs équipements, la **consommation électrique de votre chauffage** renseigné doit correspondre à la somme des puissances.
> 5. Si vous utilisez la carte Verstatile Thermostat UI (cf. [ici](additions.md#bien-mieux-avec-le-versatile-thermostat-ui-card)), le délestage est représenté comme suit : ![délestage](images/power-exceeded-icon.png).
> 5. Si vous utilisez la carte Verstatile Thermostat UI (cf. [ici](additions.md#bien-mieux-avec-le-versatile-thermostat-ui-card)), le délestage est représenté comme suit : ![délestage](images/power-exceeded-icon.png),
> 6. Un délai pouvant aller jusqu'à 20 sec est possible entre la réception d'une nouvelle valeur du capteur de puissance consommée et la mise en délestage de _VTherm_. Ce délai évite de trop solliciter Home Assistant si vous avez des remontées rapides de votre puissance consommée.

View File

@@ -6,7 +6,7 @@
## Configurer les températures préréglées
Le mode préréglé (preset) vous permet de préconfigurer la température ciblée. Utilisé en conjonction avec Scheduler (voir [scheduler](additions#composant-scheduler-)) vous aurez un moyen puissant et simple d'optimiser la température par rapport à la consommation électrique de votre maison. Les préréglages gérés sont les suivants :
Le mode préréglé (preset) vous permet de préconfigurer la température ciblée. Utilisé en conjonction avec Scheduler (voir [scheduler](additions.md#composant-scheduler-)) vous aurez un moyen puissant et simple d'optimiser la température par rapport à la consommation électrique de votre maison. Les préréglages gérés sont les suivants :
- **Eco** : l'appareil est en mode d'économie d'énergie
- **Confort** : l'appareil est en mode confort
- **Boost** : l'appareil tourne toutes les vannes à fond

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,8 @@ L'installation doit ressembler à ça :
## Configuration
Cliquer sur l'option de menu "Sous-jacents" et vous allez avoir cette page de configuration :
Configurez d'abord les paramètres principaux et communs à tous les _VTherm_ (cf. [paramètres principaux](base-attributes.md)).
Cliquez ensuite sur l'option de menu "Sous-jacents" et vous allez avoir cette page de configuration :
![image](images/config-linked-entity2.png)
@@ -39,7 +40,7 @@ Il est possible de choisir un thermostat `over_climate` qui commande une climati
### L'auto-régulation
En mode `over_cliamte`, le device utilise son propre algorithme de régulation : il s'allume / s'éteint et se met en pause tout seul en fonction de la consigne transmise par le VTherm à travers son entité `climate`. Il utilise pour ça son thermomètre interne et la consigne reçue.
En mode `over_climate`, le device utilise son propre algorithme de régulation : il s'allume / s'éteint et se met en pause tout seul en fonction de la consigne transmise par le VTherm à travers son entité `climate`. Il utilise pour ça son thermomètre interne et la consigne reçue.
Selon l'équipement cette régulation interne peut être plus ou moins bonne. Ca dépend beaucoup de la qualité de l'équipement, du fonctionnement de son thermomètre interne et de son algorithme interne. Pour améliorer les équipements qui régule mal, VTherm propose de tricher un peu sur la consigne qui lui est envoyée en augmentant ou diminuant celle-ci en fonction cette fois de la température de la pièce mesurée par VTherm et non plus de la température interne.
@@ -62,6 +63,8 @@ Une fois l'écart de température redevenu faible, la ventilation se mettra dans
### Compenser la température interne du sous-jacent
Attention : cette option ne doit pas être utilisée avec une régulation par contrôle direct de la vanne si une entité de calibrage a été fournie.
Quelque fois, il arrive que le thermomètre interne du sous-jacent (TRV, climatisation, ...) soit tellement faux que l' auto-régulation ne suffise pas à réguler.
Cela arrive lorsque le thermomètre interne est trop près de la source de chaleur. La température interne monte alors beaucoup plus vite que la température de la pièce, ce qui génère des défauts dans la régulation.
Exemple :
@@ -102,4 +105,4 @@ Lorsque cette entité est 'On', tous les changements de température ou d'état
Attention, si vous utilisez cette fonction, votre équipement est maintenant contrôlé par 2 moyens : _VTherm_ et par vous même directement. Les ordres peuvent être contradictoires et cela peut induire une incompréhension sur l'état de l'équipement. _VTherm_ est équipé d'un mécanisme de temporisation qui évite les boucles : l'utilisateur donne une consigne qui est captée par _VTherm_ qui change la consigne, ... Cette temporisation peut faire que le changement fait directement sur l'équipement est ignoré si ces changements sont trop rapprochés dans le temps.
Certains équipements (Daikin par exemple), changent d'état tout seul. Si la case est cochée, cela peut éteindre le _VTherm_ alors que ce n'est pas ce que vous souhaitiez.
C'est pour ça qu'il est préférable de ne pas l'utiliser. Cela génère beaucoup d'incompréhensions et de nombreuses demandes de support.
C'est pour ça qu'il est préférable de ne pas l'utiliser. Cela génère beaucoup d'incompréhensions et de nombreuses demandes de support.

View File

@@ -7,6 +7,7 @@
- [Le keep-alive](#le-keep-alive)
- [Le mode AC](#le-mode-ac)
- [L'inversion de la commande](#linversion-de-la-commande)
- [La personnalisation des commandes](#la-personnalisation-des-commandes)
## Pré-requis
@@ -17,20 +18,23 @@ L'installation doit ressembler à ça :
1. L'utilisateur ou une automatisation ou le Sheduler programme une consigne (setpoint) par le biais d'un pre-réglage ou directement d'une température,
2. régulièrement le thermomètre intérieur (2) ou extérieur (2b) envoie la température mesurée. Le thermomètre interieur doit être placé à une place pertinente pour le ressenti de l'utilisateur : idéalement au milieu du lieu de vie. Evitez de le mettre trop près d'une fenêtre ou trop proche du radiateur,
3. avec les valeurs de consigne, les différentes températures et des paramètres de l'algorithme TPI (cf. [TPI](algorithms.md#lalgorithme-tpi)), VTherm va calculer un pourcentage de temps d'allumage,
4. et va régulièrement commander l'allumage et l'extinction du ou des entités `switch` sous-jacentes,
5. ces entités switchs sous-jacentes vont alors commander le switch physique
4. et va régulièrement commander l'allumage et l'extinction du ou des entités `switch` (ou `select` ou `climate`) sous-jacentes,
5. ces entités sous-jacentes vont alors commander l'équipement physique
6. la commande du switch physique allumera ou éteindra le radiateur.
> Le pourcentage d'allumage est recalculé à chaque cycle et c'est ce qui permet de réguler la température de la pièce.
## Configuration
Cliquer sur l'option de menu "Sous-jacents" et vous allez avoir cette page de configuration :
Configurez d'abord les paramètres principaux et communs à tous les _VTherm_ (cf. [paramètres principaux](base-attributes.md)).
Ensuite cliquez sur l'option de menu "Sous-jacents" et vous allez avoir cette page de configuration :
![image](images/config-linked-entity.png)
### les sous-jacents
Dans la "liste des équipements à contrôler" vous mettez les switchs qui vont être controllés par le VTherm. Seuls les entités de type `switch` ou `input_boolean` sont acceptées.
Dans la "liste des équipements à contrôler" vous mettez les switchs qui vont être controllés par le VTherm. Seuls les entités de type `switch` ou `input_boolean` ou `select` ou `input_select` ou `climate` sont acceptées.
Si un des sous-jacents n'est pas un `switch` alors la personnalisation des commandes est obligatoires. Par défaut pour les `switch` les commandes sont les commandes classique d'allumage / extinction du switch (`turn_on`, `turn_off`)
L'algorithme à utiliser est aujourd'hui limité à TPI est disponible. Voir [algorithme](#algorithme).
Si plusieurs entités de type sont configurées, la thermostat décale les activations afin de minimiser le nombre de switch actif à un instant t. Ca permet une meilleure répartition de la puissance puisque chaque radiateur va s'allumer à son tour.
@@ -53,3 +57,37 @@ Il est possible de choisir un thermostat over switch qui commande une climatisat
Si votre équipement est commandé par un fil pilote avec un diode, vous aurez certainement besoin de cocher la case "Inverser la case". Elle permet de mettre le switch à `On` lorsqu'on doit étiendre l'équipement et à `Off` lorsqu'on doit l'allumer. Les temps de cycle sont donc inversés avec cette option.
### La personnalisation des commandes
Cette section de configuration permet de personnaliser les commandes d'allumage et d'extinction envoyée à l'équipement sous-jacent,
Ces commandes sont obligatoires si un des sous-jacents n'est pas un `switch` (pour les `switchs` les commandes d'allumage/extinction classiques sont utilisées).
Pour personnaliser les commande, cliquez sur `Ajouter` en bas de page sur les commandes d'allumage et sur les commandes d'extinction :
![virtual switch](images/config-vswitch1.png)
et donner la commande d'allumage et d'exinction avec le format `commande[/attribut[:valeur]]`.
Les commandes possibles dépendent du type de sous-jacents :
| type de sous-jacent | commandes d'allumage possibles | commandes d'extinction possibles | S'applique à |
| --------------------------- | ------------------------------------- | ---------------------------------------------- | ------------------------------ |
| `switch` ou `input_boolean` | `turn_on` | `turn_off` | tous les switchs |
| `select` ou `input_select` | `select_option/option:comfort` | `select_option/option:frost_protection` | Nodon SIN-4-FP-21 et assimilés |
| `climate` (hvac_mode) | `set_hvac_mode/hvac_mode:heat` | `set_hvac_mode/hvac_mode:off` | eCosy (via Tuya Local) |
| `climate` (preset) | `set_preset_mode/preset_mode:comfort` | `set_preset_mode/preset_mode:frost_protection` | Heatzy |
Evidemment, tous ces exemples peuvent être adaptés à votre cas.
Exemple pour un Nodon SIN-4-FP-21 :
![virtual switch Nodon](images/config-vswitch2.png)
Cliquez sur valider pour accepter les modifications.
Si l'erreur suivante se produit :
> La configuration de la personnalisation des commandes est incorrecte. Elle est obligatoire pour les sous-jacents non switch et le format doit être 'service_name[/attribut:valeur]'. Plus d'informations dans le README.
Cela signifie que une des commandes saisies est invalide. Les règles à respecter sont les suivantes :
1. chaque commande doit avoir le format `commande[/attribut[:valeur]]` (ex: `select_option/option:comfort` ou `turn_on`) sans blanc et sans caractères spéciaux sauf '_',
2. il doit y avoir autant de commandes qu'il y a de sous-jacents déclarés sauf si tous les sous-jacents sont des `switchs` auquel cas il n'est pas nécessaire de paramétrer les commandes,
3. si plusieurs sous-jacents sont configurés, les commandes doivent être dans le même ordre. Le nombre de commandes d'allumage doit être égal au nombre de commandes d'extinction et de sous-jacents (dans l'ordre donc). Il est possible de mettre des sous-jacents de type différent. À partir du moment où un sous-jacent n'est pas un `switch`, il faut paramétrer toutes les commandes de tous les sous-jacents y compris des éventuels switchs.

View File

@@ -22,11 +22,12 @@ L'installation doit ressembler à celle pour le VTherm `over_switch` sauf que l'
## Configuration
Cliquer sur l'option de menu "Sous-jacents" et vous allez avoir cette page de configuration. Vous mettez les entités `numnber` ou `input_number`qui vont être controllés par le VTherm :
Configurez d'abord les paramètres principaux et communs à tous les _VTherm_ (cf. [paramètres principaux](base-attributes.md)).
Ensuite cliquez sur l'option de menu "Sous-jacents" et vous allez avoir cette page de configuration. Vous mettez les entités `numnber` ou `input_number`qui vont être controllés par le VTherm :
![image](images/config-linked-entity3.png)
L'algorithme à utiliser est aujourd'hui limité à TPI est disponible. Voir [algorithme](#algorithme).
Il est possible de choisir un thermostat over valve qui commande une climatisation en cochant la case "AC Mode". Dans ce cas, seul le mode refroidissement sera visible.
Il est possible de choisir un thermostat `over-valve` qui commande une climatisation en cochant la case "AC Mode". Dans ce cas, seul le mode refroidissement sera visible.

View File

@@ -5,8 +5,8 @@ Ce thermostat peut piloter 3 types d'équipements :
2. une sonde de température pour la pièce (ou un input_number),
3. un capteur de température externe (pensez à l'intégration météo si vous n'en avez pas)
2. un autre thermostat qui a ses propres modes de fonctionnement (nommé ```thermostat_over_climate```). Pour ce type de thermostat la configuration minimale nécessite :
1. un équipement - comme une climatisation, une valve thermostatique - qui est pilotée par sa propre entity de type ```climate```,
3. un équipement qui peut prendre une valeur de 0 à 100% (nommée ```thermostat_over_valve```). A 0 le chauffage est coupé, 100% il est ouvert à fond. Ce type permet de piloter une valve thermostatique (cf. valve Shelly) qui expose une entité de type `number.` permetttant de piloter directement l'ouverture de la vanne. Versatile Thermostat régule la température de la pièce en jouant sur le pourcentage d'ouverture, à l'aide des capteurs de température intérieur et extérieur en utilisant l'algorithme TPI décrit ci-dessous.
1. un équipement - comme une climatisation, une vanne thermostatique - qui est pilotée par sa propre entity de type ```climate```,
3. un équipement qui peut prendre une valeur de 0 à 100% (nommée ```thermostat_over_valve```). A 0 le chauffage est coupé, 100% il est ouvert à fond. Ce type permet de piloter une vanne thermostatique (cf. TRV Shelly) qui expose une entité de type `number.` permetttant de piloter directement l'ouverture de la vanne. Versatile Thermostat régule la température de la pièce en jouant sur le pourcentage d'ouverture, à l'aide des capteurs de température intérieur et extérieur en utilisant l'algorithme TPI décrit ci-dessous.
Le type `over_climate` vous permet d'ajouter à votre équipement existant toutes les fonctionnalités apportées par VersatileThermostat. L'entité `climate` VersatileThermostat contrôlera votre entité climate sous-jacente, l'éteindra si les fenêtres sont ouvertes, la fera passer en mode Eco si personne n'est présent, etc. Voir [ici] (#pourquoi-un-nouveau-thermostat-implémentation). Pour ce type de thermostat, tous les cycles de chauffage sont contrôlés par l'entité climate sous-jacente et non par le thermostat polyvalent lui-même. Une fonction facultative d'auto-régulation permet au Versatile Thermostat d'ajuster la température donnée en consigne au sous-jacent afin d'atteindre la consigne.
@@ -18,7 +18,7 @@ Ce composant nommé __Versatile thermostat__ gère les cas d'utilisation suivant
- Configuration via l'interface graphique d'intégration standard (à l'aide du flux Config Entry),
- Utilisations complètes du **mode préréglages**,
- Désactiver le mode préréglé lorsque la température est **définie manuellement** sur un thermostat,
- Éteindre/allumer un thermostat ou chager de preset lorsqu'une **porte ou des fenêtres sont ouvertes/fermées** après un certain délai,
- Éteindre/allumer un thermostat ou changer de preset lorsqu'une **porte ou des fenêtres sont ouvertes/fermées** après un certain délai,
- Changer de preset lorsqu'une **activité est détectée** ou non dans une pièce pendant un temps défini,
- Utiliser un algorithme **TPI (Time Proportional Interval)** grâce à l'algorithme [[Argonaute](https://forum.hacf.fr/u/argonaute/summary)] ,
- 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é.
@@ -33,11 +33,12 @@ Toutes ces fonctions sont configurables de façon centralisée ou individuelle e
## Incompatibilités
Certains thermostat de type TRV sont réputés incompatibles avec le Versatile Thermostat. C'est le cas des vannes suivantes :
1. les vannes POPP de Danfoss avec retour de température. Il est impossible d'éteindre cette vanne et elle s'auto-régule d'elle-même causant des conflits avec le VTherm,
1. les vannes POPP de Danfoss avec retour de température. Il est impossible d'éteindre cette vanne et elle s'auto-régule d'elle-même causant des conflits avec le _VTherm_,
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
4. les thermostats de type Rointe ont tendance a se réveiller tout seul. Le reste fonctionne normalement.
5. les TRV de type Aqara SRTS-A01 et MOES TV01-ZB qui n'ont pas le retour d'état `hvac_action` permettant de savoir si elle chauffe ou pas. Donc les retours d'état sont faussés, le reste à l'air fonctionnel.
6. La clim Airwell avec l'intégration "Midea AC LAN". Si 2 commandes de VTherm sont trop rapprochées, la clim s'arrête d'elle même.
7. Les climates basés sur l'intégration Overkiz ne fonctionnent pas. Il parait impossible d'éteindre ni même de changer la température sur ces systèmes.
8. Les systèmes de chauffage basés sur Netatmo fonctionnent mal. Les plannings Netatmo viennent en concurrence de la programmation _VTherm_. Les sous-jacents Netatmo repasse en mode `Auto` tout le temps et ce mode est très mal géré avec _VTherm_ qui ne peut pas savoir si le sysème chauffe ou refroidit et donc quel algorithme choisir. Certains ont quand même réussi à le faire fonctionner avec un switch virtuel entre le _VTherm_ et le sous-jacent mais sans garantie de stabilité. Un exemple est donné dans la section [dépannage](troubleshooting.md)

View File

@@ -0,0 +1,100 @@
# Démarrage rapide
Cette page présente les étapes à suivre pour avoir un _VTherm_ basique mais opérationnel rapidement. Elle est présentée par type d'équipements.
- [Démarrage rapide](#démarrage-rapide)
- [Nodon SIN-4-FP-21 ou assimilé (fil pilote)](#nodon-sin-4-fp-21-ou-assimilé-fil-pilote)
- [Heatzy ou eCosy ou assimilé (entité `climate`)](#heatzy-ou-ecosy-ou-assimilé-entité-climate)
- [Simple switch comme Aqara T1, Nous B2Z, Sonoff ZBMini, Sonoff POW, ...](#simple-switch-comme-aqara-t1-nous-b2z-sonoff-zbmini-sonoff-pow-)
- [Sonoff TRVZB ou assimilé (TRV avec contrôle de la vanne)](#sonoff-trvzb-ou-assimilé-trv-avec-contrôle-de-la-vanne)
- [_PAC_ reversibles ou climatisation ou équipements commandables à travers une entité `climate`](#pac-reversibles-ou-climatisation-ou-équipements-commandables-à-travers-une-entité-climate)
- [La suite](#la-suite)
- [Appel à contribution](#appel-à-contribution)
## Nodon SIN-4-FP-21 ou assimilé (fil pilote)
Ce module permet de commander un radiateur avec un fil pilote. Il est visible sous _HA_ sous la forme d'une entité `select` qui permet de choisir le preset de chauffage à appliquer.
_VTherm_ va réguler la température en changeant de preset via les commandes personnalisées régulièrement jusqu'à ce que la consigne soit atteinte.
Pour que cela fonctionne, il faut le preset utiliser pour la commande de chauffage soit supérieur à la température maximale que vous aurez besoin (24° est une bonne valeur).
Pour l'intégrer dans _VTherm_ vous devez :
1. Créer un _VTherm_ de type `over_switch`. Cf. [création d'un _VTherm_](creation.md),
2. Lui donner des principaux attributs (nom, capteur de température de la pièce et capteur de température extérieure au minimium). Cf. [principaux attributs](base-attributes.md),
3. Lui donner un ou plusieurs équipements sous-jacents à contrôler. Le sous-jacent est ici l'entité `select` qui contrôle le Nodon. Cf. [sous-jacent](over-switch.md),
4. Lui donner des commandes d'allumage et extinction personnalisées (obligatoire pour le Nodon). Cf. [sous-jacent](over-switch.md#la-personnalisation-des-commandes). Les commandes personnalisées sont du type `select_option/option:<preset>` comme indiqué dans le lien.
A l'issue de ces 4 étapes vous avez un _VTherm_ opérationnel ultra simple qui contrôle votre Nodon ou assimilé.
## Heatzy ou eCosy ou assimilé (entité `climate`)
Ce module permet de commander un radiateur qui est visible sous _HA_ sous la forme d'une entité `climate` qui permet de choisir le preset de chauffage à appliquer ou le mode (Chauffe / Froid / éteint).
_VTherm_ va réguler la température en allumant / éteignant via les commandes personnalisées régulièrement l'équipement jusqu'à ce que la consigne soit atteinte.
Pour l'intégrer dans _VTherm_ vous devez :
1. Créer un _VTherm_ de type `over_switch`. Cf. [création d'un _VTherm_](creation.md),
2. Lui donner des principaux attributs (nom, capteur de température de la pièce et capteur de température extérieure au minimium). Cf. [principaux attributs](base-attributes.md),
3. Lui donner un ou plusieurs équipements sous-jacents à contrôler. Le sous-jacent est ici l'entité `climate` qui contrôle le Heatzy ou le eCosy. Cf. [sous-jacent](over-switch.md),
4. Lui donner des commandes d'allumage et extinction personnalisées (obligatoire). Cf. [sous-jacent](over-switch.md#la-personnalisation-des-commandes). Les commandes personnalisées sont du type `set_hvac_mode/hvac_mode:<mode>` ou `set_preset_mode/preset_mode:<preset>` comme indiqué dans le lien.
A l'issue de ces 4 étapes vous avez un _VTherm_ opérationnel ultra simple qui commande votre équipement Heatzy ou eCosy ou assimilé.
## Simple switch comme Aqara T1, Nous B2Z, Sonoff ZBMini, Sonoff POW, ...
Ce module permet de commander un radiateur contrôlé par un simple switch. Il est visible sous _HA_ sous la forme d'une entité `switch` qui permet d'allumer ou éteindre directement le radiateur.
_VTherm_ va réguler la température en allumant / éteignant régulièrement le `switch` jusqu'à ce que la consigne soit atteinte.
Pour l'intégrer dans _VTherm_ vous devez :
1. Créer un _VTherm_ de type `over_switch`. Cf. [création d'un _VTherm_](creation.md),
2. Lui donner des principaux attributs (nom, capteur de température de la pièce et capteur de température extérieure au minimium). Cf. [principaux attributs](base-attributes.md),
3. Lui donner un ou plusieurs équipements sous-jacents à contrôler. Le sous-jacent est ici l'entité `switch` qui contrôle le switch. Cf. [sous-jacent](over-switch.md)
A l'issue de ces 3 étapes vous avez un _VTherm_ opérationnel ultra simple qui contrôle votre `switch` ou assimilé.
## Sonoff TRVZB ou assimilé (TRV avec contrôle de la vanne)
Cet équipement de type _TRV_ contrôle l'ouverture d'une vanne qui laisse plus ou moins passer de l'eau chaude produit par une chaudière ou une PAC. Il est visible sous _HA_ sous la forme d'une entité de type `climate` et d'entités de type `number` qui vont permettre le contrôle de la vanne. Ces entités `number` peuvent être cachées et doivent être explicitement ajoutées dans certains cas.
Le _VTherm_ va moduler le taux d'ouverture de la vanne jusqu'à obtention de la température de consigne.
Pour l'intégrer dans _VTherm_ vous devez :
1. Créer un _VTherm_ de type `over_climate`. Cf. [création d'un _VTherm_](creation.md),
2. Lui donner des principaux attributs (nom, capteur de température de la pièce et capteur de température extérieure au minimium). Cf. [principaux attributs](base-attributes.md),
3. Lui donner un ou plusieurs équipements sous-jacents à contrôler. Le sous-jacent est ici l'entité `climate` qui contrôle le TRV. Cf. [sous-jacent](over-climate.md),
4. Spécifiez la régulation de type `Par contrôle direct de la vanne` uniquement. Laissez décochée l'option `Compenser la température interne du sous-jacent`. Cf. [sous-jacent](over-climate.md#lauto-régulation),
5. Lui donner les entités de type `number` nommées `opening_degree` et `calibration offset`. Ne pas renseigner l'entité `closing degree` Cf. [sous-jacent](over-switch.md),
Pour que cela fonctionne, il faut que le `closing degree` soit réglé au maximum (100%). Ne cocher pas tout de suite l'entité `Follow underlying temperature change` avant d'avoir bien vérifier que cette configuration de base fonctionne.
A l'issue de ces 5 étapes vous avez un _VTherm_ opérationnel ultra simple qui commande votre équipement Sonoff TRVZB ou assimilé.
## _PAC_ reversibles ou climatisation ou équipements commandables à travers une entité `climate`
Les _PAC_ ou assimilés sont visibles sous _HA_ sous la forme d'une entité `climate` qui permet de choisir le preset de chauffage à appliquer ou le mode (Chauffe / Froid / éteint).
_VTherm_ va réguler la température en commandant la consigne de l'équipement et son mode à l'aide de commandes envoyée à l'entité `climate` sous-jacente.
Pour l'intégrer dans _VTherm_ vous devez :
1. Créer un _VTherm_ de type `over_climate`. Cf. [création d'un _VTherm_](creation.md),
2. Lui donner des principaux attributs (nom, capteur de température de la pièce et capteur de température extérieure au minimium). Cf. [principaux attributs](base-attributes.md),
3. Lui donner un ou plusieurs équipements sous-jacents à contrôler. Le sous-jacent est ici l'entité `climate` qui contrôle la _PAC_ ou la clim... . Cf. [sous-jacent](over-climate.md),
A l'issue de ces 3 étapes vous avez un _VTherm_ opérationnel ultra simple qui commande votre _PAC_, climatisation ou assimilé. Pour aller plus loin, une auto-régulation peut êrte nécessaire en fonction du bon fonctionnement de votre équipement. L'auto-régulation consiste à envoyer des consignes légèrement modifiées par _VTherm_ pour forcer l'équipement à chauffer plus ou moins jusqu'à ce que la consigne soit atteinte. L'auto-régulation est détaillée ici : [auto-régulation](self-regulation.md).
# La suite
Uen fois créé, vous devez paramétrer les températures des presets. Cf. [presets](feature-presets.md) pour avoir une configuration minimale.
Vous pouvez aussi (facultatif mais conseillé) installer la carte dédiée pour vos dashboard. (Cf. [VTHerm UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card))
Une fois cette configuration minimale opérationnelle - et seulement lorsque cette configuration minimale fonctionne -, vous pourrez ajouter des fonctions supplémentaires comme la détection de présence pour éviter de chauffer lorsqu'il n'y a personne. Ajoutez les une par une, vérifiez à chaque nouvelle fonction que _VTherm_ réagit correctement avant de passer à la suivante.
Ensuite vous pourrez ajouter une configuration centrale pour mettre en commum des réglages entre tous les _VTherm_, paraméter le mode central permettant de contrôler tous les _VTherms_ d'un coup ([la configuration centralisée](feature-central-mode.md)), ajouter un éventuel contrôle d'une chaudière centrale ([la chaudière centrale](feature-central-boiler.md)). Cette liste est non exhaustive, merci de consulter le sommaire pour avoir la liste de toutes les fonctions de _VTherm_.
# Appel à contribution
Cette page ne demande qu'à se compléter. N'hésitez pas à proposer vos équipements et la configuration minimale.

View File

@@ -61,8 +61,8 @@
| ``power_temp`` | Température si délestaqe | X | X | X | X |
| ``presence_sensor_entity_id`` | Capteur de présence entity id (true si quelqu'un est présent) | X | X | X | - |
| ``minimal_activation_delay`` | Délai minimal d'activation | X | - | - | X |
| ``security_delay_min`` | Délai maximal entre 2 mesures de températures | X | - | X | X |
| ``security_min_on_percent`` | Pourcentage minimal de puissance pour passer en mode sécurité | X | - | X | X |
| ``safety_delay_min`` | Délai maximal entre 2 mesures de températures | X | - | X | X |
| ``safety_min_on_percent`` | Pourcentage minimal de puissance pour passer en mode sécurité | X | - | X | 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 | - | - |
@@ -173,7 +173,7 @@ Si le thermostat est en mode ``security`` les nouveaux paramètres sont appliqu
Pour changer les paramètres de sécurité utilisez le code suivant :
```yaml
service : versatile_thermostat.set_security
service : versatile_thermostat.set_safety
data:
min_on_percent: "0.5"
default_on_percent: "0.1"
@@ -202,7 +202,7 @@ Les évènements notifiés sont les suivants:
- ``versatile_thermostat_security_event`` : un thermostat entre ou sort du preset ``security``
- ``versatile_thermostat_power_event`` : un thermostat entre ou sort du preset ``power``
- ``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 `safety_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_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.
@@ -247,13 +247,13 @@ Les attributs personnalisés sont les suivants :
| ``saved_preset_mode`` | Le dernier preset utilisé avant le basculement automatique du preset |
| ``saved_target_temp`` | La dernière température utilisée avant la commutation automatique |
| ``window_state`` | Le dernier état connu du capteur de fenêtre. Aucun si la fenêtre n'est pas configurée |
| ``window_bypass_state`` | True si le bypass de la détection d'ouverture et activé |
| ``is_window_bypass`` | True si le bypass de la détection d'ouverture et activé |
| ``motion_state`` | Le dernier état connu du capteur de mouvement. Aucun si le mouvement n'est pas configuré |
| ``overpowering_state`` | Le dernier état connu du capteur surpuissant. Aucun si la gestion de l'alimentation n'est pas configurée |
| ``presence_state`` | Le dernier état connu du capteur de présence. Aucun si la gestion de présence n'est pas configurée |
| ``security_delay_min`` | Le délai avant d'activer le mode de sécurité lorsque un des 2 capteurs de température n'envoie plus de mesures |
| ``security_min_on_percent`` | Pourcentage de chauffe en dessous duquel le thermostat ne passera pas en sécurité |
| ``security_default_on_percent`` | Pourcentage de chauffe utilisé lorsque le thermostat est en sécurité |
| ``safety_delay_min`` | Le délai avant d'activer le mode de sécurité lorsque un des 2 capteurs de température n'envoie plus de mesures |
| ``safety_min_on_percent`` | Pourcentage de chauffe en dessous duquel le thermostat ne passera pas en sécurité |
| ``safety_default_on_percent`` | Pourcentage de chauffe utilisé lorsque le thermostat est en sécurité |
| ``last_temperature_datetime`` | La date et l'heure au format ISO8866 de la dernière réception de température interne |
| ``last_ext_temperature_datetime`` | La date et l'heure au format ISO8866 de la dernière réception de température extérieure |
| ``security_state`` | L'état de sécurité. vrai ou faux |

View File

@@ -2,6 +2,9 @@
![Nouveau](images/new-icon.png)
> * **Release 7.1**:
> - Refonte de la fonction de délestage (gestion de la puissance). Le délestage est maintenant géré de façon centralisé (auparavent chaque _VTherm_ était autonome). Cela permet une gestion bien plus efficace et de prioriser le délestage sur les équipements qui sont proches de la consigne. Attention, vous devez impérativement avoir une configuration centralisée avec gestion de la puissance pour que cela fonctionne. Plus d'infos [ici](./feature-power.md)
> * **Release 6.8**:
> - Ajout d'une nouvelle méthode de régulation pour les Versatile Thermostat de type `over_climate`. Cette méthode nommée 'Contrôle direct de la vanne' permet de contrôler directement la vanne d'un TRV et éventuellement un décalage pour calibrer le thermomètre interne de votre TRV. Cette nouvelle méthode a été testée avec des Sonoff TRVZB et généralisée pour d'autre type de TRV pour lesquels la vanne est directement commandable via des entités de type `number`. Plus d'informations [ici](over-climate.md#lauto-régulation) et [ici](self-regulation.md#auto-régulation-par-contrôle-direct-de-la-vanne).
@@ -24,7 +27,7 @@
> * **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.1** : Limitation des valeurs envoyées aux vannes et au température envoyées au climate sous-jacent.
> * **Release 5.0** : Ajout d'une configuration centrale permettant de mettre en commun les attributs qui peuvent l'être [#239](https://github.com/jmcollin78/versatile_thermostat/issues/239).
> * **Release 4.3** : Ajout d'un mode auto-fan pour le type `over_climate` permettant d'activer la ventilation si l'écart de température est important [#223](https://github.com/jmcollin78/versatile_thermostat/issues/223).
> * **Release 4.2** : Le calcul de la pente de la courbe de température se fait maintenant en °/heure et non plus en °/min [#242](https://github.com/jmcollin78/versatile_thermostat/issues/242). Correction de la détection automatique des ouvertures par l'ajout d'un lissage de la courbe de température .

View File

@@ -2,8 +2,11 @@
# L'auto-régulation
- [L'auto-régulation](#lauto-régulation)
- [Configuration](#configuration)
- [auto-régulation par contrôle direct de la vanne](#auto-régulation-par-contrôle-direct-de-la-vanne)
- [autres auto-régulation](#autres-auto-régulation)
- [L'auto-régulation en mode Expert](#lauto-régulation-en-mode-expert)
- [Synthèse de l'algorithme d'auto-régulation](#synthèse-de-lalgorithme-dauto-régulation)
- [Synthèse de l'algorithme d'auto-régulation](#synthèse-de-lalgorithme-dauto-régulation)
Vous avez la possibilité d'activer la fonction d'auto-régulation pour les _VTherm_ de type `over_climate` uniquement.
@@ -32,13 +35,15 @@ Elle permet de configurer les entités de contrôle de la vanne :
Vous devez donner :
1. autant d'entités de contrôle d'ouverture de la vanne qu'il y a de sous-jacents et dans le même odre. Ces paramètres sont obligatoires,
2. autant d'entités de calibrage du décalage de température qu'il y a de sous-jacents et dans le même ordre. Ces paramètres sont facultatifs ; ils doivent être tous founis ou aucun,
3. autant d'entités de de contrôile du taux de fermture qu'il y a de sous-jacents et dans le même ordre. Ces paramètres sont facultatifs ; ils doivent être tous founis ou aucun,
4. une liste de valeurs minimales d'ouverture de la vanne lorsqu'elle doit être ouverte. Ce champ est une liste d'entier. Si la vanne doit être ouverte, elle le sera au minimum avec cette valeur d'ouverture. Cela permet de laisser passer suffisamment d'eau lorsqu'elle doit être ouverte.
3. autant d'entités de de contrôile du taux de fermture qu'il y a de sous-jacents et dans le même ordre. Ces paramètres sont facultatifs ; ils doivent être tous founis ou aucun. Pour les Sonoff TRVZB, ils ne doivent pas être fournis,
4. une liste de valeurs minimales d'ouverture de la vanne lorsqu'elle doit être ouverte. Ce champ est une liste d'entier. Si la vanne doit être ouverte, elle le sera au minimum avec cette valeur d'ouverture, sinon elle sera totalement close (0). Cela permet de laisser passer suffisamment d'eau lorsqu'elle doit être ouverte mais garde la fermeeture complète si il n'y a pas besoin de chauffer.
L'algorithme de calcul du taux d'ouverture est basé sur le _TPI_ qui est décrit [ici](algorithms.md). C'est le même algorithme qui est utilisé pour les _VTherm_ `over_switch` et `over_valve`.
Si une entité de type taux de fermeture de la vanne est configurée, il sera positionné avec la valeur 100 - taux d'ouverture pour forcer la vanne dans un état.
Note: pour les Sonoff TRVZB, vous ne devez pas configurer les "closing degree". Cela rend inopérant le `hvac_action` qui est utilisé par _VTherm_ et qui indique que l'équipement est en chauffe.
### autres auto-régulation
Dans ce deuxième cas, le Versatile Thermostat calcule un décalage basé sur les informations suivantes :
@@ -150,4 +155,4 @@ Pour que les modifications soient prises en compte, il faut soit **relancer tota
## Synthèse de l'algorithme d'auto-régulation
Une synthèse de l'algorithme d'auto-régulation est décrite [ici](algorithms.md#lalgorithme-dauto-régulation-sans-contrôle-de-la-vanne)
Une synthèse de l'algorithme d'auto-régulation est décrite [ici](algorithms.md#lalgorithme-dauto-régulation-sans-contrôle-de-la-vanne)

View File

@@ -4,6 +4,7 @@
- [Dépannages](#dépannages)
- [Utilisation d'un Heatzy](#utilisation-dun-heatzy)
- [Utilisation d'un radiateur avec un fil pilote (Nodon SIN-4-FP-21)](#utilisation-dun-radiateur-avec-un-fil-pilote-nodon-sin-4-fp-21)
- [Utilisation d'un système Netatmo](#utilisation-dun-système-netatmo)
- [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)
@@ -16,10 +17,15 @@
- [Utilisation d'un groupe de personnes comme capteur de présence](#utilisation-dun-groupe-de-personnes-comme-capteur-de-présence)
- [Activer les logs du Versatile Thermostat](#activer-les-logs-du-versatile-thermostat)
- [VTherm ne suit pas les changements de consigne faits directement depuis le sous-jacents (`over_climate`)](#vtherm-ne-suit-pas-les-changements-de-consigne-faits-directement-depuis-le-sous-jacents-over_climate)
- [VTherm passe tout seul en mode 'clim' ou en mode 'chauffage'](#vtherm-passe-tout-seul-en-mode-clim-ou-en-mode-chauffage)
## Utilisation d'un Heatzy
Le Heatzy est maintenant pris en charge nativement par _VTherm_. Cf. [Démarrage rapide](quick-start.md#heatzy-ou-ecosy-ou-assimilé-entité-climate).
Cette configuration est gardée pour mémoire uniquement.
L'utilisation d'un Heatzy ou Nodon est possible à la condition d'utiliser un switch virtuel sur ce modèle :
```yaml
@@ -51,6 +57,11 @@ L'utilisation d'un Heatzy ou Nodon est possible à la condition d'utiliser un sw
Merci à @gael pour cet exemple.
## Utilisation d'un radiateur avec un fil pilote (Nodon SIN-4-FP-21)
Le Nodon est maintenant pris en charge nativement par _VTherm_. Cf. [Démarrage rapide](quick-start.md#nodon-sin-4-fp-21-ou-assimilé-fil-pilote).
Cette configuration est gardée pour mémoire uniquement.
Comme pour le Heatzy ci-dessus vous pouvez utiliser un switch virtuel qui va changer le preset de votre radiateur en fonction de l'état d'allumage du VTherm.
Exemple :
@@ -84,6 +95,15 @@ Exemple :
```
Un exemple plus complet est [ici](https://github.com/jmcollin78/versatile_thermostat/discussions/431#discussioncomment-11393065)
## Utilisation d'un système Netatmo
Le système à base de TRV Netatmo fonctionne mal avec _VTherm_. Vous avez ici une discussion sur le fonctionnement particulier des systèmes Netatmo (en Français) : https://forum.hacf.fr/t/vannes-netatmo-et-vtherm/56063
Cependant certains ont réussi une intégration _VTerm_ Netatmo en intégrant un switch virtuel entre _VTherm_ et le `climate` Netatmo suivant :
```
TODO add virtual switch code
```
## Seul le premier radiateur chauffe
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.
@@ -123,9 +143,9 @@ versatile_thermostat:
Ces paramètres sont sensibles et assez difficiles à régler. Merci de ne les utiliser que si vous savez ce que vous faites et que vos mesures de température ne sont pas déjà lisses.
## Pourquoi mon Versatile Thermostat se met en Securite ?
Le mode sécurité est possible sur les types de VTherm de type `over_switch` et `over_valve` uniquement. Il survient lorsqu'un des 2 thermomètres qui donne la température de la pièce ou la température extérieure n'a pas envoyé de valeur depuis plus de `security_delay_min` minutes et que le radiateur chauffait à au moins `security_min_on_percent`. Cf. [mode sécurité](feature-advanced.md#la-mise-en-sécurité)
Le mode sécurité est possible sur les types de VTherm de type `over_switch` et `over_valve` uniquement. Il survient lorsqu'un des 2 thermomètres qui donne la température de la pièce ou la température extérieure n'a pas envoyé de valeur depuis plus de `safety_delay_min` minutes et que le radiateur chauffait à au moins `safety_min_on_percent`. Cf. [mode sécurité](feature-advanced.md#la-mise-en-sécurité)
Comme l'algorithme est basé sur les mesures de température, si elles ne sont plus reçues par le VTherm, il y a un risque de surchauffe et d'incendie. Pour éviter ça, lorsque les conditions rappelées ci-dessus sont détectées, la chauffe est limité au paramètre `security_default_on_percent`. Cette valeur doit donc être raisonnablement faible (10% est une bonne valeur). Elle permet d'éviter un incendie tout en évitant de couper totalement le radiateur (risque de gel).
Comme l'algorithme est basé sur les mesures de température, si elles ne sont plus reçues par le VTherm, il y a un risque de surchauffe et d'incendie. Pour éviter ça, lorsque les conditions rappelées ci-dessus sont détectées, la chauffe est limité au paramètre `safety_default_on_percent`. Cette valeur doit donc être raisonnablement faible (10% est une bonne valeur). Elle permet d'éviter un incendie tout en évitant de couper totalement le radiateur (risque de gel).
Tous ces paramètres se règlent dans la dernière page de la configuration du VTherm : "Paramètres avancés".
@@ -149,14 +169,14 @@ last_temperature_datetime: "2023-12-06T18:43:28.346010+01:00"
last_ext_temperature_datetime: "2023-12-06T13:04:35.164367+01:00"
last_update_datetime: "2023-12-06T18:43:28.351103+01:00"
...
security_delay_min: 60
safety_delay_min: 60
```
On voit que :
1. le VTherm est bien en mode sécurité (`security_state: true`),
2. l'heure courante est le 06/12/2023 à 18h43:28 (`last_update_datetime: "2023-12-06T18:43:28.351103+01:00"`),
3. l'heure de dernière réception de la température intérieure est le 06/12/2023 à 18h43:28 (`last_temperature_datetime: "2023-12-06T18:43:28.346010+01:00"`). Elle est donc récente,
4. l'heure de dernière réception de la température extérieure est le 06/12/2023 à 13h04:35 (`last_ext_temperature_datetime: "2023-12-06T13:04:35.164367+01:00`). C'est donc l'heure extérieure qui a plus de 5 h de retard et qui a provoquée le passage en mode sécurité, car le seuil est limité à 60 min (`security_delay_min: 60`).
4. l'heure de dernière réception de la température extérieure est le 06/12/2023 à 13h04:35 (`last_ext_temperature_datetime: "2023-12-06T13:04:35.164367+01:00`). C'est donc l'heure extérieure qui a plus de 5 h de retard et qui a provoquée le passage en mode sécurité, car le seuil est limité à 60 min (`safety_delay_min: 60`).
### Comment être averti lorsque cela se produit ?
Pour être averti, le VTherm envoie un évènement dès que ça se produit et un en fin d'alerte sécurité. Vous pouvez capter ces évènements dans une automatisation et envoyer une notification par exemple, faire clignoter un voyant, déclencher une sirène, ... A vous de voir.
@@ -166,8 +186,8 @@ Pour manipuler les évènements générés par le VTherm, cf. [Eveènements](#ev
### Comment réparer ?
Cela va dépendre de la cause du problème :
1. Si un capteur est en défaut, il faut le réparer (remettre des piles, le changer, vérifier l'intégration Météo qui donne la température extérieure, ...),
2. Si le paramètre `security_delay_min` est trop petit, cela risque de générer beaucoup de fausses alertes. Une valeur correcte est de l'ordre de 60 min, surtout si vous avez des capteurs de température à pile. Cf [mes réglages](tuning-examples.md#le-capteur-de-température-alimenté-par-batterie)
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 ou Zigbee), on peut forcer le délai max entre 2 mesures. Il conviendra de mettre un délai max < `security_delay_min`,
2. Si le paramètre `safety_delay_min` est trop petit, cela risque de générer beaucoup de fausses alertes. Une valeur correcte est de l'ordre de 60 min, surtout si vous avez des capteurs de température à pile. Cf [mes réglages](tuning-examples.md#le-capteur-de-température-alimenté-par-batterie)
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 ou Zigbee), on peut forcer le délai max entre 2 mesures. Il conviendra de mettre un délai max < `safety_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.
5. Si c'est le capteur de température extérieur qui est en défaut, vous pouvez désactiver le déclenchement du mode sécurité puisqu'il influe assez peu sur le résultat. Pour ce faire, cf. [ici](feature-advanced.md#la-mise-en-sécurité)
@@ -210,4 +230,9 @@ Attention, en mode debug Versatile Thermostat est très verbeux et peut vite ral
## VTherm ne suit pas les changements de consigne faits directement depuis le sous-jacents (`over_climate`)
Voir le détail de cette fonction [ici](over-climate.md#suivre-les-changements-de-température-du-sous-jacent).
Voir le détail de cette fonction [ici](over-climate.md#suivre-les-changements-de-température-du-sous-jacent).
## VTherm passe tout seul en mode 'clim' ou en mode 'chauffage'
Certaine _PAC_ réversibles ont des modes qui permettent de laisser le choix à la _PAC_ de chauffer ou de réfroidir. Ces modes sont 'Auto' or 'Heat_cool' selon les marques. Ces 2 modes ne doivent pas être utilisés avec _VTherm_ car les algorithmes de _VTherm_ ont besoin de savoir si ils sont en mode chauffe ou refroidissement ce que ne permettent pas ces modes.
Vous devez donc utiliser uniquement les modes : `Heat`, `Cool`, `Off` ou `Fan` éventuellement (bien que fan n'a aucun sens avec _Vtherm)

View File

@@ -18,9 +18,9 @@
## Le capteur de température alimenté par batterie
Ces capteurs sont souvent paresseux et n'envoit pas toujours de mesure de température lorsqu'elle est stable. Par conséquent, les réglages doivent être laches pour éviter les faux positifs.
- security_delay_min : 60 min (parce que ces capteurs sont paresseux)
- security_min_on_percent : 0,7 (70% - on passe en mode sécurité si le radiateur chauffait plus de 70% du temps)
- security_default_on_percent : 0,4 (40% - en mode sécurité, on garde un fond de chauffe de 40% du temps pour éviter d'avoir trop froid)
- safety_delay_min : 60 min (parce que ces capteurs sont paresseux)
- safety_min_on_percent : 0,7 (70% - on passe en mode sécurité si le radiateur chauffait plus de 70% du temps)
- safety_default_on_percent : 0,4 (40% - en mode sécurité, on garde un fond de chauffe de 40% du temps pour éviter d'avoir trop froid)
Il faut comprendre ces réglages comme suit :
@@ -34,9 +34,9 @@ Versatile Thermostat vous permet d'être notifié lorsqu'un évènement de ce ty
## Capteur de température réactif (sur secteur)
Un thermomètre alimenté est censé est très régulier dans l'envoi des températures. Si il n'envoie rien pendant 15 min, il a certainement un soucis et on peut réagir plus vite sans risque de faux positif.
- security_delay_min : 15 min
- security_min_on_percent : 0,5 (50% - on passe en preset ``security`` si le radiateur chauffait plus de 50% du temps)
- security_default_on_percent : 0,25 (20% - en preset ``security``, on garde un fond de chauffe de 20% du temps)
- safety_delay_min : 15 min
- safety_min_on_percent : 0,5 (50% - on passe en preset ``security`` si le radiateur chauffait plus de 50% du temps)
- safety_default_on_percent : 0,25 (20% - en preset ``security``, on garde un fond de chauffe de 20% du temps)
## Mes presets

12
faq.md
View File

@@ -153,9 +153,9 @@ Ces paramètres sont sensibles et assez difficiles à régler. Merci de ne les u
<summary>Pourquoi mon Versatile Thermostat se met en Securite ?</summary>
## Pourquoi mon Versatile Thermostat se met en Securite ?
Le mode sécurité est possible sur tous les types de VTherm . Il survient lorsqu'un des 2 thermomètres qui donne la température de la pièce ou la température extérieure n'a pas envoyé de valeur depuis plus de `security_delay_min` minutes et que le radiateur chauffait à au moins `security_min_on_percent`.
Le mode sécurité est possible sur tous les types de VTherm . Il survient lorsqu'un des 2 thermomètres qui donne la température de la pièce ou la température extérieure n'a pas envoyé de valeur depuis plus de `safety_delay_min` minutes et que le radiateur chauffait à au moins `safety_min_on_percent`.
Comme l'algorithme est basé sur les mesures de température, si elles ne sont plus reçues par le VTherm, il y a un risque de surchauffe et d'incendie. Pour éviter ça, lorsque les conditions rappelées ci-dessus sont détectées, la chauffe est limité au paramètre `security_default_on_percent`. Cette valeur doit donc être raisonnablement faible (10% est une bonne valeur). Elle permet d'éviter un incendie tout en évitant de couper totalement le radiateur (risque de gel).
Comme l'algorithme est basé sur les mesures de température, si elles ne sont plus reçues par le VTherm, il y a un risque de surchauffe et d'incendie. Pour éviter ça, lorsque les conditions rappelées ci-dessus sont détectées, la chauffe est limité au paramètre `safety_default_on_percent`. Cette valeur doit donc être raisonnablement faible (10% est une bonne valeur). Elle permet d'éviter un incendie tout en évitant de couper totalement le radiateur (risque de gel).
Tous ces paramètres se règlent dans la dernière page de la configuration du VTherm : "Paramètres avancés".
@@ -178,14 +178,14 @@ last_temperature_datetime: "2023-12-06T18:43:28.346010+01:00"
last_ext_temperature_datetime: "2023-12-06T13:04:35.164367+01:00"
last_update_datetime: "2023-12-06T18:43:28.351103+01:00"
...
security_delay_min: 60
safety_delay_min: 60
```
On voit que :
1. le VTherm est bien en mode sécurité (`security_state: true`),
2. l'heure courante est le 06/12/2023 à 18h43:28 (`last_update_datetime: "2023-12-06T18:43:28.351103+01:00"`),
3. l'heure de dernière réception de la température intérieure est le 06/12/2023 à 18h43:28 (`last_temperature_datetime: "2023-12-06T18:43:28.346010+01:00"`). Elle est donc récente,
4. l'heure de dernière réception de la température extérieure est le 06/12/2023 à 13h04:35 (`last_ext_temperature_datetime: "2023-12-06T13:04:35.164367+01:00`). C'est donc l'heure extérieure qui a plus de 5 h de retard et qui a provoquée le passage en mode sécurité, car le seuil est limité à 60 min (`security_delay_min: 60`).
4. l'heure de dernière réception de la température extérieure est le 06/12/2023 à 13h04:35 (`last_ext_temperature_datetime: "2023-12-06T13:04:35.164367+01:00`). C'est donc l'heure extérieure qui a plus de 5 h de retard et qui a provoquée le passage en mode sécurité, car le seuil est limité à 60 min (`safety_delay_min: 60`).
### Comment être averti lorsque cela se produit ?
Pour être averti, le VTherm envoie un évènement dès que ça se produit et un en fin d'alerte sécurité. Vous pouvez capter ces évènements dans une automatisation et envoyer une notification par exemple, faire clignoter un voyant, déclencher une sirène, ... A vous de voir.
@@ -195,8 +195,8 @@ Pour manipuler les évènements générés par le VTherm, cf. [Eveènements](#ev
### Comment réparer ?
Cela va dépendre de la cause du problème :
1. Si un capteur est en défaut, il faut le réparer (remettre des piles, le changer, vérifier l'intégration Météo qui donne la température extérieure, ...),
2. Si le paramètre `security_delay_min` est trop petit, cela rsique de générer beaucoup de fausses alertes. Une valeur correcte est de l'ordre de 60 min, surtout si vous avez des capteurs de température à pile.
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`,
2. Si le paramètre `safety_delay_min` est trop petit, cela rsique de générer beaucoup de fausses alertes. Une valeur correcte est de l'ordre de 60 min, surtout si vous avez des capteurs de température à pile.
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 < `safety_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.
</details>

View File

@@ -3,5 +3,5 @@
"content_in_root": false,
"render_readme": true,
"hide_default_branch": false,
"homeassistant": "2024.12.3"
"homeassistant": "2025.1.2"
}

BIN
images/new-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,3 @@
[tool.black]
# don't work. Options are in the devcontainer.yaml
line-length = 180

View File

@@ -1 +1 @@
homeassistant==2024.12.3
homeassistant==2025.1.4

View File

@@ -1,4 +1,4 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, abstract-method, too-many-lines, redefined-builtin
# pylint: disable=wildcard-import, unused-wildcard-import, unused-import, protected-access, unused-argument, line-too-long, abstract-method, too-many-lines, redefined-builtin
""" Some common resources """
import asyncio
@@ -8,7 +8,16 @@ from unittest.mock import patch, MagicMock # pylint: disable=unused-import
import pytest # pylint: disable=unused-import
from homeassistant.core import HomeAssistant, Event, EVENT_STATE_CHANGED, State
from homeassistant.const import UnitOfTemperature, STATE_ON, STATE_OFF, ATTR_TEMPERATURE
from homeassistant.const import (
UnitOfTemperature,
STATE_ON,
STATE_OFF,
ATTR_TEMPERATURE,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
STATE_HOME,
STATE_NOT_HOME,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.helpers.entity import Entity
@@ -50,8 +59,6 @@ from .const import ( # pylint: disable=unused-import
MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG,
MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG,
MOCK_TH_OVER_SWITCH_TPI_CONFIG,
MOCK_PRESETS_CONFIG,
MOCK_PRESETS_AC_CONFIG,
MOCK_WINDOW_CONFIG,
MOCK_MOTION_CONFIG,
MOCK_POWER_CONFIG,
@@ -80,7 +87,6 @@ FULL_SWITCH_CONFIG = (
| MOCK_TH_OVER_SWITCH_CENTRAL_MAIN_CONFIG
| MOCK_TH_OVER_SWITCH_TYPE_CONFIG
| MOCK_TH_OVER_SWITCH_TPI_CONFIG
| MOCK_PRESETS_CONFIG
| MOCK_FULL_FEATURES
| MOCK_WINDOW_CONFIG
| MOCK_MOTION_CONFIG
@@ -95,7 +101,6 @@ FULL_SWITCH_AC_CONFIG = (
| MOCK_TH_OVER_SWITCH_CENTRAL_MAIN_CONFIG
| MOCK_TH_OVER_SWITCH_AC_TYPE_CONFIG
| MOCK_TH_OVER_SWITCH_TPI_CONFIG
| MOCK_PRESETS_AC_CONFIG
| MOCK_FULL_FEATURES
| MOCK_WINDOW_CONFIG
| MOCK_MOTION_CONFIG
@@ -105,12 +110,7 @@ FULL_SWITCH_AC_CONFIG = (
)
PARTIAL_CLIMATE_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_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_ADVANCED_CONFIG
)
PARTIAL_CLIMATE_CONFIG_USE_DEVICE_TEMP = (
@@ -118,7 +118,6 @@ PARTIAL_CLIMATE_CONFIG_USE_DEVICE_TEMP = (
| MOCK_TH_OVER_CLIMATE_MAIN_CONFIG
| MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG
| MOCK_TH_OVER_CLIMATE_TYPE_USE_DEVICE_TEMP_CONFIG
| MOCK_PRESETS_CONFIG
| MOCK_ADVANCED_CONFIG
)
@@ -127,24 +126,17 @@ PARTIAL_CLIMATE_NOT_REGULATED_CONFIG = (
| MOCK_TH_OVER_CLIMATE_MAIN_CONFIG
| MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG
| MOCK_TH_OVER_CLIMATE_TYPE_NOT_REGULATED_CONFIG
| MOCK_PRESETS_CONFIG
| MOCK_ADVANCED_CONFIG
)
PARTIAL_CLIMATE_AC_CONFIG = (
MOCK_TH_OVER_CLIMATE_USER_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_ADVANCED_CONFIG
MOCK_TH_OVER_CLIMATE_USER_CONFIG | MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG | MOCK_TH_OVER_CLIMATE_MAIN_CONFIG | MOCK_TH_OVER_CLIMATE_CENTRAL_MAIN_CONFIG | MOCK_ADVANCED_CONFIG
)
FULL_4SWITCH_CONFIG = (
MOCK_TH_OVER_4SWITCH_USER_CONFIG
| MOCK_TH_OVER_4SWITCH_TYPE_CONFIG
| MOCK_TH_OVER_SWITCH_TPI_CONFIG
| MOCK_PRESETS_CONFIG
| MOCK_WINDOW_CONFIG
| MOCK_MOTION_CONFIG
| MOCK_POWER_CONFIG
@@ -176,6 +168,7 @@ FULL_CENTRAL_CONFIG = {
"comfort_ac_away_temp": 0,
"boost_ac_away_temp": 30.7,
CONF_WINDOW_DELAY: 15,
CONF_WINDOW_OFF_DELAY: 30,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 4,
CONF_WINDOW_AUTO_CLOSE_THRESHOLD: 1,
CONF_WINDOW_AUTO_MAX_DURATION: 31,
@@ -188,9 +181,10 @@ FULL_CENTRAL_CONFIG = {
CONF_PRESENCE_SENSOR: "binary_sensor.mock_presence_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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 61,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.2,
CONF_USE_CENTRAL_BOILER_FEATURE: False,
}
@@ -229,9 +223,10 @@ FULL_CENTRAL_CONFIG_WITH_BOILER = {
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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 61,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.2,
CONF_USE_CENTRAL_BOILER_FEATURE: True,
CONF_CENTRAL_BOILER_ACTIVATION_SRV: "switch.pompe_chaudiere/switch.turn_on",
CONF_CENTRAL_BOILER_DEACTIVATION_SRV: "switch.pompe_chaudiere/switch.turn_off",
@@ -583,19 +578,22 @@ class MockNumber(NumberEntity):
async def create_thermostat(
hass: HomeAssistant, entry: MockConfigEntry, entity_id: str
hass: HomeAssistant,
entry: MockConfigEntry,
entity_id: str,
temps: dict | None = None,
) -> BaseThermostat:
"""Creates and return a TPI Thermostat"""
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
assert entry.state is ConfigEntryState.LOADED
# We should reload the VTherm links
# vtherm_api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api()
# central_config = vtherm_api.find_central_configuration()
entity = search_entity(hass, entity_id, CLIMATE_DOMAIN)
# if entity and hasattr(entity, "init_presets")::
# await entity.init_presets(central_config)
if entity and temps:
await set_all_climate_preset_temp(
hass, entity, temps, entity.entity_id.replace("climate.", "")
)
return entity
@@ -737,9 +735,11 @@ async def send_power_change_event(entity: BaseThermostat, new_power, date, sleep
)
},
)
await entity._async_power_changed(power_event)
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
await vtherm_api.central_power_manager._power_sensor_changed(power_event)
await vtherm_api.central_power_manager._do_immediate_shedding()
if sleep:
await asyncio.sleep(0.1)
await entity.hass.async_block_till_done()
async def send_max_power_change_event(
@@ -763,9 +763,11 @@ async def send_max_power_change_event(
)
},
)
await entity._async_max_power_changed(power_event)
vtherm_api = VersatileThermostatAPI.get_vtherm_api()
await vtherm_api.central_power_manager._max_power_sensor_changed(power_event)
await vtherm_api.central_power_manager._do_immediate_shedding()
if sleep:
await asyncio.sleep(0.1)
await entity.hass.async_block_till_done()
async def send_window_change_event(
@@ -796,7 +798,7 @@ async def send_window_change_event(
),
},
)
ret = await entity._async_windows_changed(window_event)
ret = await entity.window_manager._window_sensor_changed(window_event)
if sleep:
await asyncio.sleep(0.1)
return ret
@@ -830,14 +832,14 @@ async def send_motion_change_event(
),
},
)
ret = await entity._async_motion_changed(motion_event)
ret = await entity.motion_manager._motion_sensor_changed(motion_event)
if sleep:
await asyncio.sleep(0.1)
return ret
async def send_presence_change_event(
entity: BaseThermostat, new_state: bool, old_state: bool, date, sleep=True
vtherm: BaseThermostat, new_state: bool, old_state: bool, date, sleep=True
):
"""Sending a new presence event simulating a change on the window state"""
_LOGGER.info(
@@ -845,26 +847,26 @@ async def send_presence_change_event(
new_state,
old_state,
date,
entity,
vtherm,
)
presence_event = Event(
EVENT_STATE_CHANGED,
{
"new_state": State(
entity_id=entity.entity_id,
entity_id=vtherm.entity_id,
state=STATE_ON if new_state else STATE_OFF,
last_changed=date,
last_updated=date,
),
"old_state": State(
entity_id=entity.entity_id,
entity_id=vtherm.entity_id,
state=STATE_ON if old_state else STATE_OFF,
last_changed=date,
last_updated=date,
),
},
)
ret = await entity._async_presence_changed(presence_event)
ret = await vtherm._presence_manager._presence_sensor_changed(presence_event)
if sleep:
await asyncio.sleep(0.1)
return ret
@@ -1000,7 +1002,7 @@ async def set_climate_preset_temp(
await temp_entity.async_set_native_value(temp)
else:
_LOGGER.warning(
"commons tests set_cliamte_preset_temp: cannot find number entity with entity_id '%s'",
"commons tests set_climate_preset_temp: cannot find number entity with entity_id '%s'",
number_entity_id,
)
@@ -1023,6 +1025,16 @@ default_temperatures_ac_away = {
"boost_ac_away": 23.1,
}
default_temperatures_ac = {
"frost": 7.0,
"eco": 17.0,
"comfort": 19.0,
"boost": 21.0,
"eco_ac": 27.0,
"comfort_ac": 25.0,
"boost_ac": 23.0,
}
default_temperatures_away = {
"frost": 7.0,
"eco": 17.0,
@@ -1062,9 +1074,14 @@ async def set_all_climate_preset_temp(
NUMBER_DOMAIN,
)
assert temp_entity
if not temp_entity:
raise ConfigurationNotCompleteError(
f"'{number_entity_name}' don't exists as number entity"
)
# Because set_value is not implemented in Number class (really don't understand why...)
assert temp_entity.state == value
await hass.async_block_till_done()
#
# Side effects management
@@ -1092,3 +1109,9 @@ class SideEffects:
def add_or_update_side_effect(self, key: str, new_value: Any):
"""Update the value of a side effect"""
self._current_side_effects[key] = new_value
async def do_central_power_refresh(hass):
"""Do a central power refresh"""
await VersatileThermostatAPI.get_vtherm_api().central_power_manager.refresh_state()
return hass.async_block_till_done()

View File

@@ -19,6 +19,8 @@
from unittest.mock import patch
import pytest
# https://github.com/miketheman/pytest-socket/pull/275
from pytest_socket import socket_allow_hosts
from homeassistant.core import StateMachine
@@ -26,6 +28,12 @@ from custom_components.versatile_thermostat.config_flow import (
VersatileThermostatBaseConfigFlow,
)
from custom_components.versatile_thermostat.const import (
CONF_POWER_SENSOR,
CONF_MAX_POWER_SENSOR,
CONF_USE_POWER_FEATURE,
CONF_PRESET_POWER,
)
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
@@ -35,12 +43,6 @@ from .commons import (
FULL_CENTRAL_CONFIG_WITH_BOILER,
)
# https://github.com/miketheman/pytest-socket/pull/275
from pytest_socket import socket_allow_hosts
# ...
# ...
def pytest_runtest_setup():
"""setup tests"""
@@ -51,16 +53,6 @@ def pytest_runtest_setup():
pytest_plugins = "pytest_homeassistant_custom_component" # pylint: disable=invalid-name
# Permet d'exclure certains test en mode d'ex
# sequential = pytest.mark.sequential
# This fixture allow to execute some tests first and not in //
# @pytest.fixture
# def order():
# return 1
#
# This fixture enables loading custom integrations in all tests.
# Remove to enable selective use of this fixture
@pytest.fixture(autouse=True)
@@ -167,3 +159,24 @@ async def init_central_config_with_boiler_fixture(
await create_central_config(hass, FULL_CENTRAL_CONFIG_WITH_BOILER)
yield
@pytest.fixture(name="init_central_power_manager")
async def init_central_power_manager_fixture(
hass, init_central_config
): # pylint: disable=unused-argument
"""Initialize the central power_manager"""
vtherm_api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass)
# 1. creation / init
vtherm_api.central_power_manager.post_init(
{
CONF_POWER_SENSOR: "sensor.the_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.the_max_power_sensor",
CONF_USE_POWER_FEATURE: True,
CONF_PRESET_POWER: 13,
}
)
assert vtherm_api.central_power_manager.is_configured
yield

View File

@@ -89,7 +89,12 @@ MOCK_TH_OVER_SWITCH_AC_TYPE_CONFIG = {
}
MOCK_TH_OVER_4SWITCH_TYPE_CONFIG = {
CONF_UNDERLYING_LIST: ["switch.mock_4switch0", "switch.mock_4switch1","switch.mock_4switch2","switch.mock_4switch3"],
CONF_UNDERLYING_LIST: [
"switch.mock_4switch0",
"switch.mock_4switch1",
"switch.mock_4switch2",
"switch.mock_4switch3",
],
CONF_HEATER_KEEP_ALIVE: 0,
CONF_PROP_FUNCTION: PROPORTIONAL_FUNCTION_TPI,
CONF_AC_MODE: False,
@@ -135,25 +140,6 @@ MOCK_TH_OVER_CLIMATE_TYPE_AC_CONFIG = {
CONF_AUTO_REGULATION_PERIOD_MIN: 1,
}
# TODO remove this later
MOCK_PRESETS_CONFIG = {
PRESET_FROST_PROTECTION + PRESET_TEMP_SUFFIX: 7,
PRESET_ECO + PRESET_TEMP_SUFFIX: 16,
PRESET_COMFORT + PRESET_TEMP_SUFFIX: 17,
PRESET_BOOST + PRESET_TEMP_SUFFIX: 18,
}
# TODO remove this later
MOCK_PRESETS_AC_CONFIG = {
PRESET_FROST_PROTECTION + PRESET_TEMP_SUFFIX: 7,
PRESET_ECO + PRESET_TEMP_SUFFIX: 17,
PRESET_COMFORT + PRESET_TEMP_SUFFIX: 19,
PRESET_BOOST + PRESET_TEMP_SUFFIX: 20,
PRESET_ECO + PRESET_AC_SUFFIX + PRESET_TEMP_SUFFIX: 25,
PRESET_COMFORT + PRESET_AC_SUFFIX + PRESET_TEMP_SUFFIX: 23,
PRESET_BOOST + PRESET_AC_SUFFIX + PRESET_TEMP_SUFFIX: 21,
}
MOCK_WINDOW_CONFIG = {
CONF_WINDOW_SENSOR: "binary_sensor.window_sensor",
# Not used normally only for tests to avoid rewrite all tests
@@ -179,12 +165,16 @@ MOCK_MOTION_CONFIG = {
CONF_NO_MOTION_PRESET: PRESET_ECO,
}
MOCK_POWER_CONFIG = {
MOCK_CENTRAL_POWER_CONFIG = {
CONF_POWER_SENSOR: "sensor.power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.power_max_sensor",
CONF_PRESET_POWER: 10,
}
MOCK_POWER_CONFIG = {
CONF_PRESET_POWER: 10,
}
MOCK_PRESENCE_CONFIG = {
CONF_PRESENCE_SENSOR: "person.presence_sensor",
}
@@ -195,9 +185,10 @@ MOCK_PRESENCE_AC_CONFIG = {
MOCK_ADVANCED_CONFIG = {
CONF_MINIMAL_ACTIVATION_DELAY: 10,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.4,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.3,
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.4,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.3,
}
MOCK_DEFAULT_FEATURE_CONFIG = {

View File

@@ -53,8 +53,9 @@ async def test_over_climate_auto_fan_mode_turbo(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
},
)
@@ -119,8 +120,9 @@ async def test_over_climate_auto_fan_mode_not_turbo(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
},
)
@@ -189,8 +191,9 @@ async def test_over_climate_auto_fan_mode_turbo_activation(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
},

View File

@@ -1,4 +1,4 @@
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, unused-variable
# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long, unused-variable, too-many-lines
""" Test the Auto Start Stop algorithm management """
from datetime import datetime, timedelta
@@ -335,8 +335,9 @@ async def test_auto_start_stop_none_vtherm(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_NONE,
@@ -363,15 +364,14 @@ async def test_auto_start_stop_none_vtherm(
# Initialize all temps
await set_all_climate_preset_temp(hass, vtherm, temps, "overclimate")
# Check correct initialization of auto_start_stop attributes
assert (
vtherm._attr_extra_state_attributes["auto_start_stop_level"]
== AUTO_START_STOP_LEVEL_NONE
)
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] is None
assert vtherm._attr_extra_state_attributes.get("auto_start_stop_level") is None
assert vtherm._attr_extra_state_attributes.get("auto_start_stop_dtmin") is None
# 1. Vtherm auto-start/stop should be in NONE mode
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_NONE
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_NONE
)
# 2. We should not find any switch Enable entity
assert (
@@ -427,8 +427,9 @@ async def test_auto_start_stop_medium_heat_vtherm(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_MEDIUM,
@@ -464,7 +465,10 @@ async def test_auto_start_stop_medium_heat_vtherm(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 15
# 1. Vtherm auto-start/stop should be in MEDIUM mode and an enable entity should exists
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_MEDIUM
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_MEDIUM
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -488,7 +492,7 @@ async def test_auto_start_stop_medium_heat_vtherm(
# 3. Set current temperature to 19 5 min later
now = now + timedelta(minutes=5)
# reset accumulated error (only for testing)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -500,7 +504,7 @@ async def test_auto_start_stop_medium_heat_vtherm(
assert vtherm.hvac_mode == HVACMode.HEAT
assert mock_send_event.call_count == 0
assert (
vtherm._auto_start_stop_algo.accumulated_error == 0
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 0
) # target = current = 19
# 4. Set current temperature to 20 5 min later
@@ -516,7 +520,10 @@ async def test_auto_start_stop_medium_heat_vtherm(
assert vtherm.hvac_mode == HVACMode.HEAT
assert mock_send_event.call_count == 0
# accumulated_error = target - current = -1 x 5 min / 2
assert vtherm._auto_start_stop_algo.accumulated_error == -2.5
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error
== -2.5
)
# 5. Set current temperature to 21 5 min later -> should turn off
now = now + timedelta(minutes=5)
@@ -532,7 +539,9 @@ async def test_auto_start_stop_medium_heat_vtherm(
assert vtherm.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
# accumulated_error = -2.5 + target - current = -2 x 5 min / 2 capped to 5
assert vtherm._auto_start_stop_algo.accumulated_error == -5
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -5
)
# a message should have been sent
assert mock_send_event.call_count >= 1
@@ -577,7 +586,9 @@ async def test_auto_start_stop_medium_heat_vtherm(
await hass.async_block_till_done()
# accumulated_error = .... capped to -5
assert vtherm._auto_start_stop_algo.accumulated_error == -5
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -5
)
# VTherm should stay stopped cause slope is too low to allow the turn to On
assert vtherm.hvac_mode == HVACMode.OFF
@@ -593,7 +604,9 @@ async def test_auto_start_stop_medium_heat_vtherm(
await hass.async_block_till_done()
# accumulated_error = -5/2 + target - current = 1 x 20 min / 2 capped to 5
assert vtherm._auto_start_stop_algo.accumulated_error == 5
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 5
)
# VTherm should have been stopped
assert vtherm.hvac_mode == HVACMode.HEAT
@@ -680,8 +693,9 @@ async def test_auto_start_stop_fast_ac_vtherm(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_UNDERLYING_LIST: ["climate.mock_climate"],
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
@@ -717,7 +731,10 @@ async def test_auto_start_stop_fast_ac_vtherm(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in MEDIUM mode
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
@@ -736,7 +753,7 @@ async def test_auto_start_stop_fast_ac_vtherm(
# 3. Set current temperature to 19 5 min later
now = now + timedelta(minutes=5)
# reset accumulated error for test
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -748,7 +765,8 @@ async def test_auto_start_stop_fast_ac_vtherm(
assert vtherm.hvac_mode == HVACMode.COOL
assert mock_send_event.call_count == 0
assert (
vtherm._auto_start_stop_algo.accumulated_error == 0 # target = current = 25
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error
== 0 # target = current = 25
)
# 4. Set current temperature to 23 5 min later -> should turn off
@@ -764,7 +782,9 @@ async def test_auto_start_stop_fast_ac_vtherm(
assert vtherm.hvac_mode == HVACMode.OFF
# accumulated_error = target - current = 2 x 5 min / 2 capped to 2
assert vtherm._auto_start_stop_algo.accumulated_error == 2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 2
)
# a message should have been sent
assert mock_send_event.call_count >= 1
@@ -809,7 +829,9 @@ async def test_auto_start_stop_fast_ac_vtherm(
await hass.async_block_till_done()
# accumulated_error = 2/2 + target - current = -1 x 20 min / 2 capped to 2
assert vtherm._auto_start_stop_algo.accumulated_error == -2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -2
)
# VTherm should stay stopped
assert vtherm.hvac_mode == HVACMode.OFF
@@ -826,7 +848,9 @@ async def test_auto_start_stop_fast_ac_vtherm(
await hass.async_block_till_done()
# accumulated_error = 2/2 + target - current = -1 x 20 min / 2 capped to 2
assert vtherm._auto_start_stop_algo.accumulated_error == -2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -2
)
# VTherm should have been stopped
assert vtherm.hvac_mode == HVACMode.COOL
@@ -911,8 +935,9 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
@@ -948,7 +973,10 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in MEDIUM mode
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
@@ -966,7 +994,7 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
# 3. Set current temperature to 21 5 min later to auto-stop
now = now + timedelta(minutes=5)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -977,7 +1005,9 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
# VTherm should have been stopped
assert vtherm.hvac_mode == HVACMode.OFF
assert vtherm._auto_start_stop_algo.accumulated_error == -2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == -2
)
# a message should have been sent
assert mock_send_event.call_count >= 1
@@ -1032,7 +1062,9 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change(
await hass.async_block_till_done()
assert vtherm.target_temperature == 21
assert vtherm._auto_start_stop_algo.accumulated_error == 2
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 2
)
# VTherm should have been restarted
assert vtherm.hvac_mode == HVACMode.HEAT
@@ -1117,8 +1149,9 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change_enable_false(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
@@ -1154,7 +1187,10 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change_enable_false(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in FAST mode and enable should be on
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -1185,7 +1221,7 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change_enable_false(
# 3. Set current temperature to 21 5 min later to auto-stop
now = now + timedelta(minutes=5)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -1197,7 +1233,9 @@ async def test_auto_start_stop_medium_heat_vtherm_preset_change_enable_false(
assert vtherm.hvac_mode == HVACMode.HEAT
# Not calculated cause enable = false
assert vtherm._auto_start_stop_algo.accumulated_error == 0
assert (
vtherm.auto_start_stop_manager._auto_start_stop_algo.accumulated_error == 0
)
# a message should have been sent
assert mock_send_event.call_count == 0
@@ -1251,8 +1289,9 @@ async def test_auto_start_stop_fast_heat_window(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
@@ -1288,7 +1327,10 @@ async def test_auto_start_stop_fast_heat_window(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in MEDIUM mode and an enable entity should exists
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -1310,12 +1352,12 @@ async def test_auto_start_stop_fast_heat_window(
# VTherm should be heating
assert vtherm.hvac_mode == HVACMode.HEAT
# VTherm window_state should be off
assert vtherm.window_state == STATE_OFF
assert vtherm.window_state == STATE_UNKNOWN # cause condition is not evaluated
# 3. Set current temperature to 21 5 min later -> should turn off VTherm
now = now + timedelta(minutes=5)
# reset accumulated error (only for testing)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -1426,8 +1468,9 @@ async def test_auto_start_stop_fast_heat_window_mixed(
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
@@ -1463,7 +1506,10 @@ async def test_auto_start_stop_fast_heat_window_mixed(
assert vtherm._attr_extra_state_attributes["auto_start_stop_dtmin"] == 7
# 1. Vtherm auto-start/stop should be in MEDIUM mode and an enable entity should exists
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)
@@ -1485,7 +1531,7 @@ async def test_auto_start_stop_fast_heat_window_mixed(
# VTherm should be heating
assert vtherm.hvac_mode == HVACMode.HEAT
# VTherm window_state should be off
assert vtherm.window_state == STATE_OFF
assert vtherm.window_state == STATE_UNKNOWN # cause try_condition is not evaluated
# 3. Open the window and wait for the delay
now = now + timedelta(minutes=2)
@@ -1513,7 +1559,7 @@ async def test_auto_start_stop_fast_heat_window_mixed(
# 4. Set current temperature to 21 5 min later -> should turn off VTherm
now = now + timedelta(minutes=5)
# reset accumulated error (only for testing)
vtherm._auto_start_stop_algo._accumulated_error = 0
vtherm.auto_start_stop_manager._auto_start_stop_algo._accumulated_error = 0
with patch(
"custom_components.versatile_thermostat.base_thermostat.BaseThermostat.send_event"
) as mock_send_event:
@@ -1605,8 +1651,9 @@ async def test_auto_start_stop_disable_vtherm_off(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: False,
CONF_AUTO_START_STOP_LEVEL: AUTO_START_STOP_LEVEL_FAST,
@@ -1637,6 +1684,9 @@ async def test_auto_start_stop_disable_vtherm_off(
await set_all_climate_preset_temp(hass, vtherm, temps, "overclimate")
# Check correct initialization of auto_start_stop attributes
assert (
vtherm._attr_extra_state_attributes["is_auto_start_stop_configured"] is True
)
assert (
vtherm._attr_extra_state_attributes["auto_start_stop_level"]
== AUTO_START_STOP_LEVEL_FAST
@@ -1646,7 +1696,10 @@ async def test_auto_start_stop_disable_vtherm_off(
# 1. Vtherm auto-start/stop should be in FAST mode and enable should be on
vtherm._set_now(now)
assert vtherm.auto_start_stop_level == AUTO_START_STOP_LEVEL_FAST
assert (
vtherm.auto_start_stop_manager.auto_start_stop_level
== AUTO_START_STOP_LEVEL_FAST
)
enable_entity = search_entity(
hass, "switch.overclimate_enable_auto_start_stop", SWITCH_DOMAIN
)

View File

@@ -0,0 +1,120 @@
# pylint: disable=unused-argument, line-too-long, protected-access, too-many-lines
""" Test the Window management """
import logging
from unittest.mock import PropertyMock, MagicMock
from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from custom_components.versatile_thermostat.feature_auto_start_stop_manager import (
FeatureAutoStartStopManager,
)
from .commons import * # pylint: disable=wildcard-import, unused-wildcard-import
logging.getLogger().setLevel(logging.DEBUG)
async def test_auto_start_stop_feature_manager_create(
hass: HomeAssistant,
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
# 1. creation
auto_start_stop_manager = FeatureAutoStartStopManager(fake_vtherm, hass)
assert auto_start_stop_manager is not None
assert auto_start_stop_manager.is_configured is False
assert auto_start_stop_manager.is_auto_stopped is False
assert auto_start_stop_manager.auto_start_stop_enable is False
assert auto_start_stop_manager.name == "the name"
assert len(auto_start_stop_manager._active_listener) == 0
custom_attributes = {}
auto_start_stop_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["is_auto_start_stop_configured"] is False
# assert custom_attributes["auto_start_stop_enable"] is False
# assert custom_attributes["auto_start_stop_level"] == AUTO_START_STOP_LEVEL_NONE
# assert custom_attributes["auto_start_stop_dtmin"] is None
# assert custom_attributes["auto_start_stop_accumulated_error"] is None
# assert custom_attributes["auto_start_stop_accumulated_error_threshold"] is None
# assert custom_attributes["auto_start_stop_last_switch_date"] is None
@pytest.mark.parametrize(
"use_auto_start_stop_feature, level, is_configured",
[
# fmt: off
( True, AUTO_START_STOP_LEVEL_NONE, True),
( True, AUTO_START_STOP_LEVEL_SLOW, True),
( True, AUTO_START_STOP_LEVEL_MEDIUM, True),
( True, AUTO_START_STOP_LEVEL_FAST, True),
# Level is missing , will be set to None
( True, None, True),
( False, AUTO_START_STOP_LEVEL_NONE, False),
( False, AUTO_START_STOP_LEVEL_SLOW, False),
( False, AUTO_START_STOP_LEVEL_MEDIUM, False),
( False, AUTO_START_STOP_LEVEL_FAST, False),
# Level is missing , will be set to None
( False, None, False),
# fmt: on
],
)
async def test_auto_start_stop_feature_manager_post_init(
hass: HomeAssistant, use_auto_start_stop_feature, level, is_configured
):
"""Test the FeatureMotionManager class direclty"""
fake_vtherm = MagicMock(spec=BaseThermostat)
type(fake_vtherm).name = PropertyMock(return_value="the name")
# 1. creation
auto_start_stop_manager = FeatureAutoStartStopManager(fake_vtherm, hass)
assert auto_start_stop_manager is not None
# 2. post_init
auto_start_stop_manager.post_init(
{
CONF_USE_AUTO_START_STOP_FEATURE: use_auto_start_stop_feature,
CONF_AUTO_START_STOP_LEVEL: level,
}
)
assert auto_start_stop_manager.is_configured is is_configured
assert (
auto_start_stop_manager.auto_start_stop_level == level
if level and is_configured
else AUTO_START_STOP_LEVEL_NONE
)
assert auto_start_stop_manager.auto_start_stop_enable is False
assert auto_start_stop_manager._auto_start_stop_algo is not None
custom_attributes = {}
auto_start_stop_manager.add_custom_attributes(custom_attributes)
assert custom_attributes["is_auto_start_stop_configured"] is is_configured
if auto_start_stop_manager.is_configured:
assert custom_attributes["auto_start_stop_enable"] is False
assert (
custom_attributes["auto_start_stop_level"] == level
if level and is_configured
else AUTO_START_STOP_LEVEL_NONE
)
assert (
custom_attributes["auto_start_stop_dtmin"]
== auto_start_stop_manager._auto_start_stop_algo.dt_min
)
assert (
custom_attributes["auto_start_stop_accumulated_error"]
== auto_start_stop_manager._auto_start_stop_algo.accumulated_error
)
assert (
custom_attributes["auto_start_stop_accumulated_error_threshold"]
== auto_start_stop_manager._auto_start_stop_algo.accumulated_error_threshold
)
assert (
custom_attributes["auto_start_stop_last_switch_date"]
== auto_start_stop_manager._auto_start_stop_algo.last_switch_date
)

View File

@@ -1,7 +1,7 @@
# pylint: disable=wildcard-import, unused-wildcard-import, unused-argument, line-too-long
# pylint: disable=wildcard-import, unused-wildcard-import, unused-argument, line-too-long, protected-access
""" Test the normal start of a Thermostat """
from unittest.mock import patch
from unittest.mock import patch, PropertyMock
from datetime import timedelta, datetime
from homeassistant.core import HomeAssistant
@@ -57,8 +57,9 @@ async def test_security_binary_sensors(
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_MINIMAL_DEACTIVATION_DELAY: 30,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
},
)
@@ -84,17 +85,17 @@ async def test_security_binary_sensors(
# Set temperature in the past
event_timestamp = now - timedelta(minutes=6)
# set temperature to 15 so that on_percent will be > security_min_on_percent (0.2)
# set temperature to 15 so that on_percent will be > safety_min_on_percent (0.2)
await send_temperature_change_event(entity, 15, event_timestamp)
assert entity.security_state is True
assert entity.safety_state is STATE_ON
# Simulate the event reception
await security_binary_sensor.async_my_climate_changed()
assert security_binary_sensor.state == STATE_ON
# set temperature now
await send_temperature_change_event(entity, 15, now)
assert entity.security_state is False
assert entity.safety_state is not STATE_ON
# Simulate the event reception
await security_binary_sensor.async_my_climate_changed()
assert security_binary_sensor.state == STATE_OFF
@@ -107,9 +108,16 @@ async def test_overpowering_binary_sensors(
skip_hass_states_is_state,
skip_turn_on_off_heater,
skip_send_event,
init_central_power_manager,
):
"""Test the overpowering binary sensors in thermostat type"""
temps = {
"eco": 17,
"comfort": 18,
"boost": 19,
}
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
@@ -122,9 +130,6 @@ async def test_overpowering_binary_sensors(
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: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: True,
@@ -134,17 +139,16 @@ async def test_overpowering_binary_sensors(
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_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_DEVICE_POWER: 100,
CONF_PRESET_POWER: 12,
},
)
entity: BaseThermostat = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
hass, entry, "climate.theoverswitchmockname", temps
)
assert entity
@@ -153,35 +157,55 @@ async def test_overpowering_binary_sensors(
)
assert overpowering_binary_sensor
now: datetime = datetime.now(tz=get_tz(hass))
now: datetime = NowClass.get_now(hass)
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
# Overpowering should be not set because poer have not been received
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert await entity.check_overpowering() is False
assert entity.overpowering_state is None
assert entity.power_manager.is_overpowering_detected is False
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
await overpowering_binary_sensor.async_my_climate_changed()
assert overpowering_binary_sensor.state is STATE_OFF
assert overpowering_binary_sensor.device_class == BinarySensorDeviceClass.POWER
await send_power_change_event(entity, 100, now)
await send_max_power_change_event(entity, 150, now)
assert await entity.check_overpowering() is True
assert entity.overpowering_state is True
# Send power mesurement
side_effects = SideEffects(
{
"sensor.the_power_sensor": State("sensor.the_power_sensor", 150),
"sensor.the_max_power_sensor": State("sensor.the_max_power_sensor", 100),
},
State("unknown.entity_id", "unknown"),
)
# fmt:off
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()), \
patch("custom_components.versatile_thermostat.thermostat_switch.ThermostatOverSwitch.is_device_active", new_callable=PropertyMock, return_value=True):
# fmt: on
await send_power_change_event(entity, 150, now)
await send_max_power_change_event(entity, 100, now)
# Simulate the event reception
await overpowering_binary_sensor.async_my_climate_changed()
assert overpowering_binary_sensor.state == STATE_ON
assert entity.power_manager.is_overpowering_detected is True
assert entity.power_manager.overpowering_state is STATE_ON
# Simulate the event reception
await overpowering_binary_sensor.async_my_climate_changed()
assert overpowering_binary_sensor.state == STATE_ON
# set max power to a low value
await send_max_power_change_event(entity, 201, now)
assert await entity.check_overpowering() is False
assert entity.overpowering_state is False
# Simulate the event reception
await overpowering_binary_sensor.async_my_climate_changed()
assert overpowering_binary_sensor.state == STATE_OFF
side_effects.add_or_update_side_effect("sensor.the_max_power_sensor", State("sensor.the_max_power_sensor", 251))
# fmt:off
with patch("homeassistant.core.StateMachine.get", side_effect=side_effects.get_side_effects()):
# fmt: on
now = now + timedelta(seconds=30)
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
await send_max_power_change_event(entity, 251, now)
assert entity.power_manager.is_overpowering_detected is False
assert entity.power_manager.overpowering_state is STATE_OFF
# Simulate the event reception
await overpowering_binary_sensor.async_my_climate_changed()
assert overpowering_binary_sensor.state == STATE_OFF
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@@ -218,8 +242,9 @@ async def test_window_binary_sensors(
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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 0, # important to not been obliged to wait
},
@@ -241,7 +266,7 @@ async def test_window_binary_sensors(
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert entity.window_state is STATE_OFF
assert entity.window_state is STATE_UNKNOWN
await window_binary_sensor.async_my_climate_changed()
assert window_binary_sensor.state is STATE_OFF
@@ -306,10 +331,13 @@ async def test_motion_binary_sensors(
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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_MOTION_SENSOR: "binary_sensor.mock_motion_sensor",
CONF_MOTION_DELAY: 0, # important to not been obliged to wait
CONF_MOTION_PRESET: PRESET_BOOST,
CONF_NO_MOTION_PRESET: PRESET_ECO,
},
)
@@ -329,7 +357,7 @@ async def test_motion_binary_sensors(
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert entity.motion_state is None
assert entity.motion_state is STATE_UNKNOWN
await motion_binary_sensor.async_my_climate_changed()
assert motion_binary_sensor.state is STATE_OFF
@@ -397,8 +425,9 @@ async def test_presence_binary_sensors(
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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_PRESENCE_SENSOR: "binary_sensor.mock_presence_sensor",
},
)
@@ -419,7 +448,7 @@ async def test_presence_binary_sensors(
await entity.async_set_preset_mode(PRESET_COMFORT)
await entity.async_set_hvac_mode(HVACMode.HEAT)
await send_temperature_change_event(entity, 15, now)
assert entity.presence_state is None
assert entity.presence_state is STATE_UNKNOWN
await presence_binary_sensor.async_my_climate_changed()
assert presence_binary_sensor.state is STATE_OFF
@@ -480,8 +509,9 @@ async def test_binary_sensors_over_climate_minimal(
CONF_USE_PRESENCE_FEATURE: False,
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
},
)
@@ -515,3 +545,4 @@ async def test_binary_sensors_over_climate_minimal(
hass, "binary_sensor.theoverclimatemockname_presence_state", "binary_sensor"
)
assert presence_binary_sensor is None

View File

@@ -37,7 +37,7 @@ async def test_bug_63(
skip_turn_on_off_heater,
skip_send_event,
):
"""Test that it should be possible to set the security_default_on_percent to 0"""
"""Test that it should be possible to set the safety_default_on_percent to 0"""
entry = MockConfigEntry(
domain=DOMAIN,
@@ -63,9 +63,10 @@ async def test_bug_63(
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.0, # !! here
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.0, # !! here
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.0, # !! here
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.0, # !! here
CONF_DEVICE_POWER: 200,
},
)
@@ -75,8 +76,8 @@ async def test_bug_63(
)
assert entity
assert entity._security_min_on_percent == 0
assert entity._security_default_on_percent == 0
assert entity.safety_manager.safety_min_on_percent == 0
assert entity.safety_manager.safety_default_on_percent == 0
# Waiting for answer in https://github.com/jmcollin78/versatile_thermostat/issues/64
@@ -89,7 +90,7 @@ async def test_bug_64(
skip_turn_on_off_heater,
skip_send_event,
):
"""Test that it should be possible to set the security_default_on_percent to 0"""
"""Test that it should be possible to set the safety_default_on_percent to 0"""
entry = MockConfigEntry(
domain=DOMAIN,
@@ -115,9 +116,10 @@ async def test_bug_64(
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.5,
CONF_SECURITY_DEFAULT_ON_PERCENT: 0.1, # !! here
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1, # !! here
CONF_DEVICE_POWER: 200,
},
)
@@ -266,7 +268,9 @@ async def test_bug_272(
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_bug_407(hass: HomeAssistant, skip_hass_states_is_state):
async def test_bug_407(
hass: HomeAssistant, skip_hass_states_is_state, init_central_power_manager
):
"""Test the followin case in power management:
1. a heater is active (heating). So the power consumption takes the heater power into account. We suppose the power consumption is near the threshold,
2. the user switch preset let's say from Comfort to Boost,
@@ -275,6 +279,12 @@ async def test_bug_407(hass: HomeAssistant, skip_hass_states_is_state):
"""
temps = {
"eco": 17,
"comfort": 18,
"boost": 19,
}
entry = MockConfigEntry(
domain=DOMAIN,
title="TheOverSwitchMockName",
@@ -287,9 +297,6 @@ async def test_bug_407(hass: HomeAssistant, skip_hass_states_is_state):
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: False,
CONF_USE_MOTION_FEATURE: False,
CONF_USE_POWER_FEATURE: True,
@@ -299,57 +306,68 @@ async def test_bug_407(hass: HomeAssistant, skip_hass_states_is_state):
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_POWER_SENSOR: "sensor.mock_power_sensor",
CONF_MAX_POWER_SENSOR: "sensor.mock_power_max_sensor",
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_DEVICE_POWER: 100,
CONF_PRESET_POWER: 12,
},
)
entity: ThermostatOverSwitch = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
hass, entry, "climate.theoverswitchmockname", temps
)
assert entity
tpi_algo = entity._prop_algorithm
assert tpi_algo
tz = get_tz(hass) # pylint: disable=invalid-name
now: datetime = datetime.now(tz=tz)
now: datetime = NowClass.get_now(hass)
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
await send_temperature_change_event(entity, 16, now)
await send_ext_temperature_change_event(entity, 10, now)
# 1. An already active heater will not switch to overpowering
side_effects = SideEffects(
{
"sensor.the_power_sensor": State("sensor.the_power_sensor", 100),
"sensor.the_max_power_sensor": State("sensor.the_max_power_sensor", 110),
},
State("unknown.entity_id", "unknown"),
)
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
new_callable=PropertyMock,
return_value=True,
), patch(
"homeassistant.core.StateMachine.get",
side_effect=side_effects.get_side_effects(),
):
await entity.async_set_hvac_mode(HVACMode.HEAT)
await entity.async_set_preset_mode(PRESET_COMFORT)
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_COMFORT
assert entity.overpowering_state is None
assert entity.power_manager.overpowering_state is STATE_UNKNOWN
assert entity.target_temperature == 18
# waits that the heater starts
await asyncio.sleep(0.1)
await hass.async_block_till_done()
assert mock_service_call.call_count >= 1
assert entity.is_device_active is True
# Send power max mesurement
await send_max_power_change_event(entity, 110, datetime.now())
await send_max_power_change_event(entity, 110, now)
# Send power mesurement (theheater is already in the power measurement)
await send_power_change_event(entity, 100, datetime.now())
await send_power_change_event(entity, 100, now)
# No overpowering yet
assert await entity.check_overpowering() is False
assert entity.power_manager.is_overpowering_detected is False
# All configuration is complete and power is < power_max
assert entity.preset_mode is PRESET_COMFORT
assert entity.overpowering_state is False
assert entity.power_manager.overpowering_state is STATE_OFF
assert entity.is_device_active is True
# 2. An already active heater that switch preset will not switch to overpowering
@@ -359,36 +377,58 @@ async def test_bug_407(hass: HomeAssistant, skip_hass_states_is_state):
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
new_callable=PropertyMock,
return_value=True,
), patch(
"homeassistant.core.StateMachine.get",
side_effect=side_effects.get_side_effects(),
):
now = now + timedelta(seconds=30)
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
# change preset to Boost
await entity.async_set_preset_mode(PRESET_BOOST)
# waits that the heater starts
await asyncio.sleep(0.1)
# doesn't work for call_later
# await hass.async_block_till_done()
assert await entity.check_overpowering() is False
# simulate a refresh for central power (not necessary)
await do_central_power_refresh(hass)
assert entity.power_manager.is_overpowering_detected is False
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_BOOST
assert entity.overpowering_state is False
assert entity.power_manager.overpowering_state is STATE_OFF
assert entity.target_temperature == 19
assert mock_service_call.call_count >= 1
# 3. if heater is stopped (is_device_active==False), then overpowering should be started
# 3. Evenif heater is stopped (is_device_active==False) and power is over max, then overpowering should be started
# due to check before start heating
side_effects.add_or_update_side_effect("sensor.the_power_sensor", State("sensor.the_power_sensor", 150))
with patch(
"homeassistant.core.ServiceRegistry.async_call"
) as mock_service_call, patch(
"custom_components.versatile_thermostat.underlyings.UnderlyingSwitch.is_device_active",
new_callable=PropertyMock,
return_value=False,
), patch(
"homeassistant.core.StateMachine.get",
side_effect=side_effects.get_side_effects(),
):
# change preset to Boost
now = now + timedelta(seconds=30)
VersatileThermostatAPI.get_vtherm_api()._set_now(now)
# change preset to Comfort
await entity.async_set_preset_mode(PRESET_COMFORT)
# waits that the heater starts
# waits the eventual heater starts
await asyncio.sleep(0.1)
assert await entity.check_overpowering() is True
# simulate a refresh for central power (not necessary because it is checked before start)
# await do_central_power_refresh(hass)
assert entity.power_manager.is_overpowering_detected is True
assert entity.hvac_mode is HVACMode.HEAT
assert entity.preset_mode is PRESET_POWER
assert entity.overpowering_state is True
assert entity.power_manager.overpowering_state is STATE_ON
@pytest.mark.parametrize("expected_lingering_tasks", [True])
@@ -445,8 +485,6 @@ async def test_bug_500_3(hass: HomeAssistant, init_vtherm_api) -> None:
CONF_USE_WINDOW_CENTRAL_CONFIG: False,
CONF_WINDOW_SENSOR: "sensor.theWindowSensor",
CONF_USE_POWER_CENTRAL_CONFIG: False,
CONF_POWER_SENSOR: "sensor.thePowerSensor",
CONF_MAX_POWER_SENSOR: "sensor.theMaxPowerSensor",
CONF_USE_PRESENCE_CENTRAL_CONFIG: False,
CONF_PRESENCE_SENSOR: "sensor.thePresenceSensor",
CONF_USE_MOTION_FEATURE: True, # motion sensor need to be checked AND a motion sensor set
@@ -456,7 +494,7 @@ async def test_bug_500_3(hass: HomeAssistant, init_vtherm_api) -> None:
flow = VersatileThermostatBaseConfigFlow(config)
assert flow._infos[CONF_USE_WINDOW_FEATURE] is True
assert flow._infos[CONF_USE_POWER_FEATURE] is True
assert flow._infos[CONF_USE_POWER_FEATURE] is False
assert flow._infos[CONF_USE_PRESENCE_FEATURE] is True
assert flow._infos[CONF_USE_MOTION_FEATURE] is True
@@ -510,8 +548,9 @@ async def test_bug_465(hass: HomeAssistant, skip_hass_states_is_state):
CONF_PRESENCE_SENSOR: "binary_sensor.presence_sensor",
CONF_CLIMATE: "climate.mock_climate",
CONF_MINIMAL_ACTIVATION_DELAY: 30,
CONF_SECURITY_DELAY_MIN: 5,
CONF_SECURITY_MIN_ON_PERCENT: 0.3,
CONF_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_TURBO,
CONF_AC_MODE: True,
},

View File

@@ -111,9 +111,10 @@ async def test_update_central_boiler_state_simple(
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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_TPI_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
@@ -298,9 +299,10 @@ async def test_update_central_boiler_state_multiple(
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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_TPI_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
@@ -626,9 +628,10 @@ async def test_update_central_boiler_state_simple_valve(
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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_TPI_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
@@ -800,9 +803,10 @@ async def test_update_central_boiler_state_simple_climate(
CONF_USE_PRESENCE_FEATURE: False,
CONF_UNDERLYING_LIST: [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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
@@ -990,9 +994,10 @@ async def test_update_central_boiler_state_simple_climate_valve_regulation(
CONF_AUTO_FAN_MODE: CONF_AUTO_FAN_HIGH,
CONF_AUTO_REGULATION_USE_DEVICE_TEMP: False,
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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,
@@ -1255,9 +1260,10 @@ async def test_bug_339(
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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_USE_MAIN_CENTRAL_CONFIG: True,
CONF_USE_PRESETS_CENTRAL_CONFIG: True,
CONF_USE_ADVANCED_CENTRAL_CONFIG: True,

View File

@@ -67,9 +67,10 @@ async def test_add_a_central_config(hass: HomeAssistant, skip_hass_states_is_sta
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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 61,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.2,
CONF_USE_CENTRAL_BOILER_FEATURE: False,
},
)
@@ -135,9 +136,10 @@ async def test_minimal_over_switch_wo_central_config(
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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_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
@@ -167,16 +169,16 @@ async def test_minimal_over_switch_wo_central_config(
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.window_manager.is_window_auto_configured 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.safety_manager.safety_delay_min == 5
assert entity.safety_manager.safety_min_on_percent == 0.3
assert entity.safety_manager.safety_default_on_percent == 0.1
assert entity.is_inversed
entity.remove_thermostat()
@@ -188,6 +190,18 @@ 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"""
temps = {
"frost": 10,
"eco": 17,
"comfort": 18,
"boost": 21,
"frost_away": 13,
"eco_away": 13,
"comfort_away": 13,
"boost_away": 13,
}
# Add a Switch VTherm
entry = MockConfigEntry(
domain=DOMAIN,
@@ -202,27 +216,20 @@ async def test_full_over_switch_wo_central_config(
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_UNDERLYING_LIST: ["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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 30,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 3,
@@ -233,8 +240,6 @@ async def test_full_over_switch_wo_central_config(
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,
@@ -249,7 +254,7 @@ async def test_full_over_switch_wo_central_config(
with patch("homeassistant.core.ServiceRegistry.async_call"):
entity: ThermostatOverSwitch = await create_thermostat(
hass, entry, "climate.theoverswitchmockname"
hass, entry, "climate.theoverswitchmockname", temps
)
assert entity
assert entity.name == "TheOverSwitchMockName"
@@ -274,28 +279,45 @@ async def test_full_over_switch_wo_central_config(
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.safety_manager.safety_delay_min == 5
assert entity.safety_manager.safety_min_on_percent == 0.3
assert entity.safety_manager.safety_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.window_manager.is_window_auto_configured is False
) # we have an entity_id
assert (
entity.window_manager._window_sensor_entity_id
== "binary_sensor.mock_window_sensor"
)
assert entity.window_manager.window_delay_sec == 30
assert entity.window_manager.window_auto_close_threshold == 0.1
assert entity.window_manager.window_auto_open_threshold == 3
assert entity.window_manager.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.motion_manager.motion_sensor_entity_id
== "binary_sensor.mock_motion_sensor"
)
assert entity.motion_manager.motion_delay_sec == 10
assert entity.motion_manager.motion_off_delay_sec == 29
assert entity.motion_manager.motion_preset == "comfort"
assert entity.motion_manager.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 (
VersatileThermostatAPI.get_vtherm_api().central_power_manager.power_sensor_entity_id
is None
)
assert (
VersatileThermostatAPI.get_vtherm_api().central_power_manager.max_power_sensor_entity_id
is None
)
assert entity._presence_sensor_entity_id == "binary_sensor.mock_presence_sensor"
assert (
entity._presence_manager.presence_sensor_entity_id
== "binary_sensor.mock_presence_sensor"
)
entity.remove_thermostat()
@@ -303,7 +325,7 @@ async def test_full_over_switch_wo_central_config(
@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
hass: HomeAssistant, skip_hass_states_is_state, init_central_power_manager
):
"""Tests that a VTherm with central_configuration is working with the central_config attributes"""
# Add a Switch VTherm
@@ -320,23 +342,20 @@ async def test_full_over_switch_with_central_config(
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_UNDERLYING_LIST: ["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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 5,
CONF_SAFETY_MIN_ON_PERCENT: 0.3,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.1,
CONF_WINDOW_SENSOR: "binary_sensor.mock_window_sensor",
CONF_WINDOW_DELAY: 30,
CONF_WINDOW_AUTO_OPEN_THRESHOLD: 3,
@@ -347,8 +366,6 @@ async def test_full_over_switch_with_central_config(
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,
@@ -387,29 +404,44 @@ async def test_full_over_switch_with_central_config(
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.safety_manager.safety_delay_min == 61
assert entity.safety_manager.safety_min_on_percent == 0.5
assert entity.safety_manager.safety_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.window_manager.is_window_auto_configured is False
assert (
entity.window_manager._window_sensor_entity_id
== "binary_sensor.mock_window_sensor"
)
assert entity.window_manager.window_delay_sec == 15
assert entity.window_manager.window_auto_close_threshold == 1
assert entity.window_manager.window_auto_open_threshold == 4
assert entity.window_manager.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.motion_manager.motion_sensor_entity_id
== "binary_sensor.mock_motion_sensor"
)
assert entity.motion_manager.motion_delay_sec == 31
assert entity.motion_manager.motion_off_delay_sec == 301
assert entity.motion_manager.motion_preset == "boost"
assert entity.motion_manager.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 (
VersatileThermostatAPI.get_vtherm_api().central_power_manager.power_sensor_entity_id
== "sensor.the_power_sensor"
)
assert (
VersatileThermostatAPI.get_vtherm_api().central_power_manager.max_power_sensor_entity_id
== "sensor.the_max_power_sensor"
)
assert entity._presence_sensor_entity_id == "binary_sensor.mock_presence_sensor"
assert (
entity._presence_manager.presence_sensor_entity_id
== "binary_sensor.mock_presence_sensor"
)
entity.remove_thermostat()
@@ -424,7 +456,7 @@ async def test_over_switch_with_central_config_but_no_central_config(
DOMAIN, context={"source": SOURCE_USER}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == SOURCE_USER
result = await hass.config_entries.flow.async_configure(
@@ -469,7 +501,7 @@ async def test_migration_of_central_config(
central_config_entry = MockConfigEntry(
version=CONFIG_VERSION,
# An old minor version
minor_version=1,
minor_version=0,
domain=DOMAIN,
title="TheCentralConfigMockName",
unique_id="centralConfigUniqueId",
@@ -483,9 +515,10 @@ async def test_migration_of_central_config(
CONF_TPI_COEF_INT: 0.5,
CONF_TPI_COEF_EXT: 0.02,
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_MINIMAL_DEACTIVATION_DELAY: 0,
CONF_SAFETY_DELAY_MIN: 61,
CONF_SAFETY_MIN_ON_PERCENT: 0.5,
CONF_SAFETY_DEFAULT_ON_PERCENT: 0.2,
# The old central_boiler parameter
"add_central_boiler_control": True,
CONF_CENTRAL_BOILER_ACTIVATION_SRV: "switch.pompe_chaudiere/switch.turn_on",

Some files were not shown because too many files have changed in this diff Show More