Compare commits

..

118 Commits

Author SHA1 Message Date
Jean-Marc Collin
f29097fbc2 Fix #661 - central boiler doesn't starts with Sonoff TRVZB 2024-11-25 19:21:07 +00:00
Jean-Marc Collin
c090692adc Documentation (not finished) 2024-11-24 19:12:39 +00:00
Jean-Marc Collin
81780bd316 With testu for config_flow ok 2024-11-24 16:23:14 +00:00
Jean-Marc Collin
ce4ea866cb All tests ok. Add a multi test for climate with valve regulation 2024-11-24 12:09:11 +00:00
Jean-Marc Collin
36cab0c91f Test multi ok 2024-11-24 09:32:19 +00:00
Jean-Marc Collin
6947056d55 First unit test ok 2024-11-23 23:08:31 +00:00
Jean-Marc Collin
7005cd7b26 Step 2: manual tests ok 2024-11-23 10:58:05 +00:00
Jean-Marc Collin
9abea3d198 Step 2 - renaming. All tests ok 2024-11-23 10:08:57 +00:00
Jean-Marc Collin
ffb976cfa1 Indus step1 2024-11-23 07:45:36 +00:00
Jean-Marc Collin
7b0c41e8ab Update custom_components/versatile_thermostat/translations/en.json
Co-authored-by: Alexander Dransfield <2844540+alexdrans@users.noreply.github.com>
2024-11-23 07:45:36 +00:00
Jean-Marc Collin
606e5ad440 Fix Valve testus. Improve sending the open percent to valve 2024-11-23 07:45:36 +00:00
Jean-Marc Collin
fd0c80585d Issue #655 - combine motion and presence 2024-11-23 07:45:35 +00:00
Jean-Marc Collin
3ea63a6819 Fix underlying target is not updated 2024-11-23 07:45:35 +00:00
Jean-Marc Collin
386fd780bc Fix hvac_action
Fix offset_calibration=room_temp - (local_temp - current_offset)
2024-11-23 07:45:35 +00:00
Jean-Marc Collin
fdcdf91f95 Calculate offset_calibration as room_temp - local_temp
Fix hvac_action calculation
2024-11-23 07:45:34 +00:00
Jean-Marc Collin
2fa6a0dd52 Add #602 - implement a max_on_percent setting 2024-11-23 07:45:34 +00:00
Jean-Marc Collin
8bae40101d Fix release name 2024-11-23 07:45:34 +00:00
Jean-Marc Collin
ddb27bb333 Release 2024-11-23 07:45:34 +00:00
Jean-Marc Collin
3f5c4f5cbe Fix Testus 2024-11-23 07:45:34 +00:00
Jean-Marc Collin
cb71821196 Work in simuated environment 2024-11-23 07:45:34 +00:00
Jean-Marc Collin
e4d42da140 With 1rst implementation of VTherm TRVZB and underlying 2024-11-23 07:45:33 +00:00
Jean-Marc Collin
14f7eb2bbe Next (not finished) 2024-11-23 07:45:33 +00:00
Jean-Marc Collin
5fa679c1f2 Fix configuration 2024-11-23 07:45:33 +00:00
Jean-Marc Collin
2d88243e79 With Sonoff configuration ok 2024-11-23 07:45:32 +00:00
Jean-Marc Collin
0a658b7a2a Add on_percent into Plotly graph 2024-11-20 10:38:32 +01:00
ms5
289ccc7bb7 Implementing max_on_percent setting (#632)
* implementing max_on_percent setting

* remove % sign from log message

* README updated: created new export-mode section, moved self-regulation expert settings to new section, added new section about on-time clamping
2024-11-17 18:28:24 +01:00
Jean-Marc Collin
c1d1e8f1db Fix safety mode doc 2024-11-16 09:33:48 +00:00
Gernot Messow
71c35ecdc0 Fixed and extended unit test (#637) 2024-11-14 22:29:04 +01:00
Gernot Messow
4f8e45dda6 Just ignore illegal target temp, do not throw away all data (#635)
Co-authored-by: Gernot Messow <gmessow@insys-locks.de>
2024-11-14 21:54:15 +01:00
Jean-Marc Collin
d624c327b6 Issue #552 (#627)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-11-13 19:14:22 +01:00
Jean-Marc Collin
b46a24f834 Issue #628 add follow underlying temp change entity (#630)
* First commit (no test)

* With tests ok

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-11-13 19:14:03 +01:00
Jean-Marc Collin
d31376d55d Add Overkiz incompatilibity 2024-11-10 15:29:35 +00:00
Jean-Marc Collin
dbfd294ff3 Issue #496 - precision on safety parameters for over_climate 2024-11-10 10:03:33 +00:00
Jean-Marc Collin
e111bd0647 Removes most of the collapsible section in README. 2024-11-10 09:48:45 +00:00
Jean-Marc Collin
ba69319198 Issue #619 - manual hvac_off should be prioritized over window and auto-start/stop hvac_off (#622)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-11-10 10:17:53 +01:00
Jean-Marc Collin
f9df925181 Issue #615 - VTherm switch to manual on its own (#618)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-11-09 18:44:13 +01:00
Jean-Marc Collin
2d72efe447 Issue 600 energy can be negative after configuration (#614)
* Add logs to diagnose the case

* Issue #552 (#608)

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>

* Fix typo (#607)

* - Force writing state when entity is removed
- Fix bug with issue #552 on CONF_USE_CENTRAL_BOILER_FEATURE which should be proposed on a central configuration
- Improve reload of entity to avoid reloading all VTherm. Only the reconfigured one will be reloaded

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
Co-authored-by: Ludovic BOUÉ <lboue@users.noreply.github.com>
2024-11-07 21:57:08 +01:00
Ludovic BOUÉ
95af6eba97 Fix typo (#607) 2024-11-05 22:47:42 +01:00
Jean-Marc Collin
06dc537767 Issue #552 (#608)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-11-05 22:39:26 +01:00
Joeri Colman
2d79d961dc Update en.json (#604)
fixed typo
2024-11-05 10:40:49 +01:00
Jean-Marc Collin
027bf8386b Add message into issue template. 2024-11-05 06:37:27 +00:00
Jean-Marc Collin
a0e548ef71 Release 2024-11-03 22:16:15 +00:00
Jean-Marc Collin
132519b471 Merge #590 and fix some tests 2024-11-03 22:12:56 +00:00
hilburn
e6c330fc9d Underlying config changes (#590)
* Changes config_flow to allow dynamic length list of underlying entities
Updates previously defined 4x entries to new config style
Changes to thermostat_X to load underlying entities from list
Changes to thermostat X to display underlying entities as a list - COULD BREAK EXISTING TEMPLATES

* Modifies tests to use the new list format

* Added English translation for UI

* Removed all references to individual entities in strings/en.json

* Fix merge mistake

---------

Co-authored-by: Jean-Marc Collin <jm.collin.78@gmail.com>
2024-11-03 22:52:19 +01:00
Jean-Marc Collin
968e8286ea Add some infos 2024-11-03 21:50:42 +00:00
hilburn
0f60c070ab Preset display tweaks (#599)
* Addded Frost Preset to translations
Added Icons for Shedding, Safety, Manual and Frost Presets

* Fixed French Translation
2024-11-03 11:50:37 +01:00
Jean-Marc Collin
810430f7b1 Update README.md
#597
2024-11-02 19:16:24 +01:00
Jean-Marc Collin
b4860c2b8d Issue 585 add auto start/stop feature (#594)
* Migrate to HA 2024.10.4

* Auto start/stop alog and testu + ConfigFlow

* With config flow ok

* Change algo

* All is fine

* Add change_preset test

* + comment

* FIX too much start/stop

* Change algo to take slop into account

* Allow calculation even if slope is None

* With enable + tests + hysteresis in calculation

* Add hvac_off_reason and test with window interaction

* Fix some tests

* Restore saved_state

* Release

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-11-01 18:54:22 +01:00
Gernot Messow
60bd522a97 Filter out-of-range target temperature sent from underlying climate devices (#581)
* Filter out-of-range temperature from underlying climate

* Fixed broken test case, added new test case for range filtering
2024-10-27 09:21:08 +01:00
Jean-Marc Collin
fc39cf5f40 Maia suggestion to README 2024-10-26 11:27:16 +02:00
Jean-Marc Collin
f6fb7487d5 Issue #467 - Always apply offset compensation (#567)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-10-16 19:33:55 +02:00
Jean-Marc Collin
0f585be0c9 issue #556 - enhance motion detection feature (2) 2024-10-16 05:08:57 +00:00
Jean-Marc Collin
492c95aff5 FIX issue #556 - enhance motion detection feature (#560)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-10-14 20:14:51 +02:00
Jean-Marc Collin
a530051bbd FIX #518 TypeError: unsupported operand type(s) for -: 'int' and 'NoneType' (#559)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-10-14 19:29:34 +02:00
Jean-Marc Collin
4ef82af8ce Merge branch 'issue_554-simulate-hvac-action' 2024-10-14 17:01:01 +00:00
Jean-Marc Collin
2ea5cf471b Cleaning 2024-10-14 16:58:18 +00:00
Jean-Marc Collin
f6afaf2715 with local tests ok. (#555)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-10-14 09:02:50 +02:00
Jean-Marc Collin
f29b2f9b81 with local tests ok. 2024-10-14 07:01:27 +00:00
Jean-Marc Collin
de9b95903e Add testu 2024-10-14 04:56:12 +00:00
Jean-Marc Collin
d112273c58 Fix preset temp is sommetimes lost on over_climate 2024-10-14 04:43:19 +00:00
Jean-Marc Collin
73a9ca4e53 Issue #478 vtherm doesn't follow underlying (#548)
* Dispatch test_bugs into each own VTherm type tests

* Local tests ok

* With testus ok.

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-10-13 11:30:04 +02:00
Jean-Marc Collin
1334bdbd8f FIX #465 and make this coherent with Windows open/close (#545)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-10-12 17:32:05 +02:00
Jean-Marc Collin
646ef47f6f Fix issue #485 (#544)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-10-12 12:50:12 +02:00
Jean-Marc Collin
c344c43185 FIX #518 (#543)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-10-12 11:02:24 +02:00
Jean-Marc Collin
062f8a617d Fix #533 (#542)
Clean some pylint hints
Avoid 2 times open percentage send at startup

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-10-12 10:46:13 +02:00
Jean-Marc Collin
70f91f3cbe Issue #524 switch from cool to heat don't change the target temp (#529)
* Preparation tests ok

* Fixed

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-10-08 17:06:16 +02:00
Jean-Marc Collin
668053b352 Fix unit_test 2024-10-07 05:22:57 +00:00
Jean-Marc Collin
6ff9ff1ee5 Fix variables in error log 2024-10-07 04:52:33 +00:00
Jean-Marc Collin
3f95ed74f4 FIX TypeError: '>' not supported between instances of 'float' and 'NoneType' error message 2024-10-06 09:04:47 +00:00
Jean-Marc Collin
6e42904ddf Issue #518 - Fix ThermostatOverClimate object has no attribute __attr_preset_modes 2024-10-06 08:58:58 +00:00
Jean-Marc Collin
4c1fc396fb Issue #500 - check feature is use central config is checked (#513)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-09-29 11:34:39 +02:00
Jean-Marc Collin
d6ec7a86be issue #506 - Add some check to verify tpi algorithm parameters are correctly set. (#512)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-09-29 09:54:39 +02:00
Jean-Marc Collin
a3f8715fe5 HA 2024.9.3 and issue 508 (#510)
* HA 2024.9.3 and issue 508

* Fix strings trailing spaces

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-09-28 19:36:46 +02:00
domozer
a1a9a8bbab Update README-fr.md (#487)
Remplacement de "Awesome Thermostat" par "Versatile Thermostat" dans la section "Encore mieux avec le composant Scheduler !"

Idem pour le fichier README.md
2024-07-23 19:24:56 +02:00
Jean-Marc Collin
d5c5869276 Update settings 2024-07-17 06:50:01 +00:00
Jean-Marc Collin
c4b03f8c1e Update manifest.json 2024-07-07 16:49:22 +02:00
Paulo Ferreira de Castro
ac206a949f Fix Home Assistant deprecation warnings (EventType, helpers.service) (#484)
* Type hints: Replace deprecated helpers.typing.EventType with core.Event

* Replace deprecated use of hass.helpers.service.async_register_admin_service
2024-07-07 16:47:30 +02:00
Jean-Marc Collin
4bccb746b8 Release 6.2.8 2024-07-02 05:18:29 +00:00
Jean-Marc Collin
e999705286 Issue 474 - TPI in AC mode is wrong 2024-07-02 05:17:14 +00:00
Jean-Marc Collin
b4873bfd27 FIX issue_479 (#480)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-07-02 07:04:47 +02:00
Jean-Marc Collin
b00dc09c80 Update manifest.json 2024-06-30 09:05:14 +02:00
Jean-Marc Collin
da6d6cbce6 Release 6.2.6 2024-06-30 09:04:51 +02:00
Paulo Ferreira de Castro
864e904e21 Reduce keep-alive warning logs when the controlled entity becomes unavailable (#476) 2024-06-28 09:53:40 +02:00
cddu33
0ee4fe355d issue with ac and mouvement detection (#471)
* Update base_thermostat.py

issue with ac and mouvement detection

* issue ac with detection mouvement

modif class find_preset_temps for preset activity
2024-06-17 10:18:03 +02:00
Maxwell Gonsalves
53dce224cd Change VTherm temperature unit to HA's preferred unit. (#461)
* Change VTherm temperature unit to HA's preferred unit.

* fix pytest issue

* update current_temperature, explicitly convert temps (fixes pytest error)
2024-06-10 18:46:45 +02:00
Jean-Marc Collin
2fd60074c7 Beers ! 2024-05-28 08:47:14 +02:00
jkreiss-coexya
549423b313 Enhance temperature regulation when working with internal device temperature (#453)
* [feature/autoregulation-send-for-underlyingtemp] Do not forget regulation send when using underlying device temperature for offset

* [feature/autoregulation-send-for-underlyingtemp] Add unit test for dtemp = 0

* [feature/autoregulation-send-for-underlyingtemp] Test with dtemp lower than 0.5

* [feature/autoregulation-send-for-underlyingtemp] Comments
2024-05-13 08:27:52 +02:00
Matt Bush
6bd1b1137e [Draft] Use user's preferred temperature unit instead of hardcoding celsius (#460)
* Use user's preferred temperature unit instead of hardcoding celsius

* Fix warnings about using is instead of == in tests
2024-05-13 07:47:04 +02:00
Jean-Marc Collin
189418e69a Beers 2024-05-11 10:47:33 +02:00
Jean-Marc Collin
4ab932f44e Update README-fr.md
Beers !
2024-05-11 10:46:59 +02:00
misa1515
e1ff23fb30 Update sk.json (#454) 2024-04-29 07:15:26 +02:00
Jean-Marc Collin
7b657ffabf Issue 444 ha 2024.04.3 (#452)
* HA 2024.4.3 and release

* [#444] - fix initial temp values with standalone presets

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-04-23 07:58:22 +02:00
Jean-Marc Collin
acd22d1fc4 HA 2024.4.3 and release (#447)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-04-16 08:54:59 +02:00
Jean-Marc Collin
d6f33d5796 [#438] - manage total_energy none until restored or calculated (#446)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-04-16 07:14:38 +02:00
Jean-Marc Collin
c1ebb46ac6 [#358] - Block preset_mode change on central_mode status (#445)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-04-15 08:06:07 +02:00
Jean-Marc Collin
eee4a9c4e3 Beers ! 2024-04-08 05:33:56 +00:00
Jean-Marc Collin
2a3d3ff877 [#429] - VTherm doesn't respect target temperature (#435)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-04-01 12:34:18 +02:00
Jean-Marc Collin
a9c368d64c Release 6.2.0 2024-03-31 18:48:47 +00:00
Jean-Marc Collin
1595ff32a2 [#432] - Use valve number max value instead of 100 (#434)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-03-31 20:47:04 +02:00
Jean-Marc Collin
c49545d9e3 [#398] - Add last_seen sensor to update temperature last datetime (#433)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-03-31 20:25:34 +02:00
Jean-Marc Collin
f4598a407e Ajout incompatibilité Airwell 2024-03-31 16:57:32 +00:00
Jean-Marc Collin
d96fe4bec7 Add Nodon module virtuak switch example 2024-03-30 07:51:22 +00:00
Jean-Marc Collin
a9b87b3aee Release 6.1.0 2024-03-26 20:10:29 +00:00
Jean-Marc Collin
c512cb6f74 [#428] - Refacto start versatile_thermostat (#430)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-03-26 21:06:25 +01:00
Jean-Marc Collin
9269240fe3 Release 2024-03-25 07:43:09 +00:00
Jean-Marc Collin
91ba2387b2 Persistence of boiler srv attribute 2024-03-25 07:25:42 +00:00
Jean-Marc Collin
162efb4709 [#425] - Boiler management entities are generated independently of this option selection (#426)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-03-25 08:00:08 +01:00
Paulo Ferreira de Castro
6a97622226 Expose the keep_alive_sec attribute in HASS Developer Tools - States (#381)
* Typing: Make BaseThermostat generic on the UnderlyingEntity type

* Typing: Change the type of IntervalCaller._interval_sec from int to float

This makes the IntervalCaller class more reusable.

* Keep-alive: Expose UnderlyingSwitch.keep_alive_sec as a HASS Dev Tools attribute

Also improve a keep-alive log message.
2024-03-23 11:49:09 +01:00
Jean-Marc Collin
5db7a49e75 [#339] - AUTO mode not counted for active boiler (#424)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-03-23 09:07:55 +01:00
Jean-Marc Collin
d7cdf79561 [#407] - shredding if heating (#423)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-03-23 08:30:17 +01:00
Jean-Marc Collin
07ac7beb7d [#419] - Turnon don't work anymore (#422)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-03-23 07:21:18 +01:00
Jean-Marc Collin
7ded723c8b [#420] - Message d'erreur dans home assistant (#421)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-03-23 07:17:55 +01:00
EPicLURcher
5369111f2d English translations (#417)
* Update strings.json

* Update en.json

* Update strings.json

* Update strings.json

* Update en.json
2024-03-20 23:11:50 +01:00
Jean-Marc Collin
4a7ae81c8f Release 2024-03-20 18:17:28 +00:00
Jean-Marc Collin
dbf2bc6982 [#412] - Fixe Unisue IDs error at startup (#416)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-03-20 19:16:38 +01:00
Jean-Marc Collin
2430e7dd8c [#388] - remove climate warning (#415)
Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-03-20 18:57:49 +01:00
Jean-Marc Collin
030069bb97 Fix README 2024-03-16 17:19:41 +00:00
Jean-Marc Collin
9fb9d89f17 Make the temperature preset as persistent entities / redesign of the configuration menu (#409)
* HA 2024.2.b4

* Add temp entities initialization

* Python12 env rebuild

* Init temperature number for central configuration + testus ok

* With calculation of VTherm temp entities + test ok

* FIX some testus. Some others are still KO

* Beers

* Update central config Number temp entity

* Many but not all testus ok

* All testus ok

* With central config temp change ok

* Cleaning and fixing Issues

* Validation tests ok

* With new menu. Testus KO

* All developped and tests ok

* Fix central_config menu

* Documentation and release

* Fix testus KO

* Add log into migration for testu

---------

Co-authored-by: Jean-Marc Collin <jean-marc.collin-extern@renault.com>
2024-03-16 18:10:13 +01:00
126 changed files with 12503 additions and 2759 deletions

View File

@@ -1,11 +1,30 @@
default_config: default_config:
recorder:
auto_purge: true
purge_keep_days: 1
commit_interval: 5
include:
domains:
- input_boolean
- input_number
- switch
- climate
- sensor
- binary_sensor
- number
- input_select
- versatile_thermostat
logger: logger:
default: info default: warning
logs: logs:
custom_components.versatile_thermostat: debug custom_components.versatile_thermostat: debug
custom_components.versatile_thermostat.underlyings: debug # custom_components.versatile_thermostat.underlyings: info
custom_components.versatile_thermostat.climate: debug # custom_components.versatile_thermostat.climate: info
# custom_components.versatile_thermostat.base_thermostat: debug
custom_components.versatile_thermostat.sensor: info
custom_components.versatile_thermostat.binary_sensor: info
# If you need to debug uncommment the line below (doc: https://www.home-assistant.io/integrations/debugpy/) # If you need to debug uncommment the line below (doc: https://www.home-assistant.io/integrations/debugpy/)
debugpy: debugpy:
@@ -72,6 +91,48 @@ input_number:
icon: mdi:thermostat icon: mdi:thermostat
unit_of_measurement: °C unit_of_measurement: °C
mode: box mode: box
fake_offset_calibration1:
name: Sonoff offset calibration 1
min: -12
max: 12
icon: mdi:tune
unit_of_measurement: °C
mode: box
fake_opening_degree1:
name: Sonoff Opening degree 1
min: 0
max: 100
icon: mdi:valve-open
unit_of_measurement: "%"
mode: box
fake_closing_degree1:
name: Sonoff Closing degree 1
min: 0
max: 100
icon: mdi:valve-closed
unit_of_measurement: "%"
mode: box
fake_offset_calibration2:
name: Sonoff offset calibration 2
min: -12
max: 12
icon: mdi:tune
unit_of_measurement: °C
mode: box
fake_opening_degree2:
name: Sonoff Opening degree 2
min: 0
max: 100
icon: mdi:valve-open
unit_of_measurement: "%"
mode: box
fake_closing_degree2:
name: Sonoff Closing degree 2
min: 0
max: 100
icon: mdi:valve-closed
unit_of_measurement: "%"
mode: box
input_boolean: input_boolean:
# input_boolean to simulate the windows entity. Only for development environment. # input_boolean to simulate the windows entity. Only for development environment.
@@ -123,6 +184,12 @@ input_boolean:
fake_presence_sensor1: fake_presence_sensor1:
name: Presence Sensor 1 name: Presence Sensor 1
icon: mdi:home icon: mdi:home
fake_valve_sonoff_trvzb1:
name: Valve Sonoff TRVZB1
icon: mdi:valve
fake_valve_sonoff_trvzb2:
name: Valve Sonoff TRVZB2
icon: mdi:valve
climate: climate:
- platform: generic_thermostat - platform: generic_thermostat
@@ -133,6 +200,7 @@ climate:
name: Underlying thermostat2 name: Underlying thermostat2
heater: input_boolean.fake_heater_switch3 heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1 target_sensor: input_number.fake_temperature_sensor1
ac_mode: false
- platform: generic_thermostat - platform: generic_thermostat
name: Underlying thermostat3 name: Underlying thermostat3
heater: input_boolean.fake_heater_switch3 heater: input_boolean.fake_heater_switch3
@@ -165,17 +233,23 @@ climate:
name: Underlying thermostat9 name: Underlying thermostat9
heater: input_boolean.fake_heater_switch3 heater: input_boolean.fake_heater_switch3
target_sensor: input_number.fake_temperature_sensor1 target_sensor: input_number.fake_temperature_sensor1
- platform: generic_thermostat
name: Underlying Sonoff TRVZB1
heater: input_boolean.fake_valve_sonoff_trvzb1
target_sensor: input_number.fake_temperature_sensor1
ac_mode: false
- platform: generic_thermostat
name: Underlying Sonoff TRVZB2
heater: input_boolean.fake_valve_sonoff_trvzb2
target_sensor: input_number.fake_temperature_sensor1
ac_mode: false
recorder: input_datetime:
commit_interval: 1 fake_last_seen:
include: name: Last seen temp sensor
domains: icon: mdi:update
- input_boolean has_date: true
- input_number has_time: true
- switch
- climate
- sensor
- binary_sensor
template: template:
- binary_sensor: - binary_sensor:
@@ -222,14 +296,14 @@ switch:
friendly_name: "Pilote chauffage SDB RDC" friendly_name: "Pilote chauffage SDB RDC"
value_template: "{{ is_state_attr('switch_seche_serviettes_sdb_rdc', 'sensor_state', 'on') }}" value_template: "{{ is_state_attr('switch_seche_serviettes_sdb_rdc', 'sensor_state', 'on') }}"
turn_on: turn_on:
service: select.select_option action: select.select_option
data: data:
option: comfort option: comfort
target: target:
entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode entity_id: select.seche_serviettes_sdb_rdc_cable_outlet_mode
turn_off: turn_off:
service: select.select_option action: select.select_option
data: data:
option: comfort-2 option: comfort-2
target: target:

View File

@@ -4,6 +4,8 @@ about: Create a report to help us improve
--- ---
> Please read carefuly this instructions and fill this form before writing an issue. It helps me to help you.
<!-- This template will allow the maintainer to be efficient and post the more accurante response as possible. There is many types / modes / configuration possible, so the analysis can be very tricky. If don't follow this template, your issue could be rejected without any message. Please help me to help you. --> <!-- This template will allow the maintainer to be efficient and post the more accurante response as possible. There is many types / modes / configuration possible, so the analysis can be very tricky. If don't follow this template, your issue could be rejected without any message. Please help me to help you. -->
<!-- Before you open a new issue, search through the existing issues to see if others have had the same problem. <!-- Before you open a new issue, search through the existing issues to see if others have had the same problem.

View File

@@ -17,7 +17,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v2 uses: actions/setup-python@v2
with: with:
python-version: 3.11 python-version: 3.12
- name: Install dependencies - name: Install dependencies
run: | run: |

View File

@@ -4,7 +4,6 @@
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.formatOnSaveMode": "modifications" "editor.formatOnSaveMode": "modifications"
}, },
"pylint.lintOnChange": false,
"files.associations": { "files.associations": {
"*.yaml": "home-assistant" "*.yaml": "home-assistant"
}, },

View File

@@ -6,20 +6,14 @@
![Tip](images/icon.png) ![Tip](images/icon.png)
> ![Tip](images/tips.png) Cette intégration de thermostat vise à simplifier considérablement vos automatisations autour de la gestion du chauffage. Parce que tous les événements autour du chauffage classiques sont gérés nativement par le thermostat (personne à la maison ?, activité détectée dans une pièce ?, fenêtre ouverte ?, délestage de courant ?), vous n'avez pas à vous encombrer de scripts et d'automatismes compliqués pour gérer vos climats. ;-). > ![Tip](images/tips.png) Cette intégration de thermostat vise à simplifier considérablement vos automatisations autour de la gestion du chauffage. Parce que tous les événements autour du chauffage classiques sont gérés nativement par le thermostat (personne à la maison ?, activité détectée dans une pièce ?, fenêtre ouverte ?, délestage de courant ?), vous n'avez pas à vous encombrer de scripts et d'automatismes compliqués pour gérer vos thermostats. ;-).
- [Changements majeurs dans la version 5.0](#changements-majeurs-dans-la-version-50) - [Merci pour les bières buymecoffee](#merci-pour-les-bières-buymecoffee)
- [Merci pour la bière buymecoffee](#merci-pour-la-bière-buymecoffee) - [Glossaire](#glossaire)
- [Quand l'utiliser et ne pas l'utiliser](#quand-lutiliser-et-ne-pas-lutiliser) - [Documentation](#documentation)
- [Incompatibilités](#incompatibilités) - [Comment installer Versatile Thermostat ?](#comment-installer-versatile-thermostat-)
- [Pourquoi une nouvelle implémentation du thermostat ?](#pourquoi-une-nouvelle-implémentation-du-thermostat-)
- [Comment installer cet incroyable Thermostat Versatile ?](#comment-installer-cet-incroyable-thermostat-versatile-)
- [HACS installation (recommendé)](#hacs-installation-recommendé) - [HACS installation (recommendé)](#hacs-installation-recommendé)
- [Installation manuelle](#installation-manuelle) - [Installation manuelle](#installation-manuelle)
- [Configuration](#configuration)
- [Création d'un nouveau Versatile Thermostat](#création-dun-nouveau-versatile-thermostat)
- [Choix des attributs de base](#choix-des-attributs-de-base)
- [Sélectionnez des entités pilotées](#sélectionnez-des-entités-pilotées)
- [Pour un thermostat de type ```thermostat_over_switch```](#pour-un-thermostat-de-type-thermostat_over_switch) - [Pour un thermostat de type ```thermostat_over_switch```](#pour-un-thermostat-de-type-thermostat_over_switch)
- [Pour un thermostat de type ```thermostat_over_climate```:](#pour-un-thermostat-de-type-thermostat_over_climate) - [Pour un thermostat de type ```thermostat_over_climate```:](#pour-un-thermostat-de-type-thermostat_over_climate)
- [L'auto-régulation](#lauto-régulation) - [L'auto-régulation](#lauto-régulation)
@@ -27,19 +21,20 @@
- [Compensation de la température interne](#compensation-de-la-température-interne) - [Compensation de la température interne](#compensation-de-la-température-interne)
- [Synthèse de l'algorithme d'auto-régulation](#synthèse-de-lalgorithme-dauto-régulation) - [Synthèse de l'algorithme d'auto-régulation](#synthèse-de-lalgorithme-dauto-régulation)
- [Le mode auto-fan](#le-mode-auto-fan) - [Le mode auto-fan](#le-mode-auto-fan)
- [Le démarrage / arrêt automatique](#le-démarrage--arrêt-automatique)
- [Pour un thermostat de type ```thermostat_over_valve```:](#pour-un-thermostat-de-type-thermostat_over_valve) - [Pour un thermostat de type ```thermostat_over_valve```:](#pour-un-thermostat-de-type-thermostat_over_valve)
- [Configurez les coefficients de l'algorithme TPI](#configurez-les-coefficients-de-lalgorithme-tpi) - [Configurez les coefficients de l'algorithme TPI](#configurez-les-coefficients-de-lalgorithme-tpi)
- [Configurer la température préréglée](#configurer-la-température-préréglée) - [Configurer les températures préréglées](#configurer-les-températures-préréglées)
- [Configurer les portes/fenêtres en allumant/éteignant les thermostats](#configurer-les-portesfenêtres-en-allumantéteignant-les-thermostats) - [Configurer les portes/fenêtres en allumant/éteignant les thermostats](#configurer-les-portesfenêtres-en-allumantéteignant-les-thermostats)
- [Le mode capteur](#le-mode-capteur) - [Le mode capteur](#le-mode-capteur)
- [Le mode auto](#le-mode-auto) - [Le mode auto](#le-mode-auto)
- [Configurer le mode d'activité ou la détection de mouvement](#configurer-le-mode-dactivité-ou-la-détection-de-mouvement) - [Configurer le mode d'activité ou la détection de mouvement](#configurer-le-mode-dactivité-ou-la-détection-de-mouvement)
- [Configurer la gestion de la puissance](#configurer-la-gestion-de-la-puissance) - [Configurer la gestion de la puissance](#configurer-la-gestion-de-la-puissance)
- [Configurer la présence ou l'occupation](#configurer-la-présence-ou-loccupation) - [Configurer la présence (ou l'absence)](#configurer-la-présence-ou-labsence)
- [Configuration avancée](#configuration-avancée) - [Configuration avancée](#configuration-avancée)
- [Le contrôle centralisé](#le-contrôle-centralisé) - [Le contrôle centralisé](#le-contrôle-centralisé)
- [Le contrôle d'une chaudière centrale](#le-contrôle-dune-chaudière-centrale) - [Le contrôle d'une chaudière centrale](#le-contrôle-dune-chaudière-centrale)
- [Configuration](#configuration-1) - [Configuration](#configuration)
- [Comment trouver le bon service ?](#comment-trouver-le-bon-service-) - [Comment trouver le bon service ?](#comment-trouver-le-bon-service-)
- [Les évènements](#les-évènements) - [Les évènements](#les-évènements)
- [Avertissement](#avertissement) - [Avertissement](#avertissement)
@@ -71,6 +66,7 @@
- [Dépannages](#dépannages) - [Dépannages](#dépannages)
- [Utilisation d'un Heatzy](#utilisation-dun-heatzy) - [Utilisation d'un Heatzy](#utilisation-dun-heatzy)
- [Utilisation d'un radiateur avec un fil pilote](#utilisation-dun-radiateur-avec-un-fil-pilote) - [Utilisation d'un radiateur avec un fil pilote](#utilisation-dun-radiateur-avec-un-fil-pilote)
- [Utilisation d'un radiateur avec un fil pilote](#utilisation-dun-radiateur-avec-un-fil-pilote-1)
- [Seul le premier radiateur chauffe](#seul-le-premier-radiateur-chauffe) - [Seul le premier radiateur chauffe](#seul-le-premier-radiateur-chauffe)
- [Le radiateur chauffe alors que la température de consigne est dépassée ou ne chauffe pas alors que la température de la pièce est bien en-dessous de la consigne](#le-radiateur-chauffe-alors-que-la-température-de-consigne-est-dépassée-ou-ne-chauffe-pas-alors-que-la-température-de-la-pièce-est-bien-en-dessous-de-la-consigne) - [Le radiateur chauffe alors que la température de consigne est dépassée ou ne chauffe pas alors que la température de la pièce est bien en-dessous de la consigne](#le-radiateur-chauffe-alors-que-la-température-de-consigne-est-dépassée-ou-ne-chauffe-pas-alors-que-la-température-de-la-pièce-est-bien-en-dessous-de-la-consigne)
- [Type `over_switch` ou `over_valve`](#type-over_switch-ou-over_valve) - [Type `over_switch` ou `over_valve`](#type-over_switch-ou-over_valve)
@@ -85,98 +81,22 @@
Ce composant personnalisé pour Home Assistant est une mise à niveau et est une réécriture complète du composant "Awesome thermostat" (voir [Github](https://github.com/dadge/awesome_thermostat)) avec l'ajout de fonctionnalités. Ce composant personnalisé pour Home Assistant est une mise à niveau et est une réécriture complète du composant "Awesome thermostat" (voir [Github](https://github.com/dadge/awesome_thermostat)) avec l'ajout de fonctionnalités.
# Merci pour les bières [buymecoffee](https://www.buymeacoffee.com/jmcollin78)
Un grand merci à @salabur, @pvince83, @bergoglio, @EPicLURcher, @ecolorado66, @Kriss1670, @maia, @f.maymil, @moutte69, @Jerome, @Gunnar M, @Greg.o, @John Burgess, @abyssmal, @capinfo26, @Helge, @MattG @Mexx62, @Someone, @Lajull, @giopeco, @fredericselier, @philpagan, @studiogriffanti, @Edwin, @Sebbou, @Gerard R., @John Burgess, @Sylvoliv, @cdenfert, @stephane.l, @jms92100, ... pour les bières. Ca fait très plaisir et ça m'encourage à continuer ! Si cette intégration vous a fait économiser, payez moi une p'tite bière !
> ![Nouveau](images/new-icon.png) _*Nouveautés*_ # Glossaire
> * **Release 5.4** :
> - Ajout du pas de température [#311](https://github.com/jmcollin78/versatile_thermostat/issues/311),
> - ajout de seuils de régulation pour les `over_valve` pour éviter de trop vider la batterie des TRV [#338](https://github.com/jmcollin78/versatile_thermostat/issues/338),
> - ajout d'une option permettant d'utiliser la température interne d'un TRV pour forcer l' auto-régulation [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348),
> - ajout d'une fonction de keep-alive pour les VTherm `over_switch` [#345](https://github.com/jmcollin78/versatile_thermostat/issues/345)
> * **Release 5.3** : Ajout d'une fonction de pilotage d'une chaudière centrale [#234](https://github.com/jmcollin78/versatile_thermostat/issues/234) - plus d'infos ici: [Le contrôle d'une chaudière centrale](#le-contrôle-dune-chaudière-centrale). Ajout de la possibilité de désactiver le mode sécurité pour le thermomètre extérieur [#343](https://github.com/jmcollin78/versatile_thermostat/issues/343)
> * **Release 5.2** : Ajout d'un `central_mode` permettant de piloter tous les VTherms de façon centralisée [#158](https://github.com/jmcollin78/versatile_thermostat/issues/158).
> * **Release 5.1** : Limitation des valeurs envoyées aux valves et au température envoyées au climate sous-jacent.
> * **Release 5.0** : Ajout d'une configuration centrale permettant de mettre en commun les attributs qui peuvent l'être [#239](https://github.com/jmcollin78/versatile_thermostat/issues/239).
<details>
<summary>Autres versions</summary>
> * **Release 4.3** : Ajout d'un mode auto-fan pour le type `over_climate` permettant d'activer la ventilation si l'écart de température est important [#223](https://github.com/jmcollin78/versatile_thermostat/issues/223). _VTherm_ : Versatile Thermostat dans la suite de ce document
> * **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 . _TRV_ : tête thermostatique équipée d'une vanne. La vanne s'ouvre ou se ferme permettant le passage de l'eau chaude
> * **Release 4.1** : Ajout d'un mode de régulation **Expert** dans lequel l'utilisateur peut spécifier ses propres paramètres d'auto-régulation au lieu d'utiliser les pre-programmés [#194](https://github.com/jmcollin78/versatile_thermostat/issues/194).
> * **Release 4.0** : Ajout de la prise en charge de la **Versatile Thermostat UI Card**. Voir [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card). Ajout d'un mode de régulation **Slow** pour les appareils de chauffage à latence lente [#168](https://github.com/jmcollin78/versatile_thermostat/issues/168). Changement de la façon dont **la puissance est calculée** dans le cas de VTherm avec des équipements multi-sous-jacents [#146](https://github.com/jmcollin78/versatile_thermostat/issues/146). Ajout de la prise en charge de AC et Heat pour VTherm via un interrupteur également [#144](https://github.com/jmcollin78/versatile_thermostat/pull/144)
> * **Release 3.8**: Ajout d'une **fonction d'auto-régulation** pour les thermostats `over climate` dont la régulation est faite par le climate sous-jacent. Cf. [L'auto-régulation](#lauto-régulation) et [#129](https://github.com/jmcollin78/versatile_thermostat/issues/129). Ajout de la **possibilité d'inverser la commande** pour un thermostat `over switch` pour adresser les installations avec fil pilote et diode [#124](https://github.com/jmcollin78/versatile_thermostat/issues/124).
> * **Release 3.7**: Ajout du type de **Versatile Thermostat `over valve`** pour piloter une vanne TRV directement ou tout autre équipement type gradateur pour le chauffage. La régulation se fait alors directement en agissant sur le pourcentage d'ouverture de l'entité sous-jacente : 0 la vanne est coupée, 100 : la vanne est ouverte à fond. Cf. [#131](https://github.com/jmcollin78/versatile_thermostat/issues/131). Ajout d'une fonction permettant le bypass de la détection d'ouverture [#138](https://github.com/jmcollin78/versatile_thermostat/issues/138). Ajout de la langue Slovaque
> * **Release 3.6**: Ajout du paramètre `motion_off_delay` pour améliorer la gestion de des mouvements [#116](https://github.com/jmcollin78/versatile_thermostat/issues/116), [#128](https://github.com/jmcollin78/versatile_thermostat/issues/128). Ajout du mode AC (air conditionné) pour un VTherm over switch. Préparation du projet Github pour faciliter les contributions [#127](https://github.com/jmcollin78/versatile_thermostat/issues/127)
> * **Release 3.5**: Plusieurs thermostats sont possibles en "thermostat over climate" mode [#113](https://github.com/jmcollin78/versatile_thermostat/issues/113)
> * **Release 3.4**: bug fix et exposition des preset temperatures pour le mode AC [#103](https://github.com/jmcollin78/versatile_thermostat/issues/103)
> * **Release 3.3**: ajout du mode Air Conditionné (AC). Cette fonction vous permet d'utiliser le mode AC de votre thermostat sous-jacent. Pour l'utiliser, vous devez cocher l'option "Uitliser le mode AC" et définir les valeurs de température pour les presets et pour les presets en cas d'absence
> * **Release 3.2** : ajout de la possibilité de commander plusieurs switch à partir du même thermostat. Dans ce mode, les switchs sont déclenchés avec un délai pour minimiser la puissance nécessaire à un instant (on minimise les périodes de recouvrement). Voir [Configuration](#sélectionnez-des-entités-pilotées)
> * **Release 3.1** : ajout d'une détection de fenêtres/portes ouvertes par chute de température. Cette nouvelle fonction permet de stopper automatiquement un radiateur lorsque la température chute brutalement. Voir [Le mode auto](#le-mode-auto)
> * **Release majeure 3.0** : ajout d'un équipement thermostat et de capteurs (binaires et non binaires) associés. Beaucoup plus proche de la philosphie Home Assistant, vous avez maintenant un accès direct à l'énergie consommée par le radiateur piloté par le thermostat et à plein d'autres capteurs qui seront utiles dans vos automatisations et dashboard.
> * **release 2.3** : ajout de la mesure de puissance et d'énergie du radiateur piloté par le thermostat.
> * **release 2.2** : ajout de fonction de sécurité permettant de ne pas laisser éternellement en chauffe un radiateur en cas de panne du thermomètre
> * **release majeure 2.0** : ajout du thermostat "over climate" permettant de transformer n'importe quel thermostat en Versatile Thermostat et lui ajouter toutes les fonctions de ce dernier.
</details>
# Changements majeurs dans la version 5.0 # Documentation
![Nouveau](images/new-icon.png)
Vous pouvez maintenant définir une configuration centrale qui va vous permettre de mettre en commun sur tous vos VTherms (ou seulement une partie), certains attributs. Pour utiliser cette possibilité, vous devez : La documentation est maintenant découpée en plusieurs pages pour faciliter la lecture et la recherche d'informations :
1. Créer un VTherm de type "Configuration Centrale", 1. [présentation](documentation/fr/presentation.md),
2. Saisir les attributs de cette configuration centrale 2. [choisir un type de VTherm](documentation/fr/creation.md),
3. [les attributs de base](documentation/fr/base-attributes.md)
Pour l'utiliser ensuite dans les autres VTherms, vous devez les reconfigurer et à chaque fois que c'est possible cocher la case "Utiliser la configuration centrale". Cette case à cocher apparait dans tous les groupes d'attributs qui peuvent avoir recours à la configuration centrale : attributs principaux, TPI, ouvertures, mouvement, puissance, présence et paramètres avancés. # Comment installer Versatile Thermostat ?
Les attributs configurable dans la configuration centrale est listée ici : [Synthèse des paramètres](#synthèse-des-paramètres).
Lors d'un changement sur la configuration centrale, tous les VTherms seront rechargés pour tenir compte de ces changements.
En conséquence toute la phase de paramètrage d'un VTherm a été profondemment modifiée pour pouvoir utiliser la configuration centrale ou surcharger les valeurs de la configuration centrale par des valeurs propre au VTherm en cours de configuration.
**Note :** les copies d'écran de la configuration d'un VTherm n'ont pas été mises à jour.
# Merci pour la bière [buymecoffee](https://www.buymeacoffee.com/jmcollin78)
Un grand merci à @salabur, @pvince83, @bergoglio, @EPicLURcher, @ecolorado66, @Kriss1670, @maia, @f.maymil, @moutte69, @Jerome, @Gunnar M, @Greg.o, @John Burgess, @abyssmal, @capinfo26, @Helge, @MattG @Mexx62, @Someone, @Lajull, @giopeco, @fredericselier, @philpagan, @studiogriffanti, @Edwin, @Sebbou pour les bières. Ca fait très plaisir et ça m'encourage à continuer !
# Quand l'utiliser et ne pas l'utiliser
Ce thermostat peut piloter 3 types d'équipements :
1. un radiateur qui ne fonctionne qu'en mode marche/arrêt (nommé ```thermostat_over_switch```). La configuration minimale nécessaire pour utiliser ce type thermostat est :
1. un équipement comme un radiateur (un ```switch``` ou équivalent),
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.
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.
Les installations avec fil pilote et diode d'activation bénéficie d'une option qui permet d'inverser la commande on/off du radiateur sous-jacent. Pour cela, utilisez le type `over switch` et cochez l'option d'inversion de la commande.
## 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,
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.
# Pourquoi une nouvelle implémentation du thermostat ?
Ce composant nommé __Versatile thermostat__ gère les cas d'utilisation suivants :
- 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 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é.
- La **gestion de la présence à domicile**. Cette fonctionnalité vous permet de modifier dynamiquement la température du préréglage en tenant compte d'un capteur de présence de votre maison.
- Des **services pour interagir avec le thermostat** à partir d'autres intégrations : vous pouvez forcer la présence / la non-présence à l'aide d'un service, et vous pouvez modifier dynamiquement la température des préréglages et changer les paramètres de sécurité.
- Ajouter des capteurs pour voir les états internes du thermostat,
- Contrôle centralisé de tous les Versatile Thermostat pour les stopper tous, les passer tous en hors-gel, les forcer en mode Chauffage (l'hiver), les forcer en mode Climatisation (l'été).
- Contrôle d'une chaudière centrale et des VTherm qui doivent contrôler cette chaudière.
# Comment installer cet incroyable Thermostat Versatile ?
## HACS installation (recommendé) ## HACS installation (recommendé)
@@ -197,58 +117,6 @@ Ce composant nommé __Versatile thermostat__ gère les cas d'utilisation suivant
6. Redémarrez l'assistant domestique 6. Redémarrez l'assistant domestique
7. Configurer la nouvelle intégration du Versatile Thermostat 7. Configurer la nouvelle intégration du Versatile Thermostat
# Configuration
-- VTherm = Versatile Thermostat dans la suite de ce document --
> ![Astuce](images/tips.png) _*Notes*_
>
> Trois façons de configurer les VTherms sont disponibles :
> 1. Chaque Versatile Thermostat est entièrement configurée de manière indépendante. Choisissez cette option si vous ne souhaitez avoir aucune configuration ou gestion centrale.
> 2. Certains aspects sont configurés de manière centralisée. Cela permet par ex. définir la température min/max, la détection de fenêtre ouverte,… au niveau d'une instance centrale et unique. Pour chaque VTherm que vous configurez, vous pouvez alors choisir d'utiliser la configuration centrale ou de la remplacer par des paramètres personnalisés.
> 3. En plus de cette configuration centralisée, tous les VTherm peuvent être contrôlées par une seule entité de type `select`. Cette fonction est nommé `central_mode`. Cela permet de stopper / démarrer / mettre en hors gel / etc tous les VTherms en une seule fois. Pour chaque VTherm, l'utilisateur indique si il est concerné par ce `central_mode`.
## Création d'un nouveau Versatile Thermostat
Cliquez sur le bouton Ajouter une intégration dans la page d'intégration
![image](images/add-an-integration.png)
La configuration peut être modifiée via la même interface. Sélectionnez simplement le thermostat à modifier, appuyez sur "Configurer" et vous pourrez modifier certains paramètres ou la configuration.
Suivez ensuite les étapes de configuration comme suit :
## Choix des attributs de base
![image](images/config-main0.png)
![image](images/config-main.png)
Donnez les principaux attributs obligatoires :
1. un nom (sera le nom de l'intégration et aussi le nom de l'entité climate)
2. le type de thermostat ```thermostat_over_switch``` pour piloter un radiateur commandé par un switch ou ```thermostat_over_climate``` pour piloter un autre thermostat, ou ```thermostat_over_valve``` Cf. [ci-dessus](#pourquoi-une-nouvelle-implémentation-du-thermostat)
4. un identifiant d'entité de capteur de température qui donne la température de la pièce dans laquelle le radiateur est installé,
5. une entité capteur de température donnant la température extérieure. Si vous n'avez pas de capteur externe, vous pouvez utiliser l'intégration météo locale
6. une durée de cycle en minutes. A chaque cycle, le radiateur s'allumera puis s'éteindra pendant une durée calculée afin d'atteindre la température ciblée (voir [preset](#configure-the-preset-temperature) ci-dessous). En mode ```over_climate```, le cycle ne sert qu'à faire des controles de base mais ne régule pas directement la température. C'est le ```climate``` sous-jacent qui le fait,
7. les températures minimales et maximales du thermostat,
8. une puissance de l'équipement ce qui va activer les capteurs de puissance et énergie consommée par l'appareil,
9. la possibilité de controler le thermostat de façon centralisée. Cf [controle centralisé](#le-contrôle-centralisé),
10. la liste des fonctionnalités qui seront utilisées pour ce thermostat. En fonction de vos choix, les écrans de configuration suivants s'afficheront ou pas.
> ![Astuce](images/tips.png) _*Notes*_
> 1. avec les types ```over_switch``` et ```over_valve```, les calculs sont effectués à chaque cycle. Donc en cas de changement de conditions, il faudra attendre le prochain cycle pour voir un changement. Pour cette raison, le cycle ne doit pas être trop long. **5 min est une bonne valeur**,
> 2. si le cycle est trop court, le radiateur ne pourra jamais atteindre la température cible. Pour le radiateur à accumulation par exemple il sera sollicité inutilement.
## Sélectionnez des entités pilotées
En fonction de votre choix sur le type de thermostat, vous devrez choisir une ou plusieurs entités de type `switch`, `climate` ou `number`. Seules les entités compatibles avec le type sont présentées.
> ![Astuce](images/tips.png) _*Comment choisir le type*_
> Le choix du type est important. Même si il toujours possible de le modifier ensuite via l'IHM de configuration, il est préférable de se poser les quelques questions suivantes :
> 1. **quel type d'équipement je vais piloter ?** Dans l'ordre voici ce qu'il faut faire :
> 1. si vous avez une vanne thermostatique (TRV) commandable dans Home Assistant via une entité de type ```number``` (par exemple une _Shelly TRV_), choisissez le type `over_valve`. C'est le type le plus direct et qui assure la meilleure régulation,
> 2. si vous avez un radiateur électrique (avec ou sans fil pilote) et qu'une entité de type ```switch``` permet de l'allumer ou de l'éteindre, alors le type ```over_switch``` est préférable. La régulation sera faite par le Versatile Thermostat en fonction de la température mesuré par votre thermomètre, à l'endroit ou vous l'avez placé,
> 3. dans tous les autres cas, utilisez le mode ```over_climate```. Vous gardez votre entité ```climate``` d'origine et le Versatile Thermostat "ne fait que" piloter le on/off et la température cible de votre thermostat d'origine. La régulation est faite par votre thermostat d'origine dans ce cas. Ce mode est particulièrement adapté aux climatisations réversible tout-en-un dont l'exposition dans Home Assistant se limite à une entité de type ```climate```
> 2. **quelle type de régulation je veux ?** Si l'équipement piloté possède son propre mécanisme de régulation (clim, certaine vanne TRV) et que cette régulation fonctionne bien, optez pour un ```over_climate```
### Pour un thermostat de type ```thermostat_over_switch``` ### Pour un thermostat de type ```thermostat_over_switch```
![image](images/config-linked-entity.png) ![image](images/config-linked-entity.png)
@@ -415,6 +283,17 @@ Il faut évidemment que votre équipement sous-jacent soit équipée d'une venti
Si votre équipement ne comprend pas le mode Turbo, le mode Forte` sera utilisé en remplacement. Si votre équipement ne comprend pas le mode Turbo, le mode Forte` sera utilisé en remplacement.
Une fois l'écart de température redevenu faible, la ventilation se mettra dans un mode "normal" qui dépend de votre équipement à savoir (dans l'ordre) : `Silence (mute)`, `Auto (auto)`, `Faible (low)`. La première valeur qui est possible pour votre équipement sera choisie. Une fois l'écart de température redevenu faible, la ventilation se mettra dans un mode "normal" qui dépend de votre équipement à savoir (dans l'ordre) : `Silence (mute)`, `Auto (auto)`, `Faible (low)`. La première valeur qui est possible pour votre équipement sera choisie.
#### Le démarrage / arrêt automatique
Cette fonction a été introduite en 6.5.0. Elle permet d'autoriser VTherm a stopper un équipement qui n'a pas besoin d'être allumé et de le redémarrer lorsque les conditions le réclame. Cette fonction est munie de 3 réglages qui permettent d'arrêter / relancer plus ou moins rapidement l'équipement.
Pour l'utiliser, vous devez :
1. Ajouter la fonction `Avec démmarrage et extinction automatique` dans le menu 'Fonctions',
2. Paramétrer le niveau de détection dans l'option 'Allumage/extinction automatique' qui s'affiche lorsque la fonction a été activée. Vous choisissez le niveau de détection entre 'Lent', 'Moyen' et 'Rapide'. Les arrêts/relances seront plus nombreux avec le niveau 'Rapide'.
Une fois paramétré, vous aurez maintenant une nouvelle entité de type `switch` qui vous permet d'autoriser ou non l'arrêt/relance automatique sans toucher à la configuration. Cette entité est disponible sur l'appareil VTherm et se nomme `switch.<name>_enable_auto_start_stop`. Cochez la pour autoriser le démarrage et extinction automatique.
L'algorithme de détection est décrit [ici](https://github.com/jmcollin78/versatile_thermostat/issues/585).
### Pour un thermostat de type ```thermostat_over_valve```: ### Pour un thermostat de type ```thermostat_over_valve```:
![image](images/config-linked-entity3.png) ![image](images/config-linked-entity3.png)
Vous pouvez choisir jusqu'à entité du domaine ```number``` ou ```ìnput_number``` qui vont commander les vannes. Vous pouvez choisir jusqu'à entité du domaine ```number``` ou ```ìnput_number``` qui vont commander les vannes.
@@ -424,7 +303,7 @@ Il est possible de choisir un thermostat over valve qui commande une climatisati
## Configurez les coefficients de l'algorithme TPI ## Configurez les coefficients de l'algorithme TPI
Si vous avez choisi un thermostat de type ```over_switch``` ou ```over_valve``` vous arriverez sur cette page : Si vous avez choisi un thermostat de type ```over_switch``` ou ```over_valve``` et que vous sélectionnez l'option "TPI" vous menu, vous arriverez sur cette page :
![image](images/config-tpi.png) ![image](images/config-tpi.png)
@@ -435,10 +314,7 @@ Vous devez donner :
Pour plus d'informations sur l'algorithme TPI et son réglage, veuillez vous référer à [algorithm](#algorithm). Pour plus d'informations sur l'algorithme TPI et son réglage, veuillez vous référer à [algorithm](#algorithm).
## Configurer la température préréglée ## Configurer les températures préréglées
Cliquez sur 'Valider' sur la page précédente et vous y arriverez :
![image](images/config-presets.png)
Le mode préréglé (preset) vous permet de préconfigurer la température ciblée. Utilisé en conjonction avec Scheduler (voir [scheduler](#even-better-with-scheduler-component) 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](#even-better-with-scheduler-component) 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 - **Eco** : l'appareil est en mode d'économie d'énergie
@@ -449,6 +325,8 @@ Le mode préréglé (preset) vous permet de préconfigurer la température cibl
**Aucun** est toujours ajouté dans la liste des modes, car c'est un moyen de ne pas utiliser les preset mais une **température manuelle** à la place. **Aucun** est toujours ajouté dans la liste des modes, car c'est un moyen de ne pas utiliser les preset mais une **température manuelle** à la place.
Les pré-réglages se font (depuis v6.0) directement depuis les entités du VTherm ou de la configuration centrale si vous utilisez la configuration centrale.
> ![Astuce](images/tips.png) _*Notes*_ > ![Astuce](images/tips.png) _*Notes*_
> 1. En modifiant manuellement la température cible, réglez le préréglage sur Aucun (pas de préréglage). De cette façon, vous pouvez toujours définir une température cible même si aucun préréglage n'est disponible. > 1. En modifiant manuellement la température cible, réglez le préréglage sur Aucun (pas de préréglage). De cette façon, vous pouvez toujours définir une température cible même si aucun préréglage n'est disponible.
> 2. Le préréglage standard ``Away`` est un préréglage caché qui n'est pas directement sélectionnable. Versatile Thermostat utilise la gestion de présence ou la gestion de mouvement pour régler automatiquement et dynamiquement la température cible en fonction d'une présence dans le logement ou d'une activité dans la pièce. Voir [gestion de la présence](#configure-the-presence-management). > 2. Le préréglage standard ``Away`` est un préréglage caché qui n'est pas directement sélectionnable. Versatile Thermostat utilise la gestion de présence ou la gestion de mouvement pour régler automatiquement et dynamiquement la température cible en fonction d'une présence dans le logement ou d'une activité dans la pièce. Voir [gestion de la présence](#configure-the-presence-management).
@@ -457,6 +335,7 @@ Le mode préréglé (preset) vous permet de préconfigurer la température cibl
> 5. Si vous ne souhaitez pas utiliser le préréglage, indiquez 0 comme température. Le préréglage sera alors ignoré et ne s'affichera pas dans le composant front > 5. Si vous ne souhaitez pas utiliser le préréglage, indiquez 0 comme température. Le préréglage sera alors ignoré et ne s'affichera pas dans le composant front
## Configurer les portes/fenêtres en allumant/éteignant les thermostats ## Configurer les portes/fenêtres en allumant/éteignant les thermostats
Vous devez avoir choisi la fonctionnalité ```Avec détection des ouvertures``` dans la première page pour arriver sur cette page. Vous devez avoir choisi la fonctionnalité ```Avec détection des ouvertures``` dans la première page pour arriver sur cette page.
La détecttion des ouvertures peut se faire de 2 manières: La détecttion des ouvertures peut se faire de 2 manières:
1. soit avec un capteur placé sur l'ouverture (mode capteur), 1. soit avec un capteur placé sur l'ouverture (mode capteur),
@@ -498,6 +377,7 @@ Et c'est tout ! votre thermostat s'éteindra lorsque les fenêtres seront ouvert
> 4. Il est déconseillé d'utiliser le mode automatique pour un équipement soumis à des variations de température fréquentes et normales (couloirs, zones ouvertes, ...) > 4. Il est déconseillé d'utiliser le mode automatique pour un équipement soumis à des variations de température fréquentes et normales (couloirs, zones ouvertes, ...)
## Configurer le mode d'activité ou la détection de mouvement ## Configurer le mode d'activité ou la détection de mouvement
Si vous avez choisi la fonctionnalité ```Avec détection de mouvement```, cliquez sur 'Valider' sur la page précédente et vous y arriverez : Si vous avez choisi la fonctionnalité ```Avec détection de mouvement```, cliquez sur 'Valider' sur la page précédente et vous y arriverez :
![image](images/config-motion.png) ![image](images/config-motion.png)
@@ -541,7 +421,8 @@ Cela vous permet de modifier la puissance maximale au fil du temps à l'aide d'u
> 4. Si vous ne souhaitez pas utiliser cette fonctionnalité, laissez simplement l'identifiant des entités vide > 4. Si vous ne souhaitez pas utiliser cette fonctionnalité, laissez simplement l'identifiant des entités vide
> 5. Si vous controlez plusieurs radiateurs, la **consommation électrique de votre chauffage** renseigné doit correspondre à la somme des puissances. > 5. Si vous controlez plusieurs radiateurs, la **consommation électrique de votre chauffage** renseigné doit correspondre à la somme des puissances.
## Configurer la présence ou l'occupation ## Configurer la présence (ou l'absence)
Si sélectionnée en première page, cette fonction vous permet de modifier dynamiquement la température de tous les préréglages du thermostat configurés lorsque personne n'est à la maison ou lorsque quelqu'un rentre à la maison. Pour cela, vous devez configurer la température qui sera utilisée pour chaque préréglage lorsque la présence est désactivée. Lorsque le capteur de présence s'éteint, ces températures seront utilisées. Lorsqu'il se rallume, la température "normale" configurée pour le préréglage est utilisée. Voir [gestion des préréglages](#configure-the-preset-temperature). Si sélectionnée en première page, cette fonction vous permet de modifier dynamiquement la température de tous les préréglages du thermostat configurés lorsque personne n'est à la maison ou lorsque quelqu'un rentre à la maison. Pour cela, vous devez configurer la température qui sera utilisée pour chaque préréglage lorsque la présence est désactivée. Lorsque le capteur de présence s'éteint, ces températures seront utilisées. Lorsqu'il se rallume, la température "normale" configurée pour le préréglage est utilisée. Voir [gestion des préréglages](#configure-the-preset-temperature).
Pour configurer la présence remplissez ce formulaire : Pour configurer la présence remplissez ce formulaire :
@@ -562,6 +443,7 @@ ATTENTION : les groupes de personnes ne fonctionnent pas en tant que capteur de
> 2. vous pouvez utiliser le capteur direct person.xxxx ou un groupe de capteurs de Home Assistant. Le capteur de présence gère les états ``on`` ou ``home`` comme présents et les états ``off`` ou ``not_home`` comme absents. > 2. vous pouvez utiliser le capteur direct person.xxxx ou un groupe de capteurs de Home Assistant. Le capteur de présence gère les états ``on`` ou ``home`` comme présents et les états ``off`` ou ``not_home`` comme absents.
## Configuration avancée ## Configuration avancée
Ces paramètres permettent d'affiner le réglage du thermostat. Ces paramètres permettent d'affiner le réglage du thermostat.
Le formulaire de configuration avancée est le suivant : Le formulaire de configuration avancée est le suivant :
@@ -576,6 +458,8 @@ Mettre ce paramètre à ``0.00`` déclenchera le préréglage sécurité quelque
Le quatrième param§tre (``security_default_on_percent``) est la valeur de ``on_percent`` qui sera utilisée lorsque le thermostat passe en mode ``security``. Si vous mettez ``0`` alors le thermostat sera coupé lorsqu'il passe en mode ``security``, mettre 0,2% par exemple permet de garder un peu de chauffage (20% dans ce cas), même en mode ``security``. Ca évite de retrouver son logement totalement gelé lors d'une panne de thermomètre. Le quatrième param§tre (``security_default_on_percent``) est la valeur de ``on_percent`` qui sera utilisée lorsque le thermostat passe en mode ``security``. Si vous mettez ``0`` alors le thermostat sera coupé lorsqu'il passe en mode ``security``, mettre 0,2% par exemple permet de garder un peu de chauffage (20% dans ce cas), même en mode ``security``. Ca évite de retrouver son logement totalement gelé lors d'une panne de thermomètre.
Note: les paramètres `security_min_on_percent` et `security_default_on_percent` ne s'applique pas aux VTherms `over_climate`.
Depuis la version 5.3 il est possible de désactiver la mise en sécurité suite à une absence de données du thermomètre extérieure. En effet, celui-ci ayant la plupart du temps un impact faible sur la régulation (dépendant de votre paramètrage), il est possible qu'il soit absent sans mettre en danger le logement. Pour cela, il faut ajouter les lignes suivantes dans votre `configuration.yaml` : Depuis la version 5.3 il est possible de désactiver la mise en sécurité suite à une absence de données du thermomètre extérieure. En effet, celui-ci ayant la plupart du temps un impact faible sur la régulation (dépendant de votre paramètrage), il est possible qu'il soit absent sans mettre en danger le logement. Pour cela, il faut ajouter les lignes suivantes dans votre `configuration.yaml` :
``` ```
versatile_thermostat: versatile_thermostat:
@@ -595,6 +479,7 @@ Voir [exemple de réglages](#examples-tuning) pour avoir des exemples de réglag
> 5. Les thermostats de type ``thermostat_over_climate`` ne sont pas concernés par le mode security. > 5. Les thermostats de type ``thermostat_over_climate`` ne sont pas concernés par le mode security.
## Le contrôle centralisé ## Le contrôle centralisé
Depuis la release 5.2, si vous avez défini une configuration centralisée, vous avez une nouvelle entité nommée `select.central_mode` qui permet de piloter tous les VTherms avec une seule action. Pour qu'un VTherm soit contrôlable de façon centralisée, il faut que son attribut de configuration nommé `use_central_mode` soit vrai. Depuis la release 5.2, si vous avez défini une configuration centralisée, vous avez une nouvelle entité nommée `select.central_mode` qui permet de piloter tous les VTherms avec une seule action. Pour qu'un VTherm soit contrôlable de façon centralisée, il faut que son attribut de configuration nommé `use_central_mode` soit vrai.
Cette entité se présente sous la forme d'une liste de choix qui contient les choix suivants : Cette entité se présente sous la forme d'une liste de choix qui contient les choix suivants :
@@ -610,6 +495,7 @@ Exemple de rendu :
![central_mode](images/central_mode.png) ![central_mode](images/central_mode.png)
## Le contrôle d'une chaudière centrale ## Le contrôle d'une chaudière centrale
Depuis la release 5.3, vous avez la possibilité de contrôler une chaudière centralisée. A partir du moment où il est possible de déclencher ou stopper cette chaudière depuis Home Assistant, alors Versatile Thermostat va pouvoir la commander directement. Depuis la release 5.3, vous avez la possibilité de contrôler une chaudière centralisée. A partir du moment où il est possible de déclencher ou stopper cette chaudière depuis Home Assistant, alors Versatile Thermostat va pouvoir la commander directement.
Le principe mis en place est globalement le suivant : Le principe mis en place est globalement le suivant :
@@ -709,6 +595,9 @@ context:
> ![Astuce](images/tips.png) _*Notes*_ > ![Astuce](images/tips.png) _*Notes*_
> Le contrôle par du logiciel ou du matériel de type domotique d'une chaudière centrale peut induire des risques pour son bon fonctionnement. Assurez-vous avant d'utiliser ces fonctions, que votre chaudière possède bien des fonctions de sécurité et que celles-ci fonctionnent. Allumer une chaudière si tous les robinets sont fermés peut générer de la sur-pression par exemple. > Le contrôle par du logiciel ou du matériel de type domotique d'une chaudière centrale peut induire des risques pour son bon fonctionnement. Assurez-vous avant d'utiliser ces fonctions, que votre chaudière possède bien des fonctions de sécurité et que celles-ci fonctionnent. Allumer une chaudière si tous les robinets sont fermés peut générer de la sur-pression par exemple.
<details>
<summary>Synthèse des paramètres</summary>
## Synthèse des paramètres ## Synthèse des paramètres
| Paramètre | Libellé | "over switch" | "over climate" | "over valve" | "configuration centrale" | | Paramètre | Libellé | "over switch" | "over climate" | "over valve" | "configuration centrale" |
@@ -743,13 +632,7 @@ context:
| ``ac_mode`` | utilisation de l'air conditionné (AC) ? | X | X | X | - | | ``ac_mode`` | utilisation de l'air conditionné (AC) ? | X | X | X | - |
| ``tpi_coef_int`` | Coefficient à utiliser pour le delta de température interne | X | - | X | X | | ``tpi_coef_int`` | Coefficient à utiliser pour le delta de température interne | X | - | X | X |
| ``tpi_coef_ext`` | Coefficient à utiliser pour le delta de température externe | X | - | X | X | | ``tpi_coef_ext`` | Coefficient à utiliser pour le delta de température externe | X | - | X | X |
| ``frost_tp`` | Température en preset Hors-gel | X | X | X | X | | ``frost_temp`` | Température en preset Hors-gel | X | X | X | X |
| ``eco_temp`` | Température en preset Eco | X | X | X | X |
| ``comfort_temp`` | Température en preset Confort | X | X | X | X |
| ``boost_temp`` | Température en preset Boost | X | X | X | X |
| ``eco_ac_temp`` | Température en preset Eco en mode AC | X | X | X | X |
| ``comfort_ac_temp`` | Température en preset Confort en mode AC | X | X | X | X |
| ``boost_ac_temp`` | Température en preset Boost en mode AC | X | X | X | X |
| ``window_sensor_entity_id`` | Détecteur d'ouverture (entity id) | X | X | X | - | | ``window_sensor_entity_id`` | Détecteur d'ouverture (entity id) | X | X | X | - |
| ``window_delay`` | Délai avant extinction (secondes) | X | X | X | X | | ``window_delay`` | Délai avant extinction (secondes) | X | X | X | X |
| ``window_auto_open_threshold`` | Seuil haut de chute de température pour la détection automatique (en °/min) | X | X | X | X | | ``window_auto_open_threshold`` | Seuil haut de chute de température pour la détection automatique (en °/min) | X | X | X | X |
@@ -764,13 +647,6 @@ context:
| ``max_power_sensor_entity_id`` | Capteur de puissance Max (entity id) | X | X | X | X | | ``max_power_sensor_entity_id`` | Capteur de puissance Max (entity id) | X | X | X | X |
| ``power_temp`` | Température si délestaqe | X | X | X | X | | ``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 | - | | ``presence_sensor_entity_id`` | Capteur de présence entity id (true si quelqu'un est présent) | X | X | X | - |
| ``frost_ay_temp`` | Température en preset Hors-gel en cas d'absence | X | X | X | X |
| ``eco_away_temp`` | Température en preset Eco en cas d'absence | X | X | X | X |
| ``comfort_away_temp`` | Température en preset Comfort en cas d'absence | X | X | X | X |
| ``boost_away_temp`` | Température en preset Boost en cas d'absence | X | X | X | X |
| ``eco_ac_away_temp`` | Température en preset Eco en cas d'absence en mode AC | X | X | X | X |
| ``comfort_ac_away_temp`` | Température en preset Comfort en cas d'absence en mode AC | X | X | X | X |
| ``boost_ac_away_temp`` | Température en preset Boost en cas d'absence en mode AC | X | X | X | X |
| ``minimal_activation_delay`` | Délai minimal d'activation | 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_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 | | ``security_min_on_percent`` | Pourcentage minimal de puissance pour passer en mode sécurité | X | - | X | X |
@@ -780,10 +656,13 @@ context:
| ``inverse_switch_command`` | Inverse la commande du switch (pour switch avec fil pilote) | X | - | - | - | | ``inverse_switch_command`` | Inverse la commande du switch (pour switch avec fil pilote) | X | - | - | - |
| ``auto_fan_mode`` | Mode de ventilation automatique | - | X | - | - | | ``auto_fan_mode`` | Mode de ventilation automatique | - | X | - | - |
| ``auto_regulation_use_device_temp`` | Utilisation de la température interne du sous-jacent | - | X | - | - | | ``auto_regulation_use_device_temp`` | Utilisation de la température interne du sous-jacent | - | X | - | - |
| ``add_central_boiler_control`` | Ajout du controle d'une chaudière centrale | - | - | - | X | | ``use_central_boiler_feature`` | Ajout du controle d'une chaudière centrale | - | - | - | X |
| ``central_boiler_activation_service`` | Service d'activation de la chaudière | - | - | - | X | | ``central_boiler_activation_service`` | Service d'activation de la chaudière | - | - | - | X |
| ``central_boiler_deactivation_service`` | Service de desactivation de la chaudière | - | - | - | X | | ``central_boiler_deactivation_service`` | Service de desactivation de la chaudière | - | - | - | X |
| ``used_by_controls_central_boiler`` | Indique si le VTherm contrôle la chaudière centrale | X | X | X | - | | ``used_by_controls_central_boiler`` | Indique si le VTherm contrôle la chaudière centrale | X | X | X | - |
| ``use_auto_start_stop_feature`` | Indique si la fonction de démarrage/extinction automatique est activée | - | X | - | - |
| ``auto_start_stop_lvel`` | Le niveau de détection de l'auto start/stop | - | X | - | - |
</details>
# Exemples de réglage # Exemples de réglage
@@ -1040,6 +919,9 @@ Les attributs personnalisés sont les suivants :
| ``is_controlled_by_central_mode`` | True si le VTherm peut être controlé de façon centrale | | ``is_controlled_by_central_mode`` | True si le VTherm peut être controlé de façon centrale |
| ``last_central_mode`` | Le dernier mode central utilisé (None si le VTherm n'est pas controlé en central) | | ``last_central_mode`` | Le dernier mode central utilisé (None si le VTherm n'est pas controlé en central) |
| ``is_used_by_central_boiler`` | Indique si le VTherm peut contrôler la chaudière centrale | | ``is_used_by_central_boiler`` | Indique si le VTherm peut contrôler la chaudière centrale |
| ``auto_start_stop_enable`` | Indique si le VTherm est autorisé à s'auto démarrer/arrêter |
| ``auto_start_stop_level`` | Indique le niveau d'auto start/stop |
| ``hvac_off_reason`` | Indique la raison de l'arrêt (hvac_off) du VTherm. Ce peut être Window, Auto-start/stop ou Manuel |
# Quelques résultats # Quelques résultats
@@ -1074,9 +956,9 @@ Une carte spéciale pour le Versatile Thermostat a été développée (sur la ba
## Encore mieux avec le composant Scheduler ! ## Encore mieux avec le composant Scheduler !
Afin de profiter de toute la puissance du Versatile Thermostat, je vous invite à l'utiliser avec https://github.com/nielsfaber/scheduler-component Afin de profiter de toute la puissance du Versatile Thermostat, je vous invite à l'utiliser avec https://github.com/nielsfaber/scheduler-component
En effet, le composant scheduler propose une gestion de la base climatique sur les modes prédéfinis. Cette fonctionnalité a un intérêt limité avec le thermostat générique mais elle devient très puissante avec le thermostat Awesome : En effet, le composant scheduler propose une gestion de la base climatique sur les modes prédéfinis. Cette fonctionnalité a un intérêt limité avec le thermostat générique mais elle devient très puissante avec le Versatile Thermostat :
À partir d'ici, je suppose que vous avez installé Awesome Thermostat et Scheduler Component. À partir d'ici, je suppose que vous avez installé Versatile Thermostat et Scheduler Component.
Dans Scheduler, ajoutez un planning : Dans Scheduler, ajoutez un planning :
@@ -1311,7 +1193,11 @@ Si vous souhaitez contribuer, veuillez lire les [directives de contribution](CON
# Dépannages # Dépannages
<details>
<summary>Utilisation d'un Heatzy</summary>
## Utilisation d'un Heatzy ## Utilisation d'un Heatzy
L'utilisation d'un Heatzy est possible à la condition d'utiliser un switch virtuel sur ce modèle : L'utilisation d'un Heatzy est possible à la condition d'utiliser un switch virtuel sur ce modèle :
``` ```
- platform: template - platform: template
@@ -1340,6 +1226,10 @@ L'utilisation d'un Heatzy est possible à la condition d'utiliser un switch virt
preset_mode: "eco" preset_mode: "eco"
``` ```
Merci à @gael pour cet exemple. Merci à @gael pour cet exemple.
</details>
<details>
<summary>Utilisation d'un radiateur avec un fil pilote</summary>
## Utilisation d'un radiateur avec un fil pilote ## Utilisation d'un radiateur avec un fil pilote
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. 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.
@@ -1361,10 +1251,58 @@ Exemple :
icon_template: "{% if is_state('switch.radiateur_soan', 'on') %}mdi:radiator-disabled{% else %}mdi:radiator{% endif %}" icon_template: "{% if is_state('switch.radiateur_soan', 'on') %}mdi:radiator-disabled{% else %}mdi:radiator{% endif %}"
``` ```
</details>
<details>
<summary>Utilisation d'un radiateur avec un module Nodon</summary>
## Utilisation d'un radiateur avec un fil pilote
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 :
```
- platform: template
switches:
chauffage_chb_parents:
unique_id: chauffage_chb_parents
friendly_name: Chauffage chambre parents
value_template: "{{ is_state('select.fp_chb_parents_pilot_wire_mode', 'comfort') }}"
icon_template: >-
{% if is_state('select.fp_chb_parents_pilot_wire_mode', 'comfort') %}
mdi:radiator
{% elif is_state('select.fp_chb_parents_pilot_wire_mode', 'frost_protection') %}
mdi:snowflake
{% else %}
mdi:radiator-disabled
{% endif %}
turn_on:
service: select.select_option
target:
entity_id: select.fp_chb_parents_pilot_wire_mode
data:
option: comfort
turn_off:
service: select.select_option
target:
entity_id: select.fp_chb_parents_pilot_wire_mode
data:
option: eco
```
</details>
<details>
<summary>Seul le premier radiateur chauffe</summary>
## Seul le premier radiateur chauffe ## 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. En mode `over_switch` si plusieurs radiateurs sont configurés pour un même VTherm, l'alllumage va se faire de façon séquentiel pour lisser au plus possible les pics de consommation.
Cela est tout à fait normal et voulu. C'est décrit ici : [Pour un thermostat de type ```thermostat_over_switch```](#pour-un-thermostat-de-type-thermostat_over_switch) Cela est tout à fait normal et voulu. C'est décrit ici : [Pour un thermostat de type ```thermostat_over_switch```](#pour-un-thermostat-de-type-thermostat_over_switch)
</details>
<details>
<summary>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</summary>
## 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`
@@ -1376,7 +1314,10 @@ Avec un VTherm de type `over_climate`, la régulation est faite par le `climate`
Exemple de discussion autour de ces sujets: [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348), [#316](https://github.com/jmcollin78/versatile_thermostat/issues/316), [#312](https://github.com/jmcollin78/versatile_thermostat/discussions/312), [#278](https://github.com/jmcollin78/versatile_thermostat/discussions/278) Exemple de discussion autour de ces sujets: [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348), [#316](https://github.com/jmcollin78/versatile_thermostat/issues/316), [#312](https://github.com/jmcollin78/versatile_thermostat/discussions/312), [#278](https://github.com/jmcollin78/versatile_thermostat/discussions/278)
Pour s'en sortir, VTherm est équipé d'une fonction nommée auto-régulation qui permet d'adapter la consigne envoyée au sous-jacent jusqu'à ce que la consigne soit respectée. Cette fonction permet de compenser le biais de mesure des thermomètres internes. Si le biais est important la régulation doit être importante. Voir [L'auto-régulation](#lauto-régulation) pour configurer l'auto-régulation. Pour s'en sortir, VTherm est équipé d'une fonction nommée auto-régulation qui permet d'adapter la consigne envoyée au sous-jacent jusqu'à ce que la consigne soit respectée. Cette fonction permet de compenser le biais de mesure des thermomètres internes. Si le biais est important la régulation doit être importante. Voir [L'auto-régulation](#lauto-régulation) pour configurer l'auto-régulation.
</details>
<details>
<summary>Régler les paramètres de détection d'ouverture de fenêtre en mode auto</summary>
## Régler les paramètres de détection d'ouverture de fenêtre en mode auto ## Régler les paramètres de détection d'ouverture de fenêtre en mode auto
@@ -1397,9 +1338,13 @@ 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. 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.
</details>
<details>
<summary>Pourquoi mon Versatile Thermostat se met en Securite ?</summary>
## Pourquoi mon Versatile Thermostat se met en Securite ? ## Pourquoi mon Versatile Thermostat se met en Securite ?
Le mode sécurité n'est possible que sur les VTherm `over_switch` et `over_valve`. Il survient lorsqu'un des 2 thermomètres qui donne la température de la pièce ou la température extérieure n'a pas envoyé de valeur depuis plus de `security_delay_min` minutes et que le radiateur chauffait à au moins `security_min_on_percent`. Le mode sécurité est possible sur tous les types de VTherm . Il survient lorsqu'un des 2 thermomètres qui donne la température de la pièce ou la température extérieure n'a pas envoyé de valeur depuis plus de `security_delay_min` minutes et que le radiateur chauffait à au moins `security_min_on_percent`.
Comme l'algorithme est basé sur les mesures de température, si elles ne sont plus reçues par le VTherm, il y a un risque de surchauffe et d'incendie. Pour éviter ça, lorsque les conditions rappelées ci-dessus sont détectées, la chauffe est limité au paramètre `security_default_on_percent`. Cette valeur doit donc être raisonnablement faible (10% est une bonne valeur). Elle permet d'éviter un incendie tout en évitant de couper totalement le radiateur (risque de gel). Comme l'algorithme est basé sur les mesures de température, si elles ne sont plus reçues par le VTherm, il y a un risque de surchauffe et d'incendie. Pour éviter ça, lorsque les conditions rappelées ci-dessus sont détectées, la chauffe est limité au paramètre `security_default_on_percent`. Cette valeur doit donc être raisonnablement faible (10% est une bonne valeur). Elle permet d'éviter un incendie tout en évitant de couper totalement le radiateur (risque de gel).
@@ -1444,8 +1389,13 @@ Cela va dépendre de la cause du problème :
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. 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`, 3. Certains capteurs de température, n'envoie pas de mesure si la température n'a pas changée. Donc en cas de température très stable pendant longtemps, le mode sécurité peut se déclencher. Ce n'est pas très grave puisqu'il s'enlève dès que le VTherm reçoit à nouveau une température. Sur certain thermomètre (TuYA par exemple), on peut forcer le délai max entre 2 mesures. Il conviendra de mettre un délai max < `security_delay_min`,
4. Dès que la température sera a nouveau reçue le mode sécurité s'enlèvera et les valeurs précédentes de preset, température cible et mode seront restaurées. 4. Dès que la température sera a nouveau reçue le mode sécurité s'enlèvera et les valeurs précédentes de preset, température cible et mode seront restaurées.
</details>
<details>
<summary>Utilisation d'un groupe de personnes comme capteur de présence</summary>
## Utilisation d'un groupe de personnes comme capteur de présence ## Utilisation d'un groupe de personnes comme capteur de présence
Malheureusement, les groupes de personnes ne sont pas reconnus comme des capteurs de présence. On ne peut donc pas les utiliser directement dans VTherm. Malheureusement, les groupes de personnes ne sont pas reconnus comme des capteurs de présence. On ne peut donc pas les utiliser directement dans VTherm.
Le contournement est de créer un template de binary_sensor avec le code suivant : Le contournement est de créer un template de binary_sensor avec le code suivant :
@@ -1466,6 +1416,10 @@ Fichier `configuration.yaml`:
template: !include templates.yaml template: !include templates.yaml
... ...
``` ```
</details>
<details>
<summary>Activer les logs du Versatile Thermostat</summary>
## Activer les logs du Versatile Thermostat ## Activer les logs du Versatile Thermostat
Des fois, vous aurez besoin d'activer les logs pour afiner les analyses. Pour cela, éditer le fichier `logger.yaml` de votre configuration et configurer les logs comme suit : Des fois, vous aurez besoin d'activer les logs pour afiner les analyses. Pour cela, éditer le fichier `logger.yaml` de votre configuration et configurer les logs comme suit :
@@ -1476,6 +1430,8 @@ logs:
``` ```
Vous devez recharger la configuration yaml (Outils de dev / Yaml / Toute la configuration Yaml) ou redémarrer Home Assistant pour que ce changement soit pris en compte. Vous devez recharger la configuration yaml (Outils de dev / Yaml / Toute la configuration Yaml) ou redémarrer Home Assistant pour que ce changement soit pris en compte.
</details>
*** ***
[versatile_thermostat]: https://github.com/jmcollin78/versatile_thermostat [versatile_thermostat]: https://github.com/jmcollin78/versatile_thermostat

475
README.md
View File

@@ -8,7 +8,12 @@
> ![Tip](images/tips.png) This thermostat integration aims to drastically simplify your automations around climate management. Because all classical events in climate are natively handled by the thermostat (nobody at home ?, activity detected in a room ?, window open ?, power shedding ?), you don't have to build over complicated scripts and automations to manage your climates ;-). > ![Tip](images/tips.png) This thermostat integration aims to drastically simplify your automations around climate management. Because all classical events in climate are natively handled by the thermostat (nobody at home ?, activity detected in a room ?, window open ?, power shedding ?), you don't have to build over complicated scripts and automations to manage your climates ;-).
- [Major changes in version 5.0](#major-changes-in-version-50) - [Changes in version 6.0](#changes-in-version-60)
- [Temperature entities for presets](#temperature-entities-for-presets)
- [In the case of a central configuration](#in-the-case-of-a-central-configuration)
- [Redesign of the configuration menu](#redesign-of-the-configuration-menu)
- [The 'Incomplete configuration' and 'Finalize' menu options](#the-incomplete-configuration-and-finalize-menu-options)
- [Changes in release 5.0](#changes-in-release-50)
- [Thanks for the beer buymecoffee](#thanks-for-the-beer-buymecoffee) - [Thanks for the beer buymecoffee](#thanks-for-the-beer-buymecoffee)
- [When to use / not use](#when-to-use--not-use) - [When to use / not use](#when-to-use--not-use)
- [Incompatibilities](#incompatibilities) - [Incompatibilities](#incompatibilities)
@@ -27,6 +32,7 @@
- [Internal temperature compensation](#internal-temperature-compensation) - [Internal temperature compensation](#internal-temperature-compensation)
- [synthesis of the self-regulation algorithm](#synthesis-of-the-self-regulation-algorithm) - [synthesis of the self-regulation algorithm](#synthesis-of-the-self-regulation-algorithm)
- [Auto-fan mode](#auto-fan-mode) - [Auto-fan mode](#auto-fan-mode)
- [Automatic start/stop](#automatic-startstop)
- [For a thermostat of type ```thermostat_over_valve```:](#for-a-thermostat-of-type-thermostat_over_valve) - [For a thermostat of type ```thermostat_over_valve```:](#for-a-thermostat-of-type-thermostat_over_valve)
- [Configure the TPI algorithm coefficients](#configure-the-tpi-algorithm-coefficients) - [Configure the TPI algorithm coefficients](#configure-the-tpi-algorithm-coefficients)
- [Configure the preset temperature](#configure-the-preset-temperature) - [Configure the preset temperature](#configure-the-preset-temperature)
@@ -44,7 +50,7 @@
- [The events](#the-events) - [The events](#the-events)
- [Warning](#warning) - [Warning](#warning)
- [Parameter summary](#parameter-summary) - [Parameter summary](#parameter-summary)
- [Examples tuning](#examples-tuning) - [Tuning examples](#tuning-examples)
- [Electrical heater](#electrical-heater) - [Electrical heater](#electrical-heater)
- [Central heating (gaz or fuel heating system)](#central-heating-gaz-or-fuel-heating-system) - [Central heating (gaz or fuel heating system)](#central-heating-gaz-or-fuel-heating-system)
- [Temperature sensor will battery](#temperature-sensor-will-battery) - [Temperature sensor will battery](#temperature-sensor-will-battery)
@@ -71,6 +77,7 @@
- [Troubleshooting](#troubleshooting) - [Troubleshooting](#troubleshooting)
- [Using a Heatzy](#using-a-heatzy) - [Using a Heatzy](#using-a-heatzy)
- [Using a Heatsink with a Pilot Wire](#using-a-heatsink-with-a-pilot-wire) - [Using a Heatsink with a Pilot Wire](#using-a-heatsink-with-a-pilot-wire)
- [Using a heater with a Nodon module](#using-a-heater-with-a-nodon-module)
- [Only the first radiator heats](#only-the-first-radiator-heats) - [Only the first radiator heats](#only-the-first-radiator-heats)
- [The radiator heats up even though the setpoint temperature is exceeded or does not heat up even though the room temperature is well below the setpoint](#the-radiator-heats-up-even-though-the-setpoint-temperature-is-exceeded-or-does-not-heat-up-even-though-the-room-temperature-is-well-below-the-setpoint) - [The radiator heats up even though the setpoint temperature is exceeded or does not heat up even though the room temperature is well below the setpoint](#the-radiator-heats-up-even-though-the-setpoint-temperature-is-exceeded-or-does-not-heat-up-even-though-the-room-temperature-is-well-below-the-setpoint)
- [Type `over_switch` or `over_valve`](#type-over_switch-or-over_valve) - [Type `over_switch` or `over_valve`](#type-over_switch-or-over_valve)
@@ -86,19 +93,25 @@
This custom component for Home Assistant is an upgrade and is a complete rewrite of the component "Awesome thermostat" (see [Github](https://github.com/dadge/awesome_thermostat)) with addition of features. This custom component for Home Assistant is an upgrade and is a complete rewrite of the component "Awesome thermostat" (see [Github](https://github.com/dadge/awesome_thermostat)) with addition of features.
>![New](images/new-icon.png) _*News*_ >![New](images/new-icon.png) _*Latest releases*_
> * **Release 6.5** :
> - Added a new function allowing the automatic shutdown and restart of a VTherm `over_climate` [585](https://github.com/jmcollin78/versatile_thermostat/issues/585)
> - Improved management of openings at startup. Allows to memorize and recalculate the state of an opening when restarting Home Assistant [504](https://github.com/jmcollin78/versatile_thermostat/issues/504)
> * **Release 6.0**:
> - Added entities from the Number domain to configure preset temperatures [354](https://github.com/jmcollin78/versatile_thermostat/issues/354)
> - Complete redesign of the configuration menu to remove temperatures and use a menu instead of a configuration tunnel [354](https://github.com/jmcollin78/versatile_thermostat/issues/354)
> * **Release 5.4**: > * **Release 5.4**:
> - Added temperature step [#311](https://github.com/jmcollin78/versatile_thermostat/issues/311), > - Added temperature step [#311](https://github.com/jmcollin78/versatile_thermostat/issues/311),
> - addition of regulation thresholds for the `over_valve` to avoid draining the TRV battery too much [#338](https://github.com/jmcollin78/versatile_thermostat/issues/338), > - addition of regulation thresholds for the `over_valve` to avoid draining the TRV battery too much [#338](https://github.com/jmcollin78/versatile_thermostat/issues/338),
> - added an option allowing the internal temperature of a TRV to be used to force self-regulation [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348), > - added an option allowing the internal temperature of a TRV to be used to force self-regulation [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348),
> - added a keep-alive function for VTherm `over_switch` [#345](https://github.com/jmcollin78/versatile_thermostat/issues/345) > - added a keep-alive function for VTherm `over_switch` [#345](https://github.com/jmcollin78/versatile_thermostat/issues/345)
<details>
<summary>Others releases</summary>
> * **Release 5.3**: Added a central boiler control function [#234](https://github.com/jmcollin78/versatile_thermostat/issues/234) - more information here: [Controlling a central boiler](#controlling-a-central-boiler). Added the ability to disable security mode for outdoor thermometer [#343](https://github.com/jmcollin78/versatile_thermostat/issues/343) > * **Release 5.3**: Added a central boiler control function [#234](https://github.com/jmcollin78/versatile_thermostat/issues/234) - more information here: [Controlling a central boiler](#controlling-a-central-boiler). Added the ability to disable security mode for outdoor thermometer [#343](https://github.com/jmcollin78/versatile_thermostat/issues/343)
> * **Release 5.2**: Added a `central_mode` allowing all VTherms to be controlled centrally [#158](https://github.com/jmcollin78/versatile_thermostat/issues/158). > * **Release 5.2**: Added a `central_mode` allowing all VTherms to be controlled centrally [#158](https://github.com/jmcollin78/versatile_thermostat/issues/158).
> * **Release 5.1**: Limitation of the values sent to the valves and the temperature sent to the underlying climate. > * **Release 5.1**: Limitation of the values sent to the valves and the temperature sent to the underlying climate.
> * **Release 5.0**: Added a central configuration allowing the sharing of attributes that can be shared [#239](https://github.com/jmcollin78/versatile_thermostat/issues/239). > * **Release 5.0**: Added a central configuration allowing the sharing of attributes that can be shared [#239](https://github.com/jmcollin78/versatile_thermostat/issues/239).
<details>
<summary>Others releases</summary>
> * **Release 4.3**: Added an auto-fan mode for the `over_climate` type allowing ventilation to be activated if the temperature difference is significant [#223](https://github.com/jmcollin78/versatile_thermostat/issues/223). > * **Release 4.3**: Added an auto-fan mode for the `over_climate` type allowing ventilation to be activated if the temperature difference is significant [#223](https://github.com/jmcollin78/versatile_thermostat/issues/223).
> * **Release 4.2**: The calculation of the slope of the temperature curve is now done in °/hour and no longer in °/min [#242](https://github.com/jmcollin78/versatile_thermostat/issues/242). Correction of automatic detection of openings by adding smoothing of the temperature curve. > * **Release 4.2**: The calculation of the slope of the temperature curve is now done in °/hour and no longer in °/min [#242](https://github.com/jmcollin78/versatile_thermostat/issues/242). Correction of automatic detection of openings by adding smoothing of the temperature curve.
> * **Release 4.1**: Added an **Expert** regulation mode in which the user can specify their own auto-regulation parameters instead of using the pre-programmed ones [#194]( https://github.com/jmcollin78/versatile_thermostat/issues/194). > * **Release 4.1**: Added an **Expert** regulation mode in which the user can specify their own auto-regulation parameters instead of using the pre-programmed ones [#194]( https://github.com/jmcollin78/versatile_thermostat/issues/194).
@@ -117,8 +130,83 @@ This custom component for Home Assistant is an upgrade and is a complete rewrite
> * **major release 2.0**: addition of the "over climate" thermostat allowing you to transform any thermostat into a Versatile Thermostat and add all the functions of the latter. > * **major release 2.0**: addition of the "over climate" thermostat allowing you to transform any thermostat into a Versatile Thermostat and add all the functions of the latter.
</details> </details>
# Major changes in version 5.0
![New](images/new-icon.png) <details>
<summary>Changes in version 6.0</summary>
# Changes in version 6.0
## Temperature entities for presets
Preset temperatures are now directly accessible in the form of entities linked to VTherm.
Example :
![Temperature entities](images/temp-entities-1.png)
The Boost, Comfort, Eco and Frost Protection entities allow you to directly adjust the temperatures of these presets without having to reconfigure the VTHerm in the configuration screens.
These modifications persist after a restart and are taken into account immediately by VTherm.
Depending on the functions activated, the list of temperatures may be more or less complete:
1. If presence management is activated, absence presets are created. They are suffixed with 'abs' for absence,
2. If air conditioning management (AC Mode) is activated, air conditioning mode presets are created. They are suffixed with 'clim' for air conditioning. Only the Frost protection preset has no equivalent in air conditioning mode,
3. The different absent and air conditioning combinations can be created depending on the configuration of the VTherm
If a VTherm uses the presets of the central configuration, these entities are not created, because the temperatures of the presets are managed by the central configuration.
### In the case of a central configuration
If you have configured a central configuration, this also has its own presets which meet the same rules as stated above.
Example of a central configuration with presence management and AC (air conditioning) mode:
![Temperature entities](images/temp-entities-2.png)
In the case of a change of a central configuration temperature, all VTherms that use this preset are immediately updated.
## Redesign of the configuration menu
The configuration menu has been completely revised. It dynamically adapts to the user's choices and allows direct access to the settings of the desired function without having to go through the entire configuration tunnel.
To create a new VTherm, you will first need to choose the type of VTherm:
![VTherm choice](images/config-main0.png)
Then, you now access the following configuration menu:
![VTherm menu](images/config-menu.png)
Each part to be configured is directly accessible, without having to go through the entire configuration tunnel as before.
You will note the menu option named `Functions` which allows you to choose which functions will be implemented for this VTherm:
![VTherm features](images/config-features.png)
Depending on your choices, the main menu will adapt to add the necessary options.
Example of menu with all functions checked:
![VTherm menu](images/config-menu-all-options.png)
You can see that the 'Opening detection', 'Motion detection', 'Power management' and 'Presence management' options have been added. You can then configure them.
### The 'Incomplete configuration' and 'Finalize' menu options
The last menu option is special. It allows you to validate the creation of the VTherm when all the functions have been correctly configured.
If one option is not configured correctly, the last option is:
![Incomplete configuration](images/config-not-complete.png)
Its selection does nothing but prevents you from finalizing the creation (resp. modification) of the VTherm.
**You must then search in the options which one is missing**.
Once all configuration is valid, the last option changes to:
![Complete configuration](images/config-complete.png)
Click on this option to create (resp. modify) the VTherm:
![Configuration Complete](images/config-terminate.png)
</details>
<details>
<summary>Changes in release 5.0</summary>
# Changes in release 5.0
You can now define a central configuration which will allow you to share certain attributes on all your VTherms (or only part of them). To use this possibility, you must: You can now define a central configuration which will allow you to share certain attributes on all your VTherms (or only part of them). To use this possibility, you must:
1. Create a VTherm of type “Central Configuration”, 1. Create a VTherm of type “Central Configuration”,
@@ -131,11 +219,10 @@ The configurable attributes in the central configuration are listed here: [Param
When changing the central configuration, all VTherms will be reloaded to take these changes into account. When changing the central configuration, all VTherms will be reloaded to take these changes into account.
Consequently, the entire configuration phase of a VTherm has been profoundly modified to be able to use the central configuration or overload the values of the central configuration with values specific to the VTherm being configured. Consequently, the entire configuration phase of a VTherm has been profoundly modified to be able to use the central configuration or overload the values of the central configuration with values specific to the VTherm being configured.
</details>
**Note:** the VTherm configuration screenshots have not been updated.
# Thanks for the beer [buymecoffee](https://www.buymeacoffee.com/jmcollin78) # Thanks for the beer [buymecoffee](https://www.buymeacoffee.com/jmcollin78)
Many thanks to @salabur, @pvince83, @bergoglio, @EPicLURcher, @ecolorado66, @Kriss1670, @maia, @f.maymil, @moutte69, @Jerome, @Gunnar M, @Greg.o, @John Burgess, @abyssmal, @capinfo26, @Helge, @MattG, @MattG, @Mexx62, @Someone, @Lajull, @giopeco, @fredericselier, @philpagan, @studiogriffanti, @Edwin, @Sebbou for the beers. It's very nice and encourages me to continue! Many thanks to @salabur, @pvince83, @bergoglio, @EPicLURcher, @ecolorado66, @Kriss1670, @maia, @f.maymil, @moutte69, @Jerome, @Gunnar M, @Greg.o, @John Burgess, @abyssmal, @capinfo26, @Helge, @MattG, @MattG, @Mexx62, @Someone, @Lajull, @giopeco, @fredericselier, @philpagan, @studiogriffanti, @Edwin, @Sebbou, @Gerard R., @John Burgess, @Sylvoliv, @cdenfert, @stephane.l, @jms92100 for the beers. It's very nice and encourages me to continue!
# When to use / not use # When to use / not use
This thermostat can control 3 types of equipment: This thermostat can control 3 types of equipment:
@@ -159,6 +246,8 @@ Some TRV type thermostats are known to be incompatible with the Versatile Thermo
3. Thermostat of type Heatzy which doesn't supports the set_temperature command. 3. Thermostat of type Heatzy which doesn't supports the set_temperature command.
4. Thermostats of type Rointe tends to awake alone even if VTherm turns it off. Others functions works fine. 4. Thermostats of type Rointe tends to awake alone even if VTherm turns it off. Others functions works fine.
5. TRV of type Aqara SRTS-A01 and MOES TV01-ZB which doesn't have the return state `hvac_action` allowing to know if it is heating or not. So return states are not available. Others features, seems to work normally. 5. TRV of type Aqara SRTS-A01 and MOES TV01-ZB which doesn't have the return state `hvac_action` allowing to know if it is heating or not. So return states are not available. Others features, seems to work normally.
6. The Airwell with the "Midea AC LAN" integration. If two orders are too close, the device shut off.
7. System based on intégration Overkiz don't work as expected. It seems not possible to turn off nor sending setpoint on those systems.
# Why another thermostat implementation ? # Why another thermostat implementation ?
@@ -210,16 +299,18 @@ This component named __Versatile thermostat__ manage the following use cases :
## Creation of a new Versatile Thermostat ## Creation of a new Versatile Thermostat
Click on Add integration button in the integration page Click on Add integration button in the integration page
![image](images/add-an-integration.png) ![image](images/add-an-integration.png)
The configuration can be change through the same interface. Simply select the thermostat to change, hit "Configure" and you will be able to change some parameters or configuration. The configuration can be change through the same interface. Simply select the thermostat to change, hit "Configure" and you will be able to change some parameters or configuration.
Then follow the configurations steps as follow: Then choose the type of VTherm you want to create:
![image](images/config-main0.png)
## Minimal configuration update ## Minimal configuration update
![image](images/config-main0.png) Then choose the “Main attributes” menu.
![image](images/config-main.png) ![image](images/config-main.png)
@@ -239,6 +330,7 @@ Give the main mandatory attributes:
> 2. if the cycle is too short, the heater could never reach the target temperature. For the storage radiator for example it will be used unnecessarily. > 2. if the cycle is too short, the heater could never reach the target temperature. For the storage radiator for example it will be used unnecessarily.
## Select the driven entity ## Select the driven entity
Depending on your choice of thermostat type, you will need to choose one or more `switch`, `climate` or `number` type entities. Only entities compatible with the type are presented. Depending on your choice of thermostat type, you will need to choose one or more `switch`, `climate` or `number` type entities. Only entities compatible with the type are presented.
> ![Tip](images/tips.png) _*How to choose the type*_ > ![Tip](images/tips.png) _*How to choose the type*_
@@ -297,98 +389,17 @@ These three parameters make it possible to modulate the regulation and avoid mul
Self-regulation consists of forcing the equipment to go further by forcing its set temperature regularly. Its consumption can therefore be increased, as well as its wear. Self-regulation consists of forcing the equipment to go further by forcing its set temperature regularly. Its consumption can therefore be increased, as well as its wear.
#### Self-regulation in Expert mode
In **Expert** mode you can finely adjust the auto-regulation parameters to achieve your objectives and optimize as best as possible. The algorithm calculates the difference between the setpoint and the actual temperature of the room. This discrepancy is called error.
The adjustable parameters are as follows:
1. `kp`: the factor applied to the raw error,
2. `ki`: the factor applied to the accumulation of errors,
3. `k_ext`: the factor applied to the difference between the interior temperature and the exterior temperature,
4. `offset_max`: the maximum correction (offset) that the regulation can apply,
5. `stabilization_threshold`: a stabilization threshold which, when reached by the error, resets the accumulation of errors to 0,
6. `accumulated_error_threshold`: the maximum for error accumulation.
For tuning, these observations must be taken into account:
1. `kp * error` will give the offset linked to the raw error. This offset is directly proportional to the error and will be 0 when the target is reached,
2. the accumulation of the error makes it possible to correct the stabilization of the curve while there remains an error. The error accumulates and the offset therefore gradually increases which should eventually stabilize at the target temperature. For this fundamental parameter to have an effect it must not be too small. An average value is 30
3. `ki * accumulated_error_threshold` will give the maximum offset linked to the accumulation of the error,
4. `k_ext` allows a correction to be applied immediately (without waiting for errors to accumulate) when the outside temperature is very different from the target temperature. If the stabilization is done too high when the temperature differences are significant, it is because this parameter is too high. It should be possible to cancel completely to let the first 2 offsets take place
The pre-programmed values are as follows:
Slow régulation :
kp: 0.2 # 20% of the current internal regulation offset are caused by the current difference of target temperature and room temperature
ki: 0.8 / 288.0 # 80% of the current internal regulation offset are caused by the average offset of the past 24 hours
k_ext: 1.0 / 25.0 # this will add 1°C to the offset when it's 25°C colder outdoor than indoor
offset_max: 2.0 # limit to a final offset of -2°C to +2°C
stabilization_threshold: 0.0 # this needs to be disabled as otherwise the long term accumulated error will always be reset when the temp briefly crosses from/to below/above the target
accumulated_error_threshold: 2.0 * 288 # this allows up to 2°C long term offset in both directions
Light régulation :
kp: 0.2
ki: 0.05
k_ext: 0.05
offset_max: 1.5
stabilization_threshold: 0.1
accumulated_error_threshold: 10
Medium régulation :
kp: 0.3
ki: 0.05
k_ext: 0.1
offset_max: 2
stabilization_threshold: 0.1
accumulated_error_threshold: 20
Strong régulation :
"""Strong parameters for regulation
A set of parameters which doesn't take into account the external temp
and concentrate to internal temp error + accumulated error.
This should work for cold external conditions which else generates
high external_offset"""
kp: 0.4
ki: 0.08
k_ext: 0.0
offset_max: 5
stabilization_threshold: 0.1
accumulated_error_threshold: 50
To use Expert mode you must declare the values you want to use for each of these parameters in your `configuration.yaml` in the following form:
```
versatile_thermostat:
auto_regulation_expert:
kp: 0.4
ki: 0.08
k_ext: 0.0
offset_max: 5
stabilization_threshold: 0.1
accumulated_error_threshold: 50
```
and of course, configure the VTherm's self-regulation mode in **Expert** mode. All VTherms in Expert mode will use these same settings.
For the changes to be taken into account, you must either **completely restart Home Assistant** or just the **Versatile Thermostat integration** (Dev tools / Yaml / reloading the configuration / Versatile Thermostat).
#### Internal temperature compensation #### Internal temperature compensation
Sometimes, it happens that the internal thermometer of the underlying (TRV, air conditioning, etc.) is so wrong that self-regulation is not enough to regulate. Sometimes, a devices internal temperature sensor (like in a TRV or AC) can give inaccurate readings, especially if its too close to a heat source. This can cause the device to stop heating too soon.
This happens when the internal thermometer is too close to the heat source. The internal temperature then rises much faster than the room temperature, which generates faults in the regulation. For example:
Example : 1. target temperature: 20 °C, room temperature: 18 °C,
1. the room temperature is 18°, the setpoint is 20°, 2. devices internal sensor: 22 °C
2. the internal temperature of the equipment is 22°, 3. If the target temperature is increased to 21 °C, the device wont heat because it thinks its already warm (internal temperature is 22°C).
3. if VTherm sends 21° as setpoint (= 20° + 1° auto-regulation), then the equipment will not heat because its internal temperature (22°) is above the setpoint (21°)
To overcome this, a new optional option was added in version 5.4: ![Use of internal temperature](images/config-use-internal-temp.png) The Adjust Setpoint for Room vs. TRV Temperature feature fixes this by adding the temperature difference between the room and the devices internal reading to the target. In this case, VTherm would adjust the target to 25°C (21°C + 4°C difference), forcing the device to continue heating.
When enabled, this function will add the difference between the internal temperature and the room temperature to the setpoint to force heating. This adjustment is specific to each device, making the heating system more accurate and avoiding issues from faulty sensor readings.
In the example above, the difference is +4° (22° - 18°), so VTherm will send 25° (21°+4°) to the equipment forcing it to heat up. See ![Use of internal temperature](images/config-use-internal-temp.png)
This difference is calculated for each underlying because each has its own internal temperature. Think of a VTherm which would be connected to 3 TRVs each with its internal temperature for example.
We then obtain much more effective self-regulation which avoids the pitfall of large variations in faulty internal temperature.
#### synthesis of the self-regulation algorithm #### synthesis of the self-regulation algorithm
The self-regulation algorithm can be summarized as follows: The self-regulation algorithm can be summarized as follows:
@@ -411,6 +422,17 @@ Obviously your underlying equipment must be equipped with ventilation and be con
If your equipment does not include Turbo mode, Forte` mode will be used as a replacement. If your equipment does not include Turbo mode, Forte` mode will be used as a replacement.
Once the temperature difference becomes low again, the ventilation will go into a "normal" mode which depends on your equipment, namely (in order): `Silence (mute)`, `Auto (auto)`, `Low (low)`. The first value that is possible for your equipment will be chosen. Once the temperature difference becomes low again, the ventilation will go into a "normal" mode which depends on your equipment, namely (in order): `Silence (mute)`, `Auto (auto)`, `Low (low)`. The first value that is possible for your equipment will be chosen.
#### Automatic start/stop
This function was introduced in 6.5.0. It allows VTherm to stop equipment that does not need to be turned on and to restart it when conditions require it. This function has 3 settings that allow the equipment to be stopped/restarted more or less quickly.
To use it, you must:
1. Add the `Use the auto start and stop feature` function in the 'Features' menu,
2. Set the detection level in the `Auto start and stop` option that is displayed when the function has been activated. You choose the detection level between 'Slow', 'Medium' and 'Fast'. The 'Fast' level will result in more shutdowns/restarts.
Once configured, you will now have a new entity of type `switch` that allows you to authorize or not the automatic shutdown/restart without touching the configuration. This entity is available on the VTherm device and is called `switch.<name>_enable_auto_start_stop`. Check it to authorize the automatic startup and shutdown.
The detection algorithm is described [here](https://github.com/jmcollin78/versatile_thermostat/issues/585).
### For a thermostat of type ```thermostat_over_valve```: ### For a thermostat of type ```thermostat_over_valve```:
![image](images/config-linked-entity3.png) ![image](images/config-linked-entity3.png)
You can choose up to domain entity ```number``` or ```ìnput_number``` which will control the valves. You can choose up to domain entity ```number``` or ```ìnput_number``` which will control the valves.
@@ -419,14 +441,13 @@ The algorithm to use is currently limited to TPI is available. See [algorithm](#
It is possible to choose an over valve thermostat which controls air conditioning by checking the "AC Mode" box. In this case, only the cooling mode will be visible. It is possible to choose an over valve thermostat which controls air conditioning by checking the "AC Mode" box. In this case, only the cooling mode will be visible.
## Configure the TPI algorithm coefficients ## Configure the TPI algorithm coefficients
click on 'Validate' on the previous page, and if you choose a ```over_switch``` or ```over_valve``` thermostat and you will get there:
Ff you choose a ```over_switch``` or ```over_valve``` thermostat and select the "TPI" menu option, you will get there:
![image](images/config-tpi.png) ![image](images/config-tpi.png)
For more informations on the TPI algorithm and tuned please refer to [algorithm](#algorithm). For more informations on the TPI algorithm and tuned please refer to [algorithm](#algorithm).
## Configure the preset temperature ## Configure the preset temperature
Click on 'Validate' on the previous page and you will get there:
![image](images/config-presets.png)
The preset mode allows you to pre-configurate targeted temperature. Used in conjonction with Scheduler (see [scheduler](#even-better-with-scheduler-component) you will have a powerfull and simple way to optimize the temperature vs electrical consumption of your hous. Preset handled are the following : The preset mode allows you to pre-configurate targeted temperature. Used in conjonction with Scheduler (see [scheduler](#even-better-with-scheduler-component) you will have a powerfull and simple way to optimize the temperature vs electrical consumption of your hous. Preset handled are the following :
- **Eco** : device is running an energy-saving mode - **Eco** : device is running an energy-saving mode
@@ -437,6 +458,8 @@ The preset mode allows you to pre-configurate targeted temperature. Used in conj
**None** is always added in the list of modes, as it is a way to not use the presets modes but a **manual temperature** instead. **None** is always added in the list of modes, as it is a way to not use the presets modes but a **manual temperature** instead.
The pre-settings are made (since v6.0) directly from the VTherm entities or from the central configuration if you use the central configuration.
> ![Tip](images/tips.png) _*Notes*_ > ![Tip](images/tips.png) _*Notes*_
> 1. Changing manually the target temperature, set the preset to None (no preset). This way you can always set a target temperature even if no preset are available. > 1. Changing manually the target temperature, set the preset to None (no preset). This way you can always set a target temperature even if no preset are available.
> 2. standard ``Away`` preset is a hidden preset which is not directly selectable. Versatile Thermostat uses the presence management or movement management to set automatically and dynamically the target temperature depending on a presence in the home or an activity in the room. See [presence management](#configure-the-presence-management). > 2. standard ``Away`` preset is a hidden preset which is not directly selectable. Versatile Thermostat uses the presence management or movement management to set automatically and dynamically the target temperature depending on a presence in the home or an activity in the room. See [presence management](#configure-the-presence-management).
@@ -445,6 +468,7 @@ The preset mode allows you to pre-configurate targeted temperature. Used in conj
> 5. ff you don't want to use the preseet, give 0 as temperature. The preset will then been ignored and will not displayed in the front component > 5. ff you don't want to use the preseet, give 0 as temperature. The preset will then been ignored and will not displayed in the front component
## Configure the doors/windows turning on/off the thermostats ## Configure the doors/windows turning on/off the thermostats
You must have chosen the ```With opening detection``` feature on the first page to arrive on this page. You must have chosen the ```With opening detection``` feature on the first page to arrive on this page.
The detection of openings can be done in 2 ways: The detection of openings can be done in 2 ways:
1. either with a sensor placed on the opening (sensor mode), 1. either with a sensor placed on the opening (sensor mode),
@@ -527,6 +551,7 @@ This allows you to change the max power along time using a Scheduler or whatever
> 5. If you control several heaters, the **power consumption of your heater** setup should be the sum of the power. > 5. If you control several heaters, the **power consumption of your heater** setup should be the sum of the power.
## Configure presence or occupancy ## Configure presence or occupancy
If selected on the first page, this feature allows you to dynamically change the temperature of all configured thermostat presets when no one is home or when someone comes home. To do this, you must configure the temperature that will be used for each preset when presence is disabled. When the presence sensor turns off, these temperatures will be used. When it turns back on, the "normal" temperature configured for the preset is used. See [preset management](#configure-the-preset-temperature). If selected on the first page, this feature allows you to dynamically change the temperature of all configured thermostat presets when no one is home or when someone comes home. To do this, you must configure the temperature that will be used for each preset when presence is disabled. When the presence sensor turns off, these temperatures will be used. When it turns back on, the "normal" temperature configured for the preset is used. See [preset management](#configure-the-preset-temperature).
To configure presence, complete this form: To configure presence, complete this form:
@@ -547,6 +572,7 @@ ATTENTION: groups of people do not function as a presence sensor. They are not r
> 2. you can use the person.xxxx direct sensor or a group of Home Assistant sensors. The presence sensor manages the ``on`` or ``home`` states as present and the ``off`` or ``not_home`` states as absent. > 2. you can use the person.xxxx direct sensor or a group of Home Assistant sensors. The presence sensor manages the ``on`` or ``home`` states as present and the ``off`` or ``not_home`` states as absent.
## Advanced configuration ## Advanced configuration
Those parameters allows to fine tune the thermostat. Those parameters allows to fine tune the thermostat.
The advanced configuration form is the following: The advanced configuration form is the following:
@@ -561,6 +587,8 @@ Setting this parameter to ``0.00`` will trigger the safety preset regardless of
The fourth parameter (``security_default_on_percent``) is the ``on_percent`` value that will be used when the thermostat enters ``safety`` mode. If you put ``0`` then the thermostat will be cut off when it goes into ``safety`` mode, putting 0.2% for example allows you to keep a little heating (20% in this case), even in mode ``safety``. It avoids finding your home totally frozen during a thermometer failure. The fourth parameter (``security_default_on_percent``) is the ``on_percent`` value that will be used when the thermostat enters ``safety`` mode. If you put ``0`` then the thermostat will be cut off when it goes into ``safety`` mode, putting 0.2% for example allows you to keep a little heating (20% in this case), even in mode ``safety``. It avoids finding your home totally frozen during a thermometer failure.
Note: parameters `security_min_on_percent` et `security_default_on_percent` are not used by `over_climate` VTherm.
Since version 5.3 it is possible to deactivate the safety device following a lack of data from the outdoor thermometer. Indeed, this most of the time having a low impact on regulation (depending on your settings), it is possible that it is absent without endangering the home. To do this, you must add the following lines to your `configuration.yaml`: Since version 5.3 it is possible to deactivate the safety device following a lack of data from the outdoor thermometer. Indeed, this most of the time having a low impact on regulation (depending on your settings), it is possible that it is absent without endangering the home. To do this, you must add the following lines to your `configuration.yaml`:
``` ```
versatile_thermostat: versatile_thermostat:
@@ -580,6 +608,7 @@ See [example tuning](#examples-tuning) for common tuning examples
> 5. Thermostat of type ``thermostat_over_climate`` are not concerned by the safety feature. > 5. Thermostat of type ``thermostat_over_climate`` are not concerned by the safety feature.
## Centralized control ## Centralized control
Since release 5.2, if you have defined a centralized configuration, you have a new entity named `select.central_mode` which allows you to control all VTherms with a single action. For a VTherm to be centrally controllable, its configuration attribute named `use_central_mode` must be true. Since release 5.2, if you have defined a centralized configuration, you have a new entity named `select.central_mode` which allows you to control all VTherms with a single action. For a VTherm to be centrally controllable, its configuration attribute named `use_central_mode` must be true.
This entity is presented in the form of a list of choices which contains the following choices: This entity is presented in the form of a list of choices which contains the following choices:
@@ -595,6 +624,7 @@ Example rendering:
![central_mode](images/central_mode.png) ![central_mode](images/central_mode.png)
## Control of a central boiler ## Control of a central boiler
Since release 5.3, you have the possibility of controlling a centralized boiler. From the moment it is possible to start or stop this boiler from Home Assistant, then Versatile Thermostat will be able to control it directly. Since release 5.3, you have the possibility of controlling a centralized boiler. From the moment it is possible to start or stop this boiler from Home Assistant, then Versatile Thermostat will be able to control it directly.
The principle put in place is generally as follows: The principle put in place is generally as follows:
@@ -694,6 +724,116 @@ context:
> ![Tip](images/tips.png) _*Notes*_ > ![Tip](images/tips.png) _*Notes*_
> Controlling a central boiler using software or hardware such as home automation can pose risks to its proper functioning. Before using these functions, make sure that your boiler has safety functions and that they are working. Turning on a boiler if all the taps are closed can generate excess pressure, for example. > Controlling a central boiler using software or hardware such as home automation can pose risks to its proper functioning. Before using these functions, make sure that your boiler has safety functions and that they are working. Turning on a boiler if all the taps are closed can generate excess pressure, for example.
## Expert Mode Settings
Expert Mode settings refer to Settings made in the Home Assistant `configuration.yaml` file under the `versatile_thermostat` section. You might have to add this section by yourself to the `configuration.yaml` file.
These settings are meant to be used only in **specific niche cases and with careful considerations**.
The following sections describe the available export mode settings in detail with examples on how to configure them. Be aware that these settings require a **complete restart** of Home Assistant or a **reload of Versatile Thermostat integration** (Dev tools / Yaml / reloading the configuration / Versatile Thermostat) to take effect.
### Self-regulation in Expert mode
In **Expert** mode you can finely adjust the auto-regulation parameters to achieve your objectives and optimize as best as possible. The algorithm calculates the difference between the setpoint and the actual temperature of the room. This discrepancy is called error.
The adjustable parameters are as follows:
1. `kp`: the factor applied to the raw error,
2. `ki`: the factor applied to the accumulation of errors,
3. `k_ext`: the factor applied to the difference between the interior temperature and the exterior temperature,
4. `offset_max`: the maximum correction (offset) that the regulation can apply,
5. `stabilization_threshold`: a stabilization threshold which, when reached by the error, resets the accumulation of errors to 0,
6. `accumulated_error_threshold`: the maximum for error accumulation.
For tuning, these observations must be taken into account:
1. `kp * error` will give the offset linked to the raw error. This offset is directly proportional to the error and will be 0 when the target is reached,
2. the accumulation of the error makes it possible to correct the stabilization of the curve while there remains an error. The error accumulates and the offset therefore gradually increases which should eventually stabilize at the target temperature. For this fundamental parameter to have an effect it must not be too small. An average value is 30
3. `ki * accumulated_error_threshold` will give the maximum offset linked to the accumulation of the error,
4. `k_ext` allows a correction to be applied immediately (without waiting for errors to accumulate) when the outside temperature is very different from the target temperature. If the stabilization is done too high when the temperature differences are significant, it is because this parameter is too high. It should be possible to cancel completely to let the first 2 offsets take place
The pre-programmed values are as follows:
Slow régulation :
kp: 0.2 # 20% of the current internal regulation offset are caused by the current difference of target temperature and room temperature
ki: 0.8 / 288.0 # 80% of the current internal regulation offset are caused by the average offset of the past 24 hours
k_ext: 1.0 / 25.0 # this will add 1°C to the offset when it's 25°C colder outdoor than indoor
offset_max: 2.0 # limit to a final offset of -2°C to +2°C
stabilization_threshold: 0.0 # this needs to be disabled as otherwise the long term accumulated error will always be reset when the temp briefly crosses from/to below/above the target
accumulated_error_threshold: 2.0 * 288 # this allows up to 2°C long term offset in both directions
Light régulation :
kp: 0.2
ki: 0.05
k_ext: 0.05
offset_max: 1.5
stabilization_threshold: 0.1
accumulated_error_threshold: 10
Medium régulation :
kp: 0.3
ki: 0.05
k_ext: 0.1
offset_max: 2
stabilization_threshold: 0.1
accumulated_error_threshold: 20
Strong régulation :
"""Strong parameters for regulation
A set of parameters which doesn't take into account the external temp
and concentrate to internal temp error + accumulated error.
This should work for cold external conditions which else generates
high external_offset"""
kp: 0.4
ki: 0.08
k_ext: 0.0
offset_max: 5
stabilization_threshold: 0.1
accumulated_error_threshold: 50
To use Expert mode you must declare the values you want to use for each of these parameters in your `configuration.yaml` in the following form:
```
versatile_thermostat:
auto_regulation_expert:
kp: 0.4
ki: 0.08
k_ext: 0.0
offset_max: 5
stabilization_threshold: 0.1
accumulated_error_threshold: 50
```
and of course, configure the VTherm's self-regulation mode in **Expert** mode. All VTherms in Expert mode will use these same settings.
For the changes to be taken into account, you must either **completely restart Home Assistant** or just the **Versatile Thermostat integration** (Dev tools / Yaml / reloading the configuration / Versatile Thermostat).
### On Time Clamping (max_on_percent)
The calculated on time percent can be limited to a maximum percentage of the cycle duration. This setting has to be made in expert mode and will be used for all Versatile Thermostats.
```
versatile_thermostat:
max_on_percent: 0.8
```
The example above limits the maximum ON time to 80% (0.8) of the cycle length. If the cycle length is for example 600 seconds (10min), the maximum ON time will be limited to 480 seconds (8min). The remaining 120 seconds of the cycle will always remain in the OFF state.
There are three debug attributes of interest regarding this feature:
* `max_on_percent` # clamping setting as configured in expert mode
* `calculated_on_percent` # calculated on percent without clamping applied
* `on_percent` # used on percent with clamping applied
<details>
<summary>Parameter summary</summary>
## Parameter summary ## Parameter summary
| Parameter | Description | "over switch" | "over climate" | "over valve" | "central configuration" | | Parameter | Description | "over switch" | "over climate" | "over valve" | "central configuration" |
@@ -728,13 +868,6 @@ context:
| ``ac_mode`` | Use the Air Conditioning (AC) mode | X | X | X | - | | ``ac_mode`` | Use the Air Conditioning (AC) mode | X | X | X | - |
| ``tpi_coef_int`` | Coefficient to use for internal temperature delta | X | - | X | X | | ``tpi_coef_int`` | Coefficient to use for internal temperature delta | X | - | X | X |
| ``tpi_coef_ext`` | Coefficient to use for external temperature delta | X | - | X | X | | ``tpi_coef_ext`` | Coefficient to use for external temperature delta | X | - | X | X |
| ``frost_temp`` | Temperature in frost protection preset | X | X | X | X |
| ``eco_temp`` | Temperature in Eco preset | X | X | X | X |
| ``comfort_temp`` | Temperature in Comfort preset | X | X | X | X |
| ``boost_temp`` | Temperature in Boost preset | X | X | X | X |
| ``eco_ac_temp`` | Temperature in Eco preset for AC mode | X | X | X | X |
| ``comfort_ac_temp`` | Temperature in Comfort preset for AC mode | X | X | X | X |
| ``boost_ac_temp`` | Temperature in Boost preset for AC mode | X | X | X | X |
| ``window_sensor_entity_id`` | Window sensor entity id | X | X | X | - | | ``window_sensor_entity_id`` | Window sensor entity id | X | X | X | - |
| ``window_delay`` | Window sensor delay (seconds) | X | X | X | X | | ``window_delay`` | Window sensor delay (seconds) | X | X | X | X |
| ``window_auto_open_threshold`` | Temperature decrease threshold for automatic window open detection (in °/min) | X | X | X | X | | ``window_auto_open_threshold`` | Temperature decrease threshold for automatic window open detection (in °/min) | X | X | X | X |
@@ -749,13 +882,6 @@ context:
| ``max_power_sensor_entity_id`` | Max power sensor entity id | X | X | X | X | | ``max_power_sensor_entity_id`` | Max power sensor entity id | X | X | X | X |
| ``power_temp`` | Temperature for Power shedding | X | X | X | X | | ``power_temp`` | Temperature for Power shedding | X | X | X | X |
| ``presence_sensor_entity_id`` | Presence sensor entity id | X | X | X | X | | ``presence_sensor_entity_id`` | Presence sensor entity id | X | X | X | X |
| ``frost_away_temp`` | Temperature in Frost protection preset when no presence | X | X | X | X |
| ``eco_away_temp`` | Temperature in Eco preset when no presence | X | X | X | X |
| ``comfort_away_temp`` | Temperature in Comfort preset when no presence | X | X | X | X |
| ``boost_away_temp`` | Temperature in Boost preset when no presence | X | X | X | X |
| ``eco_ac_away_temp`` | Temperature in Eco preset when no presence in AC mode | X | X | X | X |
| ``comfort_ac_away_temp`` | Temperature in Comfort preset when no presence in AC mode | X | X | X | X |
| ``boost_ac_away_temp`` | Temperature in Boost preset when no presence in AC mode | X | X | X | X |
| ``minimal_activation_delay`` | Minimal activation delay | X | - | X | X | | ``minimal_activation_delay`` | Minimal activation delay | X | - | X | X |
| ``security_delay_min`` | Safety delay (in minutes) | X | - | X | X | | ``security_delay_min`` | Safety delay (in minutes) | X | - | X | X |
| ``security_min_on_percent`` | Minimal power percent to enable safety mode | X | - | X | X | | ``security_min_on_percent`` | Minimal power percent to enable safety mode | X | - | X | X |
@@ -766,13 +892,15 @@ context:
| ``inverse_switch_command`` | Inverse the switch command (for pilot wire switch) | X | - | - | - | | ``inverse_switch_command`` | Inverse the switch command (for pilot wire switch) | X | - | - | - |
| ``auto_fan_mode`` | Auto fan mode | - | X | - | - | | ``auto_fan_mode`` | Auto fan mode | - | X | - | - |
| ``auto_regulation_use_device_temp`` | Use the internal temperature of the underlying device | - | X | - | - | | ``auto_regulation_use_device_temp`` | Use the internal temperature of the underlying device | - | X | - | - |
| ``add_central_boiler_control`` | Add the control of a central boiler | - | - | - | X | | ``use_central_boiler_feature`` | Add the control of a central boiler | - | - | - | X |
| ``central_boiler_activation_service`` | Activation service of the boiler | - | - | - | X | | ``central_boiler_activation_service`` | Activation service of the boiler | - | - | - | X |
| ``central_boiler_deactivation_service`` | Deactivaiton service of the boiler | - | - | - | X | | ``central_boiler_deactivation_service`` | Deactivaiton service of the boiler | - | - | - | X |
| ``used_by_controls_central_boiler`` | Indicate if the VTherm control the central boiler | X | X | X | - | | ``used_by_controls_central_boiler`` | Indicate if the VTherm control the central boiler | X | X | X | - |
| ``use_auto_start_stop_feature`` | Indique si la fonction de démarrage/extinction automatique est activée | - | X | - | - |
| ``auto_start_stop_lvel`` | Le niveau de détection de l'auto start/stop | - | X | - | - |
</details>
# Tuning examples
# Examples tuning
## Electrical heater ## Electrical heater
- cycle: between 5 and 10 minutes, - cycle: between 5 and 10 minutes,
@@ -1025,6 +1153,9 @@ Custom attributes are the following:
| ``is_controlled_by_central_mode`` | True if the VTherm can be centrally controlled | | ``is_controlled_by_central_mode`` | True if the VTherm can be centrally controlled |
| ``last_central_mode`` | The last central mode used (None if the VTherm is not centrally controlled) | | ``last_central_mode`` | The last central mode used (None if the VTherm is not centrally controlled) |
| ``is_used_by_central_boiler`` | Indicate if the VTherm can control the central boiler | | ``is_used_by_central_boiler`` | Indicate if the VTherm can control the central boiler |
| ``auto_start_stop_enable`` | Indicate if the VTherm is allowed to do auto start and stop |
| ``auto_start_stop_level`` | Give the level of auto start/stop |
| ``hvac_off_reason`` | Give the reason of stop of the VTherm. This could be Window, Auto-start/stop or Manual |
# Some results # Some results
@@ -1148,9 +1279,13 @@ Replace values in [[ ]] by yours.
yaxis: y1 yaxis: y1
name: Ema name: Ema
- entity: '[[climate]]' - entity: '[[climate]]'
attribute: regulated_target_temperature attribute: on_percent
yaxis: y1 yaxis: y2
name: Regulated T° name: Power percent
fill: tozeroy
fillcolor: rgba(200, 10, 10, 0.3)
line:
color: rgba(200, 10, 10, 0.9)
- entity: '[[slope]]' - entity: '[[slope]]'
name: Slope name: Slope
fill: tozeroy fill: tozeroy
@@ -1175,12 +1310,19 @@ Replace values in [[ ]] by yours.
yaxis: yaxis:
visible: true visible: true
position: 0 position: 0
yaxis2:
visible: true
position: 0
fixedrange: true
range:
- 0
- 1
yaxis9: yaxis9:
visible: true visible: true
fixedrange: false fixedrange: false
range: range:
- -0.5 - -2
- 0.5 - 2
position: 1 position: 1
xaxis: xaxis:
rangeselector: rangeselector:
@@ -1203,7 +1345,7 @@ Example of graph obtained with Plotly :
## And always better and better with the NOTIFIER daemon app to notify events ## And always better and better with the NOTIFIER daemon app to notify events
This automation uses the excellent App Daemon named NOTIFIER developed by Horizon Domotique that you will find in demonstration [here](https://www.youtube.com/watch?v=chJylIK0ASo&ab_channel=HorizonDomotique) and the code is [here](https ://github.com/jlpouffier/home-assistant-config/blob/master/appdaemon/apps/notifier.py). It allows you to notify the users of the accommodation when one of the events affecting safety occurs on one of the Versatile Thermostats. This automation uses the excellent App Daemon named NOTIFIER developed by Horizon Domotique that you will find in demonstration [here](https://www.youtube.com/watch?v=chJylIK0ASo&ab_channel=HorizonDomotique) and the code is [here](https://github.com/jlpouffier/home-assistant-config/blob/master/appdaemon/apps/notifier.py). It allows you to notify the users of the accommodation when one of the events affecting safety occurs on one of the Versatile Thermostats.
This is a great example of using the notifications described here [notification](#notifications). This is a great example of using the notifications described here [notification](#notifications).
@@ -1295,7 +1437,11 @@ If you want to contribute to this please read the [Contribution guidelines](CONT
# Troubleshooting # Troubleshooting
<details>
<summary>Using a Heatzy</summary>
## Using a Heatzy ## Using a Heatzy
The use of a Heatzy is possible provided you use a virtual switch on this model: The use of a Heatzy is possible provided you use a virtual switch on this model:
``` ```
- platform:template - platform:template
@@ -1325,6 +1471,11 @@ The use of a Heatzy is possible provided you use a virtual switch on this model:
``` ```
Thanks to @gael for this example. Thanks to @gael for this example.
</details>
<details>
<summary>Using a Heatsink with a Pilot Wire</summary>
## Using a Heatsink with a Pilot Wire ## Using a Heatsink with a Pilot Wire
As with the Heatzy above you can use a virtual switch which will change the preset of your radiator depending on the ignition state of the VTherm. As with the Heatzy above you can use a virtual switch which will change the preset of your radiator depending on the ignition state of the VTherm.
Example : Example :
@@ -1344,10 +1495,56 @@ Example :
entity_id: switch.radiateur_soan entity_id: switch.radiateur_soan
icon_template: "{% if is_state('switch.radiateur_soan', 'on') %}mdi:radiator-disabled{% else %}mdi:radiator{% endif %}" icon_template: "{% if is_state('switch.radiateur_soan', 'on') %}mdi:radiator-disabled{% else %}mdi:radiator{% endif %}"
``` ```
</details>
<details>
<summary>Using a heater with a Nodon</summary>
## Using a heater with a Nodon module
As for the heatzy module above you can use a virtual switch which will change the preset of your heater depending of the state of the VTherm.
Example :
```
- platform: template
switches:
chauffage_chb_parents:
unique_id: chauffage_chb_parents
friendly_name: Chauffage chambre parents
value_template: "{{ is_state('select.fp_chb_parents_pilot_wire_mode', 'comfort') }}"
icon_template: >-
{% if is_state('select.fp_chb_parents_pilot_wire_mode', 'comfort') %}
mdi:radiator
{% elif is_state('select.fp_chb_parents_pilot_wire_mode', 'frost_protection') %}
mdi:snowflake
{% else %}
mdi:radiator-disabled
{% endif %}
turn_on:
service: select.select_option
target:
entity_id: select.fp_chb_parents_pilot_wire_mode
data:
option: comfort
turn_off:
service: select.select_option
target:
entity_id: select.fp_chb_parents_pilot_wire_mode
data:
option: eco
```
</details>
<details>
<summary>Only the first radiator heats</summary>
## Only the first radiator heats ## Only the first radiator heats
In `over_switch` mode if several radiators are configured for the same VTherm, switching on will be done sequentially to smooth out consumption peaks as much as possible. In `over_switch` mode if several radiators are configured for the same VTherm, switching on will be done sequentially to smooth out consumption peaks as much as possible.
This is completely normal and desired. It is described here: [For a thermostat of type ``thermostat_over_switch```](#for-a-thermostat-of-type-thermostat_over_switch)v This is completely normal and desired. It is described here: [For a thermostat of type ``thermostat_over_switch```](#for-a-thermostat-of-type-thermostat_over_switch)v
</details>
<details>
<summary>The radiator heats up even though the setpoint temperature is exceeded or does not heat up even though the room temperature is well below the setpoint</summary>
## The radiator heats up even though the setpoint temperature is exceeded or does not heat up even though the room temperature is well below the setpoint ## The radiator heats up even though the setpoint temperature is exceeded or does not heat up even though the room temperature is well below the setpoint
@@ -1360,6 +1557,10 @@ With an `over_climate` type VTherm, the regulation is done by the underlying `cl
Example of discussion around these topics: [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348), [#316](https://github.com/jmcollin78/versatile_thermostat/issues/316), [#312](https://github.com/jmcollin78/versatile_thermostat/discussions/312 ), [#278](https://github.com/jmcollin78/versatile_thermostat/discussions/278) Example of discussion around these topics: [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348), [#316](https://github.com/jmcollin78/versatile_thermostat/issues/316), [#312](https://github.com/jmcollin78/versatile_thermostat/discussions/312 ), [#278](https://github.com/jmcollin78/versatile_thermostat/discussions/278)
To get around this, VTherm is equipped with a function called self-regulation which allows the instruction sent to the underlying to be adapted until the target temperature is respected. This function compensates for the measurement bias of internal thermometers. If the bias is important the regulation must be important. See [Self-regulation](#self-regulation) to configure self-regulation. To get around this, VTherm is equipped with a function called self-regulation which allows the instruction sent to the underlying to be adapted until the target temperature is respected. This function compensates for the measurement bias of internal thermometers. If the bias is important the regulation must be important. See [Self-regulation](#self-regulation) to configure self-regulation.
</details>
<details>
<summary>Adjust window opening detection parameters in auto mode</summary>
## Adjust window opening detection parameters in auto mode ## Adjust window opening detection parameters in auto mode
@@ -1380,9 +1581,14 @@ versatile_thermostat:
``` ```
These parameters are sensitive and quite difficult to adjust. Please only use them if you know what you are doing and your temperature measurements are not already smooth. These parameters are sensitive and quite difficult to adjust. Please only use them if you know what you are doing and your temperature measurements are not already smooth.
</details>
<details>
<summary>Why does my Versatile Thermostat go into Safety?</summary>
## Why does my Versatile Thermostat go into Safety? ## Why does my Versatile Thermostat go into Safety?
Safety mode is only possible on VTherm `over_switch` and `over_valve`. It occurs when one of the 2 thermometers which gives the room temperature or the outside temperature has not sent a value for more than `security_delay_min` minutes and the radiator was heating at least `security_min_on_percent`.
Safety mode is possible on all VTherm's type. It occurs when one of the 2 thermometers which gives the room temperature or the outside temperature has not sent a value for more than `security_delay_min` minutes and the radiator was heating at least `security_min_on_percent`.
As the algorithm is based on temperature measurements, if they are no longer received by the VTherm, there is a risk of overheating and fire. To avoid this, when the conditions mentioned above are detected, heating is limited to the `security_default_on_percent` parameter. This value must therefore be reasonably low. It helps prevent a fire while avoiding completely cutting off the radiator (risk of freezing). As the algorithm is based on temperature measurements, if they are no longer received by the VTherm, there is a risk of overheating and fire. To avoid this, when the conditions mentioned above are detected, heating is limited to the `security_default_on_percent` parameter. This value must therefore be reasonably low. It helps prevent a fire while avoiding completely cutting off the radiator (risk of freezing).
@@ -1427,8 +1633,13 @@ This will depend on the cause of the problem:
2. If the `security_delay_min` parameter is too small, it risks generating a lot of false alerts. A correct value is around 60 min, especially if you have battery-powered temperature sensors. 2. If the `security_delay_min` parameter is too small, it risks generating a lot of false alerts. A correct value is around 60 min, especially if you have battery-powered temperature sensors.
3. Some temperature sensors do not send a measurement if the temperature has not changed. So in the event of a very stable temperature for a long time, the safety mode may be triggered. This is not very serious since it is removed as soon as the VTherm receives a temperature again. On certain thermometers (TuYA for example), you can force the maximum delay between 2 measurements. It will be appropriate to set a max delay < `security_delay_min`, 3. Some temperature sensors do not send a measurement if the temperature has not changed. So in the event of a very stable temperature for a long time, the safety mode may be triggered. This is not very serious since it is removed as soon as the VTherm receives a temperature again. On certain thermometers (TuYA for example), you can force the maximum delay between 2 measurements. It will be appropriate to set a max delay < `security_delay_min`,
4. As soon as the temperature is received again the safety mode will be removed and the previous values of preset, target temperature and mode will be restored. 4. As soon as the temperature is received again the safety mode will be removed and the previous values of preset, target temperature and mode will be restored.
</details>
<details>
<summary>Using a group of people as a presence sensor</summary>
## Using a group of people as a presence sensor ## Using a group of people as a presence sensor
Unfortunately, groups of people are not recognized as presence sensors. We cannot therefore use them directly in VTherm. Unfortunately, groups of people are not recognized as presence sensors. We cannot therefore use them directly in VTherm.
The workaround is to create a binary_sensor template with the following code: The workaround is to create a binary_sensor template with the following code:
@@ -1449,6 +1660,10 @@ You will note in this example, the use of an input_boolean named force_presence
template: !include templates.yaml template: !include templates.yaml
... ...
``` ```
</details>
<details>
<summary>Enable Versatile Thermostat logs</summary>
## Enable Versatile Thermostat logs ## Enable Versatile Thermostat logs
Sometimes you will need to enable logs to refine the analyses. To do this, edit the `logger.yaml` file of your configuration and configure the logs as follows: Sometimes you will need to enable logs to refine the analyses. To do this, edit the `logger.yaml` file of your configuration and configure the logs as follows:
@@ -1458,6 +1673,8 @@ logs:
custom_components.versatile_thermostat: info custom_components.versatile_thermostat: info
``` ```
You must reload the yaml configuration (Dev Tools / Yaml / All Yaml configuration) or restart Home Assistant for this change to take effect. You must reload the yaml configuration (Dev Tools / Yaml / All Yaml configuration) or restart Home Assistant for this change to take effect.
</details>
*** ***

View File

@@ -1,4 +1,5 @@
"""The Versatile Thermostat integration.""" """The Versatile Thermostat integration."""
from __future__ import annotations from __future__ import annotations
from typing import Dict from typing import Dict
@@ -12,12 +13,15 @@ from homeassistant.const import SERVICE_RELOAD, EVENT_HOMEASSISTANT_STARTED
from homeassistant.config_entries import ConfigEntry, ConfigType from homeassistant.config_entries import ConfigEntry, ConfigType
from homeassistant.core import HomeAssistant, CoreState, callback from homeassistant.core import HomeAssistant, CoreState, callback
from homeassistant.helpers.service import async_register_admin_service
from .base_thermostat import BaseThermostat from .base_thermostat import BaseThermostat
from .const import ( from .const import (
DOMAIN, DOMAIN,
PLATFORMS, PLATFORMS,
CONFIG_VERSION,
CONFIG_MINOR_VERSION,
CONF_AUTO_REGULATION_LIGHT, CONF_AUTO_REGULATION_LIGHT,
CONF_AUTO_REGULATION_MEDIUM, CONF_AUTO_REGULATION_MEDIUM,
CONF_AUTO_REGULATION_STRONG, CONF_AUTO_REGULATION_STRONG,
@@ -27,6 +31,30 @@ from .const import (
CONF_SAFETY_MODE, CONF_SAFETY_MODE,
CONF_THERMOSTAT_CENTRAL_CONFIG, CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_TYPE,
CONF_USE_WINDOW_FEATURE,
CONF_USE_MOTION_FEATURE,
CONF_USE_PRESENCE_FEATURE,
CONF_USE_POWER_FEATURE,
CONF_USE_CENTRAL_BOILER_FEATURE,
CONF_POWER_SENSOR,
CONF_PRESENCE_SENSOR,
CONF_UNDERLYING_LIST,
CONF_HEATER,
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
CONF_VALVE,
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_VALVE,
CONF_MAX_ON_PERCENT,
) )
from .vtherm_api import VersatileThermostatAPI from .vtherm_api import VersatileThermostatAPI
@@ -59,6 +87,7 @@ CONFIG_SCHEMA = vol.Schema(
CONF_AUTO_REGULATION_EXPERT: vol.Schema(SELF_REGULATION_PARAM_SCHEMA), CONF_AUTO_REGULATION_EXPERT: vol.Schema(SELF_REGULATION_PARAM_SCHEMA),
CONF_SHORT_EMA_PARAMS: vol.Schema(EMA_PARAM_SCHEMA), CONF_SHORT_EMA_PARAMS: vol.Schema(EMA_PARAM_SCHEMA),
CONF_SAFETY_MODE: vol.Schema(SAFETY_MODE_PARAM_SCHEMA), CONF_SAFETY_MODE: vol.Schema(SAFETY_MODE_PARAM_SCHEMA),
vol.Optional(CONF_MAX_ON_PERCENT): vol.Coerce(float),
} }
), ),
}, },
@@ -97,13 +126,16 @@ async def async_setup(
"VersatileThermostat - HA is started, initialize all links between VTherm entities" "VersatileThermostat - HA is started, initialize all links between VTherm entities"
) )
await api.init_vtherm_links() await api.init_vtherm_links()
await api.notify_central_mode_change()
await api.reload_central_boiler_entities_list()
if hass.state == CoreState.running: if hass.state == CoreState.running:
await _async_startup_internal() await _async_startup_internal()
else: else:
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _async_startup_internal) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _async_startup_internal)
hass.helpers.service.async_register_admin_service( async_register_admin_service(
hass,
DOMAIN, DOMAIN,
SERVICE_RELOAD, SERVICE_RELOAD,
_handle_reload, _handle_reload,
@@ -146,14 +178,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
await api.reload_central_boiler_entities_list() if hass.state == CoreState.running:
await api.init_vtherm_links() await api.reload_central_boiler_entities_list()
await api.init_vtherm_links(entry.entry_id)
return True return True
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None: async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update listener.""" """Update listener."""
_LOGGER.debug(
"Calling update_listener entry: entry_id='%s', value='%s'",
entry.entry_id,
entry.data,
)
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CENTRAL_CONFIG: if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CENTRAL_CONFIG:
await reload_all_vtherm(hass) await reload_all_vtherm(hass)
else: else:
@@ -162,7 +202,7 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass) api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(hass)
if api is not None: if api is not None:
await api.reload_central_boiler_entities_list() await api.reload_central_boiler_entities_list()
await api.init_vtherm_links() await api.init_vtherm_links(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@@ -180,15 +220,83 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Example migration function # Example migration function
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry): async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"""Migrate old entry.""" """Migrate old entry."""
_LOGGER.debug("Migrating from version %s", config_entry.version) _LOGGER.debug(
"Migrating from version %s/%s", config_entry.version, config_entry.minor_version
)
if config_entry.version == 1: if (
config_entry.version != CONFIG_VERSION
or config_entry.minor_version != CONFIG_MINOR_VERSION
):
_LOGGER.debug(
"Migration to %s/%s is needed", CONFIG_VERSION, CONFIG_MINOR_VERSION
)
new = {**config_entry.data} new = {**config_entry.data}
# TO DO: modify Config Entry data if there will be something to migrate
config_entry.version = 2 thermostat_type = config_entry.data.get(CONF_THERMOSTAT_TYPE)
hass.config_entries.async_update_entry(config_entry, data=new)
_LOGGER.info("Migration to version %s successful", config_entry.version) if thermostat_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
new[CONF_USE_WINDOW_FEATURE] = True
new[CONF_USE_MOTION_FEATURE] = True
new[CONF_USE_POWER_FEATURE] = new.get(CONF_POWER_SENSOR, None) is not None
new[CONF_USE_PRESENCE_FEATURE] = (
new.get(CONF_PRESENCE_SENSOR, None) is not None
)
new[CONF_USE_CENTRAL_BOILER_FEATURE] = new.get(
"add_central_boiler_control", False
) or new.get(CONF_USE_CENTRAL_BOILER_FEATURE, False)
if config_entry.data.get(CONF_UNDERLYING_LIST, None) is None:
underlying_list = []
if thermostat_type == CONF_THERMOSTAT_SWITCH:
underlying_list = [
config_entry.data.get(CONF_HEATER, None),
config_entry.data.get(CONF_HEATER_2, None),
config_entry.data.get(CONF_HEATER_3, None),
config_entry.data.get(CONF_HEATER_4, None),
]
elif thermostat_type == CONF_THERMOSTAT_CLIMATE:
underlying_list = [
config_entry.data.get(CONF_CLIMATE, None),
config_entry.data.get(CONF_CLIMATE_2, None),
config_entry.data.get(CONF_CLIMATE_3, None),
config_entry.data.get(CONF_CLIMATE_4, None),
]
elif thermostat_type == CONF_THERMOSTAT_VALVE:
underlying_list = [
config_entry.data.get(CONF_VALVE, None),
config_entry.data.get(CONF_VALVE_2, None),
config_entry.data.get(CONF_VALVE_3, None),
config_entry.data.get(CONF_VALVE_4, None),
]
new[CONF_UNDERLYING_LIST] = [
entity for entity in underlying_list if entity is not None
]
for key in [
CONF_HEATER,
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
CONF_VALVE,
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
]:
new.pop(key, None)
hass.config_entries.async_update_entry(
config_entry,
data=new,
version=CONFIG_VERSION,
minor_version=CONFIG_MINOR_VERSION,
)
_LOGGER.info("Migration to version %s successful", config_entry.version)
return True return True

View File

@@ -0,0 +1,239 @@
# pylint: disable=line-too-long
""" This file implements the Auto start/stop algorithm as described here: https://github.com/jmcollin78/versatile_thermostat/issues/585
"""
import logging
from datetime import datetime
from typing import Literal
from homeassistant.components.climate import HVACMode
from .const import (
AUTO_START_STOP_LEVEL_NONE,
AUTO_START_STOP_LEVEL_FAST,
AUTO_START_STOP_LEVEL_MEDIUM,
AUTO_START_STOP_LEVEL_SLOW,
TYPE_AUTO_START_STOP_LEVELS,
)
_LOGGER = logging.getLogger(__name__)
# Some constant to make algorithm depending of level
DT_MIN = {
AUTO_START_STOP_LEVEL_NONE: 0, # Not used
AUTO_START_STOP_LEVEL_SLOW: 30,
AUTO_START_STOP_LEVEL_MEDIUM: 15,
AUTO_START_STOP_LEVEL_FAST: 7,
}
# the measurement cycle (2 min)
CYCLE_SEC = 120
# A temp hysteresis to avoid rapid OFF/ON
TEMP_HYSTERESIS = 0.5
ERROR_THRESHOLD = {
AUTO_START_STOP_LEVEL_NONE: 0, # Not used
AUTO_START_STOP_LEVEL_SLOW: 10, # 10 cycle above 1° or 5 cycle above 2°, ...
AUTO_START_STOP_LEVEL_MEDIUM: 5, # 5 cycle above 1° or 3 cycle above 2°, ..., 1 cycle above 5°
AUTO_START_STOP_LEVEL_FAST: 2, # 2 cycle above 1° or 1 cycle above 2°
}
AUTO_START_STOP_ACTION_OFF = "turnOff"
AUTO_START_STOP_ACTION_ON = "turnOn"
AUTO_START_STOP_ACTION_NOTHING = "nothing"
AUTO_START_STOP_ACTIONS = Literal[ # pylint: disable=invalid-name
AUTO_START_STOP_ACTION_OFF,
AUTO_START_STOP_ACTION_ON,
AUTO_START_STOP_ACTION_NOTHING,
]
class AutoStartStopDetectionAlgorithm:
"""The class that implements the algorithm listed above"""
_dt: float | None = None
_level: str = AUTO_START_STOP_LEVEL_NONE
_accumulated_error: float = 0
_error_threshold: float | None = None
_last_calculation_date: datetime | None = None
def __init__(self, level: TYPE_AUTO_START_STOP_LEVELS, vtherm_name) -> None:
"""Initalize a new algorithm with the right constants"""
self._vtherm_name = vtherm_name
self._init_level(level)
def _init_level(self, level: TYPE_AUTO_START_STOP_LEVELS):
"""Initialize a new level"""
if level == self._level:
return
self._level = level
if self._level != AUTO_START_STOP_LEVEL_NONE:
self._dt = DT_MIN[level]
self._error_threshold = ERROR_THRESHOLD[level]
# reset accumulated error if we change the level
self._accumulated_error = 0
def calculate_action(
self,
hvac_mode: HVACMode | None,
saved_hvac_mode: HVACMode | None,
target_temp: float,
current_temp: float,
slope_min: float | None,
now: datetime,
) -> AUTO_START_STOP_ACTIONS:
"""Calculate an eventual action to do depending of the value in parameter"""
if self._level == AUTO_START_STOP_LEVEL_NONE:
_LOGGER.debug(
"%s - auto-start/stop is disabled",
self,
)
return AUTO_START_STOP_ACTION_NOTHING
_LOGGER.debug(
"%s - calculate_action: hvac_mode=%s, saved_hvac_mode=%s, target_temp=%s, current_temp=%s, slope_min=%s at %s",
self,
hvac_mode,
saved_hvac_mode,
target_temp,
current_temp,
slope_min,
now,
)
if hvac_mode is None or target_temp is None or current_temp is None:
_LOGGER.debug(
"%s - No all mandatory parameters are set. Disable auto-start/stop",
self,
)
return AUTO_START_STOP_ACTION_NOTHING
# Calculate the error factor (P)
error = target_temp - current_temp
# reduce the error considering the dt between the last measurement
if self._last_calculation_date is not None:
dtmin = (now - self._last_calculation_date).total_seconds() / CYCLE_SEC
# ignore two calls too near (< 24 sec)
if dtmin <= 0.2:
_LOGGER.debug(
"%s - new calculation of auto_start_stop (%s) is too near of the last one (%s). Forget it",
self,
now,
self._last_calculation_date,
)
return AUTO_START_STOP_ACTION_NOTHING
error = error * dtmin
# If the error have change its sign, reset smoothly the accumulated error
if error * self._accumulated_error < 0:
self._accumulated_error = self._accumulated_error / 2.0
self._accumulated_error += error
# Capping of the error
self._accumulated_error = min(
self._error_threshold,
max(-self._error_threshold, self._accumulated_error),
)
self._last_calculation_date = now
temp_at_dt = current_temp + slope_min * self._dt
# Check to turn-off
# When we hit the threshold, that mean we can turn off
if hvac_mode == HVACMode.HEAT:
if (
self._accumulated_error <= -self._error_threshold
and temp_at_dt >= target_temp + TEMP_HYSTERESIS
):
_LOGGER.info(
"%s - We need to stop, there is no need for heating for a long time.",
self,
)
return AUTO_START_STOP_ACTION_OFF
else:
_LOGGER.debug("%s - nothing to do, we are heating", self)
return AUTO_START_STOP_ACTION_NOTHING
if hvac_mode == HVACMode.COOL:
if (
self._accumulated_error >= self._error_threshold
and temp_at_dt <= target_temp - TEMP_HYSTERESIS
):
_LOGGER.info(
"%s - We need to stop, there is no need for cooling for a long time.",
self,
)
return AUTO_START_STOP_ACTION_OFF
else:
_LOGGER.debug(
"%s - nothing to do, we are cooling",
self,
)
return AUTO_START_STOP_ACTION_NOTHING
# check to turn on
if hvac_mode == HVACMode.OFF and saved_hvac_mode == HVACMode.HEAT:
if temp_at_dt <= target_temp - TEMP_HYSTERESIS:
_LOGGER.info(
"%s - We need to start, because it will be time to heat",
self,
)
return AUTO_START_STOP_ACTION_ON
else:
_LOGGER.debug(
"%s - nothing to do, we don't need to heat soon",
self,
)
return AUTO_START_STOP_ACTION_NOTHING
if hvac_mode == HVACMode.OFF and saved_hvac_mode == HVACMode.COOL:
if temp_at_dt >= target_temp + TEMP_HYSTERESIS:
_LOGGER.info(
"%s - We need to start, because it will be time to cool",
self,
)
return AUTO_START_STOP_ACTION_ON
else:
_LOGGER.debug(
"%s - nothing to do, we don't need to cool soon",
self,
)
return AUTO_START_STOP_ACTION_NOTHING
_LOGGER.debug(
"%s - nothing to do, no conditions applied",
self,
)
return AUTO_START_STOP_ACTION_NOTHING
def set_level(self, level: TYPE_AUTO_START_STOP_LEVELS):
"""Set a new level"""
self._init_level(level)
@property
def dt_min(self) -> float:
"""Get the dt value"""
return self._dt
@property
def accumulated_error(self) -> float:
"""Get the accumulated error value"""
return self._accumulated_error
@property
def accumulated_error_threshold(self) -> float:
"""Get the accumulated error threshold value"""
return self._error_threshold
@property
def level(self) -> TYPE_AUTO_START_STOP_LEVELS:
"""Get the level value"""
return self._level
def __str__(self) -> str:
return f"AutoStartStopDetectionAlgorithm-{self._vtherm_name}"

File diff suppressed because it is too large Load Diff

View File

@@ -39,6 +39,7 @@ from .const import (
CONF_USE_WINDOW_FEATURE, CONF_USE_WINDOW_FEATURE,
CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_CENTRAL_CONFIG, CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_USE_CENTRAL_BOILER_FEATURE,
CONF_CENTRAL_BOILER_ACTIVATION_SRV, CONF_CENTRAL_BOILER_ACTIVATION_SRV,
CONF_CENTRAL_BOILER_DEACTIVATION_SRV, CONF_CENTRAL_BOILER_DEACTIVATION_SRV,
overrides, overrides,
@@ -63,10 +64,13 @@ async def async_setup_entry(
name = entry.data.get(CONF_NAME) name = entry.data.get(CONF_NAME)
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE) vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
entities = None
if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG: if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
entities = [ if entry.data.get(CONF_USE_CENTRAL_BOILER_FEATURE):
CentralBoilerBinarySensor(hass, unique_id, name, entry.data), entities = [
] CentralBoilerBinarySensor(hass, unique_id, name, entry.data),
]
else: else:
entities = [ entities = [
SecurityBinarySensor(hass, unique_id, name, entry.data), SecurityBinarySensor(hass, unique_id, name, entry.data),
@@ -81,7 +85,8 @@ async def async_setup_entry(
if entry.data.get(CONF_USE_POWER_FEATURE): if entry.data.get(CONF_USE_POWER_FEATURE):
entities.append(OverpoweringBinarySensor(hass, unique_id, name, entry.data)) entities.append(OverpoweringBinarySensor(hass, unique_id, name, entry.data))
async_add_entities(entities, True) if entities:
async_add_entities(entities, True)
class SecurityBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity): class SecurityBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
@@ -95,7 +100,7 @@ class SecurityBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
entry_infos, entry_infos,
) -> None: ) -> None:
"""Initialize the SecurityState Binary sensor""" """Initialize the SecurityState Binary sensor"""
super().__init__(hass, unique_id, entry_infos.get(CONF_NAME)) super().__init__(hass, unique_id, name)
self._attr_name = "Security state" self._attr_name = "Security state"
self._attr_unique_id = f"{self._device_name}_security_state" self._attr_unique_id = f"{self._device_name}_security_state"
self._attr_is_on = False self._attr_is_on = False
@@ -103,7 +108,7 @@ class SecurityBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on old_state = self._attr_is_on
self._attr_is_on = self.my_climate.security_state is True self._attr_is_on = self.my_climate.security_state is True
@@ -142,7 +147,7 @@ class OverpoweringBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on 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 True
@@ -181,7 +186,7 @@ class WindowBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on old_state = self._attr_is_on
# Issue 120 - only take defined presence value # Issue 120 - only take defined presence value
@@ -231,7 +236,7 @@ class MotionBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on old_state = self._attr_is_on
# Issue 120 - only take defined presence value # Issue 120 - only take defined presence value
if self.my_climate.motion_state in [STATE_ON, STATE_OFF]: if self.my_climate.motion_state in [STATE_ON, STATE_OFF]:
@@ -272,7 +277,7 @@ class PresenceBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity):
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on old_state = self._attr_is_on
# Issue 120 - only take defined presence value # Issue 120 - only take defined presence value
if self.my_climate.presence_state in [STATE_ON, STATE_OFF]: if self.my_climate.presence_state in [STATE_ON, STATE_OFF]:
@@ -312,7 +317,7 @@ class WindowByPassBinarySensor(VersatileThermostatBaseEntity, BinarySensorEntity
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_is_on old_state = self._attr_is_on
if self.my_climate.window_bypass_state in [True, False]: if self.my_climate.window_bypass_state in [True, False]:
self._attr_is_on = self.my_climate.window_bypass_state self._attr_is_on = self.my_climate.window_bypass_state

View File

@@ -22,26 +22,12 @@ from homeassistant.const import (
STATE_NOT_HOME, STATE_NOT_HOME,
) )
from .const import ( from .const import * # pylint: disable=wildcard-import,unused-wildcard-import
DOMAIN,
PLATFORMS,
CONF_PRESETS_WITH_AC,
SERVICE_SET_PRESENCE,
SERVICE_SET_PRESET_TEMPERATURE,
SERVICE_SET_SECURITY,
SERVICE_SET_WINDOW_BYPASS,
SERVICE_SET_AUTO_REGULATION_MODE,
SERVICE_SET_AUTO_FAN_MODE,
CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_VALVE,
CONF_THERMOSTAT_CENTRAL_CONFIG,
)
from .thermostat_switch import ThermostatOverSwitch from .thermostat_switch import ThermostatOverSwitch
from .thermostat_climate import ThermostatOverClimate from .thermostat_climate import ThermostatOverClimate
from .thermostat_valve import ThermostatOverValve from .thermostat_valve import ThermostatOverValve
from .thermostat_climate_valve import ThermostatOverClimateValve
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -60,6 +46,9 @@ async def async_setup_entry(
unique_id = entry.entry_id unique_id = entry.entry_id
name = entry.data.get(CONF_NAME) name = entry.data.get(CONF_NAME)
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE) vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
have_valve_regulation = (
entry.data.get(CONF_AUTO_REGULATION_MODE) == CONF_AUTO_REGULATION_VALVE
)
if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG: if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
return return
@@ -69,9 +58,19 @@ async def async_setup_entry(
if vt_type == CONF_THERMOSTAT_SWITCH: if vt_type == CONF_THERMOSTAT_SWITCH:
entity = ThermostatOverSwitch(hass, unique_id, name, entry.data) entity = ThermostatOverSwitch(hass, unique_id, name, entry.data)
elif vt_type == CONF_THERMOSTAT_CLIMATE: elif vt_type == CONF_THERMOSTAT_CLIMATE:
entity = ThermostatOverClimate(hass, unique_id, name, entry.data) if have_valve_regulation is True:
entity = ThermostatOverClimateValve(hass, unique_id, name, entry.data)
else:
entity = ThermostatOverClimate(hass, unique_id, name, entry.data)
elif vt_type == CONF_THERMOSTAT_VALVE: elif vt_type == CONF_THERMOSTAT_VALVE:
entity = ThermostatOverValve(hass, unique_id, name, entry.data) entity = ThermostatOverValve(hass, unique_id, name, entry.data)
else:
_LOGGER.error(
"Cannot create Versatile Thermostat name=%s of type %s which is unknown",
name,
vt_type,
)
return
async_add_entities([entity], True) async_add_entities([entity], True)

View File

@@ -3,39 +3,20 @@
# pylint: disable=line-too-long # pylint: disable=line-too-long
import logging import logging
from datetime import timedelta, datetime from datetime import timedelta
from homeassistant.core import HomeAssistant, callback, Event from homeassistant.core import HomeAssistant, callback, Event
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.helpers.event import async_track_state_change_event, async_call_later from homeassistant.helpers.event import async_track_state_change_event, async_call_later
from homeassistant.util import dt as dt_util
from .base_thermostat import BaseThermostat from .base_thermostat import BaseThermostat
from .const import DOMAIN, DEVICE_MANUFACTURER, ServiceConfigurationError from .const import DOMAIN, DEVICE_MANUFACTURER, ServiceConfigurationError
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def get_tz(hass: HomeAssistant):
"""Get the current timezone"""
return dt_util.get_time_zone(hass.config.time_zone)
class NowClass:
"""For testing purpose only"""
@staticmethod
def get_now(hass: HomeAssistant) -> datetime:
"""A test function to get the now.
For testing purpose this method can be overriden to get a specific
timestamp.
"""
return datetime.now(get_tz(hass))
def round_to_nearest(n: float, x: float) -> float: def round_to_nearest(n: float, x: float) -> float:
"""Round a number to the nearest x (which should be decimal but not null) """Round a number to the nearest x (which should be decimal but not null)
Example: Example:
@@ -235,7 +216,7 @@ class VersatileThermostatBaseEntity(Entity):
) )
) )
else: else:
_LOGGER.warning("%s - no entity to listen. Try later", self) _LOGGER.debug("%s - no entity to listen. Try later", self)
self._cancel_call = async_call_later( self._cancel_call = async_call_later(
self.hass, timedelta(seconds=1), try_find_climate self.hass, timedelta(seconds=1), try_find_climate
) )

View File

@@ -29,27 +29,6 @@ COMES_FROM = "comes_from"
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# Not used but can be useful in other context
# def schema_defaults(schema, **defaults):
# """Create a new schema with default values filled in."""
# copy = schema.extend({})
# for field, field_type in copy.schema.items():
# if isinstance(field_type, vol.In):
# value = None
#
# if value in field_type.container:
# # field.default = vol.default_factory(value)
# field.description = {"suggested_value": value}
# continue
#
# if field.schema in defaults:
# # field.default = vol.default_factory(defaults[field])
# field.description = {"suggested_value": defaults[field]}
# return copy
#
def add_suggested_values_to_schema( def add_suggested_values_to_schema(
data_schema: vol.Schema, suggested_values: Mapping[str, Any] data_schema: vol.Schema, suggested_values: Mapping[str, Any]
) -> vol.Schema: ) -> vol.Schema:
@@ -74,7 +53,9 @@ def add_suggested_values_to_schema(
class VersatileThermostatBaseConfigFlow(FlowHandler): class VersatileThermostatBaseConfigFlow(FlowHandler):
"""The base Config flow class. Used to put some code in commons.""" """The base Config flow class. Used to put some code in commons."""
VERSION = 1 VERSION = CONFIG_VERSION
MINOR_VERSION = CONFIG_MINOR_VERSION
_infos: dict _infos: dict
_placeholders = { _placeholders = {
CONF_NAME: "", CONF_NAME: "",
@@ -97,21 +78,38 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
def _init_feature_flags(self, _): def _init_feature_flags(self, _):
"""Fix features selection depending to infos""" """Fix features selection depending to infos"""
is_empty: bool = False # TODO remove this not bool(infos) is_central_config = (
self._infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CENTRAL_CONFIG
)
self._infos[CONF_USE_WINDOW_FEATURE] = ( self._infos[CONF_USE_WINDOW_FEATURE] = (
is_empty self._infos.get(CONF_USE_WINDOW_CENTRAL_CONFIG)
or self._infos.get(CONF_WINDOW_SENSOR) is not None or self._infos.get(CONF_WINDOW_SENSOR) is not None
or self._infos.get(CONF_WINDOW_AUTO_OPEN_THRESHOLD) is not None or self._infos.get(CONF_WINDOW_AUTO_OPEN_THRESHOLD) is not None
) )
self._infos[CONF_USE_MOTION_FEATURE] = ( self._infos[CONF_USE_MOTION_FEATURE] = self._infos.get(
is_empty or self._infos.get(CONF_MOTION_SENSOR) is not None 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] = is_empty or (
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 self._infos.get(CONF_POWER_SENSOR) is not None
and self._infos.get(CONF_MAX_POWER_SENSOR) is not None and self._infos.get(CONF_MAX_POWER_SENSOR) is not None
) )
self._infos[CONF_USE_PRESENCE_FEATURE] = ( self._infos[CONF_USE_PRESENCE_FEATURE] = (
is_empty or self._infos.get(CONF_PRESENCE_SENSOR) is not None self._infos.get(CONF_USE_PRESENCE_CENTRAL_CONFIG, False)
or self._infos.get(CONF_PRESENCE_SENSOR) is not None
)
self._infos[CONF_USE_CENTRAL_BOILER_FEATURE] = (
self._infos.get(CONF_CENTRAL_BOILER_ACTIVATION_SRV) is not None
and self._infos.get(CONF_CENTRAL_BOILER_DEACTIVATION_SRV) is not None
)
self._infos[CONF_USE_AUTO_START_STOP_FEATURE] = (
self._infos.get(CONF_USE_AUTO_START_STOP_FEATURE, False) is True
and self._infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE
) )
def _init_central_config_flags(self, infos): def _init_central_config_flags(self, infos):
@@ -126,19 +124,59 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
CONF_USE_PRESETS_CENTRAL_CONFIG, CONF_USE_PRESETS_CENTRAL_CONFIG,
CONF_USE_PRESENCE_CENTRAL_CONFIG, CONF_USE_PRESENCE_CENTRAL_CONFIG,
CONF_USE_ADVANCED_CENTRAL_CONFIG, CONF_USE_ADVANCED_CENTRAL_CONFIG,
CONF_USE_CENTRAL_MODE,
): ):
if not is_empty: if not is_empty:
current_config = self._infos.get(config, None) current_config = self._infos.get(config, None)
self._infos[config] = current_config is True or (
current_config is None and self._central_config is not None self._infos[config] = self._central_config is not None and (
current_config is True or current_config is None
) )
# self._infos[config] = current_config is True or (
# current_config is None and self._central_config is not None
# )
else: else:
self._infos[config] = self._central_config is not None self._infos[config] = self._central_config is not None
if COMES_FROM in self._infos: if COMES_FROM in self._infos:
del self._infos[COMES_FROM] del self._infos[COMES_FROM]
async def validate_input(self, data: dict) -> None: def is_valve_regulation_selected(self, infos) -> bool:
"""True of the valve regulation mode is selected"""
return infos.get(CONF_AUTO_REGULATION_MODE, None) == CONF_AUTO_REGULATION_VALVE
def check_valve_regulation_nb_entities(self, data: dict, step_id=None) -> bool:
"""Check the number of entities for Valve regulation"""
if step_id not in ["type", "valve_regulation", "check_complete"]:
return True
# underlyings_to_check = data if step_id == "type" else self._infos
underlyings_to_check = self._infos # data if step_id == "type" else self._infos
regulation_infos_to_check = (
data if step_id == "valve_regulation" else self._infos
)
ret = True
if self.is_valve_regulation_selected(underlyings_to_check):
nb_unders = len(underlyings_to_check.get(CONF_UNDERLYING_LIST))
nb_offset = len(
regulation_infos_to_check.get(CONF_OFFSET_CALIBRATION_LIST, [])
)
nb_opening = len(
regulation_infos_to_check.get(CONF_OPENING_DEGREE_LIST, [])
)
nb_closing = len(
regulation_infos_to_check.get(CONF_CLOSING_DEGREE_LIST, [])
)
if (
nb_unders != nb_opening
or (nb_unders != nb_offset and nb_offset > 0)
or (nb_unders != nb_closing and nb_closing > 0)
):
ret = False
return ret
async def validate_input(self, data: dict, step_id) -> None:
"""Validate the user input allows us to connect. """Validate the user input allows us to connect.
Data has the keys from STEP_*_DATA_SCHEMA with values provided by the user. Data has the keys from STEP_*_DATA_SCHEMA with values provided by the user.
@@ -146,7 +184,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
# check the heater_entity_id # check the heater_entity_id
for conf in [ for conf in [
CONF_HEATER, CONF_UNDERLYING_LIST,
CONF_TEMP_SENSOR, CONF_TEMP_SENSOR,
CONF_EXTERNAL_TEMP_SENSOR, CONF_EXTERNAL_TEMP_SENSOR,
CONF_WINDOW_SENSOR, CONF_WINDOW_SENSOR,
@@ -154,15 +192,20 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
CONF_POWER_SENSOR, CONF_POWER_SENSOR,
CONF_MAX_POWER_SENSOR, CONF_MAX_POWER_SENSOR,
CONF_PRESENCE_SENSOR, CONF_PRESENCE_SENSOR,
CONF_CLIMATE, CONF_OFFSET_CALIBRATION_LIST,
CONF_OPENING_DEGREE_LIST,
CONF_CLOSING_DEGREE_LIST,
]: ]:
d = data.get(conf, None) # pylint: disable=invalid-name d = data.get(conf, None) # pylint: disable=invalid-name
if d is not None and self.hass.states.get(d) is None: if not isinstance(d, list):
_LOGGER.error( d = [d]
"Entity id %s doesn't have any state. We cannot use it in the Versatile Thermostat configuration", # pylint: disable=line-too-long for e in d:
d, if e is not None and self.hass.states.get(e) is None:
) _LOGGER.error(
raise UnknownEntity(conf) "Entity id %s doesn't have any state. We cannot use it in the Versatile Thermostat configuration", # pylint: disable=line-too-long
e,
)
raise UnknownEntity(conf)
# Check that only one window feature is used # Check that only one window feature is used
ws = self._infos.get(CONF_WINDOW_SENSOR) # pylint: disable=invalid-name ws = self._infos.get(CONF_WINDOW_SENSOR) # pylint: disable=invalid-name
@@ -188,6 +231,9 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
CONF_USE_PRESENCE_CENTRAL_CONFIG, CONF_USE_PRESENCE_CENTRAL_CONFIG,
CONF_USE_PRESETS_CENTRAL_CONFIG, CONF_USE_PRESETS_CENTRAL_CONFIG,
CONF_USE_ADVANCED_CENTRAL_CONFIG, CONF_USE_ADVANCED_CENTRAL_CONFIG,
CONF_USE_CENTRAL_MODE,
# CONF_USE_CENTRAL_BOILER_FEATURE, this is for Central Config
CONF_USED_BY_CENTRAL_BOILER,
]: ]:
if data.get(conf) is True: if data.get(conf) is True:
_LOGGER.error( _LOGGER.error(
@@ -196,7 +242,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
raise NoCentralConfig(conf) raise NoCentralConfig(conf)
# Check the service for central boiler format # Check the service for central boiler format
if self._infos.get(CONF_ADD_CENTRAL_BOILER_CONTROL): if self._infos.get(CONF_USE_CENTRAL_BOILER_FEATURE):
for conf in [ for conf in [
CONF_CENTRAL_BOILER_ACTIVATION_SRV, CONF_CENTRAL_BOILER_ACTIVATION_SRV,
CONF_CENTRAL_BOILER_DEACTIVATION_SRV, CONF_CENTRAL_BOILER_DEACTIVATION_SRV,
@@ -206,67 +252,108 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
except ServiceConfigurationError as err: except ServiceConfigurationError as err:
raise ServiceConfigurationError(conf) from err raise ServiceConfigurationError(conf) from err
# Check that the number of offet_calibration and opening_degree and closing_degree are equals
# to the number of underlying entities
if not self.check_valve_regulation_nb_entities(data, step_id):
raise ValveRegulationNbEntitiesIncorrect()
def check_config_complete(self, infos) -> bool: def check_config_complete(self, infos) -> bool:
"""True if the config is now complete (ie all mandatory attributes are set)""" """True if the config is now complete (ie all mandatory attributes are set)"""
if ( is_central_config = (
infos.get(CONF_NAME) is None infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CENTRAL_CONFIG
or infos.get(CONF_TEMP_SENSOR) is None )
or infos.get(CONF_CYCLE_MIN) is None if is_central_config:
): if (
return False infos.get(CONF_NAME) is None
or infos.get(CONF_EXTERNAL_TEMP_SENSOR) is None
):
return False
if ( if infos.get(CONF_USE_POWER_FEATURE, False) is True and (
infos.get(CONF_USE_MAIN_CENTRAL_CONFIG, False) is False
and infos.get(CONF_EXTERNAL_TEMP_SENSOR) is None
):
return False
if (
infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_SWITCH
and infos.get(CONF_HEATER, None) is None
):
return False
if (
infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE
and infos.get(CONF_CLIMATE, None) is None
):
return False
if (
infos.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE
and infos.get(CONF_VALVE, None) is None
):
return False
if (
infos.get(CONF_USE_MOTION_FEATURE, False) is True
and infos.get(CONF_MOTION_SENSOR, None) is None
):
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 infos.get(CONF_POWER_SENSOR, None) is None
or infos.get(CONF_MAX_POWER_SENSOR, None) is None or infos.get(CONF_MAX_POWER_SENSOR, None) is None
) ):
): return False
return False
if ( if (
infos.get(CONF_USE_PRESENCE_FEATURE, False) is True infos.get(CONF_USE_PRESENCE_FEATURE, False) is True
and infos.get(CONF_USE_PRESENCE_CENTRAL_CONFIG, False) is False and infos.get(CONF_PRESENCE_SENSOR, None) is None
and infos.get(CONF_PRESENCE_SENSOR, None) is None ):
): return False
return False
if ( if self._infos[CONF_USE_CENTRAL_BOILER_FEATURE] and (
infos.get(CONF_USE_ADVANCED_CENTRAL_CONFIG, False) is False not self._infos.get(CONF_CENTRAL_BOILER_ACTIVATION_SRV, False)
and infos.get(CONF_MINIMAL_ACTIVATION_DELAY, -1) == -1 or len(self._infos.get(CONF_CENTRAL_BOILER_ACTIVATION_SRV)) <= 0
): or not self._infos.get(CONF_CENTRAL_BOILER_DEACTIVATION_SRV, False)
return False or len(self._infos.get(CONF_CENTRAL_BOILER_DEACTIVATION_SRV)) <= 0
):
return False
else:
if (
infos.get(CONF_NAME) is None
or infos.get(CONF_TEMP_SENSOR) is None
or infos.get(CONF_CYCLE_MIN) is None
):
return False
if (
infos.get(CONF_USE_MAIN_CENTRAL_CONFIG, False) is False
and infos.get(CONF_EXTERNAL_TEMP_SENSOR) is None
):
return False
if infos.get(CONF_UNDERLYING_LIST, None) is not None and not infos.get(
CONF_UNDERLYING_LIST, None
):
return False
if (
infos.get(CONF_USE_MOTION_FEATURE, False) is True
and infos.get(CONF_MOTION_SENSOR, None) is None
):
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
)
):
return False
if (
infos.get(CONF_USE_PRESENCE_FEATURE, False) is True
and infos.get(CONF_USE_PRESENCE_CENTRAL_CONFIG, False) is False
and infos.get(CONF_PRESENCE_SENSOR, None) is None
):
return False
if (
infos.get(CONF_USE_ADVANCED_CENTRAL_CONFIG, False) is False
and infos.get(CONF_MINIMAL_ACTIVATION_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
and (
infos.get(CONF_TPI_COEF_INT, None) is None
or infos.get(CONF_TPI_COEF_EXT) is None
)
):
return False
if (
infos.get(CONF_USE_PRESETS_CENTRAL_CONFIG, False) is True
and self._central_config is None
):
return False
if not self.check_valve_regulation_nb_entities(infos, "check_complete"):
return False
return True return True
@@ -297,7 +384,7 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
if user_input is not None: if user_input is not None:
defaults.update(user_input or {}) defaults.update(user_input or {})
try: try:
await self.validate_input(user_input) await self.validate_input(user_input, step_id)
except UnknownEntity as err: except UnknownEntity as err:
errors[str(err)] = "unknown_entity" errors[str(err)] = "unknown_entity"
except WindowOpenDetectionMethod as err: except WindowOpenDetectionMethod as err:
@@ -306,6 +393,10 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
errors[str(err)] = "no_central_config" errors[str(err)] = "no_central_config"
except ServiceConfigurationError as err: except ServiceConfigurationError as err:
errors[str(err)] = "service_configuration_format" errors[str(err)] = "service_configuration_format"
except ConfigurationNotCompleteError as err:
errors["base"] = "configuration_not_complete"
except ValveRegulationNbEntitiesIncorrect as err:
errors["base"] = "valve_regulation_nb_entities_incorrect"
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception") _LOGGER.exception("Unexpected exception")
errors["base"] = "unknown" errors["base"] = "unknown"
@@ -336,17 +427,29 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
"user", STEP_USER_DATA_SCHEMA, user_input, self.async_step_menu "user", STEP_USER_DATA_SCHEMA, user_input, self.async_step_menu
) )
async def async_step_configuration_not_complete(
self, user_input: dict | None = None
) -> FlowResult:
"""A fake step to handle the incomplete configuration flow"""
return await self.async_step_menu(user_input)
async def async_step_menu(self, user_input: dict | None = None) -> FlowResult: async def async_step_menu(self, user_input: dict | None = None) -> FlowResult:
"""Handle the flow steps""" """Handle the flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_menu user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_menu user_input=%s", user_input)
menu_options = ["main", "type"] is_central_config = (
if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CENTRAL_CONFIG: self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CENTRAL_CONFIG
menu_options.append("central_boiler") )
else:
menu_options.append("features")
if self._infos.get(CONF_PROP_FUNCTION) == PROPORTIONAL_FUNCTION_TPI: menu_options = ["main", "features"]
if not is_central_config:
menu_options.append("type")
if (
self._infos.get(CONF_PROP_FUNCTION) == PROPORTIONAL_FUNCTION_TPI
or is_central_config
or self.is_valve_regulation_selected(self._infos)
):
menu_options.append("tpi") menu_options.append("tpi")
if self._infos[CONF_THERMOSTAT_TYPE] in [ if self._infos[CONF_THERMOSTAT_TYPE] in [
@@ -356,6 +459,12 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
]: ]:
menu_options.append("presets") menu_options.append("presets")
if (
is_central_config
and self._infos.get(CONF_USE_CENTRAL_BOILER_FEATURE) is True
):
menu_options.append("central_boiler")
if self._infos[CONF_USE_WINDOW_FEATURE] is True: if self._infos[CONF_USE_WINDOW_FEATURE] is True:
menu_options.append("window") menu_options.append("window")
@@ -368,10 +477,23 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
if self._infos[CONF_USE_PRESENCE_FEATURE] is True: if self._infos[CONF_USE_PRESENCE_FEATURE] is True:
menu_options.append("presence") menu_options.append("presence")
if self._infos.get(CONF_USE_AUTO_START_STOP_FEATURE) is True and self._infos[
CONF_THERMOSTAT_TYPE
] in [
CONF_THERMOSTAT_CLIMATE,
]:
menu_options.append("auto_start_stop")
if self.is_valve_regulation_selected(self._infos):
menu_options.append("valve_regulation")
menu_options.append("advanced") menu_options.append("advanced")
if self.check_config_complete(self._infos): if self.check_config_complete(self._infos):
menu_options.append("finalize") menu_options.append("finalize")
else:
_LOGGER.info("The configuration is not terminated")
menu_options.append("configuration_not_complete")
return self.async_show_menu( return self.async_show_menu(
step_id="menu", step_id="menu",
@@ -434,6 +556,24 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
"""Handle the Type flow steps""" """Handle the Type flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_type user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_type user_input=%s", user_input)
if (
self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CLIMATE
and user_input is not None
and not self.is_valve_regulation_selected(user_input)
):
# Remove TPI info
for key in [
PROPORTIONAL_FUNCTION_TPI,
CONF_PROP_FUNCTION,
CONF_TPI_COEF_INT,
CONF_TPI_COEF_EXT,
CONF_OFFSET_CALIBRATION_LIST,
CONF_OPENING_DEGREE_LIST,
CONF_CLOSING_DEGREE_LIST,
]:
if self._infos.get(key):
del self._infos[key]
if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_SWITCH: if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_SWITCH:
return await self.generic_step( return await self.generic_step(
"type", STEP_THERMOSTAT_SWITCH, user_input, self.async_step_menu "type", STEP_THERMOSTAT_SWITCH, user_input, self.async_step_menu
@@ -454,13 +594,45 @@ class VersatileThermostatBaseConfigFlow(FlowHandler):
"""Handle the Type flow steps""" """Handle the Type flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_features user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_features user_input=%s", user_input)
schema = STEP_FEATURES_DATA_SCHEMA
if self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CENTRAL_CONFIG:
schema = STEP_CENTRAL_FEATURES_DATA_SCHEMA
elif self._infos[CONF_THERMOSTAT_TYPE] == CONF_THERMOSTAT_CLIMATE:
schema = STEP_CLIMATE_FEATURES_DATA_SCHEMA
return await self.generic_step( return await self.generic_step(
"features", "features",
STEP_FEATURES_DATA_SCHEMA, schema,
user_input, user_input,
self.async_step_menu, self.async_step_menu,
) )
async def async_step_auto_start_stop(self, user_input: dict | None = None) -> FlowResult:
""" Handle the Auto start stop step"""
_LOGGER.debug("Into ConfigFlow.async_step_auto_start_stop user_input=%s", user_input)
schema = STEP_AUTO_START_STOP
self._infos[COMES_FROM] = None
next_step = self.async_step_menu
return await self.generic_step("auto_start_stop", schema, user_input, next_step)
async def async_step_valve_regulation(
self, user_input: dict | None = None
) -> FlowResult:
"""Handle the valve regulation configuration step"""
_LOGGER.debug(
"Into ConfigFlow.async_step_valve_regulation user_input=%s", user_input
)
schema = STEP_VALVE_REGULATION
self._infos[COMES_FROM] = None
next_step = self.async_step_menu
return await self.generic_step(
"valve_regulation", schema, user_input, next_step
)
async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult: async def async_step_tpi(self, user_input: dict | None = None) -> FlowResult:
"""Handle the TPI flow steps""" """Handle the TPI flow steps"""
_LOGGER.debug("Into ConfigFlow.async_step_tpi user_input=%s", user_input) _LOGGER.debug("Into ConfigFlow.async_step_tpi user_input=%s", user_input)
@@ -796,6 +968,11 @@ class VersatileThermostatOptionsFlowHandler(
if not self._infos[CONF_USE_PRESENCE_FEATURE]: if not self._infos[CONF_USE_PRESENCE_FEATURE]:
self._infos[CONF_USE_PRESENCE_CENTRAL_CONFIG] = False self._infos[CONF_USE_PRESENCE_CENTRAL_CONFIG] = False
self._infos[CONF_PRESENCE_SENSOR] = None 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( _LOGGER.info(
"Recreating entry %s due to configuration change. New config is now: %s", "Recreating entry %s due to configuration change. New config is now: %s",

View File

@@ -16,6 +16,10 @@ from homeassistant.components.input_number import (
DOMAIN as INPUT_NUMBER_DOMAIN, DOMAIN as INPUT_NUMBER_DOMAIN,
) )
from homeassistant.components.input_datetime import (
DOMAIN as INPUT_DATETIME_DOMAIN,
)
from homeassistant.components.person import DOMAIN as PERSON_DOMAIN from homeassistant.components.person import DOMAIN as PERSON_DOMAIN
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
@@ -42,6 +46,11 @@ STEP_MAIN_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
vol.Required(CONF_TEMP_SENSOR): selector.EntitySelector( vol.Required(CONF_TEMP_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SENSOR_DOMAIN, INPUT_NUMBER_DOMAIN]), selector.EntitySelectorConfig(domain=[SENSOR_DOMAIN, INPUT_NUMBER_DOMAIN]),
), ),
vol.Optional(CONF_LAST_SEEN_TEMP_SENSOR): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[SENSOR_DOMAIN, INPUT_DATETIME_DOMAIN]
),
),
vol.Required(CONF_CYCLE_MIN, default=5): cv.positive_int, vol.Required(CONF_CYCLE_MIN, default=5): cv.positive_int,
vol.Optional(CONF_DEVICE_POWER, default="1"): vol.Coerce(float), vol.Optional(CONF_DEVICE_POWER, default="1"): vol.Coerce(float),
vol.Required(CONF_USE_MAIN_CENTRAL_CONFIG, default=True): cv.boolean, vol.Required(CONF_USE_MAIN_CENTRAL_CONFIG, default=True): cv.boolean,
@@ -59,6 +68,26 @@ STEP_FEATURES_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
} }
) )
STEP_CLIMATE_FEATURES_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Optional(CONF_USE_WINDOW_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_MOTION_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_POWER_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_PRESENCE_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_AUTO_START_STOP_FEATURE, default=False): cv.boolean,
}
)
STEP_CENTRAL_FEATURES_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{
vol.Optional(CONF_USE_WINDOW_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_MOTION_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_POWER_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_PRESENCE_FEATURE, default=False): cv.boolean,
vol.Optional(CONF_USE_CENTRAL_BOILER_FEATURE, default=False): cv.boolean,
}
)
STEP_CENTRAL_MAIN_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name STEP_CENTRAL_MAIN_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Required(CONF_EXTERNAL_TEMP_SENSOR): selector.EntitySelector( vol.Required(CONF_EXTERNAL_TEMP_SENSOR): selector.EntitySelector(
@@ -67,7 +96,6 @@ STEP_CENTRAL_MAIN_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
vol.Required(CONF_TEMP_MIN, default=7): vol.Coerce(float), vol.Required(CONF_TEMP_MIN, default=7): vol.Coerce(float),
vol.Required(CONF_TEMP_MAX, default=35): vol.Coerce(float), vol.Required(CONF_TEMP_MAX, default=35): vol.Coerce(float),
vol.Required(CONF_STEP_TEMPERATURE, default=0.1): vol.Coerce(float), vol.Required(CONF_STEP_TEMPERATURE, default=0.1): vol.Coerce(float),
vol.Required(CONF_ADD_CENTRAL_BOILER_CONTROL, default=False): cv.boolean,
} }
) )
@@ -91,17 +119,10 @@ STEP_CENTRAL_BOILER_SCHEMA = vol.Schema(
STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Required(CONF_HEATER): selector.EntitySelector( vol.Required(CONF_UNDERLYING_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]), selector.EntitySelectorConfig(
), domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN], multiple=True
vol.Optional(CONF_HEATER_2): selector.EntitySelector( ),
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
),
vol.Optional(CONF_HEATER_3): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
),
vol.Optional(CONF_HEATER_4): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SWITCH_DOMAIN, INPUT_BOOLEAN_DOMAIN]),
), ),
vol.Optional(CONF_HEATER_KEEP_ALIVE): cv.positive_int, vol.Optional(CONF_HEATER_KEEP_ALIVE): cv.positive_int,
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In( vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
@@ -116,17 +137,8 @@ STEP_THERMOSTAT_SWITCH = vol.Schema( # pylint: disable=invalid-name
STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Required(CONF_CLIMATE): selector.EntitySelector( vol.Required(CONF_UNDERLYING_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN), selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN, multiple=True),
),
vol.Optional(CONF_CLIMATE_2): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
),
vol.Optional(CONF_CLIMATE_3): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
),
vol.Optional(CONF_CLIMATE_4): selector.EntitySelector(
selector.EntitySelectorConfig(domain=CLIMATE_DOMAIN),
), ),
vol.Optional(CONF_AC_MODE, default=False): cv.boolean, vol.Optional(CONF_AC_MODE, default=False): cv.boolean,
vol.Optional( vol.Optional(
@@ -155,17 +167,10 @@ STEP_THERMOSTAT_CLIMATE = vol.Schema( # pylint: disable=invalid-name
STEP_THERMOSTAT_VALVE = vol.Schema( # pylint: disable=invalid-name STEP_THERMOSTAT_VALVE = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Required(CONF_VALVE): selector.EntitySelector( vol.Required(CONF_UNDERLYING_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]), selector.EntitySelectorConfig(
), domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
vol.Optional(CONF_VALVE_2): selector.EntitySelector( ),
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Optional(CONF_VALVE_3): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
),
vol.Optional(CONF_VALVE_4): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN]),
), ),
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In( vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
[ [
@@ -178,6 +183,45 @@ STEP_THERMOSTAT_VALVE = vol.Schema( # pylint: disable=invalid-name
} }
) )
STEP_AUTO_START_STOP = vol.Schema( # pylint: disable=invalid-name
{
vol.Optional(
CONF_AUTO_START_STOP_LEVEL, default=AUTO_START_STOP_LEVEL_NONE
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=CONF_AUTO_START_STOP_LEVELS,
translation_key="auto_start_stop",
mode="dropdown",
)
),
}
)
STEP_VALVE_REGULATION = vol.Schema( # pylint: disable=invalid-name
{
vol.Required(CONF_OPENING_DEGREE_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
),
),
vol.Optional(CONF_OFFSET_CALIBRATION_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
),
),
vol.Optional(CONF_CLOSING_DEGREE_LIST): selector.EntitySelector(
selector.EntitySelectorConfig(
domain=[NUMBER_DOMAIN, INPUT_NUMBER_DOMAIN], multiple=True
),
),
vol.Required(CONF_PROP_FUNCTION, default=PROPORTIONAL_FUNCTION_TPI): vol.In(
[
PROPORTIONAL_FUNCTION_TPI,
]
),
}
)
STEP_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name STEP_TPI_DATA_SCHEMA = vol.Schema( # pylint: disable=invalid-name
{ {
vol.Required(CONF_USE_TPI_CENTRAL_CONFIG, default=True): cv.boolean, vol.Required(CONF_USE_TPI_CENTRAL_CONFIG, default=True): cv.boolean,

View File

@@ -2,8 +2,12 @@
"""Constants for the Versatile Thermostat integration.""" """Constants for the Versatile Thermostat integration."""
import logging import logging
import math
from typing import Literal
from datetime import datetime
from enum import Enum from enum import Enum
from homeassistant.core import HomeAssistant
from homeassistant.const import CONF_NAME, Platform from homeassistant.const import CONF_NAME, Platform
from homeassistant.components.climate import ( from homeassistant.components.climate import (
@@ -15,6 +19,7 @@ from homeassistant.components.climate import (
) )
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.util import dt as dt_util
from .prop_algorithm import ( from .prop_algorithm import (
PROPORTIONAL_FUNCTION_TPI, PROPORTIONAL_FUNCTION_TPI,
@@ -22,6 +27,9 @@ from .prop_algorithm import (
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONFIG_VERSION = 2
CONFIG_MINOR_VERSION = 0
PRESET_TEMP_SUFFIX = "_temp" PRESET_TEMP_SUFFIX = "_temp"
PRESET_AC_SUFFIX = "_ac" PRESET_AC_SUFFIX = "_ac"
PRESET_ECO_AC = PRESET_ECO + PRESET_AC_SUFFIX PRESET_ECO_AC = PRESET_ECO + PRESET_AC_SUFFIX
@@ -48,14 +56,13 @@ PLATFORMS: list[Platform] = [
# Number should be after CLIMATE # Number should be after CLIMATE
Platform.NUMBER, Platform.NUMBER,
Platform.BINARY_SENSOR, Platform.BINARY_SENSOR,
Platform.SWITCH,
] ]
CONF_HEATER = "heater_entity_id" CONF_UNDERLYING_LIST = "underlying_entity_ids"
CONF_HEATER_2 = "heater_entity2_id"
CONF_HEATER_3 = "heater_entity3_id"
CONF_HEATER_4 = "heater_entity4_id"
CONF_HEATER_KEEP_ALIVE = "heater_keep_alive" CONF_HEATER_KEEP_ALIVE = "heater_keep_alive"
CONF_TEMP_SENSOR = "temperature_sensor_entity_id" CONF_TEMP_SENSOR = "temperature_sensor_entity_id"
CONF_LAST_SEEN_TEMP_SENSOR = "last_seen_temperature_sensor_entity_id"
CONF_EXTERNAL_TEMP_SENSOR = "external_temperature_sensor_entity_id" CONF_EXTERNAL_TEMP_SENSOR = "external_temperature_sensor_entity_id"
CONF_POWER_SENSOR = "power_sensor_entity_id" CONF_POWER_SENSOR = "power_sensor_entity_id"
CONF_MAX_POWER_SENSOR = "max_power_sensor_entity_id" CONF_MAX_POWER_SENSOR = "max_power_sensor_entity_id"
@@ -84,24 +91,19 @@ CONF_THERMOSTAT_CENTRAL_CONFIG = "thermostat_central_config"
CONF_THERMOSTAT_SWITCH = "thermostat_over_switch" CONF_THERMOSTAT_SWITCH = "thermostat_over_switch"
CONF_THERMOSTAT_CLIMATE = "thermostat_over_climate" CONF_THERMOSTAT_CLIMATE = "thermostat_over_climate"
CONF_THERMOSTAT_VALVE = "thermostat_over_valve" CONF_THERMOSTAT_VALVE = "thermostat_over_valve"
CONF_CLIMATE = "climate_entity_id"
CONF_CLIMATE_2 = "climate_entity2_id"
CONF_CLIMATE_3 = "climate_entity3_id"
CONF_CLIMATE_4 = "climate_entity4_id"
CONF_USE_WINDOW_FEATURE = "use_window_feature" CONF_USE_WINDOW_FEATURE = "use_window_feature"
CONF_USE_MOTION_FEATURE = "use_motion_feature" CONF_USE_MOTION_FEATURE = "use_motion_feature"
CONF_USE_PRESENCE_FEATURE = "use_presence_feature" CONF_USE_PRESENCE_FEATURE = "use_presence_feature"
CONF_USE_POWER_FEATURE = "use_power_feature" CONF_USE_POWER_FEATURE = "use_power_feature"
CONF_USE_CENTRAL_BOILER_FEATURE = "use_central_boiler_feature"
CONF_USE_AUTO_START_STOP_FEATURE = "use_auto_start_stop_feature"
CONF_AC_MODE = "ac_mode" CONF_AC_MODE = "ac_mode"
CONF_WINDOW_AUTO_OPEN_THRESHOLD = "window_auto_open_threshold" CONF_WINDOW_AUTO_OPEN_THRESHOLD = "window_auto_open_threshold"
CONF_WINDOW_AUTO_CLOSE_THRESHOLD = "window_auto_close_threshold" CONF_WINDOW_AUTO_CLOSE_THRESHOLD = "window_auto_close_threshold"
CONF_WINDOW_AUTO_MAX_DURATION = "window_auto_max_duration" CONF_WINDOW_AUTO_MAX_DURATION = "window_auto_max_duration"
CONF_VALVE = "valve_entity_id"
CONF_VALVE_2 = "valve_entity2_id"
CONF_VALVE_3 = "valve_entity3_id"
CONF_VALVE_4 = "valve_entity4_id"
CONF_AUTO_REGULATION_MODE = "auto_regulation_mode" CONF_AUTO_REGULATION_MODE = "auto_regulation_mode"
CONF_AUTO_REGULATION_NONE = "auto_regulation_none" CONF_AUTO_REGULATION_NONE = "auto_regulation_none"
CONF_AUTO_REGULATION_VALVE = "auto_regulation_valve"
CONF_AUTO_REGULATION_SLOW = "auto_regulation_slow" CONF_AUTO_REGULATION_SLOW = "auto_regulation_slow"
CONF_AUTO_REGULATION_LIGHT = "auto_regulation_light" CONF_AUTO_REGULATION_LIGHT = "auto_regulation_light"
CONF_AUTO_REGULATION_MEDIUM = "auto_regulation_medium" CONF_AUTO_REGULATION_MEDIUM = "auto_regulation_medium"
@@ -118,10 +120,28 @@ CONF_AUTO_FAN_MEDIUM = "auto_fan_medium"
CONF_AUTO_FAN_HIGH = "auto_fan_high" CONF_AUTO_FAN_HIGH = "auto_fan_high"
CONF_AUTO_FAN_TURBO = "auto_fan_turbo" CONF_AUTO_FAN_TURBO = "auto_fan_turbo"
CONF_STEP_TEMPERATURE = "step_temperature" CONF_STEP_TEMPERATURE = "step_temperature"
CONF_OFFSET_CALIBRATION_LIST = "offset_calibration_entity_ids"
CONF_OPENING_DEGREE_LIST = "opening_degree_entity_ids"
CONF_CLOSING_DEGREE_LIST = "closing_degree_entity_ids"
# Deprecated
CONF_HEATER = "heater_entity_id"
CONF_HEATER_2 = "heater_entity2_id"
CONF_HEATER_3 = "heater_entity3_id"
CONF_HEATER_4 = "heater_entity4_id"
CONF_CLIMATE = "climate_entity_id"
CONF_CLIMATE_2 = "climate_entity2_id"
CONF_CLIMATE_3 = "climate_entity3_id"
CONF_CLIMATE_4 = "climate_entity4_id"
CONF_VALVE = "valve_entity_id"
CONF_VALVE_2 = "valve_entity2_id"
CONF_VALVE_3 = "valve_entity3_id"
CONF_VALVE_4 = "valve_entity4_id"
# Global params into configuration.yaml # Global params into configuration.yaml
CONF_SHORT_EMA_PARAMS = "short_ema_params" CONF_SHORT_EMA_PARAMS = "short_ema_params"
CONF_SAFETY_MODE = "safety_mode" CONF_SAFETY_MODE = "safety_mode"
CONF_MAX_ON_PERCENT = "max_on_percent"
CONF_USE_MAIN_CENTRAL_CONFIG = "use_main_central_config" CONF_USE_MAIN_CENTRAL_CONFIG = "use_main_central_config"
CONF_USE_TPI_CENTRAL_CONFIG = "use_tpi_central_config" CONF_USE_TPI_CENTRAL_CONFIG = "use_tpi_central_config"
@@ -134,13 +154,42 @@ CONF_USE_ADVANCED_CENTRAL_CONFIG = "use_advanced_central_config"
CONF_USE_CENTRAL_MODE = "use_central_mode" CONF_USE_CENTRAL_MODE = "use_central_mode"
CONF_ADD_CENTRAL_BOILER_CONTROL = "add_central_boiler_control"
CONF_CENTRAL_BOILER_ACTIVATION_SRV = "central_boiler_activation_service" CONF_CENTRAL_BOILER_ACTIVATION_SRV = "central_boiler_activation_service"
CONF_CENTRAL_BOILER_DEACTIVATION_SRV = "central_boiler_deactivation_service" CONF_CENTRAL_BOILER_DEACTIVATION_SRV = "central_boiler_deactivation_service"
CONF_USED_BY_CENTRAL_BOILER = "used_by_controls_central_boiler" CONF_USED_BY_CENTRAL_BOILER = "used_by_controls_central_boiler"
CONF_WINDOW_ACTION = "window_action" CONF_WINDOW_ACTION = "window_action"
CONF_AUTO_START_STOP_LEVEL = "auto_start_stop_level"
AUTO_START_STOP_LEVEL_NONE = "auto_start_stop_none"
AUTO_START_STOP_LEVEL_SLOW = "auto_start_stop_slow"
AUTO_START_STOP_LEVEL_MEDIUM = "auto_start_stop_medium"
AUTO_START_STOP_LEVEL_FAST = "auto_start_stop_fast"
CONF_AUTO_START_STOP_LEVELS = [
AUTO_START_STOP_LEVEL_NONE,
AUTO_START_STOP_LEVEL_SLOW,
AUTO_START_STOP_LEVEL_MEDIUM,
AUTO_START_STOP_LEVEL_FAST,
]
# For explicit typing purpose only
TYPE_AUTO_START_STOP_LEVELS = Literal[ # pylint: disable=invalid-name
AUTO_START_STOP_LEVEL_FAST,
AUTO_START_STOP_LEVEL_MEDIUM,
AUTO_START_STOP_LEVEL_SLOW,
AUTO_START_STOP_LEVEL_NONE,
]
HVAC_OFF_REASON_NAME = "hvac_off_reason"
HVAC_OFF_REASON_MANUAL = "manual"
HVAC_OFF_REASON_AUTO_START_STOP = "auto_start_stop"
HVAC_OFF_REASON_WINDOW_DETECTION = "window_detection"
HVAC_OFF_REASONS = Literal[ # pylint: disable=invalid-name
HVAC_OFF_REASON_MANUAL,
HVAC_OFF_REASON_AUTO_START_STOP,
HVAC_OFF_REASON_WINDOW_DETECTION,
]
DEFAULT_SHORT_EMA_PARAMS = { DEFAULT_SHORT_EMA_PARAMS = {
"max_alpha": 0.5, "max_alpha": 0.5,
# In sec # In sec
@@ -212,10 +261,6 @@ CONF_PRESETS_AWAY_WITH_AC_VALUES = list(CONF_PRESETS_AWAY_WITH_AC.values())
ALL_CONF = ( ALL_CONF = (
[ [
CONF_NAME, CONF_NAME,
CONF_HEATER,
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_HEATER_KEEP_ALIVE, CONF_HEATER_KEEP_ALIVE,
CONF_TEMP_SENSOR, CONF_TEMP_SENSOR,
CONF_EXTERNAL_TEMP_SENSOR, CONF_EXTERNAL_TEMP_SENSOR,
@@ -245,19 +290,12 @@ ALL_CONF = (
CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_SWITCH, CONF_THERMOSTAT_SWITCH,
CONF_THERMOSTAT_CLIMATE, CONF_THERMOSTAT_CLIMATE,
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
CONF_USE_WINDOW_FEATURE, CONF_USE_WINDOW_FEATURE,
CONF_USE_MOTION_FEATURE, CONF_USE_MOTION_FEATURE,
CONF_USE_PRESENCE_FEATURE, CONF_USE_PRESENCE_FEATURE,
CONF_USE_POWER_FEATURE, CONF_USE_POWER_FEATURE,
CONF_USE_CENTRAL_BOILER_FEATURE,
CONF_AC_MODE, CONF_AC_MODE,
CONF_VALVE,
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
CONF_AUTO_REGULATION_MODE, CONF_AUTO_REGULATION_MODE,
CONF_AUTO_REGULATION_DTEMP, CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN, CONF_AUTO_REGULATION_PERIOD_MIN,
@@ -273,7 +311,6 @@ ALL_CONF = (
CONF_USE_PRESENCE_CENTRAL_CONFIG, CONF_USE_PRESENCE_CENTRAL_CONFIG,
CONF_USE_ADVANCED_CENTRAL_CONFIG, CONF_USE_ADVANCED_CENTRAL_CONFIG,
CONF_USE_CENTRAL_MODE, CONF_USE_CENTRAL_MODE,
CONF_ADD_CENTRAL_BOILER_CONTROL,
CONF_USED_BY_CENTRAL_BOILER, CONF_USED_BY_CENTRAL_BOILER,
CONF_CENTRAL_BOILER_ACTIVATION_SRV, CONF_CENTRAL_BOILER_ACTIVATION_SRV,
CONF_CENTRAL_BOILER_DEACTIVATION_SRV, CONF_CENTRAL_BOILER_DEACTIVATION_SRV,
@@ -292,6 +329,7 @@ CONF_FUNCTIONS = [
CONF_AUTO_REGULATION_MODES = [ CONF_AUTO_REGULATION_MODES = [
CONF_AUTO_REGULATION_NONE, CONF_AUTO_REGULATION_NONE,
CONF_AUTO_REGULATION_VALVE,
CONF_AUTO_REGULATION_LIGHT, CONF_AUTO_REGULATION_LIGHT,
CONF_AUTO_REGULATION_MEDIUM, CONF_AUTO_REGULATION_MEDIUM,
CONF_AUTO_REGULATION_STRONG, CONF_AUTO_REGULATION_STRONG,
@@ -326,7 +364,11 @@ CONF_WINDOW_ACTIONS = [
CONF_WINDOW_ECO_TEMP, CONF_WINDOW_ECO_TEMP,
] ]
SUPPORT_FLAGS = ClimateEntityFeature.TARGET_TEMPERATURE SUPPORT_FLAGS = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TURN_OFF
| ClimateEntityFeature.TURN_ON
)
SERVICE_SET_PRESENCE = "set_presence" SERVICE_SET_PRESENCE = "set_presence"
SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature" SERVICE_SET_PRESET_TEMPERATURE = "set_preset_temperature"
@@ -426,9 +468,9 @@ class RegulationParamVeryStrong:
kp: float = 0.6 kp: float = 0.6
ki: float = 0.1 ki: float = 0.1
k_ext: float = 0.2 k_ext: float = 0.2
offset_max: float = 4 offset_max: float = 8
stabilization_threshold: float = 0.1 stabilization_threshold: float = 0.1
accumulated_error_threshold: float = 30 accumulated_error_threshold: float = 80
class EventType(Enum): class EventType(Enum):
@@ -441,6 +483,7 @@ class EventType(Enum):
CENTRAL_BOILER_EVENT: str = "versatile_thermostat_central_boiler_event" CENTRAL_BOILER_EVENT: str = "versatile_thermostat_central_boiler_event"
PRESET_EVENT: str = "versatile_thermostat_preset_event" PRESET_EVENT: str = "versatile_thermostat_preset_event"
WINDOW_AUTO_EVENT: str = "versatile_thermostat_window_auto_event" WINDOW_AUTO_EVENT: str = "versatile_thermostat_window_auto_event"
AUTO_START_STOP_EVENT: str = "versatile_thermostat_auto_start_stop_event"
def send_vtherm_event(hass, event_type: EventType, entity, data: dict): def send_vtherm_event(hass, event_type: EventType, entity, data: dict):
@@ -452,6 +495,38 @@ def send_vtherm_event(hass, event_type: EventType, entity, data: dict):
hass.bus.fire(event_type.value, data) hass.bus.fire(event_type.value, data)
def get_safe_float(hass, entity_id: str):
"""Get a safe float state value for an entity.
Return None if entity is not available"""
if (
entity_id is None
or not (state := hass.states.get(entity_id))
or state.state == "unknown"
or state.state == "unavailable"
):
return None
float_val = float(state.state)
return None if math.isinf(float_val) or not math.isfinite(float_val) else float_val
def get_tz(hass: HomeAssistant):
"""Get the current timezone"""
return dt_util.get_time_zone(hass.config.time_zone)
class NowClass:
"""For testing purpose only"""
@staticmethod
def get_now(hass: HomeAssistant) -> datetime:
"""A test function to get the now.
For testing purpose this method can be overriden to get a specific
timestamp.
"""
return datetime.now(get_tz(hass))
class UnknownEntity(HomeAssistantError): class UnknownEntity(HomeAssistantError):
"""Error to indicate there is an unknown entity_id given.""" """Error to indicate there is an unknown entity_id given."""
@@ -468,6 +543,15 @@ class ServiceConfigurationError(HomeAssistantError):
"""Error in the service configuration to control the central boiler""" """Error in the service configuration to control the central boiler"""
class ConfigurationNotCompleteError(HomeAssistantError):
"""Error the configuration is not complete"""
class ValveRegulationNbEntitiesIncorrect(HomeAssistantError):
"""Error to indicate there is an error in the configuration of the TRV with valve regulation.
The number of specific entities is incorrect."""
class overrides: # pylint: disable=invalid-name class overrides: # pylint: disable=invalid-name
"""An annotation to inform overrides""" """An annotation to inform overrides"""

View File

@@ -0,0 +1,18 @@
{
"entity": {
"climate": {
"versatile_thermostat": {
"state_attributes": {
"preset_mode": {
"state": {
"shedding": "mdi:power-plug-off",
"safety": "mdi:shield-alert",
"none": "mdi:knob",
"frost": "mdi:snowflake"
}
}
}
}
}
}
}

View File

@@ -10,6 +10,7 @@ the keep_alive setting of Home Assistant's Generic Thermostat integration:
import logging import logging
from collections.abc import Awaitable, Callable from collections.abc import Awaitable, Callable
from datetime import timedelta, datetime from datetime import timedelta, datetime
from time import monotonic
from homeassistant.core import HomeAssistant, CALLBACK_TYPE from homeassistant.core import HomeAssistant, CALLBACK_TYPE
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
@@ -18,16 +19,95 @@ from homeassistant.helpers.event import async_track_time_interval
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class BackoffTimer:
"""Exponential backoff timer with a non-blocking polling-style implementation.
Usage example:
timer = BackoffTimer(multiplier=1.5, upper_limit_sec=600)
while some_condition:
if timer.is_ready():
do_something()
"""
def __init__(
self,
*,
multiplier=2.0,
lower_limit_sec=30,
upper_limit_sec=86400,
initially_ready=True,
):
"""Initialize a BackoffTimer instance.
Args:
multiplier (int, optional): Period multiplier applied when is_ready() is True.
lower_limit_sec (int, optional): Initial backoff period in seconds.
upper_limit_sec (int, optional): Maximum backoff period in seconds.
initially_ready (bool, optional): Whether is_ready() should return True the
first time it is called, or after a call to reset().
"""
self._multiplier = multiplier
self._lower_limit_sec = lower_limit_sec
self._upper_limit_sec = upper_limit_sec
self._initially_ready = initially_ready
self._timestamp = 0
self._period_sec = self._lower_limit_sec
@property
def in_progress(self) -> bool:
"""Whether the backoff timer is in progress (True after a call to is_ready())."""
return bool(self._timestamp)
def reset(self):
"""Reset a BackoffTimer instance."""
self._timestamp = 0
self._period_sec = self._lower_limit_sec
def is_ready(self) -> bool:
"""Check whether an exponentially increasing period of time has passed.
Whenever is_ready() returns True, the timer period is multiplied so that
it takes longer until is_ready() returns True again.
Returns:
bool: True if enough time has passed since one of the following events,
in relation to an instance of this class:
- The last time when this method returned True, if it ever did.
- Or else, when this method was first called after a call to reset().
- Or else, when this method was first called.
False otherwise.
"""
now = monotonic()
if self._timestamp == 0:
self._timestamp = now
return self._initially_ready
elif now - self._timestamp >= self._period_sec:
self._timestamp = now
self._period_sec = max(
self._lower_limit_sec,
min(self._upper_limit_sec, self._period_sec * self._multiplier),
)
return True
return False
class IntervalCaller: class IntervalCaller:
"""Repeatedly call a given async action function at a given regular interval. """Repeatedly call a given async action function at a given regular interval.
Convenience wrapper around Home Assistant's `async_track_time_interval` function. Convenience wrapper around Home Assistant's `async_track_time_interval` function.
""" """
def __init__(self, hass: HomeAssistant, interval_sec: int) -> None: def __init__(self, hass: HomeAssistant, interval_sec: float) -> None:
self._hass = hass self._hass = hass
self._interval_sec = interval_sec self._interval_sec = interval_sec
self._remove_handle: CALLBACK_TYPE | None = None self._remove_handle: CALLBACK_TYPE | None = None
self.backoff_timer = BackoffTimer()
@property
def interval_sec(self) -> float:
"""Return the calling interval in seconds."""
return self._interval_sec
def cancel(self): def cancel(self):
"""Cancel the regular calls to the action function.""" """Cancel the regular calls to the action function."""
@@ -43,7 +123,11 @@ class IntervalCaller:
async def callback(_time: datetime): async def callback(_time: datetime):
try: try:
_LOGGER.debug("Calling keep-alive action") _LOGGER.debug(
"Calling keep-alive action '%s' (%ss interval)",
action.__name__,
self._interval_sec,
)
await action() await action()
except Exception as e: # pylint: disable=broad-exception-caught except Exception as e: # pylint: disable=broad-exception-caught
_LOGGER.error(e) _LOGGER.error(e)

View File

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

View File

@@ -11,13 +11,15 @@ from homeassistant.components.number import (
NumberMode, NumberMode,
NumberDeviceClass, NumberDeviceClass,
DOMAIN as NUMBER_DOMAIN, DOMAIN as NUMBER_DOMAIN,
DEFAULT_MAX_VALUE,
DEFAULT_MIN_VALUE,
DEFAULT_STEP,
) )
from homeassistant.components.climate import ( from homeassistant.components.climate import (
PRESET_BOOST, PRESET_BOOST,
PRESET_COMFORT, PRESET_COMFORT,
PRESET_ECO, PRESET_ECO,
) )
from homeassistant.components.sensor import UnitOfTemperature
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@@ -51,7 +53,9 @@ from .const import (
CONF_USE_PRESETS_CENTRAL_CONFIG, CONF_USE_PRESETS_CENTRAL_CONFIG,
CONF_USE_PRESENCE_CENTRAL_CONFIG, CONF_USE_PRESENCE_CENTRAL_CONFIG,
CONF_USE_PRESENCE_FEATURE, CONF_USE_PRESENCE_FEATURE,
CONF_USE_CENTRAL_BOILER_FEATURE,
overrides, overrides,
CONF_USE_MAIN_CENTRAL_CONFIG,
) )
PRESET_ICON_MAPPING = { PRESET_ICON_MAPPING = {
@@ -91,77 +95,96 @@ async def async_setup_entry(
unique_id = entry.entry_id unique_id = entry.entry_id
name = entry.data.get(CONF_NAME) name = entry.data.get(CONF_NAME)
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE) vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
# is_central_boiler = entry.data.get(CONF_ADD_CENTRAL_BOILER_CONTROL) # is_central_boiler = entry.data.get(CONF_USE_CENTRAL_BOILER_FEATURE)
entities = [] entities = []
if vt_type != CONF_THERMOSTAT_CENTRAL_CONFIG: if vt_type != CONF_THERMOSTAT_CENTRAL_CONFIG:
# Creates non central temperature entities # Creates non central temperature entities
if not entry.data.get(CONF_USE_PRESETS_CENTRAL_CONFIG, False): if not entry.data.get(CONF_USE_PRESETS_CENTRAL_CONFIG, False):
for preset in CONF_PRESETS_VALUES:
entities.append(
TemperatureNumber(
hass, unique_id, name, preset, False, False, entry.data
)
)
if entry.data.get(CONF_AC_MODE, False): if entry.data.get(CONF_AC_MODE, False):
for preset in CONF_PRESETS_WITH_AC_VALUES: for preset in CONF_PRESETS_WITH_AC_VALUES:
_LOGGER.debug(
"%s - configuring Number non central, AC, non AWAY for preset %s",
name,
preset,
)
entities.append( entities.append(
TemperatureNumber( TemperatureNumber(
hass, unique_id, name, preset, True, False, entry.data hass, unique_id, name, preset, True, False, entry.data
) )
) )
else:
for preset in CONF_PRESETS_VALUES:
_LOGGER.debug(
"%s - configuring Number non central, non AC, non AWAY for preset %s",
name,
preset,
)
entities.append(
TemperatureNumber(
hass, unique_id, name, preset, False, False, entry.data
)
)
if entry.data.get( if entry.data.get(
CONF_USE_PRESENCE_FEATURE, False CONF_USE_PRESENCE_FEATURE, False
) is True and not entry.data.get(CONF_USE_PRESENCE_CENTRAL_CONFIG, False): ) is True and not entry.data.get(CONF_USE_PRESENCE_CENTRAL_CONFIG, False):
for preset in CONF_PRESETS_AWAY_VALUES:
entities.append(
TemperatureNumber(
hass, unique_id, name, preset, False, True, entry.data
)
)
if entry.data.get(CONF_AC_MODE, False): if entry.data.get(CONF_AC_MODE, False):
for preset in CONF_PRESETS_AWAY_WITH_AC_VALUES: for preset in CONF_PRESETS_AWAY_WITH_AC_VALUES:
_LOGGER.debug(
"%s - configuring Number non central, AC, AWAY for preset %s",
name,
preset,
)
entities.append( entities.append(
TemperatureNumber( TemperatureNumber(
hass, unique_id, name, preset, True, True, entry.data hass, unique_id, name, preset, True, True, entry.data
) )
) )
else:
for preset in CONF_PRESETS_AWAY_VALUES:
_LOGGER.debug(
"%s - configuring Number non central, non AC, AWAY for preset %s",
name,
preset,
)
entities.append(
TemperatureNumber(
hass, unique_id, name, preset, False, True, entry.data
)
)
# For central config only # For central config only
else: else:
entities.append( if entry.data.get(CONF_USE_CENTRAL_BOILER_FEATURE):
ActivateBoilerThresholdNumber(hass, unique_id, name, entry.data)
)
for preset in CONF_PRESETS_VALUES:
entities.append( entities.append(
CentralConfigTemperatureNumber( ActivateBoilerThresholdNumber(hass, unique_id, name, entry.data)
hass, unique_id, name, preset, False, False, entry.data
)
) )
for preset in CONF_PRESETS_WITH_AC_VALUES: for preset in CONF_PRESETS_WITH_AC_VALUES:
_LOGGER.debug(
"%s - configuring Number central, AC, non AWAY for preset %s",
name,
preset,
)
entities.append( entities.append(
CentralConfigTemperatureNumber( CentralConfigTemperatureNumber(
hass, unique_id, name, preset, True, False, entry.data hass, unique_id, name, preset, True, False, entry.data
) )
) )
for preset in CONF_PRESETS_AWAY_VALUES:
entities.append(
CentralConfigTemperatureNumber(
hass, unique_id, name, preset, False, True, entry.data
)
)
for preset in CONF_PRESETS_AWAY_WITH_AC_VALUES: for preset in CONF_PRESETS_AWAY_WITH_AC_VALUES:
_LOGGER.debug(
"%s - configuring Number central, AC, AWAY for preset %s", name, preset
)
entities.append( entities.append(
CentralConfigTemperatureNumber( CentralConfigTemperatureNumber(
hass, unique_id, name, preset, True, True, entry.data hass, unique_id, name, preset, True, True, entry.data
) )
) )
async_add_entities(entities, True) if len(entities) > 0:
async_add_entities(entities, True)
class ActivateBoilerThresholdNumber( class ActivateBoilerThresholdNumber(
@@ -256,10 +279,10 @@ class CentralConfigTemperatureNumber(
# self._attr_name = name # self._attr_name = name
self._attr_translation_key = preset_name self._attr_translation_key = preset_name
self.entity_id = f"{NUMBER_DOMAIN}.{slugify(name)}_{preset_name}" self.entity_id = f"{NUMBER_DOMAIN}.{slugify(name)}_preset_{preset_name}"
self._attr_unique_id = f"central_configuration_{preset_name}" self._attr_unique_id = f"central_configuration_preset_{preset_name}"
self._attr_device_class = NumberDeviceClass.TEMPERATURE self._attr_device_class = NumberDeviceClass.TEMPERATURE
self._attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS self._attr_native_unit_of_measurement = hass.config.units.temperature_unit
self._attr_native_step = entry_infos.get(CONF_STEP_TEMPERATURE, 0.5) self._attr_native_step = entry_infos.get(CONF_STEP_TEMPERATURE, 0.5)
self._attr_native_min_value = entry_infos.get(CONF_TEMP_MIN) self._attr_native_min_value = entry_infos.get(CONF_TEMP_MIN)
@@ -321,7 +344,10 @@ class CentralConfigTemperatureNumber(
async def async_set_native_value(self, value: float) -> None: async def async_set_native_value(self, value: float) -> None:
"""The value have change from the Number Entity in UI""" """The value have change from the Number Entity in UI"""
float_value = float(value) float_value = float(value)
old_value = float(self._attr_native_value) old_value = (
None if self._attr_native_value is None else float(self._attr_native_value)
)
if float_value == old_value: if float_value == old_value:
return return
@@ -333,7 +359,7 @@ class CentralConfigTemperatureNumber(
# We have to reload all VTherm for which uses the central configuration # We have to reload all VTherm for which uses the central configuration
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self.hass) api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self.hass)
# Update the VTherms which have temperature in central config # Update the VTherms which have temperature in central config
self.hass.create_task(api.init_vtherm_links(only_use_central=True)) self.hass.create_task(api.init_vtherm_preset_with_central())
def __str__(self): def __str__(self):
return f"VersatileThermostat-{self.name}" return f"VersatileThermostat-{self.name}"
@@ -344,7 +370,7 @@ class CentralConfigTemperatureNumber(
# TODO Kelvin ? It seems not because all internal values are stored in # 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 # ° Celsius but only the render in front can be in °K depending on the
# user configuration. # user configuration.
return UnitOfTemperature.CELSIUS return self.hass.config.units.temperature_unit
class TemperatureNumber( # pylint: disable=abstract-method class TemperatureNumber( # pylint: disable=abstract-method
@@ -369,15 +395,17 @@ class TemperatureNumber( # pylint: disable=abstract-method
super().__init__(hass, unique_id, name) super().__init__(hass, unique_id, name)
self._attr_translation_key = preset_name self._attr_translation_key = preset_name
self.entity_id = f"{NUMBER_DOMAIN}.{slugify(name)}_{preset_name}" self.entity_id = f"{NUMBER_DOMAIN}.{slugify(name)}_preset_{preset_name}"
self._attr_unique_id = f"{self._device_name}_{preset_name}" self._attr_unique_id = f"{self._device_name}_preset_{preset_name}"
self._attr_device_class = NumberDeviceClass.TEMPERATURE self._attr_device_class = NumberDeviceClass.TEMPERATURE
self._attr_native_unit_of_measurement = UnitOfTemperature.CELSIUS self._attr_native_unit_of_measurement = hass.config.units.temperature_unit
self._attr_native_step = entry_infos.get(CONF_STEP_TEMPERATURE, 0.5) self._has_central_main_attributes = entry_infos.get(
self._attr_native_min_value = entry_infos.get(CONF_TEMP_MIN) CONF_USE_MAIN_CENTRAL_CONFIG, False
self._attr_native_max_value = entry_infos.get(CONF_TEMP_MAX) )
self.init_min_max_step(entry_infos)
# Initialize the values if included into the entry_infos. This will do # Initialize the values if included into the entry_infos. This will do
# the temperature migration. # the temperature migration.
@@ -439,7 +467,9 @@ class TemperatureNumber( # pylint: disable=abstract-method
return return
float_value = float(value) float_value = float(value)
old_value = float(self._attr_native_value) old_value = (
None if self._attr_native_value is None else float(self._attr_native_value)
)
if float_value == old_value: if float_value == old_value:
return return
@@ -456,6 +486,10 @@ class TemperatureNumber( # pylint: disable=abstract-method
) )
) )
# We set the min, max and step from central config if relevant because it is possible
# that central config was not loaded at startup
self.init_min_max_step()
def __str__(self): def __str__(self):
return f"VersatileThermostat-{self.name}" return f"VersatileThermostat-{self.name}"
@@ -463,5 +497,28 @@ class TemperatureNumber( # pylint: disable=abstract-method
def native_unit_of_measurement(self) -> str | None: def native_unit_of_measurement(self) -> str | None:
"""The unit of measurement""" """The unit of measurement"""
if not self.my_climate: if not self.my_climate:
return UnitOfTemperature.CELSIUS return self.hass.config.units.temperature_unit
return self.my_climate.temperature_unit return self.my_climate.temperature_unit
def init_min_max_step(self, entry_infos=None):
"""Initialize min, max and step value from config or from central config"""
if self._has_central_main_attributes:
vthermapi: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api()
central_config = vthermapi.find_central_configuration()
if central_config:
self._attr_native_step = central_config.data.get(CONF_STEP_TEMPERATURE)
self._attr_native_min_value = central_config.data.get(CONF_TEMP_MIN)
self._attr_native_max_value = central_config.data.get(CONF_TEMP_MAX)
return
if entry_infos:
self._attr_native_step = entry_infos.get(
CONF_STEP_TEMPERATURE, DEFAULT_STEP
)
self._attr_native_min_value = entry_infos.get(
CONF_TEMP_MIN, DEFAULT_MIN_VALUE
)
self._attr_native_max_value = entry_infos.get(
CONF_TEMP_MAX, DEFAULT_MAX_VALUE
)

View File

@@ -1,4 +1,5 @@
""" The TPI calculation module """ """ The TPI calculation module """
# pylint: disable='line-too-long'
import logging import logging
from homeassistant.components.climate import HVACMode from homeassistant.components.climate import HVACMode
@@ -14,6 +15,11 @@ PROPORTIONAL_MIN_DURATION_SEC = 10
FUNCTION_TYPE = [PROPORTIONAL_FUNCTION_ATAN, PROPORTIONAL_FUNCTION_LINEAR] FUNCTION_TYPE = [PROPORTIONAL_FUNCTION_ATAN, PROPORTIONAL_FUNCTION_LINEAR]
def is_number(value):
"""check if value is a number"""
return isinstance(value, (int, float))
class PropAlgorithm: class PropAlgorithm:
"""This class aims to do all calculation of the Proportional alogorithm""" """This class aims to do all calculation of the Proportional alogorithm"""
@@ -24,16 +30,44 @@ class PropAlgorithm:
tpi_coef_ext, tpi_coef_ext,
cycle_min: int, cycle_min: int,
minimal_activation_delay: int, minimal_activation_delay: int,
vtherm_entity_id: str = None,
max_on_percent: float = None,
) -> None: ) -> None:
"""Initialisation of the Proportional Algorithm""" """Initialisation of the Proportional Algorithm"""
_LOGGER.debug( _LOGGER.debug(
"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", # pylint: disable=line-too-long
vtherm_entity_id,
function_type, function_type,
tpi_coef_int, tpi_coef_int,
tpi_coef_ext, tpi_coef_ext,
cycle_min, cycle_min,
minimal_activation_delay, minimal_activation_delay,
) )
# Issue 506 - check parameters
if (
vtherm_entity_id is None
or not is_number(tpi_coef_int)
or not is_number(tpi_coef_ext)
or not is_number(cycle_min)
or not is_number(minimal_activation_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",
vtherm_entity_id,
function_type,
vtherm_entity_id,
tpi_coef_int,
tpi_coef_ext,
cycle_min,
minimal_activation_delay,
)
raise TypeError(
"TPI parameters are not set correctly. VTherm will not work as expected. Please reconfigure it correctly. See previous log for values"
)
self._vtherm_entity_id = vtherm_entity_id
self._function = function_type self._function = function_type
self._tpi_coef_int = tpi_coef_int self._tpi_coef_int = tpi_coef_int
self._tpi_coef_ext = tpi_coef_ext self._tpi_coef_ext = tpi_coef_ext
@@ -45,6 +79,7 @@ class PropAlgorithm:
self._off_time_sec = self._cycle_min * 60 self._off_time_sec = self._cycle_min * 60
self._security = False self._security = False
self._default_on_percent = 0 self._default_on_percent = 0
self._max_on_percent = max_on_percent
def calculate( def calculate(
self, self,
@@ -57,16 +92,19 @@ class PropAlgorithm:
if target_temp is None or current_temp is None: if target_temp is None or current_temp is None:
log = _LOGGER.debug if hvac_mode == HVACMode.OFF else _LOGGER.warning log = _LOGGER.debug if hvac_mode == HVACMode.OFF else _LOGGER.warning
log( log(
"Proportional algorithm: calculation is not possible cause target_temp or current_temp is null. Heating/cooling will be disabled" # pylint: disable=line-too-long "%s - Proportional algorithm: calculation is not possible cause target_temp (%s) or current_temp (%s) is null. Heating/cooling will be disabled. This could be normal at startup", # pylint: disable=line-too-long
self._vtherm_entity_id,
target_temp,
current_temp,
) )
self._calculated_on_percent = 0 self._calculated_on_percent = 0
else: else:
if hvac_mode == HVACMode.COOL: if hvac_mode == HVACMode.COOL:
delta_temp = current_temp - target_temp delta_temp = current_temp - target_temp
delta_ext_temp = ( delta_ext_temp = (
ext_current_temp ext_current_temp - target_temp
if ext_current_temp is not None if ext_current_temp is not None
else 0 - target_temp else 0
) )
else: else:
delta_temp = target_temp - current_temp delta_temp = target_temp - current_temp
@@ -83,7 +121,8 @@ class PropAlgorithm:
) )
else: else:
_LOGGER.warning( _LOGGER.warning(
"Proportional algorithm: unknown %s function. Heating will be disabled", "%s - Proportional algorithm: unknown %s function. Heating will be disabled",
self._vtherm_entity_id,
self._function, self._function,
) )
self._calculated_on_percent = 0 self._calculated_on_percent = 0
@@ -91,7 +130,8 @@ class PropAlgorithm:
self._calculate_internal() self._calculate_internal()
_LOGGER.debug( _LOGGER.debug(
"heating percent calculated for current_temp %.1f, ext_current_temp %.1f and target_temp %.1f is %.2f, on_time is %d (sec), off_time is %d (sec)", # pylint: disable=line-too-long "%s - heating percent calculated for current_temp %.1f, ext_current_temp %.1f and target_temp %.1f is %.2f, on_time is %d (sec), off_time is %d (sec)", # pylint: disable=line-too-long
self._vtherm_entity_id,
current_temp if current_temp else -9999.0, current_temp if current_temp else -9999.0,
ext_current_temp if ext_current_temp else -9999.0, ext_current_temp if ext_current_temp else -9999.0,
target_temp if target_temp else -9999.0, target_temp if target_temp else -9999.0,
@@ -110,11 +150,12 @@ class PropAlgorithm:
self._calculated_on_percent = 0 self._calculated_on_percent = 0
if self._security: if self._security:
_LOGGER.debug(
"Security is On using the default_on_percent %f",
self._default_on_percent,
)
self._on_percent = self._default_on_percent self._on_percent = self._default_on_percent
_LOGGER.info(
"%s - Security is On using the default_on_percent %f",
self._vtherm_entity_id,
self._on_percent,
)
else: else:
_LOGGER.debug( _LOGGER.debug(
"Security is Off using the calculated_on_percent %f", "Security is Off using the calculated_on_percent %f",
@@ -122,19 +163,23 @@ class PropAlgorithm:
) )
self._on_percent = self._calculated_on_percent self._on_percent = self._calculated_on_percent
if self._max_on_percent is not None and self._on_percent > self._max_on_percent:
_LOGGER.debug(
"%s - Heating period clamped to %s (instead of %s) due to max_on_percent setting.",
self._vtherm_entity_id,
self._max_on_percent,
self._on_percent,
)
self._on_percent = self._max_on_percent
self._on_time_sec = self._on_percent * self._cycle_min * 60 self._on_time_sec = self._on_percent * self._cycle_min * 60
# Do not heat for less than xx sec # Do not heat for less than xx sec
if self._on_time_sec < self._minimal_activation_delay: if self._on_time_sec < self._minimal_activation_delay:
if self._on_time_sec > 0: if self._on_time_sec > 0:
_LOGGER.info( _LOGGER.info(
"No heating period due to heating period too small (%f < %f)", "%s - No heating period due to heating period too small (%f < %f)",
self._on_time_sec, self._vtherm_entity_id,
self._minimal_activation_delay,
)
else:
_LOGGER.debug(
"No heating period due to heating period too small (%f < %f)",
self._on_time_sec, self._on_time_sec,
self._minimal_activation_delay, self._minimal_activation_delay,
) )
@@ -144,12 +189,18 @@ class PropAlgorithm:
def set_security(self, default_on_percent: float): def set_security(self, default_on_percent: float):
"""Set a default value for on_percent (used for safety mode)""" """Set a default value for on_percent (used for safety mode)"""
_LOGGER.info(
"%s - Proportional Algo - set security to ON", self._vtherm_entity_id
)
self._security = True self._security = True
self._default_on_percent = default_on_percent self._default_on_percent = default_on_percent
self._calculate_internal() self._calculate_internal()
def unset_security(self): def unset_security(self):
"""Unset the safety mode""" """Unset the safety mode"""
_LOGGER.info(
"%s - Proportional Algo - set security to OFF", self._vtherm_entity_id
)
self._security = False self._security = False
self._calculate_internal() self._calculate_internal()

View File

@@ -3,21 +3,20 @@
""" Implements the VersatileThermostat select component """ """ Implements the VersatileThermostat select component """
import logging import logging
from homeassistant.const import EVENT_HOMEASSISTANT_START from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, CoreState, callback
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN
from homeassistant.components.select import SelectEntity from homeassistant.components.select import SelectEntity
from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType from homeassistant.helpers.device_registry import DeviceInfo, DeviceEntryType
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_component import EntityComponent
from custom_components.versatile_thermostat.base_thermostat import ( from custom_components.versatile_thermostat.base_thermostat import (
BaseThermostat,
ConfigData, ConfigData,
) )
from custom_components.versatile_thermostat.vtherm_api import VersatileThermostatAPI
from .const import ( from .const import (
DOMAIN, DOMAIN,
DEVICE_MANUFACTURER, DEVICE_MANUFACTURER,
@@ -96,17 +95,20 @@ class CentralModeSelect(SelectEntity, RestoreEntity):
if old_state is not None: if old_state is not None:
self._attr_current_option = old_state.state self._attr_current_option = old_state.state
@callback api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self.hass)
async def _async_startup_internal(*_): api.register_central_mode_select(self)
_LOGGER.debug("%s - Calling async_startup_internal", self)
await self.notify_central_mode_change()
if self.hass.state == CoreState.running: # @callback
await _async_startup_internal() # async def _async_startup_internal(*_):
else: # _LOGGER.debug("%s - Calling async_startup_internal", self)
self.hass.bus.async_listen_once( # await self.notify_central_mode_change()
EVENT_HOMEASSISTANT_START, _async_startup_internal #
) # if self.hass.state == CoreState.running:
# await _async_startup_internal()
# else:
# self.hass.bus.async_listen_once(
# EVENT_HOMEASSISTANT_START, _async_startup_internal
# )
@overrides @overrides
async def async_select_option(self, option: str) -> None: async def async_select_option(self, option: str) -> None:
@@ -120,19 +122,17 @@ class CentralModeSelect(SelectEntity, RestoreEntity):
self._attr_current_option = option self._attr_current_option = option
await self.notify_central_mode_change(old_central_mode=old_option) await self.notify_central_mode_change(old_central_mode=old_option)
@overrides
def select_option(self, option: str) -> None:
"""Change the selected option"""
# Update the VTherms which have temperature in central config
self.hass.create_task(self.async_select_option(option))
async def notify_central_mode_change(self, old_central_mode: str | None = None): async def notify_central_mode_change(self, old_central_mode: str | None = None):
"""Notify all VTherm that the central_mode have change""" """Notify all VTherm that the central_mode have change"""
api: VersatileThermostatAPI = VersatileThermostatAPI.get_vtherm_api(self.hass)
# Update all VTherm states # Update all VTherm states
component: EntityComponent[ClimateEntity] = self.hass.data[CLIMATE_DOMAIN] await api.notify_central_mode_change(old_central_mode)
for entity in component.entities:
if isinstance(entity, BaseThermostat):
_LOGGER.debug(
"Changing the central_mode. We have find %s to update",
entity.name,
)
await entity.check_central_mode(
self._attr_current_option, old_central_mode
)
def __str__(self) -> str: def __str__(self) -> str:
return f"VersatileThermostat-{self.name}" return f"VersatileThermostat-{self.name}"

View File

@@ -17,7 +17,6 @@ from homeassistant.components.sensor import (
SensorEntity, SensorEntity,
SensorDeviceClass, SensorDeviceClass,
SensorStateClass, SensorStateClass,
UnitOfTemperature,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@@ -49,7 +48,9 @@ from .const import (
CONF_THERMOSTAT_CLIMATE, CONF_THERMOSTAT_CLIMATE,
CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_CENTRAL_CONFIG, CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_ADD_CENTRAL_BOILER_CONTROL, CONF_USE_CENTRAL_BOILER_FEATURE,
CONF_AUTO_REGULATION_VALVE,
CONF_AUTO_REGULATION_MODE,
overrides, overrides,
) )
@@ -71,15 +72,17 @@ async def async_setup_entry(
unique_id = entry.entry_id unique_id = entry.entry_id
name = entry.data.get(CONF_NAME) name = entry.data.get(CONF_NAME)
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE) vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
have_valve_regulation = (
entry.data.get(CONF_AUTO_REGULATION_MODE) == CONF_AUTO_REGULATION_VALVE
)
entities = None entities = None
if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG: if vt_type == CONF_THERMOSTAT_CENTRAL_CONFIG:
if entry.data.get(CONF_ADD_CENTRAL_BOILER_CONTROL): if entry.data.get(CONF_USE_CENTRAL_BOILER_FEATURE):
entities = [ entities = [
NbActiveDeviceForBoilerSensor(hass, unique_id, name, entry.data) NbActiveDeviceForBoilerSensor(hass, unique_id, name, entry.data)
] ]
async_add_entities(entities, True)
else: else:
entities = [ entities = [
LastTemperatureSensor(hass, unique_id, name, entry.data), LastTemperatureSensor(hass, unique_id, name, entry.data),
@@ -100,14 +103,21 @@ async def async_setup_entry(
entities.append(OnTimeSensor(hass, unique_id, name, entry.data)) entities.append(OnTimeSensor(hass, unique_id, name, entry.data))
entities.append(OffTimeSensor(hass, unique_id, name, entry.data)) entities.append(OffTimeSensor(hass, unique_id, name, entry.data))
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE: if (
entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_VALVE
or have_valve_regulation
):
entities.append(ValveOpenPercentSensor(hass, unique_id, name, entry.data)) entities.append(ValveOpenPercentSensor(hass, unique_id, name, entry.data))
if entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE: if (
entry.data.get(CONF_THERMOSTAT_TYPE) == CONF_THERMOSTAT_CLIMATE
and not have_valve_regulation
):
entities.append( entities.append(
RegulatedTemperatureSensor(hass, unique_id, name, entry.data) RegulatedTemperatureSensor(hass, unique_id, name, entry.data)
) )
if entities:
async_add_entities(entities, True) async_add_entities(entities, True)
@@ -123,17 +133,17 @@ class EnergySensor(VersatileThermostatBaseEntity, SensorEntity):
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
if math.isnan(self.my_climate.total_energy) or math.isinf( energy = self.my_climate.total_energy
self.my_climate.total_energy if energy is None:
): return
if math.isnan(energy) or math.isinf(energy):
raise ValueError(f"Sensor has illegal state {self.my_climate.total_energy}") raise ValueError(f"Sensor has illegal state {self.my_climate.total_energy}")
old_state = self._attr_native_value old_state = self._attr_native_value
self._attr_native_value = round( self._attr_native_value = round(energy, self.suggested_display_precision)
self.my_climate.total_energy, self.suggested_display_precision
)
if old_state != self._attr_native_value: if old_state != self._attr_native_value:
self.async_write_ha_state() self.async_write_ha_state()
return return
@@ -178,7 +188,7 @@ class MeanPowerSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
if math.isnan(float(self.my_climate.mean_cycle_power)) or math.isinf( if math.isnan(float(self.my_climate.mean_cycle_power)) or math.isinf(
self.my_climate.mean_cycle_power self.my_climate.mean_cycle_power
@@ -235,7 +245,7 @@ class OnPercentSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
on_percent = ( on_percent = (
float(self.my_climate.proportional_algorithm.on_percent) float(self.my_climate.proportional_algorithm.on_percent)
@@ -290,7 +300,7 @@ class ValveOpenPercentSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_native_value old_state = self._attr_native_value
self._attr_native_value = self.my_climate.valve_open_percent self._attr_native_value = self.my_climate.valve_open_percent
@@ -332,7 +342,7 @@ class OnTimeSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
on_time = ( on_time = (
float(self.my_climate.proportional_algorithm.on_time_sec) float(self.my_climate.proportional_algorithm.on_time_sec)
@@ -381,7 +391,7 @@ class OffTimeSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
off_time = ( off_time = (
float(self.my_climate.proportional_algorithm.off_time_sec) float(self.my_climate.proportional_algorithm.off_time_sec)
@@ -429,7 +439,7 @@ class LastTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_native_value old_state = self._attr_native_value
self._attr_native_value = self.my_climate.last_temperature_measure self._attr_native_value = self.my_climate.last_temperature_measure
@@ -458,7 +468,7 @@ class LastExtTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
old_state = self._attr_native_value old_state = self._attr_native_value
self._attr_native_value = self.my_climate.last_ext_temperature_measure self._attr_native_value = self.my_climate.last_ext_temperature_measure
@@ -487,7 +497,7 @@ class TemperatureSlopeSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
last_slope = self.my_climate.last_temperature_slope last_slope = self.my_climate.last_temperature_slope
if last_slope is None: if last_slope is None:
@@ -540,7 +550,7 @@ class RegulatedTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
new_temp = self.my_climate.regulated_target_temp new_temp = self.my_climate.regulated_target_temp
if new_temp is None: if new_temp is None:
@@ -570,7 +580,7 @@ class RegulatedTemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
@property @property
def native_unit_of_measurement(self) -> str | None: def native_unit_of_measurement(self) -> str | None:
if not self.my_climate: if not self.my_climate:
return UnitOfTemperature.CELSIUS return self.hass.config.units.temperature_unit
return self.my_climate.temperature_unit return self.my_climate.temperature_unit
@property @property
@@ -591,7 +601,7 @@ class EMATemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
@callback @callback
async def async_my_climate_changed(self, event: Event = None): async def async_my_climate_changed(self, event: Event = None):
"""Called when my climate have change""" """Called when my climate have change"""
_LOGGER.debug("%s - climate state change", self._attr_unique_id) # _LOGGER.debug("%s - climate state change", self._attr_unique_id)
new_ema = self.my_climate.ema_temperature new_ema = self.my_climate.ema_temperature
if new_ema is None: if new_ema is None:
@@ -621,7 +631,7 @@ class EMATemperatureSensor(VersatileThermostatBaseEntity, SensorEntity):
@property @property
def native_unit_of_measurement(self) -> str | None: def native_unit_of_measurement(self) -> str | None:
if not self.my_climate: if not self.my_climate:
return UnitOfTemperature.CELSIUS return self.hass.config.units.temperature_unit
return self.my_climate.temperature_unit return self.my_climate.temperature_unit
@property @property
@@ -722,21 +732,23 @@ class NbActiveDeviceForBoilerSensor(SensorEntity):
"""Calculate the number of active VTherm that have an """Calculate the number of active VTherm that have an
influence on central boiler""" influence on central boiler"""
_LOGGER.debug("%s - calculating the number of active VTherm", self) _LOGGER.debug(
"%s - calculating the number of active underlying device for boiler activation",
self,
)
nb_active = 0 nb_active = 0
for entity in self._entities: for entity in self._entities:
_LOGGER.debug( _LOGGER.debug(
"Examining the hvac_action of %s", "Examining the hvac_action of %s",
entity.name, entity.name,
) )
if ( nb_active += entity.nb_device_actives
entity.hvac_mode == HVACMode.HEAT
and entity.hvac_action == HVACAction.HEATING
):
for under in entity.underlying_entities:
nb_active += 1 if under.is_device_active else 0
self._attr_native_value = nb_active self._attr_native_value = nb_active
_LOGGER.debug(
"%s - Number of active underlying entities is %s", self, nb_active
)
self.async_write_ha_state() self.async_write_ha_state()
def __str__(self): def __str__(self):

View File

@@ -14,7 +14,7 @@
}, },
"menu": { "menu": {
"title": "Menu", "title": "Menu",
"description": "Configure your thermostat. You will be able to finalize the configuration when all needed parameters are valued.", "description": "Configure your thermostat. You will be able to finalize the configuration when all required parameters are entered.",
"menu_options": { "menu_options": {
"main": "Main attributes", "main": "Main attributes",
"central_boiler": "Central boiler", "central_boiler": "Central boiler",
@@ -27,7 +27,10 @@
"power": "Power management", "power": "Power management",
"presence": "Presence detection", "presence": "Presence detection",
"advanced": "Advanced parameters", "advanced": "Advanced parameters",
"finalize": "All done" "auto_start_stop": "Auto start and stop",
"valve_regulation": "Valve regulation configuration",
"finalize": "All done",
"configuration_not_complete": "Configuration not complete"
} }
}, },
"main": { "main": {
@@ -36,19 +39,21 @@
"data": { "data": {
"name": "Name", "name": "Name",
"thermostat_type": "Thermostat type", "thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Room temperature sensor entity id", "temperature_sensor_entity_id": "Room temperature",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature datetime",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id", "external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)", "cycle_min": "Cycle duration (minutes)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimum temperature allowed",
"temp_max": "Maximal temperature allowed", "temp_max": "Maximum temperature allowed",
"step_temperature": "Temperature step", "step_temperature": "Temperature step",
"device_power": "Device power", "device_power": "Device power",
"use_central_mode": "Enable the control by central entity (need central config). Check to enable the control of the VTherm with the select central_mode entities.", "use_central_mode": "Enable the control by central entity (requires central config). Check to enable the control of the VTherm with the select central_mode entities.",
"use_main_central_config": "Use additional central main configuration. Check to use the central main configuration (outdoor temperature, min, max, step, ...).", "use_main_central_config": "Use additional central main configuration. Check to use the central main configuration (outdoor temperature, min, max, step, ...).",
"add_central_boiler_control": "Add a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm need heating, the boiler will be turned on. If no VTherm needs heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the next configuration page",
"used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler" "used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler"
}, },
"data_description": { "data_description": {
"temperature_sensor_entity_id": "Room temperature sensor entity id",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature sensor entity id. Should be datetime sensor",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected" "external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
} }
}, },
@@ -59,57 +64,37 @@
"use_window_feature": "Use window detection", "use_window_feature": "Use window detection",
"use_motion_feature": "Use motion detection", "use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management", "use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection" "use_presence_feature": "Use presence detection",
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after selecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
"use_auto_start_stop_feature": "Use the auto start and stop feature"
} }
}, },
"type": { "type": {
"title": "Linked entities", "title": "Linked entities",
"description": "Linked entities attributes", "description": "Linked entities attributes",
"data": { "data": {
"heater_entity_id": "1st heater switch", "underlying_entity_ids": "The device(s) to be controlled",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"heater_keep_alive": "Switch keep-alive interval in seconds", "heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm", "proportional_function": "Algorithm",
"climate_entity_id": "1st underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode", "ac_mode": "AC mode",
"valve_entity_id": "1st valve number",
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation", "auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold", "auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period", "auto_regulation_periode_min": "Regulation minimum period",
"auto_regulation_use_device_temp": "Use internal temperature of the underlying", "auto_regulation_use_device_temp": "Use internal temperature of the underlying",
"inverse_switch_command": "Inverse switch command", "inverse_switch_command": "Inverse switch command",
"auto_fan_mode": " Auto fan mode" "auto_fan_mode": "Auto fan mode"
}, },
"data_description": { "data_description": {
"heater_entity_id": "Mandatory heater entity id", "underlying_entity_ids": "The device(s) to be controlled - 1 is required",
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not used",
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.", "heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
"proportional_function": "Algorithm to use (TPI is the only one for now)", "proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id",
"climate_entity2_id": "2nd underlying climate entity id",
"climate_entity3_id": "3rd underlying climate entity id",
"climate_entity4_id": "4th underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode", "ac_mode": "Use the Air Conditioning (AC) mode",
"valve_entity_id": "1st valve number entity id",
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature", "auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent", "auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update", "auto_regulation_periode_min": "Duration in minutes between two regulation update",
"auto_regulation_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation", "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", "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"
} }
}, },
"tpi": { "tpi": {
@@ -128,7 +113,7 @@
}, },
"presets": { "presets": {
"title": "Presets", "title": "Presets",
"description": "Check if the thermostat will use central presets. Uncheck and the thermostat will have its own preset entities", "description": "Select if the thermostat will use central preset - deselect for the thermostat to have its own presets",
"data": { "data": {
"use_presets_central_config": "Use central presets configuration" "use_presets_central_config": "Use central presets configuration"
} }
@@ -151,8 +136,8 @@
"window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used", "window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used",
"window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used", "window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used",
"window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used", "window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used",
"use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm", "use_window_central_config": "Select to use the central window configuration. Deselect to use a specific window configuration for this VTherm",
"window_action": "Action to do if window is deteted as open" "window_action": "Action to perform if window is deteted as open"
} }
}, },
"motion": { "motion": {
@@ -177,7 +162,7 @@
}, },
"power": { "power": {
"title": "Power management", "title": "Power management",
"description": "Power management attributes.\nGives the power and max power sensor of your home.\nThen specify the power consumption of the heater when on.\nAll sensors and device power should have the same unit (kW or W).", "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).",
"data": { "data": {
"power_sensor_entity_id": "Power", "power_sensor_entity_id": "Power",
"max_power_sensor_entity_id": "Max power", "max_power_sensor_entity_id": "Max power",
@@ -196,7 +181,7 @@
"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.",
"data": { "data": {
"presence_sensor_entity_id": "Presence sensor", "presence_sensor_entity_id": "Presence sensor",
"use_presence_central_config": "Use central presence temperature configuration. Uncheck to use specific temperature entities" "use_presence_central_config": "Use central presence temperature configuration. Deselect to use specific temperature entities"
}, },
"data_description": { "data_description": {
"presence_sensor_entity_id": "Presence sensor entity id" "presence_sensor_entity_id": "Presence sensor entity id"
@@ -206,26 +191,54 @@
"title": "Advanced parameters", "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.",
"data": { "data": {
"minimal_activation_delay": "Minimal activation delay", "minimal_activation_delay": "Minimum activation delay",
"security_delay_min": "Safety delay (in minutes)", "security_delay_min": "Safety delay (in minutes)",
"security_min_on_percent": "Minimal power percent to enable safety mode", "security_min_on_percent": "Minimum power percent to enable safety mode",
"security_default_on_percent": "Power percent to use in safety mode", "security_default_on_percent": "Power percent to use in safety mode",
"use_advanced_central_config": "Use central advanced configuration" "use_advanced_central_config": "Use central advanced configuration"
}, },
"data_description": { "data_description": {
"minimal_activation_delay": "Delay in seconds under which the equipment will not be activated", "minimal_activation_delay": "Delay in seconds under which the equipment will not be activated",
"security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state", "security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state",
"security_min_on_percent": "Minimal heating percent value for safety preset activation. Below this amount of power percent the thermostat won't go into safety preset", "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", "security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm" "use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
} }
},
"central_boiler": {
"title": "Control of the central boiler",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
"data": {
"central_boiler_activation_service": "Command to turn-on",
"central_boiler_deactivation_service": "Command to turn-off"
},
"data_description": {
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
}
},
"valve_regulation": {
"title": "Self-regulation with valve",
"description": "Configuration for self-regulation with direct control of the valve",
"data": {
"offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities",
"closing_degree_entity_ids": "Closing degree entities",
"proportional_function": "Algorithm"
},
"data_description": {
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have 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",
"proportional_function": "Algorithm to use (TPI is the only one for now)"
}
} }
}, },
"error": { "error": {
"unknown": "Unexpected error", "unknown": "Unexpected error",
"unknown_entity": "Unknown entity id", "unknown_entity": "Unknown entity id",
"window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both", "window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both",
"no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it." "no_central_config": "You cannot select 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it."
}, },
"abort": { "abort": {
"already_configured": "Device is already configured" "already_configured": "Device is already configured"
@@ -245,7 +258,7 @@
}, },
"menu": { "menu": {
"title": "Menu", "title": "Menu",
"description": "Configure your thermostat. You will be able to finalize the configuration when all needed parameters are valued.", "description": "Configure your thermostat. You will be able to finalize the configuration when all required parameters are entered.",
"menu_options": { "menu_options": {
"main": "Main attributes", "main": "Main attributes",
"central_boiler": "Central boiler", "central_boiler": "Central boiler",
@@ -258,7 +271,10 @@
"power": "Power management", "power": "Power management",
"presence": "Presence detection", "presence": "Presence detection",
"advanced": "Advanced parameters", "advanced": "Advanced parameters",
"finalize": "All done" "auto_start_stop": "Auto start and stop",
"valve_regulation": "Valve regulation configuration",
"finalize": "All done",
"configuration_not_complete": "Configuration not complete"
} }
}, },
"main": { "main": {
@@ -267,19 +283,21 @@
"data": { "data": {
"name": "Name", "name": "Name",
"thermostat_type": "Thermostat type", "thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Room temperature sensor entity id", "temperature_sensor_entity_id": "Room temperature",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature datetime",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id", "external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)", "cycle_min": "Cycle duration (minutes)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimum temperature allowed",
"temp_max": "Maximal temperature allowed", "temp_max": "Maximum temperature allowed",
"step_temperature": "Temperature step", "step_temperature": "Temperature step",
"device_power": "Device power", "device_power": "Device power",
"use_central_mode": "Enable the control by central entity (need central config). Check to enable the control of the VTherm with the select central_mode entities.", "use_central_mode": "Enable the control by central entity (requires central config). Check to enable the control of the VTherm with the select central_mode entities.",
"use_main_central_config": "Use additional central main configuration. Check to use the central main configuration (outdoor temperature, min, max, step, ...).", "use_main_central_config": "Use additional central main configuration. Check to use the central main configuration (outdoor temperature, min, max, step, ...).",
"add_central_boiler_control": "Add a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm need heating, the boiler will be turned on. If no VTherm needs heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the next configuration page",
"used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler" "used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler"
}, },
"data_description": { "data_description": {
"temperature_sensor_entity_id": "Room temperature sensor entity id",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature sensor entity id. Should be datetime sensor",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected" "external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
} }
}, },
@@ -290,57 +308,37 @@
"use_window_feature": "Use window detection", "use_window_feature": "Use window detection",
"use_motion_feature": "Use motion detection", "use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management", "use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection" "use_presence_feature": "Use presence detection",
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after selecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
"use_auto_start_stop_feature": "Use the auto start and stop feature"
} }
}, },
"type": { "type": {
"title": "Entities - {name}", "title": "Entities - {name}",
"description": "Linked entities attributes", "description": "Linked entities attributes",
"data": { "data": {
"heater_entity_id": "1st heater switch", "underlying_entity_ids": "The device(s) to be controlled",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"heater_keep_alive": "Switch keep-alive interval in seconds", "heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm", "proportional_function": "Algorithm",
"climate_entity_id": "1st underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode", "ac_mode": "AC mode",
"valve_entity_id": "1st valve number",
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation", "auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold", "auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period", "auto_regulation_periode_min": "Regulation minimum period",
"auto_regulation_use_device_temp": "Use internal temperature of the underlying", "auto_regulation_use_device_temp": "Use internal temperature of the underlying",
"inverse_switch_command": "Inverse switch command", "inverse_switch_command": "Inverse switch command",
"auto_fan_mode": " Auto fan mode" "auto_fan_mode": "Auto fan mode"
}, },
"data_description": { "data_description": {
"heater_entity_id": "Mandatory heater entity id", "underlying_entity_ids": "The device(s) to be controlled - 1 is required",
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not used",
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.", "heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
"proportional_function": "Algorithm to use (TPI is the only one for now)", "proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id",
"climate_entity2_id": "2nd underlying climate entity id",
"climate_entity3_id": "3rd underlying climate entity id",
"climate_entity4_id": "4th underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode", "ac_mode": "Use the Air Conditioning (AC) mode",
"valve_entity_id": "1st valve number entity id",
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature", "auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent", "auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update", "auto_regulation_periode_min": "Duration in minutes between two regulation update",
"auto_regulation_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation", "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", "inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" "auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary"
} }
}, },
"tpi": { "tpi": {
@@ -437,19 +435,47 @@
"title": "Advanced - {name}", "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": "Advanced parameters. Leave the default values if you don't know what you are doing.\nThese parameters can lead to very poor temperature control or bad power regulation.",
"data": { "data": {
"minimal_activation_delay": "Minimal activation delay", "minimal_activation_delay": "Minimum activation delay",
"security_delay_min": "Safety delay (in minutes)", "security_delay_min": "Safety delay (in minutes)",
"security_min_on_percent": "Minimal power percent to enable safety mode", "security_min_on_percent": "Minimum power percent to enable safety mode",
"security_default_on_percent": "Power percent to use in safety mode", "security_default_on_percent": "Power percent to use in safety mode",
"use_advanced_central_config": "Use central advanced configuration" "use_advanced_central_config": "Use central advanced configuration"
}, },
"data_description": { "data_description": {
"minimal_activation_delay": "Delay in seconds under which the equipment will not be activated", "minimal_activation_delay": "Delay in seconds under which the equipment will not be activated",
"security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state", "security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state",
"security_min_on_percent": "Minimal heating percent value for safety preset activation. Below this amount of power percent the thermostat won't go into safety preset", "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", "security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm" "use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
} }
},
"central_boiler": {
"title": "Control of the central boiler - {name}",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
"data": {
"central_boiler_activation_service": "Command to turn-on",
"central_boiler_deactivation_service": "Command to turn-off"
},
"data_description": {
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
}
},
"valve_regulation": {
"title": "Self-regulation with valve - {name}",
"description": "Configuration for self-regulation with direct control of the valve",
"data": {
"offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities",
"closing_degree_entity_ids": "Closing degree entities",
"proportional_function": "Algorithm"
},
"data_description": {
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have 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",
"proportional_function": "Algorithm to use (TPI is the only one for now)"
}
} }
}, },
"error": { "error": {
@@ -457,7 +483,8 @@
"unknown_entity": "Unknown entity id", "unknown_entity": "Unknown entity id",
"window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both", "window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both",
"no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.", "no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.",
"service_configuration_format": "The format of the service configuration is wrong" "service_configuration_format": "The format of the service configuration is wrong",
"valve_regulation_nb_entities_incorrect": "The number of valve entities for valve regulation should be equal to the number of underlyings"
}, },
"abort": { "abort": {
"already_configured": "Device is already configured" "already_configured": "Device is already configured"
@@ -479,7 +506,8 @@
"auto_regulation_medium": "Medium", "auto_regulation_medium": "Medium",
"auto_regulation_light": "Light", "auto_regulation_light": "Light",
"auto_regulation_expert": "Expert", "auto_regulation_expert": "Expert",
"auto_regulation_none": "No auto-regulation" "auto_regulation_none": "No auto-regulation",
"auto_regulation_valve": "Direct control of valve"
} }
}, },
"auto_fan_mode": { "auto_fan_mode": {
@@ -506,6 +534,14 @@
"comfort": "Comfort", "comfort": "Comfort",
"boost": "Boost" "boost": "Boost"
} }
},
"auto_start_stop": {
"options": {
"auto_start_stop_none": "No auto start/stop",
"auto_start_stop_slow": "Slow detection",
"auto_start_stop_medium": "Medium detection",
"auto_start_stop_fast": "Fast detection"
}
} }
}, },
"entity": { "entity": {
@@ -516,7 +552,8 @@
"state": { "state": {
"power": "Shedding", "power": "Shedding",
"security": "Safety", "security": "Safety",
"none": "Manual" "none": "Manual",
"frost": "Frost"
} }
} }
} }

View File

@@ -0,0 +1,168 @@
## pylint: disable=unused-argument
""" Implements the VersatileThermostat select component """
import logging
from typing import Any
from homeassistant.core import HomeAssistant, callback
from homeassistant.components.switch import SwitchEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .commons import VersatileThermostatBaseEntity
from .const import * # pylint: disable=unused-wildcard-import,wildcard-import
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the VersatileThermostat switches with config flow."""
_LOGGER.debug(
"Calling async_setup_entry entry=%s, data=%s", entry.entry_id, entry.data
)
unique_id = entry.entry_id
name = entry.data.get(CONF_NAME)
vt_type = entry.data.get(CONF_THERMOSTAT_TYPE)
auto_start_stop_feature = entry.data.get(CONF_USE_AUTO_START_STOP_FEATURE)
entities = []
if vt_type == CONF_THERMOSTAT_CLIMATE:
entities.append(FollowUnderlyingTemperatureChange(hass, unique_id, name, entry))
if auto_start_stop_feature is True:
# Creates a switch to enable the auto-start/stop
enable_entity = AutoStartStopEnable(hass, unique_id, name, entry)
entities.append(enable_entity)
async_add_entities(entities, True)
class AutoStartStopEnable(VersatileThermostatBaseEntity, SwitchEntity, RestoreEntity):
"""The that enables the ManagedDevice optimisation with"""
def __init__(
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigEntry
):
super().__init__(hass, unique_id, name)
self._attr_name = "Enable auto start/stop"
self._attr_unique_id = f"{self._device_name}_enable_auto_start_stop"
self._default_value = (
entry_infos.data.get(CONF_AUTO_START_STOP_LEVEL)
!= AUTO_START_STOP_LEVEL_NONE
)
self._attr_is_on = self._default_value
@property
def icon(self) -> str | None:
"""The icon"""
return "mdi:power-sleep"
async def async_added_to_hass(self):
await super().async_added_to_hass()
# Récupérer le dernier état sauvegardé de l'entité
last_state = await self.async_get_last_state()
# Si l'état précédent existe, vous pouvez l'utiliser
if last_state is not None:
self._attr_is_on = last_state.state == "on"
else:
# If no previous state set it to false by default
self._attr_is_on = self._default_value
self.update_my_state_and_vtherm()
def update_my_state_and_vtherm(self):
"""Update the auto_start_stop_enable flag in my VTherm"""
self.async_write_ha_state()
if self.my_climate is not None:
self.my_climate.set_auto_start_stop_enable(self._attr_is_on)
@callback
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on."""
self.turn_on()
@callback
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off."""
self.turn_off()
@overrides
def turn_off(self, **kwargs: Any):
self._attr_is_on = False
self.update_my_state_and_vtherm()
@overrides
def turn_on(self, **kwargs: Any):
self._attr_is_on = True
self.update_my_state_and_vtherm()
class FollowUnderlyingTemperatureChange(
VersatileThermostatBaseEntity, SwitchEntity, RestoreEntity
):
"""The that enables the ManagedDevice optimisation with"""
def __init__(
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigEntry
):
super().__init__(hass, unique_id, name)
self._attr_name = "Follow underlying temp change"
self._attr_unique_id = f"{self._device_name}_follow_underlying_temp_change"
self._attr_is_on = False
@property
def icon(self) -> str | None:
"""The icon"""
return "mdi:content-copy"
async def async_added_to_hass(self):
await super().async_added_to_hass()
# Récupérer le dernier état sauvegardé de l'entité
last_state = await self.async_get_last_state()
# Si l'état précédent existe, vous pouvez l'utiliser
if last_state is not None:
self._attr_is_on = last_state.state == "on"
else:
# If no previous state set it to false by default
self._attr_is_on = False
self.update_my_state_and_vtherm()
def update_my_state_and_vtherm(self):
"""Update the follow flag in my VTherm"""
self.async_write_ha_state()
if self.my_climate is not None:
self.my_climate.set_follow_underlying_temp_change(self._attr_is_on)
@callback
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on."""
self.turn_on()
@callback
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off."""
self.turn_off()
@overrides
def turn_off(self, **kwargs: Any):
self._attr_is_on = False
self.update_my_state_and_vtherm()
@overrides
def turn_on(self, **kwargs: Any):
self._attr_is_on = True
self.update_my_state_and_vtherm()

View File

@@ -1,64 +1,45 @@
# pylint: disable=line-too-long, too-many-lines # pylint: disable=line-too-long, too-many-lines, abstract-method
""" A climate over switch classe """ """ A climate over climate classe """
import logging import logging
from datetime import timedelta, datetime from datetime import timedelta, datetime
from homeassistant.core import HomeAssistant, State, callback from homeassistant.const import STATE_ON
from homeassistant.core import Event, HomeAssistant, State, callback
from homeassistant.helpers.event import ( from homeassistant.helpers.event import (
async_track_state_change_event, async_track_state_change_event,
async_track_time_interval, async_track_time_interval,
EventStateChangedData, EventStateChangedData,
) )
from homeassistant.helpers.typing import EventType as HASSEventType
from homeassistant.components.climate import ( from homeassistant.components.climate import (
HVACAction, HVACAction,
HVACMode, HVACMode,
ClimateEntityFeature, ClimateEntityFeature,
) )
from .commons import NowClass, round_to_nearest from .commons import round_to_nearest
from .base_thermostat import BaseThermostat, ConfigData from .base_thermostat import BaseThermostat, ConfigData
from .pi_algorithm import PITemperatureRegulator from .pi_algorithm import PITemperatureRegulator
from .const import ( from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
overrides,
DOMAIN,
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
CONF_AUTO_REGULATION_MODE,
CONF_AUTO_REGULATION_NONE,
CONF_AUTO_REGULATION_SLOW,
CONF_AUTO_REGULATION_LIGHT,
CONF_AUTO_REGULATION_MEDIUM,
CONF_AUTO_REGULATION_STRONG,
CONF_AUTO_REGULATION_EXPERT,
CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN,
CONF_AUTO_REGULATION_USE_DEVICE_TEMP,
CONF_AUTO_FAN_MODE,
CONF_AUTO_FAN_NONE,
CONF_AUTO_FAN_LOW,
CONF_AUTO_FAN_MEDIUM,
CONF_AUTO_FAN_HIGH,
CONF_AUTO_FAN_TURBO,
RegulationParamSlow,
RegulationParamLight,
RegulationParamMedium,
RegulationParamStrong,
AUTO_FAN_DTEMP_THRESHOLD,
AUTO_FAN_DEACTIVATED_MODES,
UnknownEntity,
)
from .vtherm_api import VersatileThermostatAPI from .vtherm_api import VersatileThermostatAPI
from .underlyings import UnderlyingClimate from .underlyings import UnderlyingClimate
from .auto_start_stop_algorithm import (
AutoStartStopDetectionAlgorithm,
AUTO_START_STOP_ACTION_OFF,
AUTO_START_STOP_ACTION_ON,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
HVAC_ACTION_ON = [ # pylint: disable=invalid-name
HVACAction.COOLING,
HVACAction.DRYING,
HVACAction.FAN,
HVACAction.HEATING,
]
class ThermostatOverClimate(BaseThermostat): class ThermostatOverClimate(BaseThermostat[UnderlyingClimate]):
"""Representation of a base class for a Versatile Thermostat over a climate""" """Representation of a base class for a Versatile Thermostat over a climate"""
_auto_regulation_mode: str | None = None _auto_regulation_mode: str | None = None
@@ -74,26 +55,31 @@ class ThermostatOverClimate(BaseThermostat):
# The fan_mode name depending of the current_mode # The fan_mode name depending of the current_mode
_auto_activated_fan_mode: str | None = None _auto_activated_fan_mode: str | None = None
_auto_deactivated_fan_mode: str | None = None _auto_deactivated_fan_mode: str | None = None
_auto_start_stop_level: TYPE_AUTO_START_STOP_LEVELS = AUTO_START_STOP_LEVEL_NONE
_auto_start_stop_algo: AutoStartStopDetectionAlgorithm | None = None
_is_auto_start_stop_enabled: bool = False
_follow_underlying_temp_change: bool = False
_entity_component_unrecorded_attributes = ( _entity_component_unrecorded_attributes = BaseThermostat._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
BaseThermostat._entity_component_unrecorded_attributes.union( frozenset(
frozenset( {
{ "is_over_climate",
"is_over_climate", "start_hvac_action_date",
"start_hvac_action_date", "underlying_entities",
"underlying_climate_0", "regulation_accumulated_error",
"underlying_climate_1", "auto_regulation_mode",
"underlying_climate_2", "auto_fan_mode",
"underlying_climate_3", "current_auto_fan_mode",
"regulation_accumulated_error", "auto_activated_fan_mode",
"auto_regulation_mode", "auto_deactivated_fan_mode",
"auto_fan_mode", "auto_regulation_use_device_temp",
"current_auto_fan_mode", "auto_start_stop_level",
"auto_activated_fan_mode", "auto_start_stop_dtmin",
"auto_deactivated_fan_mode", "auto_start_stop_enable",
"auto_regulation_use_device_temp", "auto_start_stop_accumulated_error",
} "auto_start_stop_accumulated_error_threshold",
) "follow_underlying_temp_change",
}
) )
) )
@@ -104,22 +90,74 @@ class ThermostatOverClimate(BaseThermostat):
# super.__init__ calls post_init at the end. So it must be called after regulation initialization # super.__init__ calls post_init at the end. So it must be called after regulation initialization
super().__init__(hass, unique_id, name, entry_infos) super().__init__(hass, unique_id, name, entry_infos)
self._regulated_target_temp = self.target_temperature self._regulated_target_temp = self.target_temperature
self._last_regulation_change = NowClass.get_now(hass) self._last_regulation_change = None # NowClass.get_now(hass)
@overrides
def post_init(self, config_entry: ConfigData):
"""Initialize the Thermostat"""
super().post_init(config_entry)
for climate in config_entry.get(CONF_UNDERLYING_LIST):
under = UnderlyingClimate(
hass=self._hass,
thermostat=self,
climate_entity_id=climate,
)
self._underlyings.append(under)
self.choose_auto_regulation_mode(
config_entry.get(CONF_AUTO_REGULATION_MODE)
if config_entry.get(CONF_AUTO_REGULATION_MODE) is not None
else CONF_AUTO_REGULATION_NONE
)
self._auto_regulation_dtemp = (
config_entry.get(CONF_AUTO_REGULATION_DTEMP)
if config_entry.get(CONF_AUTO_REGULATION_DTEMP) is not None
else 0.5
)
self._auto_regulation_period_min = (
config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN)
if config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None
else 5
)
self._auto_fan_mode = (
config_entry.get(CONF_AUTO_FAN_MODE)
if config_entry.get(CONF_AUTO_FAN_MODE) is not None
else CONF_AUTO_FAN_NONE
)
self._auto_regulation_use_device_temp = config_entry.get(
CONF_AUTO_REGULATION_USE_DEVICE_TEMP, False
)
use_auto_start_stop = config_entry.get(CONF_USE_AUTO_START_STOP_FEATURE, False)
if use_auto_start_stop:
self._auto_start_stop_level = config_entry.get(
CONF_AUTO_START_STOP_LEVEL, AUTO_START_STOP_LEVEL_NONE
)
else:
self._auto_start_stop_level = AUTO_START_STOP_LEVEL_NONE
# Instanciate the auto start stop algo
self._auto_start_stop_algo = AutoStartStopDetectionAlgorithm(
self._auto_start_stop_level, self.name
)
@property @property
def is_over_climate(self) -> bool: def is_over_climate(self) -> bool:
"""True if the Thermostat is over_climate""" """True if the Thermostat is over_climate"""
return True return True
@property def calculate_hvac_action(self, under_list: list) -> HVACAction | None:
def hvac_action(self) -> HVACAction | None: """Calculate an hvac action based on the hvac_action of the list in argument"""
"""Returns the current hvac_action by checking all hvac_action of the underlyings"""
# if one not IDLE or OFF -> return it # if one not IDLE or OFF -> return it
# else if one IDLE -> IDLE # else if one IDLE -> IDLE
# else OFF # else OFF
one_idle = False one_idle = False
for under in self._underlyings: for under in under_list:
if (action := under.hvac_action) not in [ if (action := under.hvac_action) not in [
HVACAction.IDLE, HVACAction.IDLE,
HVACAction.OFF, HVACAction.OFF,
@@ -131,19 +169,35 @@ class ThermostatOverClimate(BaseThermostat):
return HVACAction.IDLE return HVACAction.IDLE
return HVACAction.OFF return HVACAction.OFF
@property
def hvac_action(self) -> HVACAction | None:
"""Returns the current hvac_action by checking all hvac_action of the underlyings"""
return self.calculate_hvac_action(self._underlyings)
@overrides @overrides
async def _async_internal_set_temperature(self, temperature: float): async def _async_internal_set_temperature(self, temperature: float):
"""Set the target temperature and the target temperature of underlying climate if any""" """Set the target temperature and the target temperature of underlying climate if any"""
await super()._async_internal_set_temperature(temperature) await super()._async_internal_set_temperature(temperature)
self._regulation_algo.set_target_temp(self.target_temperature) self._regulation_algo.set_target_temp(self.target_temperature)
await self._send_regulated_temperature(force=True) # is done by control_heating method. No need to do it here
# await self._send_regulated_temperature(force=True)
async def _send_regulated_temperature(self, force=False): async def _send_regulated_temperature(self, force=False):
"""Sends the regulated temperature to all underlying""" """Sends the regulated temperature to all underlying"""
if self.hvac_mode == HVACMode.OFF: if self.hvac_mode == HVACMode.OFF:
_LOGGER.debug("%s - don't send regulated temperature cause VTherm is off ") _LOGGER.debug(
"%s - don't send regulated temperature cause VTherm is off ", self
)
return
if self.target_temperature is None:
_LOGGER.warning(
"%s - don't send regulated temperature cause VTherm target_temp (%s) is None. This should be a temporary warning message.",
self,
self.target_temperature,
)
return return
_LOGGER.info( _LOGGER.info(
@@ -152,28 +206,37 @@ class ThermostatOverClimate(BaseThermostat):
force, force,
) )
now: datetime = NowClass.get_now(self._hass) if self._last_regulation_change is not None:
period = float((now - self._last_regulation_change).total_seconds()) / 60.0 period = (
if not force and period < self._auto_regulation_period_min: float((self.now - self._last_regulation_change).total_seconds()) / 60.0
_LOGGER.info(
"%s - period (%.1f) min is < %.0f min -> forget the regulation send",
self,
period,
self._auto_regulation_period_min,
) )
return if not force and period < self._auto_regulation_period_min:
_LOGGER.info(
"%s - period (%.1f) min is < %.0f min -> forget the regulation send",
self,
period,
self._auto_regulation_period_min,
)
return
if not self._regulated_target_temp: if not self._regulated_target_temp:
self._regulated_target_temp = self.target_temperature self._regulated_target_temp = self.target_temperature
_LOGGER.info("%s - regulation calculation will be done", self) _LOGGER.info("%s - regulation calculation will be done", self)
new_regulated_temp = round_to_nearest( # use _attr_target_temperature_step to round value if _auto_regulation_dtemp is equal to 0
self._regulation_algo.calculate_regulated_temperature( regulation_step = self._auto_regulation_dtemp if self._auto_regulation_dtemp else self._attr_target_temperature_step
self.current_temperature, self._cur_ext_temp _LOGGER.debug("%s - usage regulation_step: %.2f ", self, regulation_step)
),
self._auto_regulation_dtemp, if self.current_temperature is not None:
) new_regulated_temp = round_to_nearest(
self._regulation_algo.calculate_regulated_temperature(
self.current_temperature, self._cur_ext_temp
),
regulation_step,
)
else:
new_regulated_temp = self.target_temperature
dtemp = new_regulated_temp - self._regulated_target_temp dtemp = new_regulated_temp - self._regulated_target_temp
if not force and abs(dtemp) < self._auto_regulation_dtemp: if not force and abs(dtemp) < self._auto_regulation_dtemp:
@@ -192,31 +255,22 @@ class ThermostatOverClimate(BaseThermostat):
new_regulated_temp, new_regulated_temp,
) )
self._last_regulation_change = now self._last_regulation_change = self.now
for under in self._underlyings: for under in self._underlyings:
# issue 348 - use device temperature if configured as offset # issue 348 - use device temperature if configured as offset
offset_temp = 0 offset_temp = 0
device_temp = 0 device_temp = 0
if ( if (
# current_temperature is set
self.current_temperature is not None
# regulation can use the device_temp # regulation can use the device_temp
self.auto_regulation_use_device_temp and self.auto_regulation_use_device_temp
# and we have access to the device temp # and we have access to the device temp
and (device_temp := under.underlying_current_temperature) is not None and (device_temp := under.underlying_current_temperature) is not None
# and target is not reach (ie we need regulation)
and (
(
self.hvac_mode == HVACMode.COOL
and self.target_temperature < self.current_temperature
)
or (
self.hvac_mode == HVACMode.HEAT
and self.target_temperature > self.current_temperature
)
)
): ):
offset_temp = device_temp - self.current_temperature offset_temp = device_temp - self.current_temperature
target_temp = round_to_nearest(self.regulated_target_temp + offset_temp, self._auto_regulation_dtemp) target_temp = round_to_nearest(self.regulated_target_temp + offset_temp, regulation_step)
_LOGGER.debug( _LOGGER.debug(
"%s - The device offset temp for regulation is %.2f - internal temp is %.2f. New target is %.2f", "%s - The device offset temp for regulation is %.2f - internal temp is %.2f. New target is %.2f",
@@ -277,53 +331,6 @@ class ThermostatOverClimate(BaseThermostat):
) )
await self.async_set_fan_mode(self._auto_deactivated_fan_mode) await self.async_set_fan_mode(self._auto_deactivated_fan_mode)
@overrides
def post_init(self, config_entry: ConfigData):
"""Initialize the Thermostat"""
super().post_init(config_entry)
for climate in [
CONF_CLIMATE,
CONF_CLIMATE_2,
CONF_CLIMATE_3,
CONF_CLIMATE_4,
]:
if config_entry.get(climate):
self._underlyings.append(
UnderlyingClimate(
hass=self._hass,
thermostat=self,
climate_entity_id=config_entry.get(climate),
)
)
self.choose_auto_regulation_mode(
config_entry.get(CONF_AUTO_REGULATION_MODE)
if config_entry.get(CONF_AUTO_REGULATION_MODE) is not None
else CONF_AUTO_REGULATION_NONE
)
self._auto_regulation_dtemp = (
config_entry.get(CONF_AUTO_REGULATION_DTEMP)
if config_entry.get(CONF_AUTO_REGULATION_DTEMP) is not None
else 0.5
)
self._auto_regulation_period_min = (
config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN)
if config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None
else 5
)
self._auto_fan_mode = (
config_entry.get(CONF_AUTO_FAN_MODE)
if config_entry.get(CONF_AUTO_FAN_MODE) is not None
else CONF_AUTO_FAN_NONE
)
self._auto_regulation_use_device_temp = config_entry.get(
CONF_AUTO_REGULATION_USE_DEVICE_TEMP, False
)
def choose_auto_regulation_mode(self, auto_regulation_mode: str): def choose_auto_regulation_mode(self, auto_regulation_mode: str):
"""Choose or change the regulation mode""" """Choose or change the regulation mode"""
self._auto_regulation_mode = auto_regulation_mode self._auto_regulation_mode = auto_regulation_mode
@@ -494,18 +501,10 @@ class ThermostatOverClimate(BaseThermostat):
self._attr_extra_state_attributes["start_hvac_action_date"] = ( self._attr_extra_state_attributes["start_hvac_action_date"] = (
self._underlying_climate_start_hvac_action_date self._underlying_climate_start_hvac_action_date
) )
self._attr_extra_state_attributes["underlying_climate_0"] = self._underlyings[
0 self._attr_extra_state_attributes["underlying_entities"] = [
].entity_id underlying.entity_id for underlying in self._underlyings
self._attr_extra_state_attributes["underlying_climate_1"] = ( ]
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
)
self._attr_extra_state_attributes["underlying_climate_2"] = (
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
)
self._attr_extra_state_attributes["underlying_climate_3"] = (
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
)
if self.is_regulated: if self.is_regulated:
self._attr_extra_state_attributes["is_regulated"] = self.is_regulated self._attr_extra_state_attributes["is_regulated"] = self.is_regulated
@@ -536,7 +535,30 @@ class ThermostatOverClimate(BaseThermostat):
self.auto_regulation_use_device_temp self.auto_regulation_use_device_temp
) )
self._attr_extra_state_attributes["auto_start_stop_enable"] = (
self.auto_start_stop_enable
)
self._attr_extra_state_attributes["auto_start_stop_level"] = (
self._auto_start_stop_algo.level
)
self._attr_extra_state_attributes["auto_start_stop_dtmin"] = (
self._auto_start_stop_algo.dt_min
)
self._attr_extra_state_attributes["auto_start_stop_accumulated_error"] = (
self._auto_start_stop_algo.accumulated_error
)
self._attr_extra_state_attributes[
"auto_start_stop_accumulated_error_threshold"
] = self._auto_start_stop_algo.accumulated_error_threshold
self._attr_extra_state_attributes["follow_underlying_temp_change"] = (
self._follow_underlying_temp_change
)
self.async_write_ha_state() self.async_write_ha_state()
_LOGGER.debug( _LOGGER.debug(
"%s - Calling update_custom_attributes: %s", "%s - Calling update_custom_attributes: %s",
self, self,
@@ -581,7 +603,21 @@ class ThermostatOverClimate(BaseThermostat):
): ):
added_energy = self._device_power * self._underlying_climate_delta_t added_energy = self._device_power * self._underlying_climate_delta_t
self._total_energy += added_energy if self._total_energy is None:
self._total_energy = added_energy
_LOGGER.debug(
"%s - incremente_energy set energy is %s",
self,
self._total_energy,
)
else:
self._total_energy += added_energy
_LOGGER.debug(
"%s - incremente_energy incremented energy is %s",
self,
self._total_energy,
)
_LOGGER.debug( _LOGGER.debug(
"%s - added energy is %.3f . Total energy is now: %.3f", "%s - added energy is %.3f . Total energy is now: %.3f",
self, self,
@@ -590,7 +626,7 @@ class ThermostatOverClimate(BaseThermostat):
) )
@callback @callback
async def _async_climate_changed(self, event: HASSEventType[EventStateChangedData]): async def _async_climate_changed(self, event: Event[EventStateChangedData]):
"""Handle unerdlying climate state changes. """Handle unerdlying climate state changes.
This method takes the underlying values and update the VTherm with them. This method takes the underlying values and update the VTherm with them.
To avoid loops (issues #121 #101 #95 #99), we discard the event if it is received To avoid loops (issues #121 #101 #95 #99), we discard the event if it is received
@@ -603,7 +639,8 @@ class ThermostatOverClimate(BaseThermostat):
async def end_climate_changed(changes: bool): async def end_climate_changed(changes: bool):
"""To end the event management""" """To end the event management"""
if changes: if changes:
self.async_write_ha_state() # already done by update_custom_attribute
# self.async_write_ha_state()
self.update_custom_attributes() self.update_custom_attributes()
await self.async_control_heating() await self.async_control_heating()
@@ -612,6 +649,15 @@ class ThermostatOverClimate(BaseThermostat):
if not new_state: if not new_state:
return return
# Find the underlying which have change
under = self.find_underlying_by_entity_id(new_state.entity_id)
if not under:
_LOGGER.warning(
"We have a receive an event from entity %s which is NOT one of our underlying entities. This is not normal and should be reported to the developper of the integration"
)
return
changes = False changes = False
new_hvac_mode = new_state.state new_hvac_mode = new_state.state
@@ -646,20 +692,68 @@ class ThermostatOverClimate(BaseThermostat):
new_state.last_updated if new_state and new_state.last_updated else None new_state.last_updated if new_state and new_state.last_updated else None
) )
new_target_temp = (
new_state.attributes.get("temperature")
if new_state and new_state.attributes
else None
)
last_sent_temperature = under.last_sent_temperature or 0
under_temp_diff = (
(new_target_temp - last_sent_temperature) if new_target_temp else 0
)
if -1 < under_temp_diff < 1:
under_temp_diff = 0
# Issue 99 - some AC turn hvac_mode=cool and hvac_action=idle when sending a HVACMode_OFF command # Issue 99 - some AC turn hvac_mode=cool and hvac_action=idle when sending a HVACMode_OFF command
# Issue 114 - Remove this because hvac_mode is now managed by local _hvac_mode and use idle action as is # Issue 114 - Remove this because hvac_mode is now managed by local _hvac_mode and use idle action as is
# if self._hvac_mode == HVACMode.OFF and new_hvac_action == HVACAction.IDLE: # if self._hvac_mode == HVACMode.OFF and new_hvac_action == HVACAction.IDLE:
# _LOGGER.debug("The underlying switch to idle instead of OFF. We will consider it as OFF") # _LOGGER.debug("The underlying switch to idle instead of OFF. We will consider it as OFF")
# new_hvac_mode = HVACMode.OFF # new_hvac_mode = HVACMode.OFF
# Forget event when the event holds no real changes
if (
new_hvac_mode == self._hvac_mode
and new_hvac_action == old_hvac_action
and under_temp_diff == 0
and (new_fan_mode is None or new_fan_mode == self._attr_fan_mode)
):
_LOGGER.debug(
"%s - a underlying state change event is received but no real change have been found. Forget the event",
self,
)
return
# Ignore new target temperature when out of range
if (
not new_target_temp is None
and not self._attr_min_temp is None
and not self._attr_max_temp is None
and not (self._attr_min_temp <= new_target_temp <= self._attr_max_temp)
):
_LOGGER.debug(
"%s - underlying sent a target temperature (%s) which is out of configured min/max range (%s / %s). The value will be ignored",
self,
new_target_temp,
self._attr_min_temp,
self._attr_max_temp,
)
new_target_temp = None
under_temp_diff = 0
# A real changes have to be managed
_LOGGER.info( _LOGGER.info(
"%s - Underlying climate %s changed. Event.new_hvac_mode is %s, current_hvac_mode=%s, new_hvac_action=%s, old_hvac_action=%s", "%s - Underlying climate %s have changed. new_hvac_mode is %s (vs %s), new_hvac_action=%s (vs %s), new_target_temp=%s (vs %s), new_fan_mode=%s (vs %s)",
self, self,
new_state.entity_id, under.entity_id,
new_hvac_mode, new_hvac_mode,
self._hvac_mode, self._hvac_mode,
new_hvac_action, new_hvac_action,
old_hvac_action, old_hvac_action,
new_target_temp,
self.target_temperature,
new_fan_mode,
self._attr_fan_mode,
) )
_LOGGER.debug( _LOGGER.debug(
@@ -673,12 +767,6 @@ class ThermostatOverClimate(BaseThermostat):
) )
# Interpretation of hvac action # Interpretation of hvac action
HVAC_ACTION_ON = [ # pylint: disable=invalid-name
HVACAction.COOLING,
HVACAction.DRYING,
HVACAction.FAN,
HVACAction.HEATING,
]
if old_hvac_action not in HVAC_ACTION_ON and new_hvac_action in HVAC_ACTION_ON: if old_hvac_action not in HVAC_ACTION_ON and new_hvac_action in HVAC_ACTION_ON:
self._underlying_climate_start_hvac_action_date = ( self._underlying_climate_start_hvac_action_date = (
self.get_last_updated_date_or_now(new_state) self.get_last_updated_date_or_now(new_state)
@@ -711,6 +799,7 @@ class ThermostatOverClimate(BaseThermostat):
) )
changes = True changes = True
# Filter new state when received just after a change from VTherm
# Issue #120 - Some TRV are changing target temperature a very long time (6 sec) after the change. # Issue #120 - Some TRV are changing target temperature a very long time (6 sec) after the change.
# In that case a loop is possible if a user change multiple times during this 6 sec. # In that case a loop is possible if a user change multiple times during this 6 sec.
if new_state_date_updated and self._last_change_time: if new_state_date_updated and self._last_change_time:
@@ -723,6 +812,7 @@ class ThermostatOverClimate(BaseThermostat):
await end_climate_changed(changes) await end_climate_changed(changes)
return return
# Update all underlyings hvac_mode state if it has change
if ( if (
new_hvac_mode new_hvac_mode
in [ in [
@@ -737,7 +827,6 @@ class ThermostatOverClimate(BaseThermostat):
] ]
and self._hvac_mode != new_hvac_mode and self._hvac_mode != new_hvac_mode
): ):
# Update all underlyings state
# Issue #334 - if all underlyings are not aligned with the same hvac_mode don't change the underlying and wait they are aligned # Issue #334 - if all underlyings are not aligned with the same hvac_mode don't change the underlying and wait they are aligned
if self.is_over_climate: if self.is_over_climate:
for under in self._underlyings: for under in self._underlyings:
@@ -768,35 +857,122 @@ class ThermostatOverClimate(BaseThermostat):
self._attr_fan_mode = new_fan_mode self._attr_fan_mode = new_fan_mode
changes = True changes = True
if not changes: # try to manage new target temperature set if state if no other changes have been found
# try to manage new target temperature set if state # and if a target temperature have already been sent
if (
self._follow_underlying_temp_change
and not changes
and under.last_sent_temperature is not None
):
_LOGGER.debug( _LOGGER.debug(
"Do temperature check. temperature is %s, new_state.attributes is %s", "Do temperature check. under.last_sent_temperature is %s, new_target_temp is %s",
self.target_temperature, under.last_sent_temperature,
new_state.attributes, new_target_temp,
) )
if ( # if the underlying have change its target temperature
# we do not change target temperature on regulated VTherm if under_temp_diff != 0:
not self.is_regulated
and new_state.attributes
and (new_target_temp := new_state.attributes.get("temperature"))
and new_target_temp != self.target_temperature
):
_LOGGER.info( _LOGGER.info(
"%s - Target temp in underlying have change to %s", "%s - Target temp in underlying have change to %s (vs %s)",
self, self,
new_target_temp, new_target_temp,
under.last_sent_temperature,
) )
await self.async_set_temperature(temperature=new_target_temp) await self.async_set_temperature(temperature=new_target_temp)
changes = True changes = True
else:
_LOGGER.debug(
"%s - Forget the eventual underlying temperature change there is no real change",
self,
)
await end_climate_changed(changes) await end_climate_changed(changes)
async def check_auto_start_stop(self):
"""Check the auto-start-stop and an eventual action
Return False if we should stop the control_heating method"""
slope = (self.last_temperature_slope or 0) / 60 # to have the slope in °/min
action = self._auto_start_stop_algo.calculate_action(
self.hvac_mode,
self._saved_hvac_mode,
self.target_temperature,
self.current_temperature,
slope,
self.now,
)
_LOGGER.debug("%s - auto_start_stop action is %s", self, action)
if action == AUTO_START_STOP_ACTION_OFF and self.is_on:
_LOGGER.info(
"%s - Turning OFF the Vtherm due to auto-start-stop conditions",
self,
)
self.set_hvac_off_reason(HVAC_OFF_REASON_AUTO_START_STOP)
await self.async_turn_off()
# Send an event
self.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "stop",
"name": self.name,
"cause": "Auto stop conditions reached",
"hvac_mode": self.hvac_mode,
"saved_hvac_mode": self._saved_hvac_mode,
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": round(slope, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
# Stop here
return False
elif (
action == AUTO_START_STOP_ACTION_ON
and self.hvac_off_reason == HVAC_OFF_REASON_AUTO_START_STOP
):
_LOGGER.info(
"%s - Turning ON the Vtherm due to auto-start-stop conditions", self
)
await self.async_turn_on()
# Send an event
self.send_event(
event_type=EventType.AUTO_START_STOP_EVENT,
data={
"type": "start",
"name": self.name,
"cause": "Auto start conditions reached",
"hvac_mode": self.hvac_mode,
"saved_hvac_mode": self._saved_hvac_mode,
"target_temperature": self.target_temperature,
"current_temperature": self.current_temperature,
"temperature_slope": round(slope, 3),
"accumulated_error": self._auto_start_stop_algo.accumulated_error,
"accumulated_error_threshold": self._auto_start_stop_algo.accumulated_error_threshold,
},
)
self.update_custom_attributes()
return True
@overrides @overrides
async def async_control_heating(self, force=False, _=None) -> bool: async def async_control_heating(self, force=False, _=None) -> bool:
"""The main function used to run the calculation at each cycle""" """The main function used to run the calculation at each cycle"""
ret = await super().async_control_heating(force, _) ret = await super().async_control_heating(force, _)
# Check if we need to auto start/stop the Vtherm
if self.auto_start_stop_enable:
continu = await self.check_auto_start_stop()
if not continu:
return ret
else:
_LOGGER.debug("%s - auto start/stop is disabled", self)
# Continue the normal async_control_heating
# Send the regulated temperature to the underlyings
await self._send_regulated_temperature() await self._send_regulated_temperature()
if self._auto_fan_mode and self._auto_fan_mode != CONF_AUTO_FAN_NONE: if self._auto_fan_mode and self._auto_fan_mode != CONF_AUTO_FAN_NONE:
@@ -804,6 +980,16 @@ class ThermostatOverClimate(BaseThermostat):
return ret return ret
def set_auto_start_stop_enable(self, is_enabled: bool):
"""Enable/Disable the auto-start/stop feature"""
self._is_auto_start_stop_enabled = is_enabled
self.update_custom_attributes()
def set_follow_underlying_temp_change(self, follow: bool):
"""Set the flaf follow the underlying temperature changes"""
self._follow_underlying_temp_change = follow
self.update_custom_attributes()
@property @property
def auto_regulation_mode(self) -> str | None: def auto_regulation_mode(self) -> str | None:
"""Get the regulation mode""" """Get the regulation mode"""
@@ -890,10 +1076,7 @@ class ThermostatOverClimate(BaseThermostat):
@property @property
def temperature_unit(self) -> str: def temperature_unit(self) -> str:
"""Return the unit of measurement.""" """Return the unit of measurement."""
if self.underlying_entity(0): return self.hass.config.units.temperature_unit
return self.underlying_entity(0).temperature_unit
return self._unit
@property @property
def supported_features(self): def supported_features(self):
@@ -934,6 +1117,14 @@ class ThermostatOverClimate(BaseThermostat):
return None return None
@property
def current_humidity(self) -> float | None:
"""Return the humidity."""
if self.underlying_entity(0):
return self.underlying_entity(0).current_humidity
return None
@property @property
def is_aux_heat(self) -> bool | None: def is_aux_heat(self) -> bool | None:
"""Return true if aux heater. """Return true if aux heater.
@@ -953,6 +1144,21 @@ class ThermostatOverClimate(BaseThermostat):
return False return False
return True return True
@property
def auto_start_stop_level(self) -> TYPE_AUTO_START_STOP_LEVELS:
"""Return the auto start/stop level."""
return self._auto_start_stop_level
@property
def auto_start_stop_enable(self) -> bool:
"""Returns the auto_start_stop_enable"""
return self._is_auto_start_stop_enabled
@property
def follow_underlying_temp_change(self) -> bool:
"""Get the follow underlying temp change flag"""
return self._follow_underlying_temp_change
@overrides @overrides
def init_underlyings(self): def init_underlyings(self):
"""Init the underlyings if not already done""" """Init the underlyings if not already done"""
@@ -1055,6 +1261,13 @@ class ThermostatOverClimate(BaseThermostat):
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_SLOW) self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_SLOW)
elif auto_regulation_mode == "Expert": elif auto_regulation_mode == "Expert":
self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_EXPERT) self.choose_auto_regulation_mode(CONF_AUTO_REGULATION_EXPERT)
else:
_LOGGER.warning(
"%s - auto_regulation_mode %s is not supported",
self,
auto_regulation_mode,
)
return
await self._send_regulated_temperature() await self._send_regulated_temperature()
self.update_custom_attributes() self.update_custom_attributes()
@@ -1084,3 +1297,29 @@ class ThermostatOverClimate(BaseThermostat):
self.choose_auto_fan_mode(CONF_AUTO_FAN_TURBO) self.choose_auto_fan_mode(CONF_AUTO_FAN_TURBO)
self.update_custom_attributes() self.update_custom_attributes()
@overrides
async def async_turn_off(self) -> None:
# if window is open, don't overwrite the saved_hvac_mode
if self.window_state != STATE_ON:
self.save_hvac_mode()
await self.async_set_hvac_mode(HVACMode.OFF)
@overrides
async def async_turn_on(self) -> None:
# don't turn_on if window is open
if self.window_state == STATE_ON:
_LOGGER.info(
"%s - refuse to turn on because window is open. We keep the save_hvac_mode",
self,
)
return
if self._saved_hvac_mode is not None: # pylint: disable=protected-access
await self.restore_hvac_mode(True)
else:
if self._ac_mode:
await self.async_set_hvac_mode(HVACMode.COOL)
else:
await self.async_set_hvac_mode(HVACMode.HEAT)

View File

@@ -0,0 +1,289 @@
# pylint: disable=line-too-long, too-many-lines, abstract-method
""" A climate with a direct valve regulation class """
import logging
from datetime import datetime
from homeassistant.core import HomeAssistant
from homeassistant.components.climate import HVACMode, HVACAction
from .underlyings import UnderlyingValveRegulation
# from .commons import NowClass, round_to_nearest
from .base_thermostat import ConfigData
from .thermostat_climate import ThermostatOverClimate
from .prop_algorithm import PropAlgorithm
from .const import * # pylint: disable=wildcard-import, unused-wildcard-import
# from .vtherm_api import VersatileThermostatAPI
_LOGGER = logging.getLogger(__name__)
class ThermostatOverClimateValve(ThermostatOverClimate):
"""This class represent a VTherm over a climate with a direct valve regulation"""
_entity_component_unrecorded_attributes = ThermostatOverClimate._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
frozenset(
{
"is_over_climate",
"have_valve_regulation",
"underlying_entities",
"on_time_sec",
"off_time_sec",
"cycle_min",
"function",
"tpi_coef_int",
"tpi_coef_ext",
"power_percent",
}
)
)
_underlyings_valve_regulation: list[UnderlyingValveRegulation] = []
_valve_open_percent: int | None = None
_last_calculation_timestamp: datetime | None = None
_auto_regulation_dpercent: float | None = None
_auto_regulation_period_min: int | None = None
def __init__(
self, hass: HomeAssistant, unique_id: str, name: str, entry_infos: ConfigData
):
"""Initialize the ThermostatOverClimateValve class"""
_LOGGER.debug("%s - creating a ThermostatOverClimateValve VTherm", name)
super().__init__(hass, unique_id, name, entry_infos)
@overrides
def post_init(self, config_entry: ConfigData):
"""Initialize the Thermostat and underlyings
Beware that the underlyings list contains the climate which represent the TRV
but also the UnderlyingValveRegulation which reprensent the valve"""
super().post_init(config_entry)
self._auto_regulation_dpercent = (
config_entry.get(CONF_AUTO_REGULATION_DTEMP)
if config_entry.get(CONF_AUTO_REGULATION_DTEMP) is not None
else 0.0
)
self._auto_regulation_period_min = (
config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN)
if config_entry.get(CONF_AUTO_REGULATION_PERIOD_MIN) is not None
else 0
)
# Initialization of the TPI algo
self._prop_algorithm = PropAlgorithm(
self._proportional_function,
self._tpi_coef_int,
self._tpi_coef_ext,
self._cycle_min,
self._minimal_activation_delay,
self.name,
)
offset_list = config_entry.get(CONF_OFFSET_CALIBRATION_LIST)
opening_list = config_entry.get(CONF_OPENING_DEGREE_LIST)
closing_list = config_entry.get(CONF_CLOSING_DEGREE_LIST)
for idx, _ in enumerate(config_entry.get(CONF_UNDERLYING_LIST)):
offset = offset_list[idx] if idx < len(offset_list) else None
# number of opening should equal number of underlying
opening = opening_list[idx]
closing = closing_list[idx] if idx < len(closing_list) else None
under = UnderlyingValveRegulation(
hass=self._hass,
thermostat=self,
offset_calibration_entity_id=offset,
opening_degree_entity_id=opening,
closing_degree_entity_id=closing,
climate_underlying=self._underlyings[idx],
)
self._underlyings_valve_regulation.append(under)
@overrides
def update_custom_attributes(self):
"""Custom attributes"""
super().update_custom_attributes()
self._attr_extra_state_attributes["have_valve_regulation"] = (
self.have_valve_regulation
)
self._attr_extra_state_attributes["underlyings_valve_regulation"] = [
underlying.valve_entity_ids
for underlying in self._underlyings_valve_regulation
]
self._attr_extra_state_attributes["on_percent"] = (
self._prop_algorithm.on_percent
)
self._attr_extra_state_attributes["power_percent"] = self.power_percent
self._attr_extra_state_attributes["on_time_sec"] = (
self._prop_algorithm.on_time_sec
)
self._attr_extra_state_attributes["off_time_sec"] = (
self._prop_algorithm.off_time_sec
)
self._attr_extra_state_attributes["cycle_min"] = self._cycle_min
self._attr_extra_state_attributes["function"] = self._proportional_function
self._attr_extra_state_attributes["tpi_coef_int"] = self._tpi_coef_int
self._attr_extra_state_attributes["tpi_coef_ext"] = self._tpi_coef_ext
self._attr_extra_state_attributes["valve_open_percent"] = (
self.valve_open_percent
)
self._attr_extra_state_attributes["auto_regulation_dpercent"] = (
self._auto_regulation_dpercent
)
self._attr_extra_state_attributes["auto_regulation_period_min"] = (
self._auto_regulation_period_min
)
self._attr_extra_state_attributes["last_calculation_timestamp"] = (
self._last_calculation_timestamp.astimezone(self._current_tz).isoformat()
if self._last_calculation_timestamp
else None
)
self.async_write_ha_state()
_LOGGER.debug(
"%s - Calling update_custom_attributes: %s",
self,
self._attr_extra_state_attributes,
)
@overrides
def recalculate(self):
"""A utility function to force the calculation of a the algo and
update the custom attributes and write the state
"""
_LOGGER.debug("%s - recalculate the open percent", self)
# TODO this is exactly the same method as the thermostat_valve recalculate. Put that in common
# For testing purpose. Should call _set_now() before
now = self.now
if self._last_calculation_timestamp is not None:
period = (now - self._last_calculation_timestamp).total_seconds() / 60
if period < self._auto_regulation_period_min:
_LOGGER.info(
"%s - do not calculate TPI because regulation_period (%d) is not exceeded",
self,
period,
)
return
self._prop_algorithm.calculate(
self._target_temp,
self._cur_temp,
self._cur_ext_temp,
self._hvac_mode or HVACMode.OFF,
)
new_valve_percent = round(
max(0, min(self.proportional_algorithm.on_percent, 1)) * 100
)
# Issue 533 - don't filter with dtemp if valve should be close. Else it will never close
if new_valve_percent < self._auto_regulation_dpercent:
new_valve_percent = 0
dpercent = (
new_valve_percent - self._valve_open_percent
if self._valve_open_percent is not None
else 0
)
if (
self._last_calculation_timestamp is not None
and new_valve_percent > 0
and -1 * self._auto_regulation_dpercent
<= dpercent
< self._auto_regulation_dpercent
):
_LOGGER.debug(
"%s - do not calculate TPI because regulation_dpercent (%.1f) is not exceeded",
self,
dpercent,
)
return
if (
self._last_calculation_timestamp is not None
and self._valve_open_percent == new_valve_percent
):
_LOGGER.debug("%s - no change in valve_open_percent.", self)
return
self._valve_open_percent = new_valve_percent
self._last_calculation_timestamp = now
super().recalculate()
async def _send_regulated_temperature(self, force=False):
"""Sends the regulated temperature to all underlying"""
if self.target_temperature is None:
return
for under in self._underlyings:
if self.target_temperature != under.last_sent_temperature:
await under.set_temperature(
self.target_temperature,
self._attr_max_temp,
self._attr_min_temp,
)
for under in self._underlyings_valve_regulation:
await under.set_valve_open_percent()
@property
def have_valve_regulation(self) -> bool:
"""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"""
if self._hvac_mode == HVACMode.OFF or self._valve_open_percent is None:
return 0
else:
return self._valve_open_percent
@property
def hvac_action(self) -> HVACAction | None:
"""Returns the current hvac_action by checking all hvac_action of the _underlyings_valve_regulation"""
return self.calculate_hvac_action(self._underlyings_valve_regulation)
@property
def is_device_active(self) -> bool:
"""A hack to overrides the state from underlyings"""
return self.valve_open_percent > 0
@property
def nb_device_actives(self) -> int:
"""Calculate the number of active devices"""
if self.is_device_active:
return len(self._underlyings_valve_regulation)
else:
return 0
@overrides
async def service_set_auto_regulation_mode(self, auto_regulation_mode: str):
"""This should not be possible in valve regulation mode"""
return

View File

@@ -1,20 +1,16 @@
# pylint: disable=line-too-long # pylint: disable=line-too-long, abstract-method
""" A climate over switch classe """ """ A climate over switch classe """
import logging import logging
from homeassistant.core import callback from homeassistant.core import Event, callback
from homeassistant.helpers.event import ( from homeassistant.helpers.event import (
async_track_state_change_event, async_track_state_change_event,
EventStateChangedData, EventStateChangedData,
) )
from homeassistant.helpers.typing import EventType as HASSEventType
from homeassistant.components.climate import HVACMode from homeassistant.components.climate import HVACMode
from .const import ( from .const import (
CONF_HEATER, CONF_UNDERLYING_LIST,
CONF_HEATER_2,
CONF_HEATER_3,
CONF_HEATER_4,
CONF_HEATER_KEEP_ALIVE, CONF_HEATER_KEEP_ALIVE,
CONF_INVERSE_SWITCH, CONF_INVERSE_SWITCH,
overrides, overrides,
@@ -26,8 +22,7 @@ from .prop_algorithm import PropAlgorithm
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class ThermostatOverSwitch(BaseThermostat[UnderlyingSwitch]):
class ThermostatOverSwitch(BaseThermostat):
"""Representation of a base class for a Versatile Thermostat over a switch.""" """Representation of a base class for a Versatile Thermostat over a switch."""
_entity_component_unrecorded_attributes = ( _entity_component_unrecorded_attributes = (
@@ -36,10 +31,7 @@ class ThermostatOverSwitch(BaseThermostat):
{ {
"is_over_switch", "is_over_switch",
"is_inversed", "is_inversed",
"underlying_switch_0", "underlying_entities",
"underlying_switch_1",
"underlying_switch_2",
"underlying_switch_3",
"on_time_sec", "on_time_sec",
"off_time_sec", "off_time_sec",
"cycle_min", "cycle_min",
@@ -47,6 +39,7 @@ class ThermostatOverSwitch(BaseThermostat):
"tpi_coef_int", "tpi_coef_int",
"tpi_coef_ext", "tpi_coef_ext",
"power_percent", "power_percent",
"calculated_on_percent",
} }
) )
) )
@@ -88,15 +81,11 @@ class ThermostatOverSwitch(BaseThermostat):
self._tpi_coef_ext, self._tpi_coef_ext,
self._cycle_min, self._cycle_min,
self._minimal_activation_delay, self._minimal_activation_delay,
self.name,
max_on_percent=self._max_on_percent,
) )
lst_switches = [config_entry.get(CONF_HEATER)] lst_switches = config_entry.get(CONF_UNDERLYING_LIST)
if config_entry.get(CONF_HEATER_2):
lst_switches.append(config_entry.get(CONF_HEATER_2))
if config_entry.get(CONF_HEATER_3):
lst_switches.append(config_entry.get(CONF_HEATER_3))
if config_entry.get(CONF_HEATER_4):
lst_switches.append(config_entry.get(CONF_HEATER_4))
delta_cycle = self._cycle_min * 60 / len(lst_switches) delta_cycle = self._cycle_min * 60 / len(lst_switches)
for idx, switch in enumerate(lst_switches): for idx, switch in enumerate(lst_switches):
@@ -136,20 +125,14 @@ class ThermostatOverSwitch(BaseThermostat):
"""Custom attributes""" """Custom attributes"""
super().update_custom_attributes() super().update_custom_attributes()
under0: UnderlyingSwitch = self._underlyings[0]
self._attr_extra_state_attributes["is_over_switch"] = self.is_over_switch self._attr_extra_state_attributes["is_over_switch"] = self.is_over_switch
self._attr_extra_state_attributes["is_inversed"] = self.is_inversed self._attr_extra_state_attributes["is_inversed"] = self.is_inversed
self._attr_extra_state_attributes["underlying_switch_0"] = self._underlyings[ self._attr_extra_state_attributes["keep_alive_sec"] = under0.keep_alive_sec
0
].entity_id self._attr_extra_state_attributes["underlying_entities"] = [
self._attr_extra_state_attributes["underlying_switch_1"] = ( underlying.entity_id for underlying in self._underlyings
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None ]
)
self._attr_extra_state_attributes["underlying_switch_2"] = (
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
)
self._attr_extra_state_attributes["underlying_switch_3"] = (
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
)
self._attr_extra_state_attributes[ self._attr_extra_state_attributes[
"on_percent" "on_percent"
@@ -165,6 +148,9 @@ class ThermostatOverSwitch(BaseThermostat):
self._attr_extra_state_attributes["function"] = self._proportional_function self._attr_extra_state_attributes["function"] = self._proportional_function
self._attr_extra_state_attributes["tpi_coef_int"] = self._tpi_coef_int self._attr_extra_state_attributes["tpi_coef_int"] = self._tpi_coef_int
self._attr_extra_state_attributes["tpi_coef_ext"] = self._tpi_coef_ext self._attr_extra_state_attributes["tpi_coef_ext"] = self._tpi_coef_ext
self._attr_extra_state_attributes[
"calculated_on_percent"
] = self._prop_algorithm.calculated_on_percent
self.async_write_ha_state() self.async_write_ha_state()
_LOGGER.debug( _LOGGER.debug(
@@ -186,7 +172,8 @@ class ThermostatOverSwitch(BaseThermostat):
self._hvac_mode or HVACMode.OFF, self._hvac_mode or HVACMode.OFF,
) )
self.update_custom_attributes() self.update_custom_attributes()
self.async_write_ha_state() # already done bu update_custom_attributes
# self.async_write_ha_state()
@overrides @overrides
def incremente_energy(self): def incremente_energy(self):
@@ -198,7 +185,23 @@ class ThermostatOverSwitch(BaseThermostat):
if not self.is_over_climate and self.mean_cycle_power is not None: 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 added_energy = self.mean_cycle_power * float(self._cycle_min) / 60.0
self._total_energy += added_energy if self._total_energy is None:
self._total_energy = added_energy
_LOGGER.debug(
"%s - incremente_energy set energy is %s",
self,
self._total_energy,
)
else:
self._total_energy += added_energy
_LOGGER.debug(
"%s - incremente_energy increment energy is %s",
self,
self._total_energy,
)
self.update_custom_attributes()
_LOGGER.debug( _LOGGER.debug(
"%s - added energy is %.3f . Total energy is now: %.3f", "%s - added energy is %.3f . Total energy is now: %.3f",
self, self,
@@ -207,7 +210,7 @@ class ThermostatOverSwitch(BaseThermostat):
) )
@callback @callback
def _async_switch_changed(self, event: HASSEventType[EventStateChangedData]): def _async_switch_changed(self, event: Event[EventStateChangedData]):
"""Handle heater switch state changes.""" """Handle heater switch state changes."""
new_state = event.data.get("new_state") new_state = event.data.get("new_state")
old_state = event.data.get("old_state") old_state = event.data.get("old_state")

View File

@@ -1,4 +1,4 @@
# pylint: disable=line-too-long # pylint: disable=line-too-long, abstract-method
""" A climate over switch classe """ """ A climate over switch classe """
import logging import logging
from datetime import timedelta, datetime from datetime import timedelta, datetime
@@ -8,18 +8,14 @@ from homeassistant.helpers.event import (
async_track_time_interval, async_track_time_interval,
EventStateChangedData, EventStateChangedData,
) )
from homeassistant.helpers.typing import EventType as HASSEventType from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.core import HomeAssistant, callback
from homeassistant.components.climate import HVACMode from homeassistant.components.climate import HVACMode
from .base_thermostat import BaseThermostat, ConfigData from .base_thermostat import BaseThermostat, ConfigData
from .prop_algorithm import PropAlgorithm from .prop_algorithm import PropAlgorithm
from .const import ( from .const import (
CONF_VALVE, CONF_UNDERLYING_LIST,
CONF_VALVE_2,
CONF_VALVE_3,
CONF_VALVE_4,
# This is not really self-regulation but regulation here # This is not really self-regulation but regulation here
CONF_AUTO_REGULATION_DTEMP, CONF_AUTO_REGULATION_DTEMP,
CONF_AUTO_REGULATION_PERIOD_MIN, CONF_AUTO_REGULATION_PERIOD_MIN,
@@ -30,30 +26,25 @@ from .underlyings import UnderlyingValve
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class ThermostatOverValve(BaseThermostat[UnderlyingValve]): # pylint: disable=abstract-method
class ThermostatOverValve(BaseThermostat): # pylint: disable=abstract-method
"""Representation of a class for a Versatile Thermostat over a Valve""" """Representation of a class for a Versatile Thermostat over a Valve"""
_entity_component_unrecorded_attributes = ( _entity_component_unrecorded_attributes = BaseThermostat._entity_component_unrecorded_attributes.union( # pylint: disable=protected-access
BaseThermostat._entity_component_unrecorded_attributes.union( frozenset(
frozenset( {
{ "is_over_valve",
"is_over_valve", "underlying_entities",
"underlying_valve_0", "on_time_sec",
"underlying_valve_1", "off_time_sec",
"underlying_valve_2", "cycle_min",
"underlying_valve_3", "function",
"on_time_sec", "tpi_coef_int",
"off_time_sec", "tpi_coef_ext",
"cycle_min", "auto_regulation_dpercent",
"function", "auto_regulation_period_min",
"tpi_coef_int", "last_calculation_timestamp",
"tpi_coef_ext", "calculated_on_percent",
"auto_regulation_dpercent", }
"auto_regulation_period_min",
"last_calculation_timestamp",
}
)
) )
) )
@@ -105,15 +96,11 @@ class ThermostatOverValve(BaseThermostat): # pylint: disable=abstract-method
self._tpi_coef_ext, self._tpi_coef_ext,
self._cycle_min, self._cycle_min,
self._minimal_activation_delay, self._minimal_activation_delay,
self.name,
max_on_percent=self._max_on_percent,
) )
lst_valves = [config_entry.get(CONF_VALVE)] lst_valves = config_entry.get(CONF_UNDERLYING_LIST)
if config_entry.get(CONF_VALVE_2):
lst_valves.append(config_entry.get(CONF_VALVE_2))
if config_entry.get(CONF_VALVE_3):
lst_valves.append(config_entry.get(CONF_VALVE_3))
if config_entry.get(CONF_VALVE_4):
lst_valves.append(config_entry.get(CONF_VALVE_4))
for _, valve in enumerate(lst_valves): for _, valve in enumerate(lst_valves):
self._underlyings.append( self._underlyings.append(
@@ -148,7 +135,7 @@ class ThermostatOverValve(BaseThermostat): # pylint: disable=abstract-method
) )
@callback @callback
async def _async_valve_changed(self, event: HASSEventType[EventStateChangedData]): async def _async_valve_changed(self, event: Event[EventStateChangedData]):
"""Handle unerdlying valve state changes. """Handle unerdlying valve state changes.
This method just log the change. It changes nothing to avoid loops. This method just log the change. It changes nothing to avoid loops.
""" """
@@ -165,18 +152,10 @@ class ThermostatOverValve(BaseThermostat): # pylint: disable=abstract-method
"valve_open_percent" "valve_open_percent"
] = self.valve_open_percent ] = self.valve_open_percent
self._attr_extra_state_attributes["is_over_valve"] = self.is_over_valve self._attr_extra_state_attributes["is_over_valve"] = self.is_over_valve
self._attr_extra_state_attributes["underlying_valve_0"] = self._underlyings[
0 self._attr_extra_state_attributes["underlying_entities"] = [
].entity_id underlying.entity_id for underlying in self._underlyings
self._attr_extra_state_attributes["underlying_valve_1"] = ( ]
self._underlyings[1].entity_id if len(self._underlyings) > 1 else None
)
self._attr_extra_state_attributes["underlying_valve_2"] = (
self._underlyings[2].entity_id if len(self._underlyings) > 2 else None
)
self._attr_extra_state_attributes["underlying_valve_3"] = (
self._underlyings[3].entity_id if len(self._underlyings) > 3 else None
)
self._attr_extra_state_attributes[ self._attr_extra_state_attributes[
"on_percent" "on_percent"
@@ -202,6 +181,9 @@ class ThermostatOverValve(BaseThermostat): # pylint: disable=abstract-method
if self._last_calculation_timestamp if self._last_calculation_timestamp
else None else None
) )
self._attr_extra_state_attributes[
"calculated_on_percent"
] = self._prop_algorithm.calculated_on_percent
self.async_write_ha_state() self.async_write_ha_state()
_LOGGER.debug( _LOGGER.debug(
@@ -241,10 +223,16 @@ class ThermostatOverValve(BaseThermostat): # pylint: disable=abstract-method
max(0, min(self.proportional_algorithm.on_percent, 1)) * 100 max(0, min(self.proportional_algorithm.on_percent, 1)) * 100
) )
# Issue 533 - don't filter with dtemp if valve should be close. Else it will never close
if new_valve_percent < self._auto_regulation_dpercent:
new_valve_percent = 0
dpercent = new_valve_percent - self.valve_open_percent dpercent = new_valve_percent - self.valve_open_percent
if ( if (
dpercent >= -1 * self._auto_regulation_dpercent new_valve_percent > 0
and dpercent < self._auto_regulation_dpercent and -1 * self._auto_regulation_dpercent
<= dpercent
< self._auto_regulation_dpercent
): ):
_LOGGER.debug( _LOGGER.debug(
"%s - do not calculate TPI because regulation_dpercent (%.1f) is not exceeded", "%s - do not calculate TPI because regulation_dpercent (%.1f) is not exceeded",
@@ -260,13 +248,15 @@ class ThermostatOverValve(BaseThermostat): # pylint: disable=abstract-method
self._valve_open_percent = new_valve_percent self._valve_open_percent = new_valve_percent
for under in self._underlyings: # is one in start_cycle now
under.set_valve_open_percent() # for under in self._underlyings:
# under.set_valve_open_percent()
self._last_calculation_timestamp = now self._last_calculation_timestamp = now
self.update_custom_attributes() self.update_custom_attributes()
self.async_write_ha_state() # already done in update_custom_attributes
# self.async_write_ha_state()
@overrides @overrides
def incremente_energy(self): def incremente_energy(self):
@@ -278,7 +268,23 @@ class ThermostatOverValve(BaseThermostat): # pylint: disable=abstract-method
if not self.is_over_climate and self.mean_cycle_power is not None: 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 added_energy = self.mean_cycle_power * float(self._cycle_min) / 60.0
self._total_energy += added_energy if self._total_energy is None:
self._total_energy = added_energy
_LOGGER.debug(
"%s - incremente_energy set energy is %s",
self,
self._total_energy,
)
else:
self._total_energy += added_energy
_LOGGER.debug(
"%s - get_my_previous_state increment energy is %s",
self,
self._total_energy,
)
self.update_custom_attributes()
_LOGGER.debug( _LOGGER.debug(
"%s - added energy is %.3f . Total energy is now: %.3f", "%s - added energy is %.3f . Total energy is now: %.3f",
self, self,

View File

@@ -43,7 +43,7 @@
"auto_regulation_dtemp": "Όριο ρύθμισης", "auto_regulation_dtemp": "Όριο ρύθμισης",
"auto_regulation_periode_min": "Ελάχιστη περίοδος ρύθμισης", "auto_regulation_periode_min": "Ελάχιστη περίοδος ρύθμισης",
"inverse_switch_command": "Αντίστροφη εντολή διακόπτη", "inverse_switch_command": "Αντίστροφη εντολή διακόπτη",
"auto_fan_mode": " Auto fan mode" "auto_fan_mode": "Auto fan mode"
}, },
"data_description": { "data_description": {
"heater_entity_id": "Υποχρεωτική ταυτότητα οντότητας θερμαντήρα", "heater_entity_id": "Υποχρεωτική ταυτότητα οντότητας θερμαντήρα",
@@ -64,7 +64,7 @@
"auto_regulation_dtemp": "Το όριο σε ° κάτω από το οποίο η αλλαγή θερμοκρασίας δεν θα αποστέλλεται", "auto_regulation_dtemp": "Το όριο σε ° κάτω από το οποίο η αλλαγή θερμοκρασίας δεν θα αποστέλλεται",
"auto_regulation_periode_min": "Διάρκεια σε λεπτά μεταξύ δύο ενημερώσεων ρύθμισης", "auto_regulation_periode_min": "Διάρκεια σε λεπτά μεταξύ δύο ενημερώσεων ρύθμισης",
"inverse_switch_command": "Για διακόπτη με πιλοτικό καλώδιο και δίοδο μπορεί να χρειαστεί να αντιστρέψετε την εντολή", "inverse_switch_command": "Για διακόπτη με πιλοτικό καλώδιο και δίοδο μπορεί να χρειαστεί να αντιστρέψετε την εντολή",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" "auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary"
} }
}, },
"tpi": { "tpi": {
@@ -216,7 +216,7 @@
"auto_regulation_dtemp": "Όριο ρύθμισης", "auto_regulation_dtemp": "Όριο ρύθμισης",
"auto_regulation_periode_min": "Ελάχιστη περίοδος ρύθμισης", "auto_regulation_periode_min": "Ελάχιστη περίοδος ρύθμισης",
"inverse_switch_command": "Αντίστροφη εντολή διακόπτη", "inverse_switch_command": "Αντίστροφη εντολή διακόπτη",
"auto_fan_mode": " Auto fan mode" "auto_fan_mode": "Auto fan mode"
}, },
"data_description": { "data_description": {
"heater_entity_id": "Υποχρεωτική ταυτότητα οντότητας θερμαντήρα", "heater_entity_id": "Υποχρεωτική ταυτότητα οντότητας θερμαντήρα",
@@ -237,7 +237,7 @@
"auto_regulation_dtemp": "Το κατώφλι σε °C κάτω από το οποίο η αλλαγή της θερμοκρασίας δεν θα αποστέλλεται", "auto_regulation_dtemp": "Το κατώφλι σε °C κάτω από το οποίο η αλλαγή της θερμοκρασίας δεν θα αποστέλλεται",
"auto_regulation_periode_min": "Διάρκεια σε λεπτά μεταξύ δύο ενημερώσεων ρύθμισης", "auto_regulation_periode_min": "Διάρκεια σε λεπτά μεταξύ δύο ενημερώσεων ρύθμισης",
"inverse_switch_command": "Για διακόπτες με πιλοτικό καλώδιο και δίοδο μπορεί να χρειαστεί να αντιστραφεί η εντολή", "inverse_switch_command": "Για διακόπτες με πιλοτικό καλώδιο και δίοδο μπορεί να χρειαστεί να αντιστραφεί η εντολή",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" "auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary"
} }
}, },
"tpi": { "tpi": {

View File

@@ -14,7 +14,7 @@
}, },
"menu": { "menu": {
"title": "Menu", "title": "Menu",
"description": "Configure your thermostat. You will be able to finalize the configuration when all needed parameters are valued.", "description": "Configure your thermostat. You will be able to finalize the configuration when all required parameters are entered.",
"menu_options": { "menu_options": {
"main": "Main attributes", "main": "Main attributes",
"central_boiler": "Central boiler", "central_boiler": "Central boiler",
@@ -27,7 +27,10 @@
"power": "Power management", "power": "Power management",
"presence": "Presence detection", "presence": "Presence detection",
"advanced": "Advanced parameters", "advanced": "Advanced parameters",
"finalize": "All done" "auto_start_stop": "Auto start and stop",
"valve_regulation": "Valve regulation configuration",
"finalize": "All done",
"configuration_not_complete": "Configuration not complete"
} }
}, },
"main": { "main": {
@@ -36,19 +39,21 @@
"data": { "data": {
"name": "Name", "name": "Name",
"thermostat_type": "Thermostat type", "thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Room temperature sensor entity id", "temperature_sensor_entity_id": "Room temperature",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature datetime",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id", "external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)", "cycle_min": "Cycle duration (minutes)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimum temperature allowed",
"temp_max": "Maximal temperature allowed", "temp_max": "Maximum temperature allowed",
"step_temperature": "Temperature step", "step_temperature": "Temperature step",
"device_power": "Device power", "device_power": "Device power",
"use_central_mode": "Enable the control by central entity (need central config). Check to enable the control of the VTherm with the select central_mode entities.", "use_central_mode": "Enable the control by central entity (requires central config). Check to enable the control of the VTherm with the select central_mode entities.",
"use_main_central_config": "Use additional central main configuration. Check to use the central main configuration (outdoor temperature, min, max, step, ...).", "use_main_central_config": "Use additional central main configuration. Check to use the central main configuration (outdoor temperature, min, max, step, ...).",
"add_central_boiler_control": "Add a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm need heating, the boiler will be turned on. If no VTherm needs heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the next configuration page",
"used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler" "used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler"
}, },
"data_description": { "data_description": {
"temperature_sensor_entity_id": "Room temperature sensor entity id",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature sensor entity id. Should be datetime sensor",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected" "external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
} }
}, },
@@ -59,57 +64,37 @@
"use_window_feature": "Use window detection", "use_window_feature": "Use window detection",
"use_motion_feature": "Use motion detection", "use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management", "use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection" "use_presence_feature": "Use presence detection",
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after selecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
"use_auto_start_stop_feature": "Use the auto start and stop feature"
} }
}, },
"type": { "type": {
"title": "Linked entities", "title": "Linked entities",
"description": "Linked entities attributes", "description": "Linked entities attributes",
"data": { "data": {
"heater_entity_id": "1st heater switch", "underlying_entity_ids": "The device(s) to be controlled",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"heater_keep_alive": "Switch keep-alive interval in seconds", "heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm", "proportional_function": "Algorithm",
"climate_entity_id": "1st underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode", "ac_mode": "AC mode",
"valve_entity_id": "1st valve number",
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation", "auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold", "auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period", "auto_regulation_periode_min": "Regulation minimum period",
"auto_regulation_use_device_temp": "Use internal temperature of the underlying", "auto_regulation_use_device_temp": "Use internal temperature of the underlying",
"inverse_switch_command": "Inverse switch command", "inverse_switch_command": "Inverse switch command",
"auto_fan_mode": " Auto fan mode" "auto_fan_mode": "Auto fan mode"
}, },
"data_description": { "data_description": {
"heater_entity_id": "Mandatory heater entity id", "underlying_entity_ids": "The device(s) to be controlled - 1 is required",
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not used",
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.", "heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
"proportional_function": "Algorithm to use (TPI is the only one for now)", "proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id",
"climate_entity2_id": "2nd underlying climate entity id",
"climate_entity3_id": "3rd underlying climate entity id",
"climate_entity4_id": "4th underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode", "ac_mode": "Use the Air Conditioning (AC) mode",
"valve_entity_id": "1st valve number entity id",
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature", "auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent", "auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update", "auto_regulation_periode_min": "Duration in minutes between two regulation update",
"auto_regulation_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation", "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", "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"
} }
}, },
"tpi": { "tpi": {
@@ -128,7 +113,7 @@
}, },
"presets": { "presets": {
"title": "Presets", "title": "Presets",
"description": "Check if the thermostat will use central presets. Uncheck and the thermostat will have its own preset entities", "description": "Select if the thermostat will use central preset - deselect for the thermostat to have its own presets",
"data": { "data": {
"use_presets_central_config": "Use central presets configuration" "use_presets_central_config": "Use central presets configuration"
} }
@@ -151,8 +136,8 @@
"window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used", "window_auto_open_threshold": "Recommended value: between 3 and 10. Leave empty if automatic window open detection is not used",
"window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used", "window_auto_close_threshold": "Recommended value: 0. Leave empty if automatic window open detection is not used",
"window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used", "window_auto_max_duration": "Recommended value: 60 (one hour). Leave empty if automatic window open detection is not used",
"use_window_central_config": "Check to use the central window configuration. Uncheck to use a specific window configuration for this VTherm", "use_window_central_config": "Select to use the central window configuration. Deselect to use a specific window configuration for this VTherm",
"window_action": "Action to do if window is deteted as open" "window_action": "Action to perform if window is deteted as open"
} }
}, },
"motion": { "motion": {
@@ -177,7 +162,7 @@
}, },
"power": { "power": {
"title": "Power management", "title": "Power management",
"description": "Power management attributes.\nGives the power and max power sensor of your home.\nThen specify the power consumption of the heater when on.\nAll sensors and device power should have the same unit (kW or W).", "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).",
"data": { "data": {
"power_sensor_entity_id": "Power", "power_sensor_entity_id": "Power",
"max_power_sensor_entity_id": "Max power", "max_power_sensor_entity_id": "Max power",
@@ -196,7 +181,7 @@
"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.",
"data": { "data": {
"presence_sensor_entity_id": "Presence sensor", "presence_sensor_entity_id": "Presence sensor",
"use_presence_central_config": "Use central presence temperature configuration. Uncheck to use specific temperature entities" "use_presence_central_config": "Use central presence temperature configuration. Deselect to use specific temperature entities"
}, },
"data_description": { "data_description": {
"presence_sensor_entity_id": "Presence sensor entity id" "presence_sensor_entity_id": "Presence sensor entity id"
@@ -206,26 +191,54 @@
"title": "Advanced parameters", "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.",
"data": { "data": {
"minimal_activation_delay": "Minimal activation delay", "minimal_activation_delay": "Minimum activation delay",
"security_delay_min": "Safety delay (in minutes)", "security_delay_min": "Safety delay (in minutes)",
"security_min_on_percent": "Minimal power percent to enable safety mode", "security_min_on_percent": "Minimum power percent to enable safety mode",
"security_default_on_percent": "Power percent to use in safety mode", "security_default_on_percent": "Power percent to use in safety mode",
"use_advanced_central_config": "Use central advanced configuration" "use_advanced_central_config": "Use central advanced configuration"
}, },
"data_description": { "data_description": {
"minimal_activation_delay": "Delay in seconds under which the equipment will not be activated", "minimal_activation_delay": "Delay in seconds under which the equipment will not be activated",
"security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state", "security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state",
"security_min_on_percent": "Minimal heating percent value for safety preset activation. Below this amount of power percent the thermostat won't go into safety preset", "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", "security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm" "use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
} }
},
"central_boiler": {
"title": "Control of the central boiler",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
"data": {
"central_boiler_activation_service": "Command to turn-on",
"central_boiler_deactivation_service": "Command to turn-off"
},
"data_description": {
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
}
},
"valve_regulation": {
"title": "Self-regulation with valve",
"description": "Configuration for self-regulation with direct control of the valve",
"data": {
"offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities",
"closing_degree_entity_ids": "Closing degree entities",
"proportional_function": "Algorithm"
},
"data_description": {
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have 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",
"proportional_function": "Algorithm to use (TPI is the only one for now)"
}
} }
}, },
"error": { "error": {
"unknown": "Unexpected error", "unknown": "Unexpected error",
"unknown_entity": "Unknown entity id", "unknown_entity": "Unknown entity id",
"window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both", "window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both",
"no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it." "no_central_config": "You cannot select 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it."
}, },
"abort": { "abort": {
"already_configured": "Device is already configured" "already_configured": "Device is already configured"
@@ -245,7 +258,7 @@
}, },
"menu": { "menu": {
"title": "Menu", "title": "Menu",
"description": "Configure your thermostat. You will be able to finalize the configuration when all needed parameters are valued.", "description": "Configure your thermostat. You will be able to finalize the configuration when all required parameters are entered.",
"menu_options": { "menu_options": {
"main": "Main attributes", "main": "Main attributes",
"central_boiler": "Central boiler", "central_boiler": "Central boiler",
@@ -258,7 +271,10 @@
"power": "Power management", "power": "Power management",
"presence": "Presence detection", "presence": "Presence detection",
"advanced": "Advanced parameters", "advanced": "Advanced parameters",
"finalize": "All done" "auto_start_stop": "Auto start and stop",
"valve_regulation": "Valve regulation configuration",
"finalize": "All done",
"configuration_not_complete": "Configuration not complete"
} }
}, },
"main": { "main": {
@@ -267,19 +283,21 @@
"data": { "data": {
"name": "Name", "name": "Name",
"thermostat_type": "Thermostat type", "thermostat_type": "Thermostat type",
"temperature_sensor_entity_id": "Room temperature sensor entity id", "temperature_sensor_entity_id": "Room temperature",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature datetime",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id", "external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id",
"cycle_min": "Cycle duration (minutes)", "cycle_min": "Cycle duration (minutes)",
"temp_min": "Minimal temperature allowed", "temp_min": "Minimum temperature allowed",
"temp_max": "Maximal temperature allowed", "temp_max": "Maximum temperature allowed",
"step_temperature": "Temperature step", "step_temperature": "Temperature step",
"device_power": "Device power", "device_power": "Device power",
"use_central_mode": "Enable the control by central entity (need central config). Check to enable the control of the VTherm with the select central_mode entities.", "use_central_mode": "Enable the control by central entity (requires central config). Check to enable the control of the VTherm with the select central_mode entities.",
"use_main_central_config": "Use additional central main configuration. Check to use the central main configuration (outdoor temperature, min, max, step, ...).", "use_main_central_config": "Use additional central main configuration. Check to use the central main configuration (outdoor temperature, min, max, step, ...).",
"add_central_boiler_control": "Add a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after seecting this checkbox to take effect. If one VTherm need heating, the boiler will be turned on. If no VTherm needs heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the next configuration page",
"used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler" "used_by_controls_central_boiler": "Used by central boiler. Check if this VTherm should have control on the central boiler"
}, },
"data_description": { "data_description": {
"temperature_sensor_entity_id": "Room temperature sensor entity id",
"last_seen_temperature_sensor_entity_id": "Last seen room temperature sensor entity id. Should be datetime sensor",
"external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected" "external_temperature_sensor_entity_id": "Outdoor temperature sensor entity id. Not used if central configuration is selected"
} }
}, },
@@ -290,57 +308,37 @@
"use_window_feature": "Use window detection", "use_window_feature": "Use window detection",
"use_motion_feature": "Use motion detection", "use_motion_feature": "Use motion detection",
"use_power_feature": "Use power management", "use_power_feature": "Use power management",
"use_presence_feature": "Use presence detection" "use_presence_feature": "Use presence detection",
"use_central_boiler_feature": "Use a central boiler. Check to add a control to your central boiler. You will have to configure the VTherm which will have a control of the central boiler after selecting this checkbox to take effect. If one VTherm requires heating, the boiler will be turned on. If no VTherm requires heating, the boiler will be turned off. Commands for turning on/off the central boiler are given in the related configuration page",
"use_auto_start_stop_feature": "Use the auto start and stop feature"
} }
}, },
"type": { "type": {
"title": "Entities - {name}", "title": "Entities - {name}",
"description": "Linked entities attributes", "description": "Linked entities attributes",
"data": { "data": {
"heater_entity_id": "1st heater switch", "underlying_entity_ids": "The device(s) to be controlled",
"heater_entity2_id": "2nd heater switch",
"heater_entity3_id": "3rd heater switch",
"heater_entity4_id": "4th heater switch",
"heater_keep_alive": "Switch keep-alive interval in seconds", "heater_keep_alive": "Switch keep-alive interval in seconds",
"proportional_function": "Algorithm", "proportional_function": "Algorithm",
"climate_entity_id": "1st underlying climate",
"climate_entity2_id": "2nd underlying climate",
"climate_entity3_id": "3rd underlying climate",
"climate_entity4_id": "4th underlying climate",
"ac_mode": "AC mode", "ac_mode": "AC mode",
"valve_entity_id": "1st valve number",
"valve_entity2_id": "2nd valve number",
"valve_entity3_id": "3rd valve number",
"valve_entity4_id": "4th valve number",
"auto_regulation_mode": "Self-regulation", "auto_regulation_mode": "Self-regulation",
"auto_regulation_dtemp": "Regulation threshold", "auto_regulation_dtemp": "Regulation threshold",
"auto_regulation_periode_min": "Regulation minimal period", "auto_regulation_periode_min": "Regulation minimum period",
"auto_regulation_use_device_temp": "Use internal temperature of the underlying", "auto_regulation_use_device_temp": "Use internal temperature of the underlying",
"inverse_switch_command": "Inverse switch command", "inverse_switch_command": "Inverse switch command",
"auto_fan_mode": " Auto fan mode" "auto_fan_mode": "Auto fan mode"
}, },
"data_description": { "data_description": {
"heater_entity_id": "Mandatory heater entity id", "underlying_entity_ids": "The device(s) to be controlled - 1 is required",
"heater_entity2_id": "Optional 2nd Heater entity id. Leave empty if not used",
"heater_entity3_id": "Optional 3rd Heater entity id. Leave empty if not used",
"heater_entity4_id": "Optional 4th Heater entity id. Leave empty if not used",
"heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.", "heater_keep_alive": "Optional heater switch state refresh interval. Leave empty if not required.",
"proportional_function": "Algorithm to use (TPI is the only one for now)", "proportional_function": "Algorithm to use (TPI is the only one for now)",
"climate_entity_id": "Underlying climate entity id",
"climate_entity2_id": "2nd underlying climate entity id",
"climate_entity3_id": "3rd underlying climate entity id",
"climate_entity4_id": "4th underlying climate entity id",
"ac_mode": "Use the Air Conditioning (AC) mode", "ac_mode": "Use the Air Conditioning (AC) mode",
"valve_entity_id": "1st valve number entity id",
"valve_entity2_id": "2nd valve number entity id",
"valve_entity3_id": "3rd valve number entity id",
"valve_entity4_id": "4th valve number entity id",
"auto_regulation_mode": "Auto adjustment of the target temperature", "auto_regulation_mode": "Auto adjustment of the target temperature",
"auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent", "auto_regulation_dtemp": "The threshold in ° (or % for valve) under which the temperature change will not be sent",
"auto_regulation_periode_min": "Duration in minutes between two regulation update", "auto_regulation_periode_min": "Duration in minutes between two regulation update",
"auto_regulation_use_device_temp": "Use the eventual internal temperature sensor of the underlying to speedup the self-regulation", "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", "inverse_switch_command": "For switch with pilot wire and diode you may need to invert the command",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" "auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary"
} }
}, },
"tpi": { "tpi": {
@@ -437,19 +435,47 @@
"title": "Advanced - {name}", "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": "Advanced parameters. Leave the default values if you don't know what you are doing.\nThese parameters can lead to very poor temperature control or bad power regulation.",
"data": { "data": {
"minimal_activation_delay": "Minimal activation delay", "minimal_activation_delay": "Minimum activation delay",
"security_delay_min": "Safety delay (in minutes)", "security_delay_min": "Safety delay (in minutes)",
"security_min_on_percent": "Minimal power percent to enable safety mode", "security_min_on_percent": "Minimum power percent to enable safety mode",
"security_default_on_percent": "Power percent to use in safety mode", "security_default_on_percent": "Power percent to use in safety mode",
"use_advanced_central_config": "Use central advanced configuration" "use_advanced_central_config": "Use central advanced configuration"
}, },
"data_description": { "data_description": {
"minimal_activation_delay": "Delay in seconds under which the equipment will not be activated", "minimal_activation_delay": "Delay in seconds under which the equipment will not be activated",
"security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state", "security_delay_min": "Maximum allowed delay in minutes between two temperature measurements. Above this delay the thermostat will turn to a safety off state",
"security_min_on_percent": "Minimal heating percent value for safety preset activation. Below this amount of power percent the thermostat won't go into safety preset", "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", "security_default_on_percent": "The default heating power percent value in safety preset. Set to 0 to switch off heater in safety preset",
"use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm" "use_advanced_central_config": "Check to use the central advanced configuration. Uncheck to use a specific advanced configuration for this VTherm"
} }
},
"central_boiler": {
"title": "Control of the central boiler - {name}",
"description": "Enter the services to call to turn on/off the central boiler. Leave blank if no service call is to be made (in this case, you will have to manage the turning on/off of your central boiler yourself). The service called must be formatted as follows: `entity_id/service_name[/attribute:value]` (/attribute:value is optional)\nFor example:\n- to turn on a switch: `switch.controle_chaudiere/switch.turn_on`\n- to turn off a switch: `switch.controle_chaudiere/switch.turn_off`\n- to program the boiler to 25° and thus force its ignition: `climate.thermostat_chaudiere/climate.set_temperature/temperature:25`\n- to send 10° to the boiler and thus force its extinction: `climate.thermostat_chaudiere/climate.set_temperature/temperature:10`",
"data": {
"central_boiler_activation_service": "Command to turn-on",
"central_boiler_deactivation_service": "Command to turn-off"
},
"data_description": {
"central_boiler_activation_service": "Command to turn-on the central boiler formatted like entity_id/service_name[/attribut:valeur]",
"central_boiler_deactivation_service": "Command to turn-off the central boiler formatted like entity_id/service_name[/attribut:valeur]"
}
},
"valve_regulation": {
"title": "Self-regulation with valve - {name}",
"description": "Configuration for self-regulation with direct control of the valve",
"data": {
"offset_calibration_entity_ids": "Offset calibration entities",
"opening_degree_entity_ids": "Opening degree entities",
"closing_degree_entity_ids": "Closing degree entities",
"proportional_function": "Algorithm"
},
"data_description": {
"offset_calibration_entity_ids": "The list of the 'offset calibration' entities. Set it if your TRV have 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",
"proportional_function": "Algorithm to use (TPI is the only one for now)"
}
} }
}, },
"error": { "error": {
@@ -457,7 +483,8 @@
"unknown_entity": "Unknown entity id", "unknown_entity": "Unknown entity id",
"window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both", "window_open_detection_method": "Only one window open detection method should be used. Use either window sensor or automatic detection through temperature threshold but not both",
"no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.", "no_central_config": "You cannot check 'use central configuration' because no central configuration was found. You need to create a Versatile Thermostat of type 'Central Configuration' to use it.",
"service_configuration_format": "The format of the service configuration is wrong" "service_configuration_format": "The format of the service configuration is wrong",
"valve_regulation_nb_entities_incorrect": "The number of valve entities for valve regulation should be equal to the number of underlyings"
}, },
"abort": { "abort": {
"already_configured": "Device is already configured" "already_configured": "Device is already configured"
@@ -479,7 +506,8 @@
"auto_regulation_medium": "Medium", "auto_regulation_medium": "Medium",
"auto_regulation_light": "Light", "auto_regulation_light": "Light",
"auto_regulation_expert": "Expert", "auto_regulation_expert": "Expert",
"auto_regulation_none": "No auto-regulation" "auto_regulation_none": "No auto-regulation",
"auto_regulation_valve": "Direct control of valve"
} }
}, },
"auto_fan_mode": { "auto_fan_mode": {
@@ -506,6 +534,14 @@
"comfort": "Comfort", "comfort": "Comfort",
"boost": "Boost" "boost": "Boost"
} }
},
"auto_start_stop": {
"options": {
"auto_start_stop_none": "No auto start/stop",
"auto_start_stop_slow": "Slow detection",
"auto_start_stop_medium": "Medium detection",
"auto_start_stop_fast": "Fast detection"
}
} }
}, },
"entity": { "entity": {
@@ -516,7 +552,8 @@
"state": { "state": {
"power": "Shedding", "power": "Shedding",
"security": "Safety", "security": "Safety",
"none": "Manual" "none": "Manual",
"frost": "Frost"
} }
} }
} }

View File

@@ -27,7 +27,10 @@
"power": "Gestion de la puissance", "power": "Gestion de la puissance",
"presence": "Détection de présence", "presence": "Détection de présence",
"advanced": "Paramètres avancés", "advanced": "Paramètres avancés",
"finalize": "Finaliser la création" "auto_start_stop": "Allumage/extinction automatique",
"valve_regulation": "Configuration de la regulation par vanne",
"finalize": "Finaliser la création",
"configuration_not_complete": "Configuration incomplète"
} }
}, },
"main": { "main": {
@@ -36,8 +39,9 @@
"data": { "data": {
"name": "Nom", "name": "Nom",
"thermostat_type": "Type de thermostat", "thermostat_type": "Type de thermostat",
"temperature_sensor_entity_id": "Température sensor entity id", "temperature_sensor_entity_id": "Capteur de température",
"external_temperature_sensor_entity_id": "Température exterieure sensor entity id", "last_seen_temperature_sensor_entity_id": "Dernière vue capteur de température",
"external_temperature_sensor_entity_id": "Capteur de température exterieure",
"cycle_min": "Durée du cycle (minutes)", "cycle_min": "Durée du cycle (minutes)",
"temp_min": "Température minimale permise", "temp_min": "Température minimale permise",
"temp_max": "Température maximale permise", "temp_max": "Température maximale permise",
@@ -45,11 +49,12 @@
"device_power": "Puissance de l'équipement", "device_power": "Puissance de l'équipement",
"use_central_mode": "Autoriser le controle par une entity centrale ('nécessite une config. centrale`). Cochez pour autoriser le contrôle du VTherm par la liste déroulante 'central_mode' de l'entité configuration centrale.", "use_central_mode": "Autoriser le controle par une entity centrale ('nécessite une config. centrale`). Cochez pour autoriser le contrôle du VTherm par la liste déroulante 'central_mode' de l'entité configuration centrale.",
"use_main_central_config": "Utiliser la configuration centrale supplémentaire. Cochez pour utiliser la configuration centrale supplémentaire (température externe, min, max, pas, ...)", "use_main_central_config": "Utiliser la configuration centrale supplémentaire. Cochez pour utiliser la configuration centrale supplémentaire (température externe, min, max, pas, ...)",
"add_central_boiler_control": "Ajouter une chaudière centrale. Cochez pour ajouter un controle sur une chaudière centrale. Vous devrez ensuite configurer les VTherms qui commande la chaudière centrale pour que cette option prenne effet. Si au moins un des VTherm a besoin de chauffer, la chaudière centrale sera activée. Si aucun VTherm n'a besoin de chauffer, elle sera éteinte. Les commandes pour allumer/éteindre la chaudière centrale sont données dans la page de configuration suivante.",
"used_by_controls_central_boiler": "Utilisé par la chaudière centrale. Cochez si ce VTherm doit contrôler la chaudière centrale." "used_by_controls_central_boiler": "Utilisé par la chaudière centrale. Cochez si ce VTherm doit contrôler la chaudière centrale."
}, },
"data_description": { "data_description": {
"external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure." "temperature_sensor_entity_id": "Id d'entité du capteur de température",
"last_seen_temperature_sensor_entity_id": "Id d'entité du capteur donnant la date et heure de dernière vue capteur de température. L'état doit être au format date heure (ex: 2024-03-31T17:07:03+00:00)",
"external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure. Non utilisé si une configuration centrale est définie"
} }
}, },
"features": { "features": {
@@ -59,55 +64,35 @@
"use_window_feature": "Avec détection des ouvertures", "use_window_feature": "Avec détection des ouvertures",
"use_motion_feature": "Avec détection de mouvement", "use_motion_feature": "Avec détection de mouvement",
"use_power_feature": "Avec gestion de la puissance", "use_power_feature": "Avec gestion de la puissance",
"use_presence_feature": "Avec détection de présence" "use_presence_feature": "Avec détection de présence",
"use_central_boiler_feature": "Ajouter une chaudière centrale. Cochez pour ajouter un controle sur une chaudière centrale. Vous devrez ensuite configurer les VTherms qui commande la chaudière centrale pour que cette option prenne effet. Si au moins un des VTherm a besoin de chauffer, la chaudière centrale sera activée. Si aucun VTherm n'a besoin de chauffer, elle sera éteinte. Les commandes pour allumer/éteindre la chaudière centrale sont données dans la page de configuration suivante.",
"use_auto_start_stop_feature": "Avec démarrage et extinction automatique"
} }
}, },
"type": { "type": {
"title": "Entité(s) liée(s)", "title": "Entité(s) liée(s)",
"description": "Attributs de(s) l'entité(s) liée(s)", "description": "Attributs de(s) l'entité(s) liée(s)",
"data": { "data": {
"heater_entity_id": "1er radiateur", "underlying_entity_ids": "Les équipements à controller",
"heater_entity2_id": "2ème radiateur",
"heater_entity3_id": "3ème radiateur",
"heater_entity4_id": "4ème radiateur",
"heater_keep_alive": "keep-alive (sec)", "heater_keep_alive": "keep-alive (sec)",
"proportional_function": "Algorithme", "proportional_function": "Algorithme",
"climate_entity_id": "Thermostat sous-jacent",
"climate_entity2_id": "2ème thermostat sous-jacent",
"climate_entity3_id": "3ème thermostat sous-jacent",
"climate_entity4_id": "4ème thermostat sous-jacent",
"ac_mode": "AC mode ?", "ac_mode": "AC mode ?",
"valve_entity_id": "1ère valve number",
"valve_entity2_id": "2ème valve number",
"valve_entity3_id": "3ème valve number",
"valve_entity4_id": "4ème valve number",
"auto_regulation_mode": "Auto-régulation", "auto_regulation_mode": "Auto-régulation",
"auto_regulation_dtemp": "Seuil de régulation", "auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale de régulation", "auto_regulation_periode_min": "Période minimale de régulation",
"auto_regulation_use_device_temp": "Utiliser la température interne du sous-jacent", "auto_regulation_use_device_temp": "Compenser la température interne du sous-jacent",
"inverse_switch_command": "Inverser la commande", "inverse_switch_command": "Inverser la commande",
"auto_fan_mode": " Auto ventilation mode" "auto_fan_mode": " Auto ventilation mode"
}, },
"data_description": { "data_description": {
"heater_entity_id": "Entity id du 1er radiateur obligatoire", "underlying_entity_ids": "La liste des équipements qui seront controlés par ce VTherm",
"heater_entity2_id": "Optionnel entity id du 2ème radiateur",
"heater_entity3_id": "Optionnel entity id du 3ème radiateur",
"heater_entity4_id": "Optionnel entity id du 4ème radiateur",
"heater_keep_alive": "Intervalle de rafraichissement du switch en secondes. Laisser vide pour désactiver. À n'utiliser que pour les switchs qui le nécessite.", "heater_keep_alive": "Intervalle de rafraichissement du switch en secondes. Laisser vide pour désactiver. À n'utiliser que pour les switchs qui le nécessite.",
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)", "proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
"climate_entity_id": "Entity id du thermostat sous-jacent",
"climate_entity2_id": "Entity id du 2ème thermostat sous-jacent",
"climate_entity3_id": "Entity id du 3ème thermostat sous-jacent",
"climate_entity4_id": "Entity id du 4ème thermostat sous-jacent",
"ac_mode": "Utilisation du mode Air Conditionné (AC)", "ac_mode": "Utilisation du mode Air Conditionné (AC)",
"valve_entity_id": "Entity id de la 1ère valve", "auto_regulation_mode": "Utilisation de l'auto-régulation faite par VTherm",
"valve_entity2_id": "Entity id de la 2ème valve", "auto_regulation_dtemp": "Le seuil en ° (ou % pour les vannes) en-dessous duquel la régulation ne sera pas envoyée",
"valve_entity3_id": "Entity id de la 3ème valve",
"valve_entity4_id": "Entity id de la 4ème valve",
"auto_regulation_mode": "Ajustement automatique de la température cible",
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation", "auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
"auto_regulation_use_device_temp": "Utiliser la temperature interne du sous-jacent pour accélérer l'auto-régulation", "auto_regulation_use_device_temp": "Compenser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode", "inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important" "auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
} }
@@ -231,6 +216,22 @@
"central_boiler_activation_service": "Commande à éxecuter pour allumer la chaudière centrale au format entity_id/service_name[/attribut:valeur]", "central_boiler_activation_service": "Commande à éxecuter pour allumer la chaudière centrale au format entity_id/service_name[/attribut:valeur]",
"central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]" "central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]"
} }
},
"valve_regulation": {
"title": "Auto-régulation par vanne - {name}",
"description": "Configuration de l'auto-régulation par controle 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'",
"closing_degree_entity_ids": "Entités 'fermeture de la vanne'",
"proportional_function": "Algorithme"
},
"data_description": {
"offset_calibration_entity_ids": "La liste des entités 'calibrage du décalage' (offset calibration). Configurez le si votre TRV possède cette fonction pour une meilleure régulation. Il doit y en avoir une par entité climate sous-jacente",
"opening_degree_entity_ids": "La liste des entités 'ouverture de vanne'. Il doit y en avoir une par entité climate sous-jacente",
"closing_degree_entity_ids": "La liste des entités 'fermeture de la vanne'. Configurez le si votre TRV possède cette fonction pour une meilleure régulation. Il doit y en avoir une par entité climate sous-jacente",
"proportional_function": "Algorithme à utiliser (seulement TPI est disponible)"
}
} }
}, },
"error": { "error": {
@@ -256,7 +257,7 @@
} }
}, },
"menu": { "menu": {
"title": "Menu", "title": "Menu - {name}",
"description": "Paramétrez votre thermostat. Vous pourrez finaliser la configuration quand tous les paramètres auront été saisis.", "description": "Paramétrez votre thermostat. Vous pourrez finaliser la configuration quand tous les paramètres auront été saisis.",
"menu_options": { "menu_options": {
"main": "Principaux Attributs", "main": "Principaux Attributs",
@@ -270,7 +271,10 @@
"power": "Gestion de la puissance", "power": "Gestion de la puissance",
"presence": "Détection de présence", "presence": "Détection de présence",
"advanced": "Paramètres avancés", "advanced": "Paramètres avancés",
"finalize": "Finaliser les modifications" "auto_start_stop": "Allumage/extinction automatique",
"valve_regulation": "Configuration de la regulation par vanne",
"finalize": "Finaliser les modifications",
"configuration_not_complete": "Configuration incomplète"
} }
}, },
"main": { "main": {
@@ -279,20 +283,22 @@
"data": { "data": {
"name": "Nom", "name": "Nom",
"thermostat_type": "Type de thermostat", "thermostat_type": "Type de thermostat",
"temperature_sensor_entity_id": "Température sensor entity id", "temperature_sensor_entity_id": "Capteur de température",
"external_temperature_sensor_entity_id": "Température exterieure sensor entity id", "last_seen_temperature_sensor_entity_id": "Dernière vue capteur de température",
"external_temperature_sensor_entity_id": "Capteur de température exterieure",
"cycle_min": "Durée du cycle (minutes)", "cycle_min": "Durée du cycle (minutes)",
"temp_min": "Température minimale permise", "temp_min": "Température minimale permise",
"temp_max": "Température maximale permise", "temp_max": "Température maximale permise",
"step_temperature": "Pas de température", "step_temperature": "Pas de température",
"device_power": "Puissance de l'équipement", "device_power": "Puissance de l'équipement",
"use_central_mode": "Autoriser le controle par une entity centrale ('nécessite une config. centrale`). Cochez pour autoriser le contrôle du VTherm par la liste déroulante 'central_mode' de l'entité configuration centrale.", "use_central_mode": "Autoriser le controle par une entity centrale ('nécessite une config. centrale`). Cochez pour autoriser le contrôle du VTherm par la liste déroulante 'central_mode' de l'entité configuration centrale.",
"use_main_central_config": "Utiliser la configuration centrale supplémentaire. Cochez pour utiliser la configuration centrale supplémentaire (température externe, min, max, pas, ...).", "use_main_central_config": "Utiliser la configuration centrale supplémentaire. Cochez pour utiliser la configuration centrale supplémentaire (température externe, min, max, pas, ...)",
"add_central_boiler_control": "Ajouter une chaudière centrale. Cochez pour ajouter un controle sur une chaudière centrale. Vous devrez ensuite configurer les VTherms qui commande la chaudière centrale pour que cette option prenne effet. Si au moins un des VTherm a besoin de chauffer, la chaudière centrale sera activée. Si aucun VTherm n'a besoin de chauffer, elle sera éteinte. Les commandes pour allumer/éteindre la chaudière centrale sont données dans la page de configuration suivante.",
"used_by_controls_central_boiler": "Utilisé par la chaudière centrale. Cochez si ce VTherm doit contrôler la chaudière centrale." "used_by_controls_central_boiler": "Utilisé par la chaudière centrale. Cochez si ce VTherm doit contrôler la chaudière centrale."
}, },
"data_description": { "data_description": {
"external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure. N'est pas utilisé si la configuration centrale est utilisée." "temperature_sensor_entity_id": "Id d'entité du capteur de température",
"last_seen_temperature_sensor_entity_id": "Id d'entité du capteur donnant la date et heure de dernière vue capteur de température. L'état doit être au format date heure (ex: 2024-03-31T17:07:03+00:00)",
"external_temperature_sensor_entity_id": "Entity id du capteur de température extérieure. Non utilisé si une configuration centrale est définie"
} }
}, },
"features": { "features": {
@@ -302,55 +308,35 @@
"use_window_feature": "Avec détection des ouvertures", "use_window_feature": "Avec détection des ouvertures",
"use_motion_feature": "Avec détection de mouvement", "use_motion_feature": "Avec détection de mouvement",
"use_power_feature": "Avec gestion de la puissance", "use_power_feature": "Avec gestion de la puissance",
"use_presence_feature": "Avec détection de présence" "use_presence_feature": "Avec détection de présence",
"use_central_boiler_feature": "Ajouter une chaudière centrale. Cochez pour ajouter un controle sur une chaudière centrale. Vous devrez ensuite configurer les VTherms qui commande la chaudière centrale pour que cette option prenne effet. Si au moins un des VTherm a besoin de chauffer, la chaudière centrale sera activée. Si aucun VTherm n'a besoin de chauffer, elle sera éteinte. Les commandes pour allumer/éteindre la chaudière centrale sont données dans la page de configuration suivante.",
"use_auto_start_stop_feature": "Avec démarrage et extinction automatique"
} }
}, },
"type": { "type": {
"title": "Entités - {name}", "title": "Entité(s) liée(s) - {name}",
"description": "Attributs de(s) l'entité(s) liée(s)", "description": "Attributs de(s) l'entité(s) liée(s)",
"data": { "data": {
"heater_entity_id": "1er radiateur", "underlying_entity_ids": "Les équipements à controller",
"heater_entity2_id": "2ème radiateur", "heater_keep_alive": "keep-alive (sec)",
"heater_entity3_id": "3ème radiateur",
"heater_entity4_id": "4ème radiateur",
"heater_keep_alive": "Keep-alive (sec)",
"proportional_function": "Algorithme", "proportional_function": "Algorithme",
"climate_entity_id": "Thermostat sous-jacent",
"climate_entity2_id": "2ème thermostat sous-jacent",
"climate_entity3_id": "3ème thermostat sous-jacent",
"climate_entity4_id": "4ème thermostat sous-jacent",
"ac_mode": "AC mode ?", "ac_mode": "AC mode ?",
"valve_entity_id": "1ère valve", "auto_regulation_mode": "Auto-régulation",
"valve_entity2_id": "2ème valve",
"valve_entity3_id": "3ème valve",
"valve_entity4_id": "4ème valve",
"auto_regulation_mode": "Auto-regulation",
"auto_regulation_dtemp": "Seuil de régulation", "auto_regulation_dtemp": "Seuil de régulation",
"auto_regulation_periode_min": "Période minimale de régulation", "auto_regulation_periode_min": "Période minimale de régulation",
"auto_regulation_use_device_temp": "Utiliser la température interne du sous-jacent", "auto_regulation_use_device_temp": "Compenser la température interne du sous-jacent",
"inverse_switch_command": "Inverser la commande", "inverse_switch_command": "Inverser la commande",
"auto_fan_mode": " Auto fan mode" "auto_fan_mode": " Auto ventilation mode"
}, },
"data_description": { "data_description": {
"heater_entity_id": "Entity id du 1er radiateur obligatoire", "underlying_entity_ids": "La liste des équipements qui seront controlés par ce VTherm",
"heater_entity2_id": "Optionnel entity id du 2ème radiateur",
"heater_entity3_id": "Optionnel entity id du 3ème radiateur",
"heater_entity4_id": "Optionnel entity id du 4ème radiateur",
"heater_keep_alive": "Intervalle de rafraichissement du switch en secondes. Laisser vide pour désactiver. À n'utiliser que pour les switchs qui le nécessite.", "heater_keep_alive": "Intervalle de rafraichissement du switch en secondes. Laisser vide pour désactiver. À n'utiliser que pour les switchs qui le nécessite.",
"proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)", "proportional_function": "Algorithme à utiliser (Seul TPI est disponible pour l'instant)",
"climate_entity_id": "Entity id du thermostat sous-jacent",
"climate_entity2_id": "Entity id du 2ème thermostat sous-jacent",
"climate_entity3_id": "Entity id du 3ème thermostat sous-jacent",
"climate_entity4_id": "Entity id du 4ème thermostat sous-jacent",
"ac_mode": "Utilisation du mode Air Conditionné (AC)", "ac_mode": "Utilisation du mode Air Conditionné (AC)",
"valve_entity_id": "Entity id de la 1ère valve", "auto_regulation_mode": "Utilisation de l'auto-régulation faite par VTherm",
"valve_entity2_id": "Entity id de la 2ème valve", "auto_regulation_dtemp": "Le seuil en ° (ou % pour les vannes) en-dessous duquel la régulation ne sera pas envoyée",
"valve_entity3_id": "Entity id de la 3ème valve",
"valve_entity4_id": "Entity id de la 4ème valve",
"auto_regulation_mode": "Ajustement automatique de la consigne",
"auto_regulation_dtemp": "Le seuil en ° (ou % pour les valves) en-dessous duquel la régulation ne sera pas envoyée",
"auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation", "auto_regulation_periode_min": "La durée en minutes entre deux mise à jour faites par la régulation",
"auto_regulation_use_device_temp": "Utiliser la temperature interne du sous-jacent pour accélérer l'auto-régulation", "auto_regulation_use_device_temp": "Compenser la temperature interne du sous-jacent pour accélérer l'auto-régulation",
"inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode", "inverse_switch_command": "Inverse la commande du switch pour une installation avec fil pilote et diode",
"auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important" "auto_fan_mode": "Active la ventilation automatiquement en cas d'écart important"
} }
@@ -468,6 +454,22 @@
"central_boiler_activation_service": "Commande à éxecuter pour allumer la chaudière centrale au format entity_id/service_name[/attribut:valeur]", "central_boiler_activation_service": "Commande à éxecuter pour allumer la chaudière centrale au format entity_id/service_name[/attribut:valeur]",
"central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]" "central_boiler_deactivation_service": "Commande à éxecuter pour étiendre la chaudière centrale au format entity_id/service_name[/attribut:valeur]"
} }
},
"valve_regulation": {
"title": "Auto-régulation par vanne - {name}",
"description": "Configuration de l'auto-régulation par controle 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'",
"closing_degree_entity_ids": "Entités 'fermeture de la vanne'",
"proportional_function": "Algorithme"
},
"data_description": {
"offset_calibration_entity_ids": "La liste des entités 'calibrage du décalage' (offset calibration). Configurez le si votre TRV possède cette fonction pour une meilleure régulation. Il doit y en avoir une par entité climate sous-jacente",
"opening_degree_entity_ids": "La liste des entités 'ouverture de vanne'. Il doit y en avoir une par entité climate sous-jacente",
"closing_degree_entity_ids": "La liste des entités 'fermeture de la vanne'. Configurez le si votre TRV possède cette fonction pour une meilleure régulation. Il doit y en avoir une par entité climate sous-jacente",
"proportional_function": "Algorithme à utiliser (seulement TPI est disponible)"
}
} }
}, },
"error": { "error": {
@@ -475,7 +477,8 @@
"unknown_entity": "entity id inconnu", "unknown_entity": "entity id inconnu",
"window_open_detection_method": "Une seule méthode de détection des ouvertures ouvertes doit être utilisée. Utilisez le détecteur d'ouverture ou les seuils de température mais pas les deux.", "window_open_detection_method": "Une seule méthode de détection des ouvertures ouvertes doit être utilisée. Utilisez le détecteur d'ouverture ou les seuils de température mais pas les deux.",
"no_central_config": "Vous ne pouvez pas cocher 'Utiliser la configuration centrale' car aucune configuration centrale n'a été trouvée. Vous devez créer un Versatile Thermostat de type 'Central Configuration' pour pouvoir l'utiliser.", "no_central_config": "Vous ne pouvez pas cocher 'Utiliser la configuration centrale' car aucune configuration centrale n'a été trouvée. Vous devez créer un Versatile Thermostat de type 'Central Configuration' pour pouvoir l'utiliser.",
"service_configuration_format": "Mauvais format de la configuration du service" "service_configuration_format": "Mauvais format de la configuration du service",
"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"
}, },
"abort": { "abort": {
"already_configured": "Le device est déjà configuré" "already_configured": "Le device est déjà configuré"
@@ -497,7 +500,8 @@
"auto_regulation_medium": "Moyenne", "auto_regulation_medium": "Moyenne",
"auto_regulation_light": "Légère", "auto_regulation_light": "Légère",
"auto_regulation_expert": "Expert", "auto_regulation_expert": "Expert",
"auto_regulation_none": "Aucune" "auto_regulation_none": "Aucune",
"auto_regulation_valve": "Contrôle direct de la vanne"
} }
}, },
"auto_fan_mode": { "auto_fan_mode": {
@@ -524,6 +528,14 @@
"comfort": "Confort", "comfort": "Confort",
"boost": "Renforcé (boost)" "boost": "Renforcé (boost)"
} }
},
"auto_start_stop": {
"options": {
"auto_start_stop_none": "No auto start/stop",
"auto_start_stop_slow": "Slow detection",
"auto_start_stop_medium": "Medium detection",
"auto_start_stop_fast": "Fast detection"
}
} }
}, },
"entity": { "entity": {
@@ -534,7 +546,8 @@
"state": { "state": {
"power": "Délestage", "power": "Délestage",
"security": "Sécurité", "security": "Sécurité",
"none": "Manuel" "none": "Manuel",
"frost": "Hors Gel"
} }
} }
} }

View File

@@ -42,7 +42,7 @@
"valve_entity4_id": "Quarta valvola", "valve_entity4_id": "Quarta valvola",
"auto_regulation_mode": "Autoregolamentazione", "auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Comando inverso", "inverse_switch_command": "Comando inverso",
"auto_fan_mode": " Auto fan mode" "auto_fan_mode": "Auto fan mode"
}, },
"data_description": { "data_description": {
"heater_entity_id": "Entity id obbligatoria del primo riscaldatore", "heater_entity_id": "Entity id obbligatoria del primo riscaldatore",
@@ -62,7 +62,7 @@
"valve_entity4_id": "Entity id della quarta valvola", "valve_entity4_id": "Entity id della quarta valvola",
"auto_regulation_mode": "Regolazione automatica della temperatura target", "auto_regulation_mode": "Regolazione automatica della temperatura target",
"inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo", "inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" "auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary"
} }
}, },
"tpi": { "tpi": {
@@ -206,7 +206,7 @@
"valve_entity4_id": "Quarta valvola", "valve_entity4_id": "Quarta valvola",
"auto_regulation_mode": "Autoregolamentazione", "auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Comando inverso", "inverse_switch_command": "Comando inverso",
"auto_fan_mode": " Auto fan mode" "auto_fan_mode": "Auto fan mode"
}, },
"data_description": { "data_description": {
"heater_entity_id": "Entity id obbligatoria del primo riscaldatore", "heater_entity_id": "Entity id obbligatoria del primo riscaldatore",
@@ -226,7 +226,7 @@
"valve_entity4_id": "Entity id della quarta valvola", "valve_entity4_id": "Entity id della quarta valvola",
"auto_regulation_mode": "Autoregolamentazione", "auto_regulation_mode": "Autoregolamentazione",
"inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo", "inverse_switch_command": "Inverte il controllo dell'interruttore per un'installazione con filo pilota e diodo",
"auto_fan_mode": " Automatically activate fan when huge heating/cooling is necessary" "auto_fan_mode": "Automatically activate fan when huge heating/cooling is necessary"
} }
}, },
"tpi": { "tpi": {
@@ -364,7 +364,8 @@
"state": { "state": {
"power": "Ripartizione", "power": "Ripartizione",
"security": "Sicurezza", "security": "Sicurezza",
"none": "Manuale" "none": "Manuale",
"frost": "Gelo"
} }
} }
} }

View File

@@ -12,6 +12,25 @@
"thermostat_type": "Len jeden centrálny typ konfigurácie je možný" "thermostat_type": "Len jeden centrálny typ konfigurácie je možný"
} }
}, },
"menu": {
"title": "Menu",
"description": "Nakonfigurujte si termostat. Po zadaní všetkých požadovaných parametrov budete môcť dokončiť konfiguráciu.",
"menu_options": {
"main": "Hlavné atribúty",
"central_boiler": "Centrálny kotol",
"type": "Podklady",
"tpi": "TPI parametre",
"features": "Vlastnosti",
"presets": "Predvoľby",
"window": "Detekcia okien",
"motion": "Detekcia pohybu",
"power": "Správa napájania",
"presence": "Detekcia prítomnosti",
"advanced": "Pokročilé parametre",
"finalize": "Všetko hotové",
"configuration_not_complete": "Konfigurácia nie je dokončená"
}
},
"main": { "main": {
"title": "Pridajte nový všestranný termostat", "title": "Pridajte nový všestranný termostat",
"description": "Hlavné povinné atribúty", "description": "Hlavné povinné atribúty",
@@ -19,22 +38,32 @@
"name": "Názov", "name": "Názov",
"thermostat_type": "Termostat typ", "thermostat_type": "Termostat typ",
"temperature_sensor_entity_id": "ID entity snímača teploty", "temperature_sensor_entity_id": "ID entity snímača teploty",
"last_seen_temperature_sensor_entity_id": "Dátum posledného zobrazenia izbovej teploty",
"external_temperature_sensor_entity_id": "ID entity externého snímača teploty", "external_temperature_sensor_entity_id": "ID entity externého snímača teploty",
"cycle_min": "Trvanie cyklu (minúty)", "cycle_min": "Trvanie cyklu (minúty)",
"temp_min": "Minimálna povolená teplota", "temp_min": "Minimálna povolená teplota",
"temp_max": "Maximálna povolená teplota", "temp_max": "Maximálna povolená teplota",
"step_temperature": "Krok teploty",
"device_power": "Napájanie zariadenia", "device_power": "Napájanie zariadenia",
"use_central_mode": "Povoliť ovládanie centrálnou entitou (potrebná centrálna konfigurácia)", "use_central_mode": "Povoliť ovládanie centrálnou entitou (potrebná centrálna konfigurácia)",
"use_main_central_config": "Použite dodatočnú centrálnu hlavnú konfiguráciu. Začiarknite, ak chcete použiť centrálnu hlavnú konfiguráciu (vonkajšia teplota, min, max, krok, ...).",
"used_by_controls_central_boiler": "Používa sa centrálnym kotlom. Skontrolujte, či má mať tento VTherm ovládanie na centrálnom kotli"
},
"data_description": {
"temperature_sensor_entity_id": "ID entity snímača izbovej teploty",
"last_seen_temperature_sensor_entity_id": "Naposledy videný snímač izbovej teploty ID entity. Mal by to byť snímač dátumu a času",
"external_temperature_sensor_entity_id": "ID entity snímača vonkajšej teploty. Nepoužíva sa, ak je zvolená centrálna konfigurácia"
}
},
"features": {
"title": "Vlastnosti",
"description": "Vlastnosti termostatu",
"data": {
"use_window_feature": "Použite detekciu okien", "use_window_feature": "Použite detekciu okien",
"use_motion_feature": "Použite detekciu pohybu", "use_motion_feature": "Použite detekciu pohybu",
"use_power_feature": "Použite správu napájania", "use_power_feature": "Použite správu napájania",
"use_presence_feature": "Použite detekciu prítomnosti", "use_presence_feature": "Použite detekciu prítomnosti",
"use_main_central_config": "Použite centrálnu hlavnú konfiguráciu" "use_central_boiler_feature": "Použite centrálny kotol. Začiarknutím tohto políčka pridáte ovládanie do centrálneho kotla. Po zaškrtnutí tohto políčka budete musieť nakonfigurovať VTherm, ktorý bude mať ovládanie centrálneho kotla, aby sa prejavilo. Ak jeden VTherm vyžaduje ohrev, kotol sa zapne. Ak žiadny VTherm nevyžaduje ohrev, kotol sa vypne. Príkazy na zapnutie/vypnutie centrálneho kotla sú uvedené na príslušnej konfiguračnej stránke"
},
"data_description": {
"use_central_mode": "Zaškrtnutím povolíte ovládanie VTherm pomocou vybraných entít central_mode",
"use_main_central_config": "Začiarknite, ak chcete použiť centrálnu hlavnú konfiguráciu. Zrušte začiarknutie, ak chcete použiť špecifickú hlavnú konfiguráciu pre tento VTherm",
"external_temperature_sensor_entity_id": "ID entity snímača vonkajšej teploty. Nepoužíva sa, ak je zvolená centrálna konfigurácia"
} }
}, },
"type": { "type": {
@@ -45,6 +74,7 @@
"heater_entity2_id": "2. spínač ohrievača", "heater_entity2_id": "2. spínač ohrievača",
"heater_entity3_id": "3. spínač ohrievača", "heater_entity3_id": "3. spínač ohrievača",
"heater_entity4_id": "4. spínač ohrievača", "heater_entity4_id": "4. spínač ohrievača",
"heater_keep_alive": "Prepnite interval udržiavania v sekundách",
"proportional_function": "Algoritmus", "proportional_function": "Algoritmus",
"climate_entity_id": "1. základná klíma", "climate_entity_id": "1. základná klíma",
"climate_entity2_id": "2. základná klíma", "climate_entity2_id": "2. základná klíma",
@@ -58,6 +88,7 @@
"auto_regulation_mode": "Samoregulácia", "auto_regulation_mode": "Samoregulácia",
"auto_regulation_dtemp": "Regulačný prah", "auto_regulation_dtemp": "Regulačný prah",
"auto_regulation_periode_min": "Regulačné minimálne obdobie", "auto_regulation_periode_min": "Regulačné minimálne obdobie",
"auto_regulation_use_device_temp": "Použite vnútornú teplotu podkladu",
"inverse_switch_command": "Inverzný prepínací príkaz", "inverse_switch_command": "Inverzný prepínací príkaz",
"auto_fan_mode": "Režim automatického ventilátora" "auto_fan_mode": "Režim automatického ventilátora"
}, },
@@ -66,6 +97,7 @@
"heater_entity2_id": "Voliteľné ID entity 2. ohrievača. Ak sa nepoužíva, nechajte prázdne", "heater_entity2_id": "Voliteľné ID entity 2. ohrievača. Ak sa nepoužíva, nechajte prázdne",
"heater_entity3_id": "Voliteľné ID entity 3. ohrievača. Ak sa nepoužíva, nechajte prázdne", "heater_entity3_id": "Voliteľné ID entity 3. ohrievača. Ak sa nepoužíva, nechajte prázdne",
"heater_entity4_id": "Voliteľné ID entity 4. ohrievača. Ak sa nepoužíva, nechajte prázdne", "heater_entity4_id": "Voliteľné ID entity 4. ohrievača. Ak sa nepoužíva, nechajte prázdne",
"heater_keep_alive": "Voliteľný interval obnovy stavu spínača ohrievača. Ak to nie je potrebné, nechajte prázdne.",
"proportional_function": "Algoritmus, ktorý sa má použiť (TPI je zatiaľ jediný)", "proportional_function": "Algoritmus, ktorý sa má použiť (TPI je zatiaľ jediný)",
"climate_entity_id": "ID základnej klimatickej entity", "climate_entity_id": "ID základnej klimatickej entity",
"climate_entity2_id": "2. základné identifikačné číslo klimatickej entity", "climate_entity2_id": "2. základné identifikačné číslo klimatickej entity",
@@ -79,6 +111,7 @@
"auto_regulation_mode": "Automatické nastavenie cieľovej teploty", "auto_regulation_mode": "Automatické nastavenie cieľovej teploty",
"auto_regulation_dtemp": "Hranica v °, pod ktorou sa zmena teploty neodošle", "auto_regulation_dtemp": "Hranica v °, pod ktorou sa zmena teploty neodošle",
"auto_regulation_periode_min": "Trvanie v minútach medzi dvoma aktualizáciami predpisov", "auto_regulation_periode_min": "Trvanie v minútach medzi dvoma aktualizáciami predpisov",
"auto_regulation_use_device_temp": "Na urýchlenie samoregulácie použite prípadný vnútorný snímač teploty podkladu",
"inverse_switch_command": "V prípade spínača s pilotným vodičom a diódou možno budete musieť príkaz invertovať", "inverse_switch_command": "V prípade spínača s pilotným vodičom a diódou možno budete musieť príkaz invertovať",
"auto_fan_mode": "Automaticky aktivujte ventilátor, keď je potrebné veľké vykurovanie/chladenie" "auto_fan_mode": "Automaticky aktivujte ventilátor, keď je potrebné veľké vykurovanie/chladenie"
} }
@@ -101,24 +134,7 @@
"title": "Predvoľby", "title": "Predvoľby",
"description": "Pre každú predvoľbu zadajte cieľovú teplotu (0, ak chcete predvoľbu ignorovať)", "description": "Pre každú predvoľbu zadajte cieľovú teplotu (0, ak chcete predvoľbu ignorovať)",
"data": { "data": {
"eco_temp": "Teplota v predvoľbe Eco",
"comfort_temp": "Prednastavená teplota v komfortnom režime",
"boost_temp": "Teplota v prednastavení Boost",
"frost_temp": "Teplota v prednastavení Frost protection",
"eco_ac_temp": "Teplota v režime Eco prednastavená pre režim AC",
"comfort_ac_temp": "Teplota v režime Comfort je prednastavená pre režim AC",
"boost_ac_temp": "Prednastavená teplota v režime Boost pre režim AC",
"use_presets_central_config": "Použite konfiguráciu centrálnych predvolieb" "use_presets_central_config": "Použite konfiguráciu centrálnych predvolieb"
},
"data_description": {
"eco_temp": "Teplota v predvoľbe Eco",
"comfort_temp": "Prednastavená teplota v komfortnom režime",
"boost_temp": "Teplota v prednastavení Boost",
"frost_temp": "Teplota v prednastavenej ochrane proti mrazu",
"eco_ac_temp": "Teplota v režime Eco prednastavená pre režim AC",
"comfort_ac_temp": "Teplota v režime Comfort je prednastavená pre režim AC",
"boost_ac_temp": "Prednastavená teplota v režime Boost pre režim AC",
"use_presets_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálnych predvolieb. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu predvolieb pre tento VTherm"
} }
}, },
"window": { "window": {
@@ -130,7 +146,8 @@
"window_auto_open_threshold": "Prah poklesu teploty pre automatickú detekciu otvoreného okna (v °/hodina)", "window_auto_open_threshold": "Prah poklesu teploty pre automatickú detekciu otvoreného okna (v °/hodina)",
"window_auto_close_threshold": "Prahová hodnota zvýšenia teploty pre koniec automatickej detekcie (v °/hodina)", "window_auto_close_threshold": "Prahová hodnota zvýšenia teploty pre koniec automatickej detekcie (v °/hodina)",
"window_auto_max_duration": "Maximálne trvanie automatickej detekcie otvoreného okna (v min)", "window_auto_max_duration": "Maximálne trvanie automatickej detekcie otvoreného okna (v min)",
"use_window_central_config": "Použite centrálnu konfiguráciu okna" "use_window_central_config": "Použite centrálnu konfiguráciu okna",
"window_action": "Akcia"
}, },
"data_description": { "data_description": {
"window_sensor_entity_id": "Nechajte prázdne, ak nemáte použiť žiadny okenný senzor", "window_sensor_entity_id": "Nechajte prázdne, ak nemáte použiť žiadny okenný senzor",
@@ -138,7 +155,8 @@
"window_auto_open_threshold": "Odporúčaná hodnota: medzi 3 a 10. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne", "window_auto_open_threshold": "Odporúčaná hodnota: medzi 3 a 10. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne",
"window_auto_close_threshold": "Odporúčaná hodnota: 0. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne", "window_auto_close_threshold": "Odporúčaná hodnota: 0. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne",
"window_auto_max_duration": "Odporúčaná hodnota: 60 (jedna hodina). Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne", "window_auto_max_duration": "Odporúčaná hodnota: 60 (jedna hodina). Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne",
"use_window_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálneho okna. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu okna pre tento VTherm" "use_window_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálneho okna. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu okna pre tento VTherm",
"window_action": "Akcia, ktorá sa má vykonať, ak sa okno zistí ako otvorené"
} }
}, },
"motion": { "motion": {
@@ -181,26 +199,11 @@
"title": "Riadenie prítomnosti", "title": "Riadenie prítomnosti",
"description": "Atribúty správy prítomnosti.\nPoskytuje senzor prítomnosti vášho domova (pravda, ak je niekto prítomný).\nPotom zadajte buď predvoľbu, ktorá sa má použiť, keď je senzor prítomnosti nepravdivý, alebo posun teploty, ktorý sa má použiť.\nAk je zadaná predvoľba, posun sa nepoužije.\nAk sa nepoužije, ponechajte zodpovedajúce entity_id prázdne.", "description": "Atribúty správy prítomnosti.\nPoskytuje senzor prítomnosti vášho domova (pravda, ak je niekto prítomný).\nPotom zadajte buď predvoľbu, ktorá sa má použiť, keď je senzor prítomnosti nepravdivý, alebo posun teploty, ktorý sa má použiť.\nAk je zadaná predvoľba, posun sa nepoužije.\nAk sa nepoužije, ponechajte zodpovedajúce entity_id prázdne.",
"data": { "data": {
"presence_sensor_entity_id": "ID entity senzora prítomnosti", "presence_sensor_entity_id": "Senzora prítomnosti",
"eco_away_temp": "Teplota v prednastavenej Eco, keď nie je žiadna prítomnosť", "use_presence_central_config": "Použite konfiguráciu centrálnej prítomnosti teploty. Ak chcete použiť špecifické teplotné entity, zrušte výber"
"comfort_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný",
"boost_away_temp": "Prednastavená teplota v režime Boost, keď nie je prítomný",
"frost_away_temp": "Prednastavená teplota v režime Frost protection, keď nie je prítomný",
"eco_ac_away_temp": "Teplota v prednastavenej Eco, keď nie je prítomná v režime AC",
"comfort_ac_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný v režime AC",
"boost_ac_away_temp": "Teplota v prednastavenom Boost, keď nie je prítomný v režime AC",
"use_presence_central_config": "Použite centrálnu konfiguráciu prítomnosti"
}, },
"data_description": { "data_description": {
"presence_sensor_entity_id": "ID entity senzora prítomnosti", "presence_sensor_entity_id": "ID entity senzora prítomnosti"
"eco_away_temp": "Teplota v prednastavenej Eco, keď nie je žiadna prítomnosť",
"comfort_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný",
"boost_away_temp": "Prednastavená teplota v režime Boost, keď nie je prítomný",
"frost_away_temp": "Teplota v Prednastavená ochrana pred mrazom, keď nie je prítomný",
"eco_ac_away_temp": "Teplota v prednastavenej Eco, keď nie je prítomná v režime AC",
"comfort_ac_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný v režime AC",
"boost_ac_away_temp": "Teplota v prednastavenom Boost, keď nie je prítomný v režime AC",
"use_presence_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálnej prítomnosti. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu prítomnosti pre tento VTherm"
} }
}, },
"advanced": { "advanced": {
@@ -244,6 +247,25 @@
"thermostat_type": "Je možný len jeden centrálny typ konfigurácie" "thermostat_type": "Je možný len jeden centrálny typ konfigurácie"
} }
}, },
"menu": {
"title": "Menu",
"description": "Nakonfigurujte si termostat. Po zadaní všetkých požadovaných parametrov budete môcť dokončiť konfiguráciu.",
"menu_options": {
"main": "Hlavné atribúty",
"central_boiler": "Centrálny kotol",
"type": "Podklady",
"tpi": "TPI parametre",
"features": "Vlastnosti",
"presets": "Predvoľby",
"window": "Detekcia okien",
"motion": "Detekcia pohybu",
"power": "Správa napájania",
"presence": "Detekcia prítomnosti",
"advanced": "Pokročilé parametre",
"finalize": "Všetko hotové",
"configuration_not_complete": "Konfigurácia nie je dokončená"
}
},
"main": { "main": {
"title": "Hlavný - {name}", "title": "Hlavný - {name}",
"description": "Hlavné povinné atribúty", "description": "Hlavné povinné atribúty",
@@ -251,22 +273,32 @@
"name": "Názov", "name": "Názov",
"thermostat_type": "Termostat typ", "thermostat_type": "Termostat typ",
"temperature_sensor_entity_id": "ID entity snímača teploty", "temperature_sensor_entity_id": "ID entity snímača teploty",
"last_seen_temperature_sensor_entity_id": "Dátum posledného zobrazenia izbovej teploty",
"external_temperature_sensor_entity_id": "ID entity externého snímača teploty", "external_temperature_sensor_entity_id": "ID entity externého snímača teploty",
"cycle_min": "Trvanie cyklu (minúty)", "cycle_min": "Trvanie cyklu (minúty)",
"temp_min": "Minimálna povolená teplota", "temp_min": "Minimálna povolená teplota",
"temp_max": "Maximálna povolená teplota", "temp_max": "Maximálna povolená teplota",
"step_temperature": "Krok teploty",
"device_power": "Výkon zariadenia (kW)", "device_power": "Výkon zariadenia (kW)",
"use_central_mode": "Povoliť ovládanie centrálnou entitou (potrebná centrálna konfigurácia)", "use_central_mode": "Povoliť ovládanie centrálnou entitou (vyžaduje centrálnu konfiguráciu). Zaškrtnutím povolíte ovládanie VTherm pomocou vybraných entít central_mode.",
"use_main_central_config": "Použite dodatočnú centrálnu hlavnú konfiguráciu. Začiarknite, ak chcete použiť centrálnu hlavnú konfiguráciu (vonkajšia teplota, min, max, krok, ...).",
"used_by_controls_central_boiler": "Používa sa centrálnym kotlom. Skontrolujte, či má mať tento VTherm ovládanie na centrálnom kotli"
},
"data_description": {
"temperature_sensor_entity_id": "ID entity snímača izbovej teploty",
"last_seen_temperature_sensor_entity_id": "Naposledy videný snímač izbovej teploty ID entity. Mal by to byť snímač dátumu a času",
"external_temperature_sensor_entity_id": "ID entity snímača vonkajšej teploty. Nepoužíva sa, ak je zvolená centrálna konfigurácia"
}
},
"features": {
"title": "Vlastnosti - {name}",
"description": "Vlastnosti termostatu",
"data": {
"use_window_feature": "Použite detekciu okien", "use_window_feature": "Použite detekciu okien",
"use_motion_feature": "Použite detekciu pohybu", "use_motion_feature": "Použite detekciu pohybu",
"use_power_feature": "Použite správu napájania", "use_power_feature": "Použite správu napájania",
"use_presence_feature": "Použite detekciu prítomnosti", "use_presence_feature": "Použite detekciu prítomnosti",
"use_main_central_config": "Použite centrálnu hlavnú konfiguráciu" "use_central_boiler_feature": "Použite centrálny kotol. Začiarknutím tohto políčka pridáte ovládanie do centrálneho kotla. Po zaškrtnutí tohto políčka budete musieť nakonfigurovať VTherm, ktorý bude mať ovládanie centrálneho kotla, aby sa prejavilo. Ak jeden VTherm vyžaduje ohrev, kotol sa zapne. Ak žiadny VTherm nevyžaduje ohrev, kotol sa vypne. Príkazy na zapnutie/vypnutie centrálneho kotla sú uvedené na príslušnej konfiguračnej stránke"
},
"data_description": {
"use_central_mode": "Zaškrtnutím povolíte ovládanie VTherm pomocou vybraných entít central_mode",
"use_main_central_config": "Začiarknite, ak chcete použiť centrálnu hlavnú konfiguráciu. Ak chcete použiť špecifickú konfiguráciu pre tento VTherm, zrušte začiarknutie",
"external_temperature_sensor_entity_id": "ID entity snímača vonkajšej teploty. Nepoužíva sa, ak je zvolená centrálna konfigurácia"
} }
}, },
"type": { "type": {
@@ -277,6 +309,7 @@
"heater_entity2_id": "2. spínač ohrievača", "heater_entity2_id": "2. spínač ohrievača",
"heater_entity3_id": "3. spínač ohrievača", "heater_entity3_id": "3. spínač ohrievača",
"heater_entity4_id": "4. spínač ohrievača", "heater_entity4_id": "4. spínač ohrievača",
"heater_keep_alive": "Prepnite interval udržiavania v sekundách",
"proportional_function": "Algoritmus", "proportional_function": "Algoritmus",
"climate_entity_id": "Základná klíma", "climate_entity_id": "Základná klíma",
"climate_entity2_id": "2. základná klíma", "climate_entity2_id": "2. základná klíma",
@@ -290,6 +323,7 @@
"auto_regulation_mode": "Samoregulácia", "auto_regulation_mode": "Samoregulácia",
"auto_regulation_dtemp": "Regulačný prah", "auto_regulation_dtemp": "Regulačný prah",
"auto_regulation_periode_min": "Regulačné minimálne obdobie", "auto_regulation_periode_min": "Regulačné minimálne obdobie",
"auto_regulation_use_device_temp": "Použite vnútornú teplotu podkladu",
"inverse_switch_command": "Inverzný prepínací príkaz", "inverse_switch_command": "Inverzný prepínací príkaz",
"auto_fan_mode": "Režim automatického ventilátora" "auto_fan_mode": "Režim automatického ventilátora"
}, },
@@ -298,6 +332,7 @@
"heater_entity2_id": "Voliteľné ID entity 2. ohrievača. Ak sa nepoužíva, nechajte prázdne", "heater_entity2_id": "Voliteľné ID entity 2. ohrievača. Ak sa nepoužíva, nechajte prázdne",
"heater_entity3_id": "Voliteľné ID entity 3. ohrievača. Ak sa nepoužíva, nechajte prázdne", "heater_entity3_id": "Voliteľné ID entity 3. ohrievača. Ak sa nepoužíva, nechajte prázdne",
"heater_entity4_id": "Voliteľné ID entity 4. ohrievača. Ak sa nepoužíva, nechajte prázdne", "heater_entity4_id": "Voliteľné ID entity 4. ohrievača. Ak sa nepoužíva, nechajte prázdne",
"heater_keep_alive": "Voliteľný interval obnovy stavu spínača ohrievača. Ak to nie je potrebné, nechajte prázdne.",
"proportional_function": "Algoritmus, ktorý sa má použiť (TPI je zatiaľ jediný)", "proportional_function": "Algoritmus, ktorý sa má použiť (TPI je zatiaľ jediný)",
"climate_entity_id": "ID základnej klimatickej entity", "climate_entity_id": "ID základnej klimatickej entity",
"climate_entity2_id": "2. základný identifikátor klimatickej entity", "climate_entity2_id": "2. základný identifikátor klimatickej entity",
@@ -311,6 +346,7 @@
"auto_regulation_mode": "Automatické nastavenie cieľovej teploty", "auto_regulation_mode": "Automatické nastavenie cieľovej teploty",
"auto_regulation_dtemp": "Hranica v °, pod ktorou sa zmena teploty neodošle", "auto_regulation_dtemp": "Hranica v °, pod ktorou sa zmena teploty neodošle",
"auto_regulation_periode_min": "Trvanie v minútach medzi dvoma aktualizáciami predpisov", "auto_regulation_periode_min": "Trvanie v minútach medzi dvoma aktualizáciami predpisov",
"auto_regulation_use_device_temp": "Na urýchlenie samoregulácie použite prípadný vnútorný snímač teploty podkladu",
"inverse_switch_command": "V prípade spínača s pilotným vodičom a diódou možno budete musieť príkaz invertovať", "inverse_switch_command": "V prípade spínača s pilotným vodičom a diódou možno budete musieť príkaz invertovať",
"auto_fan_mode": "Automaticky aktivujte ventilátor, keď je potrebné veľké vykurovanie/chladenie" "auto_fan_mode": "Automaticky aktivujte ventilátor, keď je potrebné veľké vykurovanie/chladenie"
} }
@@ -333,24 +369,7 @@
"title": "Predvoľby - {name}", "title": "Predvoľby - {name}",
"description": "Pre každú predvoľbu zadajte cieľovú teplotu (0, ak chcete predvoľbu ignorovať)", "description": "Pre každú predvoľbu zadajte cieľovú teplotu (0, ak chcete predvoľbu ignorovať)",
"data": { "data": {
"eco_temp": "Teplota v predvoľbe Eco",
"comfort_temp": "Prednastavená teplota v komfortnom režime",
"boost_temp": "Teplota v prednastavení Boost",
"frost_temp": "Teplota v prednastavení Frost protection",
"eco_ac_temp": "Teplota v režime Eco prednastavená pre režim AC",
"comfort_ac_temp": "Teplota v režime Comfort je prednastavená pre režim AC",
"boost_ac_temp": "Prednastavená teplota v režime Boost pre režim AC",
"use_presets_central_config": "Použite konfiguráciu centrálnych predvolieb" "use_presets_central_config": "Použite konfiguráciu centrálnych predvolieb"
},
"data_description": {
"eco_temp": "Teplota v predvoľbe Eco",
"comfort_temp": "Prednastavená teplota v komfortnom režime",
"boost_temp": "Teplota v prednastavení Boost",
"frost_temp": "Teplota v prednastavenej ochrane proti mrazu",
"eco_ac_temp": "Teplota v režime Eco prednastavená pre režim AC",
"comfort_ac_temp": "Teplota v režime Comfort je prednastavená pre režim AC",
"boost_ac_temp": "Prednastavená teplota v režime Boost pre režim AC",
"use_presets_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálnych predvolieb. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu predvolieb pre tento VTherm"
} }
}, },
"window": { "window": {
@@ -362,7 +381,8 @@
"window_auto_open_threshold": "Prah poklesu teploty pre automatickú detekciu otvoreného okna (v °/hodina)", "window_auto_open_threshold": "Prah poklesu teploty pre automatickú detekciu otvoreného okna (v °/hodina)",
"window_auto_close_threshold": "Prahová hodnota zvýšenia teploty pre koniec automatickej detekcie (v °/hodina)", "window_auto_close_threshold": "Prahová hodnota zvýšenia teploty pre koniec automatickej detekcie (v °/hodina)",
"window_auto_max_duration": "Maximálne trvanie automatickej detekcie otvoreného okna (v min)", "window_auto_max_duration": "Maximálne trvanie automatickej detekcie otvoreného okna (v min)",
"use_window_central_config": "Použite centrálnu konfiguráciu okna" "use_window_central_config": "Použite centrálnu konfiguráciu okna",
"window_action": "Akcia"
}, },
"data_description": { "data_description": {
"window_sensor_entity_id": "Nechajte prázdne, ak nemáte použiť žiadny okenný senzor", "window_sensor_entity_id": "Nechajte prázdne, ak nemáte použiť žiadny okenný senzor",
@@ -370,7 +390,8 @@
"window_auto_open_threshold": "Odporúčaná hodnota: medzi 3 a 10. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne", "window_auto_open_threshold": "Odporúčaná hodnota: medzi 3 a 10. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne",
"window_auto_close_threshold": "Odporúčaná hodnota: 0. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne", "window_auto_close_threshold": "Odporúčaná hodnota: 0. Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne",
"window_auto_max_duration": "Odporúčaná hodnota: 60 (jedna hodina). Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne", "window_auto_max_duration": "Odporúčaná hodnota: 60 (jedna hodina). Ak sa nepoužíva automatická detekcia otvoreného okna, nechajte prázdne",
"use_window_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálneho okna. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu okna pre tento VTherm" "use_window_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálneho okna. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu okna pre tento VTherm",
"window_action": "Akcia, ktorá sa má vykonať, ak sa okno zistí ako otvorené"
} }
}, },
"motion": { "motion": {
@@ -410,29 +431,14 @@
} }
}, },
"presence": { "presence": {
"title": "Riadenie prítomnosti", "title": "Prítommnosť - {name}",
"description": "Atribúty správy prítomnosti.\nPoskytuje senzor prítomnosti vášho domova (pravda, ak je niekto prítomný).\nPotom zadajte buď predvoľbu, ktorá sa má použiť, keď je senzor prítomnosti nepravdivý, alebo posun teploty, ktorý sa má použiť.\nAk je zadaná predvoľba, posun sa nepoužije.\nAk sa nepoužije, ponechajte zodpovedajúce entity_id prázdne.", "description": "Atribúty riadenia prítomnosti.\nPoskytuje senzor prítomnosti vášho domova (pravda, je niekto prítomný) a poskytuje zodpovedajúce prednastavené nastavenie teploty.",
"data": { "data": {
"presence_sensor_entity_id": "ID entity senzora prítomnosti (pravda je prítomná)", "presence_sensor_entity_id": "Senzor prítomnosti",
"eco_away_temp": "Teplota v prednastavenej Eco, keď nie je žiadna prítomnosť", "use_presence_central_config": "Použite konfiguráciu centrálnej prítomnosti teploty. Ak chcete použiť špecifické entity teploty, zrušte začiarknutie"
"comfort_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný",
"boost_away_temp": "Prednastavená teplota v režime Boost, keď nie je prítomný",
"frost_away_temp": "Prednastavená teplota v režime Frost protection, keď nie je prítomný",
"eco_ac_away_temp": "Teplota v prednastavenej Eco, keď nie je prítomná v režime AC",
"comfort_ac_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný v režime AC",
"boost_ac_away_temp": "Teplota v prednastavenom Boost, keď nie je prítomný v režime AC",
"use_presence_central_config": "Použite centrálnu konfiguráciu prítomnosti"
}, },
"data_description": { "data_description": {
"presence_sensor_entity_id": "ID entity senzora prítomnosti", "presence_sensor_entity_id": "ID entity senzora prítomnosti"
"eco_away_temp": "Teplota v prednastavenej Eco, keď nie je žiadna prítomnosť",
"comfort_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný",
"boost_away_temp": "Prednastavená teplota v režime Boost, keď nie je prítomný",
"frost_away_temp": "Teplota v Prednastavená ochrana pred mrazom, keď nie je prítomný",
"eco_ac_away_temp": "Teplota v prednastavenej Eco, keď nie je prítomná v režime AC",
"comfort_ac_away_temp": "Teplota v režime Comfort je prednastavená, keď nie je prítomný v režime AC",
"boost_ac_away_temp": "Teplota v prednastavenom Boost, keď nie je prítomný v režime AC",
"use_presence_central_config": "Začiarknite, ak chcete použiť konfiguráciu centrálnej prítomnosti. Zrušte začiarknutie, ak chcete použiť špecifickú konfiguráciu prítomnosti pre tento VTherm"
} }
}, },
"advanced": { "advanced": {
@@ -458,7 +464,8 @@
"unknown": "Neočakávaná chyba", "unknown": "Neočakávaná chyba",
"unknown_entity": "Neznáme ID entity", "unknown_entity": "Neznáme ID entity",
"window_open_detection_method": "Mala by sa použiť iba jedna metóda detekcie otvoreného okna. Použite senzor alebo automatickú detekciu cez teplotný prah, ale nie oboje", "window_open_detection_method": "Mala by sa použiť iba jedna metóda detekcie otvoreného okna. Použite senzor alebo automatickú detekciu cez teplotný prah, ale nie oboje",
"no_central_config": "Nemôžete zaškrtnúť „použiť centrálnu konfiguráciu“, pretože sa nenašla žiadna centrálna konfigurácia. Aby ste ho mohli používať, musíte si vytvoriť všestranný termostat typu „Central Configuration“." "no_central_config": "Nemôžete zaškrtnúť „použiť centrálnu konfiguráciu“, pretože sa nenašla žiadna centrálna konfigurácia. Aby ste ho mohli používať, musíte si vytvoriť všestranný termostat typu „Central Configuration“.",
"service_configuration_format": "Formát konfigurácie služby je nesprávny"
}, },
"abort": { "abort": {
"already_configured": "Zariadenie je už nakonfigurované" "already_configured": "Zariadenie je už nakonfigurované"
@@ -491,6 +498,22 @@
"auto_fan_high": "Vysoký", "auto_fan_high": "Vysoký",
"auto_fan_turbo": "Turbo" "auto_fan_turbo": "Turbo"
} }
},
"window_action": {
"options": {
"window_turn_off": "Vypnúť",
"window_fan_only": "Len ventilátor",
"window_frost_temp": "Ochrana pred mrazom",
"window_eco_temp": "Eco"
}
},
"presets": {
"options": {
"frost": "Ochrana proti mrazu",
"eco": "Eco",
"comfort": "Komfort",
"boost": "Boost"
}
} }
}, },
"entity": { "entity": {
@@ -506,6 +529,53 @@
} }
} }
} }
},
"number": {
"frost_temp": {
"name": "Mráz"
},
"eco_temp": {
"name": "Eco"
},
"comfort_temp": {
"name": "Komfort"
},
"boost_temp": {
"name": "Boost"
},
"frost_ac_temp": {
"name": "Mráz ac"
},
"eco_ac_temp": {
"name": "Eco ac"
},
"comfort_ac_temp": {
"name": "Komfort ac"
},
"boost_ac_temp": {
"name": "Boost ac"
},
"frost_away_temp": {
"name": "Mráz mimo"
},
"eco_away_temp": {
"name": "Eko mimo"
},
"comfort_away_temp": {
"name": "Komfort mimo"
},
"boost_away_temp": {
"name": "Boost mimo"
},
"eco_ac_away_temp": {
"name": "Eco ac mimo"
},
"comfort_ac_away_temp": {
"name": "Komfort ac mimo"
},
"boost_ac_away_temp": {
"name": "Boost ac mimo"
}
} }
} }
} }

View File

@@ -1,11 +1,11 @@
# pylint: disable=unused-argument, line-too-long # pylint: disable=unused-argument, line-too-long, too-many-lines
""" Underlying entities classes """ """ Underlying entities classes """
import logging import logging
from typing import Any from typing import Any
from enum import StrEnum from enum import StrEnum
from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, UnitOfTemperature from homeassistant.const import ATTR_ENTITY_ID, STATE_ON, STATE_UNAVAILABLE
from homeassistant.core import State from homeassistant.core import State
from homeassistant.exceptions import ServiceNotFound from homeassistant.exceptions import ServiceNotFound
@@ -30,8 +30,9 @@ from homeassistant.components.number import SERVICE_SET_VALUE
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import async_call_later from homeassistant.helpers.event import async_call_later
from homeassistant.util.unit_conversion import TemperatureConverter
from .const import UnknownEntity, overrides from .const import UnknownEntity, overrides, get_safe_float
from .keep_alive import IntervalCaller from .keep_alive import IntervalCaller
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -52,6 +53,9 @@ class UnderlyingEntityType(StrEnum):
# a valve # a valve
VALVE = "valve" VALVE = "valve"
# a direct valve regulation
VALVE_REGULATION = "valve_regulation"
class UnderlyingEntity: class UnderlyingEntity:
"""Represent a underlying device which could be a switch or a climate""" """Represent a underlying device which could be a switch or a climate"""
@@ -61,6 +65,7 @@ class UnderlyingEntity:
_thermostat: Any _thermostat: Any
_entity_id: str _entity_id: str
_type: UnderlyingEntityType _type: UnderlyingEntityType
_hvac_mode: HVACMode | None
def __init__( def __init__(
self, self,
@@ -74,6 +79,7 @@ class UnderlyingEntity:
self._thermostat = thermostat self._thermostat = thermostat
self._type = entity_type self._type = entity_type
self._entity_id = entity_id self._entity_id = entity_id
self._hvac_mode = None
def __str__(self): def __str__(self):
return str(self._thermostat) + "-" + self._entity_id return str(self._thermostat) + "-" + self._entity_id
@@ -99,13 +105,24 @@ class UnderlyingEntity:
async def set_hvac_mode(self, hvac_mode: HVACMode): async def set_hvac_mode(self, hvac_mode: HVACMode):
"""Set the HVACmode""" """Set the HVACmode"""
self._hvac_mode = hvac_mode
return return
@property
def hvac_mode(self) -> HVACMode | None:
"""Return the current hvac_mode"""
return self._hvac_mode
@property @property
def is_device_active(self) -> bool | None: def is_device_active(self) -> bool | None:
"""If the toggleable device is currently active.""" """If the toggleable device is currently active."""
return None return None
@property
def hvac_action(self) -> HVACAction:
"""Calculate a hvac_action"""
return HVACAction.HEATING if self.is_device_active is True else HVACAction.OFF
async def set_temperature(self, temperature, max_temp, min_temp): async def set_temperature(self, temperature, max_temp, min_temp):
"""Set the target temperature""" """Set the target temperature"""
return return
@@ -180,7 +197,6 @@ class UnderlyingSwitch(UnderlyingEntity):
_initialDelaySec: int _initialDelaySec: int
_on_time_sec: int _on_time_sec: int
_off_time_sec: int _off_time_sec: int
_hvac_mode: HVACMode
def __init__( def __init__(
self, self,
@@ -188,7 +204,7 @@ class UnderlyingSwitch(UnderlyingEntity):
thermostat: Any, thermostat: Any,
switch_entity_id: str, switch_entity_id: str,
initial_delay_sec: int, initial_delay_sec: int,
keep_alive_sec: int, keep_alive_sec: float,
) -> None: ) -> None:
"""Initialize the underlying switch""" """Initialize the underlying switch"""
@@ -203,7 +219,6 @@ class UnderlyingSwitch(UnderlyingEntity):
self._should_relaunch_control_heating = False self._should_relaunch_control_heating = False
self._on_time_sec = 0 self._on_time_sec = 0
self._off_time_sec = 0 self._off_time_sec = 0
self._hvac_mode = None
self._keep_alive = IntervalCaller(hass, keep_alive_sec) self._keep_alive = IntervalCaller(hass, keep_alive_sec)
@property @property
@@ -217,6 +232,11 @@ class UnderlyingSwitch(UnderlyingEntity):
"""Tells if the switch command should be inversed""" """Tells if the switch command should be inversed"""
return self._thermostat.is_inversed return self._thermostat.is_inversed
@property
def keep_alive_sec(self) -> float:
"""Return the switch keep-alive interval in seconds."""
return self._keep_alive.interval_sec
@overrides @overrides
def startup(self): def startup(self):
super().startup() super().startup()
@@ -231,8 +251,8 @@ class UnderlyingSwitch(UnderlyingEntity):
await self.turn_off() await self.turn_off()
self._cancel_cycle() self._cancel_cycle()
if self._hvac_mode != hvac_mode: if self.hvac_mode != hvac_mode:
self._hvac_mode = hvac_mode super().set_hvac_mode(hvac_mode)
return True return True
else: else:
return False return False
@@ -247,7 +267,28 @@ class UnderlyingSwitch(UnderlyingEntity):
async def _keep_alive_callback(self): async def _keep_alive_callback(self):
"""Keep alive: Turn on if already turned on, turn off if already turned off.""" """Keep alive: Turn on if already turned on, turn off if already turned off."""
await (self.turn_on() if self.is_device_active else self.turn_off()) timer = self._keep_alive.backoff_timer
state: State | None = self._hass.states.get(self._entity_id)
# Normal, expected state.state values are "on" and "off". An absent
# underlying MQTT switch was observed to produce either state == None
# or state.state == STATE_UNAVAILABLE ("unavailable").
if state is None or state.state == STATE_UNAVAILABLE:
if timer.is_ready():
_LOGGER.warning(
"Entity %s is not available (state: %s). Will keep trying "
"keep alive calls, but won't log this condition every time.",
self._entity_id,
state.state if state else "None",
)
else:
if timer.in_progress:
timer.reset()
_LOGGER.warning(
"Entity %s has recovered (state: %s).",
self._entity_id,
state.state,
)
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 # @overrides this breaks some unit tests TypeError: object MagicMock can't be used in 'await' expression
async def turn_off(self): async def turn_off(self):
@@ -461,6 +502,7 @@ class UnderlyingClimate(UnderlyingEntity):
entity_id=climate_entity_id, entity_id=climate_entity_id,
) )
self._underlying_climate = None self._underlying_climate = None
self._last_sent_temperature = None
def find_underlying_climate(self) -> ClimateEntity: def find_underlying_climate(self) -> ClimateEntity:
"""Find the underlying climate entity""" """Find the underlying climate entity"""
@@ -481,8 +523,8 @@ class UnderlyingClimate(UnderlyingEntity):
self._underlying_climate, self._underlying_climate,
) )
else: else:
_LOGGER.error( _LOGGER.info(
"%s - Cannot find the underlying climate entity: %s. Thermostat will not be operational", "%s - Cannot find the underlying climate entity: %s. Thermostat will not be operational. Will try later.",
self, self,
self.entity_id, self.entity_id,
) )
@@ -522,14 +564,11 @@ class UnderlyingClimate(UnderlyingEntity):
def is_device_active(self): def is_device_active(self):
"""If the toggleable device is currently active.""" """If the toggleable device is currently active."""
if self.is_initialized: if self.is_initialized:
return ( return self.hvac_mode != HVACMode.OFF and self.hvac_action not in [
self._underlying_climate.hvac_mode != HVACMode.OFF HVACAction.IDLE,
and self._underlying_climate.hvac_action HVACAction.OFF,
not in [ None,
HVACAction.IDLE, ]
HVACAction.OFF,
]
)
else: else:
return None return None
@@ -585,12 +624,24 @@ class UnderlyingClimate(UnderlyingEntity):
if not self.is_initialized: if not self.is_initialized:
return return
data = { # Issue 508 we have to take care of service set_temperature or set_range
ATTR_ENTITY_ID: self._entity_id, target_temp = self.cap_sent_value(temperature)
"temperature": self.cap_sent_value(temperature), if (
"target_temp_high": max_temp, ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
"target_temp_low": min_temp, 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,
}
await self._hass.services.async_call( await self._hass.services.async_call(
CLIMATE_DOMAIN, CLIMATE_DOMAIN,
@@ -598,12 +649,48 @@ class UnderlyingClimate(UnderlyingEntity):
data, data,
) )
self._last_sent_temperature = target_temp
@property
def last_sent_temperature(self) -> float | None:
"""Get the last send temperature. None if no temperature have been sent yet"""
return self._last_sent_temperature
@property @property
def hvac_action(self) -> HVACAction | None: def hvac_action(self) -> HVACAction | None:
"""Get the hvac action of the underlying""" """Get the hvac action of the underlying"""
if not self.is_initialized: if not self.is_initialized:
return None return None
return self._underlying_climate.hvac_action
hvac_action = self._underlying_climate.hvac_action
if hvac_action is None:
target = (
self.underlying_target_temperature
or self._thermostat.target_temperature
)
current = (
self.underlying_current_temperature
or self._thermostat.current_temperature
)
hvac_mode = self.hvac_mode
_LOGGER.debug(
"%s - hvac_action simulation target=%s, current=%s, hvac_mode=%s",
self,
target,
current,
hvac_mode,
)
hvac_action = HVACAction.IDLE
if target is not None and current is not None:
dtemp = target - current
if hvac_mode == HVACMode.COOL and dtemp < 0:
hvac_action = HVACAction.COOLING
elif hvac_mode in [HVACMode.HEAT, HVACMode.HEAT_COOL] and dtemp > 0:
hvac_action = HVACAction.HEATING
return hvac_action
@property @property
def hvac_mode(self) -> HVACMode | None: def hvac_mode(self) -> HVACMode | None:
@@ -640,6 +727,13 @@ class UnderlyingClimate(UnderlyingEntity):
return [] return []
return self._underlying_climate.hvac_modes return self._underlying_climate.hvac_modes
@property
def current_humidity(self) -> float | None:
"""Get the humidity"""
if not self.is_initialized:
return None
return self._underlying_climate.current_humidity
@property @property
def fan_modes(self) -> list[str]: def fan_modes(self) -> list[str]:
"""Get the fan_modes""" """Get the fan_modes"""
@@ -658,7 +752,7 @@ class UnderlyingClimate(UnderlyingEntity):
def temperature_unit(self) -> str: def temperature_unit(self) -> str:
"""Get the temperature_unit""" """Get the temperature_unit"""
if not self.is_initialized: if not self.is_initialized:
return UnitOfTemperature.CELSIUS return self._hass.config.units.temperature_unit
return self._underlying_climate.temperature_unit return self._underlying_climate.temperature_unit
@property @property
@@ -683,11 +777,19 @@ class UnderlyingClimate(UnderlyingEntity):
return self._underlying_climate.target_temperature_low return self._underlying_climate.target_temperature_low
@property @property
def is_aux_heat(self) -> bool: def underlying_target_temperature(self) -> float:
"""Get the is_aux_heat""" """Get the target_temperature"""
if not self.is_initialized: if not self.is_initialized:
return False return None
return self._underlying_climate.is_aux_heat
if not hasattr(self._underlying_climate, "target_temperature"):
return None
else:
return self._underlying_climate.target_temperature
# return self._hass.states.get(self._entity_id).attributes.get(
# "target_temperature"
# )
@property @property
def underlying_current_temperature(self) -> float | None: def underlying_current_temperature(self) -> float | None:
@@ -698,8 +800,17 @@ class UnderlyingClimate(UnderlyingEntity):
if not hasattr(self._underlying_climate, "current_temperature"): if not hasattr(self._underlying_climate, "current_temperature"):
return None return None
else:
return self._underlying_climate.current_temperature
return self._underlying_climate.current_temperature # return self._hass.states.get(self._entity_id).attributes.get("current_temperature")
@property
def is_aux_heat(self) -> bool:
"""Get the is_aux_heat"""
if not self.is_initialized:
return False
return self._underlying_climate.is_aux_heat
def turn_aux_heat_on(self) -> None: def turn_aux_heat_on(self) -> None:
"""Turn auxiliary heater on.""" """Turn auxiliary heater on."""
@@ -726,8 +837,12 @@ class UnderlyingClimate(UnderlyingEntity):
self._underlying_climate.min_temp is not None self._underlying_climate.min_temp is not None
and self._underlying_climate is not None and self._underlying_climate is not None
): ):
min_val = self._underlying_climate.min_temp min_val = TemperatureConverter.convert(
max_val = self._underlying_climate.max_temp self._underlying_climate.min_temp, self._underlying_climate.temperature_unit, self._hass.config.units.temperature_unit
)
max_val = TemperatureConverter.convert(
self._underlying_climate.max_temp, self._underlying_climate.temperature_unit, self._hass.config.units.temperature_unit
)
new_value = max(min_val, min(value, max_val)) new_value = max(min_val, min(value, max_val))
else: else:
@@ -753,11 +868,16 @@ class UnderlyingValve(UnderlyingEntity):
_hvac_mode: HVACMode _hvac_mode: HVACMode
# This is the percentage of opening int integer (from 0 to 100) # This is the percentage of opening int integer (from 0 to 100)
_percent_open: int _percent_open: int
_last_sent_temperature = None
def __init__( def __init__(
self, hass: HomeAssistant, thermostat: Any, valve_entity_id: str self,
hass: HomeAssistant,
thermostat: Any,
valve_entity_id: str,
entity_type: UnderlyingEntityType = UnderlyingEntityType.VALVE,
) -> None: ) -> None:
"""Initialize the underlying switch""" """Initialize the underlying valve"""
super().__init__( super().__init__(
hass=hass, hass=hass,
@@ -768,22 +888,30 @@ class UnderlyingValve(UnderlyingEntity):
self._async_cancel_cycle = None self._async_cancel_cycle = None
self._should_relaunch_control_heating = False self._should_relaunch_control_heating = False
self._hvac_mode = None self._hvac_mode = None
self._percent_open = self._thermostat.valve_open_percent self._percent_open = None # self._thermostat.valve_open_percent
self._valve_entity_id = valve_entity_id self._valve_entity_id = valve_entity_id
async def _send_value_to_number(self, number_entity_id: str, value: int):
"""Send a value to a number entity"""
try:
data = {"value": value}
target = {ATTR_ENTITY_ID: number_entity_id}
domain = number_entity_id.split(".")[0]
await self._hass.services.async_call(
domain=domain,
service=SERVICE_SET_VALUE,
service_data=data,
target=target,
)
except ServiceNotFound as err:
_LOGGER.error(err)
# This could happens in unit test if input_number domain is not yet loaded
# raise err
async def send_percent_open(self): async def send_percent_open(self):
"""Send the percent open to the underlying valve""" """Send the percent open to the underlying valve"""
# This may fails if called after shutdown # This may fails if called after shutdown
try: return await self._send_value_to_number(self._entity_id, self._percent_open)
data = {ATTR_ENTITY_ID: self._entity_id, "value": self._percent_open}
domain = self._entity_id.split(".")[0]
await self._hass.services.async_call(
domain,
SERVICE_SET_VALUE,
data,
)
except ServiceNotFound as err:
_LOGGER.error(err)
async def turn_off(self): async def turn_off(self):
"""Turn heater toggleable device off.""" """Turn heater toggleable device off."""
@@ -796,7 +924,7 @@ class UnderlyingValve(UnderlyingEntity):
async def turn_on(self): async def turn_on(self):
"""Nothing to do for Valve because it cannot be turned on""" """Nothing to do for Valve because it cannot be turned on"""
self.set_valve_open_percent() await self.set_valve_open_percent()
async def set_hvac_mode(self, hvac_mode: HVACMode) -> bool: async def set_hvac_mode(self, hvac_mode: HVACMode) -> bool:
"""Set the HVACmode. Returns true if something have change""" """Set the HVACmode. Returns true if something have change"""
@@ -834,9 +962,8 @@ class UnderlyingValve(UnderlyingEntity):
force=False, force=False,
): ):
"""We use this function to change the on_percent""" """We use this function to change the on_percent"""
if force: # if force:
self._percent_open = self.cap_sent_value(self._percent_open) await self.set_valve_open_percent()
await self.send_percent_open()
@overrides @overrides
def cap_sent_value(self, value) -> float: def cap_sent_value(self, value) -> float:
@@ -852,7 +979,7 @@ class UnderlyingValve(UnderlyingEntity):
min_val = valve_state.attributes["min"] min_val = valve_state.attributes["min"]
max_val = valve_state.attributes["max"] max_val = valve_state.attributes["max"]
new_value = round(max(min_val, min(value, max_val))) new_value = round(max(min_val, min(value / 100 * max_val, max_val)))
else: else:
_LOGGER.debug("%s - no min and max attributes on underlying", self) _LOGGER.debug("%s - no min and max attributes on underlying", self)
new_value = value new_value = value
@@ -869,7 +996,7 @@ class UnderlyingValve(UnderlyingEntity):
return new_value return new_value
def set_valve_open_percent(self): async def set_valve_open_percent(self):
"""Update the valve open percent""" """Update the valve open percent"""
caped_val = self.cap_sent_value(self._thermostat.valve_open_percent) caped_val = self.cap_sent_value(self._thermostat.valve_open_percent)
if self._percent_open == caped_val: if self._percent_open == caped_val:
@@ -883,8 +1010,185 @@ class UnderlyingValve(UnderlyingEntity):
"%s - Setting valve ouverture percent to %s", self, self._percent_open "%s - Setting valve ouverture percent to %s", self, self._percent_open
) )
# Send the change to the valve, in background # Send the change to the valve, in background
self._hass.create_task(self.send_percent_open()) # self._hass.create_task(self.send_percent_open())
await self.send_percent_open()
def remove_entity(self): def remove_entity(self):
"""Remove the entity after stopping its cycle""" """Remove the entity after stopping its cycle"""
self._cancel_cycle() self._cancel_cycle()
class UnderlyingValveRegulation(UnderlyingValve):
"""A specific underlying class for Valve regulation"""
_offset_calibration_entity_id: str
_opening_degree_entity_id: str
_closing_degree_entity_id: str
def __init__(
self,
hass: HomeAssistant,
thermostat: Any,
offset_calibration_entity_id: str,
opening_degree_entity_id: str,
closing_degree_entity_id: str,
climate_underlying: UnderlyingClimate,
) -> None:
"""Initialize the underlying TRV with valve regulation"""
super().__init__(
hass,
thermostat,
opening_degree_entity_id,
entity_type=UnderlyingEntityType.VALVE_REGULATION,
)
self._offset_calibration_entity_id = offset_calibration_entity_id
self._opening_degree_entity_id = opening_degree_entity_id
self._closing_degree_entity_id = closing_degree_entity_id
self._climate_underlying = climate_underlying
self._is_min_max_initialized = False
self._max_opening_degree = None
self._min_offset_calibration = None
self._max_offset_calibration = None
async def send_percent_open(self):
"""Send the percent open to the underlying valve"""
if not self._is_min_max_initialized:
_LOGGER.debug(
"%s - initialize min offset_calibration and max open_degree", self
)
self._max_opening_degree = self._hass.states.get(
self._opening_degree_entity_id
).attributes.get("max")
if self.have_offset_calibration_entity:
self._min_offset_calibration = self._hass.states.get(
self._offset_calibration_entity_id
).attributes.get("min")
self._max_offset_calibration = self._hass.states.get(
self._offset_calibration_entity_id
).attributes.get("max")
self._is_min_max_initialized = self._max_opening_degree is not None and (
not self.have_offset_calibration_entity
or (
self._min_offset_calibration is not None
and self._max_offset_calibration is not None
)
)
if not self._is_min_max_initialized:
_LOGGER.warning(
"%s - impossible to initialize max_opening_degree or min_offset_calibration. Abort sending percent open to the valve. This could be a temporary message at startup."
)
return
# Send opening_degree
await super().send_percent_open()
# Send closing_degree if set
closing_degree = None
if self.have_closing_degree_entity:
await self._send_value_to_number(
self._closing_degree_entity_id,
closing_degree := self._max_opening_degree - self._percent_open,
)
# send offset_calibration to the difference between target temp and local temp
offset = None
if self.have_offset_calibration_entity:
if (
(local_temp := self._climate_underlying.underlying_current_temperature)
is not None
and (room_temp := self._thermostat.current_temperature) is not None
and (
current_offset := get_safe_float(
self._hass, self._offset_calibration_entity_id
)
)
is not None
):
offset = min(
self._max_offset_calibration,
max(
self._min_offset_calibration,
room_temp - (local_temp - current_offset),
),
)
await self._send_value_to_number(
self._offset_calibration_entity_id, offset
)
_LOGGER.debug(
"%s - valve regulation - I have sent offset_calibration=%s opening_degree=%s closing_degree=%s",
self,
offset,
self._percent_open,
closing_degree,
)
@property
def offset_calibration_entity_id(self) -> str:
"""The offset_calibration_entity_id"""
return self._offset_calibration_entity_id
@property
def opening_degree_entity_id(self) -> str:
"""The offset_calibration_entity_id"""
return self._opening_degree_entity_id
@property
def closing_degree_entity_id(self) -> str:
"""The offset_calibration_entity_id"""
return self._closing_degree_entity_id
@property
def have_closing_degree_entity(self) -> bool:
"""Return True if the underlying have a closing_degree entity"""
return self._closing_degree_entity_id is not None
@property
def have_offset_calibration_entity(self) -> bool:
"""Return True if the underlying have a offset_calibration entity"""
return self._offset_calibration_entity_id is not None
@property
def hvac_modes(self) -> list[HVACMode]:
"""Get the hvac_modes"""
if not self.is_initialized:
return []
return [HVACMode.OFF, HVACMode.HEAT]
@overrides
async def start_cycle(
self,
hvac_mode: HVACMode,
_1,
_2,
_3,
force=False,
):
"""We use this function to change the on_percent"""
# if force:
await self.set_valve_open_percent()
@property
def is_device_active(self):
"""If the opening valve is open."""
try:
return get_safe_float(self._hass, self._opening_degree_entity_id) > 0
except Exception: # pylint: disable=broad-exception-caught
return False
@property
def valve_entity_ids(self) -> [str]:
"""get an arrary with all entityd id of the valve"""
ret = []
for entity in [
self.opening_degree_entity_id,
self.closing_degree_entity_id,
self.offset_calibration_entity_id,
]:
if entity:
ret.append(entity)
return ret

View File

@@ -15,6 +15,7 @@ from .const import (
CONF_SAFETY_MODE, CONF_SAFETY_MODE,
CONF_THERMOSTAT_TYPE, CONF_THERMOSTAT_TYPE,
CONF_THERMOSTAT_CENTRAL_CONFIG, CONF_THERMOSTAT_CENTRAL_CONFIG,
CONF_MAX_ON_PERCENT,
) )
VTHERM_API_NAME = "vtherm_api" VTHERM_API_NAME = "vtherm_api"
@@ -57,8 +58,10 @@ class VersatileThermostatAPI(dict):
self._threshold_number_entity = None self._threshold_number_entity = None
self._nb_active_number_entity = None self._nb_active_number_entity = None
self._central_configuration = None self._central_configuration = None
self._central_mode_select = None
# A dict that will store all Number entities which holds the temperature # A dict that will store all Number entities which holds the temperature
self._number_temperatures = dict() self._number_temperatures = dict()
self._max_on_percent = None
def find_central_configuration(self): def find_central_configuration(self):
"""Search for a central configuration""" """Search for a central configuration"""
@@ -106,6 +109,12 @@ class VersatileThermostatAPI(dict):
if self._safety_mode: if self._safety_mode:
_LOGGER.debug("We have found safet_mode params %s", self._safety_mode) _LOGGER.debug("We have found safet_mode params %s", self._safety_mode)
self._max_on_percent = config.get(CONF_MAX_ON_PERCENT)
if self._max_on_percent:
_LOGGER.debug(
"We have found max_on_percent setting %s", self._max_on_percent
)
def register_central_boiler(self, central_boiler_entity): def register_central_boiler(self, central_boiler_entity):
"""Register the central boiler entity. This is used by the CentralBoilerBinarySensor """Register the central boiler entity. This is used by the CentralBoilerBinarySensor
class to register itself at creation""" class to register itself at creation"""
@@ -149,10 +158,11 @@ class VersatileThermostatAPI(dict):
return entity.state return entity.state
return None return None
async def init_vtherm_links(self, only_use_central=False): async def init_vtherm_links(self, entry_id=None):
"""INitialize all VTherms entities links """Initialize all VTherms entities links
This method is called when HA is fully started (and all entities should be initialized) This method is called when HA is fully started (and all entities should be initialized)
Or when we need to reload all VTherm links (with Number temp entities, central boiler, ...) Or when we need to reload all VTherm links (with Number temp entities, central boiler, ...)
If entry_id is set, only the VTherm of this entry will be reloaded
""" """
await self.reload_central_boiler_binary_listener() await self.reload_central_boiler_binary_listener()
await self.reload_central_boiler_entities_list() await self.reload_central_boiler_entities_list()
@@ -162,12 +172,36 @@ class VersatileThermostatAPI(dict):
) )
if component: if component:
for entity in component.entities: for entity in component.entities:
if hasattr(entity, "init_presets"): # if hasattr(entity, "init_presets"):
if ( # if (
only_use_central is False # only_use_central is False
or entity.use_central_config_temperature # or entity.use_central_config_temperature
): # ):
await entity.init_presets(self.find_central_configuration()) # await entity.init_presets(self.find_central_configuration())
# A little hack to test if the climate is a VTherm. Cannot use isinstance
# due to circular dependency of BaseThermostat
if (
entity.device_info
and entity.device_info.get("model", None) == DOMAIN
):
if entry_id is None or entry_id == entity.unique_id:
await entity.async_startup(self.find_central_configuration())
async def init_vtherm_preset_with_central(self):
"""Init all VTherm presets when the VTherm uses central temperature"""
# Initialization of all preset for all VTherm
component: EntityComponent[ClimateEntity] = self._hass.data.get(
CLIMATE_DOMAIN, None
)
if component:
for entity in component.entities:
if (
entity.device_info
and entity.device_info.get("model", None) == DOMAIN
and entity.use_central_config_temperature
):
await entity.init_presets(self.find_central_configuration())
async def reload_central_boiler_binary_listener(self): async def reload_central_boiler_binary_listener(self):
"""Reloads the BinarySensor entity which listen to the number of """Reloads the BinarySensor entity which listen to the number of
@@ -180,6 +214,27 @@ class VersatileThermostatAPI(dict):
if self._nb_active_number_entity is not None: if self._nb_active_number_entity is not None:
await self._nb_active_number_entity.listen_vtherms_entities() await self._nb_active_number_entity.listen_vtherms_entities()
def register_central_mode_select(self, central_mode_select):
"""Register the select entity which holds the central_mode"""
self._central_mode_select = central_mode_select
async def notify_central_mode_change(self, old_central_mode: str | None = None):
"""Notify all VTherm that the central_mode have change"""
if self._central_mode_select is None:
return
# Update all VTherm states
component: EntityComponent[ClimateEntity] = self.hass.data[CLIMATE_DOMAIN]
for entity in component.entities:
if entity.device_info and entity.device_info.get("model", None) == DOMAIN:
_LOGGER.debug(
"Changing the central_mode. We have find %s to update",
entity.name,
)
await entity.check_central_mode(
self._central_mode_select.state, old_central_mode
)
@property @property
def self_regulation_expert(self): def self_regulation_expert(self):
"""Get the self regulation params""" """Get the self regulation params"""
@@ -195,6 +250,11 @@ class VersatileThermostatAPI(dict):
"""Get the safety_mode params""" """Get the safety_mode params"""
return self._safety_mode return self._safety_mode
@property
def max_on_percent(self):
"""Get the max_open_percent params"""
return self._max_on_percent
@property @property
def central_boiler_entity(self): def central_boiler_entity(self):
"""Get the central boiler binary_sensor entity""" """Get the central boiler binary_sensor entity"""
@@ -229,6 +289,14 @@ class VersatileThermostatAPI(dict):
return None return None
return int(self._threshold_number_entity.native_value) return int(self._threshold_number_entity.native_value)
@property
def central_mode(self) -> str | None:
"""Get the current central mode or None"""
if self._central_mode_select:
return self._central_mode_select.state
else:
return None
@property @property
def hass(self): def hass(self):
"""Get the HomeAssistant object""" """Get the HomeAssistant object"""

View File

@@ -0,0 +1,42 @@
# Choix des attributs de base
Choisisez le menu "Principaux attributs".
![image](images/config-main.png)
Donnez les principaux attributs obligatoires. Ces attributs sont communs à tous les VTherms :
1. un nom (sera le nom de l'intégration et aussi le nom de l'entité `climate`)
4. un identifiant d'entité de capteur de température qui donne la température de la pièce dans laquelle le radiateur est installé,
5. une entité facultative de capteur de donnant la date et heure de dernière vue du capteur (`last_seen`). Si vous avez ce capteur donnez le ici, il permet d'éviter des mises en sécurité lorsque la température est stable et que le capteur ne remonte plus de température pendant longtemps. (cf. TODO),
6. une durée de cycle en minutes. A chaque cycle :
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.
8. une puissance de l'équipement ce qui va activer les capteurs de puissance et énergie consommée par l'appareil,
9. 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
10. la possibilité de controler le thermostat de façon centralisée. Cf [controle centralisé](#le-contrôle-centralisé),
11. une case à cocher si ce VTherm est utilisé pour déclencher une éventuelle chaudière centrale.
> ![Astuce](images/tips.png) _*Notes*_
> 1. avec les types ```over_switch``` et ```over_valve```, les calculs sont effectués à chaque cycle. Donc en cas de changement de conditions, il faudra attendre le prochain cycle pour voir un changement. Pour cette raison, le cycle ne doit pas être trop long. **5 min est une bonne valeur** mais doit être adapté à votre type de chauffage. Plus l'inertie est grande et plus le cycle doit être long. Cf. 'TODO exemples de reglages,
> 2. si le cycle est trop court, le radiateur ne pourra jamais atteindre la température cible. Pour le radiateur à accumulation par exemple il sera sollicité inutilement.
# Choix des fonctions utilisées
Choisissez le menu "Fonctions".
![image](images/config-features.png)
Les différentes fonctions que vous souhaitez utiliser pour ce VTherm :
1. la détection d'ouvertures (portes, fenêtres) permettant de stopper le chauffage lorsque l'ouverture est ouverte. (f. TODO)
2. la détection de mouvement : VTherm peut adapter une consigne de température lorsqu'un mouvement est détecté dans la pièce. (cf. TODO)
3. la gestion de la puissance : VTherm peut stopper un équipement si la puissance consommée dans votre habitation dépasse un seuil. (cf TODO)
4. la détection de présence : si vous avez un capteur indiquant une présence ou non dans votre habitation, vous pouvez l'utiliser pour changer la température de consigne. CF. TODO. Attention de ne pas confondre cette fonction avec la détection de mouvement. La présence est plus faite pour être à l'échelle de l'habitation alors que le mouvement est plus fait pour être à l'échelle de la pièce.
5. l'arrêt/démarrage automatique : pour les VTherm de type `over_climate` uniquement. Cette fonction permet d'arrêter un équipement lorsque VTherm détete qu'il ne sera plus néessaire pendant un certain temps. Il utilise la courbe de température pour prévoir quand l'équipement sera de nouveau utile et le rallumera à ce moment là.
> ![Astuce](images/tips.png) _*Notes*_
> 1. La liste des fonctions disponibles s'adapte à votre type de VTherm.
> 2. Lorsque vous cochez une fonction, une nouvelle entrée menu s'ajoute pour configurer la fonction.
> 3. Vous ne pourrez pas valider la création d'un VTherm si tous les paramètres de toutes les fonctions n'ont pas été saisis.

View File

@@ -0,0 +1,61 @@
# Choix du Vtherm
> ![Astuce](images/tips.png) _*Notes*_
>
> Trois façons de travailler avec les VTherms sont disponibles :
> 1. Chaque Versatile Thermostat est entièrement configurée de manière indépendante. Choisissez cette option si vous ne souhaitez avoir aucune configuration ou gestion centrale.
> 2. Certains aspects sont configurés de manière centralisée. Cela permet par ex. définir la température min/max, la détection de fenêtre ouverte,… au niveau d'une instance centrale et unique. Pour chaque VTherm que vous configurez, vous pouvez alors choisir d'utiliser la configuration centrale ou de la remplacer par des paramètres personnalisés.
> 3. En plus de cette configuration centralisée, tous les VTherm peuvent être contrôlées par une seule entité de type `select`. Cette fonction est nommé `central_mode`. Cela permet de stopper / démarrer / mettre en hors gel / etc tous les VTherms en une seule fois. Pour chaque VTherm, l'utilisateur indique si il est concerné par ce `central_mode`.
## Création d'un nouveau Versatile Thermostat
Cliquez sur le bouton Ajouter une intégration dans la page d'intégration
![image](images/add-an-integration.png)
puis
![image](images/config-main0.png)
La configuration peut être modifiée via la même interface. Sélectionnez simplement le thermostat à modifier, appuyez sur "Configurer" et vous pourrez modifier certains paramètres ou la configuration.
Suivez ensuite les étapes de configuration en sélectionnant dans le menu l'option à configurer.
# Choix d'un type de VTherm
## Configuration centralisée
Ce choix permet de configurer une fois pour tous les VTherms certains aspects qui peuvent être répétitifs comme :
1. les paramètres des différents algorithmes (TPI, détection d'ouvertures, détection de mouvements, capteurs de puissance de votre habitation, la détection de présence). Tous ces paramètres sont transverses à tous les VTherms. Vous pouvez donc ne les saisir qu'une seule fois dans la `Configuration centralisée`. Cette configuration ne créé pas de VTherm à proprement parler. Elle permet juste de mettre en commun des paramètres qu'il serait fastidieux de resaisir pour chaque VTherm. Noter que vous pouvez surcharger les paramètres sur les VTherms pour les spécialisés au besoin,
2. la configuration de la commande d'un chauffage central,
3. certains paramètre avancés comme la mise en sécurité
## VTherm sur un switch
Ce VTherm permet de contrôler un interrupteur qui allume ou étient un radiateur. Cet interrupteur peut être un interrupteur physique qui allume ou éteint directement un radiateur (souvent électrique) ou un interrupteur virtuel qui pourra effectuer les actions que vous voulez sur demande d'allumage ou extinction. Ce dernier type permet par exemple de commander des switchs avec fil pilote ou deu DIY avec diode pour fil pilote. VTherm va moduler la proportion de temps allumé vs éteint pour obtenir la température souhaitée. Si il fait froid, il allume plus souvent (jusqu'à 100%), si il fait chaud il baisse le pourcentage d'allumage. Ce pourcentage d'allumage en nommé `on_percent`.
Les entités sous-jacentes sont donc des `switchs` ou des `input_boolean`.
## Vtherm sur un autre thermostat
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.
Les entités sous-jacentes de ce type de VTherm sont donc des `climate` exclusivement.
## VTherm sur une vanne
Lorsque tout ce que vous avez à disposition pour réguler la température de votre radiateur est une entité de type `number` vous devez utiliser le type `over_valve`. VTherm ouvre ou ferme la vanne en fonction de l'écart entre la consigne et la température réelle de la pièce (et de la température extérieure).
Ce type peut être utilisé pour les _TRV_ qui n'ont pas de `climate` associé ou tout autre solution type DIY qui expose une entité `number`.
# Le bon choix
> ![Astuce](images/tips.png) _*Comment choisir le type*_
> Le choix du type est important. Il n'est plus possible de le modifier via l'IHM de configuration. Pour bien chsoisir, il faut se poser les quelques questions suivantes :
> 1. **quel type d'équipement je vais piloter ?** Dans l'ordre voici ce qu'il faut faire :
> 1. si vous avez une vanne thermostatique (_TRV_) commandable dans Home Assistant via une entité de type ```number``` (par exemple une _Shelly TRV_), choisissez le type `over_valve`. C'est le type le plus direct et qui assure la meilleure régulation,
> 2. si vous avez un radiateur électrique (avec ou sans fil pilote) et qu'une entité de type ```switch``` permet de l'allumer ou de l'éteindre, alors le type ```over_switch``` est préférable. La régulation sera faite par le Versatile Thermostat en fonction de la température mesuré par votre thermomètre, à l'endroit ou vous l'avez placé,
> 3. dans tous les autres cas, utilisez le mode ```over_climate```. Vous gardez votre entité ```climate``` d'origine et le Versatile Thermostat "ne fait que" piloter le on/off et la température cible de votre thermostat d'origine. La régulation est faite par votre thermostat d'origine dans ce cas. Ce mode est particulièrement adapté aux climatisations réversible tout-en-un dont l'exposition dans Home Assistant se limite à une entité de type ```climate```. Une auto-régulation avancée permet d'atteindre la consigne en forçant la consigne ou un pilotant directement la vanne lorsque c'est possible.
> 2. **quelle type de régulation je veux ?** Si l'équipement piloté possède son propre mécanisme de régulation (clim, certaine vanne TRV) et que cette régulation fonctionne bien, optez pour un ```over_climate```. Si l'équipement est de type _TRV_ avec une vanne pilotable sous HA, alors le type `over_climate` avec une auto-régulation `Contrôle direct de la vanne` est le meilleur choix.
# Article en référence
Un article permettant d'aller plus loin sur les concepts est visible ici (en Français) : https://www.hacf.fr/optimisation-versatile-thermostat/#optimiser-vos-vtherm

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

1676
documentation/fr/one-page.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
# Quand l'utiliser et ne pas l'utiliser
Ce thermostat peut piloter 3 types d'équipements :
1. un radiateur qui ne fonctionne qu'en mode marche/arrêt (nommé ```thermostat_over_switch```). La configuration minimale nécessaire pour utiliser ce type thermostat est :
1. un équipement comme un radiateur (un ```switch``` ou équivalent),
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.
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.
Les installations avec fil pilote et diode d'activation bénéficie d'une option qui permet d'inverser la commande on/off du radiateur sous-jacent. Pour cela, utilisez le type `over switch` et cochez l'option d'inversion de la commande.
## 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,
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.
# Pourquoi une nouvelle implémentation du thermostat ?
Ce composant nommé __Versatile thermostat__ gère les cas d'utilisation suivants :
- 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 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é.
- La **gestion de la présence à domicile**. Cette fonctionnalité vous permet de modifier dynamiquement la température du préréglage en tenant compte d'un capteur de présence de votre maison.
- Des **services pour interagir avec le thermostat** à partir d'autres intégrations : vous pouvez forcer la présence / la non-présence à l'aide d'un service, et vous pouvez modifier dynamiquement la température des préréglages et changer les paramètres de sécurité.
- Ajouter des capteurs pour voir les états internes du thermostat,
- Contrôle centralisé de tous les Versatile Thermostat pour les stopper tous, les passer tous en hors-gel, les forcer en mode Chauffage (l'hiver), les forcer en mode Climatisation (l'été).
- Contrôle d'une chaudière centrale et des VTherm qui doivent contrôler cette chaudière.

View File

@@ -0,0 +1,129 @@
> ![Nouveau](images/new-icon.png) _*Historique des dernières versions*_
> * **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`
> * **Release 6.5** :
> - Ajout d'une nouvelle fonction permettant l'arrêt et la relance automatique d'un VTherm `over_climate` [585](https://github.com/jmcollin78/versatile_thermostat/issues/585)
> - Amélioration de la gestion des ouvertures au démarrage. Permet de mémoriser et de recalculer l'état d'une ouverture au redémarage de Home Assistant [504](https://github.com/jmcollin78/versatile_thermostat/issues/504)
> * **Release 6.0** :
> - Ajout d'entités du domaine Number permettant de configurer les températures des presets [354](https://github.com/jmcollin78/versatile_thermostat/issues/354)
> - Refonte complète du menu de configuration pour supprimer les températures et utililsation d'un menu au lieu d'un tunnel de configuration [354](https://github.com/jmcollin78/versatile_thermostat/issues/354)
> * **Release 5.4** :
> - Ajout du pas de température [#311](https://github.com/jmcollin78/versatile_thermostat/issues/311),
> - ajout de seuils de régulation pour les `over_valve` pour éviter de trop vider la batterie des TRV [#338](https://github.com/jmcollin78/versatile_thermostat/issues/338),
> - ajout d'une option permettant d'utiliser la température interne d'un TRV pour forcer l' auto-régulation [#348](https://github.com/jmcollin78/versatile_thermostat/issues/348),
> - ajout d'une fonction de keep-alive pour les VTherm `over_switch` [#345](https://github.com/jmcollin78/versatile_thermostat/issues/345)
<details>
<summary>Autres versions</summary>
> * **Release 5.3** : Ajout d'une fonction de pilotage d'une chaudière centrale [#234](https://github.com/jmcollin78/versatile_thermostat/issues/234) - plus d'infos ici: [Le contrôle d'une chaudière centrale](#le-contrôle-dune-chaudière-centrale). Ajout de la possibilité de désactiver le mode sécurité pour le thermomètre extérieur [#343](https://github.com/jmcollin78/versatile_thermostat/issues/343)
> * **Release 5.2** : Ajout d'un `central_mode` permettant de piloter tous les VTherms de façon centralisée [#158](https://github.com/jmcollin78/versatile_thermostat/issues/158).
> * **Release 5.1** : Limitation des valeurs envoyées aux valves et au température envoyées au climate sous-jacent.
> * **Release 5.0** : Ajout d'une configuration centrale permettant de mettre en commun les attributs qui peuvent l'être [#239](https://github.com/jmcollin78/versatile_thermostat/issues/239).
> * **Release 4.3** : Ajout d'un mode auto-fan pour le type `over_climate` permettant d'activer la ventilation si l'écart de température est important [#223](https://github.com/jmcollin78/versatile_thermostat/issues/223).
> * **Release 4.2** : Le calcul de la pente de la courbe de température se fait maintenant en °/heure et non plus en °/min [#242](https://github.com/jmcollin78/versatile_thermostat/issues/242). Correction de la détection automatique des ouvertures par l'ajout d'un lissage de la courbe de température .
> * **Release 4.1** : Ajout d'un mode de régulation **Expert** dans lequel l'utilisateur peut spécifier ses propres paramètres d'auto-régulation au lieu d'utiliser les pre-programmés [#194](https://github.com/jmcollin78/versatile_thermostat/issues/194).
> * **Release 4.0** : Ajout de la prise en charge de la **Versatile Thermostat UI Card**. Voir [Versatile Thermostat UI Card](https://github.com/jmcollin78/versatile-thermostat-ui-card). Ajout d'un mode de régulation **Slow** pour les appareils de chauffage à latence lente [#168](https://github.com/jmcollin78/versatile_thermostat/issues/168). Changement de la façon dont **la puissance est calculée** dans le cas de VTherm avec des équipements multi-sous-jacents [#146](https://github.com/jmcollin78/versatile_thermostat/issues/146). Ajout de la prise en charge de AC et Heat pour VTherm via un interrupteur également [#144](https://github.com/jmcollin78/versatile_thermostat/pull/144)
> * **Release 3.8**: Ajout d'une **fonction d'auto-régulation** pour les thermostats `over climate` dont la régulation est faite par le climate sous-jacent. Cf. [L'auto-régulation](#lauto-régulation) et [#129](https://github.com/jmcollin78/versatile_thermostat/issues/129). Ajout de la **possibilité d'inverser la commande** pour un thermostat `over switch` pour adresser les installations avec fil pilote et diode [#124](https://github.com/jmcollin78/versatile_thermostat/issues/124).
> * **Release 3.7**: Ajout du type de **Versatile Thermostat `over valve`** pour piloter une vanne TRV directement ou tout autre équipement type gradateur pour le chauffage. La régulation se fait alors directement en agissant sur le pourcentage d'ouverture de l'entité sous-jacente : 0 la vanne est coupée, 100 : la vanne est ouverte à fond. Cf. [#131](https://github.com/jmcollin78/versatile_thermostat/issues/131). Ajout d'une fonction permettant le bypass de la détection d'ouverture [#138](https://github.com/jmcollin78/versatile_thermostat/issues/138). Ajout de la langue Slovaque
> * **Release 3.6**: Ajout du paramètre `motion_off_delay` pour améliorer la gestion de des mouvements [#116](https://github.com/jmcollin78/versatile_thermostat/issues/116), [#128](https://github.com/jmcollin78/versatile_thermostat/issues/128). Ajout du mode AC (air conditionné) pour un VTherm over switch. Préparation du projet Github pour faciliter les contributions [#127](https://github.com/jmcollin78/versatile_thermostat/issues/127)
> * **Release 3.5**: Plusieurs thermostats sont possibles en "thermostat over climate" mode [#113](https://github.com/jmcollin78/versatile_thermostat/issues/113)
> * **Release 3.4**: bug fix et exposition des preset temperatures pour le mode AC [#103](https://github.com/jmcollin78/versatile_thermostat/issues/103)
> * **Release 3.3**: ajout du mode Air Conditionné (AC). Cette fonction vous permet d'utiliser le mode AC de votre thermostat sous-jacent. Pour l'utiliser, vous devez cocher l'option "Uitliser le mode AC" et définir les valeurs de température pour les presets et pour les presets en cas d'absence
> * **Release 3.2** : ajout de la possibilité de commander plusieurs switch à partir du même thermostat. Dans ce mode, les switchs sont déclenchés avec un délai pour minimiser la puissance nécessaire à un instant (on minimise les périodes de recouvrement). Voir [Configuration](#sélectionnez-des-entités-pilotées)
> * **Release 3.1** : ajout d'une détection de fenêtres/portes ouvertes par chute de température. Cette nouvelle fonction permet de stopper automatiquement un radiateur lorsque la température chute brutalement. Voir [Le mode auto](#le-mode-auto)
> * **Release majeure 3.0** : ajout d'un équipement thermostat et de capteurs (binaires et non binaires) associés. Beaucoup plus proche de la philosphie Home Assistant, vous avez maintenant un accès direct à l'énergie consommée par le radiateur piloté par le thermostat et à plein d'autres capteurs qui seront utiles dans vos automatisations et dashboard.
> * **release 2.3** : ajout de la mesure de puissance et d'énergie du radiateur piloté par le thermostat.
> * **release 2.2** : ajout de fonction de sécurité permettant de ne pas laisser éternellement en chauffe un radiateur en cas de panne du thermomètre
> * **release majeure 2.0** : ajout du thermostat "over climate" permettant de transformer n'importe quel thermostat en Versatile Thermostat et lui ajouter toutes les fonctions de ce dernier.
</details>
<details>
<summary>Changements dans la version 6.0</summary>
# Changements dans la version 6.0
## Entités de température pour les pre-réglages
Les températures des presets sont maintenant directement acessibles sous la forme d'entités reliés au VTherm.
Exemple :
![Entités température](images/temp-entities-1.png)
Les entités Boost, Confort, Eco et Hors-gel permettent de régler directement les températures de ces présets sans avoir à reconfigurer le VTHerm dans les écrans de configuration.
Ces modifications sont persistentent à un redémarrage et sont prises en compte immédiatement par le VTherm.
En fonction des fonctions activées, la liste des températures peut être plus ou moins complète :
1. Si la gestion de présence est activée, les presets en cas d'absence sont créés. Ils sont suffixés par 'abs' pour absence,
2. Si la gestion de la climatisation (Mode AC) est activé, les presets en mode clim sont créés. Ils sont suffixés par 'clim' pour climatisation. Seul le preset Hors gel n'a pas d'équivalent en mode clim,
3. Les différentes combinaison absent et clim peuvent être créés en fonction de la configuration du VTherm
Si un VTherm utilise les preset de la configuration centrale, ces entités ne sont pas créées, car les températures des presets sont gérés par la configuration centrale.
### Dans le cas d'une configuration centrale
Si vous avez configuré une configuration centrale, celle-ci possède aussi ses propres presets qui répondent au même règles qu'énoncées ci-dessus.
Exemple d'une configuration centrale avec gestion de présence et mode AC (climatisation) :
![Entités température](images/temp-entities-2.png)
Dans le cas d'un changement d'une température de la configuration centrale, tous les VTherm qui utilisent ce preset sont immédiatement mis à jour.
## Refonte du menu de configuration
Le menu de configuration a été totalement revu. Il s'adapte dynamiquement aux choix de l'utilisateur et permet d'accéder directement aux réglages de la fonction voulue sans avoir à dérouler tous le tunnel de configuration.
Pour créer un nouveau VTherm, il faudra d'abord choisir le type de VTherm :
![Choix VTherm](images/config-main0.png)
Puis, vous accédez maintenant au menu de configuration suivant :
![VTherm menu](images/config-menu.png)
Chaque partie à configurer est accessible directement, sans avoir à dérouler tout le tunnel de configuration comme précédemment.
Vous noterez l'option de menu nommée `Fonctions` qui permet de choisir quelles fonctions vont être implémentées pour ce VTherm :
![VTherm fonctions](images/config-features.png)
En fonction de vos choix, le menu principal s'adaptera pour ajouter les options nécessaires.
Exemple de menu avec toutes les fonctions cochées :
![VTherm menu](images/config-menu-all-options.png)
Vous pouvez constater que les options 'Détection des ouvertures', 'Détection de mouvement', 'Gestion de la puissance' et 'Gestion de présence' ont été ajoutées. Vous pouvez alors les configurer.
### Les options de menu 'Configuration incomplète' et 'Finaliser'
La dernière option du menu est spéciale. Elle permet de valider la création du VTherm lorsque toutes les fonctions ont été correctement configurées.
Si l'une options n'est pas correctement configurée, la dernière option est la suivante :
![Configuration incomplète](images/config-not-complete.png)
Sa sélection ne fait rien mais vous empêche de finaliser la création (resp. la modification) du VTherm.
**Vous devez alors chercher dans les options laquelle manque**.
Une fois que toute la configuration est valide, la dernière option se transforme en :
![Configuration complète](images/config-complete.png)
Cliquez sur cette option pour créér (resp. modifier) le VTherm :
![Configuration terminée](images/config-terminate.png)
</details>
<details>
<summary>Changements dans la version 5.0</summary>
# Changements dans la version 5.0
Vous pouvez maintenant définir une configuration centrale qui va vous permettre de mettre en commun sur tous vos VTherms (ou seulement une partie), certains attributs. Pour utiliser cette possibilité, vous devez :
1. Créer un VTherm de type "Configuration Centrale",
2. Saisir les attributs de cette configuration centrale
Pour l'utiliser ensuite dans les autres VTherms, vous devez les reconfigurer et à chaque fois que c'est possible cocher la case "Utiliser la configuration centrale". Cette case à cocher apparait dans tous les groupes d'attributs qui peuvent avoir recours à la configuration centrale : attributs principaux, TPI, ouvertures, mouvement, puissance, présence et paramètres avancés.
Les attributs configurable dans la configuration centrale est listée ici : [Synthèse des paramètres](#synthèse-des-paramètres).
Lors d'un changement sur la configuration centrale, tous les VTherms seront rechargés pour tenir compte de ces changements.
En conséquence toute la phase de paramètrage d'un VTherm a été profondemment modifiée pour pouvoir utiliser la configuration centrale ou surcharger les valeurs de la configuration centrale par des valeurs propre au VTherm en cours de configuration.
</details>

View File

@@ -3,5 +3,5 @@
"content_in_root": false, "content_in_root": false,
"render_readme": true, "render_readme": true,
"hide_default_branch": false, "hide_default_branch": false,
"homeassistant": "2023.12.1" "homeassistant": "2024.10.4"
} }

BIN
images/config-complete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
images/config-features.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
images/config-menu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
images/config-terminate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

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