This commit is contained in:
Mateusz Gruszczyński committed 2026-10-17 20:00:28 +02:00
1 parent ccf85a2fb8
commit f376f0a7b6
32 files changed
+312 -114

No files matched your search

Generated
+1 -1
View File
@@ -719,7 +719,7 @@ dependencies = [
[[package]] [[package]]
name = "gree-controller" name = "gree-controller"
version = "0.16.0" version = "0.16.1"
dependencies = [ dependencies = [
"aes", "aes",
"aes-gcm", "aes-gcm",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "gree-controller" name = "gree-controller"
version = "0.16.0" version = "0.16.1"
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"
+36 -35
View File
@@ -1,17 +1,18 @@
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore 2fd3bf1fb7ef548c7abc9b26d1cc4ef470784b8637af65446b845b27a45b547b ./.gitignore
3c92ae20c070a1db66390a8ec95ed87bc0fed9ec82188e5348fc862ddcbff06e ./Cargo.lock 1ece15d35e56470fbc0e9f0d9b1963f518c81a7b80270f2d2d91e53a4eb463ce ./Cargo.lock
5a7d380c42118a8ab57939bfc99fe15dcf4ad4c1f4bfd5fd808164012ca2c0df ./Cargo.toml 8ad83bc5b719a3c0e058d0d6d28cc5af834f7a6d07d8fdc89c4c8ae804e1eb25 ./Cargo.toml
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
8f2573aeec2240fa85c090b616575a9f5f79acc06ccfe8a98bf216ab13d3176a ./README.md 3da89b24afe096e116b5725a279c2e84b493ea2ead4aa24895647e083c02298f ./README.md
43cc9dbec1deb5c9f2fdddf03c4f19e745014a0f5ef44de556ca75b312b3acd8 ./SOURCE_MANIFEST.sha256 9d39a66e1b041fef40a180e918ebfc676b9bf0406af3237327201b2b60ab53e8 ./SOURCE_MANIFEST.sha256
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs 29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
dcbfdc5a281adb94c3e4186f88248d48d6e995b45591cdba7183f28e658698e5 ./docs/API.md a1f77b4c93063376a8568667713fc708d8e59541260834f9bb353ca44f1670cc ./docs/API.md
4c2a0ea22061c39ae4ff534134392edfb31264c9ccaa453e8448b60b881c3847 ./docs/FLOW.md dd90e743bb9d3743ea677300a2ce0276f87f02fec2db0a3ffdcf5cc1bdf6cc60 ./docs/FLOW.md
1a675292d181edb313850b26fa264b6a9daa3ccc4a632fb86828a04b3803ea36 ./docs/openapi.json 7f8afa5a07a021f3f0fd49b1c65b14724e34229620ff58565b73dfe1fe7722a5 ./docs/openapi.json
26130f86bc8bd020f0a1668d11bb0220f577779cbe2b008fb194d62dbb385ddd ./flows/stability/igor---heating-ha-stable.flow.json 716523299bfdf791276cc7c590caa3ea54530207c5e05cebb45e2ba6ef9da915 ./flows/multisplit/v4/INSTRUKCJA.txt
63553ab25775d332b8aae0bf7715c37a3d2a19b12ebabbbf09335534f369c29a ./flows/stability/jan---heating-ha-stable.flow.json 0a2f74baec4202b45708aecd7456d7339e4dbbe6c65fa497fb8b89c9d0e1d7c9 ./flows/multisplit/v4/igor-multisplit-soft-coast-v4.flow.json
8ffec14bd7ad358134e25cb901906494186a8a73788111fd4ced450f87cf3b6f ./flows/stability/sypialnia---heating-ha-stable.flow.json 1e91ca707015988ea01b944570b88fb27fa9a7b8aca48a8493885969ce69b452 ./flows/multisplit/v4/jan-multisplit-soft-coast-v4.flow.json
ef649edd25d41c2cf348149896003d21143bcadd599665c21322f6ee0d52ed36 ./flows/multisplit/v4/sypialnia-multisplit-soft-coast-v4.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 +26,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
002170e041762ba17d2a082722a5c23fe13d126281e04aa5438a835ce98542a0 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json 8df63d9ae43ac72e47e1004ece2afc397f777c169f5715ad6148e3bfebedfc36 ./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 +34,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
bfce426a0151c5616122446ded457bdefb20d691afa64003fd57e8b67b26ff85 ./ha-addon/repository/gree-controller/CHANGELOG.md eaca083a91d15b31ed3415171f9bf4773312245ebd80ef0e9873d4ea11266986 ./ha-addon/repository/gree-controller/CHANGELOG.md
50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md 50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md
40036e3de45a68322e26d970eca50d3f17b57572f00fb13b0d4939d4a0fb36e0 ./ha-addon/repository/gree-controller/README.md f7038a752048ecb1d2004e9dcccb9b43e5534bae4046331ceb33322316ad83b4 ./ha-addon/repository/gree-controller/README.md
94cd29892fe53337bf5cf20ddd5317da514f311be14e056652ecfc3c45c7d45a ./ha-addon/repository/gree-controller/config.yaml 878d3d369c458bc0c2d261b339be1843379e9f77f28141e3db0f19b77cd11105 ./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 +47,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
ea015719d066cab86aff9daab33c3a83a29def711195eca416652be45a033ef8 ./lang/en.json 271aa0ff32672f7888f0cfcb12dc1543ab22640549d5a43c4f3db9d6d45b64ee ./lang/en.json
fefb2710c34195a2d50c62aa107e2f284875990fa63f7da42c8019b0398c3387 ./lang/pl.json 0f6a09664d90b2a5edf9b7c33d77baf1ea39e94392ae3cc868d68169b4a8ad4f ./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
@@ -88,7 +89,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json 804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
0636d1f517e866ea5d92960dd16a37eb848715e0f5b6c5e4e5a2f7fd550e2e4e ./scripts/api_dev_test.py 532ed10e2b6b52ad07685e90858ead075d51124b2cba4da57d19221224bfca19 ./scripts/api_dev_test.py
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh 5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh 6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh 1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
@@ -104,13 +105,13 @@ b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/upda
ea84f5230da97447c60dfb4f8dadd0747c92fd1f7a74d5a65d6fdb6f23cf4b6d ./src/api.rs ea84f5230da97447c60dfb4f8dadd0747c92fd1f7a74d5a65d6fdb6f23cf4b6d ./src/api.rs
0db0d81da4b8161004ddcbed2096812c285ecec9d944771670e6c8edf0775175 ./src/api/assets.rs 0db0d81da4b8161004ddcbed2096812c285ecec9d944771670e6c8edf0775175 ./src/api/assets.rs
1139dacbd3f6ff94bf721eb5a60d469016fb935ddcf484d71bfeb249b72c833a ./src/api/auth.rs 1139dacbd3f6ff94bf721eb5a60d469016fb935ddcf484d71bfeb249b72c833a ./src/api/auth.rs
1eb8c3cd86beb4f23aef21a23d31c144a9b31ec5bbaeaf24536723da5e78b768 ./src/api/automations.rs 36ad26dfc488e74359355069e5ea3309527b3d03bf879abf51c5d28497389901 ./src/api/automations.rs
fffad7874465739cd6b508e9d7791387b864fa88420ce0f7e2fc933d129a2751 ./src/api/configuration.rs fffad7874465739cd6b508e9d7791387b864fa88420ce0f7e2fc933d129a2751 ./src/api/configuration.rs
19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs 19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs
ca21f6d5a407e2e3532899d53ba4e8682c2fe961a46d8cd870ddc459721513ef ./src/api/device_groups.rs ca21f6d5a407e2e3532899d53ba4e8682c2fe961a46d8cd870ddc459721513ef ./src/api/device_groups.rs
8f92909dc575ea6238f4a003f8bd4056c2752fa3247b9ab226e47f875c80b916 ./src/api/devices.rs 8f92909dc575ea6238f4a003f8bd4056c2752fa3247b9ab226e47f875c80b916 ./src/api/devices.rs
4b5b784670ae761a6848b9a33fe3fb391bebd39d65e87936907428711e96ca44 ./src/api/events.rs 4b5b784670ae761a6848b9a33fe3fb391bebd39d65e87936907428711e96ca44 ./src/api/events.rs
1c5bc76fca312e6cd5fce988b3bfee785068322337b609405c12e18c1448665a ./src/api/flows.rs a4bd4d09ee1f6ea6039d2eb563e446142aab6b28f7de510ca8a27ba3465abb92 ./src/api/flows.rs
82c02fa135132ea3ebdb4d88dd1210dc536d22f987151d7f82595ee3d3d03440 ./src/api/gree_cloud.rs 82c02fa135132ea3ebdb4d88dd1210dc536d22f987151d7f82595ee3d3d03440 ./src/api/gree_cloud.rs
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
b6d586bc42b166ac05648f3da7d9aeb88efde2e9eaab5642685eb4d9cf15f95a ./src/api/history.rs b6d586bc42b166ac05648f3da7d9aeb88efde2e9eaab5642685eb4d9cf15f95a ./src/api/history.rs
@@ -123,7 +124,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
ddd1bfc8625b66f6e5f3d24b3122ac86c25f89b0af527e3dfce9102551a8f142 ./src/api/zones.rs 95e636288b46cdeb00f9fbf34ac2dbd226184e0eca5a3a111f188a305b0a5258 ./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
@@ -139,7 +140,7 @@ d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/sched
28ee42d08147b0f154af818065d7d014d74ee8ddd59fdef98115f5c5368056cc ./src/db/tests.rs 28ee42d08147b0f154af818065d7d014d74ee8ddd59fdef98115f5c5368056cc ./src/db/tests.rs
f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs
0e49ea4a52017b0a8501d5101b1906a7a1e8d5158acb0bb65caa43cde2836ec0 ./src/engine.rs 0e49ea4a52017b0a8501d5101b1906a7a1e8d5158acb0bb65caa43cde2836ec0 ./src/engine.rs
fbd0c387f48133cf7b1119bdd2830c3e3bb9238d94bcb3b76133b98ab657b601 ./src/engine/automations.rs 0bad57d36f6f58c40b0be595c3fe47d981460d61736fb48f389f649ccd4bea3b ./src/engine/automations.rs
1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs 1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs
357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs 357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs
828657b3c13b68792991b5ca2a26ae0b1d5c417191c83c4e5f650f62b6440096 ./src/engine/control_plan.rs 828657b3c13b68792991b5ca2a26ae0b1d5c417191c83c4e5f650f62b6440096 ./src/engine/control_plan.rs
@@ -149,15 +150,15 @@ e350aa63f3bb14ca887eaaa7555edbeae00fb584cbcaf552cc972f0c8918f5d8 ./src/engine/g
4ec11426ef860811348f2bf1b458bc5713fe8a7794fb4c8045dd637652e8e696 ./src/engine/history.rs 4ec11426ef860811348f2bf1b458bc5713fe8a7794fb4c8045dd637652e8e696 ./src/engine/history.rs
41b7045877771b7c352328998cb45ce2433e7f9631fad3eb1a882ab4897dc31f ./src/engine/local_thermostat.rs 41b7045877771b7c352328998cb45ce2433e7f9631fad3eb1a882ab4897dc31f ./src/engine/local_thermostat.rs
e61d7dea4b60e06fd8d08daaceecf53bd71b13ea2b0ad462692049f3cdaa5cef ./src/engine/ownership.rs e61d7dea4b60e06fd8d08daaceecf53bd71b13ea2b0ad462692049f3cdaa5cef ./src/engine/ownership.rs
76c1594f31af64c9a34e2555ac015e8719d42273c687b4c85b9c8f306b3c69a2 ./src/engine/polling.rs 5fafacadbc4a3d7e375a8079f9ae1b80eb013c2fb651b8fcc65f5f17cf023426 ./src/engine/polling.rs
c73691c3637465ab0f79aecf7f5428d0317799a8ab5329ed564cf57963bbe86e ./src/engine/runtime.rs c73691c3637465ab0f79aecf7f5428d0317799a8ab5329ed564cf57963bbe86e ./src/engine/runtime.rs
48a496c3935f886a90b90461c38a7aa31ebc40c6482d6feaeb2be47bc2387625 ./src/engine/schedules.rs 48a496c3935f886a90b90461c38a7aa31ebc40c6482d6feaeb2be47bc2387625 ./src/engine/schedules.rs
b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs
975298f6dec43bd962de67a1fdd2d6d9466bb0b16cc5dc46d04c2b23229e7a27 ./src/engine/temperature.rs d62ac5e97a36bbdb31bc23871ea7de73d8ac64a1e8203153d9faff0b1a075bc9 ./src/engine/temperature.rs
833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs 833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs
bb7758192868f6e7589e70a3d2962fe7d05357821609f4e1eb8237cfb56af597 ./src/engine/tests.rs ca5fbcdf44842766dd0741d2b728656535ff3a00f4a8a6da989d7183ceda51b6 ./src/engine/tests.rs
c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs
08cb170ab73152720dbcf137bf8f3190e18ea2d498a49df128790fa59c2f3416 ./src/engine/zone_control.rs edee10929eb89d7a774474cd9054890ee53b8c1fdf54e3d6f143265dead8f6b8 ./src/engine/zone_control.rs
bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs
d681f2aa53c39f37411834c300e9200299f94ee4ea021f44f70a3ea1774bd698 ./src/home_assistant.rs d681f2aa53c39f37411834c300e9200299f94ee4ea021f44f70a3ea1774bd698 ./src/home_assistant.rs
bcd14e4d1af3ca580f3b95bcd86164c40f762511e1b8e2501cb8a598a6d8adf4 ./src/influxdb.rs bcd14e4d1af3ca580f3b95bcd86164c40f762511e1b8e2501cb8a598a6d8adf4 ./src/influxdb.rs
@@ -166,7 +167,7 @@ da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb
67e42a92295480d2f1d364958f184654cfb5d4b77e7e1df47f588bdec18868a0 ./src/influxdb/write.rs 67e42a92295480d2f1d364958f184654cfb5d4b77e7e1df47f588bdec18868a0 ./src/influxdb/write.rs
62f9da6ef4f5fd92701d0857773f9f6236ef25356f21b9b42236abab6ba7da53 ./src/main.rs 62f9da6ef4f5fd92701d0857773f9f6236ef25356f21b9b42236abab6ba7da53 ./src/main.rs
23688a5cb68efded89471ce959328d27adff370d451bd9f378a283ab51437e76 ./src/models.rs 23688a5cb68efded89471ce959328d27adff370d451bd9f378a283ab51437e76 ./src/models.rs
615fba5dc5f3ad75497d637b02cc83b7aacfe7baa6372c034dd5609bfff398a0 ./src/models/automation.rs 75bb9db327ae4ab1f29a5aa888e130be3a8e75b714fb5dbad4307bebee583c66 ./src/models/automation.rs
e4195a40135f408302a78cb200a54e6ef1c0e6031fb069a2431d068e5dbbdd8d ./src/models/control_plan.rs e4195a40135f408302a78cb200a54e6ef1c0e6031fb069a2431d068e5dbbdd8d ./src/models/control_plan.rs
06357b352ff6494d83e72796b869573a8b8f36b835985a5900ac571c922b8da9 ./src/models/defaults.rs 06357b352ff6494d83e72796b869573a8b8f36b835985a5900ac571c922b8da9 ./src/models/defaults.rs
698dcbeba72037a251c25d07c95833f121716d949f9217f1f9429e8cd40638e5 ./src/models/device.rs 698dcbeba72037a251c25d07c95833f121716d949f9217f1f9429e8cd40638e5 ./src/models/device.rs
@@ -176,7 +177,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
bc2818ce8d0dae1edfb1b98e5d1c00b193f03eeaa28e343d3532f6023288738c ./src/models/zone.rs 94ed531788a118f2f3fb5dd1748b2f416480a5945d8c7c7a722c201ce03212dd ./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
@@ -205,19 +206,19 @@ b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree
6a30a5ecee99d2cf40381b9f770ed574e9e85b8b50e3425a77e6f64283faeee6 ./web/css/styles.css 6a30a5ecee99d2cf40381b9f770ed574e9e85b8b50e3425a77e6f64283faeee6 ./web/css/styles.css
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html 183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
78996e460243aa3043bd889891a99b9417d930beb7c9ea81b4a7f496e3d58844 ./web/index.html 477de38c1cfbf2b925059795ef1b30522813d70908c36d3a471faf16da6935c8 ./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
80aaf5c56375345c67fd30c4fc1622319e4749b2859ec10613f77ac117f0441e ./web/js-dynamic/dashboard.js 008a85cddf6a5357eff35764eabddf1d7baaecf949b481d195124de50431e0d2 ./web/js-dynamic/dashboard.js
930e67432f55e8476513e808edfa9a0809a8da7479307284fcc136082a4129a4 ./web/js-dynamic/entities.js c58dddbd1238ed9dfb590fc810e569c9a8408113cd85623e86190874591079e6 ./web/js-dynamic/entities.js
4612c958d7b9ccf89d6d45a11c3bc7e864bbd2ceaccc16fd7d76811ba4c74196 ./web/js-dynamic/events.js bc1e7c4cccda0447fe805c61e655b773375bf503141195b82d4e95cdf093ec80 ./web/js-dynamic/events.js
3e52db4f0ab5d721a047bf5a8f690c2d634c1cb24433d50d95bcbaa617115bcc ./web/js-dynamic/flows.js c4e14ebc7ddcec3c7346a037d488ceacd19606f76e84004b4f7c7dbd5fef646f ./web/js-dynamic/flows.js
3ae714de6b71c068ba665c81a7689f994b1716284e8d937db669ad500f12cf63 ./web/js-dynamic/forms.js 1d54ae8919edb61414f9ffc46f85394b29c3c355827120264352cccb32f65e68 ./web/js-dynamic/forms.js
92d013bb5d64f64aa8c55380704540a5f72732bde7ccd16554965e401daf50ea ./web/js-dynamic/history.js 92d013bb5d64f64aa8c55380704540a5f72732bde7ccd16554965e401daf50ea ./web/js-dynamic/history.js
511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js-dynamic/main.js 511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js-dynamic/main.js
30cbaebb817d5d416e1a372595b647aae42c14b9cc406c4751d0ced563243b69 ./web/js-dynamic/navigation.js d9596cef1b86c0d3f73ccfc8d3a73c2b5293d81293fdbdcefd77346cbd3fe983 ./web/js-dynamic/navigation.js
3ff9c027beb8fb033330bf3cfb3496e06fb38c42041f1ffe522c6c7af18e9e30 ./web/js-dynamic/realtime.js 3ff9c027beb8fb033330bf3cfb3496e06fb38c42041f1ffe522c6c7af18e9e30 ./web/js-dynamic/realtime.js
1b667f0a8bce824b27f56a8d2f44ff8feef9a011289591dc629ee288db70d137 ./web/js-dynamic/router.js 1b667f0a8bce824b27f56a8d2f44ff8feef9a011289591dc629ee288db70d137 ./web/js-dynamic/router.js
c2710fd9c1f2b31878c1e25ceb02d74ae30456c1bbb400e69fcf7619ecc72bc2 ./web/js-dynamic/select-ui.js c2710fd9c1f2b31878c1e25ceb02d74ae30456c1bbb400e69fcf7619ecc72bc2 ./web/js-dynamic/select-ui.js
+2 -2
View File
@@ -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.0** **Version: 0.16.1**
Version 0.16.0 improves heating stability with Home Assistant room-temperature sensor control, reliable Flow ownership, and safer shutdown behavior. Version 0.16.1 adds configurable early heat coasting without unit ON/OFF cycling, per-Flow settings for coasting and deep standby, and separate Flow/manual status in the Web UI. Existing compressor protection and shared multisplit OFF logic are unchanged.
> [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`.
+35 -34
View File
@@ -1,16 +1,17 @@
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore 2fd3bf1fb7ef548c7abc9b26d1cc4ef470784b8637af65446b845b27a45b547b ./.gitignore
3c92ae20c070a1db66390a8ec95ed87bc0fed9ec82188e5348fc862ddcbff06e ./Cargo.lock 1ece15d35e56470fbc0e9f0d9b1963f518c81a7b80270f2d2d91e53a4eb463ce ./Cargo.lock
5a7d380c42118a8ab57939bfc99fe15dcf4ad4c1f4bfd5fd808164012ca2c0df ./Cargo.toml 8ad83bc5b719a3c0e058d0d6d28cc5af834f7a6d07d8fdc89c4c8ae804e1eb25 ./Cargo.toml
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
8f2573aeec2240fa85c090b616575a9f5f79acc06ccfe8a98bf216ab13d3176a ./README.md 3da89b24afe096e116b5725a279c2e84b493ea2ead4aa24895647e083c02298f ./README.md
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs 29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
dcbfdc5a281adb94c3e4186f88248d48d6e995b45591cdba7183f28e658698e5 ./docs/API.md a1f77b4c93063376a8568667713fc708d8e59541260834f9bb353ca44f1670cc ./docs/API.md
4c2a0ea22061c39ae4ff534134392edfb31264c9ccaa453e8448b60b881c3847 ./docs/FLOW.md dd90e743bb9d3743ea677300a2ce0276f87f02fec2db0a3ffdcf5cc1bdf6cc60 ./docs/FLOW.md
1a675292d181edb313850b26fa264b6a9daa3ccc4a632fb86828a04b3803ea36 ./docs/openapi.json 7f8afa5a07a021f3f0fd49b1c65b14724e34229620ff58565b73dfe1fe7722a5 ./docs/openapi.json
26130f86bc8bd020f0a1668d11bb0220f577779cbe2b008fb194d62dbb385ddd ./flows/stability/igor---heating-ha-stable.flow.json 716523299bfdf791276cc7c590caa3ea54530207c5e05cebb45e2ba6ef9da915 ./flows/multisplit/v4/INSTRUKCJA.txt
63553ab25775d332b8aae0bf7715c37a3d2a19b12ebabbbf09335534f369c29a ./flows/stability/jan---heating-ha-stable.flow.json 0a2f74baec4202b45708aecd7456d7339e4dbbe6c65fa497fb8b89c9d0e1d7c9 ./flows/multisplit/v4/igor-multisplit-soft-coast-v4.flow.json
8ffec14bd7ad358134e25cb901906494186a8a73788111fd4ced450f87cf3b6f ./flows/stability/sypialnia---heating-ha-stable.flow.json 1e91ca707015988ea01b944570b88fb27fa9a7b8aca48a8493885969ce69b452 ./flows/multisplit/v4/jan-multisplit-soft-coast-v4.flow.json
ef649edd25d41c2cf348149896003d21143bcadd599665c21322f6ee0d52ed36 ./flows/multisplit/v4/sypialnia-multisplit-soft-coast-v4.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 +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
002170e041762ba17d2a082722a5c23fe13d126281e04aa5438a835ce98542a0 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json 8df63d9ae43ac72e47e1004ece2afc397f777c169f5715ad6148e3bfebedfc36 ./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 +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
bfce426a0151c5616122446ded457bdefb20d691afa64003fd57e8b67b26ff85 ./ha-addon/repository/gree-controller/CHANGELOG.md eaca083a91d15b31ed3415171f9bf4773312245ebd80ef0e9873d4ea11266986 ./ha-addon/repository/gree-controller/CHANGELOG.md
50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md 50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md
40036e3de45a68322e26d970eca50d3f17b57572f00fb13b0d4939d4a0fb36e0 ./ha-addon/repository/gree-controller/README.md f7038a752048ecb1d2004e9dcccb9b43e5534bae4046331ceb33322316ad83b4 ./ha-addon/repository/gree-controller/README.md
94cd29892fe53337bf5cf20ddd5317da514f311be14e056652ecfc3c45c7d45a ./ha-addon/repository/gree-controller/config.yaml 878d3d369c458bc0c2d261b339be1843379e9f77f28141e3db0f19b77cd11105 ./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 +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
ea015719d066cab86aff9daab33c3a83a29def711195eca416652be45a033ef8 ./lang/en.json 271aa0ff32672f7888f0cfcb12dc1543ab22640549d5a43c4f3db9d6d45b64ee ./lang/en.json
fefb2710c34195a2d50c62aa107e2f284875990fa63f7da42c8019b0398c3387 ./lang/pl.json 0f6a09664d90b2a5edf9b7c33d77baf1ea39e94392ae3cc868d68169b4a8ad4f ./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
@@ -87,7 +88,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json 804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
0636d1f517e866ea5d92960dd16a37eb848715e0f5b6c5e4e5a2f7fd550e2e4e ./scripts/api_dev_test.py 532ed10e2b6b52ad07685e90858ead075d51124b2cba4da57d19221224bfca19 ./scripts/api_dev_test.py
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh 5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh 6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh 1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
@@ -103,13 +104,13 @@ b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/upda
ea84f5230da97447c60dfb4f8dadd0747c92fd1f7a74d5a65d6fdb6f23cf4b6d ./src/api.rs ea84f5230da97447c60dfb4f8dadd0747c92fd1f7a74d5a65d6fdb6f23cf4b6d ./src/api.rs
0db0d81da4b8161004ddcbed2096812c285ecec9d944771670e6c8edf0775175 ./src/api/assets.rs 0db0d81da4b8161004ddcbed2096812c285ecec9d944771670e6c8edf0775175 ./src/api/assets.rs
1139dacbd3f6ff94bf721eb5a60d469016fb935ddcf484d71bfeb249b72c833a ./src/api/auth.rs 1139dacbd3f6ff94bf721eb5a60d469016fb935ddcf484d71bfeb249b72c833a ./src/api/auth.rs
1eb8c3cd86beb4f23aef21a23d31c144a9b31ec5bbaeaf24536723da5e78b768 ./src/api/automations.rs 36ad26dfc488e74359355069e5ea3309527b3d03bf879abf51c5d28497389901 ./src/api/automations.rs
fffad7874465739cd6b508e9d7791387b864fa88420ce0f7e2fc933d129a2751 ./src/api/configuration.rs fffad7874465739cd6b508e9d7791387b864fa88420ce0f7e2fc933d129a2751 ./src/api/configuration.rs
19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs 19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs
ca21f6d5a407e2e3532899d53ba4e8682c2fe961a46d8cd870ddc459721513ef ./src/api/device_groups.rs ca21f6d5a407e2e3532899d53ba4e8682c2fe961a46d8cd870ddc459721513ef ./src/api/device_groups.rs
8f92909dc575ea6238f4a003f8bd4056c2752fa3247b9ab226e47f875c80b916 ./src/api/devices.rs 8f92909dc575ea6238f4a003f8bd4056c2752fa3247b9ab226e47f875c80b916 ./src/api/devices.rs
4b5b784670ae761a6848b9a33fe3fb391bebd39d65e87936907428711e96ca44 ./src/api/events.rs 4b5b784670ae761a6848b9a33fe3fb391bebd39d65e87936907428711e96ca44 ./src/api/events.rs
1c5bc76fca312e6cd5fce988b3bfee785068322337b609405c12e18c1448665a ./src/api/flows.rs a4bd4d09ee1f6ea6039d2eb563e446142aab6b28f7de510ca8a27ba3465abb92 ./src/api/flows.rs
82c02fa135132ea3ebdb4d88dd1210dc536d22f987151d7f82595ee3d3d03440 ./src/api/gree_cloud.rs 82c02fa135132ea3ebdb4d88dd1210dc536d22f987151d7f82595ee3d3d03440 ./src/api/gree_cloud.rs
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
b6d586bc42b166ac05648f3da7d9aeb88efde2e9eaab5642685eb4d9cf15f95a ./src/api/history.rs b6d586bc42b166ac05648f3da7d9aeb88efde2e9eaab5642685eb4d9cf15f95a ./src/api/history.rs
@@ -122,7 +123,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
ddd1bfc8625b66f6e5f3d24b3122ac86c25f89b0af527e3dfce9102551a8f142 ./src/api/zones.rs 95e636288b46cdeb00f9fbf34ac2dbd226184e0eca5a3a111f188a305b0a5258 ./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
@@ -138,7 +139,7 @@ d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/sched
28ee42d08147b0f154af818065d7d014d74ee8ddd59fdef98115f5c5368056cc ./src/db/tests.rs 28ee42d08147b0f154af818065d7d014d74ee8ddd59fdef98115f5c5368056cc ./src/db/tests.rs
f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs
0e49ea4a52017b0a8501d5101b1906a7a1e8d5158acb0bb65caa43cde2836ec0 ./src/engine.rs 0e49ea4a52017b0a8501d5101b1906a7a1e8d5158acb0bb65caa43cde2836ec0 ./src/engine.rs
fbd0c387f48133cf7b1119bdd2830c3e3bb9238d94bcb3b76133b98ab657b601 ./src/engine/automations.rs 0bad57d36f6f58c40b0be595c3fe47d981460d61736fb48f389f649ccd4bea3b ./src/engine/automations.rs
1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs 1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs
357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs 357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs
828657b3c13b68792991b5ca2a26ae0b1d5c417191c83c4e5f650f62b6440096 ./src/engine/control_plan.rs 828657b3c13b68792991b5ca2a26ae0b1d5c417191c83c4e5f650f62b6440096 ./src/engine/control_plan.rs
@@ -148,15 +149,15 @@ e350aa63f3bb14ca887eaaa7555edbeae00fb584cbcaf552cc972f0c8918f5d8 ./src/engine/g
4ec11426ef860811348f2bf1b458bc5713fe8a7794fb4c8045dd637652e8e696 ./src/engine/history.rs 4ec11426ef860811348f2bf1b458bc5713fe8a7794fb4c8045dd637652e8e696 ./src/engine/history.rs
41b7045877771b7c352328998cb45ce2433e7f9631fad3eb1a882ab4897dc31f ./src/engine/local_thermostat.rs 41b7045877771b7c352328998cb45ce2433e7f9631fad3eb1a882ab4897dc31f ./src/engine/local_thermostat.rs
e61d7dea4b60e06fd8d08daaceecf53bd71b13ea2b0ad462692049f3cdaa5cef ./src/engine/ownership.rs e61d7dea4b60e06fd8d08daaceecf53bd71b13ea2b0ad462692049f3cdaa5cef ./src/engine/ownership.rs
76c1594f31af64c9a34e2555ac015e8719d42273c687b4c85b9c8f306b3c69a2 ./src/engine/polling.rs 5fafacadbc4a3d7e375a8079f9ae1b80eb013c2fb651b8fcc65f5f17cf023426 ./src/engine/polling.rs
c73691c3637465ab0f79aecf7f5428d0317799a8ab5329ed564cf57963bbe86e ./src/engine/runtime.rs c73691c3637465ab0f79aecf7f5428d0317799a8ab5329ed564cf57963bbe86e ./src/engine/runtime.rs
48a496c3935f886a90b90461c38a7aa31ebc40c6482d6feaeb2be47bc2387625 ./src/engine/schedules.rs 48a496c3935f886a90b90461c38a7aa31ebc40c6482d6feaeb2be47bc2387625 ./src/engine/schedules.rs
b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs
975298f6dec43bd962de67a1fdd2d6d9466bb0b16cc5dc46d04c2b23229e7a27 ./src/engine/temperature.rs d62ac5e97a36bbdb31bc23871ea7de73d8ac64a1e8203153d9faff0b1a075bc9 ./src/engine/temperature.rs
833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs 833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs
bb7758192868f6e7589e70a3d2962fe7d05357821609f4e1eb8237cfb56af597 ./src/engine/tests.rs ca5fbcdf44842766dd0741d2b728656535ff3a00f4a8a6da989d7183ceda51b6 ./src/engine/tests.rs
c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs
08cb170ab73152720dbcf137bf8f3190e18ea2d498a49df128790fa59c2f3416 ./src/engine/zone_control.rs edee10929eb89d7a774474cd9054890ee53b8c1fdf54e3d6f143265dead8f6b8 ./src/engine/zone_control.rs
bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs
d681f2aa53c39f37411834c300e9200299f94ee4ea021f44f70a3ea1774bd698 ./src/home_assistant.rs d681f2aa53c39f37411834c300e9200299f94ee4ea021f44f70a3ea1774bd698 ./src/home_assistant.rs
bcd14e4d1af3ca580f3b95bcd86164c40f762511e1b8e2501cb8a598a6d8adf4 ./src/influxdb.rs bcd14e4d1af3ca580f3b95bcd86164c40f762511e1b8e2501cb8a598a6d8adf4 ./src/influxdb.rs
@@ -165,7 +166,7 @@ da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb
67e42a92295480d2f1d364958f184654cfb5d4b77e7e1df47f588bdec18868a0 ./src/influxdb/write.rs 67e42a92295480d2f1d364958f184654cfb5d4b77e7e1df47f588bdec18868a0 ./src/influxdb/write.rs
62f9da6ef4f5fd92701d0857773f9f6236ef25356f21b9b42236abab6ba7da53 ./src/main.rs 62f9da6ef4f5fd92701d0857773f9f6236ef25356f21b9b42236abab6ba7da53 ./src/main.rs
23688a5cb68efded89471ce959328d27adff370d451bd9f378a283ab51437e76 ./src/models.rs 23688a5cb68efded89471ce959328d27adff370d451bd9f378a283ab51437e76 ./src/models.rs
615fba5dc5f3ad75497d637b02cc83b7aacfe7baa6372c034dd5609bfff398a0 ./src/models/automation.rs 75bb9db327ae4ab1f29a5aa888e130be3a8e75b714fb5dbad4307bebee583c66 ./src/models/automation.rs
e4195a40135f408302a78cb200a54e6ef1c0e6031fb069a2431d068e5dbbdd8d ./src/models/control_plan.rs e4195a40135f408302a78cb200a54e6ef1c0e6031fb069a2431d068e5dbbdd8d ./src/models/control_plan.rs
06357b352ff6494d83e72796b869573a8b8f36b835985a5900ac571c922b8da9 ./src/models/defaults.rs 06357b352ff6494d83e72796b869573a8b8f36b835985a5900ac571c922b8da9 ./src/models/defaults.rs
698dcbeba72037a251c25d07c95833f121716d949f9217f1f9429e8cd40638e5 ./src/models/device.rs 698dcbeba72037a251c25d07c95833f121716d949f9217f1f9429e8cd40638e5 ./src/models/device.rs
@@ -175,7 +176,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
bc2818ce8d0dae1edfb1b98e5d1c00b193f03eeaa28e343d3532f6023288738c ./src/models/zone.rs 94ed531788a118f2f3fb5dd1748b2f416480a5945d8c7c7a722c201ce03212dd ./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
@@ -204,19 +205,19 @@ b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree
6a30a5ecee99d2cf40381b9f770ed574e9e85b8b50e3425a77e6f64283faeee6 ./web/css/styles.css 6a30a5ecee99d2cf40381b9f770ed574e9e85b8b50e3425a77e6f64283faeee6 ./web/css/styles.css
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html 183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
78996e460243aa3043bd889891a99b9417d930beb7c9ea81b4a7f496e3d58844 ./web/index.html 477de38c1cfbf2b925059795ef1b30522813d70908c36d3a471faf16da6935c8 ./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
80aaf5c56375345c67fd30c4fc1622319e4749b2859ec10613f77ac117f0441e ./web/js-dynamic/dashboard.js 008a85cddf6a5357eff35764eabddf1d7baaecf949b481d195124de50431e0d2 ./web/js-dynamic/dashboard.js
930e67432f55e8476513e808edfa9a0809a8da7479307284fcc136082a4129a4 ./web/js-dynamic/entities.js c58dddbd1238ed9dfb590fc810e569c9a8408113cd85623e86190874591079e6 ./web/js-dynamic/entities.js
4612c958d7b9ccf89d6d45a11c3bc7e864bbd2ceaccc16fd7d76811ba4c74196 ./web/js-dynamic/events.js bc1e7c4cccda0447fe805c61e655b773375bf503141195b82d4e95cdf093ec80 ./web/js-dynamic/events.js
3e52db4f0ab5d721a047bf5a8f690c2d634c1cb24433d50d95bcbaa617115bcc ./web/js-dynamic/flows.js c4e14ebc7ddcec3c7346a037d488ceacd19606f76e84004b4f7c7dbd5fef646f ./web/js-dynamic/flows.js
3ae714de6b71c068ba665c81a7689f994b1716284e8d937db669ad500f12cf63 ./web/js-dynamic/forms.js 1d54ae8919edb61414f9ffc46f85394b29c3c355827120264352cccb32f65e68 ./web/js-dynamic/forms.js
92d013bb5d64f64aa8c55380704540a5f72732bde7ccd16554965e401daf50ea ./web/js-dynamic/history.js 92d013bb5d64f64aa8c55380704540a5f72732bde7ccd16554965e401daf50ea ./web/js-dynamic/history.js
511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js-dynamic/main.js 511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js-dynamic/main.js
30cbaebb817d5d416e1a372595b647aae42c14b9cc406c4751d0ced563243b69 ./web/js-dynamic/navigation.js d9596cef1b86c0d3f73ccfc8d3a73c2b5293d81293fdbdcefd77346cbd3fe983 ./web/js-dynamic/navigation.js
3ff9c027beb8fb033330bf3cfb3496e06fb38c42041f1ffe522c6c7af18e9e30 ./web/js-dynamic/realtime.js 3ff9c027beb8fb033330bf3cfb3496e06fb38c42041f1ffe522c6c7af18e9e30 ./web/js-dynamic/realtime.js
1b667f0a8bce824b27f56a8d2f44ff8feef9a011289591dc629ee288db70d137 ./web/js-dynamic/router.js 1b667f0a8bce824b27f56a8d2f44ff8feef9a011289591dc629ee288db70d137 ./web/js-dynamic/router.js
c2710fd9c1f2b31878c1e25ceb02d74ae30456c1bbb400e69fcf7619ecc72bc2 ./web/js-dynamic/select-ui.js c2710fd9c1f2b31878c1e25ceb02d74ae30456c1bbb400e69fcf7619ecc72bc2 ./web/js-dynamic/select-ui.js
+6 -3
View File
@@ -1,6 +1,9 @@
# GREE Controller API reference # GREE Controller API reference
HTTP and WebSocket API for GREE Controller **0.16.0**. HTTP and WebSocket API for GREE Controller **0.16.1**.
Version 0.16.1: Zone configuration exposes `heat_anticipation_c` (0..2.5°C, default 0), runtime Zone exposes `heat_coasting`, and optional Flow `zone_thermostat` node configuration supports `heat_anticipation_c` and `standby_offset_c`. These settings alter the GREE temperature target, not physical power.
[← Main documentation](../README.md) [← Main documentation](../README.md)
@@ -243,7 +246,7 @@ Response:
{ {
"status": "ok", "status": "ok",
"name": "gree-controller", "name": "gree-controller",
"version": "0.16.0", "version": "0.16.1",
"uptime_seconds": 1234, "uptime_seconds": 1234,
"control_ready": true, "control_ready": true,
"time": "2026-08-30T06:54:00Z" "time": "2026-08-30T06:54:00Z"
@@ -306,7 +309,7 @@ Returns the initial Web UI snapshot:
"control_plan": {"generated_at": "2026-09-04T08:00:00Z", "zones": [], "rules": []}, "control_plan": {"generated_at": "2026-09-04T08:00:00Z", "zones": [], "rules": []},
"control_plan_revision": 42, "control_plan_revision": 42,
"system": { "system": {
"version": "0.16.0", "version": "0.16.1",
"uptime_seconds": 1234, "uptime_seconds": 1234,
"auth_required": false, "auth_required": false,
"control_ready": true, "control_ready": true,
+1
View File
@@ -14,6 +14,7 @@ Flow is an authoring/orchestration layer. It does not replace the thermostat, sc
## Existing control domains remain authoritative ## Existing control domains remain authoritative
- Optional `zone_thermostat` fields `heat_anticipation_c` (0–2.5 °C) and `standby_offset_c` (0.5–8 °C) persist into the target Zone when the Flow action executes. The regulator enters a hysteretic low-GREE-setpoint *coasting* state when room temperature reaches `target - heat_anticipation_c`, and resumes full setpoint heating once it falls another 0.4 °C. Coasting changes the requested GREE temperature and smart fan/Quiet behavior, **never the indoor unit power**; pre-existing compressor protection and multisplit shared-OFF flow gates remain unchanged. For older Flows the fields are absent and the previous regulator behavior remains.
- `zone_thermostat` writes the existing persistent Zone intent and wakes the normal thermostat cycle. `power=false` sends an explicit physical OFF to the assigned unit but does **not** change the persistent Zone `enabled` flag; Zone enable/disable remains an administrative control gate. A concrete Flow mode (`heat`/`cool`) is also written with the OFF frame so a stale device `dry`/`fan` mode is not retained; Flow `auto` remains an ownership policy and is never mapped to physical GREE Auto on OFF. While a zone is owned by `automation.flow`, the global outdoor-assist bias is suppressed so the Flow's own room-temperature thresholds remain authoritative. It can also issue one-shot granular vertical/horizontal louver commands to the assigned unit; these auxiliary fields do not take over thermostat ownership. - `zone_thermostat` writes the existing persistent Zone intent and wakes the normal thermostat cycle. `power=false` sends an explicit physical OFF to the assigned unit but does **not** change the persistent Zone `enabled` flag; Zone enable/disable remains an administrative control gate. A concrete Flow mode (`heat`/`cool`) is also written with the OFF frame so a stale device `dry`/`fan` mode is not retained; Flow `auto` remains an ownership policy and is never mapped to physical GREE Auto on OFF. While a zone is owned by `automation.flow`, the global outdoor-assist bias is suppressed so the Flow's own room-temperature thresholds remain authoritative. It can also issue one-shot granular vertical/horizontal louver commands to the assigned unit; these auxiliary fields do not take over thermostat ownership.
- `group_action` calls the existing `control_group` path, including custom group targets. - `group_action` calls the existing `control_group` path, including custom group targets.
- `device_action` uses the existing `DeviceCommand` / automatic-device path. - `device_action` uses the existing `DeviceCommand` / automatic-device path.
+20 -2
View File
@@ -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.0", "version": "0.16.1",
"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.0" "example": "0.16.1"
}, },
"uptime_seconds": { "uptime_seconds": {
"type": "integer", "type": "integer",
@@ -7173,6 +7173,13 @@
"description": "Optimistic concurrency revision when updating", "description": "Optimistic concurrency revision when updating",
"minimum": 1, "minimum": 1,
"example": 12 "example": 12
},
"heat_anticipation_c": {
"type": "number",
"minimum": 0.0,
"maximum": 2.5,
"default": 0.0,
"description": "Temperature margin before target for early coasting without turning the unit OFF."
} }
}, },
"required": [ "required": [
@@ -7362,6 +7369,17 @@
"updated_at": { "updated_at": {
"type": "string", "type": "string",
"format": "date-time" "format": "date-time"
},
"heat_anticipation_c": {
"type": "number",
"minimum": 0.0,
"maximum": 2.5,
"default": 0.0,
"description": "Temperature margin before target for early coasting without turning the unit OFF."
},
"heat_coasting": {
"type": "boolean",
"description": "True when the controller requested low setpoint while keeping unit powered ON."
} }
}, },
"required": [ "required": [
@@ -1,7 +1,7 @@
{ {
"domain": "gree_controller", "domain": "gree_controller",
"name": "GREE Controller", "name": "GREE Controller",
"version": "0.16.0", "version": "0.16.1",
"config_flow": true, "config_flow": true,
"integration_type": "hub", "integration_type": "hub",
"iot_class": "local_polling", "iot_class": "local_polling",
@@ -1,5 +1,12 @@
# Changelog # Changelog
## 0.16.1
- Adds hysteretic early-heating coasting: lower the GREE target before reaching room comfort temperature without switching the indoor unit OFF.
- Supports `heat_anticipation_c` and `standby_offset_c` settings in Visual Flow thermostat actions, including import/export and zone editing.
- Shows Flow heating/coasting/standby distinctly from real direct-manual device takeover.
- Preserves existing compressor protection and backward compatibility with existing Flow definitions.
## 0.16.0 ## 0.16.0
- Improves heating stability when Visual Flow automations use Home Assistant room-temperature sensors. - Improves heating stability when Visual Flow automations use Home Assistant room-temperature sensors.
@@ -2,7 +2,7 @@
Local GREE HVAC controller packaged as a Home Assistant add-on, with Web UI, HTTP/WebSocket API, built-in Swagger API documentation, UDP discovery/control, `amd64` and `aarch64` support, ingress, and database backup from `/data/gree-controller.db`. Local GREE HVAC controller packaged as a Home Assistant add-on, with Web UI, HTTP/WebSocket API, built-in Swagger API documentation, UDP discovery/control, `amd64` and `aarch64` support, ingress, and database backup from `/data/gree-controller.db`.
Version 0.16.0 improves heating stability with Home Assistant room-temperature sensor control, reliable Flow ownership, and safer shutdown behavior. Version 0.16.1 adds early heating coasting with GREE setpoint modulation, configurable from Visual Flow, plus distinct Flow/manual status. Compressor protection and shared multisplit shutdown logic remain unchanged.
![Home Assistant OS + GREE + VLAN topology](https://git.linuxiarz.pl/gru/gree-controller-ha-addon/raw/branch/master/gree-controller/topology-ha-vlan-gree-en.png) ![Home Assistant OS + GREE + VLAN topology](https://git.linuxiarz.pl/gru/gree-controller-ha-addon/raw/branch/master/gree-controller/topology-ha-vlan-gree-en.png)
@@ -1,5 +1,5 @@
name: "GREE Controller" name: "GREE Controller"
version: "0.16.0" version: "0.16.1"
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/"
+8 -1
View File
@@ -1548,6 +1548,13 @@
"flow.contextDeleteConnection": "Delete connection", "flow.contextDeleteConnection": "Delete connection",
"flow.rightClickKey": "Right click", "flow.rightClickKey": "Right click",
"flow.shortcutContextMenu": "Flow context menu", "flow.shortcutContextMenu": "Flow context menu",
"flow.heatingCurveDryRunValue": "Value passed from curve" "flow.heatingCurveDryRunValue": "Value passed from curve",
"zones.ownerFlow": "Flow automation",
"zones.flowHeating": "Flow active - heating",
"zones.flowCoasting": "Flow active - coasting (unit ON, low target)",
"zones.flowStandby": "Flow active - soft standby",
"zones.heatAnticipation": "Early heat coasting (°C ahead)",
"zones.heatAnticipationHint": "Lowers the GREE target before the comfort target without powering the unit off. 0 disables coasting.",
"simulation.reasonCoasting": "Early heating coasting before {target}°C: GREE stays ON with a {standby}°C setpoint."
} }
} }
+8 -1
View File
@@ -1548,6 +1548,13 @@
"flow.contextDeleteConnection": "Usuń połączenie", "flow.contextDeleteConnection": "Usuń połączenie",
"flow.rightClickKey": "Prawy przycisk", "flow.rightClickKey": "Prawy przycisk",
"flow.shortcutContextMenu": "Menu kontekstowe Flow", "flow.shortcutContextMenu": "Menu kontekstowe Flow",
"flow.heatingCurveDryRunValue": "Wartość przekazana z krzywej" "flow.heatingCurveDryRunValue": "Wartość przekazana z krzywej",
"zones.ownerFlow": "Flow (automatyka)",
"zones.flowHeating": "Flow aktywny - grzanie",
"zones.flowCoasting": "Flow aktywny - wyhamowanie (ON, niska nastawa)",
"zones.flowStandby": "Flow aktywny - soft standby",
"zones.heatAnticipation": "Wyprzedzenie wyhamowania grzania (°C)",
"zones.heatAnticipationHint": "Obniża nastawę GREE przed osiągnięciem celu, bez OFF jednostki. 0 wyłącza funkcję.",
"simulation.reasonCoasting": "Wyhamowanie grzania przed celem {target}°C: GREE utrzymuje jednostkę ON z nastawą {standby}°C."
} }
} }
+3 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Developer smoke/integration tests for GREE Controller API 0.16.0. """Developer smoke/integration tests for GREE Controller API 0.16.1.
Default mode is read-only and safe to run against a real controller. Default mode is read-only and safe to run against a real controller.
Use --settings-write to additionally round-trip all split settings resources and Use --settings-write to additionally round-trip all split settings resources and
@@ -26,7 +26,7 @@ from typing import Any, Callable, Iterable, Optional
DEFAULT_BASE_URL = os.environ.get("GREE_API_URL", "http://127.0.0.1:8787") DEFAULT_BASE_URL = os.environ.get("GREE_API_URL", "http://127.0.0.1:8787")
DEFAULT_TOKEN = os.environ.get("GREE_API_TOKEN", "") DEFAULT_TOKEN = os.environ.get("GREE_API_TOKEN", "")
DEFAULT_EXPECTED_VERSION = "0.16.0" DEFAULT_EXPECTED_VERSION = "0.16.1"
SETTINGS_PATHS: dict[str, set[str]] = { SETTINGS_PATHS: dict[str, set[str]] = {
"/api/settings/application": {"simulator_enabled"}, "/api/settings/application": {"simulator_enabled"},
@@ -599,7 +599,7 @@ def run_suite(args: argparse.Namespace) -> int:
print(f"Settings write tests: {'ENABLED' if args.settings_write else 'disabled'}\n") print(f"Settings write tests: {'ENABLED' if args.settings_write else 'disabled'}\n")
runner.run("public health", lambda: test_health(client, args.expected_version)) runner.run("public health", lambda: test_health(client, args.expected_version))
runner.run("OpenAPI 0.16.0 contract", lambda: test_openapi(client, args.expected_version)) runner.run("OpenAPI 0.16.1 contract", lambda: test_openapi(client, args.expected_version))
runner.run("bootstrap snapshot contract", lambda: test_bootstrap_contract(client)) runner.run("bootstrap snapshot contract", lambda: test_bootstrap_contract(client))
runner.run("protected API requires auth", lambda: test_protected_auth(client)) runner.run("protected API requires auth", lambda: test_protected_auth(client))
+2
View File
@@ -164,6 +164,8 @@ impl AutomationInput {
last_trigger_trace: vec![], last_trigger_trace: vec![],
action_zone_id: None, action_zone_id: None,
action_zone_preset: None, action_zone_preset: None,
action_zone_heat_anticipation_c: None,
action_zone_standby_offset_c: None,
action_ha_domain: None, action_ha_domain: None,
action_ha_service: None, action_ha_service: None,
action_ha_entity_id: None, action_ha_entity_id: None,
+28
View File
@@ -1563,6 +1563,8 @@ fn compile_flow(
last_trigger_trace, last_trigger_trace,
action_zone_id: None, action_zone_id: None,
action_zone_preset: None, action_zone_preset: None,
action_zone_heat_anticipation_c: None,
action_zone_standby_offset_c: None,
action_ha_domain: None, action_ha_domain: None,
action_ha_service: None, action_ha_service: None,
action_ha_entity_id: None, action_ha_entity_id: None,
@@ -1606,6 +1608,32 @@ fn compile_flow(
return Err(AppError::BadRequest("unsupported thermostat preset".into())); return Err(AppError::BadRequest("unsupported thermostat preset".into()));
} }
item.action_zone_preset = Some(preset.clone()); item.action_zone_preset = Some(preset.clone());
if let Some(value) = action_node.config.get("heat_anticipation_c") {
if !value.is_null() {
let offset = value.as_f64().ok_or_else(|| AppError::BadRequest(
"Flow heat anticipation must be numeric".into(),
))?;
if !offset.is_finite() || !(0.0..=2.5).contains(&offset) {
return Err(AppError::BadRequest(
"Flow heat anticipation must be between 0 and 2.5 C".into(),
));
}
item.action_zone_heat_anticipation_c = Some(offset);
}
}
if let Some(value) = action_node.config.get("standby_offset_c") {
if !value.is_null() {
let offset = value.as_f64().ok_or_else(|| AppError::BadRequest(
"Flow standby offset must be numeric".into(),
))?;
if !offset.is_finite() || !(0.5..=8.0).contains(&offset) {
return Err(AppError::BadRequest(
"Flow standby offset must be between 0.5 and 8 C".into(),
));
}
item.action_zone_standby_offset_c = Some(offset);
}
}
if preset == "custom" && value_binding.is_none() { if preset == "custom" && value_binding.is_none() {
let target = flow_f64(&action_node.config, "setpoint").ok_or_else(|| { let target = flow_f64(&action_node.config, "setpoint").ok_or_else(|| {
AppError::BadRequest("custom thermostat block needs a target".into()) AppError::BadRequest("custom thermostat block needs a target".into())
+12
View File
@@ -36,6 +36,8 @@ struct ZoneInput {
min_off_seconds: u64, min_off_seconds: u64,
#[serde(default = "min_adjust")] #[serde(default = "min_adjust")]
min_adjust_seconds: u64, min_adjust_seconds: u64,
#[serde(default)]
heat_anticipation_c: f64,
#[serde(default = "standby_offset")] #[serde(default = "standby_offset")]
standby_offset_c: f64, standby_offset_c: f64,
#[serde(default = "yes")] #[serde(default = "yes")]
@@ -142,6 +144,13 @@ impl ZoneInput {
"heating hysteresis must be between 0.1 and 5 C".into(), "heating hysteresis must be between 0.1 and 5 C".into(),
)); ));
} }
if !self.heat_anticipation_c.is_finite()
|| !(0.0..=2.5).contains(&self.heat_anticipation_c)
{
return Err(AppError::BadRequest(
"heat anticipation must be between 0 and 2.5 C".into(),
));
}
if !(0.5..=8.0).contains(&self.standby_offset_c) { if !(0.5..=8.0).contains(&self.standby_offset_c) {
return Err(AppError::BadRequest( return Err(AppError::BadRequest(
"standby offset must be between 0.5 and 8 C".into(), "standby offset must be between 0.5 and 8 C".into(),
@@ -205,6 +214,8 @@ impl ZoneInput {
min_on_seconds: self.min_on_seconds, min_on_seconds: self.min_on_seconds,
min_off_seconds: self.min_off_seconds, min_off_seconds: self.min_off_seconds,
min_adjust_seconds: self.min_adjust_seconds, min_adjust_seconds: self.min_adjust_seconds,
heat_anticipation_c: self.heat_anticipation_c,
heat_coasting: false,
standby_offset_c: self.standby_offset_c, standby_offset_c: self.standby_offset_c,
smart_fan: self.smart_fan, smart_fan: self.smart_fan,
sensor_source: self.sensor_source, sensor_source: self.sensor_source,
@@ -400,6 +411,7 @@ async fn update_zone(
zone.effective_mode = existing.effective_mode; zone.effective_mode = existing.effective_mode;
zone.effective_setpoint = existing.effective_setpoint; zone.effective_setpoint = existing.effective_setpoint;
zone.device_setpoint = existing.device_setpoint; zone.device_setpoint = existing.device_setpoint;
zone.heat_coasting = existing.heat_coasting;
zone.demand = existing.demand; zone.demand = existing.demand;
zone.demand_since = existing.demand_since; zone.demand_since = existing.demand_since;
zone.target_alerted_at = existing.target_alerted_at; zone.target_alerted_at = existing.target_alerted_at;
+19 -2
View File
@@ -31,7 +31,14 @@ fn flow_zone_action_already_satisfied(
device_swing_horizontal: u8, device_swing_horizontal: u8,
preset: Option<&str>, preset: Option<&str>,
action: &DeviceCommand, action: &DeviceCommand,
heat_anticipation_c: Option<f64>,
standby_offset_c: Option<f64>,
) -> bool { ) -> bool {
if heat_anticipation_c.is_some_and(|value| (zone.heat_anticipation_c - value).abs() > 0.001)
|| standby_offset_c.is_some_and(|value| (zone.standby_offset_c - value).abs() > 0.001)
{
return false;
}
let mode_satisfied = match action.mode.as_deref() { let mode_satisfied = match action.mode.as_deref() {
Some("auto") => zone.inherit_house_mode, Some("auto") => zone.inherit_house_mode,
Some(mode) if matches!(mode, "heat" | "cool") => { Some(mode) if matches!(mode, "heat" | "cool") => {
@@ -193,6 +200,8 @@ async fn run_automations(state: &AppState) -> Result<()> {
device.swing_horizontal, device.swing_horizontal,
item.action_zone_preset.as_deref(), item.action_zone_preset.as_deref(),
&effective_action, &effective_action,
item.action_zone_heat_anticipation_c,
item.action_zone_standby_offset_c,
) { ) {
let before_runtime = item.flow_runtime.clone(); let before_runtime = item.flow_runtime.clone();
flow_acknowledge_change_gates(&mut item); flow_acknowledge_change_gates(&mut item);
@@ -277,7 +286,10 @@ async fn run_automations(state: &AppState) -> Result<()> {
Err(err) => Err(AppError::BadRequest(format!("Home Assistant service action failed: {err}"))), Err(err) => Err(AppError::BadRequest(format!("Home Assistant service action failed: {err}"))),
} }
} else if let Some(zone_id) = item.action_zone_id.as_deref() { } else if let Some(zone_id) = item.action_zone_id.as_deref() {
apply_flow_zone_action(state, zone_id, item.action_zone_preset.as_deref(), &effective_action).await apply_flow_zone_action(
state, zone_id, item.action_zone_preset.as_deref(), &effective_action,
item.action_zone_heat_anticipation_c, item.action_zone_standby_offset_c,
).await
} else if let Some(group_id) = item.action_group_id.as_deref() { } else if let Some(group_id) = item.action_group_id.as_deref() {
let group_mode = effective_action.mode.as_deref().map(|mode| if mode == "auto" { "house".to_string() } else { mode.to_string() }); let group_mode = effective_action.mode.as_deref().map(|mode| if mode == "auto" { "house".to_string() } else { mode.to_string() });
control_group(state, group_id, GroupControlPatch { control_group(state, group_id, GroupControlPatch {
@@ -1105,7 +1117,10 @@ fn flow_condition_kind_runtime(kind: &str) -> bool {
) )
} }
async fn apply_flow_zone_action(state: &AppState, zone_id: &str, preset: Option<&str>, action: &DeviceCommand) -> Result<bool, AppError> { async fn apply_flow_zone_action(
state: &AppState, zone_id: &str, preset: Option<&str>, action: &DeviceCommand,
heat_anticipation_c: Option<f64>, standby_offset_c: Option<f64>,
) -> Result<bool, AppError> {
let _schedule_guard = state.lock_schedule_operation().await; let _schedule_guard = state.lock_schedule_operation().await;
// Match the canonical thermostat ordering used by Web/HA zone control. This prevents // Match the canonical thermostat ordering used by Web/HA zone control. This prevents
// a Flow action from racing a thermostat arbitration cycle that already holds a stale snapshot. // a Flow action from racing a thermostat arbitration cycle that already holds a stale snapshot.
@@ -1117,6 +1132,8 @@ async fn apply_flow_zone_action(state: &AppState, zone_id: &str, preset: Option<
// Flow power commands control only the assigned unit runtime; they never change zone.enabled. // Flow power commands control only the assigned unit runtime; they never change zone.enabled.
if !zone.enabled { return Ok(false); } if !zone.enabled { return Ok(false); }
let device_id = zone.device_id.clone(); let device_id = zone.device_id.clone();
if let Some(value) = heat_anticipation_c { zone.heat_anticipation_c = value; }
if let Some(value) = standby_offset_c { zone.standby_offset_c = value; }
if let Some(mode) = action.mode.as_deref() { if let Some(mode) = action.mode.as_deref() {
match mode { "auto" => zone.inherit_house_mode = true, "heat" | "cool" => { zone.mode = mode.to_string(); zone.inherit_house_mode = false; }, _ => return Err(AppError::BadRequest("unsupported Flow thermostat mode".into())) } match mode { "auto" => zone.inherit_house_mode = true, "heat" | "cool" => { zone.mode = mode.to_string(); zone.inherit_house_mode = false; }, _ => return Err(AppError::BadRequest("unsupported Flow thermostat mode".into())) }
} }
+1 -1
View File
@@ -638,7 +638,7 @@ fn externally_changed_control_fields(before: &Device, after: &Device, zone: &Zon
// again without user interaction. Treat that one known normalization as firmware drift, // again without user interaction. Treat that one known normalization as firmware drift,
// not as a remote-control takeover. Other fan changes remain meaningful manual input. // not as a remote-control takeover. Other fan changes remain meaningful manual input.
let standby_low_to_auto = let standby_low_to_auto =
zone.smart_fan && !zone.demand && before.fan_speed == 1 && after.fan_speed == 0; zone.smart_fan && (!zone.demand || zone.heat_coasting) && before.fan_speed == 1 && after.fan_speed == 0;
if before.fan_speed != after.fan_speed && !standby_low_to_auto { if before.fan_speed != after.fan_speed && !standby_low_to_auto {
fields.push("fan_speed".to_string()); fields.push("fan_speed".to_string());
} }
+21
View File
@@ -1,3 +1,24 @@
/// GREE stays powered during coasting: only its temperature target is lowered.
/// Separate enter/exit thresholds prevent target chatter on a noisy HA sensor.
fn heat_coasting_next_state(
was_coasting: bool,
mode: &str,
temperature: f64,
comfort_target: f64,
anticipation_c: f64,
) -> bool {
if mode != "heat" || anticipation_c <= 0.0 {
return false;
}
let enter_at = comfort_target - anticipation_c;
let resume_heat_at = enter_at - 0.4;
if was_coasting {
temperature > resume_heat_at + 1e-6
} else {
temperature + 1e-6 >= enter_at
}
}
fn select_zone_temperature( fn select_zone_temperature(
zone: &Zone, zone: &Zone,
device_temperature: Option<f64>, device_temperature: Option<f64>,
+28 -5
View File
@@ -3,6 +3,17 @@ mod tests {
use super::*; use super::*;
use chrono::TimeZone; use chrono::TimeZone;
#[test]
fn heat_coasting_modulates_setpoint_without_power_cycle() {
assert!(!heat_coasting_next_state(false, "heat", 19.8, 20.8, 0.9));
assert!(heat_coasting_next_state(false, "heat", 19.9, 20.8, 0.9));
assert!(heat_coasting_next_state(true, "heat", 19.6, 20.8, 0.9));
assert!(!heat_coasting_next_state(true, "heat", 19.5, 20.8, 0.9));
assert!(!heat_coasting_next_state(true, "heat", 21.0, 20.8, 0.0));
assert!(!heat_coasting_next_state(true, "cool", 21.0, 20.8, 0.9));
assert_eq!(round_device_setpoint("heat", false, 20.8 - 5.0), 16.0);
}
#[test] #[test]
fn thermostat_target_normalization_preserves_tenths() { fn thermostat_target_normalization_preserves_tenths() {
assert!((normalize_thermostat_target(24.2) - 24.2).abs() < 1e-9); assert!((normalize_thermostat_target(24.2) - 24.2).abs() < 1e-9);
@@ -242,7 +253,15 @@ mod tests {
..Default::default() ..Default::default()
}; };
assert!(flow_zone_action_already_satisfied( assert!(flow_zone_action_already_satisfied(
&zone, false, 0, 0, Some("custom"), &on &zone, false, 0, 0, Some("custom"), &on, None, None
));
assert!(!flow_zone_action_already_satisfied(
&zone, false, 0, 0, Some("custom"), &on, Some(1.1), Some(5.0)
));
zone.heat_anticipation_c = 1.1;
zone.standby_offset_c = 5.0;
assert!(flow_zone_action_already_satisfied(
&zone, false, 0, 0, Some("custom"), &on, Some(1.1), Some(5.0)
)); ));
zone.manual_preset = None; zone.manual_preset = None;
@@ -257,10 +276,10 @@ mod tests {
..Default::default() ..Default::default()
}; };
assert!(flow_zone_action_already_satisfied( assert!(flow_zone_action_already_satisfied(
&zone, false, 0, 0, Some("auto"), &off &zone, false, 0, 0, Some("auto"), &off, None, None
)); ));
assert!(!flow_zone_action_already_satisfied( assert!(!flow_zone_action_already_satisfied(
&zone, true, 0, 0, Some("auto"), &off &zone, true, 0, 0, Some("auto"), &off, None, None
)); ));
} }
@@ -279,10 +298,10 @@ mod tests {
..Default::default() ..Default::default()
}; };
assert!(!flow_zone_action_already_satisfied( assert!(!flow_zone_action_already_satisfied(
&zone, true, 0, 0, Some("custom"), &action &zone, true, 0, 0, Some("custom"), &action, None, None
)); ));
assert!(flow_zone_action_already_satisfied( assert!(flow_zone_action_already_satisfied(
&zone, true, 1, 0, Some("custom"), &action &zone, true, 1, 0, Some("custom"), &action, None, None
)); ));
} }
@@ -547,6 +566,8 @@ mod tests {
last_trigger_trace: vec![], last_trigger_trace: vec![],
action_zone_id: None, action_zone_id: None,
action_zone_preset: None, action_zone_preset: None,
action_zone_heat_anticipation_c: None,
action_zone_standby_offset_c: None,
action_ha_domain: None, action_ha_domain: None,
action_ha_service: None, action_ha_service: None,
action_ha_entity_id: None, action_ha_entity_id: None,
@@ -589,6 +610,8 @@ mod tests {
min_on_seconds: 180, min_on_seconds: 180,
min_off_seconds: 180, min_off_seconds: 180,
min_adjust_seconds: 120, min_adjust_seconds: 120,
heat_anticipation_c: 0.0,
heat_coasting: false,
standby_offset_c: 2.0, standby_offset_c: 2.0,
smart_fan: true, smart_fan: true,
sensor_source: source.into(), sensor_source: source.into(),
+27 -9
View File
@@ -683,6 +683,7 @@ async fn control_zones(state: &AppState) -> Result<()> {
// publishing the zone, but never overwrite manual device state while it follows // publishing the zone, but never overwrite manual device state while it follows
// the house mode. Explicit per-zone heat/cool bypasses this branch above. // the house mode. Explicit per-zone heat/cool bypasses this branch above.
if effective_mode == "off" { if effective_mode == "off" {
zone.heat_coasting = false;
zone.effective_setpoint = None; zone.effective_setpoint = None;
zone.device_setpoint = None; zone.device_setpoint = None;
zone.demand = false; zone.demand = false;
@@ -709,6 +710,7 @@ async fn control_zones(state: &AppState) -> Result<()> {
// controller stays physically OFF. This also makes gaps between schedule windows true // controller stays physically OFF. This also makes gaps between schedule windows true
// OFF periods instead of silently falling back to Comfort and recreating demand. // OFF periods instead of silently falling back to Comfort and recreating demand.
if !zone_has_active_thermostat_intent(&zone, active_schedule, Utc::now()) { if !zone_has_active_thermostat_intent(&zone, active_schedule, Utc::now()) {
zone.heat_coasting = false;
clear_compressor_pending(&mut zone, true); clear_compressor_pending(&mut zone, true);
zone.effective_mode = "off".into(); zone.effective_mode = "off".into();
zone.effective_setpoint = None; zone.effective_setpoint = None;
@@ -802,6 +804,14 @@ async fn control_zones(state: &AppState) -> Result<()> {
let half = zone.hysteresis_for_mode(effective_mode) / 2.0; let half = zone.hysteresis_for_mode(effective_mode) / 2.0;
let previous_demand = zone.demand; let previous_demand = zone.demand;
let previous_heat_coasting = zone.heat_coasting;
zone.heat_coasting = heat_coasting_next_state(
zone.heat_coasting,
effective_mode,
temp,
target,
zone.heat_anticipation_c,
);
zone.demand = match effective_mode { zone.demand = match effective_mode {
"heat" => { "heat" => {
if temp <= target - half { if temp <= target - half {
@@ -857,14 +867,17 @@ async fn control_zones(state: &AppState) -> Result<()> {
"heat" => target + outdoor_assist, "heat" => target + outdoor_assist,
_ => target - demand_assist, _ => target - demand_assist,
}; };
// Early heat coasting changes only GREE's requested temperature. It does NOT
// toggle unit power or touch compressor protection/whole-multisplit OFF policy.
let delivering_heat = zone.demand && !(effective_mode == "heat" && zone.heat_coasting);
let standby_target = match effective_mode { let standby_target = match effective_mode {
"heat" => target - zone.standby_offset_c.max(0.5), "heat" => target - zone.standby_offset_c.max(0.5),
_ => target + zone.standby_offset_c.max(0.5), _ => target + zone.standby_offset_c.max(0.5),
}; };
let desired_device_target = round_device_setpoint( let desired_device_target = round_device_setpoint(
effective_mode, effective_mode,
zone.demand, delivering_heat,
if zone.demand { if delivering_heat {
active_target active_target
} else { } else {
standby_target standby_target
@@ -878,7 +891,10 @@ async fn control_zones(state: &AppState) -> Result<()> {
None None
}; };
let demand_changed = previous_demand != zone.demand; let demand_changed = previous_demand != zone.demand
|| (effective_mode == "heat" && previous_heat_coasting != zone.heat_coasting);
let previous_delivering_heat = previous_demand
&& !(effective_mode == "heat" && previous_heat_coasting);
let desired_fan = if night_active { let desired_fan = if night_active {
let max_fan = settings.night_mode.max_fan_speed.clamp(1, 5); let max_fan = settings.night_mode.max_fan_speed.clamp(1, 5);
if zone.smart_fan { if zone.smart_fan {
@@ -888,7 +904,7 @@ async fn control_zones(state: &AppState) -> Result<()> {
temp, temp,
target, target,
outdoor_assist_temperature, outdoor_assist_temperature,
zone.demand, delivering_heat,
), ),
max_fan, max_fan,
)) ))
@@ -903,7 +919,7 @@ async fn control_zones(state: &AppState) -> Result<()> {
temp, temp,
target, target,
outdoor_assist_temperature, outdoor_assist_temperature,
zone.demand, delivering_heat,
)) ))
} else { } else {
None None
@@ -934,8 +950,8 @@ async fn control_zones(state: &AppState) -> Result<()> {
let desired_quiet = smart_quiet_command( let desired_quiet = smart_quiet_command(
zone.smart_fan, zone.smart_fan,
quiet_supported, quiet_supported,
previous_demand, previous_delivering_heat,
zone.demand, delivering_heat,
device.quiet, device.quiet,
settings.night_mode.enabled, settings.night_mode.enabled,
night_active, night_active,
@@ -1113,7 +1129,7 @@ async fn control_zones(state: &AppState) -> Result<()> {
let fan_needs_command = desired_fan let fan_needs_command = desired_fan
.map(|fan| fan != device.fan_speed) .map(|fan| fan != device.fan_speed)
.unwrap_or(false) .unwrap_or(false)
&& (zone.demand || demand_changed || core_needs_command || night_active); && (delivering_heat || demand_changed || core_needs_command || (night_active && !zone.smart_fan));
let needs_command = core_needs_command let needs_command = core_needs_command
|| fan_needs_command || fan_needs_command
|| desired_quiet || desired_quiet
@@ -1150,10 +1166,12 @@ async fn control_zones(state: &AppState) -> Result<()> {
}; };
zone.last_action_at = Some(transition_at); zone.last_action_at = Some(transition_at);
clear_compressor_pending(&mut zone, true); clear_compressor_pending(&mut zone, true);
state.log("info", "zone.setpoint_modulation", &format!("Zone {} -> {:.1} C ({})", zone.name, desired_device_target, if zone.demand { "demand" } else { "standby" }), json!({ state.log("info", "zone.setpoint_modulation", &format!("Zone {} -> {:.1} C ({})", zone.name, desired_device_target, if zone.heat_coasting { "coasting" } else if zone.demand { "demand" } else { "standby" }), json!({
"zone_id": zone.id, "zone_id": zone.id,
"room_temperature": temp, "room_temperature": temp,
"comfort_target": target, "comfort_target": target,
"heat_coasting": zone.heat_coasting,
"heat_anticipation_c": zone.heat_anticipation_c,
"device_target": desired_device_target, "device_target": desired_device_target,
"mode": effective_mode, "mode": effective_mode,
"preset": zone.active_preset, "preset": zone.active_preset,
+5
View File
@@ -83,6 +83,11 @@ pub struct Automation {
pub action_zone_id: Option<String>, pub action_zone_id: Option<String>,
#[serde(default)] #[serde(default)]
pub action_zone_preset: Option<String>, pub action_zone_preset: Option<String>,
/// Optional soft-coast parameters set by a Flow thermostat action.
#[serde(default)]
pub action_zone_heat_anticipation_c: Option<f64>,
#[serde(default)]
pub action_zone_standby_offset_c: Option<f64>,
#[serde(default)] #[serde(default)]
pub action_ha_domain: Option<String>, pub action_ha_domain: Option<String>,
#[serde(default)] #[serde(default)]
+7
View File
@@ -44,6 +44,13 @@ pub struct Zone {
/// Minimum interval between automatic setpoint/fan adjustments. /// Minimum interval between automatic setpoint/fan adjustments.
#[serde(default = "default_min_adjust")] #[serde(default = "default_min_adjust")]
pub min_adjust_seconds: u64, pub min_adjust_seconds: u64,
/// Degrees before the comfort target at which GREE enters low-heat coasting.
/// Zero disables predictive soft standby (the pre-upgrade behavior).
#[serde(default)]
pub heat_anticipation_c: f64,
/// Hysteretic coasting state; never changes the physical unit power state.
#[serde(default)]
pub heat_coasting: bool,
/// Difference applied to the AC setpoint while the room is satisfied. /// Difference applied to the AC setpoint while the room is satisfied.
#[serde(default = "default_standby_offset")] #[serde(default = "default_standby_offset")]
pub standby_offset_c: f64, pub standby_offset_c: f64,
+3
View File
@@ -1598,6 +1598,9 @@
type="number" min="15" name="min_adjust_seconds" value="120"></label><label><span type="number" min="15" name="min_adjust_seconds" value="120"></label><label><span
data-i18n="zones.standbyOffset">Standby setpoint offset °C</span><input type="text" inputmode="decimal" data-i18n="zones.standbyOffset">Standby setpoint offset °C</span><input type="text" inputmode="decimal"
step="0.5" min="0.5" max="8" name="standby_offset_c" value="2"></label></div> step="0.5" min="0.5" max="8" name="standby_offset_c" value="2"></label></div>
<label><span data-i18n="zones.heatAnticipation">Early heat coasting (°C before target)</span><input
type="text" inputmode="decimal" step="0.1" min="0" max="2.5" name="heat_anticipation_c" value="0"></label>
<p class="field-note" data-i18n="zones.heatAnticipationHint">A positive value lowers the GREE setpoint early without powering the indoor unit off. 0 disables early coasting.</p>
<label class="check"><input type="checkbox" name="smart_fan" checked> <span data-i18n="zones.smartFan">Smart fan <label class="check"><input type="checkbox" name="smart_fan" checked> <span data-i18n="zones.smartFan">Smart fan
assist</span></label> assist</span></label>
<input type="hidden" name="min_on_seconds" value="180"><input type="hidden" name="min_off_seconds" <input type="hidden" name="min_on_seconds" value="180"><input type="hidden" name="min_off_seconds"
+13 -6
View File
@@ -405,15 +405,20 @@ function buildZoneSimulation(zone, planZone) {
status = demand ? 'demand' : 'satisfied'; status = demand ? 'demand' : 'satisfied';
} }
const standbyOffset = Math.max(simulationNumeric(zone?.standby_offset_c) || 0.5, 0.5); const standbyOffset = Math.max(simulationNumeric(zone?.standby_offset_c) || 0.5, 0.5);
const assist = current == null || target == null ? 0 : simulationOutdoorAssist(mode, outdoor, current, target); const anticipation = Math.max(simulationNumeric(zone?.heat_anticipation_c) || 0, 0);
const coastEnter = target == null ? null : target - anticipation;
const heatCoasting = mode === 'heat' && anticipation > 0 && current != null && coastEnter != null
&& (zone?.heat_coasting ? current > coastEnter - 0.4 + 1e-6 : current + 1e-6 >= coastEnter);
const deliveringHeat = demand && !heatCoasting;
const assist = current == null || target == null || zone?.control_source === 'automation.flow' ? 0 : simulationOutdoorAssist(mode, outdoor, current, target);
const activeTarget = current == null || target == null ? null : (mode === 'heat' ? target + assist : target - assist); const activeTarget = current == null || target == null ? null : (mode === 'heat' ? target + assist : target - assist);
const standbyTarget = target == null ? null : (mode === 'heat' ? target - standbyOffset : target + standbyOffset); const standbyTarget = target == null ? null : (mode === 'heat' ? target - standbyOffset : target + standbyOffset);
const desiredDeviceTarget = current == null || target == null ? null : simulationRoundDeviceSetpoint(mode, demand, demand ? activeTarget : standbyTarget); const desiredDeviceTarget = current == null || target == null ? null : simulationRoundDeviceSetpoint(mode, deliveringHeat, deliveringHeat ? activeTarget : standbyTarget);
const nightActive = !!app.controlPlan?.night_mode_active; const nightActive = !!app.controlPlan?.night_mode_active;
const nightMaxFan = clamp(Number(app.controlPlan?.night_mode_max_fan_speed || 1), 1, 5); const nightMaxFan = clamp(Number(app.controlPlan?.night_mode_max_fan_speed || 1), 1, 5);
let fanSpeed = simulationNumeric(device?.fan_speed); let fanSpeed = simulationNumeric(device?.fan_speed);
if (mode !== 'off') { if (mode !== 'off') {
fanSpeed = zone?.smart_fan && current != null && target != null ? simulationSmartFanSpeed(mode, current, target, outdoor, demand) : fanSpeed; fanSpeed = zone?.smart_fan && current != null && target != null ? simulationSmartFanSpeed(mode, current, target, outdoor, deliveringHeat) : fanSpeed;
if (nightActive) { if (nightActive) {
if (zone?.smart_fan) fanSpeed = fanSpeed === 0 ? 1 : Math.min(fanSpeed ?? nightMaxFan, nightMaxFan); if (zone?.smart_fan) fanSpeed = fanSpeed === 0 ? 1 : Math.min(fanSpeed ?? nightMaxFan, nightMaxFan);
else if (fanSpeed === 0 || fanSpeed == null || fanSpeed > nightMaxFan) fanSpeed = nightMaxFan; else if (fanSpeed === 0 || fanSpeed == null || fanSpeed > nightMaxFan) fanSpeed = nightMaxFan;
@@ -423,7 +428,7 @@ function buildZoneSimulation(zone, planZone) {
? tr('simulation.manual') ? tr('simulation.manual')
: nightActive && app.settings?.night_mode?.force_quiet : nightActive && app.settings?.night_mode?.force_quiet
? tr('simulation.quietIfSupported') ? tr('simulation.quietIfSupported')
: (zone?.smart_fan ? (!demand ? tr('simulation.quietIfSupported') : tr('common.off')) : tr('simulation.manual')); : (zone?.smart_fan ? (!deliveringHeat ? tr('simulation.quietIfSupported') : tr('common.off')) : tr('simulation.manual'));
const nativeSleep = mode !== 'off' && nightActive && app.settings?.night_mode?.use_native_sleep && device?.supports_sleep === true; const nativeSleep = mode !== 'off' && nightActive && app.settings?.night_mode?.use_native_sleep && device?.supports_sleep === true;
const reasoning = !zone?.enabled || planZoneEffectiveEnabled(planZone) === false const reasoning = !zone?.enabled || planZoneEffectiveEnabled(planZone) === false
? tr('simulation.reasonDisabled') ? tr('simulation.reasonDisabled')
@@ -431,10 +436,12 @@ function buildZoneSimulation(zone, planZone) {
? tr('simulation.reasonHouseOff') ? tr('simulation.reasonHouseOff')
: current == null || target == null : current == null || target == null
? tr('simulation.reasonWaiting') ? tr('simulation.reasonWaiting')
: demand : heatCoasting
? tr('simulation.reasonCoasting', { target: target.toFixed(1), standby: standbyTarget.toFixed(1) })
: demand
? tr('simulation.reasonDemand', { mode: modeLabel(mode), target: target.toFixed(1), hysteresis: hysteresis.toFixed(1) }) ? tr('simulation.reasonDemand', { mode: modeLabel(mode), target: target.toFixed(1), hysteresis: hysteresis.toFixed(1) })
: tr('simulation.reasonSatisfied', { target: target.toFixed(1), standby: standbyTarget.toFixed(1) }); : tr('simulation.reasonSatisfied', { target: target.toFixed(1), standby: standbyTarget.toFixed(1) });
return { device, mode, current, target, demand, status, outdoor, hysteresis, assist, activeTarget, standbyTarget, desiredDeviceTarget, fanSpeed, quiet, nativeSleep, reasoning }; return { device, mode, current, target, demand, heatCoasting, status, outdoor, hysteresis, assist, activeTarget, standbyTarget, desiredDeviceTarget, fanSpeed, quiet, nativeSleep, reasoning };
} }
function simulationRuleAction(rule) { function simulationRuleAction(rule) {
+6
View File
@@ -453,6 +453,7 @@ function zoneControlOwnerLabel(zone) {
} }
if (owner === 'local_thermostat') return tr('zones.ownerLocalThermostat'); if (owner === 'local_thermostat') return tr('zones.ownerLocalThermostat');
if (source.startsWith('group:')) return tr('zones.ownerGroup'); if (source.startsWith('group:')) return tr('zones.ownerGroup');
if (source === 'automation.flow') return tr('zones.ownerFlow');
return tr('zones.ownerAutomation'); return tr('zones.ownerAutomation');
} }
@@ -478,6 +479,10 @@ function zoneRuntimeStatusLabel(zone, effectiveMode, device = null) {
if (zone.compressor_cancelled_action && device && (!device.power || (['heat', 'cool'].includes(effectiveMode) && device.mode !== effectiveMode))) return tr('zones.queueCancelledStatus'); if (zone.compressor_cancelled_action && device && (!device.power || (['heat', 'cool'].includes(effectiveMode) && device.mode !== effectiveMode))) return tr('zones.queueCancelledStatus');
if (zone.demand && device && !device.power) return tr('zones.waitingStart'); if (zone.demand && device && !device.power) return tr('zones.waitingStart');
if (zone.demand && device?.power && ['heat', 'cool'].includes(effectiveMode) && device.mode !== effectiveMode) return tr('zones.waitingMode'); if (zone.demand && device?.power && ['heat', 'cool'].includes(effectiveMode) && device.mode !== effectiveMode) return tr('zones.waitingMode');
if (zone.control_owner === 'automation' && zone.control_source === 'automation.flow') {
if (zone.demand && zone.heat_coasting && device?.power) return tr('zones.flowCoasting');
return zone.demand ? tr('zones.flowHeating') : tr('zones.flowStandby');
}
return zone.demand ? tr('zones.runningDemand') : tr('zones.satisfied'); return zone.demand ? tr('zones.runningDemand') : tr('zones.satisfied');
} }
@@ -555,6 +560,7 @@ function zoneCard(zone, detailed = true) {
<div><small>${esc(tr('zones.source'))}</small><strong>${esc(zoneStrategyLabel(zone))}</strong></div> <div><small>${esc(tr('zones.source'))}</small><strong>${esc(zoneStrategyLabel(zone))}</strong></div>
<div><small>${esc(tr('zones.hysteresis'))}</small><strong>${zone.separate_hysteresis ? `${esc(tr('mode.cool'))} ${Number(zone.cool_hysteresis ?? zone.hysteresis ?? 0.6).toFixed(1)}°C · ${esc(tr('mode.heat'))} ${Number(zone.heat_hysteresis ?? zone.hysteresis ?? 0.6).toFixed(1)}°C` : `${Number(zone.hysteresis ?? 0.6).toFixed(1)}°C`}</strong></div> <div><small>${esc(tr('zones.hysteresis'))}</small><strong>${zone.separate_hysteresis ? `${esc(tr('mode.cool'))} ${Number(zone.cool_hysteresis ?? zone.hysteresis ?? 0.6).toFixed(1)}°C · ${esc(tr('mode.heat'))} ${Number(zone.heat_hysteresis ?? zone.hysteresis ?? 0.6).toFixed(1)}°C` : `${Number(zone.hysteresis ?? 0.6).toFixed(1)}°C`}</strong></div>
<div><small>${esc(tr('zones.smartFan'))}</small><strong>${esc(tr(zone.smart_fan === false ? 'common.off' : 'common.on'))}</strong></div> <div><small>${esc(tr('zones.smartFan'))}</small><strong>${esc(tr(zone.smart_fan === false ? 'common.off' : 'common.on'))}</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>
${compressorQueuePanel(zone)} ${compressorQueuePanel(zone)}
+3 -2
View File
@@ -340,7 +340,7 @@ $('#copyZoneSettings')?.addEventListener('click', () => {
ha_outdoor_entity_id: f.elements.ha_outdoor_entity_id.value, ha_outdoor_entity_id: f.elements.ha_outdoor_entity_id.value,
enabled: f.elements.enabled.checked, enabled: f.elements.enabled.checked,
}; };
const copyFields = ['setpoint', 'cool_comfort_setpoint', 'cool_sleep_setpoint', 'cool_away_setpoint', 'heat_comfort_setpoint', 'heat_sleep_setpoint', 'heat_away_setpoint', 'hysteresis', 'cool_hysteresis', 'heat_hysteresis', 'min_on_seconds', 'min_off_seconds', 'min_adjust_seconds', 'standby_offset_c', 'external_sensor_weight_percent', 'max_sensor_difference', 'sensor_stale_after_seconds']; const copyFields = ['setpoint', 'cool_comfort_setpoint', 'cool_sleep_setpoint', 'cool_away_setpoint', 'heat_comfort_setpoint', 'heat_sleep_setpoint', 'heat_away_setpoint', 'hysteresis', 'cool_hysteresis', 'heat_hysteresis', 'min_on_seconds', 'min_off_seconds', 'min_adjust_seconds', 'standby_offset_c', 'heat_anticipation_c', 'external_sensor_weight_percent', 'max_sensor_difference', 'sensor_stale_after_seconds'];
copyFields.forEach(name => { copyFields.forEach(name => {
const input = f.elements[name]; const input = f.elements[name];
if (!input) return; if (!input) return;
@@ -583,7 +583,8 @@ $('#zoneForm').addEventListener('submit', async event => {
cool_comfort_setpoint: parseDecimal(raw.cool_comfort_setpoint), cool_sleep_setpoint: parseDecimal(raw.cool_sleep_setpoint), cool_away_setpoint: parseDecimal(raw.cool_away_setpoint), cool_comfort_setpoint: parseDecimal(raw.cool_comfort_setpoint), cool_sleep_setpoint: parseDecimal(raw.cool_sleep_setpoint), cool_away_setpoint: parseDecimal(raw.cool_away_setpoint),
heat_comfort_setpoint: parseDecimal(raw.heat_comfort_setpoint), heat_sleep_setpoint: parseDecimal(raw.heat_sleep_setpoint), heat_away_setpoint: parseDecimal(raw.heat_away_setpoint), heat_comfort_setpoint: parseDecimal(raw.heat_comfort_setpoint), heat_sleep_setpoint: parseDecimal(raw.heat_sleep_setpoint), heat_away_setpoint: parseDecimal(raw.heat_away_setpoint),
hysteresis: parseDecimal(raw.hysteresis), separate_hysteresis: form.separate_hysteresis.checked, cool_hysteresis: parseDecimal(raw.cool_hysteresis), heat_hysteresis: parseDecimal(raw.heat_hysteresis), min_on_seconds: Number(raw.min_on_seconds), min_off_seconds: Number(raw.min_off_seconds), hysteresis: parseDecimal(raw.hysteresis), separate_hysteresis: form.separate_hysteresis.checked, cool_hysteresis: parseDecimal(raw.cool_hysteresis), heat_hysteresis: parseDecimal(raw.heat_hysteresis), min_on_seconds: Number(raw.min_on_seconds), min_off_seconds: Number(raw.min_off_seconds),
min_adjust_seconds: Number(raw.min_adjust_seconds), standby_offset_c: parseDecimal(raw.standby_offset_c), smart_fan: form.smart_fan.checked, min_adjust_seconds: Number(raw.min_adjust_seconds), standby_offset_c: parseDecimal(raw.standby_offset_c),
heat_anticipation_c: parseDecimal(raw.heat_anticipation_c), smart_fan: form.smart_fan.checked,
sensor_source: raw.sensor_source, ha_entity_id: raw.ha_entity_id || null, ha_outdoor_entity_id: raw.ha_outdoor_entity_id || null, external_sensor_weight: Number(raw.external_sensor_weight_percent) / 100, max_sensor_difference: parseDecimal(raw.max_sensor_difference), sensor_stale_after_seconds: Number(raw.sensor_stale_after_seconds || 300), revision: raw.revision ? Number(raw.revision) : null sensor_source: raw.sensor_source, ha_entity_id: raw.ha_entity_id || null, ha_outdoor_entity_id: raw.ha_outdoor_entity_id || null, external_sensor_weight: Number(raw.external_sensor_weight_percent) / 100, max_sensor_difference: parseDecimal(raw.max_sensor_difference), sensor_stale_after_seconds: Number(raw.sensor_stale_after_seconds || 300), revision: raw.revision ? Number(raw.revision) : null
}; };
await runFormTask(form, async () => { await runFormTask(form, async () => {
+4 -2
View File
@@ -98,7 +98,7 @@ function flowDefaultConfig(kind) {
if (kind === 'night_mode') return {}; if (kind === 'night_mode') return {};
if (kind === 'constant') return { value: true }; if (kind === 'constant') return { value: true };
if (kind === 'shared_input') return sharedFlowReferenceDefaultConfig(app.flowSharedInputs?.[0]); if (kind === 'shared_input') return sharedFlowReferenceDefaultConfig(app.flowSharedInputs?.[0]);
if (kind === 'zone_thermostat') return { zone_id: app.zones[0]?.id || '', preset: 'comfort', setpoint: 21, mode: 'auto', power: null, swing_vertical: null, swing_horizontal: null, cooldown_seconds: 60 }; if (kind === 'zone_thermostat') return { zone_id: app.zones[0]?.id || '', preset: 'comfort', setpoint: 21, mode: 'auto', power: null, heat_anticipation_c: null, standby_offset_c: null, swing_vertical: null, swing_horizontal: null, cooldown_seconds: 60 };
if (kind === 'device_action') return { device_id: app.devices[0]?.id || '', power: true, mode: '', target_temperature: null, fan_speed: null, swing_vertical: null, swing_horizontal: null, quiet: null, turbo: null, light: null, air: null, xfan: null, health: null, sleep: null, cooldown_seconds: 60 }; if (kind === 'device_action') return { device_id: app.devices[0]?.id || '', power: true, mode: '', target_temperature: null, fan_speed: null, swing_vertical: null, swing_horizontal: null, quiet: null, turbo: null, light: null, air: null, xfan: null, health: null, sleep: null, cooldown_seconds: 60 };
if (kind === 'device_feature_action') return { device_id: app.devices[0]?.id || '', feature: 'light', value: true, cooldown_seconds: 60 }; if (kind === 'device_feature_action') return { device_id: app.devices[0]?.id || '', feature: 'light', value: true, cooldown_seconds: 60 };
if (kind === 'group_action') return { group_id: app.groups[0]?.id || '', power: true, mode: '', preset: '', setpoint: 21, cooldown_seconds: 60 }; if (kind === 'group_action') return { group_id: app.groups[0]?.id || '', power: true, mode: '', preset: '', setpoint: 21, cooldown_seconds: 60 };
@@ -813,7 +813,7 @@ function renderFlowInspector() {
else if (node.kind === 'logic_and') fields = `<p class="field-note">${esc(tr('flow.andHint'))}</p>`; else if (node.kind === 'logic_and') fields = `<p class="field-note">${esc(tr('flow.andHint'))}</p>`;
else if (node.kind === 'logic_or') fields = `<p class="field-note">${esc(tr('flow.orHint'))}</p>`; else if (node.kind === 'logic_or') fields = `<p class="field-note">${esc(tr('flow.orHint'))}</p>`;
else if (node.kind === 'logic_not') fields = `<p class="field-note">${esc(tr('flow.notHint'))}</p>`; else if (node.kind === 'logic_not') fields = `<p class="field-note">${esc(tr('flow.notHint'))}</p>`;
else if (node.kind === 'zone_thermostat') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-flow-config="zone_id">${flowSelectOptions(app.zones, c.zone_id)}</select></label><div class="two"><label><span>${esc(tr('schedules.profile'))}</span><select data-flow-config="preset">${['auto', 'comfort', 'sleep', 'away', 'custom'].map(v => `<option value="${v}" ${c.preset === v ? 'selected' : ''}>${esc(zonePresetLabel(v))}</option>`).join('')}</select></label><label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.1" data-flow-config="setpoint" value="${Number(c.setpoint ?? 21)}"></label></div><div class="two"><label><span>${esc(tr('common.mode'))}</span><select data-flow-config="mode"><option value="auto" ${c.mode === 'auto' ? 'selected' : ''}>Auto</option><option value="heat" ${c.mode === 'heat' ? 'selected' : ''}>${esc(tr('mode.heat'))}</option><option value="cool" ${c.mode === 'cool' ? 'selected' : ''}>${esc(tr('mode.cool'))}</option></select></label><label><span>${esc(tr('common.power'))}</span><select data-flow-config="power"><option value="" ${c.power == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option><option value="true" ${c.power === true ? 'selected' : ''}>${esc(tr('common.on'))}</option><option value="false" ${c.power === false ? 'selected' : ''}>${esc(tr('common.off'))}</option></select></label></div><details class="flow-device-options"><summary>${esc(tr('flow.thermostatDeviceOptions'))}</summary><div class="two">${flowOptionalLouverField(c, 'swing_vertical', 'vertical')}${flowOptionalLouverField(c, 'swing_horizontal', 'horizontal')}</div><p class="field-note">${esc(tr('flow.thermostatDeviceOptionsHint'))}</p></details><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`; else if (node.kind === 'zone_thermostat') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-flow-config="zone_id">${flowSelectOptions(app.zones, c.zone_id)}</select></label><div class="two"><label><span>${esc(tr('schedules.profile'))}</span><select data-flow-config="preset">${['auto', 'comfort', 'sleep', 'away', 'custom'].map(v => `<option value="${v}" ${c.preset === v ? 'selected' : ''}>${esc(zonePresetLabel(v))}</option>`).join('')}</select></label><label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.1" data-flow-config="setpoint" value="${Number(c.setpoint ?? 21)}"></label></div><div class="two"><label><span>${esc(tr('common.mode'))}</span><select data-flow-config="mode"><option value="auto" ${c.mode === 'auto' ? 'selected' : ''}>Auto</option><option value="heat" ${c.mode === 'heat' ? 'selected' : ''}>${esc(tr('mode.heat'))}</option><option value="cool" ${c.mode === 'cool' ? 'selected' : ''}>${esc(tr('mode.cool'))}</option></select></label><label><span>${esc(tr('common.power'))}</span><select data-flow-config="power"><option value="" ${c.power == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option><option value="true" ${c.power === true ? 'selected' : ''}>${esc(tr('common.on'))}</option><option value="false" ${c.power === false ? 'selected' : ''}>${esc(tr('common.off'))}</option></select></label></div><div class="two"><label><span>${esc(tr('zones.heatAnticipation'))}</span><input type="number" min="0" max="2.5" step="0.1" data-flow-config="heat_anticipation_c" value="${c.heat_anticipation_c == null ? '' : Number(c.heat_anticipation_c)}" placeholder="—"></label><label><span>${esc(tr('zones.standbyOffset'))}</span><input type="number" min="0.5" max="8" step="0.1" data-flow-config="standby_offset_c" value="${c.standby_offset_c == null ? '' : Number(c.standby_offset_c)}" placeholder="—"></label></div><details class="flow-device-options"><summary>${esc(tr('flow.thermostatDeviceOptions'))}</summary><div class="two">${flowOptionalLouverField(c, 'swing_vertical', 'vertical')}${flowOptionalLouverField(c, 'swing_horizontal', 'horizontal')}</div><p class="field-note">${esc(tr('flow.thermostatDeviceOptionsHint'))}</p></details><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
else if (node.kind === 'device_action') fields = `<label><span>${esc(tr('common.device'))}</span><select data-flow-config="device_id">${flowSelectOptions(app.devices, c.device_id)}</select></label>${flowActionFields(c, false)}`; else if (node.kind === 'device_action') fields = `<label><span>${esc(tr('common.device'))}</span><select data-flow-config="device_id">${flowSelectOptions(app.devices, c.device_id)}</select></label>${flowActionFields(c, false)}`;
else if (node.kind === 'device_feature_action') fields = `<label><span>${esc(tr('common.device'))}</span><select data-flow-config="device_id">${flowSelectOptions(app.devices, c.device_id)}</select></label><label><span>${esc(tr('flow.deviceFeature'))}</span><select data-flow-config="feature">${flowDeviceFeatureOptions(c.feature)}</select></label>${flowDeviceFeatureValueField(c)}<p class="field-note">${esc(tr('flow.deviceFeatureHint'))}</p><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`; else if (node.kind === 'device_feature_action') fields = `<label><span>${esc(tr('common.device'))}</span><select data-flow-config="device_id">${flowSelectOptions(app.devices, c.device_id)}</select></label><label><span>${esc(tr('flow.deviceFeature'))}</span><select data-flow-config="feature">${flowDeviceFeatureOptions(c.feature)}</select></label>${flowDeviceFeatureValueField(c)}<p class="field-note">${esc(tr('flow.deviceFeatureHint'))}</p><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
else if (node.kind === 'group_action') fields = `<label><span>${esc(tr('groups.group'))}</span><select data-flow-config="group_id">${flowSelectOptions(app.groups, c.group_id)}</select></label>${flowActionFields(c, true)}`; else if (node.kind === 'group_action') fields = `<label><span>${esc(tr('groups.group'))}</span><select data-flow-config="group_id">${flowSelectOptions(app.groups, c.group_id)}</select></label>${flowActionFields(c, true)}`;
@@ -1736,6 +1736,8 @@ function updateFlowConfig(input) {
else if (wasLouver) node.config.value = ''; else if (wasLouver) node.config.value = '';
} }
else if (node.kind === 'device_state' && key === 'value' && ['swing_vertical', 'swing_horizontal'].includes(node.config.field)) node.config.value = Number(input.value); else if (node.kind === 'device_state' && key === 'value' && ['swing_vertical', 'swing_horizontal'].includes(node.config.field)) node.config.value = Number(input.value);
else if (node.kind === 'zone_thermostat' && ['heat_anticipation_c', 'standby_offset_c'].includes(key))
node.config[key] = input.value === '' ? null : Number(input.value);
else if (['swing_vertical', 'swing_horizontal'].includes(key)) node.config[key] = input.value === '' ? null : Number(input.value); else if (['swing_vertical', 'swing_horizontal'].includes(key)) node.config[key] = input.value === '' ? null : Number(input.value);
else if (['power', 'quiet', 'turbo', 'light', 'air', 'xfan', 'health', 'sleep'].includes(key)) node.config[key] = input.value === '' ? null : input.value === 'true'; else if (['power', 'quiet', 'turbo', 'light', 'air', 'xfan', 'health', 'sleep'].includes(key)) node.config[key] = input.value === '' ? null : input.value === 'true';
else if (key === 'value' && node.kind === 'constant') node.config[key] = input.value === 'true'; else if (key === 'value' && node.kind === 'constant') node.config[key] = input.value === 'true';
+1 -1
View File
@@ -150,7 +150,7 @@ function validateForm(form) {
const zoneDecimalFields = [ const zoneDecimalFields = [
['cool_comfort_setpoint', 8, 30], ['cool_sleep_setpoint', 8, 30], ['cool_away_setpoint', 8, 30], ['cool_comfort_setpoint', 8, 30], ['cool_sleep_setpoint', 8, 30], ['cool_away_setpoint', 8, 30],
['heat_comfort_setpoint', 8, 30], ['heat_sleep_setpoint', 8, 30], ['heat_away_setpoint', 8, 30], ['heat_comfort_setpoint', 8, 30], ['heat_sleep_setpoint', 8, 30], ['heat_away_setpoint', 8, 30],
['max_sensor_difference', 0.1, 20], ['standby_offset_c', 0.5, 8], ['max_sensor_difference', 0.1, 20], ['standby_offset_c', 0.5, 8], ['heat_anticipation_c', 0, 2.5],
...(form.elements.separate_hysteresis?.checked ...(form.elements.separate_hysteresis?.checked
? [['cool_hysteresis', 0.1, 5], ['heat_hysteresis', 0.1, 5]] ? [['cool_hysteresis', 0.1, 5], ['heat_hysteresis', 0.1, 5]]
: [['hysteresis', 0.1, 5]]) : [['hysteresis', 0.1, 5]])
+1
View File
@@ -598,6 +598,7 @@ function populateZone(id) {
form.max_sensor_difference.value = Number(item.max_sensor_difference ?? 3); form.max_sensor_difference.value = Number(item.max_sensor_difference ?? 3);
form.min_adjust_seconds.value = Number(item.min_adjust_seconds ?? 120); form.min_adjust_seconds.value = Number(item.min_adjust_seconds ?? 120);
form.standby_offset_c.value = Number(item.standby_offset_c ?? 2); form.standby_offset_c.value = Number(item.standby_offset_c ?? 2);
form.heat_anticipation_c.value = Number(item.heat_anticipation_c ?? 0);
form.smart_fan.checked = item.smart_fan !== false; form.smart_fan.checked = item.smart_fan !== false;
form.separate_hysteresis.checked = item.separate_hysteresis === true; form.separate_hysteresis.checked = item.separate_hysteresis === true;
const commonHysteresis = Number(item.hysteresis ?? 0.6); const commonHysteresis = Number(item.hysteresis ?? 0.6);