This commit is contained in:
Mateusz Gruszczyński committed 2026-09-23 09:24:15 +02:00
1 parent 0148daf410
commit f08613a254
23 files changed
+290 -70

No files matched your search

Generated
+1 -1
View File
@@ -719,7 +719,7 @@ dependencies = [
[[package]]
name = "gree-controller"
version = "0.15.17"
version = "0.15.18"
dependencies = [
"aes",
"aes-gcm",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "gree-controller"
version = "0.15.17"
version = "0.15.18"
edition = "2021"
authors = ["GREE Controller contributors"]
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
+22 -22
View File
@@ -1,14 +1,14 @@
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
4051d2de7fa9858545582dc6bb5a411c980a282d0b25aa3766df88597442f94a ./Cargo.lock
4bf5b286ffb52b22a32ef3d2d5e4dfaae600c1be01c1cac0b87c7138ecb4a129 ./Cargo.toml
dfac61e5ea474221798f0a72d6cb4d6b5a31bcd853573e14c172087c2f7e98c5 ./Cargo.lock
f8e6fa48a584d6446e6360185b0ca320379b325d62afc062e7d2efedc2b14af1 ./Cargo.toml
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
9a429e8b766c66061e85ccd535cc1429fe1284d14fcf75daa362fea724e0bf85 ./README.md
03d73e4b4c0036d2cc07d8057f25ae8c9d89451eebfb43715ee51aa06edd1c93 ./SOURCE_MANIFEST.sha256
591cf7e1fc98bbcdf4854c28ffc22df92b47ac7cd6e81bfa07ae4f99a9baaa3c ./README.md
7da5e80fea6c3ba99fc7d66224d377dcaa15c287a2a4a16acef847aeb1e0aa44 ./SOURCE_MANIFEST.sha256
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
3c1fce0f3097e7860f8ccf6a79749af4e7e4bd43e612c6419d91c0e620480506 ./docs/API.md
312ca0de508126a473cad4a06d35eb5b44086de37f6d0f5f4d9f5fa85e5992fe ./docs/API.md
43af7941ec4d427f9db7ab3cc6baf3d182a72dba7a6791f14094fee138324d8d ./docs/FLOW.md
3ef573d60de5dcba3fdb4004df53ea170440cbeae2e244a1f6acfcb3255b7c78 ./docs/openapi.json
d3702f852b4427c67043d16d3edab40be23a1ebf9a3baf44902ef764241643dd ./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
5aea34a2adb03ed46a93199e943d7820e34825658fefc3d7c3eb5267ac9066b0 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
813f932b99eda416330688504c67872148b6aeae27042f93505085cbfe06a3ed ./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
69d6cf26824f8851cdf076f87c7270d6f87717a2820b68b57a82beed53d2c736 ./ha-addon/repository/gree-controller/CHANGELOG.md
e8b1f335c9c7c5e3510aad8961f8f3e67d9fdd711d5407f1adb9db1746fd2407 ./ha-addon/repository/gree-controller/CHANGELOG.md
50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md
d3d0e54672751a3400021ffd1fdcc83a9a903be72cff32cdc3607a61aec0a31e ./ha-addon/repository/gree-controller/README.md
c591ab21837150e313407a34a5a3758302aaafd91cd5fcebe18bb5358b702433 ./ha-addon/repository/gree-controller/config.yaml
cf3e7106bb516598ca7d635be3d00807af024a9448632d89ee1a60d139c1c573 ./ha-addon/repository/gree-controller/README.md
0733d428c986e22c0300c3d8a82971dc1d6cadc88d38c928667c74c93638b5df ./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
@@ -43,8 +43,8 @@ cea12bc74c066a588a5ef0ef7bad9d4906bdbcea99f2d275429b0eefc94b4b5f ./ha-addon/run
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
859875af2aad9929fe6ecfb80244c8d09182345edd99663e54b04910268e902e ./lang/en.json
7139d82eb65341e72ad978be3f57efbe04228b2101b19f792d665d146697d24f ./lang/pl.json
c5c95c19ceac8969435c6f7742c86e7efa4db9bf723a0b30b098acf7fe6295fa ./lang/en.json
9ebf7bcfd49220e828336f551251ffc86986772864e75a5a74493cee27929749 ./lang/pl.json
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
@@ -85,7 +85,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
5969f2442a8d61ac5b1303f4cd6d8dcfe80596ef5427a513ae3addb126bf0273 ./scripts/api_dev_test.py
ff01f3a4b69bbd58e824eb6dfb8d36568c189cbc38bea21417b91ae81c4c43dc ./scripts/api_dev_test.py
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
@@ -101,13 +101,13 @@ b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/upda
ea84f5230da97447c60dfb4f8dadd0747c92fd1f7a74d5a65d6fdb6f23cf4b6d ./src/api.rs
0db0d81da4b8161004ddcbed2096812c285ecec9d944771670e6c8edf0775175 ./src/api/assets.rs
1139dacbd3f6ff94bf721eb5a60d469016fb935ddcf484d71bfeb249b72c833a ./src/api/auth.rs
9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs
a652409de6e2e2897d67d19dc16a7d4209c5c35afda39081fef627a098a739b4 ./src/api/automations.rs
fffad7874465739cd6b508e9d7791387b864fa88420ce0f7e2fc933d129a2751 ./src/api/configuration.rs
19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs
ca21f6d5a407e2e3532899d53ba4e8682c2fe961a46d8cd870ddc459721513ef ./src/api/device_groups.rs
8f92909dc575ea6238f4a003f8bd4056c2752fa3247b9ab226e47f875c80b916 ./src/api/devices.rs
4b5b784670ae761a6848b9a33fe3fb391bebd39d65e87936907428711e96ca44 ./src/api/events.rs
ca5bc21e051222dcaf0c507e84cb3e60f52a0b612a6056847fa0aee3a4d1a672 ./src/api/flows.rs
b1e9567cae7036af51945ce1219f537567c944c0402c2ff03edb1249403670b5 ./src/api/flows.rs
82c02fa135132ea3ebdb4d88dd1210dc536d22f987151d7f82595ee3d3d03440 ./src/api/gree_cloud.rs
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
b6d586bc42b166ac05648f3da7d9aeb88efde2e9eaab5642685eb4d9cf15f95a ./src/api/history.rs
@@ -136,10 +136,10 @@ d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/sched
28ee42d08147b0f154af818065d7d014d74ee8ddd59fdef98115f5c5368056cc ./src/db/tests.rs
f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs
0e49ea4a52017b0a8501d5101b1906a7a1e8d5158acb0bb65caa43cde2836ec0 ./src/engine.rs
f3cc7d2cbf44180372d987d7502409547eadb1c725bcce6eeb5a26cc4277b83f ./src/engine/automations.rs
8c8d45fa9f37c724a758128726403cf64a2af2ab4e063f1b3e74e4986082de79 ./src/engine/automations.rs
1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs
357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs
315116a0dbeeba1af2f72513ea5fcc238da1b5ce9501b039c5188e44976978dc ./src/engine/control_plan.rs
828657b3c13b68792991b5ca2a26ae0b1d5c417191c83c4e5f650f62b6440096 ./src/engine/control_plan.rs
7b48a33343d519a2ff2d81e9d757eb56a0c1beacfc0b7b8ef61f49c2ad363f9a ./src/engine/deadlines.rs
519f67ab805f61afea107215f5df50562fa1669a48334cc015570952e25e773a ./src/engine/energy.rs
e350aa63f3bb14ca887eaaa7555edbeae00fb584cbcaf552cc972f0c8918f5d8 ./src/engine/groups.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
bf1a3840135a71ce64148559cce0fbd570ff99d31c8972cdea7447a49f6868f7 ./src/engine/tests.rs
0d4f2df3c3881aceb41b9f8d78adde7ecbb5b39d2f8aea444b865166a0670dec ./src/engine/tests.rs
c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs
87ab87d434a6462a2c3f25249b42c59ecb2bca6ed700ec73e95e4613349cf16f ./src/engine/zone_control.rs
bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs
@@ -163,8 +163,8 @@ da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb
67e42a92295480d2f1d364958f184654cfb5d4b77e7e1df47f588bdec18868a0 ./src/influxdb/write.rs
62f9da6ef4f5fd92701d0857773f9f6236ef25356f21b9b42236abab6ba7da53 ./src/main.rs
23688a5cb68efded89471ce959328d27adff370d451bd9f378a283ab51437e76 ./src/models.rs
e7b6e49d369aaf665a8aea2464950fd795957de0ad7528a002e11c55c7983f35 ./src/models/automation.rs
a7a35e60318a620d411ec16ab8faecd34410ccba3310f612e89c06904a16539b ./src/models/control_plan.rs
a7d40ff2b00c6352223bd6335dc734925c42d70be2c70d1cb0b23051baa6f867 ./src/models/automation.rs
e4195a40135f408302a78cb200a54e6ef1c0e6031fb069a2431d068e5dbbdd8d ./src/models/control_plan.rs
06357b352ff6494d83e72796b869573a8b8f36b835985a5900ac571c922b8da9 ./src/models/defaults.rs
698dcbeba72037a251c25d07c95833f121716d949f9217f1f9429e8cd40638e5 ./src/models/device.rs
aeab5b5acf35d38a54fd1d82e964fe56635ade69c0149ad423df317095d01555 ./src/models/flow.rs
@@ -199,7 +199,7 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
d8a602ed8906d77593bd140315ce1bd56716038e789f53ff31762e9b3cb7220d ./src/state.rs
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
1d6fa9e291ebbae40a7a8b06a7cef9dbd0ee06d88ec6b664361c37cb6c1aa281 ./web/css/styles.css
da9cb50e003fa25989a693741c81ceb8b36c2bebbbb370c42184e2a89057c83c ./web/css/styles.css
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
43f33fc21bb11df0ea149fe7fbca722a57f93890e4d9bb542ced703c1311207e ./web/index.html
@@ -207,7 +207,7 @@ e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.
e7318cd92b76e8a0109dc01adc2431474be17d8bb1488cdf6ffb39dc5450909d ./web/js-dynamic/bootstrap.js
5d57253a013fd1cb79e4b8701d4be6e394e3f2acbce73dfcd726a86e5878f1a1 ./web/js-dynamic/charts.js
07629b331bd498e8bea769d577321bbd12d9b076264242706d4ef16165fb22fa ./web/js-dynamic/core.js
5e681095a559fafbeca067024afb3705ba114f01fa562d487a5717d12dac0123 ./web/js-dynamic/dashboard.js
80aaf5c56375345c67fd30c4fc1622319e4749b2859ec10613f77ac117f0441e ./web/js-dynamic/dashboard.js
930e67432f55e8476513e808edfa9a0809a8da7479307284fcc136082a4129a4 ./web/js-dynamic/entities.js
4612c958d7b9ccf89d6d45a11c3bc7e864bbd2ceaccc16fd7d76811ba4c74196 ./web/js-dynamic/events.js
43c78a3871cfdb0382081ec30b8d48650450c71b44d006414b161349ac1fa659 ./web/js-dynamic/flows.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.
**Version: 0.15.17**
**Version: 0.15.18**
Version 0.15.17 hides the Global power emergency STOP while the system is idle, keeps the green return-to-normal action visible whenever STOP is active, and shows the source Flow name for Flow-generated entries in Recently triggered automations.
Version 0.15.18 records the exact Flow condition trace at execution time and shows the evaluated Flow variables and gates directly under Recently triggered automations in Control plan.
> [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`.
+21 -21
View File
@@ -1,13 +1,13 @@
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
4051d2de7fa9858545582dc6bb5a411c980a282d0b25aa3766df88597442f94a ./Cargo.lock
4bf5b286ffb52b22a32ef3d2d5e4dfaae600c1be01c1cac0b87c7138ecb4a129 ./Cargo.toml
dfac61e5ea474221798f0a72d6cb4d6b5a31bcd853573e14c172087c2f7e98c5 ./Cargo.lock
f8e6fa48a584d6446e6360185b0ca320379b325d62afc062e7d2efedc2b14af1 ./Cargo.toml
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
9a429e8b766c66061e85ccd535cc1429fe1284d14fcf75daa362fea724e0bf85 ./README.md
591cf7e1fc98bbcdf4854c28ffc22df92b47ac7cd6e81bfa07ae4f99a9baaa3c ./README.md
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
3c1fce0f3097e7860f8ccf6a79749af4e7e4bd43e612c6419d91c0e620480506 ./docs/API.md
312ca0de508126a473cad4a06d35eb5b44086de37f6d0f5f4d9f5fa85e5992fe ./docs/API.md
43af7941ec4d427f9db7ab3cc6baf3d182a72dba7a6791f14094fee138324d8d ./docs/FLOW.md
3ef573d60de5dcba3fdb4004df53ea170440cbeae2e244a1f6acfcb3255b7c78 ./docs/openapi.json
d3702f852b4427c67043d16d3edab40be23a1ebf9a3baf44902ef764241643dd ./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
5aea34a2adb03ed46a93199e943d7820e34825658fefc3d7c3eb5267ac9066b0 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
813f932b99eda416330688504c67872148b6aeae27042f93505085cbfe06a3ed ./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
69d6cf26824f8851cdf076f87c7270d6f87717a2820b68b57a82beed53d2c736 ./ha-addon/repository/gree-controller/CHANGELOG.md
e8b1f335c9c7c5e3510aad8961f8f3e67d9fdd711d5407f1adb9db1746fd2407 ./ha-addon/repository/gree-controller/CHANGELOG.md
50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md
d3d0e54672751a3400021ffd1fdcc83a9a903be72cff32cdc3607a61aec0a31e ./ha-addon/repository/gree-controller/README.md
c591ab21837150e313407a34a5a3758302aaafd91cd5fcebe18bb5358b702433 ./ha-addon/repository/gree-controller/config.yaml
cf3e7106bb516598ca7d635be3d00807af024a9448632d89ee1a60d139c1c573 ./ha-addon/repository/gree-controller/README.md
0733d428c986e22c0300c3d8a82971dc1d6cadc88d38c928667c74c93638b5df ./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
@@ -42,8 +42,8 @@ cea12bc74c066a588a5ef0ef7bad9d4906bdbcea99f2d275429b0eefc94b4b5f ./ha-addon/run
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
859875af2aad9929fe6ecfb80244c8d09182345edd99663e54b04910268e902e ./lang/en.json
7139d82eb65341e72ad978be3f57efbe04228b2101b19f792d665d146697d24f ./lang/pl.json
c5c95c19ceac8969435c6f7742c86e7efa4db9bf723a0b30b098acf7fe6295fa ./lang/en.json
9ebf7bcfd49220e828336f551251ffc86986772864e75a5a74493cee27929749 ./lang/pl.json
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
@@ -84,7 +84,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
5969f2442a8d61ac5b1303f4cd6d8dcfe80596ef5427a513ae3addb126bf0273 ./scripts/api_dev_test.py
ff01f3a4b69bbd58e824eb6dfb8d36568c189cbc38bea21417b91ae81c4c43dc ./scripts/api_dev_test.py
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
@@ -100,13 +100,13 @@ b50782b3742dfbf8a319c60571c968e93fdf8547db747c759edcffae68cb98bf ./scripts/upda
ea84f5230da97447c60dfb4f8dadd0747c92fd1f7a74d5a65d6fdb6f23cf4b6d ./src/api.rs
0db0d81da4b8161004ddcbed2096812c285ecec9d944771670e6c8edf0775175 ./src/api/assets.rs
1139dacbd3f6ff94bf721eb5a60d469016fb935ddcf484d71bfeb249b72c833a ./src/api/auth.rs
9e4fc675306e111ed3db7af9822e2925d88109c31b9e309851665a975c0bd83c ./src/api/automations.rs
a652409de6e2e2897d67d19dc16a7d4209c5c35afda39081fef627a098a739b4 ./src/api/automations.rs
fffad7874465739cd6b508e9d7791387b864fa88420ce0f7e2fc933d129a2751 ./src/api/configuration.rs
19bc141286a336ad67e99079496eb0ae5126f986ab28be62b5f97758e5f6969c ./src/api/debug_tokens.rs
ca21f6d5a407e2e3532899d53ba4e8682c2fe961a46d8cd870ddc459721513ef ./src/api/device_groups.rs
8f92909dc575ea6238f4a003f8bd4056c2752fa3247b9ab226e47f875c80b916 ./src/api/devices.rs
4b5b784670ae761a6848b9a33fe3fb391bebd39d65e87936907428711e96ca44 ./src/api/events.rs
ca5bc21e051222dcaf0c507e84cb3e60f52a0b612a6056847fa0aee3a4d1a672 ./src/api/flows.rs
b1e9567cae7036af51945ce1219f537567c944c0402c2ff03edb1249403670b5 ./src/api/flows.rs
82c02fa135132ea3ebdb4d88dd1210dc536d22f987151d7f82595ee3d3d03440 ./src/api/gree_cloud.rs
b61b0b4f196c5e72e47cf06ca0c14df3cf8eedbdef0242a9d2af33623663489f ./src/api/groups.rs
b6d586bc42b166ac05648f3da7d9aeb88efde2e9eaab5642685eb4d9cf15f95a ./src/api/history.rs
@@ -135,10 +135,10 @@ d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/sched
28ee42d08147b0f154af818065d7d014d74ee8ddd59fdef98115f5c5368056cc ./src/db/tests.rs
f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs
0e49ea4a52017b0a8501d5101b1906a7a1e8d5158acb0bb65caa43cde2836ec0 ./src/engine.rs
f3cc7d2cbf44180372d987d7502409547eadb1c725bcce6eeb5a26cc4277b83f ./src/engine/automations.rs
8c8d45fa9f37c724a758128726403cf64a2af2ab4e063f1b3e74e4986082de79 ./src/engine/automations.rs
1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs
357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs
315116a0dbeeba1af2f72513ea5fcc238da1b5ce9501b039c5188e44976978dc ./src/engine/control_plan.rs
828657b3c13b68792991b5ca2a26ae0b1d5c417191c83c4e5f650f62b6440096 ./src/engine/control_plan.rs
7b48a33343d519a2ff2d81e9d757eb56a0c1beacfc0b7b8ef61f49c2ad363f9a ./src/engine/deadlines.rs
519f67ab805f61afea107215f5df50562fa1669a48334cc015570952e25e773a ./src/engine/energy.rs
e350aa63f3bb14ca887eaaa7555edbeae00fb584cbcaf552cc972f0c8918f5d8 ./src/engine/groups.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
bf1a3840135a71ce64148559cce0fbd570ff99d31c8972cdea7447a49f6868f7 ./src/engine/tests.rs
0d4f2df3c3881aceb41b9f8d78adde7ecbb5b39d2f8aea444b865166a0670dec ./src/engine/tests.rs
c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs
87ab87d434a6462a2c3f25249b42c59ecb2bca6ed700ec73e95e4613349cf16f ./src/engine/zone_control.rs
bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs
@@ -162,8 +162,8 @@ da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb
67e42a92295480d2f1d364958f184654cfb5d4b77e7e1df47f588bdec18868a0 ./src/influxdb/write.rs
62f9da6ef4f5fd92701d0857773f9f6236ef25356f21b9b42236abab6ba7da53 ./src/main.rs
23688a5cb68efded89471ce959328d27adff370d451bd9f378a283ab51437e76 ./src/models.rs
e7b6e49d369aaf665a8aea2464950fd795957de0ad7528a002e11c55c7983f35 ./src/models/automation.rs
a7a35e60318a620d411ec16ab8faecd34410ccba3310f612e89c06904a16539b ./src/models/control_plan.rs
a7d40ff2b00c6352223bd6335dc734925c42d70be2c70d1cb0b23051baa6f867 ./src/models/automation.rs
e4195a40135f408302a78cb200a54e6ef1c0e6031fb069a2431d068e5dbbdd8d ./src/models/control_plan.rs
06357b352ff6494d83e72796b869573a8b8f36b835985a5900ac571c922b8da9 ./src/models/defaults.rs
698dcbeba72037a251c25d07c95833f121716d949f9217f1f9429e8cd40638e5 ./src/models/device.rs
aeab5b5acf35d38a54fd1d82e964fe56635ade69c0149ad423df317095d01555 ./src/models/flow.rs
@@ -198,7 +198,7 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
d8a602ed8906d77593bd140315ce1bd56716038e789f53ff31762e9b3cb7220d ./src/state.rs
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
1d6fa9e291ebbae40a7a8b06a7cef9dbd0ee06d88ec6b664361c37cb6c1aa281 ./web/css/styles.css
da9cb50e003fa25989a693741c81ceb8b36c2bebbbb370c42184e2a89057c83c ./web/css/styles.css
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
43f33fc21bb11df0ea149fe7fbca722a57f93890e4d9bb542ced703c1311207e ./web/index.html
@@ -206,7 +206,7 @@ e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.
e7318cd92b76e8a0109dc01adc2431474be17d8bb1488cdf6ffb39dc5450909d ./web/js-dynamic/bootstrap.js
5d57253a013fd1cb79e4b8701d4be6e394e3f2acbce73dfcd726a86e5878f1a1 ./web/js-dynamic/charts.js
07629b331bd498e8bea769d577321bbd12d9b076264242706d4ef16165fb22fa ./web/js-dynamic/core.js
5e681095a559fafbeca067024afb3705ba114f01fa562d487a5717d12dac0123 ./web/js-dynamic/dashboard.js
80aaf5c56375345c67fd30c4fc1622319e4749b2859ec10613f77ac117f0441e ./web/js-dynamic/dashboard.js
930e67432f55e8476513e808edfa9a0809a8da7479307284fcc136082a4129a4 ./web/js-dynamic/entities.js
4612c958d7b9ccf89d6d45a11c3bc7e864bbd2ceaccc16fd7d76811ba4c74196 ./web/js-dynamic/events.js
43c78a3871cfdb0382081ec30b8d48650450c71b44d006414b161349ac1fa659 ./web/js-dynamic/flows.js
+7 -5
View File
@@ -1,6 +1,6 @@
# GREE Controller API reference
HTTP and WebSocket API for GREE Controller **0.15.17**.
HTTP and WebSocket API for GREE Controller **0.15.18**.
[← Main documentation](../README.md)
@@ -243,7 +243,7 @@ Response:
{
"status": "ok",
"name": "gree-controller",
"version": "0.15.17",
"version": "0.15.18",
"uptime_seconds": 1234,
"control_ready": true,
"time": "2026-08-30T06:54:00Z"
@@ -306,7 +306,7 @@ Returns the initial Web UI snapshot:
"control_plan": {"generated_at": "2026-09-04T08:00:00Z", "zones": [], "rules": []},
"control_plan_revision": 42,
"system": {
"version": "0.15.17",
"version": "0.15.18",
"uptime_seconds": 1234,
"auth_required": false,
"control_ready": true,
@@ -955,7 +955,7 @@ Action target is either:
- direct device: `action_device_id` + full `DeviceCommand`, or
- group: `action_group_id`; group automation supports only power, `house`/`cool`/`heat` mode and optional `action_preset` (`auto|comfort|sleep|away`).
The response also contains runtime `last_fired_at`, `created_at`, `updated_at`.
The response also contains runtime `last_fired_at`, `last_trigger_trace`, `created_at`, `updated_at`. For Flow-generated automations, `last_trigger_trace` is the snapshot of evaluated Flow blocks from the latest execution attempt (`node_id`, `kind`, `matched`, `actual`, `expected`, `config`, `inputs`).
Routes:
@@ -1056,7 +1056,7 @@ When InfluxDB is enabled, older history can be read from Influx and merged with
### `GET /api/control-plan`
Returns the latest materialized machine-readable thermostat plan. The response shape is unchanged; the bundled Web UI receives plan updates primarily through WebSocket and uses this endpoint for fallback/resynchronization.
Returns the latest materialized machine-readable thermostat plan. The bundled Web UI receives plan updates primarily through WebSocket and uses this endpoint for fallback/resynchronization. Flow-generated entries in `rules[]` include `flow_id`, `flow_node_id` and the persisted `last_trigger_trace` snapshot used by Recently triggered automations.
Top-level fields:
@@ -1097,6 +1097,8 @@ current_schedule_id, current_schedule_name,
next_events[]
```
Each automation rule includes runtime readiness fields. Flow-generated rules also expose `flow_id`, `flow_node_id` and `last_trigger_trace[]`; the trace preserves the evaluated block configuration and observed values from the latest execution attempt.
This endpoint is the best way for another client to understand **desired vs actual state**, who owns control, and why a zone is blocked/paused.
---
+13 -4
View File
@@ -2,7 +2,7 @@
"openapi": "3.1.0",
"info": {
"title": "GREE Controller API",
"version": "0.15.17",
"version": "0.15.18",
"summary": "Local HTTP/WebSocket API for GREE Controller",
"description": "Local API used by the GREE Controller Web UI and Home Assistant integration.",
"license": {
@@ -3120,7 +3120,7 @@
"History"
],
"summary": "Get resolved control plan",
"description": "Machine-readable desired-vs-actual thermostat plan with ownership, blocking reasons, schedules, next events and per-zone control decisions. The response shape is unchanged; the bundled Web UI receives plan changes through WebSocket and uses this endpoint as a fallback/resynchronization surface.",
"description": "Machine-readable desired-vs-actual thermostat plan with ownership, blocking reasons, schedules, next events and per-zone control decisions. Flow-generated rules include flow ownership and the persisted last_trigger_trace snapshot from the latest execution attempt. The bundled Web UI receives plan changes through WebSocket and uses this endpoint as a fallback/resynchronization surface.",
"operationId": "controlPlan",
"security": [
{
@@ -6079,7 +6079,7 @@
},
"version": {
"type": "string",
"example": "0.15.17"
"example": "0.15.18"
},
"uptime_seconds": {
"type": "integer",
@@ -7846,7 +7846,8 @@
"enum": [
"temperature_above",
"temperature_below",
"time"
"time",
"flow"
],
"example": "temperature_above"
},
@@ -7914,6 +7915,14 @@
],
"format": "date-time"
},
"last_trigger_trace": {
"type": "array",
"description": "For Flow-generated automations, snapshot of evaluated Flow blocks from the latest execution attempt.",
"items": {
"type": "object",
"additionalProperties": true
}
},
"flow_id": {
"type": [
"string",
@@ -1,7 +1,7 @@
{
"domain": "gree_controller",
"name": "GREE Controller",
"version": "0.15.17",
"version": "0.15.18",
"config_flow": true,
"integration_type": "hub",
"iot_class": "local_polling",
@@ -1,5 +1,11 @@
# Changelog
## 0.15.18
- Persists the evaluated Flow trigger trace whenever a Flow-generated automation executes or reaches a failed execution attempt, so the trigger context reflects the values from that exact moment instead of current live readings.
- Expands Control plan → Recently triggered automations with per-block Flow details: matched state, configured condition, observed value, timing/rolling-stat/rate-limit state and logic gates.
- Keeps the newest Flow trigger details expanded by default while older recent entries remain available in compact expandable sections.
## 0.15.17
- Hides Dashboard → Global power Emergency STOP whenever all units are off and no automatic control is currently requesting work; an active STOP always keeps the green return-to-normal action visible.
@@ -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`.
Version 0.15.17 hides the Global power emergency STOP while the system is idle, keeps the green return-to-normal action visible whenever STOP is active, and shows the source Flow name for Flow-generated entries in Recently triggered automations.
Version 0.15.18 records the exact Flow condition trace at execution time and shows the evaluated Flow variables and gates directly under Recently triggered automations in Control plan.
![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"
version: "0.15.17"
version: "0.15.18"
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/"
+1
View File
@@ -467,6 +467,7 @@
"plan.reasonSourceThermostat": "Local thermostat",
"plan.reasonSourceAutomation": "Automation",
"plan.reasonTriggerReading": "{trigger} · now {current}",
"plan.reasonTriggerDetails": "Trigger details · {count} Flow blocks",
"plan.reasonLastTriggered": "Last triggered: {when}",
"zones.enable": "Enable zone",
"zones.disable": "Disable zone",
+1
View File
@@ -467,6 +467,7 @@
"plan.reasonSourceThermostat": "Termostat lokalny",
"plan.reasonSourceAutomation": "Automatyka",
"plan.reasonTriggerReading": "{trigger} · teraz {current}",
"plan.reasonTriggerDetails": "Szczegóły wyzwolenia · {count} bloków Flow",
"plan.reasonLastTriggered": "Ostatnie wyzwolenie: {when}",
"zones.enable": "Włącz strefę",
"zones.disable": "Wyłącz strefę",
+3 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Developer smoke/integration tests for GREE Controller API 0.15.17.
"""Developer smoke/integration tests for GREE Controller API 0.15.18.
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
@@ -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_TOKEN = os.environ.get("GREE_API_TOKEN", "")
DEFAULT_EXPECTED_VERSION = "0.15.17"
DEFAULT_EXPECTED_VERSION = "0.15.18"
SETTINGS_PATHS: dict[str, set[str]] = {
"/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")
runner.run("public health", lambda: test_health(client, args.expected_version))
runner.run("OpenAPI 0.15.17 contract", lambda: test_openapi(client, args.expected_version))
runner.run("OpenAPI 0.15.18 contract", lambda: test_openapi(client, args.expected_version))
runner.run("bootstrap snapshot contract", lambda: test_bootstrap_contract(client))
runner.run("protected API requires auth", lambda: test_protected_auth(client))
+1
View File
@@ -161,6 +161,7 @@ impl AutomationInput {
action: self.action,
cooldown_seconds: self.cooldown_seconds.max(30),
last_fired_at,
last_trigger_trace: vec![],
action_zone_id: None,
action_zone_preset: None,
action_ha_domain: None,
+5
View File
@@ -1361,6 +1361,10 @@ fn compile_flow(
let last_fired_at = previous_automations
.get(&id)
.and_then(|item| item.last_fired_at.clone());
let last_trigger_trace = previous_automations
.get(&id)
.map(|item| item.last_trigger_trace.clone())
.unwrap_or_default();
let flow_runtime = previous_automations
.get(&id)
.map(|item| {
@@ -1411,6 +1415,7 @@ fn compile_flow(
action: DeviceCommand::default(),
cooldown_seconds: 60,
last_fired_at,
last_trigger_trace,
action_zone_id: None,
action_zone_preset: None,
action_ha_domain: None,
+13 -6
View File
@@ -26,6 +26,7 @@ async fn run_automations(state: &AppState) -> Result<()> {
for mut item in automations {
if !item.enabled { continue; }
let ready = automation_ready(&item);
let mut trigger_trace = Vec::<Value>::new();
let should_fire = match item.trigger_kind.as_str() {
// Stateful Flow blocks must keep observing while an action is in cooldown. Otherwise
// stable/change-duration state could silently span an unobserved interval.
@@ -34,7 +35,10 @@ async fn run_automations(state: &AppState) -> Result<()> {
let result = flow_conditions_match(state, &devices, &mut item).await;
if item.flow_runtime != before_runtime { state.db.save_automation(&item)?; }
match result {
Ok(value) => value,
Ok((value, trace)) => {
trigger_trace = trace;
value
},
Err(err) => {
state.log("warn", "flow.condition_error", &format!("Flow automation {} condition evaluation failed: {}", item.name, err), json!({"automation_id": item.id, "flow_id": item.flow_id, "flow_node_id": item.flow_node_id}));
false
@@ -168,6 +172,7 @@ async fn run_automations(state: &AppState) -> Result<()> {
flow_acknowledge_change_gates(&mut item);
flow_record_rate_limited_execution(&mut item, fired_at.clone());
item.last_fired_at = Some(fired_at);
item.last_trigger_trace = trigger_trace.clone();
item.updated_at = Utc::now();
state.db.save_automation(&item)?;
// Keep connected clients in sync with runtime metadata such as last_fired_at.
@@ -191,6 +196,7 @@ async fn run_automations(state: &AppState) -> Result<()> {
// A failed action is still an execution attempt. Apply the configured cooldown
// so an offline/disabled target cannot be hammered on every automation cycle.
item.last_fired_at = Some(Utc::now());
item.last_trigger_trace = trigger_trace.clone();
item.updated_at = Utc::now();
state.db.save_automation(&item)?;
state.broadcast("automation.updated", serde_json::to_value(&item)?);
@@ -704,7 +710,7 @@ pub async fn evaluate_flow_conditions_trace(
for condition in conditions {
let (matched, actual) = flow_leaf_observation(state, devices, &zones, outdoor_temperature, condition, &now, &settings, overrides).await?;
final_value &= matched;
trace.push(json!({"node_id": condition.id, "kind": condition.kind, "matched": matched, "actual": actual, "expected": condition.config.get("value")}));
trace.push(json!({"node_id": condition.id, "kind": condition.kind, "matched": matched, "actual": actual, "expected": condition.config.get("value"), "config": condition.config}));
}
return Ok((final_value, trace));
}
@@ -875,23 +881,24 @@ pub async fn evaluate_flow_conditions_trace(
"matched": matched,
"actual": actual,
"expected": expected,
"config": condition.config,
"inputs": condition.inputs
}));
}
Ok((final_id.and_then(|id| values.get(&id).copied()).unwrap_or(false), trace))
}
async fn flow_conditions_match(state: &AppState, devices: &[Device], item: &mut Automation) -> Result<bool, AppError> {
async fn flow_conditions_match(state: &AppState, devices: &[Device], item: &mut Automation) -> Result<(bool, Vec<Value>), AppError> {
let overrides = HashMap::new();
let conditions = item.flow_conditions.clone();
let now = Local::now();
let (matched, _) = evaluate_flow_conditions_trace(state, devices, &conditions, now.clone(), &overrides, Some(&mut item.flow_runtime)).await?;
let (matched, trace) = evaluate_flow_conditions_trace(state, devices, &conditions, now.clone(), &overrides, Some(&mut item.flow_runtime)).await?;
if matched && conditions.iter().any(|condition| condition.kind == "cron_trigger") {
if let Some(last) = item.last_fired_at.as_ref().map(|value| value.with_timezone(&Local)) {
if last.year() == now.year() && last.ordinal() == now.ordinal() && last.hour() == now.hour() && last.minute() == now.minute() { return Ok(false); }
if last.year() == now.year() && last.ordinal() == now.ordinal() && last.hour() == now.hour() && last.minute() == now.minute() { return Ok((false, trace)); }
}
}
Ok(matched)
Ok((matched, trace))
}
fn flow_condition_kind_runtime(kind: &str) -> bool {
+3
View File
@@ -326,6 +326,9 @@ pub async fn build_control_plan(state: &AppState) -> Result<ControlPlan, AppErro
action_preset: item.action_preset,
action: item.action,
last_fired_at: item.last_fired_at,
last_trigger_trace: item.last_trigger_trace,
flow_id: item.flow_id,
flow_node_id: item.flow_node_id,
next_ready_at,
});
}
+1
View File
@@ -431,6 +431,7 @@ mod tests {
},
cooldown_seconds: 30,
last_fired_at: None,
last_trigger_trace: vec![],
action_zone_id: None,
action_zone_preset: None,
action_ha_domain: None,
+4
View File
@@ -74,6 +74,10 @@ pub struct Automation {
pub cooldown_seconds: u64,
#[serde(default)]
pub last_fired_at: Option<DateTime<Utc>>,
/// Snapshot of the evaluated Flow condition trace from the most recent execution attempt.
/// Empty for legacy/direct automations and for Flow automations that have never executed.
#[serde(default)]
pub last_trigger_trace: Vec<Value>,
/// Optional Flow-owned thermostat zone target.
#[serde(default)]
pub action_zone_id: Option<String>,
+6
View File
@@ -92,6 +92,12 @@ pub struct AutomationPlanRule {
pub action_preset: Option<String>,
pub action: DeviceCommand,
pub last_fired_at: Option<DateTime<Utc>>,
#[serde(default)]
pub last_trigger_trace: Vec<Value>,
#[serde(default)]
pub flow_id: Option<String>,
#[serde(default)]
pub flow_node_id: Option<String>,
pub next_ready_at: Option<DateTime<Utc>>,
}
+93
View File
@@ -4895,6 +4895,90 @@ body.simulation-standalone [data-view="simulation"] {
line-height: 1.4;
}
#controlPlan .plan-recent-trigger {
align-items: start;
}
#controlPlan .plan-trigger-trace {
grid-column: 1 / -1;
min-width: 0;
margin-top: 2px;
border-top: 1px solid var(--line);
padding-top: 7px;
}
#controlPlan .plan-trigger-trace summary {
width: max-content;
max-width: 100%;
cursor: pointer;
color: var(--muted);
font-size: 10px;
font-weight: 800;
}
#controlPlan .plan-trigger-trace-list {
display: grid;
gap: 5px;
margin-top: 7px;
}
#controlPlan .plan-trigger-trace-row {
display: grid;
grid-template-columns: 18px minmax(150px, 1fr) minmax(110px, .85fr);
gap: 8px;
align-items: center;
padding: 5px 7px;
border-radius: 8px;
background: var(--surface);
}
#controlPlan .plan-trigger-trace-row>div {
display: grid;
gap: 1px;
min-width: 0;
}
#controlPlan .plan-trigger-trace-row strong {
font-size: 10px;
}
#controlPlan .plan-trigger-trace-row small {
overflow: hidden;
color: var(--muted);
font-size: 9px;
text-overflow: ellipsis;
white-space: nowrap;
}
#controlPlan .plan-trigger-trace-row code {
overflow: hidden;
color: var(--text-soft);
font-size: 9px;
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;
}
#controlPlan .plan-trigger-trace-mark {
display: grid;
place-items: center;
width: 18px;
height: 18px;
border-radius: 999px;
font-size: 11px;
font-weight: 900;
}
#controlPlan .plan-trigger-trace-row.pass .plan-trigger-trace-mark {
color: var(--accent-text);
background: var(--accent);
}
#controlPlan .plan-trigger-trace-row.fail .plan-trigger-trace-mark {
color: var(--warning);
background: var(--warning-soft);
}
#controlPlan .plan-reason-alert {
display: grid;
gap: 3px;
@@ -10939,4 +11023,13 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr
grid-template-columns: 1fr;
gap: 5px;
}
#controlPlan .plan-trigger-trace-row {
grid-template-columns: 18px minmax(0, 1fr);
}
#controlPlan .plan-trigger-trace-row code {
grid-column: 2;
text-align: left;
}
}
+83 -2
View File
@@ -104,6 +104,86 @@ function planZoneReason(zone) {
return tr('plan.reasonWaiting');
}
function planFlowTraceNode(rule, item) {
const flow = rule.flow_id ? (app.flows || []).find(candidate => candidate.id === rule.flow_id) : null;
const current = flow?.nodes?.find(node => node.id === item.node_id);
return {
id: item.node_id || current?.id || '',
kind: item.kind || current?.kind || '',
config: item.config && typeof item.config === 'object' ? item.config : (current?.config || {}),
};
}
function planFlowTraceSourceLabel(node) {
const c = node?.config || {};
if (!['rolling_stat', 'oscillates'].includes(node?.kind)) return '';
if (c.source === 'outdoor_temperature') return tr('house.outdoor');
if (c.source === 'device_temperature') return (app.devices || []).find(device => device.id === c.device_id)?.name || c.device_id || '';
if (c.source === 'zone_temperature') return (app.zones || []).find(zone => zone.id === c.zone_id)?.name || c.zone_id || '';
if (c.source === 'ha_numeric') return c.entity_id ? haSensorLabel(c.entity_id) : '';
return c.source || '';
}
function planFlowTraceScalar(node, value) {
const formatted = flowSimpleSimulationActualText(node, value);
if (formatted) return formatted;
if (value === null || value === undefined) return '—';
if (typeof value === 'object') return JSON.stringify(value);
return String(value);
}
function planFlowTraceActual(rule, item, node) {
const actual = item?.actual;
if (!actual || typeof actual !== 'object' || Array.isArray(actual)) return planFlowTraceScalar(node, actual);
if (actual.error) return `${actual.error}${actual.entity_id ? ` · ${actual.entity_id}` : ''}`;
if (node.kind === 'stable_for' || node.kind === 'delay') {
const since = actual.since ? new Date(actual.since).toLocaleTimeString(locale(), { hour: '2-digit', minute: '2-digit', second: '2-digit' }) : '—';
return `${actual.input ? tr('common.yes') : tr('common.no')} · ≥${flowDurationLabel(actual.seconds)} · ${since}`;
}
if (node.kind === 'state_duration') {
const max = actual.max_seconds == null ? '∞' : flowDurationLabel(actual.max_seconds);
return `${flowDurationLabel(actual.elapsed_seconds)} · ${flowDurationLabel(actual.min_seconds)}–${max}`;
}
if (node.kind === 'on_change') {
const previous = actual.previous === null || actual.previous === undefined ? '—' : planFlowTraceScalar(node, actual.previous);
const current = actual.current === null || actual.current === undefined ? '—' : planFlowTraceScalar(node, actual.current);
return `${previous} → ${current}`;
}
if (node.kind === 'rate_limit') return `${Number(actual.used || 0)}/${Number(actual.max_count || 0)} · ${flowDurationLabel(actual.period_seconds)}`;
if (node.kind === 'rolling_stat') {
const sourceKind = node.config?.source;
const tempSource = ['outdoor_temperature', 'device_temperature', 'zone_temperature'].includes(sourceKind);
const aggregate = actual.aggregate == null ? '—' : tempSource ? fmtTemp(Number(actual.aggregate)) : Number(actual.aggregate).toFixed(2);
const sample = actual.sample == null ? '—' : tempSource ? fmtTemp(Number(actual.sample)) : Number(actual.sample).toFixed(2);
return `${aggregate} · x=${sample} · n=${Number(actual.samples || 0)}`;
}
if (node.kind === 'oscillates') {
const span = actual.span == null ? '—' : Number(actual.span).toFixed(2);
return `Δ=${span} · ↕=${Number(actual.direction_changes || 0)} · n=${Number(actual.samples || 0)}`;
}
if (node.kind === 'logic_and' || node.kind === 'logic_or') {
return (Array.isArray(actual) ? actual : []).map(value => value ? tr('common.yes') : tr('common.no')).join(' · ') || '—';
}
if (node.kind === 'logic_not') return actual ? tr('common.yes') : tr('common.no');
return JSON.stringify(actual);
}
function planFlowTriggerTrace(rule, expanded = false) {
const trace = Array.isArray(rule.last_trigger_trace) ? rule.last_trigger_trace : [];
if (rule.trigger_kind !== 'flow' || !trace.length) return '';
const rows = trace.map(item => {
const node = planFlowTraceNode(rule, item);
const meta = FLOW_NODE_META[node.kind] || { title: node.kind || item.node_id || 'Flow' };
const source = planFlowTraceSourceLabel(node);
const summary = [source, flowNodeSummary(node)].filter(Boolean).join(' · ');
const actual = planFlowTraceActual(rule, item, node);
const matched = item.matched === true;
return `<div class="plan-trigger-trace-row ${matched ? 'pass' : 'fail'}"><span class="plan-trigger-trace-mark" aria-hidden="true">${matched ? '✓' : '×'}</span><div><strong>${esc(flowNodeTitle(meta))}</strong>${summary ? `<small>${esc(summary)}</small>` : ''}</div><code>${esc(actual)}</code></div>`;
}).join('');
return `<details class="plan-trigger-trace" ${expanded ? 'open' : ''}><summary>${esc(tr('plan.reasonTriggerDetails', { count: trace.length }))}</summary><div class="plan-trigger-trace-list">${rows}</div></details>`;
}
function planReasonCard(plan) {
const zones = (plan.zones || []).map(zone => {
const reason = planZoneReason(zone);
@@ -118,14 +198,15 @@ function planReasonCard(plan) {
.filter(rule => rule.enabled && rule.last_fired_at)
.sort((a, b) => new Date(b.last_fired_at) - new Date(a.last_fired_at))
.slice(0, 4)
.map(rule => {
.map((rule, index) => {
const when = new Date(rule.last_fired_at).toLocaleString(locale(), { weekday: 'short', hour: '2-digit', minute: '2-digit' });
const triggerDevice = rule.trigger_device_id ? (app.devices || []).find(device => device.id === rule.trigger_device_id) : null;
const current = triggerDevice?.current_temperature == null ? null : Number(triggerDevice.current_temperature);
const trigger = Number.isFinite(current)
? tr('plan.reasonTriggerReading', { trigger: automationTriggerLabel(rule), current: fmtTemp(current) })
: automationTriggerLabel(rule);
return `<li><div><strong>${esc(rule.name)}</strong><small>${esc(trigger)}</small></div><span>${esc(tr('plan.reasonLastTriggered', { when }))}</span></li>`;
const trace = planFlowTriggerTrace(rule, index === 0);
return `<li class="plan-recent-trigger"><div><strong>${esc(rule.name)}</strong><small>${esc(trigger)}</small></div><span>${esc(tr('plan.reasonLastTriggered', { when }))}</span>${trace}</li>`;
}).join('');
const emergency = plan.emergency_stop_active