v0.16.5
This commit is contained in:
1 parent
bb5caa769f
commit
a679eb5747
25 files changed
+484
-78
No files matched your search
Generated
+1
-1
@@ -719,7 +719,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gree-controller"
|
name = "gree-controller"
|
||||||
version = "0.16.4"
|
version = "0.16.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "gree-controller"
|
name = "gree-controller"
|
||||||
version = "0.16.4"
|
version = "0.16.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["GREE Controller contributors"]
|
authors = ["GREE Controller contributors"]
|
||||||
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
||||||
|
|||||||
+27
-26
@@ -1,17 +1,17 @@
|
|||||||
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
||||||
2fd3bf1fb7ef548c7abc9b26d1cc4ef470784b8637af65446b845b27a45b547b ./.gitignore
|
2fd3bf1fb7ef548c7abc9b26d1cc4ef470784b8637af65446b845b27a45b547b ./.gitignore
|
||||||
147eed97b308e0c27d6a5ee738a0ee710fa6ba2c6bbf7503df0fe484e0c7f842 ./Cargo.lock
|
795c232a7845a217a5711da4d9547282ba7f42fd160a249df07d631fac83928b ./Cargo.lock
|
||||||
c604af0060aef4e79d2e11fd1e1222a2444cebc835bc50629cff3d2373c5a05a ./Cargo.toml
|
2c57e2334ace9e7e320c97b14e1b0372c881717dad4bf3e6ef6abae252ed8056 ./Cargo.toml
|
||||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||||
75a6652611273ae458cb3509401cdb196d1084db1e49496ecdce448f490e6249 ./README.md
|
eb8dd4d456419762d78e8d426326ac33fa99dbf857ab0f34f52dadc30e8a62be ./README.md
|
||||||
eedafc7faaa9eb95b564a249a4fef82d5f4cc32697eb0e370b96264f3c47b69c ./SOURCE_MANIFEST.sha256
|
c0f61039799416d2d72d37be85beabd1a68fc3325563189732027df31e7efc2b ./SOURCE_MANIFEST.sha256
|
||||||
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
|
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
|
||||||
bb9840a0dc2b89d057d1461ae581ee61ebf856e3b6d38bc7d1bdd111eef7c550 ./docs/API.md
|
e961deaaa82be18f9e2933999dda33f55bfc648200e62043b5e8fffdfdd4464c ./docs/API.md
|
||||||
4f9826f1bf3b42ed2d62d8a8e9221b50175b3a9fdda7e9d219351f7a33039257 ./docs/FLOW.md
|
4f9826f1bf3b42ed2d62d8a8e9221b50175b3a9fdda7e9d219351f7a33039257 ./docs/FLOW.md
|
||||||
52c8dc48aed987fd28b2b543af1e5a782efb2a44104ccb0fd6b1740255cf384e ./docs/openapi.json
|
5c24d9f03e5025eda289e6862a7919fe3951992f7120647dc6e0cd0f1f9aa96f ./docs/openapi.json
|
||||||
d8a1b3e34cf8a79ac3296bf600ad47826d639cdef4c1c2c099f8b6b21a391ebd ./flows/simple-heating/igor-simple-heat-v5.flow.json
|
a5ad73f7933031dfaa99aa92e27f10223d53fb84b14ee0a378eb5f10eb80e8c8 ./flows/simple-heating/igor-simple-heat-v5.flow.json
|
||||||
bd9a07b8ca4b090ad0089e9037492cc25c47f3456b97ec08152947314ea904a6 ./flows/simple-heating/jan-pieczara-simple-heat-v5.flow.json
|
9743f4328a18bc9d89d10a9011d13ca201a4f352db2119335a66a2147a9a486d ./flows/simple-heating/jan-pieczara-simple-heat-v5.flow.json
|
||||||
c4e6e3e8df5eb7f9730a79fb90edd155f0510f7bb4862a04b9f35ba649c22376 ./flows/simple-heating/sypialnia-simple-heat-v5.flow.json
|
2d804831a72577e80dc1ec3cf4f86dc8bcf00b1d8c80929896f11da0230b2b07 ./flows/simple-heating/sypialnia-simple-heat-v5.flow.json
|
||||||
77b8becf7a2c16a384ea85721d878b76e2d520549cb4455d9aa806e57e5a2ecd ./ha-addon/.env.example
|
77b8becf7a2c16a384ea85721d878b76e2d520549cb4455d9aa806e57e5a2ecd ./ha-addon/.env.example
|
||||||
0ca6e58170c2b8925194431ebbdb55f0c8b377fec5cd422cd2bd257e2d9328e3 ./ha-addon/Dockerfile
|
0ca6e58170c2b8925194431ebbdb55f0c8b377fec5cd422cd2bd257e2d9328e3 ./ha-addon/Dockerfile
|
||||||
0951696a2c2e66d4d969f8e7f8641184e3012f261583b31074bc38a7a1c0cc1a ./ha-addon/README.md
|
0951696a2c2e66d4d969f8e7f8641184e3012f261583b31074bc38a7a1c0cc1a ./ha-addon/README.md
|
||||||
@@ -25,7 +25,7 @@ b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/hom
|
|||||||
6509286c7b7de638dcc5d40ce53181e65d1eddc5dc36a13306dc6d87220fedbb ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py
|
6509286c7b7de638dcc5d40ce53181e65d1eddc5dc36a13306dc6d87220fedbb ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py
|
||||||
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py
|
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py
|
||||||
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png
|
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png
|
||||||
e8d079ebe63cd2916bd219cd45f583940e3d7ba5769af8deb1038415084a1a35 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
4ecd424fd1969a1bcd81eb85ea5ac50295a0d7691d50013027a7c2e400aec449 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
||||||
c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py
|
c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py
|
||||||
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py
|
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py
|
||||||
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py
|
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py
|
||||||
@@ -33,10 +33,10 @@ cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/hom
|
|||||||
a745cc458c235158d5d70b6a33d8000fd8b40ff93bfe2f2796af0f4b3a9eef9b ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
a745cc458c235158d5d70b6a33d8000fd8b40ff93bfe2f2796af0f4b3a9eef9b ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
||||||
50b7d6fe6329ad4ea6bee9612ab141c2f73f8382fcc2af0bb076a8e8d4dd4465 ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json
|
50b7d6fe6329ad4ea6bee9612ab141c2f73f8382fcc2af0bb076a8e8d4dd4465 ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json
|
||||||
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json
|
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json
|
||||||
904b21f54c7af0f59d91016d8513baaea5e927a349e18207f4290c51d7f2be8a ./ha-addon/repository/gree-controller/CHANGELOG.md
|
a4ae5bfa385365ef5db5332f2635a77e212a82e00a3cf473c5617ad134d3133e ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||||
50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md
|
50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md
|
||||||
e3834bf712d4cd2acc715d8c85db96dd6e2fb3fbb758dac537f6034c337b3745 ./ha-addon/repository/gree-controller/README.md
|
e3834bf712d4cd2acc715d8c85db96dd6e2fb3fbb758dac537f6034c337b3745 ./ha-addon/repository/gree-controller/README.md
|
||||||
efe06cb35044b19474ae11e5ef42e3201787ff1d164087cdbdb845a5ad6a16d8 ./ha-addon/repository/gree-controller/config.yaml
|
1fa36a0a0a3219604073950cd936c68fa6de1f51c031443ab22bf66596e0733f ./ha-addon/repository/gree-controller/config.yaml
|
||||||
3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png
|
3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png
|
||||||
0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png
|
0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png
|
||||||
2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml
|
2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml
|
||||||
@@ -46,8 +46,8 @@ cea12bc74c066a588a5ef0ef7bad9d4906bdbcea99f2d275429b0eefc94b4b5f ./ha-addon/run
|
|||||||
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
|
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
|
||||||
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
|
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
|
||||||
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
||||||
4bf92ee5627be67b66d76a6106b224159f95071f0aa2a727cd964e113bc3b32a ./lang/en.json
|
f3c1286c3758a98b6a2945562753262e9083a6c638a1dcae556c3e978b4a69af ./lang/en.json
|
||||||
1e056cd915ba2e808db1287c99c06bbbbb3c98e5008481471b895e6ec20ea43a ./lang/pl.json
|
ac6e6285a6153c5d8ce3c45d81ca6fe6c688505957e142e79e0ec0b496c400f3 ./lang/pl.json
|
||||||
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
||||||
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
||||||
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
||||||
@@ -103,6 +103,7 @@ e00d211e3885e30d7fed1e43b44e6fdad40a67019060156c0641816a93e3365f ./scripts/netw
|
|||||||
b1e7a7a7a1d6e21012808818ff1478f5116a3dc4c7da3650b57a719ca9df7873 ./scripts/test_flow_block_help_ui.js
|
b1e7a7a7a1d6e21012808818ff1478f5116a3dc4c7da3650b57a719ca9df7873 ./scripts/test_flow_block_help_ui.js
|
||||||
e3825cfd9e6e149fd227c521d886dc116170f2553d4d7639d61874267f257b6e ./scripts/test_flow_disable_ui.js
|
e3825cfd9e6e149fd227c521d886dc116170f2553d4d7639d61874267f257b6e ./scripts/test_flow_disable_ui.js
|
||||||
2942b34d82d06c950fbf167442fd1a25185de193055e02f2ce8dff62c2039add ./scripts/test_simple_heating_ui.js
|
2942b34d82d06c950fbf167442fd1a25185de193055e02f2ce8dff62c2039add ./scripts/test_simple_heating_ui.js
|
||||||
|
9bef00c3a0956c5c893eff6cf1fee9681140a1e76da630fa5a347bce19f07981 ./scripts/test_temperature_explanation_ui.js
|
||||||
b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/update.sh
|
b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/update.sh
|
||||||
c260a6d291ad913f6aca60f23a0acf6928d68cfb0545b2a698d58c5a95fcf934 ./scripts/verify_simple_heating.py
|
c260a6d291ad913f6aca60f23a0acf6928d68cfb0545b2a698d58c5a95fcf934 ./scripts/verify_simple_heating.py
|
||||||
c32caf3ac7b521324d1d27233c271374ccf4efc3d243544ece727372c792e14a ./src/api.rs
|
c32caf3ac7b521324d1d27233c271374ccf4efc3d243544ece727372c792e14a ./src/api.rs
|
||||||
@@ -127,7 +128,7 @@ b777e7fbeccef699e11ffd16bb18633843c99afecacce2221280e54b09f6d2da ./src/api/publ
|
|||||||
fd08162d0f9287700196028ad0ca2aec7c242238056b0a228ca5bdcb319ece4e ./src/api/settings.rs
|
fd08162d0f9287700196028ad0ca2aec7c242238056b0a228ca5bdcb319ece4e ./src/api/settings.rs
|
||||||
7c974a98c72092886247f67c3a1c62736898d4b279ceb9c4c306690a3c6e4294 ./src/api/system.rs
|
7c974a98c72092886247f67c3a1c62736898d4b279ceb9c4c306690a3c6e4294 ./src/api/system.rs
|
||||||
3b0124a76dbc654584bcf186d43f89d20b6e7fd980ce0ffbf5a9e8eced5ec7f6 ./src/api/websocket.rs
|
3b0124a76dbc654584bcf186d43f89d20b6e7fd980ce0ffbf5a9e8eced5ec7f6 ./src/api/websocket.rs
|
||||||
eaa4070afbc19686d5e9bdb2d24fb2c7fc4bf1f90dc1c4f12fef17017d4cb975 ./src/api/zones.rs
|
79076544f4145c0eb120d544e9fab767b5c7ae625c2db007ee8be8a06bb0b85e ./src/api/zones.rs
|
||||||
32fa38d4315682cd3ef3ecdc91e571c2729b5e68b6f2bff965e43de3fdd2aeed ./src/config.rs
|
32fa38d4315682cd3ef3ecdc91e571c2729b5e68b6f2bff965e43de3fdd2aeed ./src/config.rs
|
||||||
de0ce7807aebe4c0e325fbef01855e7fc1cfd117529f53f04a416fda3997673a ./src/db.rs
|
de0ce7807aebe4c0e325fbef01855e7fc1cfd117529f53f04a416fda3997673a ./src/db.rs
|
||||||
87f1614a80addde69b619421187526d7f86f893c0ac18998854f11675d6e7cca ./src/db/climate.rs
|
87f1614a80addde69b619421187526d7f86f893c0ac18998854f11675d6e7cca ./src/db/climate.rs
|
||||||
@@ -146,7 +147,7 @@ f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_
|
|||||||
e6d6bf39e254bd2779d3bcb31d966aa3f6688ebfa91b4088b70fd796cad66596 ./src/engine/automations.rs
|
e6d6bf39e254bd2779d3bcb31d966aa3f6688ebfa91b4088b70fd796cad66596 ./src/engine/automations.rs
|
||||||
1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs
|
1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs
|
||||||
357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs
|
357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs
|
||||||
62a15ea757c53796149e670863dae1b4e1a16ceb6ef88ed84ebd3089c0b9880d ./src/engine/control_plan.rs
|
c6b9651090e89fe0436826f66ae56825bf1e4b209ef8a27beb22fb1deaaa8c36 ./src/engine/control_plan.rs
|
||||||
7b48a33343d519a2ff2d81e9d757eb56a0c1beacfc0b7b8ef61f49c2ad363f9a ./src/engine/deadlines.rs
|
7b48a33343d519a2ff2d81e9d757eb56a0c1beacfc0b7b8ef61f49c2ad363f9a ./src/engine/deadlines.rs
|
||||||
519f67ab805f61afea107215f5df50562fa1669a48334cc015570952e25e773a ./src/engine/energy.rs
|
519f67ab805f61afea107215f5df50562fa1669a48334cc015570952e25e773a ./src/engine/energy.rs
|
||||||
e350aa63f3bb14ca887eaaa7555edbeae00fb584cbcaf552cc972f0c8918f5d8 ./src/engine/groups.rs
|
e350aa63f3bb14ca887eaaa7555edbeae00fb584cbcaf552cc972f0c8918f5d8 ./src/engine/groups.rs
|
||||||
@@ -158,13 +159,13 @@ c73691c3637465ab0f79aecf7f5428d0317799a8ab5329ed564cf57963bbe86e ./src/engine/r
|
|||||||
48a496c3935f886a90b90461c38a7aa31ebc40c6482d6feaeb2be47bc2387625 ./src/engine/schedules.rs
|
48a496c3935f886a90b90461c38a7aa31ebc40c6482d6feaeb2be47bc2387625 ./src/engine/schedules.rs
|
||||||
9adfcb3c3dffbaa870b314904bd1995e7b78c3f12febd69e23433179e4bf3fba ./src/engine/simple_heat.rs
|
9adfcb3c3dffbaa870b314904bd1995e7b78c3f12febd69e23433179e4bf3fba ./src/engine/simple_heat.rs
|
||||||
2694363e607b7f4407ed07c24019c457f0e0d093cb336ee2dc5cf77487c528f6 ./src/engine/targets.rs
|
2694363e607b7f4407ed07c24019c457f0e0d093cb336ee2dc5cf77487c528f6 ./src/engine/targets.rs
|
||||||
d62ac5e97a36bbdb31bc23871ea7de73d8ac64a1e8203153d9faff0b1a075bc9 ./src/engine/temperature.rs
|
b35d7e96e50d40bf26be2e47e1fa1d393f4c07caeac41f3595f5d595579e55f7 ./src/engine/temperature.rs
|
||||||
833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs
|
833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs
|
||||||
4160811f47eca6e75970fe6c6b95198f86a4b97b7050531c19a19efb3d76d97d ./src/engine/tests.rs
|
f563f2da2409bf291d67daa1b2b2fd6c7a463a3a11343afb08131d20cd26cd69 ./src/engine/tests.rs
|
||||||
07c3c462884344f018654267de3d454d895343b4699e34635e4d1ec88546df0a ./src/engine/zone_actions.rs
|
63ded0ec62833699cbb7fcc6e4189ef19feafc23b9838d8fe9ba98889feb3e67 ./src/engine/zone_actions.rs
|
||||||
e246b6d6e3126941b989735a27e5fbe88dcaf351420aaaa060bb3761620d8244 ./src/engine/zone_control.rs
|
38256ec0e5ee735aa2f7ad7f8daaf2aa96077ea16a6c2cbceac43c4fe2bf8aa5 ./src/engine/zone_control.rs
|
||||||
bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs
|
bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs
|
||||||
d681f2aa53c39f37411834c300e9200299f94ee4ea021f44f70a3ea1774bd698 ./src/home_assistant.rs
|
8e181f29119ef6c32ee3821fe220ade97298c105c681c41466af0c16e539b7db ./src/home_assistant.rs
|
||||||
bcd14e4d1af3ca580f3b95bcd86164c40f762511e1b8e2501cb8a598a6d8adf4 ./src/influxdb.rs
|
bcd14e4d1af3ca580f3b95bcd86164c40f762511e1b8e2501cb8a598a6d8adf4 ./src/influxdb.rs
|
||||||
da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb/codec.rs
|
da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb/codec.rs
|
||||||
2d36405912646b30b1a1a37a7a4e18c2104ab22e865b593316a609dda884aec2 ./src/influxdb/query.rs
|
2d36405912646b30b1a1a37a7a4e18c2104ab22e865b593316a609dda884aec2 ./src/influxdb/query.rs
|
||||||
@@ -181,7 +182,7 @@ aeab5b5acf35d38a54fd1d82e964fe56635ade69c0149ad423df317095d01555 ./src/models/f
|
|||||||
17cac849056b677a3872e600a0d99218fb383ad5ec91cde596562df8e2f47508 ./src/models/runtime.rs
|
17cac849056b677a3872e600a0d99218fb383ad5ec91cde596562df8e2f47508 ./src/models/runtime.rs
|
||||||
41b85cff6fd234c8d6ea007b2c3c22df5fee5107dcd57c64dcc94a356ff70c12 ./src/models/settings_api.rs
|
41b85cff6fd234c8d6ea007b2c3c22df5fee5107dcd57c64dcc94a356ff70c12 ./src/models/settings_api.rs
|
||||||
c4de6101de85d9d7e6814b2504b87d9216561e3d9e274054f081f505fb152140 ./src/models/temporary_thermostat.rs
|
c4de6101de85d9d7e6814b2504b87d9216561e3d9e274054f081f505fb152140 ./src/models/temporary_thermostat.rs
|
||||||
79cb16a9f12e8a20fcf0acc8649d771b8a76a05503c0372443d29003536c1bcb ./src/models/zone.rs
|
2b861f3b37274f4d0b9713706ae959220935bdaf63afc513329d1c48ac0f4dd9 ./src/models/zone.rs
|
||||||
4c1f676eaf0b6da45b4d4b090ae3ac862fb97c1f9e53284a1640767ae9591d8f ./src/notifications.rs
|
4c1f676eaf0b6da45b4d4b090ae3ac862fb97c1f9e53284a1640767ae9591d8f ./src/notifications.rs
|
||||||
7746bca683809b82529ed152272ae5c0cd3971758a02e076def58f770e7adf10 ./src/protocol/crypto.rs
|
7746bca683809b82529ed152272ae5c0cd3971758a02e076def58f770e7adf10 ./src/protocol/crypto.rs
|
||||||
578b9f39110abbeb98323044b6ae1a941733c82021afeb4d7ff78ad4256b5aa5 ./src/protocol/gree.rs
|
578b9f39110abbeb98323044b6ae1a941733c82021afeb4d7ff78ad4256b5aa5 ./src/protocol/gree.rs
|
||||||
@@ -207,16 +208,16 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
|
|||||||
d8a602ed8906d77593bd140315ce1bd56716038e789f53ff31762e9b3cb7220d ./src/state.rs
|
d8a602ed8906d77593bd140315ce1bd56716038e789f53ff31762e9b3cb7220d ./src/state.rs
|
||||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||||
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
||||||
b030e61b30435495f492e15fa9daf228968cc1de2982e8f40a626452dbf5b81a ./web/css/styles.css
|
49d7992d8ebaf0b1eddd6538479b00a750897bc0454f3daa37eb01ed914bea8b ./web/css/styles.css
|
||||||
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
|
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
|
||||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||||
bbe640e99447f632383b2eb4b52da580ab95d60cd45cda47f6fae1c2380e5b8a ./web/index.html
|
bb1229db66ab2b510a44308e21796ba01f54c36238f5785ff7fc998dedd09c18 ./web/index.html
|
||||||
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js-dynamic/README.md
|
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js-dynamic/README.md
|
||||||
e7318cd92b76e8a0109dc01adc2431474be17d8bb1488cdf6ffb39dc5450909d ./web/js-dynamic/bootstrap.js
|
e7318cd92b76e8a0109dc01adc2431474be17d8bb1488cdf6ffb39dc5450909d ./web/js-dynamic/bootstrap.js
|
||||||
5d57253a013fd1cb79e4b8701d4be6e394e3f2acbce73dfcd726a86e5878f1a1 ./web/js-dynamic/charts.js
|
5d57253a013fd1cb79e4b8701d4be6e394e3f2acbce73dfcd726a86e5878f1a1 ./web/js-dynamic/charts.js
|
||||||
07629b331bd498e8bea769d577321bbd12d9b076264242706d4ef16165fb22fa ./web/js-dynamic/core.js
|
07629b331bd498e8bea769d577321bbd12d9b076264242706d4ef16165fb22fa ./web/js-dynamic/core.js
|
||||||
ef20fb1e706105d0afdc2fa505d92ea52e43d93c9a687f2a09d381227a464564 ./web/js-dynamic/dashboard.js
|
1bf5b5e295f685b4c54064eb91220a50cb85264f7e90dfcd5bfe5c5e8ac111a2 ./web/js-dynamic/dashboard.js
|
||||||
3a6d2edd64fbdc2a8d468b05816366a9e05df284adea752c42a91e2a3a7bd786 ./web/js-dynamic/entities.js
|
4ddfe365f66b968fae490b19ab321e3497f9fba4e220123edd0a4e0353254d97 ./web/js-dynamic/entities.js
|
||||||
bc1e7c4cccda0447fe805c61e655b773375bf503141195b82d4e95cdf093ec80 ./web/js-dynamic/events.js
|
bc1e7c4cccda0447fe805c61e655b773375bf503141195b82d4e95cdf093ec80 ./web/js-dynamic/events.js
|
||||||
3e2a5f1aab56e918624e6e7af064eecb9e94e554fd4d185715f8ecf3747a6895 ./web/js-dynamic/flows.js
|
3e2a5f1aab56e918624e6e7af064eecb9e94e554fd4d185715f8ecf3747a6895 ./web/js-dynamic/flows.js
|
||||||
1d54ae8919edb61414f9ffc46f85394b29c3c355827120264352cccb32f65e68 ./web/js-dynamic/forms.js
|
1d54ae8919edb61414f9ffc46f85394b29c3c355827120264352cccb32f65e68 ./web/js-dynamic/forms.js
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
Self-hosted controller for GREE-compatible air conditioners with a local Web UI, thermostat zones, schedules, Home Assistant integration, history, notifications and a documented HTTP/WebSocket API.
|
Self-hosted controller for GREE-compatible air conditioners with a local Web UI, thermostat zones, schedules, Home Assistant integration, history, notifications and a documented HTTP/WebSocket API.
|
||||||
|
|
||||||
**Version: 0.16.4**
|
**Version: 0.16.5**
|
||||||
|
|
||||||
Version 0.16.4 fixes disabled Flows that could resume execution after an asynchronous condition evaluation, hides disabled Flow outputs from control plans and schedules, resets timing gates on re-enable, and releases stale Flow thermostat intent.
|
Version 0.16.5 adds a temperature-origin explanation in the zone/thermostat UI, transparent room-sensor diagnostics, and a fail-safe that pauses automatic heating when HA sensor readings cannot be trusted.
|
||||||
|
|
||||||
> [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples.
|
> [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples.
|
||||||
> Built-in API: `/api/*` and `/ws`; interactive Swagger docs: `/api-docs`; OpenAPI JSON: `/api-docs/openapi.json`.
|
> Built-in API: `/api/*` and `/ws`; interactive Swagger docs: `/api-docs`; OpenAPI JSON: `/api-docs/openapi.json`.
|
||||||
@@ -306,7 +306,7 @@ In standalone mode, the Home Assistant connection test checks only `HA_URL` and
|
|||||||
- `home_assistant` — Home Assistant room sensor,
|
- `home_assistant` — Home Assistant room sensor,
|
||||||
- `combined` — weighted GREE + Home Assistant value.
|
- `combined` — weighted GREE + Home Assistant value.
|
||||||
|
|
||||||
For `home_assistant` and `combined`, each zone must set its own `ha_entity_id` room-temperature sensor. Room sensors are independent from the outdoor-temperature source and stale/unavailable room data falls back to the GREE sensor when possible.
|
For `home_assistant` and `combined`, each zone must set its own `ha_entity_id` room-temperature sensor. Room sensors are independent from the outdoor-temperature source. Stale/unavailable room data falls back to GREE as a **display reading**, but automatic **heating is paused** rather than started or boosted from that fallback. Already-running automatic heating is powered off when compressor protection permits; cooling, explicit manual operation and GREE-only zones retain their existing behavior. The guard also applies when `combined` readings disagree beyond the configured maximum difference. The UI includes a temperature-origin help tooltip and a sensor-safeguard status. Where Home Assistant supplies `last_reported`, it takes precedence over `last_updated` for sensor freshness, so a stable temperature repeatedly reported by HA is not incorrectly treated as stale.
|
||||||
|
|
||||||
`HA_OUTDOOR_ENTITY_ID` is the global outdoor-temperature source used by outdoor-temperature assist. A zone may leave `ha_outdoor_entity_id` empty to use that global sensor, or set `ha_outdoor_entity_id` to use a different outdoor sensor only for that zone. If a zone override is unavailable, the controller falls back to the global outdoor source. Global and per-zone outdoor sensors are exposed through the normal alias/history/metric paths and appear in Home Assistant entity suggestions used by Visual Flows.
|
`HA_OUTDOOR_ENTITY_ID` is the global outdoor-temperature source used by outdoor-temperature assist. A zone may leave `ha_outdoor_entity_id` empty to use that global sensor, or set `ha_outdoor_entity_id` to use a different outdoor sensor only for that zone. If a zone override is unavailable, the controller falls back to the global outdoor source. Global and per-zone outdoor sensors are exposed through the normal alias/history/metric paths and appear in Home Assistant entity suggestions used by Visual Flows.
|
||||||
|
|
||||||
|
|||||||
+26
-25
@@ -1,16 +1,16 @@
|
|||||||
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
||||||
2fd3bf1fb7ef548c7abc9b26d1cc4ef470784b8637af65446b845b27a45b547b ./.gitignore
|
2fd3bf1fb7ef548c7abc9b26d1cc4ef470784b8637af65446b845b27a45b547b ./.gitignore
|
||||||
147eed97b308e0c27d6a5ee738a0ee710fa6ba2c6bbf7503df0fe484e0c7f842 ./Cargo.lock
|
795c232a7845a217a5711da4d9547282ba7f42fd160a249df07d631fac83928b ./Cargo.lock
|
||||||
c604af0060aef4e79d2e11fd1e1222a2444cebc835bc50629cff3d2373c5a05a ./Cargo.toml
|
2c57e2334ace9e7e320c97b14e1b0372c881717dad4bf3e6ef6abae252ed8056 ./Cargo.toml
|
||||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||||
75a6652611273ae458cb3509401cdb196d1084db1e49496ecdce448f490e6249 ./README.md
|
eb8dd4d456419762d78e8d426326ac33fa99dbf857ab0f34f52dadc30e8a62be ./README.md
|
||||||
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
|
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
|
||||||
bb9840a0dc2b89d057d1461ae581ee61ebf856e3b6d38bc7d1bdd111eef7c550 ./docs/API.md
|
e961deaaa82be18f9e2933999dda33f55bfc648200e62043b5e8fffdfdd4464c ./docs/API.md
|
||||||
4f9826f1bf3b42ed2d62d8a8e9221b50175b3a9fdda7e9d219351f7a33039257 ./docs/FLOW.md
|
4f9826f1bf3b42ed2d62d8a8e9221b50175b3a9fdda7e9d219351f7a33039257 ./docs/FLOW.md
|
||||||
52c8dc48aed987fd28b2b543af1e5a782efb2a44104ccb0fd6b1740255cf384e ./docs/openapi.json
|
5c24d9f03e5025eda289e6862a7919fe3951992f7120647dc6e0cd0f1f9aa96f ./docs/openapi.json
|
||||||
d8a1b3e34cf8a79ac3296bf600ad47826d639cdef4c1c2c099f8b6b21a391ebd ./flows/simple-heating/igor-simple-heat-v5.flow.json
|
a5ad73f7933031dfaa99aa92e27f10223d53fb84b14ee0a378eb5f10eb80e8c8 ./flows/simple-heating/igor-simple-heat-v5.flow.json
|
||||||
bd9a07b8ca4b090ad0089e9037492cc25c47f3456b97ec08152947314ea904a6 ./flows/simple-heating/jan-pieczara-simple-heat-v5.flow.json
|
9743f4328a18bc9d89d10a9011d13ca201a4f352db2119335a66a2147a9a486d ./flows/simple-heating/jan-pieczara-simple-heat-v5.flow.json
|
||||||
c4e6e3e8df5eb7f9730a79fb90edd155f0510f7bb4862a04b9f35ba649c22376 ./flows/simple-heating/sypialnia-simple-heat-v5.flow.json
|
2d804831a72577e80dc1ec3cf4f86dc8bcf00b1d8c80929896f11da0230b2b07 ./flows/simple-heating/sypialnia-simple-heat-v5.flow.json
|
||||||
77b8becf7a2c16a384ea85721d878b76e2d520549cb4455d9aa806e57e5a2ecd ./ha-addon/.env.example
|
77b8becf7a2c16a384ea85721d878b76e2d520549cb4455d9aa806e57e5a2ecd ./ha-addon/.env.example
|
||||||
0ca6e58170c2b8925194431ebbdb55f0c8b377fec5cd422cd2bd257e2d9328e3 ./ha-addon/Dockerfile
|
0ca6e58170c2b8925194431ebbdb55f0c8b377fec5cd422cd2bd257e2d9328e3 ./ha-addon/Dockerfile
|
||||||
0951696a2c2e66d4d969f8e7f8641184e3012f261583b31074bc38a7a1c0cc1a ./ha-addon/README.md
|
0951696a2c2e66d4d969f8e7f8641184e3012f261583b31074bc38a7a1c0cc1a ./ha-addon/README.md
|
||||||
@@ -24,7 +24,7 @@ b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/hom
|
|||||||
6509286c7b7de638dcc5d40ce53181e65d1eddc5dc36a13306dc6d87220fedbb ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py
|
6509286c7b7de638dcc5d40ce53181e65d1eddc5dc36a13306dc6d87220fedbb ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py
|
||||||
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py
|
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py
|
||||||
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png
|
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png
|
||||||
e8d079ebe63cd2916bd219cd45f583940e3d7ba5769af8deb1038415084a1a35 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
4ecd424fd1969a1bcd81eb85ea5ac50295a0d7691d50013027a7c2e400aec449 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
||||||
c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py
|
c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py
|
||||||
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py
|
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py
|
||||||
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py
|
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py
|
||||||
@@ -32,10 +32,10 @@ cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/hom
|
|||||||
a745cc458c235158d5d70b6a33d8000fd8b40ff93bfe2f2796af0f4b3a9eef9b ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
a745cc458c235158d5d70b6a33d8000fd8b40ff93bfe2f2796af0f4b3a9eef9b ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
||||||
50b7d6fe6329ad4ea6bee9612ab141c2f73f8382fcc2af0bb076a8e8d4dd4465 ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json
|
50b7d6fe6329ad4ea6bee9612ab141c2f73f8382fcc2af0bb076a8e8d4dd4465 ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json
|
||||||
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json
|
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json
|
||||||
904b21f54c7af0f59d91016d8513baaea5e927a349e18207f4290c51d7f2be8a ./ha-addon/repository/gree-controller/CHANGELOG.md
|
a4ae5bfa385365ef5db5332f2635a77e212a82e00a3cf473c5617ad134d3133e ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||||
50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md
|
50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md
|
||||||
e3834bf712d4cd2acc715d8c85db96dd6e2fb3fbb758dac537f6034c337b3745 ./ha-addon/repository/gree-controller/README.md
|
e3834bf712d4cd2acc715d8c85db96dd6e2fb3fbb758dac537f6034c337b3745 ./ha-addon/repository/gree-controller/README.md
|
||||||
efe06cb35044b19474ae11e5ef42e3201787ff1d164087cdbdb845a5ad6a16d8 ./ha-addon/repository/gree-controller/config.yaml
|
1fa36a0a0a3219604073950cd936c68fa6de1f51c031443ab22bf66596e0733f ./ha-addon/repository/gree-controller/config.yaml
|
||||||
3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png
|
3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png
|
||||||
0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png
|
0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png
|
||||||
2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml
|
2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml
|
||||||
@@ -45,8 +45,8 @@ cea12bc74c066a588a5ef0ef7bad9d4906bdbcea99f2d275429b0eefc94b4b5f ./ha-addon/run
|
|||||||
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
|
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
|
||||||
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
|
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
|
||||||
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
||||||
4bf92ee5627be67b66d76a6106b224159f95071f0aa2a727cd964e113bc3b32a ./lang/en.json
|
f3c1286c3758a98b6a2945562753262e9083a6c638a1dcae556c3e978b4a69af ./lang/en.json
|
||||||
1e056cd915ba2e808db1287c99c06bbbbb3c98e5008481471b895e6ec20ea43a ./lang/pl.json
|
ac6e6285a6153c5d8ce3c45d81ca6fe6c688505957e142e79e0ec0b496c400f3 ./lang/pl.json
|
||||||
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
||||||
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
||||||
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
||||||
@@ -102,6 +102,7 @@ e00d211e3885e30d7fed1e43b44e6fdad40a67019060156c0641816a93e3365f ./scripts/netw
|
|||||||
b1e7a7a7a1d6e21012808818ff1478f5116a3dc4c7da3650b57a719ca9df7873 ./scripts/test_flow_block_help_ui.js
|
b1e7a7a7a1d6e21012808818ff1478f5116a3dc4c7da3650b57a719ca9df7873 ./scripts/test_flow_block_help_ui.js
|
||||||
e3825cfd9e6e149fd227c521d886dc116170f2553d4d7639d61874267f257b6e ./scripts/test_flow_disable_ui.js
|
e3825cfd9e6e149fd227c521d886dc116170f2553d4d7639d61874267f257b6e ./scripts/test_flow_disable_ui.js
|
||||||
2942b34d82d06c950fbf167442fd1a25185de193055e02f2ce8dff62c2039add ./scripts/test_simple_heating_ui.js
|
2942b34d82d06c950fbf167442fd1a25185de193055e02f2ce8dff62c2039add ./scripts/test_simple_heating_ui.js
|
||||||
|
9bef00c3a0956c5c893eff6cf1fee9681140a1e76da630fa5a347bce19f07981 ./scripts/test_temperature_explanation_ui.js
|
||||||
b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/update.sh
|
b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/update.sh
|
||||||
c260a6d291ad913f6aca60f23a0acf6928d68cfb0545b2a698d58c5a95fcf934 ./scripts/verify_simple_heating.py
|
c260a6d291ad913f6aca60f23a0acf6928d68cfb0545b2a698d58c5a95fcf934 ./scripts/verify_simple_heating.py
|
||||||
c32caf3ac7b521324d1d27233c271374ccf4efc3d243544ece727372c792e14a ./src/api.rs
|
c32caf3ac7b521324d1d27233c271374ccf4efc3d243544ece727372c792e14a ./src/api.rs
|
||||||
@@ -126,7 +127,7 @@ b777e7fbeccef699e11ffd16bb18633843c99afecacce2221280e54b09f6d2da ./src/api/publ
|
|||||||
fd08162d0f9287700196028ad0ca2aec7c242238056b0a228ca5bdcb319ece4e ./src/api/settings.rs
|
fd08162d0f9287700196028ad0ca2aec7c242238056b0a228ca5bdcb319ece4e ./src/api/settings.rs
|
||||||
7c974a98c72092886247f67c3a1c62736898d4b279ceb9c4c306690a3c6e4294 ./src/api/system.rs
|
7c974a98c72092886247f67c3a1c62736898d4b279ceb9c4c306690a3c6e4294 ./src/api/system.rs
|
||||||
3b0124a76dbc654584bcf186d43f89d20b6e7fd980ce0ffbf5a9e8eced5ec7f6 ./src/api/websocket.rs
|
3b0124a76dbc654584bcf186d43f89d20b6e7fd980ce0ffbf5a9e8eced5ec7f6 ./src/api/websocket.rs
|
||||||
eaa4070afbc19686d5e9bdb2d24fb2c7fc4bf1f90dc1c4f12fef17017d4cb975 ./src/api/zones.rs
|
79076544f4145c0eb120d544e9fab767b5c7ae625c2db007ee8be8a06bb0b85e ./src/api/zones.rs
|
||||||
32fa38d4315682cd3ef3ecdc91e571c2729b5e68b6f2bff965e43de3fdd2aeed ./src/config.rs
|
32fa38d4315682cd3ef3ecdc91e571c2729b5e68b6f2bff965e43de3fdd2aeed ./src/config.rs
|
||||||
de0ce7807aebe4c0e325fbef01855e7fc1cfd117529f53f04a416fda3997673a ./src/db.rs
|
de0ce7807aebe4c0e325fbef01855e7fc1cfd117529f53f04a416fda3997673a ./src/db.rs
|
||||||
87f1614a80addde69b619421187526d7f86f893c0ac18998854f11675d6e7cca ./src/db/climate.rs
|
87f1614a80addde69b619421187526d7f86f893c0ac18998854f11675d6e7cca ./src/db/climate.rs
|
||||||
@@ -145,7 +146,7 @@ f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_
|
|||||||
e6d6bf39e254bd2779d3bcb31d966aa3f6688ebfa91b4088b70fd796cad66596 ./src/engine/automations.rs
|
e6d6bf39e254bd2779d3bcb31d966aa3f6688ebfa91b4088b70fd796cad66596 ./src/engine/automations.rs
|
||||||
1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs
|
1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs
|
||||||
357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs
|
357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs
|
||||||
62a15ea757c53796149e670863dae1b4e1a16ceb6ef88ed84ebd3089c0b9880d ./src/engine/control_plan.rs
|
c6b9651090e89fe0436826f66ae56825bf1e4b209ef8a27beb22fb1deaaa8c36 ./src/engine/control_plan.rs
|
||||||
7b48a33343d519a2ff2d81e9d757eb56a0c1beacfc0b7b8ef61f49c2ad363f9a ./src/engine/deadlines.rs
|
7b48a33343d519a2ff2d81e9d757eb56a0c1beacfc0b7b8ef61f49c2ad363f9a ./src/engine/deadlines.rs
|
||||||
519f67ab805f61afea107215f5df50562fa1669a48334cc015570952e25e773a ./src/engine/energy.rs
|
519f67ab805f61afea107215f5df50562fa1669a48334cc015570952e25e773a ./src/engine/energy.rs
|
||||||
e350aa63f3bb14ca887eaaa7555edbeae00fb584cbcaf552cc972f0c8918f5d8 ./src/engine/groups.rs
|
e350aa63f3bb14ca887eaaa7555edbeae00fb584cbcaf552cc972f0c8918f5d8 ./src/engine/groups.rs
|
||||||
@@ -157,13 +158,13 @@ c73691c3637465ab0f79aecf7f5428d0317799a8ab5329ed564cf57963bbe86e ./src/engine/r
|
|||||||
48a496c3935f886a90b90461c38a7aa31ebc40c6482d6feaeb2be47bc2387625 ./src/engine/schedules.rs
|
48a496c3935f886a90b90461c38a7aa31ebc40c6482d6feaeb2be47bc2387625 ./src/engine/schedules.rs
|
||||||
9adfcb3c3dffbaa870b314904bd1995e7b78c3f12febd69e23433179e4bf3fba ./src/engine/simple_heat.rs
|
9adfcb3c3dffbaa870b314904bd1995e7b78c3f12febd69e23433179e4bf3fba ./src/engine/simple_heat.rs
|
||||||
2694363e607b7f4407ed07c24019c457f0e0d093cb336ee2dc5cf77487c528f6 ./src/engine/targets.rs
|
2694363e607b7f4407ed07c24019c457f0e0d093cb336ee2dc5cf77487c528f6 ./src/engine/targets.rs
|
||||||
d62ac5e97a36bbdb31bc23871ea7de73d8ac64a1e8203153d9faff0b1a075bc9 ./src/engine/temperature.rs
|
b35d7e96e50d40bf26be2e47e1fa1d393f4c07caeac41f3595f5d595579e55f7 ./src/engine/temperature.rs
|
||||||
833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs
|
833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs
|
||||||
4160811f47eca6e75970fe6c6b95198f86a4b97b7050531c19a19efb3d76d97d ./src/engine/tests.rs
|
f563f2da2409bf291d67daa1b2b2fd6c7a463a3a11343afb08131d20cd26cd69 ./src/engine/tests.rs
|
||||||
07c3c462884344f018654267de3d454d895343b4699e34635e4d1ec88546df0a ./src/engine/zone_actions.rs
|
63ded0ec62833699cbb7fcc6e4189ef19feafc23b9838d8fe9ba98889feb3e67 ./src/engine/zone_actions.rs
|
||||||
e246b6d6e3126941b989735a27e5fbe88dcaf351420aaaa060bb3761620d8244 ./src/engine/zone_control.rs
|
38256ec0e5ee735aa2f7ad7f8daaf2aa96077ea16a6c2cbceac43c4fe2bf8aa5 ./src/engine/zone_control.rs
|
||||||
bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs
|
bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs
|
||||||
d681f2aa53c39f37411834c300e9200299f94ee4ea021f44f70a3ea1774bd698 ./src/home_assistant.rs
|
8e181f29119ef6c32ee3821fe220ade97298c105c681c41466af0c16e539b7db ./src/home_assistant.rs
|
||||||
bcd14e4d1af3ca580f3b95bcd86164c40f762511e1b8e2501cb8a598a6d8adf4 ./src/influxdb.rs
|
bcd14e4d1af3ca580f3b95bcd86164c40f762511e1b8e2501cb8a598a6d8adf4 ./src/influxdb.rs
|
||||||
da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb/codec.rs
|
da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb/codec.rs
|
||||||
2d36405912646b30b1a1a37a7a4e18c2104ab22e865b593316a609dda884aec2 ./src/influxdb/query.rs
|
2d36405912646b30b1a1a37a7a4e18c2104ab22e865b593316a609dda884aec2 ./src/influxdb/query.rs
|
||||||
@@ -180,7 +181,7 @@ aeab5b5acf35d38a54fd1d82e964fe56635ade69c0149ad423df317095d01555 ./src/models/f
|
|||||||
17cac849056b677a3872e600a0d99218fb383ad5ec91cde596562df8e2f47508 ./src/models/runtime.rs
|
17cac849056b677a3872e600a0d99218fb383ad5ec91cde596562df8e2f47508 ./src/models/runtime.rs
|
||||||
41b85cff6fd234c8d6ea007b2c3c22df5fee5107dcd57c64dcc94a356ff70c12 ./src/models/settings_api.rs
|
41b85cff6fd234c8d6ea007b2c3c22df5fee5107dcd57c64dcc94a356ff70c12 ./src/models/settings_api.rs
|
||||||
c4de6101de85d9d7e6814b2504b87d9216561e3d9e274054f081f505fb152140 ./src/models/temporary_thermostat.rs
|
c4de6101de85d9d7e6814b2504b87d9216561e3d9e274054f081f505fb152140 ./src/models/temporary_thermostat.rs
|
||||||
79cb16a9f12e8a20fcf0acc8649d771b8a76a05503c0372443d29003536c1bcb ./src/models/zone.rs
|
2b861f3b37274f4d0b9713706ae959220935bdaf63afc513329d1c48ac0f4dd9 ./src/models/zone.rs
|
||||||
4c1f676eaf0b6da45b4d4b090ae3ac862fb97c1f9e53284a1640767ae9591d8f ./src/notifications.rs
|
4c1f676eaf0b6da45b4d4b090ae3ac862fb97c1f9e53284a1640767ae9591d8f ./src/notifications.rs
|
||||||
7746bca683809b82529ed152272ae5c0cd3971758a02e076def58f770e7adf10 ./src/protocol/crypto.rs
|
7746bca683809b82529ed152272ae5c0cd3971758a02e076def58f770e7adf10 ./src/protocol/crypto.rs
|
||||||
578b9f39110abbeb98323044b6ae1a941733c82021afeb4d7ff78ad4256b5aa5 ./src/protocol/gree.rs
|
578b9f39110abbeb98323044b6ae1a941733c82021afeb4d7ff78ad4256b5aa5 ./src/protocol/gree.rs
|
||||||
@@ -206,16 +207,16 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
|
|||||||
d8a602ed8906d77593bd140315ce1bd56716038e789f53ff31762e9b3cb7220d ./src/state.rs
|
d8a602ed8906d77593bd140315ce1bd56716038e789f53ff31762e9b3cb7220d ./src/state.rs
|
||||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||||
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
||||||
b030e61b30435495f492e15fa9daf228968cc1de2982e8f40a626452dbf5b81a ./web/css/styles.css
|
49d7992d8ebaf0b1eddd6538479b00a750897bc0454f3daa37eb01ed914bea8b ./web/css/styles.css
|
||||||
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
|
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
|
||||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||||
bbe640e99447f632383b2eb4b52da580ab95d60cd45cda47f6fae1c2380e5b8a ./web/index.html
|
bb1229db66ab2b510a44308e21796ba01f54c36238f5785ff7fc998dedd09c18 ./web/index.html
|
||||||
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js-dynamic/README.md
|
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js-dynamic/README.md
|
||||||
e7318cd92b76e8a0109dc01adc2431474be17d8bb1488cdf6ffb39dc5450909d ./web/js-dynamic/bootstrap.js
|
e7318cd92b76e8a0109dc01adc2431474be17d8bb1488cdf6ffb39dc5450909d ./web/js-dynamic/bootstrap.js
|
||||||
5d57253a013fd1cb79e4b8701d4be6e394e3f2acbce73dfcd726a86e5878f1a1 ./web/js-dynamic/charts.js
|
5d57253a013fd1cb79e4b8701d4be6e394e3f2acbce73dfcd726a86e5878f1a1 ./web/js-dynamic/charts.js
|
||||||
07629b331bd498e8bea769d577321bbd12d9b076264242706d4ef16165fb22fa ./web/js-dynamic/core.js
|
07629b331bd498e8bea769d577321bbd12d9b076264242706d4ef16165fb22fa ./web/js-dynamic/core.js
|
||||||
ef20fb1e706105d0afdc2fa505d92ea52e43d93c9a687f2a09d381227a464564 ./web/js-dynamic/dashboard.js
|
1bf5b5e295f685b4c54064eb91220a50cb85264f7e90dfcd5bfe5c5e8ac111a2 ./web/js-dynamic/dashboard.js
|
||||||
3a6d2edd64fbdc2a8d468b05816366a9e05df284adea752c42a91e2a3a7bd786 ./web/js-dynamic/entities.js
|
4ddfe365f66b968fae490b19ab321e3497f9fba4e220123edd0a4e0353254d97 ./web/js-dynamic/entities.js
|
||||||
bc1e7c4cccda0447fe805c61e655b773375bf503141195b82d4e95cdf093ec80 ./web/js-dynamic/events.js
|
bc1e7c4cccda0447fe805c61e655b773375bf503141195b82d4e95cdf093ec80 ./web/js-dynamic/events.js
|
||||||
3e2a5f1aab56e918624e6e7af064eecb9e94e554fd4d185715f8ecf3747a6895 ./web/js-dynamic/flows.js
|
3e2a5f1aab56e918624e6e7af064eecb9e94e554fd4d185715f8ecf3747a6895 ./web/js-dynamic/flows.js
|
||||||
1d54ae8919edb61414f9ffc46f85394b29c3c355827120264352cccb32f65e68 ./web/js-dynamic/forms.js
|
1d54ae8919edb61414f9ffc46f85394b29c3c355827120264352cccb32f65e68 ./web/js-dynamic/forms.js
|
||||||
|
|||||||
@@ -606,6 +606,14 @@ Important rules:
|
|||||||
|
|
||||||
The returned `Zone` also contains runtime state including sensor readings, resolved/effective setpoints, demand, current preset, manual overrides, local Quick Thermostat ownership, temporary session state, device-manual takeover, control owner/source/reason, lockout timestamps and `created_at`/`updated_at`.
|
The returned `Zone` also contains runtime state including sensor readings, resolved/effective setpoints, demand, current preset, manual overrides, local Quick Thermostat ownership, temporary session state, device-manual takeover, control owner/source/reason, lockout timestamps and `created_at`/`updated_at`.
|
||||||
|
|
||||||
|
Temperature diagnostics in `GET /api/zones` and zone WebSocket updates:
|
||||||
|
|
||||||
|
- `control_temperature_source`: `device`, `external`, `combined`, `device_fallback`, `device_discrepancy_fallback`, `unavailable`.
|
||||||
|
- `room_sensor_status`: `not_used`, `ok`, `stale`, `error`, `discrepancy` (empty until the first cycle on older persisted zones).
|
||||||
|
- `heating_sensor_guard_active`: `true` when the thermostat would otherwise fall back to GREE while an automatic heating zone is configured for a Home Assistant room sensor. Automatic heating is paused, pending starts are cancelled, and existing automatic heat is switched OFF when compressor protection permits. The guard clears automatically with a valid room reading. Explicit manual control and GREE-only heating are unchanged.
|
||||||
|
|
||||||
|
For a `combined` sensor strategy, the displayed control temperature is GREE × (1 − `external_sensor_weight`) + room × `external_sensor_weight`, rounded to 0.1°C, unless the difference exceeds `max_sensor_difference`. The Web UI provides this explanation in the zone and thermostat temperature help. Home Assistant sample freshness prefers `last_reported` when supplied and otherwise uses `last_updated`.
|
||||||
|
|
||||||
### `GET /api/zones`
|
### `GET /api/zones`
|
||||||
|
|
||||||
Returns `Zone[]`.
|
Returns `Zone[]`.
|
||||||
|
|||||||
+15
-2
@@ -2,7 +2,7 @@
|
|||||||
"openapi": "3.1.0",
|
"openapi": "3.1.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "GREE Controller API",
|
"title": "GREE Controller API",
|
||||||
"version": "0.16.4",
|
"version": "0.16.5",
|
||||||
"summary": "Local HTTP/WebSocket API for GREE Controller",
|
"summary": "Local HTTP/WebSocket API for GREE Controller",
|
||||||
"description": "Local API used by the GREE Controller Web UI and Home Assistant integration.",
|
"description": "Local API used by the GREE Controller Web UI and Home Assistant integration.",
|
||||||
"license": {
|
"license": {
|
||||||
@@ -6079,7 +6079,7 @@
|
|||||||
},
|
},
|
||||||
"version": {
|
"version": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "0.16.4"
|
"example": "0.16.5"
|
||||||
},
|
},
|
||||||
"uptime_seconds": {
|
"uptime_seconds": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
@@ -7267,6 +7267,19 @@
|
|||||||
"null"
|
"null"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"control_temperature_source": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Temperature source selected this cycle: device, external, combined, device_fallback, device_discrepancy_fallback or unavailable."
|
||||||
|
},
|
||||||
|
"room_sensor_status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["not_used", "ok", "stale", "error", "discrepancy"],
|
||||||
|
"description": "Room sensor diagnostic for the latest zone cycle; empty for legacy zones not yet cycled."
|
||||||
|
},
|
||||||
|
"heating_sensor_guard_active": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "True when automatic heating is paused due to an untrusted room sensor; the unit is turned OFF as compressor protection allows."
|
||||||
|
},
|
||||||
"effective_mode": {
|
"effective_mode": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "gree_controller",
|
"domain": "gree_controller",
|
||||||
"name": "GREE Controller",
|
"name": "GREE Controller",
|
||||||
"version": "0.16.4",
|
"version": "0.16.5",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.16.5
|
||||||
|
|
||||||
|
- Shows an accessible "Where does this temperature come from?" explanation on each zone and dashboard thermostat: weighted GREE/room formula, rounded result, source fallback and sensor status.
|
||||||
|
- Adds fail-safe automatic-heating control: missing, stale or excessively different HA room readings cannot start or boost heat from the GREE fallback. Existing automatic heating turns OFF once compressor minimum-on protection permits. Manual control, cooling and GREE-only configurations are unchanged.
|
||||||
|
- Displays blocked heating in the live thermostat status and "Why the controller works this way" control plan, including logs on guard activation/recovery.
|
||||||
|
- Uses Home Assistant `last_reported` for freshness when available (otherwise `last_updated`), to avoid treating repeatedly reported stable readings as stale.
|
||||||
|
- Extends the zone API diagnostics and ships Rust/UI regression tests plus the three day/night Flow examples.
|
||||||
|
|
||||||
## 0.16.4
|
## 0.16.4
|
||||||
|
|
||||||
- Fixes a race in stateful Flow checkpoint saving: no stale condition evaluation can re-enable, overwrite or resurrect a disabled/deleted/recompiled automation.
|
- Fixes a race in stateful Flow checkpoint saving: no stale condition evaluation can re-enable, overwrite or resurrect a disabled/deleted/recompiled automation.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: "GREE Controller"
|
name: "GREE Controller"
|
||||||
version: "0.16.4"
|
version: "0.16.5"
|
||||||
slug: "gree_controller"
|
slug: "gree_controller"
|
||||||
description: "Local GREE HVAC controller with Web UI, API, and Home Assistant integration"
|
description: "Local GREE HVAC controller with Web UI, API, and Home Assistant integration"
|
||||||
url: "https://git.linuxiarz.pl/gru/gree-controller-ha-addon/"
|
url: "https://git.linuxiarz.pl/gru/gree-controller-ha-addon/"
|
||||||
|
|||||||
+20
-1
@@ -132,7 +132,7 @@
|
|||||||
"zones.roomSensorEntity": "Room sensor entity_id",
|
"zones.roomSensorEntity": "Room sensor entity_id",
|
||||||
"zones.roomSensorWeight": "Room sensor weight %",
|
"zones.roomSensorWeight": "Room sensor weight %",
|
||||||
"zones.maxDifference": "Max. sensor difference °C",
|
"zones.maxDifference": "Max. sensor difference °C",
|
||||||
"zones.sensorHelp": "Required when the temperature strategy uses Home Assistant. If it becomes unavailable, the controller falls back to the GREE sensor.",
|
"zones.sensorHelp": "Required when the temperature strategy uses Home Assistant. If readings are missing or disagree, GREE is shown as a fallback but automatic heating is paused. Explicit manual control is unaffected.",
|
||||||
"zones.sensorRequired": "Enter the Home Assistant room temperature entity_id for this zone.",
|
"zones.sensorRequired": "Enter the Home Assistant room temperature entity_id for this zone.",
|
||||||
"zones.outdoorSensorEntity": "Outdoor temperature entity_id override",
|
"zones.outdoorSensorEntity": "Outdoor temperature entity_id override",
|
||||||
"zones.outdoorSensorHelp": "Leave empty to use the global Outdoor temperature entity_id. Set another entity_id to use a different outdoor sensor only for this zone. It is used by outdoor-temperature assist and recorded in history/metrics.",
|
"zones.outdoorSensorHelp": "Leave empty to use the global Outdoor temperature entity_id. Set another entity_id to use a different outdoor sensor only for this zone. It is used by outdoor-temperature assist and recorded in history/metrics.",
|
||||||
@@ -145,6 +145,25 @@
|
|||||||
"zones.sourceFallback": "GREE fallback",
|
"zones.sourceFallback": "GREE fallback",
|
||||||
"zones.sourceDiscrepancy": "GREE fallback: sensor mismatch",
|
"zones.sourceDiscrepancy": "GREE fallback: sensor mismatch",
|
||||||
"zones.sourceUnavailable": "unavailable",
|
"zones.sourceUnavailable": "unavailable",
|
||||||
|
"zones.temperatureHelp": "Where does this temperature come from?",
|
||||||
|
"zones.temperatureFormula": "Weighted average: GREE {gree} × {greeWeight}% + room {room} × {roomWeight}% = {used} (rounded to 0.1°C).",
|
||||||
|
"zones.temperatureFromRoom": "Control temperature {room} comes from the room sensor. The GREE reading {gree} does not affect it.",
|
||||||
|
"zones.temperatureFromGree": "Control temperature {gree} comes only from GREE.",
|
||||||
|
"zones.temperatureDiscrepancy": "Sensors differ by more than {limit}. GREE {gree}, room {room}; using GREE as fallback.",
|
||||||
|
"zones.temperatureFallback": "{reason} Temperature {gree} comes from the GREE fallback.",
|
||||||
|
"zones.temperatureUnavailable": "No reliable temperature measurement is available.",
|
||||||
|
"zones.sensorStale": "The room sensor reading is stale.",
|
||||||
|
"zones.sensorUnavailable": "The room sensor is unavailable or returned an error.",
|
||||||
|
"zones.sensorStatusLabel": "sensor: {status}",
|
||||||
|
"zones.sensorStatusOk": "OK",
|
||||||
|
"zones.sensorStatusStale": "stale",
|
||||||
|
"zones.sensorStatusError": "unavailable",
|
||||||
|
"zones.sensorStatusDiscrepancy": "readings disagree",
|
||||||
|
"zones.heatGuardActive": "Heating sensor safeguard active.",
|
||||||
|
"zones.heatGuardHint": "Automatic heating is paused until a valid room reading returns.",
|
||||||
|
"zones.heatGuardPendingOff": "Heating is paused; the controller will turn the unit off when compressor protection allows or retry.",
|
||||||
|
"zones.heatGuardExplanation": "The fallback GREE reading is for display only. Automatic control will not start or boost heating from it.",
|
||||||
|
"plan.reasonRoomSensorGuard": "Automatic heating paused: no trustworthy room sensor reading.",
|
||||||
"schedules.calendar": "Calendar",
|
"schedules.calendar": "Calendar",
|
||||||
"schedules.weekdays": "Weekdays",
|
"schedules.weekdays": "Weekdays",
|
||||||
"schedules.crossMidnight": "Intervals crossing midnight are supported",
|
"schedules.crossMidnight": "Intervals crossing midnight are supported",
|
||||||
|
|||||||
+20
-1
@@ -132,7 +132,7 @@
|
|||||||
"zones.roomSensorEntity": "entity_id czujnika temperatury pokojowej",
|
"zones.roomSensorEntity": "entity_id czujnika temperatury pokojowej",
|
||||||
"zones.roomSensorWeight": "Waga czujnika pokojowego %",
|
"zones.roomSensorWeight": "Waga czujnika pokojowego %",
|
||||||
"zones.maxDifference": "Maks. różnica czujników °C",
|
"zones.maxDifference": "Maks. różnica czujników °C",
|
||||||
"zones.sensorHelp": "Wymagany, gdy strategia temperatury używa Home Assistant. Gdy czujnik będzie niedostępny, kontroler wróci do sensora GREE.",
|
"zones.sensorHelp": "Wymagany, gdy strategia temperatury używa Home Assistant. Gdy odczyt jest niedostępny lub rozbieżny, GREE służy jako odczyt awaryjny, ale automatyczne grzanie zostaje wstrzymane. Sterowanie ręczne nie jest blokowane.",
|
||||||
"zones.sensorRequired": "Podaj entity_id czujnika temperatury pokojowej z Home Assistant dla tej strefy.",
|
"zones.sensorRequired": "Podaj entity_id czujnika temperatury pokojowej z Home Assistant dla tej strefy.",
|
||||||
"zones.outdoorSensorEntity": "Nadpisanie entity_id temperatury zewnętrznej",
|
"zones.outdoorSensorEntity": "Nadpisanie entity_id temperatury zewnętrznej",
|
||||||
"zones.outdoorSensorHelp": "Pozostaw puste, aby użyć globalnego Outdoor temperature entity_id. Wybierz inne entity_id, aby tylko ta strefa używała innego czujnika zewnętrznego. Czujnik jest używany przez wspomaganie temperaturą zewnętrzną oraz zapisywany w historii i metrykach.",
|
"zones.outdoorSensorHelp": "Pozostaw puste, aby użyć globalnego Outdoor temperature entity_id. Wybierz inne entity_id, aby tylko ta strefa używała innego czujnika zewnętrznego. Czujnik jest używany przez wspomaganie temperaturą zewnętrzną oraz zapisywany w historii i metrykach.",
|
||||||
@@ -145,6 +145,25 @@
|
|||||||
"zones.sourceFallback": "fallback GREE",
|
"zones.sourceFallback": "fallback GREE",
|
||||||
"zones.sourceDiscrepancy": "fallback GREE: rozbieżność",
|
"zones.sourceDiscrepancy": "fallback GREE: rozbieżność",
|
||||||
"zones.sourceUnavailable": "niedostępna",
|
"zones.sourceUnavailable": "niedostępna",
|
||||||
|
"zones.temperatureHelp": "Skąd ta temperatura?",
|
||||||
|
"zones.temperatureFormula": "Średnia ważona: GREE {gree} × {greeWeight}% + pokój {room} × {roomWeight}% = {used} (wynik zaokrąglony do 0,1°C).",
|
||||||
|
"zones.temperatureFromRoom": "Temperatura sterująca {room} pochodzi z czujnika pokojowego. Odczyt GREE {gree} nie wpływa na wynik.",
|
||||||
|
"zones.temperatureFromGree": "Temperatura sterująca {gree} pochodzi wyłącznie z GREE.",
|
||||||
|
"zones.temperatureDiscrepancy": "Czujniki różnią się o więcej niż {limit}. GREE {gree}, pokój {room}; awaryjnie wybrano GREE.",
|
||||||
|
"zones.temperatureFallback": "{reason} Temperatura {gree} pochodzi awaryjnie z GREE.",
|
||||||
|
"zones.temperatureUnavailable": "Brak wiarygodnego pomiaru temperatury.",
|
||||||
|
"zones.sensorStale": "Odczyt czujnika pokojowego jest nieaktualny.",
|
||||||
|
"zones.sensorUnavailable": "Czujnik pokojowy jest niedostępny lub zgłosił błąd.",
|
||||||
|
"zones.sensorStatusLabel": "czujnik: {status}",
|
||||||
|
"zones.sensorStatusOk": "OK",
|
||||||
|
"zones.sensorStatusStale": "nieaktualny",
|
||||||
|
"zones.sensorStatusError": "niedostępny",
|
||||||
|
"zones.sensorStatusDiscrepancy": "rozbieżność odczytów",
|
||||||
|
"zones.heatGuardActive": "Zabezpieczenie grzania aktywne.",
|
||||||
|
"zones.heatGuardHint": "Automatyczne grzanie wstrzymane do powrotu prawidłowego odczytu pokojowego.",
|
||||||
|
"zones.heatGuardPendingOff": "Grzanie wstrzymane; sterownik wyłączy jednostkę po spełnieniu ochrony sprężarki lub ponowi próbę.",
|
||||||
|
"zones.heatGuardExplanation": "Odczyt awaryjny GREE służy tylko do prezentacji. Automatyka nie uruchomi ani nie zwiększy grzania na jego podstawie.",
|
||||||
|
"plan.reasonRoomSensorGuard": "Automatyczne grzanie wstrzymane: brak wiarygodnego odczytu czujnika pokojowego.",
|
||||||
"schedules.calendar": "Kalendarz",
|
"schedules.calendar": "Kalendarz",
|
||||||
"schedules.weekdays": "Dni tygodnia",
|
"schedules.weekdays": "Dni tygodnia",
|
||||||
"schedules.crossMidnight": "Obsługa przedziałów przez północ",
|
"schedules.crossMidnight": "Obsługa przedziałów przez północ",
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// Focused UI checks for zone temperature origin and sensor-safety indicators.
|
||||||
|
// Run from the project root: node scripts/test_temperature_explanation_ui.js
|
||||||
|
'use strict';
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const vm = require('node:vm');
|
||||||
|
const dict = JSON.parse(fs.readFileSync('lang/pl.json', 'utf8')).translations;
|
||||||
|
const ctx = vm.createContext({
|
||||||
|
app: { zones: [], devices: [], groups: [] },
|
||||||
|
tr: (key, vars = {}) => String(dict[key] || key).replace(/\{(\w+)\}/g, (match, name) => vars[name] ?? match),
|
||||||
|
fmtTemp: value => value == null ? '--' : `${Number(value).toFixed(1)}°C`,
|
||||||
|
uiIcon: name => `<icon name="${name}"></icon>`,
|
||||||
|
esc: value => String(value ?? '').replace(/[&<>'"]/g, x => ({ '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' })[x]),
|
||||||
|
});
|
||||||
|
vm.runInContext(fs.readFileSync('web/js-dynamic/entities.js', 'utf8'), ctx);
|
||||||
|
|
||||||
|
const combined = {
|
||||||
|
sensor_source: 'combined',
|
||||||
|
control_temperature_source: 'combined',
|
||||||
|
device_temperature: 19,
|
||||||
|
external_temperature: 21,
|
||||||
|
external_sensor_weight: 0.8,
|
||||||
|
current_temperature: 20.6,
|
||||||
|
room_sensor_status: 'ok',
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
const formula = ctx.zoneTemperatureExplanation(combined);
|
||||||
|
assert.match(formula, /GREE 19\.0°C × 20%/);
|
||||||
|
assert.match(formula, /pokój 21\.0°C × 80% = 20\.6°C/);
|
||||||
|
const help = ctx.zoneTemperatureHelp(combined);
|
||||||
|
assert.match(help, /<details/);
|
||||||
|
assert.match(help, /Skąd ta temperatura/);
|
||||||
|
assert.match(help, /aria-label=/);
|
||||||
|
assert.match(ctx.zoneTemperatureExplanation({ ...combined, control_temperature_source: 'external' }), /21\.0°C pochodzi z czujnika pokojowego/);
|
||||||
|
assert.match(ctx.zoneTemperatureExplanation({ ...combined, control_temperature_source: 'device' }), /19\.0°C pochodzi wyłącznie z GREE/);
|
||||||
|
|
||||||
|
const stale = {
|
||||||
|
...combined, control_temperature_source: 'device_fallback', room_sensor_status: 'stale',
|
||||||
|
external_temperature: null, current_temperature: 19, heating_sensor_guard_active: true,
|
||||||
|
};
|
||||||
|
assert.match(ctx.zoneTemperatureExplanation(stale), /nieaktualny/);
|
||||||
|
assert.match(ctx.zoneTemperatureHelp(stale), /nie uruchomi ani nie zwiększy grzania/);
|
||||||
|
assert.match(ctx.zoneHeatingGuardNotice(stale, { power: true, mode: 'heat' }), /ochrony sprężarki/);
|
||||||
|
assert.match(ctx.zoneHeatingGuardNotice(stale, { power: true, mode: 'auto' }), /ochrony sprężarki/);
|
||||||
|
assert.equal(ctx.zoneRuntimeStatusLabel(stale, 'heat', { power: true }), dict['zones.heatGuardActive']);
|
||||||
|
const mismatch = { ...stale, room_sensor_status: 'discrepancy', control_temperature_source: 'device_discrepancy_fallback', external_temperature: 23, max_sensor_difference: 3 };
|
||||||
|
assert.match(ctx.zoneTemperatureExplanation(mismatch), /więcej niż 3\.0°C/);
|
||||||
|
assert.equal(ctx.zoneHeatingGuardNotice({ ...combined, heating_sensor_guard_active: false }, null), '');
|
||||||
|
console.log('OK: weighted formula, control sources, stale/mismatch warning and automatic heat safeguard UI');
|
||||||
@@ -230,6 +230,8 @@ impl ZoneInput {
|
|||||||
external_temperature: None,
|
external_temperature: None,
|
||||||
current_temperature: None,
|
current_temperature: None,
|
||||||
control_temperature_source: "device".into(),
|
control_temperature_source: "device".into(),
|
||||||
|
room_sensor_status: "not_used".into(),
|
||||||
|
heating_sensor_guard_active: false,
|
||||||
active_preset: "comfort".into(),
|
active_preset: "comfort".into(),
|
||||||
manual_preset: None,
|
manual_preset: None,
|
||||||
manual_setpoint: None,
|
manual_setpoint: None,
|
||||||
@@ -385,6 +387,8 @@ async fn update_zone(
|
|||||||
zone.external_temperature = existing.external_temperature;
|
zone.external_temperature = existing.external_temperature;
|
||||||
zone.current_temperature = existing.current_temperature;
|
zone.current_temperature = existing.current_temperature;
|
||||||
zone.control_temperature_source = existing.control_temperature_source;
|
zone.control_temperature_source = existing.control_temperature_source;
|
||||||
|
zone.room_sensor_status = existing.room_sensor_status;
|
||||||
|
zone.heating_sensor_guard_active = existing.heating_sensor_guard_active;
|
||||||
zone.active_preset = existing.active_preset;
|
zone.active_preset = existing.active_preset;
|
||||||
zone.simple_heat = existing.simple_heat.clone();
|
zone.simple_heat = existing.simple_heat.clone();
|
||||||
zone.simple_heat_runtime = existing.simple_heat_runtime.clone();
|
zone.simple_heat_runtime = existing.simple_heat_runtime.clone();
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ pub async fn build_control_plan(state: &AppState) -> Result<ControlPlan, AppErro
|
|||||||
device_setpoint: device
|
device_setpoint: device
|
||||||
.filter(|item| item.power)
|
.filter(|item| item.power)
|
||||||
.map(|item| item.target_temperature),
|
.map(|item| item.target_temperature),
|
||||||
desired_power: automatic_active && !simple_day_off,
|
desired_power: automatic_active && !simple_day_off && !zone.heating_sensor_guard_active,
|
||||||
desired_mode: effective_mode.to_string(),
|
desired_mode: effective_mode.to_string(),
|
||||||
actual_power: device.map(|item| item.power),
|
actual_power: device.map(|item| item.power),
|
||||||
actual_mode: device.map(|item| {
|
actual_mode: device.map(|item| {
|
||||||
@@ -248,6 +248,8 @@ pub async fn build_control_plan(state: &AppState) -> Result<ControlPlan, AppErro
|
|||||||
resume_at: zone.control_resume_at,
|
resume_at: zone.control_resume_at,
|
||||||
control_reason: if emergency_stop_active {
|
control_reason: if emergency_stop_active {
|
||||||
"Emergency stop pauses automatic climate control".into()
|
"Emergency stop pauses automatic climate control".into()
|
||||||
|
} else if zone.heating_sensor_guard_active {
|
||||||
|
format!("Automatic heating paused: room sensor {}", zone.room_sensor_status)
|
||||||
} else if automation_idle {
|
} else if automation_idle {
|
||||||
"Automation idle: no active schedule or explicit thermostat request".into()
|
"Automation idle: no active schedule or explicit thermostat request".into()
|
||||||
} else {
|
} else {
|
||||||
@@ -274,6 +276,8 @@ pub async fn build_control_plan(state: &AppState) -> Result<ControlPlan, AppErro
|
|||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
{
|
{
|
||||||
Some("offline".into())
|
Some("offline".into())
|
||||||
|
} else if zone.heating_sensor_guard_active {
|
||||||
|
Some("heating_room_sensor_guard".into())
|
||||||
} else if automation_idle {
|
} else if automation_idle {
|
||||||
Some("no_active_thermostat_intent".into())
|
Some("no_active_thermostat_intent".into())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -55,6 +55,18 @@ fn select_zone_temperature(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Never infer a new AUTOMATIC heating demand from GREE when the room sensor
|
||||||
|
/// selected by the user is stale, missing or disagrees beyond the configured limit.
|
||||||
|
/// A valid HA-only reading is fine even if GREE is offline. Explicit manual
|
||||||
|
/// ownership and GREE-only zones keep their existing semantics.
|
||||||
|
fn automatic_heating_needs_room_sensor_guard(zone: &Zone, mode: &str) -> bool {
|
||||||
|
mode == "heat"
|
||||||
|
&& zone.enabled
|
||||||
|
&& zone.control_owner == "automation"
|
||||||
|
&& matches!(zone.sensor_source.as_str(), "home_assistant" | "combined")
|
||||||
|
&& !matches!(zone.control_temperature_source.as_str(), "external" | "combined")
|
||||||
|
}
|
||||||
|
|
||||||
fn adjustment_allowed(zone: &Zone) -> bool {
|
fn adjustment_allowed(zone: &Zone) -> bool {
|
||||||
let Some(last) = zone.last_action_at else {
|
let Some(last) = zone.last_action_at else {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -770,6 +770,8 @@ mod tests {
|
|||||||
external_temperature: None,
|
external_temperature: None,
|
||||||
current_temperature: None,
|
current_temperature: None,
|
||||||
control_temperature_source: "device".into(),
|
control_temperature_source: "device".into(),
|
||||||
|
room_sensor_status: "not_used".into(),
|
||||||
|
heating_sensor_guard_active: false,
|
||||||
active_preset: "comfort".into(),
|
active_preset: "comfort".into(),
|
||||||
manual_preset: None,
|
manual_preset: None,
|
||||||
manual_setpoint: None,
|
manual_setpoint: None,
|
||||||
@@ -1502,6 +1504,45 @@ mod tests {
|
|||||||
assert!(!discrepancy);
|
assert!(!discrepancy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn room_sensor_guard_blocks_only_automatic_heating_on_untrusted_fallback() {
|
||||||
|
let mut zone = test_zone("combined");
|
||||||
|
zone.control_temperature_source = "device_fallback".into();
|
||||||
|
assert!(automatic_heating_needs_room_sensor_guard(&zone, "heat"));
|
||||||
|
zone.control_temperature_source = "device_discrepancy_fallback".into();
|
||||||
|
assert!(automatic_heating_needs_room_sensor_guard(&zone, "heat"));
|
||||||
|
zone.control_temperature_source = "unavailable".into();
|
||||||
|
assert!(automatic_heating_needs_room_sensor_guard(&zone, "heat"));
|
||||||
|
|
||||||
|
zone.control_temperature_source = "combined".into();
|
||||||
|
assert!(!automatic_heating_needs_room_sensor_guard(&zone, "heat"));
|
||||||
|
zone.control_temperature_source = "external".into();
|
||||||
|
assert!(!automatic_heating_needs_room_sensor_guard(&zone, "heat"));
|
||||||
|
zone.control_temperature_source = "device_fallback".into();
|
||||||
|
assert!(!automatic_heating_needs_room_sensor_guard(&zone, "cool"));
|
||||||
|
zone.control_owner = "local_thermostat".into();
|
||||||
|
assert!(!automatic_heating_needs_room_sensor_guard(&zone, "heat"));
|
||||||
|
zone.control_owner = "automation".into();
|
||||||
|
zone.sensor_source = "device".into();
|
||||||
|
assert!(!automatic_heating_needs_room_sensor_guard(&zone, "heat"));
|
||||||
|
zone.sensor_source = "home_assistant".into();
|
||||||
|
assert!(automatic_heating_needs_room_sensor_guard(&zone, "heat"));
|
||||||
|
zone.enabled = false;
|
||||||
|
assert!(!automatic_heating_needs_room_sensor_guard(&zone, "heat"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zone_sensor_guard_fields_default_when_loading_existing_zone() {
|
||||||
|
let zone = test_zone("combined");
|
||||||
|
let mut payload = serde_json::to_value(&zone).unwrap();
|
||||||
|
let object = payload.as_object_mut().unwrap();
|
||||||
|
object.remove("room_sensor_status");
|
||||||
|
object.remove("heating_sensor_guard_active");
|
||||||
|
let migrated: Zone = serde_json::from_value(payload).unwrap();
|
||||||
|
assert_eq!(migrated.room_sensor_status, "");
|
||||||
|
assert!(!migrated.heating_sensor_guard_active);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn seasonal_profiles_resolve_independently() {
|
fn seasonal_profiles_resolve_independently() {
|
||||||
let zone = test_zone("device");
|
let zone = test_zone("device");
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ fn zone_runtime_change_affects_control_plan(before: &Zone, after: &Zone) -> bool
|
|||||||
"heat_away_setpoint": zone.heat_away_setpoint,
|
"heat_away_setpoint": zone.heat_away_setpoint,
|
||||||
"current_temperature": zone.current_temperature,
|
"current_temperature": zone.current_temperature,
|
||||||
"control_temperature_source": zone.control_temperature_source,
|
"control_temperature_source": zone.control_temperature_source,
|
||||||
|
"heating_sensor_guard_active": zone.heating_sensor_guard_active,
|
||||||
"manual_preset": zone.manual_preset,
|
"manual_preset": zone.manual_preset,
|
||||||
"manual_setpoint": zone.manual_setpoint,
|
"manual_setpoint": zone.manual_setpoint,
|
||||||
"manual_override_until": zone.manual_override_until,
|
"manual_override_until": zone.manual_override_until,
|
||||||
@@ -111,6 +112,9 @@ fn persist_zone_cycle(
|
|||||||
latest.external_temperature = computed.external_temperature;
|
latest.external_temperature = computed.external_temperature;
|
||||||
latest.current_temperature = computed.current_temperature;
|
latest.current_temperature = computed.current_temperature;
|
||||||
latest.control_temperature_source = computed.control_temperature_source.clone();
|
latest.control_temperature_source = computed.control_temperature_source.clone();
|
||||||
|
latest.room_sensor_status = computed.room_sensor_status.clone();
|
||||||
|
// Guard state is a controller decision, not just a sensor observation; do not
|
||||||
|
// overwrite a newer manual/configuration change with a stale cycle's decision.
|
||||||
latest.updated_at = Utc::now();
|
latest.updated_at = Utc::now();
|
||||||
state.db.save_zone(&latest)?;
|
state.db.save_zone(&latest)?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,6 +206,11 @@ fn refresh_zone_temperature(
|
|||||||
room_sensor_results: &mut HashMap<String, (Option<String>, Result<f64, String>)>,
|
room_sensor_results: &mut HashMap<String, (Option<String>, Result<f64, String>)>,
|
||||||
) -> (String, bool) {
|
) -> (String, bool) {
|
||||||
let previous_source = zone.control_temperature_source.clone();
|
let previous_source = zone.control_temperature_source.clone();
|
||||||
|
let mut room_sensor_status = if zone.sensor_source == "device" {
|
||||||
|
"not_used"
|
||||||
|
} else {
|
||||||
|
"error"
|
||||||
|
};
|
||||||
let device_temperature =
|
let device_temperature =
|
||||||
if device.enabled && device.online && device.communication_failures == 0 {
|
if device.enabled && device.online && device.communication_failures == 0 {
|
||||||
device.current_temperature
|
device.current_temperature
|
||||||
@@ -216,6 +221,7 @@ fn refresh_zone_temperature(
|
|||||||
if matches!(zone.sensor_source.as_str(), "home_assistant" | "combined") {
|
if matches!(zone.sensor_source.as_str(), "home_assistant" | "combined") {
|
||||||
match room_sensor_results.remove(&zone.id) {
|
match room_sensor_results.remove(&zone.id) {
|
||||||
Some((resolved_entity, Ok(value))) => {
|
Some((resolved_entity, Ok(value))) => {
|
||||||
|
room_sensor_status = "ok";
|
||||||
if let Some(entity_id) = resolved_entity.as_deref() {
|
if let Some(entity_id) = resolved_entity.as_deref() {
|
||||||
record_ha_history(
|
record_ha_history(
|
||||||
state,
|
state,
|
||||||
@@ -229,6 +235,11 @@ fn refresh_zone_temperature(
|
|||||||
Some(value)
|
Some(value)
|
||||||
}
|
}
|
||||||
Some((resolved_entity, Err(err))) => {
|
Some((resolved_entity, Err(err))) => {
|
||||||
|
room_sensor_status = if err.contains("Home Assistant sensor is stale:") {
|
||||||
|
"stale"
|
||||||
|
} else {
|
||||||
|
"error"
|
||||||
|
};
|
||||||
if !matches!(
|
if !matches!(
|
||||||
previous_source.as_str(),
|
previous_source.as_str(),
|
||||||
"device_fallback" | "device_discrepancy_fallback"
|
"device_fallback" | "device_discrepancy_fallback"
|
||||||
@@ -262,10 +273,14 @@ fn refresh_zone_temperature(
|
|||||||
};
|
};
|
||||||
let (temperature, source, discrepancy) =
|
let (temperature, source, discrepancy) =
|
||||||
select_zone_temperature(zone, device_temperature, external_temperature);
|
select_zone_temperature(zone, device_temperature, external_temperature);
|
||||||
|
if discrepancy {
|
||||||
|
room_sensor_status = "discrepancy";
|
||||||
|
}
|
||||||
zone.device_temperature = device_temperature;
|
zone.device_temperature = device_temperature;
|
||||||
zone.external_temperature = external_temperature;
|
zone.external_temperature = external_temperature;
|
||||||
zone.current_temperature = temperature;
|
zone.current_temperature = temperature;
|
||||||
zone.control_temperature_source = source;
|
zone.control_temperature_source = source;
|
||||||
|
zone.room_sensor_status = room_sensor_status.into();
|
||||||
zone.updated_at = Utc::now();
|
zone.updated_at = Utc::now();
|
||||||
(previous_source, discrepancy)
|
(previous_source, discrepancy)
|
||||||
}
|
}
|
||||||
@@ -743,6 +758,8 @@ async fn control_zones(state: &AppState) -> Result<()> {
|
|||||||
refresh_control_ownership(&mut zone);
|
refresh_control_ownership(&mut zone);
|
||||||
let effective_mode = effective_mode_owned.as_str();
|
let effective_mode = effective_mode_owned.as_str();
|
||||||
|
|
||||||
|
let was_heating_sensor_guard_active = zone.heating_sensor_guard_active;
|
||||||
|
zone.heating_sensor_guard_active = false;
|
||||||
let (previous_source, discrepancy) = refresh_zone_temperature(
|
let (previous_source, discrepancy) = refresh_zone_temperature(
|
||||||
state,
|
state,
|
||||||
&settings,
|
&settings,
|
||||||
@@ -902,6 +919,66 @@ async fn control_zones(state: &AppState) -> Result<()> {
|
|||||||
zone.active_preset = preset;
|
zone.active_preset = preset;
|
||||||
zone.effective_setpoint = Some(target);
|
zone.effective_setpoint = Some(target);
|
||||||
|
|
||||||
|
if automatic_heating_needs_room_sensor_guard(&zone, effective_mode) {
|
||||||
|
// Failing over to GREE is useful for displaying a temperature, but its
|
||||||
|
// lower reading must never start or boost automatic heating when the
|
||||||
|
// configured room sensor is stale/missing or disagrees excessively.
|
||||||
|
// Physically stop existing automatic heat once compressor protection allows.
|
||||||
|
zone.heating_sensor_guard_active = true;
|
||||||
|
zone.heat_coasting = false;
|
||||||
|
zone.demand = false;
|
||||||
|
zone.demand_since = None;
|
||||||
|
zone.target_alerted_at = None;
|
||||||
|
zone.simple_heat_runtime = SimpleHeatRuntime::default();
|
||||||
|
clear_compressor_pending(&mut zone, true);
|
||||||
|
zone.device_setpoint = if device.power { Some(device.target_temperature) } else { None };
|
||||||
|
if !was_heating_sensor_guard_active {
|
||||||
|
state.log("warn", "zone.heating_sensor_guard", &format!(
|
||||||
|
"Automatic heating paused for {}: room sensor {} (using {} temperature for display)",
|
||||||
|
zone.name, zone.room_sensor_status, zone.control_temperature_source,
|
||||||
|
), json!({
|
||||||
|
"zone_id": zone.id,
|
||||||
|
"room_sensor_status": zone.room_sensor_status,
|
||||||
|
"control_temperature_source": zone.control_temperature_source,
|
||||||
|
"ha_entity_id": zone.ha_entity_id,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if device.power && matches!(device.mode.as_str(), "heat" | "auto")
|
||||||
|
&& device.online && device.communication_failures == 0
|
||||||
|
{
|
||||||
|
let protection = chrono::Duration::seconds(
|
||||||
|
zone.min_on_seconds.max(settings.compressor_protection_seconds).max(30) as i64,
|
||||||
|
);
|
||||||
|
let safe_to_switch_off = !settings.compressor_protection_enabled
|
||||||
|
|| zone.last_power_change_at.is_none_or(|at|
|
||||||
|
Utc::now().signed_duration_since(at) >= protection);
|
||||||
|
if safe_to_switch_off {
|
||||||
|
match send_zone_command_if_owned(state, &zone.id, &zone.device_id, DeviceCommand {
|
||||||
|
power: Some(false), ..Default::default()
|
||||||
|
}).await {
|
||||||
|
Ok(Some(updated_device)) => {
|
||||||
|
if !updated_device.power {
|
||||||
|
zone.last_power_change_at = Some(Utc::now());
|
||||||
|
zone.last_action_at = Some(Utc::now());
|
||||||
|
zone.device_setpoint = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None) => {}
|
||||||
|
Err(err) => state.log("warn", "zone.heating_sensor_guard_off_error", &err.to_string(),
|
||||||
|
json!({"zone_id": zone.id})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
record_zone_history(state, &zone, outdoor_temperature, settings.poll_interval_seconds);
|
||||||
|
publish_persisted_zone_cycle(state, persist_zone_cycle(state, &zone, cycle_started_at)?)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if was_heating_sensor_guard_active {
|
||||||
|
state.log("info", "zone.heating_sensor_guard_released", &format!(
|
||||||
|
"Automatic heating sensor guard cleared for {} (sensor recovered or control configuration changed)", zone.name,
|
||||||
|
), json!({"zone_id": zone.id, "room_sensor_status": zone.room_sensor_status}));
|
||||||
|
}
|
||||||
|
|
||||||
let Some(temp) = zone.current_temperature else {
|
let Some(temp) = zone.current_temperature else {
|
||||||
record_zone_history(
|
record_zone_history(
|
||||||
state,
|
state,
|
||||||
|
|||||||
+33
-11
@@ -227,17 +227,7 @@ pub async fn read_temperature(
|
|||||||
.await
|
.await
|
||||||
.context("invalid Home Assistant JSON")?;
|
.context("invalid Home Assistant JSON")?;
|
||||||
if let Some(limit) = stale_after_seconds.filter(|value| *value > 0) {
|
if let Some(limit) = stale_after_seconds.filter(|value| *value > 0) {
|
||||||
let updated = payload
|
let age = temperature_sample_age_seconds(&payload, chrono::Utc::now())?;
|
||||||
.get("last_updated")
|
|
||||||
.and_then(Value::as_str)
|
|
||||||
.ok_or_else(|| anyhow!("Home Assistant last_updated is missing"))?;
|
|
||||||
let updated = chrono::DateTime::parse_from_rfc3339(updated)
|
|
||||||
.context("invalid Home Assistant last_updated")?
|
|
||||||
.with_timezone(&chrono::Utc);
|
|
||||||
let age = chrono::Utc::now()
|
|
||||||
.signed_duration_since(updated)
|
|
||||||
.num_seconds()
|
|
||||||
.max(0) as u64;
|
|
||||||
if age > limit {
|
if age > limit {
|
||||||
bail!("Home Assistant sensor is stale: {age}s old (limit {limit}s)")
|
bail!("Home Assistant sensor is stale: {age}s old (limit {limit}s)")
|
||||||
}
|
}
|
||||||
@@ -256,9 +246,27 @@ pub async fn read_temperature(
|
|||||||
if unit.eq_ignore_ascii_case("°F") || unit.eq_ignore_ascii_case("F") {
|
if unit.eq_ignore_ascii_case("°F") || unit.eq_ignore_ascii_case("F") {
|
||||||
temperature = (temperature - 32.0) * 5.0 / 9.0;
|
temperature = (temperature - 32.0) * 5.0 / 9.0;
|
||||||
}
|
}
|
||||||
|
if !temperature.is_finite() {
|
||||||
|
bail!("Home Assistant temperature must be finite")
|
||||||
|
}
|
||||||
Ok((temperature * 10.0).round() / 10.0)
|
Ok((temperature * 10.0).round() / 10.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn temperature_sample_age_seconds(payload: &Value, now: chrono::DateTime<chrono::Utc>) -> Result<u64> {
|
||||||
|
// last_updated may stay unchanged while a sensor repeatedly reports the same
|
||||||
|
// temperature. Prefer last_reported (when HA provides it) for freshness,
|
||||||
|
// and retain last_updated compatibility with older HA versions.
|
||||||
|
let timestamp = payload
|
||||||
|
.get("last_reported")
|
||||||
|
.and_then(Value::as_str)
|
||||||
|
.or_else(|| payload.get("last_updated").and_then(Value::as_str))
|
||||||
|
.ok_or_else(|| anyhow!("Home Assistant sensor timestamp is missing"))?;
|
||||||
|
let reported_at = chrono::DateTime::parse_from_rfc3339(timestamp)
|
||||||
|
.context("invalid Home Assistant sensor timestamp")?
|
||||||
|
.with_timezone(&chrono::Utc);
|
||||||
|
Ok(now.signed_duration_since(reported_at).num_seconds().max(0) as u64)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -279,6 +287,20 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unchanged_temperature_can_be_fresh_when_last_reported_is_recent() {
|
||||||
|
let now = chrono::Utc::now();
|
||||||
|
let payload = serde_json::json!({
|
||||||
|
"last_updated": (now - chrono::Duration::minutes(16)).to_rfc3339(),
|
||||||
|
"last_reported": (now - chrono::Duration::seconds(45)).to_rfc3339(),
|
||||||
|
});
|
||||||
|
assert_eq!(temperature_sample_age_seconds(&payload, now).unwrap(), 45);
|
||||||
|
let old_payload = serde_json::json!({
|
||||||
|
"last_updated": (now - chrono::Duration::minutes(16)).to_rfc3339(),
|
||||||
|
});
|
||||||
|
assert_eq!(temperature_sample_age_seconds(&old_payload, now).unwrap(), 960);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn aliases_never_replace_real_home_assistant_entity_ids() {
|
fn aliases_never_replace_real_home_assistant_entity_ids() {
|
||||||
let settings = settings();
|
let settings = settings();
|
||||||
|
|||||||
@@ -114,6 +114,14 @@ pub struct Zone {
|
|||||||
/// `device`, `external`, `combined`, `device_fallback`, or `device_discrepancy_fallback`.
|
/// `device`, `external`, `combined`, `device_fallback`, or `device_discrepancy_fallback`.
|
||||||
#[serde(default = "default_control_temperature_source")]
|
#[serde(default = "default_control_temperature_source")]
|
||||||
pub control_temperature_source: String,
|
pub control_temperature_source: String,
|
||||||
|
/// Room sensor diagnostic from the latest zone cycle: not_used, ok, stale, error or discrepancy.
|
||||||
|
/// It is intentionally distinct from the selected control temperature source.
|
||||||
|
#[serde(default)]
|
||||||
|
pub room_sensor_status: String,
|
||||||
|
/// Automatic heating is suspended because a zone configured to use a room sensor
|
||||||
|
/// currently has no trustworthy room-sensor reading. Manual operation is unaffected.
|
||||||
|
#[serde(default)]
|
||||||
|
pub heating_sensor_guard_active: bool,
|
||||||
/// Effective profile currently used by the zone: comfort/sleep/away/custom.
|
/// Effective profile currently used by the zone: comfort/sleep/away/custom.
|
||||||
#[serde(default = "default_active_preset")]
|
#[serde(default = "default_active_preset")]
|
||||||
pub active_preset: String,
|
pub active_preset: String,
|
||||||
|
|||||||
@@ -5128,6 +5128,64 @@ body.simulation-standalone [data-view="simulation"] {
|
|||||||
letter-spacing: -.03em;
|
letter-spacing: -.03em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.zone-temperature-help {
|
||||||
|
margin-top: 5px;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone-temperature-help summary {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone-temperature-help summary::-webkit-details-marker { display: none; }
|
||||||
|
|
||||||
|
.zone-temperature-help summary .ui-icon { width: 14px; height: 14px; flex: 0 0 auto; }
|
||||||
|
|
||||||
|
.zone-temperature-help summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
|
||||||
|
|
||||||
|
.zone-temperature-help-content {
|
||||||
|
margin-top: 6px;
|
||||||
|
padding: 9px 10px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 9px;
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--text);
|
||||||
|
line-height: 1.5;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone-temperature-help-content p { margin: 6px 0 0; }
|
||||||
|
|
||||||
|
.zone-temperature-guard-detail { margin: 6px 0 0; color: var(--warning); }
|
||||||
|
|
||||||
|
.zone-heating-sensor-guard {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 9px 10px;
|
||||||
|
margin: 0;
|
||||||
|
border: 1px solid var(--warning);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: var(--warning-soft);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zone-heating-sensor-guard .ui-icon { width: 16px; height: 16px; flex: 0 0 auto; color: var(--warning); }
|
||||||
|
|
||||||
|
.zone-heating-sensor-guard strong { font-weight: 700; }
|
||||||
|
|
||||||
.zone-config-runtime {
|
.zone-config-runtime {
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-items: end;
|
justify-items: end;
|
||||||
|
|||||||
+1
-1
@@ -1621,7 +1621,7 @@
|
|||||||
data-i18n="zones.maxDifference">Max. sensor difference °C</span><input type="text" inputmode="decimal"
|
data-i18n="zones.maxDifference">Max. sensor difference °C</span><input type="text" inputmode="decimal"
|
||||||
step="0.1" min="0.1" max="20" name="max_sensor_difference" value="3.0"></label></div>
|
step="0.1" min="0.1" max="20" name="max_sensor_difference" value="3.0"></label></div>
|
||||||
<p class="field-note" data-i18n="zones.sensorHelp">This room sensor is required when the temperature strategy
|
<p class="field-note" data-i18n="zones.sensorHelp">This room sensor is required when the temperature strategy
|
||||||
uses Home Assistant. If it becomes unavailable, the controller falls back to the GREE sensor.</p>
|
uses Home Assistant. Missing or disagreeing readings pause automatic heating; manual control is unaffected.</p>
|
||||||
</div>
|
</div>
|
||||||
<label><span data-i18n="zones.outdoorSensorEntity">Outdoor temperature entity_id override</span><input
|
<label><span data-i18n="zones.outdoorSensorEntity">Outdoor temperature entity_id override</span><input
|
||||||
name="ha_outdoor_entity_id" list="haEntitySuggestions" placeholder="sensor.outdoor_temperature"></label>
|
name="ha_outdoor_entity_id" list="haEntitySuggestions" placeholder="sensor.outdoor_temperature"></label>
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ function planBlockedReasonLabel(reason) {
|
|||||||
manual_override: 'plan.reasonManual',
|
manual_override: 'plan.reasonManual',
|
||||||
zone_disabled: 'plan.reasonDisabled',
|
zone_disabled: 'plan.reasonDisabled',
|
||||||
offline: 'plan.reasonOffline',
|
offline: 'plan.reasonOffline',
|
||||||
|
heating_room_sensor_guard: 'plan.reasonRoomSensorGuard',
|
||||||
no_active_thermostat_intent: 'plan.reasonNoIntent',
|
no_active_thermostat_intent: 'plan.reasonNoIntent',
|
||||||
}[reason];
|
}[reason];
|
||||||
return key ? tr(key) : reason;
|
return key ? tr(key) : reason;
|
||||||
|
|||||||
@@ -331,6 +331,59 @@ function zoneControlSourceLabel(source) {
|
|||||||
})[source] || source || tr('zones.sourceUnavailable');
|
})[source] || source || tr('zones.sourceUnavailable');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function zoneTemperatureExplanation(zone) {
|
||||||
|
const gree = fmtTemp(zone.device_temperature);
|
||||||
|
const room = fmtTemp(zone.external_temperature);
|
||||||
|
const used = fmtTemp(zone.current_temperature);
|
||||||
|
const source = zone.control_temperature_source;
|
||||||
|
if (source === 'combined' && zone.device_temperature != null && zone.external_temperature != null) {
|
||||||
|
const roomWeight = Math.round(Math.max(0, Math.min(1, Number(zone.external_sensor_weight ?? 0.4))) * 100);
|
||||||
|
return tr('zones.temperatureFormula', { gree, room, used, greeWeight: 100 - roomWeight, roomWeight });
|
||||||
|
}
|
||||||
|
if (source === 'external') {
|
||||||
|
return tr('zones.temperatureFromRoom', { room, gree });
|
||||||
|
}
|
||||||
|
if (source === 'device') {
|
||||||
|
return tr('zones.temperatureFromGree', { gree });
|
||||||
|
}
|
||||||
|
if (source === 'device_discrepancy_fallback') {
|
||||||
|
return tr('zones.temperatureDiscrepancy', { gree, room, limit: fmtTemp(zone.max_sensor_difference) });
|
||||||
|
}
|
||||||
|
if (source === 'device_fallback') {
|
||||||
|
const reason = zone.room_sensor_status === 'stale'
|
||||||
|
? tr('zones.sensorStale')
|
||||||
|
: tr('zones.sensorUnavailable');
|
||||||
|
return tr('zones.temperatureFallback', { gree, reason });
|
||||||
|
}
|
||||||
|
return tr('zones.temperatureUnavailable');
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoneTemperatureHelp(zone) {
|
||||||
|
const explanation = zoneTemperatureExplanation(zone);
|
||||||
|
const guarded = zone.heating_sensor_guard_active === true;
|
||||||
|
const guardDetail = guarded ? tr('zones.heatGuardExplanation') : '';
|
||||||
|
const sensorStatus = zoneRoomSensorStatusText(zone);
|
||||||
|
const tooltip = [explanation, sensorStatus, guardDetail].filter(Boolean).join(' ');
|
||||||
|
return `<details class="zone-temperature-help"><summary title="${esc(tooltip)}" aria-label="${esc(tr('zones.temperatureHelp'))}">${uiIcon('system-info')}<span>${esc(tr('zones.temperatureHelp'))}</span></summary><div class="zone-temperature-help-content">${esc(explanation)}${sensorStatus ? `<p>${esc(sensorStatus)}</p>` : ''}${guarded ? `<p class="zone-temperature-guard-detail">${esc(guardDetail)}</p>` : ''}</div></details>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoneRoomSensorStatusText(zone) {
|
||||||
|
if (!zone || zone.sensor_source === 'device') return '';
|
||||||
|
const key = {
|
||||||
|
ok: 'zones.sensorStatusOk',
|
||||||
|
stale: 'zones.sensorStatusStale',
|
||||||
|
error: 'zones.sensorStatusError',
|
||||||
|
discrepancy: 'zones.sensorStatusDiscrepancy',
|
||||||
|
}[zone.room_sensor_status];
|
||||||
|
return key ? tr('zones.sensorStatusLabel', { status: tr(key) }) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoneHeatingGuardNotice(zone, device) {
|
||||||
|
if (zone.heating_sensor_guard_active !== true) return '';
|
||||||
|
const pending = device?.power && ['heat', 'auto'].includes(device?.mode);
|
||||||
|
return `<p class="zone-heating-sensor-guard" role="status">${uiIcon('system-info')}<span><strong>${esc(tr('zones.heatGuardActive'))}</strong> ${esc(tr(pending ? 'zones.heatGuardPendingOff' : 'zones.heatGuardHint'))}</span></p>`;
|
||||||
|
}
|
||||||
|
|
||||||
function zonePresetLabel(preset) {
|
function zonePresetLabel(preset) {
|
||||||
const key = `preset.${preset || 'comfort'}`;
|
const key = `preset.${preset || 'comfort'}`;
|
||||||
return tr(key) === key ? (preset || 'comfort') : tr(key);
|
return tr(key) === key ? (preset || 'comfort') : tr(key);
|
||||||
@@ -471,6 +524,7 @@ function zoneRuntimeStatusLabel(zone, effectiveMode, device = null) {
|
|||||||
if (zone.device_manual_override === true) return tr('zones.manualDeviceControl');
|
if (zone.device_manual_override === true) return tr('zones.manualDeviceControl');
|
||||||
if (!zone.enabled) return tr('common.disabled');
|
if (!zone.enabled) return tr('common.disabled');
|
||||||
if (zone.local_thermostat_power === false) return tr('zones.localThermostatOff');
|
if (zone.local_thermostat_power === false) return tr('zones.localThermostatOff');
|
||||||
|
if (zone.heating_sensor_guard_active === true) return tr('zones.heatGuardActive');
|
||||||
if ((effectiveMode || 'off') === 'off') {
|
if ((effectiveMode || 'off') === 'off') {
|
||||||
if (zone.control_source === 'automation.flow' && zone.simple_heat && zone.control_owner === 'automation') return tr(device?.power ? 'zones.flowDayPendingOff' : 'zones.flowDayOff');
|
if (zone.control_source === 'automation.flow' && zone.simple_heat && zone.control_owner === 'automation') return tr(device?.power ? 'zones.flowDayPendingOff' : 'zones.flowDayOff');
|
||||||
return tr('zones.waiting');
|
return tr('zones.waiting');
|
||||||
@@ -494,8 +548,9 @@ function zoneCard(zone, detailed = true) {
|
|||||||
const state = zone.enabled ? tr('common.active') : tr('common.disabled');
|
const state = zone.enabled ? tr('common.active') : tr('common.disabled');
|
||||||
const sensorDetails = zone.sensor_source === 'device'
|
const sensorDetails = zone.sensor_source === 'device'
|
||||||
? `${tr('zones.greeTemp')}: ${fmtTemp(zone.device_temperature ?? device?.current_temperature)}`
|
? `${tr('zones.greeTemp')}: ${fmtTemp(zone.device_temperature ?? device?.current_temperature)}`
|
||||||
: `${tr('zones.greeTemp')}: ${fmtTemp(zone.device_temperature)} · ${tr('zones.externalTemp')}: ${fmtTemp(zone.external_temperature)} · ${tr('zones.usedSource')}: ${zoneControlSourceLabel(zone.control_temperature_source)}`;
|
: `${tr('zones.greeTemp')}: ${fmtTemp(zone.device_temperature)} · ${tr('zones.externalTemp')}: ${fmtTemp(zone.external_temperature)} · ${tr('zones.usedSource')}: ${zoneControlSourceLabel(zone.control_temperature_source)}${zoneRoomSensorStatusText(zone) ? ` · ${zoneRoomSensorStatusText(zone)}` : ''}`;
|
||||||
const roomTemperature = zone.current_temperature ?? zone.device_temperature ?? device?.current_temperature;
|
const roomTemperature = zone.current_temperature ?? zone.device_temperature ?? device?.current_temperature;
|
||||||
|
const temperatureTooltip = zoneTemperatureExplanation(zone);
|
||||||
const manual = zone.manual_preset || 'auto';
|
const manual = zone.manual_preset || 'auto';
|
||||||
const mode = zone.inherit_house_mode ? 'house' : zone.mode;
|
const mode = zone.inherit_house_mode ? 'house' : zone.mode;
|
||||||
const effectiveMode = zone.effective_mode || (zone.inherit_house_mode ? (app.settings?.house_mode || 'off') : zone.mode) || 'off';
|
const effectiveMode = zone.effective_mode || (zone.inherit_house_mode ? (app.settings?.house_mode || 'off') : zone.mode) || 'off';
|
||||||
@@ -556,7 +611,7 @@ function zoneCard(zone, detailed = true) {
|
|||||||
return `<article class="list-card zone-config-card ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''}" data-zone-config="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
|
return `<article class="list-card zone-config-card ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''}" data-zone-config="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
|
||||||
<div class="list-card-head"><div><span class="eyebrow">${esc(tr('zones.configuration'))}</span><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(tr('groups.group'))}: ${esc(groupText)}</p></div><button type="button" class="enable-toggle ${zone.enabled ? 'active' : ''}" data-action="zone-enabled" data-id="${esc(zone.id)}" data-value="${zone.enabled ? 'false' : 'true'}" aria-label="${esc(tr(zone.enabled ? 'zones.disable' : 'zones.enable'))}" title="${esc(tr('zones.automationToggleHint'))}"><span>${uiIcon(zone.enabled ? 'check' : 'circle')}</span>${esc(state)}</button></div>
|
<div class="list-card-head"><div><span class="eyebrow">${esc(tr('zones.configuration'))}</span><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(tr('groups.group'))}: ${esc(groupText)}</p></div><button type="button" class="enable-toggle ${zone.enabled ? 'active' : ''}" data-action="zone-enabled" data-id="${esc(zone.id)}" data-value="${zone.enabled ? 'false' : 'true'}" aria-label="${esc(tr(zone.enabled ? 'zones.disable' : 'zones.enable'))}" title="${esc(tr('zones.automationToggleHint'))}"><span>${uiIcon(zone.enabled ? 'check' : 'circle')}</span>${esc(state)}</button></div>
|
||||||
<div class="zone-config-status">
|
<div class="zone-config-status">
|
||||||
<div class="zone-config-temperature"><small>${esc(tr('zones.currentStatus'))}</small><div><span>${fmtTemp(roomTemperature)}</span><b>→</b><strong>${Number.isFinite(target) ? `${target.toFixed(1)}°C` : '—'}</strong></div></div>
|
<div class="zone-config-temperature"><small>${esc(tr('zones.currentStatus'))}</small><div><span title="${esc(temperatureTooltip)}">${fmtTemp(roomTemperature)}</span><b>→</b><strong>${Number.isFinite(target) ? `${target.toFixed(1)}°C` : '—'}</strong></div>${zoneTemperatureHelp(zone)}</div>
|
||||||
<div class="zone-config-runtime"><span class="badge ${workingDemand ? 'active' : ''}">${esc(zoneRuntimeStatusLabel(zone, effectiveMode, device))}</span><span>${esc(houseModeLabel(effectiveMode))} · ${esc(zonePresetLabel(displayPreset))}</span><small>${esc(override)}</small><small><strong>${esc(tr('zones.controlOwner'))}:</strong> ${esc(zoneControlOwnerLabel(zone))}${zoneControlOwnerMeta(zone) ? ` · ${esc(zoneControlOwnerMeta(zone))}` : ''}</small></div>
|
<div class="zone-config-runtime"><span class="badge ${workingDemand ? 'active' : ''}">${esc(zoneRuntimeStatusLabel(zone, effectiveMode, device))}</span><span>${esc(houseModeLabel(effectiveMode))} · ${esc(zonePresetLabel(displayPreset))}</span><small>${esc(override)}</small><small><strong>${esc(tr('zones.controlOwner'))}:</strong> ${esc(zoneControlOwnerLabel(zone))}${zoneControlOwnerMeta(zone) ? ` · ${esc(zoneControlOwnerMeta(zone))}` : ''}</small></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="zone-config-grid">
|
<div class="zone-config-grid">
|
||||||
@@ -567,6 +622,7 @@ function zoneCard(zone, detailed = true) {
|
|||||||
<div><small>${esc(tr('zones.heatAnticipation'))}</small><strong>${Number(zone.heat_anticipation_c ?? 0).toFixed(1)}°C</strong></div>
|
<div><small>${esc(tr('zones.heatAnticipation'))}</small><strong>${Number(zone.heat_anticipation_c ?? 0).toFixed(1)}°C</strong></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="sensor-detail">${esc(sensorDetails)}</div>
|
<div class="sensor-detail">${esc(sensorDetails)}</div>
|
||||||
|
${zoneHeatingGuardNotice(zone, device)}
|
||||||
${compressorQueuePanel(zone)}
|
${compressorQueuePanel(zone)}
|
||||||
${manualTakeover}
|
${manualTakeover}
|
||||||
<div class="card-footer"><small>${esc(tr('zones.controlOnDashboard'))}</small><div class="card-menu"><button class="primary" data-action="zone-go-control" data-id="${esc(zone.id)}">${esc(tr('zones.controlNow'))}</button><button data-action="edit-zone" data-id="${esc(zone.id)}">${esc(tr('actions.edit'))}</button><details class="card-overflow"><summary aria-label="${esc(tr('actions.more'))}" title="${esc(tr('actions.more'))}">${uiIcon('more-horizontal')}</summary><div class="card-overflow-menu"><button class="danger" data-action="delete-zone" data-id="${esc(zone.id)}">${esc(tr('actions.delete'))}</button></div></details></div></div>
|
<div class="card-footer"><small>${esc(tr('zones.controlOnDashboard'))}</small><div class="card-menu"><button class="primary" data-action="zone-go-control" data-id="${esc(zone.id)}">${esc(tr('zones.controlNow'))}</button><button data-action="edit-zone" data-id="${esc(zone.id)}">${esc(tr('actions.edit'))}</button><details class="card-overflow"><summary aria-label="${esc(tr('actions.more'))}" title="${esc(tr('actions.more'))}">${uiIcon('more-horizontal')}</summary><div class="card-overflow-menu"><button class="danger" data-action="delete-zone" data-id="${esc(zone.id)}">${esc(tr('actions.delete'))}</button></div></details></div></div>
|
||||||
@@ -577,7 +633,8 @@ function zoneCard(zone, detailed = true) {
|
|||||||
return `<article class="list-card zone-thermostat quick-control-card quick-thermostat-control ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''}" data-zone-card="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
|
return `<article class="list-card zone-thermostat quick-control-card quick-thermostat-control ${workingDemand ? 'demanding' : ''} ${waitingDemand ? 'lockout-waiting' : ''} ${zone.enabled ? '' : 'zone-disabled'} ${deviceManualOverride ? 'manual-takeover' : ''} ${controlGroup ? 'group-controlled' : ''}" data-zone-card="${esc(zone.id)}"${groupControlStyle(visualGroup)}>
|
||||||
<div class="list-card-head"><div><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(zonePresetLabel(displayPreset))}</p></div><div class="zone-quick-actions">${localThermostatPowerControl}${temporaryThermostatControl}</div></div>
|
<div class="list-card-head"><div><h3>${esc(zone.name)}</h3><p>${esc(device?.name || tr('common.noDevice'))} · ${esc(zonePresetLabel(displayPreset))}</p></div><div class="zone-quick-actions">${localThermostatPowerControl}${temporaryThermostatControl}</div></div>
|
||||||
${disabledManualControlNotice}
|
${disabledManualControlNotice}
|
||||||
<div class="thermostat-main"><div><small>${esc(tr('zones.measurement'))}</small><strong>${fmtTemp(roomTemperature)}</strong></div><div class="temperature-control compact"><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="-0.5">${uiIcon('minus')}</button><div class="target-temp compact editable-target" data-temperature-kind="zone" data-id="${esc(zone.id)}" data-value="${Number.isFinite(target) ? target : ''}" tabindex="0" role="button" title="${esc(tr('common.clickTemperatureToEdit'))}">${Number.isFinite(target) ? target.toFixed(1) : '--'}<small>°C</small></div><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="0.5">${uiIcon('plus')}</button></div><div><small>${esc(tr('zones.deviceTarget'))}</small><strong>${fmtTemp(zone.device_setpoint)}</strong></div></div>
|
<div class="thermostat-main"><div><small>${esc(tr('zones.measurement'))}</small><strong title="${esc(temperatureTooltip)}">${fmtTemp(roomTemperature)}</strong>${zoneTemperatureHelp(zone)}</div><div class="temperature-control compact"><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="-0.5">${uiIcon('minus')}</button><div class="target-temp compact editable-target" data-temperature-kind="zone" data-id="${esc(zone.id)}" data-value="${Number.isFinite(target) ? target : ''}" tabindex="0" role="button" title="${esc(tr('common.clickTemperatureToEdit'))}">${Number.isFinite(target) ? target.toFixed(1) : '--'}<small>°C</small></div><button data-action="zone-temperature" data-id="${esc(zone.id)}" data-delta="0.5">${uiIcon('plus')}</button></div><div><small>${esc(tr('zones.deviceTarget'))}</small><strong>${fmtTemp(zone.device_setpoint)}</strong></div></div>
|
||||||
|
${zoneHeatingGuardNotice(zone, device)}
|
||||||
<div class="preset-row quick-control-row quick-control-row-4">
|
<div class="preset-row quick-control-row quick-control-row-4">
|
||||||
${['auto', 'comfort', 'sleep', 'away'].map(preset => `<button class="${manual === preset ? 'active' : ''}" data-action="zone-preset" data-id="${esc(zone.id)}" data-value="${preset}">${esc(preset === 'sleep' ? tr('zones.sleepNow') : zonePresetLabel(preset))}</button>`).join('')}
|
${['auto', 'comfort', 'sleep', 'away'].map(preset => `<button class="${manual === preset ? 'active' : ''}" data-action="zone-preset" data-id="${esc(zone.id)}" data-value="${preset}">${esc(preset === 'sleep' ? tr('zones.sleepNow') : zonePresetLabel(preset))}</button>`).join('')}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user