From f08613a254b642e4d4848e65f2985533a7e74655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Wed, 23 Sep 2026 09:24:15 +0200 Subject: [PATCH] v0.15.18 --- Cargo.lock | 2 +- Cargo.toml | 2 +- FILE_MANIFEST.sha256 | 44 ++++----- README.md | 4 +- SOURCE_MANIFEST.sha256 | 42 ++++----- docs/API.md | 12 ++- docs/openapi.json | 17 +++- .../gree_controller/manifest.json | 2 +- .../repository/gree-controller/CHANGELOG.md | 6 ++ ha-addon/repository/gree-controller/README.md | 2 +- .../repository/gree-controller/config.yaml | 2 +- lang/en.json | 1 + lang/pl.json | 1 + scripts/api_dev_test.py | 6 +- src/api/automations.rs | 1 + src/api/flows.rs | 5 + src/engine/automations.rs | 19 ++-- src/engine/control_plan.rs | 3 + src/engine/tests.rs | 1 + src/models/automation.rs | 4 + src/models/control_plan.rs | 6 ++ web/css/styles.css | 93 +++++++++++++++++++ web/js-dynamic/dashboard.js | 85 ++++++++++++++++- 23 files changed, 290 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 58d1386..e5bc221 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -719,7 +719,7 @@ dependencies = [ [[package]] name = "gree-controller" -version = "0.15.17" +version = "0.15.18" dependencies = [ "aes", "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index 40ce766..76933de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/FILE_MANIFEST.sha256 b/FILE_MANIFEST.sha256 index 0cc0888..89c9913 100644 --- a/FILE_MANIFEST.sha256 +++ b/FILE_MANIFEST.sha256 @@ -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 diff --git a/README.md b/README.md index 3c1252a..90a5756 100644 --- a/README.md +++ b/README.md @@ -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`. diff --git a/SOURCE_MANIFEST.sha256 b/SOURCE_MANIFEST.sha256 index 10a6c34..49e88a6 100644 --- a/SOURCE_MANIFEST.sha256 +++ b/SOURCE_MANIFEST.sha256 @@ -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 diff --git a/docs/API.md b/docs/API.md index 545c8e0..551da2d 100644 --- a/docs/API.md +++ b/docs/API.md @@ -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. --- diff --git a/docs/openapi.json b/docs/openapi.json index 5b1b297..0969706 100644 --- a/docs/openapi.json +++ b/docs/openapi.json @@ -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", 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 cbd35c6..213a75e 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.17", + "version": "0.15.18", "config_flow": true, "integration_type": "hub", "iot_class": "local_polling", diff --git a/ha-addon/repository/gree-controller/CHANGELOG.md b/ha-addon/repository/gree-controller/CHANGELOG.md index aaea558..0f355fa 100644 --- a/ha-addon/repository/gree-controller/CHANGELOG.md +++ b/ha-addon/repository/gree-controller/CHANGELOG.md @@ -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. diff --git a/ha-addon/repository/gree-controller/README.md b/ha-addon/repository/gree-controller/README.md index 5719415..d453a02 100644 --- a/ha-addon/repository/gree-controller/README.md +++ b/ha-addon/repository/gree-controller/README.md @@ -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) diff --git a/ha-addon/repository/gree-controller/config.yaml b/ha-addon/repository/gree-controller/config.yaml index 65194d5..7d83dcd 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.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/" diff --git a/lang/en.json b/lang/en.json index 184f5d5..9dbbf7e 100644 --- a/lang/en.json +++ b/lang/en.json @@ -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", diff --git a/lang/pl.json b/lang/pl.json index 7659588..ee522ad 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -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ę", diff --git a/scripts/api_dev_test.py b/scripts/api_dev_test.py index 08e93af..88ec200 100644 --- a/scripts/api_dev_test.py +++ b/scripts/api_dev_test.py @@ -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)) diff --git a/src/api/automations.rs b/src/api/automations.rs index e2975ff..3af2a6d 100644 --- a/src/api/automations.rs +++ b/src/api/automations.rs @@ -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, diff --git a/src/api/flows.rs b/src/api/flows.rs index 6593de3..c466ecd 100644 --- a/src/api/flows.rs +++ b/src/api/flows.rs @@ -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, diff --git a/src/engine/automations.rs b/src/engine/automations.rs index eb4316e..28fd1e6 100644 --- a/src/engine/automations.rs +++ b/src/engine/automations.rs @@ -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::::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 { +async fn flow_conditions_match(state: &AppState, devices: &[Device], item: &mut Automation) -> Result<(bool, Vec), 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 { diff --git a/src/engine/control_plan.rs b/src/engine/control_plan.rs index ba72380..edd4aa1 100644 --- a/src/engine/control_plan.rs +++ b/src/engine/control_plan.rs @@ -326,6 +326,9 @@ pub async fn build_control_plan(state: &AppState) -> Result>, + /// 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, /// Optional Flow-owned thermostat zone target. #[serde(default)] pub action_zone_id: Option, diff --git a/src/models/control_plan.rs b/src/models/control_plan.rs index dfea6eb..19ebd3a 100644 --- a/src/models/control_plan.rs +++ b/src/models/control_plan.rs @@ -92,6 +92,12 @@ pub struct AutomationPlanRule { pub action_preset: Option, pub action: DeviceCommand, pub last_fired_at: Option>, + #[serde(default)] + pub last_trigger_trace: Vec, + #[serde(default)] + pub flow_id: Option, + #[serde(default)] + pub flow_node_id: Option, pub next_ready_at: Option>, } diff --git a/web/css/styles.css b/web/css/styles.css index 58cb076..d6e9c13 100644 --- a/web/css/styles.css +++ b/web/css/styles.css @@ -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; + } } diff --git a/web/js-dynamic/dashboard.js b/web/js-dynamic/dashboard.js index dc0f7e9..e07cab4 100644 --- a/web/js-dynamic/dashboard.js +++ b/web/js-dynamic/dashboard.js @@ -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 `
${esc(flowNodeTitle(meta))}${summary ? `${esc(summary)}` : ''}
${esc(actual)}
`; + }).join(''); + return `
${esc(tr('plan.reasonTriggerDetails', { count: trace.length }))}
${rows}
`; +} + 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 `
  • ${esc(rule.name)}${esc(trigger)}
    ${esc(tr('plan.reasonLastTriggered', { when }))}
  • `; + const trace = planFlowTriggerTrace(rule, index === 0); + return `
  • ${esc(rule.name)}${esc(trigger)}
    ${esc(tr('plan.reasonLastTriggered', { when }))}${trace}
  • `; }).join(''); const emergency = plan.emergency_stop_active