From 8a7501e4ab81b5b5e6e79cc7849c30336e20244e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Mon, 28 Sep 2026 14:30:44 +0200 Subject: [PATCH] v0.15.21 --- Cargo.lock | 2 +- Cargo.toml | 2 +- FILE_MANIFEST.sha256 | 28 +++---- SOURCE_MANIFEST.sha256 | 26 +++--- docs/FLOW.md | 2 +- .../gree_controller/manifest.json | 2 +- .../repository/gree-controller/config.yaml | 2 +- src/engine/automations.rs | 84 +++++++++++++++++++ src/engine/tests.rs | 59 +++++++++++++ 9 files changed, 175 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 67dfa87..0b7f26b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -719,7 +719,7 @@ dependencies = [ [[package]] name = "gree-controller" -version = "0.15.20" +version = "0.15.21" dependencies = [ "aes", "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index 7427570..e4a54f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gree-controller" -version = "0.15.20" +version = "0.15.21" edition = "2021" authors = ["GREE Controller contributors"] description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support" diff --git a/FILE_MANIFEST.sha256 b/FILE_MANIFEST.sha256 index 781d86c..49e4e0c 100644 --- a/FILE_MANIFEST.sha256 +++ b/FILE_MANIFEST.sha256 @@ -1,14 +1,14 @@ de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore -3b388654ec7089465c8d45a8e8a638f237955e742d8145563a617ac7fc673584 ./Cargo.lock -ab2b0e10ea3a02b56d689f994bd89fa71a61eff49306995e725f7104c122b53d ./Cargo.toml +9c5b11e08d0ba943d28d8523cc28da4f96fc82fd5a9b33379db6f8cb11559c52 ./Cargo.lock +37714bcb89253514dbe5f5c60db51320884dbd7e8b72e0a3716746ec48906eaf ./Cargo.toml 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE -98217f563d7ed22f8e36a8092cfb3fdba7efe217fea543f7edae4a836a38baab ./README.md -9b99f30c22e96e328366a8b3f5ea88be0a6405e6675b27b54c66e5ba16a2c1c4 ./SOURCE_MANIFEST.sha256 +993a7cc4670f28ba5a07de387cb1af7914e65530e5ba385d3f6b3d176a1a3ea1 ./README.md +ea169f0015ebed8e9ccda7623b68990fb44aa160d09434f15e9a895d7513e7c1 ./SOURCE_MANIFEST.sha256 29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs -41e1b38bb5378e1fbcf59e2a956008eea2b3141cfef102c72cf88d28f5f4cf65 ./docs/API.md -eab8cbca46f87ffcba537aa882e671a6c0ac368856f3aa13896617d272e7ae02 ./docs/FLOW.md -a273744fab39afc79defa0957d8f4a404abb222f32f35316075521cb3a6cc4b0 ./docs/openapi.json +8f4a11ee2abc781da91821bd35053b510ce96694715f446eabee680be05bac88 ./docs/API.md +1b4f9b1f5b06aec9ba08176ccc9730fb8827ff93ef8a31bfdd5e60a2169084ef ./docs/FLOW.md +cdf6bbc5c1bec268fcd89be5179d5ca150785a475d92cbab5d98a1b2bbb3d31e ./docs/openapi.json 77b8becf7a2c16a384ea85721d878b76e2d520549cb4455d9aa806e57e5a2ecd ./ha-addon/.env.example 0ca6e58170c2b8925194431ebbdb55f0c8b377fec5cd422cd2bd257e2d9328e3 ./ha-addon/Dockerfile 0951696a2c2e66d4d969f8e7f8641184e3012f261583b31074bc38a7a1c0cc1a ./ha-addon/README.md @@ -22,7 +22,7 @@ b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/hom 6509286c7b7de638dcc5d40ce53181e65d1eddc5dc36a13306dc6d87220fedbb ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py 5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png -8cf9ced849dae67f6e9eb2cac0ede74232256562d9c2cbf6e144de0a540fb2ba ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json +ea5ad1d3caa6a9f565d8ee432c3010f72f46c3aca44f4501627ba8dd63aa3e58 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py 39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py @@ -30,10 +30,10 @@ cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/hom a745cc458c235158d5d70b6a33d8000fd8b40ff93bfe2f2796af0f4b3a9eef9b ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json 50b7d6fe6329ad4ea6bee9612ab141c2f73f8382fcc2af0bb076a8e8d4dd4465 ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json -fb9543f96b5f2cc6d464f102209c2fc1953e90f42a70ddf9610f70f0cea66b16 ./ha-addon/repository/gree-controller/CHANGELOG.md +c39535dc96c7cbb8b23d9a724b6a3ad927419cba06be39913a05dd6ddbf434a9 ./ha-addon/repository/gree-controller/CHANGELOG.md 50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md -aa4f0fea858a37cb5d0611cfe7791a09ba6dafed44fbd725f0236e6b9a9da086 ./ha-addon/repository/gree-controller/README.md -539c6da3413640b4f468ae1f9884d4781342ed0bf3b75dd8342c6c8c4df6c406 ./ha-addon/repository/gree-controller/config.yaml +849d2123d1de2901425ceb1654662f0a320bf5d0ebf60706beb806e4a0b05bd4 ./ha-addon/repository/gree-controller/README.md +a14199621f683d28e2b465f0d1a8eac1d3a30d565dd63262016e6ac5a1cc9a47 ./ha-addon/repository/gree-controller/config.yaml 3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png 0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png 2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml @@ -85,7 +85,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind 804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md -c9fbd7e49d4a866bf085a1fe5d18df294f63a0698962a1ffb956f96fa7b3c500 ./scripts/api_dev_test.py +0f94af93d556821d566a6251a9f6601071bce7f74d8f4ad0d813dbeac529cc9d ./scripts/api_dev_test.py 5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh 6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh 1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh @@ -136,7 +136,7 @@ d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/sched 28ee42d08147b0f154af818065d7d014d74ee8ddd59fdef98115f5c5368056cc ./src/db/tests.rs f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs 0e49ea4a52017b0a8501d5101b1906a7a1e8d5158acb0bb65caa43cde2836ec0 ./src/engine.rs -b187f88dd821940a7c44ab57e49d46423c6d039ba3ebb8ba2601fb08e268b7fa ./src/engine/automations.rs +7e584307145cae0056f894a659529d3fddc3d81cf766fbc78194881b477be2e2 ./src/engine/automations.rs 1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs 357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs 828657b3c13b68792991b5ca2a26ae0b1d5c417191c83c4e5f650f62b6440096 ./src/engine/control_plan.rs @@ -152,7 +152,7 @@ c73691c3637465ab0f79aecf7f5428d0317799a8ab5329ed564cf57963bbe86e ./src/engine/r b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs 682c8b3956c657b655746cbd19ee97ee2f4cf953bc9de67d6d70a1b14f3dd746 ./src/engine/temperature.rs 833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs -cea4b27a419fd8eb92f755cd19c79c70bc164f2f44199a9253587df574f534f1 ./src/engine/tests.rs +2c169af5e182fb338d97612326f4c6da6e594989f15b6e00337034474c0314f0 ./src/engine/tests.rs c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs 87ab87d434a6462a2c3f25249b42c59ecb2bca6ed700ec73e95e4613349cf16f ./src/engine/zone_control.rs bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs diff --git a/SOURCE_MANIFEST.sha256 b/SOURCE_MANIFEST.sha256 index ce0b9f5..98cbeec 100644 --- a/SOURCE_MANIFEST.sha256 +++ b/SOURCE_MANIFEST.sha256 @@ -1,13 +1,13 @@ de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore -3b388654ec7089465c8d45a8e8a638f237955e742d8145563a617ac7fc673584 ./Cargo.lock -ab2b0e10ea3a02b56d689f994bd89fa71a61eff49306995e725f7104c122b53d ./Cargo.toml +9c5b11e08d0ba943d28d8523cc28da4f96fc82fd5a9b33379db6f8cb11559c52 ./Cargo.lock +37714bcb89253514dbe5f5c60db51320884dbd7e8b72e0a3716746ec48906eaf ./Cargo.toml 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE -98217f563d7ed22f8e36a8092cfb3fdba7efe217fea543f7edae4a836a38baab ./README.md +993a7cc4670f28ba5a07de387cb1af7914e65530e5ba385d3f6b3d176a1a3ea1 ./README.md 29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs -41e1b38bb5378e1fbcf59e2a956008eea2b3141cfef102c72cf88d28f5f4cf65 ./docs/API.md -eab8cbca46f87ffcba537aa882e671a6c0ac368856f3aa13896617d272e7ae02 ./docs/FLOW.md -a273744fab39afc79defa0957d8f4a404abb222f32f35316075521cb3a6cc4b0 ./docs/openapi.json +8f4a11ee2abc781da91821bd35053b510ce96694715f446eabee680be05bac88 ./docs/API.md +1b4f9b1f5b06aec9ba08176ccc9730fb8827ff93ef8a31bfdd5e60a2169084ef ./docs/FLOW.md +cdf6bbc5c1bec268fcd89be5179d5ca150785a475d92cbab5d98a1b2bbb3d31e ./docs/openapi.json 77b8becf7a2c16a384ea85721d878b76e2d520549cb4455d9aa806e57e5a2ecd ./ha-addon/.env.example 0ca6e58170c2b8925194431ebbdb55f0c8b377fec5cd422cd2bd257e2d9328e3 ./ha-addon/Dockerfile 0951696a2c2e66d4d969f8e7f8641184e3012f261583b31074bc38a7a1c0cc1a ./ha-addon/README.md @@ -21,7 +21,7 @@ b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/hom 6509286c7b7de638dcc5d40ce53181e65d1eddc5dc36a13306dc6d87220fedbb ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py 5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png -8cf9ced849dae67f6e9eb2cac0ede74232256562d9c2cbf6e144de0a540fb2ba ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json +ea5ad1d3caa6a9f565d8ee432c3010f72f46c3aca44f4501627ba8dd63aa3e58 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py 39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py @@ -29,10 +29,10 @@ cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/hom a745cc458c235158d5d70b6a33d8000fd8b40ff93bfe2f2796af0f4b3a9eef9b ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json 50b7d6fe6329ad4ea6bee9612ab141c2f73f8382fcc2af0bb076a8e8d4dd4465 ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json -fb9543f96b5f2cc6d464f102209c2fc1953e90f42a70ddf9610f70f0cea66b16 ./ha-addon/repository/gree-controller/CHANGELOG.md +c39535dc96c7cbb8b23d9a724b6a3ad927419cba06be39913a05dd6ddbf434a9 ./ha-addon/repository/gree-controller/CHANGELOG.md 50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md -aa4f0fea858a37cb5d0611cfe7791a09ba6dafed44fbd725f0236e6b9a9da086 ./ha-addon/repository/gree-controller/README.md -539c6da3413640b4f468ae1f9884d4781342ed0bf3b75dd8342c6c8c4df6c406 ./ha-addon/repository/gree-controller/config.yaml +849d2123d1de2901425ceb1654662f0a320bf5d0ebf60706beb806e4a0b05bd4 ./ha-addon/repository/gree-controller/README.md +a14199621f683d28e2b465f0d1a8eac1d3a30d565dd63262016e6ac5a1cc9a47 ./ha-addon/repository/gree-controller/config.yaml 3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png 0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png 2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml @@ -84,7 +84,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind 804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md -c9fbd7e49d4a866bf085a1fe5d18df294f63a0698962a1ffb956f96fa7b3c500 ./scripts/api_dev_test.py +0f94af93d556821d566a6251a9f6601071bce7f74d8f4ad0d813dbeac529cc9d ./scripts/api_dev_test.py 5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh 6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh 1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh @@ -135,7 +135,7 @@ d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/sched 28ee42d08147b0f154af818065d7d014d74ee8ddd59fdef98115f5c5368056cc ./src/db/tests.rs f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs 0e49ea4a52017b0a8501d5101b1906a7a1e8d5158acb0bb65caa43cde2836ec0 ./src/engine.rs -b187f88dd821940a7c44ab57e49d46423c6d039ba3ebb8ba2601fb08e268b7fa ./src/engine/automations.rs +7e584307145cae0056f894a659529d3fddc3d81cf766fbc78194881b477be2e2 ./src/engine/automations.rs 1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs 357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs 828657b3c13b68792991b5ca2a26ae0b1d5c417191c83c4e5f650f62b6440096 ./src/engine/control_plan.rs @@ -151,7 +151,7 @@ c73691c3637465ab0f79aecf7f5428d0317799a8ab5329ed564cf57963bbe86e ./src/engine/r b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs 682c8b3956c657b655746cbd19ee97ee2f4cf953bc9de67d6d70a1b14f3dd746 ./src/engine/temperature.rs 833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs -cea4b27a419fd8eb92f755cd19c79c70bc164f2f44199a9253587df574f534f1 ./src/engine/tests.rs +2c169af5e182fb338d97612326f4c6da6e594989f15b6e00337034474c0314f0 ./src/engine/tests.rs c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs 87ab87d434a6462a2c3f25249b42c59ecb2bca6ed700ec73e95e4613349cf16f ./src/engine/zone_control.rs bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs diff --git a/docs/FLOW.md b/docs/FLOW.md index dc5e87f..28ef735 100644 --- a/docs/FLOW.md +++ b/docs/FLOW.md @@ -60,7 +60,7 @@ Dry-run never mutates thermostats, devices, groups, schedules or automations. Fo ## Blocks -Condition/source blocks include weekday, time range, date range, optional 5-field CRON triggers, application Night mode, outdoor/device/zone temperature, house mode, device state, thermostat/zone state, group state, Home Assistant state/numeric/attribute/availability, rolling mean/median windows, numeric oscillation detection, a diagnostic constant, `heating_curve`, and `shared_input`. Stateful gates include `stable_for`, bounded `state_duration`, `on_change` (condition-result or raw-value edge), `rate_limit` (maximum successful executions in a rolling period), and `delay`. Their runtime state, together with rolling statistics and oscillation samples, is persisted in the generated automation payload so restarts do not silently reset timing/history. Editing a stateful block resets only that block's incompatible runtime state. Flow state continues to be observed while the action itself is in cooldown. `on_change` latches a detected event while its upstream condition remains true and clears it only after a successful downstream action, so cooldown, ownership suppression or same-cycle target arbitration cannot silently consume the edge. Rolling mean/median blocks wait for one complete configured time window before matching and restart that warm-up after their source becomes unavailable. A `rate_limit` block must be placed directly before an action and consumes quota only after a successful execution; suppressed or failed actions do not consume it. Shared inputs are configured centrally in Home Assistant / Sensors and referenced by ID. Shared inputs store reusable value sources only; operators and comparison values are configured exclusively in each Flow reference. Logic blocks are AND, OR and NOT. +Condition/source blocks include weekday, time range, date range, optional 5-field CRON triggers, application Night mode, outdoor/device/zone temperature, house mode, device state, thermostat/zone state, group state, Home Assistant state/numeric/attribute/availability, rolling mean/median windows, numeric oscillation detection, a diagnostic constant, `heating_curve`, and `shared_input`. Stateful gates include `stable_for`, bounded `state_duration`, `on_change` (condition-result or raw-value edge), `rate_limit` (maximum successful executions in a rolling period), and `delay`. Their runtime state, together with rolling statistics and oscillation samples, is persisted in the generated automation payload so restarts do not silently reset timing/history. Editing a stateful block resets only that block's incompatible runtime state. Flow state continues to be observed while the action itself is in cooldown. Repeated thermostat actions whose requested mode/preset/target/power state is already satisfied are treated as no-ops before target arbitration, so persistent level conditions do not generate recurring `automation.fired` / `automation.conflict` noise. `on_change` latches a detected event while its upstream condition remains true and clears it only after a successful downstream action, so cooldown, ownership suppression or same-cycle target arbitration cannot silently consume the edge. Rolling mean/median blocks wait for one complete configured time window before matching and restart that warm-up after their source becomes unavailable. A `rate_limit` block must be placed directly before an action and consumes quota only after a successful execution; suppressed or failed actions do not consume it. Shared inputs are configured centrally in Home Assistant / Sensors and referenced by ID. Shared inputs store reusable value sources only; operators and comparison values are configured exclusively in each Flow reference. Logic blocks are AND, OR and NOT. `heating_curve` is a numeric transform block. Its X axis is the selected temperature source (`outdoor_temperature`, a device temperature, a zone temperature, or a numeric Home Assistant entity) and its Y axis is the requested target temperature. The editor renders the configured X/Y points as a chart; runtime evaluation linearly interpolates between adjacent points and clamps values outside the configured X range to the nearest endpoint. If the source is unavailable, the block fails closed and the downstream action does not execute. diff --git a/ha-addon/home-assistant/custom_components/gree_controller/manifest.json b/ha-addon/home-assistant/custom_components/gree_controller/manifest.json index 553521e..0db1beb 100644 --- a/ha-addon/home-assistant/custom_components/gree_controller/manifest.json +++ b/ha-addon/home-assistant/custom_components/gree_controller/manifest.json @@ -1,7 +1,7 @@ { "domain": "gree_controller", "name": "GREE Controller", - "version": "0.15.20", + "version": "0.15.21", "config_flow": true, "integration_type": "hub", "iot_class": "local_polling", diff --git a/ha-addon/repository/gree-controller/config.yaml b/ha-addon/repository/gree-controller/config.yaml index 2257506..f0be1d3 100644 --- a/ha-addon/repository/gree-controller/config.yaml +++ b/ha-addon/repository/gree-controller/config.yaml @@ -1,5 +1,5 @@ name: "GREE Controller" -version: "0.15.20" +version: "0.15.21" slug: "gree_controller" description: "Local GREE HVAC controller with Web UI, API, and Home Assistant integration" url: "https://git.linuxiarz.pl/gru/gree-controller-ha-addon/" diff --git a/src/engine/automations.rs b/src/engine/automations.rs index 96bcac0..a53f5c1 100644 --- a/src/engine/automations.rs +++ b/src/engine/automations.rs @@ -12,6 +12,64 @@ fn device_has_thermostat_zone(device_id: &str, zones: &[Zone]) -> bool { zones.iter().any(|zone| zone.device_id == device_id) } +fn flow_zone_action_already_satisfied( + zone: &Zone, + device_power: bool, + device_swing_vertical: u8, + device_swing_horizontal: u8, + preset: Option<&str>, + action: &DeviceCommand, +) -> bool { + let mode_satisfied = match action.mode.as_deref() { + Some("auto") => zone.inherit_house_mode, + Some(mode) if matches!(mode, "heat" | "cool") => { + !zone.inherit_house_mode && zone.mode == mode + } + Some(_) => false, + None => true, + }; + if !mode_satisfied { + return false; + } + + let preset_satisfied = match preset.unwrap_or("auto") { + "auto" => zone.manual_preset.is_none() + && zone.manual_setpoint.is_none() + && zone.manual_override_until.is_none(), + "custom" => action.target_temperature.is_some_and(|target| { + zone.manual_preset.as_deref() == Some("custom") + && zone.manual_setpoint.is_some_and(|value| { + (value - normalize_thermostat_target(target.clamp(8.0, 30.0))).abs() < 0.05 + }) + }), + preset @ ("comfort" | "sleep" | "away") => { + zone.manual_preset.as_deref() == Some(preset) && zone.manual_setpoint.is_none() + } + _ => false, + }; + if !preset_satisfied { + return false; + } + + let power_satisfied = match action.power { + Some(false) => !device_power + && !zone.demand + && zone.effective_mode == "off" + && zone.device_setpoint.is_none(), + // A Flow thermostat ON action establishes durable thermostat intent. Once the same + // mode/preset/target is already present, the normal zone controller owns retries, + // compressor lockout and physical power-on. Re-firing the Flow would only refresh + // metadata and produce duplicate automation.fired events. + Some(true) | None => true, + }; + if !power_satisfied { + return false; + } + + action.swing_vertical.map(|value| value == device_swing_vertical).unwrap_or(true) + && action.swing_horizontal.map(|value| value == device_swing_horizontal).unwrap_or(true) +} + async fn run_automations(state: &AppState) -> Result<()> { if state.settings.read().await.emergency_stop_enabled { return Ok(()); @@ -109,6 +167,32 @@ async fn run_automations(state: &AppState) -> Result<()> { let zones = state.db.list_zones()?; let groups = state.db.list_groups()?; + // Level-style Flow conditions (stable_for + rate_limit) intentionally remain true + // after they first fire. Do not execute or log the same thermostat command forever. + // This check happens before same-cycle target arbitration so two equivalent OFF + // branches do not manufacture automation.conflict noise either. + if let Some(zone_id) = item.action_zone_id.as_deref() { + if let Some(zone) = zones.iter().find(|zone| zone.id == zone_id) { + if let Some(device) = state.db.get_device(&zone.device_id)? { + if flow_zone_action_already_satisfied( + zone, + device.power, + device.swing_vertical, + device.swing_horizontal, + item.action_zone_preset.as_deref(), + &effective_action, + ) { + let before_runtime = item.flow_runtime.clone(); + flow_acknowledge_change_gates(&mut item); + if item.flow_runtime != before_runtime { + state.db.save_automation(&item)?; + } + continue; + } + } + } + } + if item.action_group_id.is_none() && item.action_zone_id.is_none() && device_blocked_by_disabled_zone(&item.action_device_id, &zones) && effective_action.power != Some(true) diff --git a/src/engine/tests.rs b/src/engine/tests.rs index f29b949..e76a44a 100644 --- a/src/engine/tests.rs +++ b/src/engine/tests.rs @@ -227,6 +227,65 @@ mod tests { assert!(cron_matches("0 8 * * 1-7", &sunday)); } + #[test] + fn flow_zone_action_noop_detects_persisted_on_intent_and_real_off_state() { + let mut zone = test_zone("device"); + zone.inherit_house_mode = false; + zone.mode = "heat".into(); + zone.manual_preset = Some("custom".into()); + zone.manual_setpoint = Some(20.0); + zone.effective_mode = "heat".into(); + let on = DeviceCommand { + power: Some(true), + mode: Some("heat".into()), + target_temperature: Some(20.0), + ..Default::default() + }; + assert!(flow_zone_action_already_satisfied( + &zone, false, 0, 0, Some("custom"), &on + )); + + zone.manual_preset = None; + zone.manual_setpoint = None; + zone.manual_override_until = None; + zone.effective_mode = "off".into(); + zone.device_setpoint = None; + zone.demand = false; + let off = DeviceCommand { + power: Some(false), + mode: Some("heat".into()), + ..Default::default() + }; + assert!(flow_zone_action_already_satisfied( + &zone, false, 0, 0, Some("auto"), &off + )); + assert!(!flow_zone_action_already_satisfied( + &zone, true, 0, 0, Some("auto"), &off + )); + } + + #[test] + fn flow_zone_action_noop_keeps_auxiliary_swing_changes_executable() { + let mut zone = test_zone("device"); + zone.inherit_house_mode = false; + zone.mode = "heat".into(); + zone.manual_preset = Some("custom".into()); + zone.manual_setpoint = Some(20.0); + let action = DeviceCommand { + power: Some(true), + mode: Some("heat".into()), + target_temperature: Some(20.0), + swing_vertical: Some(1), + ..Default::default() + }; + assert!(!flow_zone_action_already_satisfied( + &zone, true, 0, 0, Some("custom"), &action + )); + assert!(flow_zone_action_already_satisfied( + &zone, true, 1, 0, Some("custom"), &action + )); + } + #[test] fn flow_timed_gate_requires_continuity_and_resets_on_false() { let mut state = crate::models::FlowRuntimeNodeState::default();