v0.15.9
This commit is contained in:
Generated
+1
-1
@@ -719,7 +719,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gree-controller"
|
name = "gree-controller"
|
||||||
version = "0.15.8"
|
version = "0.15.9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "gree-controller"
|
name = "gree-controller"
|
||||||
version = "0.15.8"
|
version = "0.15.9"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
authors = ["GREE Controller contributors"]
|
authors = ["GREE Controller contributors"]
|
||||||
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
|
||||||
|
|||||||
+20
-20
@@ -1,14 +1,14 @@
|
|||||||
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
||||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||||
ddda584a029e67d057e3afbe93246795e6579d7efd2f3e9ebe4cfd2bb164e907 ./Cargo.lock
|
0c5cd71516b15e7b51a39fd694d8532a38485d8a8d358caab8ae850274a41127 ./Cargo.lock
|
||||||
d09e3c8438adcfac6aa9d16de9aade898a1fa6f64f7edf17bcd70afb714db12f ./Cargo.toml
|
e3b77008eebcb6db5b188102f788cd06f5159c1a66577bb212d96d125aa72510 ./Cargo.toml
|
||||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||||
5b5586d2e3e7b80c0ffe2d241517f8f7a8c4d7e80169a6cb3cf46221024f54e9 ./README.md
|
d5e5fdafaf8d777e3848b5c8f3aacfd0cf4c80735fbbb6bda14e86468329de88 ./README.md
|
||||||
54e2716acb43f0813c6b979df802b8a0f7e3f8f03b1e42b45c746945ed21d098 ./SOURCE_MANIFEST.sha256
|
f4806d66acea0baaf5b1457afb708771b2765f7c08b4c4824c5ff1eace42c83d ./SOURCE_MANIFEST.sha256
|
||||||
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
|
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
|
||||||
70a8217b46a12ae47e6b461f6f619fa19facb0b422fd8d850208b55237abc752 ./docs/API.md
|
96144a5c5f3688854ab19dcd97794bd74da8ed26133c1468d91f6659a54faa4b ./docs/API.md
|
||||||
c6b1aded8ef6f4c840a2b1f2ffa3f0b22eddc080a4750adb34ee950f12f64382 ./docs/FLOW.md
|
0884be80021ff93826525f0b5bd5c81418f0c9c4eadec9583386ec61ae8e82ba ./docs/FLOW.md
|
||||||
9debc0d7d3f663c2a46ab17fd385a37aa732757e6bb7c75d6d60281ea89092b5 ./docs/openapi.json
|
d883d4a58b19b10b5b33dacd4a58b0a4eb69dcc133a478524407027956ed9f70 ./docs/openapi.json
|
||||||
77b8becf7a2c16a384ea85721d878b76e2d520549cb4455d9aa806e57e5a2ecd ./ha-addon/.env.example
|
77b8becf7a2c16a384ea85721d878b76e2d520549cb4455d9aa806e57e5a2ecd ./ha-addon/.env.example
|
||||||
0ca6e58170c2b8925194431ebbdb55f0c8b377fec5cd422cd2bd257e2d9328e3 ./ha-addon/Dockerfile
|
0ca6e58170c2b8925194431ebbdb55f0c8b377fec5cd422cd2bd257e2d9328e3 ./ha-addon/Dockerfile
|
||||||
0951696a2c2e66d4d969f8e7f8641184e3012f261583b31074bc38a7a1c0cc1a ./ha-addon/README.md
|
0951696a2c2e66d4d969f8e7f8641184e3012f261583b31074bc38a7a1c0cc1a ./ha-addon/README.md
|
||||||
@@ -22,7 +22,7 @@ b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/hom
|
|||||||
6509286c7b7de638dcc5d40ce53181e65d1eddc5dc36a13306dc6d87220fedbb ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py
|
6509286c7b7de638dcc5d40ce53181e65d1eddc5dc36a13306dc6d87220fedbb ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py
|
||||||
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py
|
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py
|
||||||
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png
|
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png
|
||||||
00fc79f5195f43cd8d076b3d9a7a782263f0bde2a6b58b310c2d7a8b307a9f88 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
62e7bb884b342349bb175ce874d11ccb845e8219512ff6cf4d6c9ed03cb12294 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
||||||
c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py
|
c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py
|
||||||
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py
|
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py
|
||||||
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py
|
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py
|
||||||
@@ -30,10 +30,10 @@ cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/hom
|
|||||||
a745cc458c235158d5d70b6a33d8000fd8b40ff93bfe2f2796af0f4b3a9eef9b ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
a745cc458c235158d5d70b6a33d8000fd8b40ff93bfe2f2796af0f4b3a9eef9b ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
||||||
50b7d6fe6329ad4ea6bee9612ab141c2f73f8382fcc2af0bb076a8e8d4dd4465 ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json
|
50b7d6fe6329ad4ea6bee9612ab141c2f73f8382fcc2af0bb076a8e8d4dd4465 ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json
|
||||||
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json
|
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json
|
||||||
2976822c9663e505d22297e2749192b4ca119bb4e46ccc1c29c813522c84b3c5 ./ha-addon/repository/gree-controller/CHANGELOG.md
|
9cb2e827567e8cb06050e24fd3811dda6f51350d8bec9b605189fa8ab85b331a ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||||
50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md
|
50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md
|
||||||
5f0bd0d1ea4aca47844f9e870e4ba1676ad8b90369577db75a1bb0d00228a9da ./ha-addon/repository/gree-controller/README.md
|
0bd253135a67d295dfca355b735b350dbf36ed396a6c8e30387d5218d4e5afcf ./ha-addon/repository/gree-controller/README.md
|
||||||
f43723e7e356c7cc637b36b5ade0e34cfb69c50c4cf653cca7fc013e926cd7c9 ./ha-addon/repository/gree-controller/config.yaml
|
cb348473b59a76dfd29adc04d281128a9b8848b92107b928073aac4e047fdc4b ./ha-addon/repository/gree-controller/config.yaml
|
||||||
3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png
|
3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png
|
||||||
0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png
|
0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png
|
||||||
2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml
|
2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml
|
||||||
@@ -43,8 +43,8 @@ cea12bc74c066a588a5ef0ef7bad9d4906bdbcea99f2d275429b0eefc94b4b5f ./ha-addon/run
|
|||||||
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
|
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
|
||||||
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
|
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
|
||||||
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
||||||
37d24f8de343b4796a0a8cfff3a839e55bdb779add0c8119799787d87ad8b953 ./lang/en.json
|
e9f8da620e017a3c06363f9b3d8709d9abaee9bbac4e53e60f27db7c072bb647 ./lang/en.json
|
||||||
1610da0b2bbb9424006647adfb95e1318b7670aaed5a2798db1251db82635317 ./lang/pl.json
|
efc35994018e526ed829ee26b3cb1c74df2ac3655433e3b9bd0bf12f087e3223 ./lang/pl.json
|
||||||
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
||||||
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
||||||
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
||||||
@@ -85,7 +85,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind
|
|||||||
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
|
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
|
||||||
fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh
|
fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh
|
||||||
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
|
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
|
||||||
3a3287a4ca516f91a1b1bf14257aeef7b1b3027d592200c0a6da1cc70afa9c32 ./scripts/api_dev_test.py
|
863f087740abee259d51dc5935c6b60dfe25b31a6a9fe64657d25a05d200a6fa ./scripts/api_dev_test.py
|
||||||
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
||||||
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
|
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
|
||||||
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
|
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
|
||||||
@@ -136,7 +136,7 @@ d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/sched
|
|||||||
28ee42d08147b0f154af818065d7d014d74ee8ddd59fdef98115f5c5368056cc ./src/db/tests.rs
|
28ee42d08147b0f154af818065d7d014d74ee8ddd59fdef98115f5c5368056cc ./src/db/tests.rs
|
||||||
f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs
|
f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs
|
||||||
0e49ea4a52017b0a8501d5101b1906a7a1e8d5158acb0bb65caa43cde2836ec0 ./src/engine.rs
|
0e49ea4a52017b0a8501d5101b1906a7a1e8d5158acb0bb65caa43cde2836ec0 ./src/engine.rs
|
||||||
76dbf72600a48a6ba5b2273777bd8f2c8a6bd212aa236d419cc59eedcb8ab22b ./src/engine/automations.rs
|
a0d60ad81d024488dfec586b0d9e9f447e9a3485985604415465cacba59d645f ./src/engine/automations.rs
|
||||||
1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs
|
1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs
|
||||||
357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs
|
357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs
|
||||||
ad53cd7be24deb759e03cbc6472522b69ebd36d8fac35398a9b19c899d3b17f2 ./src/engine/control_plan.rs
|
ad53cd7be24deb759e03cbc6472522b69ebd36d8fac35398a9b19c899d3b17f2 ./src/engine/control_plan.rs
|
||||||
@@ -152,7 +152,7 @@ c73691c3637465ab0f79aecf7f5428d0317799a8ab5329ed564cf57963bbe86e ./src/engine/r
|
|||||||
b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs
|
b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs
|
||||||
682c8b3956c657b655746cbd19ee97ee2f4cf953bc9de67d6d70a1b14f3dd746 ./src/engine/temperature.rs
|
682c8b3956c657b655746cbd19ee97ee2f4cf953bc9de67d6d70a1b14f3dd746 ./src/engine/temperature.rs
|
||||||
833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs
|
833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs
|
||||||
af4edaf12faf294f44a8992d120a99b09edd1cbdd66981390e027d13fd6b6790 ./src/engine/tests.rs
|
0404e040c5d6f388682edb3d103a658e0b963a402e4e847fc92fd0aea1c0e773 ./src/engine/tests.rs
|
||||||
c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs
|
c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs
|
||||||
fb169bcc20f95abe9892367df6dd3a683584bdbc721792d6ffb9311b81ca30d9 ./src/engine/zone_control.rs
|
fb169bcc20f95abe9892367df6dd3a683584bdbc721792d6ffb9311b81ca30d9 ./src/engine/zone_control.rs
|
||||||
bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs
|
bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs
|
||||||
@@ -163,7 +163,7 @@ da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb
|
|||||||
67e42a92295480d2f1d364958f184654cfb5d4b77e7e1df47f588bdec18868a0 ./src/influxdb/write.rs
|
67e42a92295480d2f1d364958f184654cfb5d4b77e7e1df47f588bdec18868a0 ./src/influxdb/write.rs
|
||||||
62f9da6ef4f5fd92701d0857773f9f6236ef25356f21b9b42236abab6ba7da53 ./src/main.rs
|
62f9da6ef4f5fd92701d0857773f9f6236ef25356f21b9b42236abab6ba7da53 ./src/main.rs
|
||||||
23688a5cb68efded89471ce959328d27adff370d451bd9f378a283ab51437e76 ./src/models.rs
|
23688a5cb68efded89471ce959328d27adff370d451bd9f378a283ab51437e76 ./src/models.rs
|
||||||
920e028d96a14cca9f74a964bb296e68a109cfead314d3164c180201e606d151 ./src/models/automation.rs
|
e7b6e49d369aaf665a8aea2464950fd795957de0ad7528a002e11c55c7983f35 ./src/models/automation.rs
|
||||||
fc5f2802a073a175a8985e56bcb87eb1ff481226d5b4eeabad73dbba1839fc45 ./src/models/control_plan.rs
|
fc5f2802a073a175a8985e56bcb87eb1ff481226d5b4eeabad73dbba1839fc45 ./src/models/control_plan.rs
|
||||||
06357b352ff6494d83e72796b869573a8b8f36b835985a5900ac571c922b8da9 ./src/models/defaults.rs
|
06357b352ff6494d83e72796b869573a8b8f36b835985a5900ac571c922b8da9 ./src/models/defaults.rs
|
||||||
698dcbeba72037a251c25d07c95833f121716d949f9217f1f9429e8cd40638e5 ./src/models/device.rs
|
698dcbeba72037a251c25d07c95833f121716d949f9217f1f9429e8cd40638e5 ./src/models/device.rs
|
||||||
@@ -199,10 +199,10 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
|
|||||||
8c34b70fb5f80e3249c2e0c63686e60ce1697cf44a7e6f11b3ad6f689ce17792 ./src/state.rs
|
8c34b70fb5f80e3249c2e0c63686e60ce1697cf44a7e6f11b3ad6f689ce17792 ./src/state.rs
|
||||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||||
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
||||||
1043dbcfb24a474ce088acb6aa86479e84db74688fb4802febeb3f3f564ca65f ./web/css/styles.css
|
c60667f270dc66ee463f425ca5eda676363fcd2390e5d652220f6b4af9ded6c3 ./web/css/styles.css
|
||||||
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
|
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
|
||||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||||
cc1509fd76f0b0bd4c069db63e1e104210c2bed05f32a3eebac7642f94ef8a99 ./web/index.html
|
ca887343ec089cbbb83c4ce5685edbade1266c89a29b5570c02f75c9c50b1c10 ./web/index.html
|
||||||
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js-dynamic/README.md
|
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js-dynamic/README.md
|
||||||
e74bb06dba646f298a4d118b5c2dca6ba24f1179ce8ea8d53c1acd6000ca332c ./web/js-dynamic/bootstrap.js
|
e74bb06dba646f298a4d118b5c2dca6ba24f1179ce8ea8d53c1acd6000ca332c ./web/js-dynamic/bootstrap.js
|
||||||
5d57253a013fd1cb79e4b8701d4be6e394e3f2acbce73dfcd726a86e5878f1a1 ./web/js-dynamic/charts.js
|
5d57253a013fd1cb79e4b8701d4be6e394e3f2acbce73dfcd726a86e5878f1a1 ./web/js-dynamic/charts.js
|
||||||
@@ -210,7 +210,7 @@ e74bb06dba646f298a4d118b5c2dca6ba24f1179ce8ea8d53c1acd6000ca332c ./web/js-dynam
|
|||||||
752b3c3b59558b42d867d4a15329238f0213f354b76f7011c10e31b4e6dbb311 ./web/js-dynamic/dashboard.js
|
752b3c3b59558b42d867d4a15329238f0213f354b76f7011c10e31b4e6dbb311 ./web/js-dynamic/dashboard.js
|
||||||
6b166a9311e30fe948bd3236189436eea769c238f963ba00acec5cdb51ff1dd7 ./web/js-dynamic/entities.js
|
6b166a9311e30fe948bd3236189436eea769c238f963ba00acec5cdb51ff1dd7 ./web/js-dynamic/entities.js
|
||||||
b4204241eecf387256364f4230b23ed50d947a362f4ceb1e528d69494e93d7cf ./web/js-dynamic/events.js
|
b4204241eecf387256364f4230b23ed50d947a362f4ceb1e528d69494e93d7cf ./web/js-dynamic/events.js
|
||||||
879a92e95575ace4775b2f9a7f189e78ad0082ca5c12d012ef456e6477010886 ./web/js-dynamic/flows.js
|
e0f604f54eafb9a6d6ac1c6c24eb27f3e3c1e31ed80ead57ac0340c7228318b8 ./web/js-dynamic/flows.js
|
||||||
3ae714de6b71c068ba665c81a7689f994b1716284e8d937db669ad500f12cf63 ./web/js-dynamic/forms.js
|
3ae714de6b71c068ba665c81a7689f994b1716284e8d937db669ad500f12cf63 ./web/js-dynamic/forms.js
|
||||||
92d013bb5d64f64aa8c55380704540a5f72732bde7ccd16554965e401daf50ea ./web/js-dynamic/history.js
|
92d013bb5d64f64aa8c55380704540a5f72732bde7ccd16554965e401daf50ea ./web/js-dynamic/history.js
|
||||||
511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js-dynamic/main.js
|
511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js-dynamic/main.js
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
Self-hosted controller for GREE-compatible air conditioners with a local Web UI, thermostat zones, schedules, Home Assistant integration, history, notifications and a documented HTTP/WebSocket API.
|
Self-hosted controller for GREE-compatible air conditioners with a local Web UI, thermostat zones, schedules, Home Assistant integration, history, notifications and a documented HTTP/WebSocket API.
|
||||||
|
|
||||||
**Version: 0.15.8**
|
**Version: 0.15.9**
|
||||||
|
|
||||||
Version 0.15.8 improves Visual Flow portability and editor ergonomics: exported Flows carry the Shared Input definitions they use, imports can create or safely remap missing Shared Inputs, and the Interpretation / Flow cycle panel is collapsed by default on every viewport.
|
Version 0.15.9 strengthens Visual Flow execution and editing: change events remain pending until their action succeeds, rolling mean/median blocks wait for a complete configured window, thermostat action summaries show power/mode explicitly, and the canvas adds box selection plus copy/paste/duplicate for groups of blocks.
|
||||||
|
|
||||||
> [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples.
|
> [Full API reference](docs/API.md) — authentication, every endpoint, request bodies, response models, WebSocket events and examples.
|
||||||
> Built-in API: `/api/*` and `/ws`; interactive Swagger docs: `/api-docs`; OpenAPI JSON: `/api-docs/openapi.json`.
|
> Built-in API: `/api/*` and `/ws`; interactive Swagger docs: `/api-docs`; OpenAPI JSON: `/api-docs/openapi.json`.
|
||||||
|
|||||||
+19
-19
@@ -1,13 +1,13 @@
|
|||||||
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
de0793949cf01d27d903653226ecc9b5f72523d8711f20bcab022cf42c4c4ed0 ./.env.example
|
||||||
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
|
||||||
ddda584a029e67d057e3afbe93246795e6579d7efd2f3e9ebe4cfd2bb164e907 ./Cargo.lock
|
0c5cd71516b15e7b51a39fd694d8532a38485d8a8d358caab8ae850274a41127 ./Cargo.lock
|
||||||
d09e3c8438adcfac6aa9d16de9aade898a1fa6f64f7edf17bcd70afb714db12f ./Cargo.toml
|
e3b77008eebcb6db5b188102f788cd06f5159c1a66577bb212d96d125aa72510 ./Cargo.toml
|
||||||
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
|
||||||
5b5586d2e3e7b80c0ffe2d241517f8f7a8c4d7e80169a6cb3cf46221024f54e9 ./README.md
|
d5e5fdafaf8d777e3848b5c8f3aacfd0cf4c80735fbbb6bda14e86468329de88 ./README.md
|
||||||
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
|
29e9821e5ed216ea3565dd6d8bc0b174ed3cf9fa72c4ccebbfa99e98db3ad8f9 ./build.rs
|
||||||
70a8217b46a12ae47e6b461f6f619fa19facb0b422fd8d850208b55237abc752 ./docs/API.md
|
96144a5c5f3688854ab19dcd97794bd74da8ed26133c1468d91f6659a54faa4b ./docs/API.md
|
||||||
c6b1aded8ef6f4c840a2b1f2ffa3f0b22eddc080a4750adb34ee950f12f64382 ./docs/FLOW.md
|
0884be80021ff93826525f0b5bd5c81418f0c9c4eadec9583386ec61ae8e82ba ./docs/FLOW.md
|
||||||
9debc0d7d3f663c2a46ab17fd385a37aa732757e6bb7c75d6d60281ea89092b5 ./docs/openapi.json
|
d883d4a58b19b10b5b33dacd4a58b0a4eb69dcc133a478524407027956ed9f70 ./docs/openapi.json
|
||||||
77b8becf7a2c16a384ea85721d878b76e2d520549cb4455d9aa806e57e5a2ecd ./ha-addon/.env.example
|
77b8becf7a2c16a384ea85721d878b76e2d520549cb4455d9aa806e57e5a2ecd ./ha-addon/.env.example
|
||||||
0ca6e58170c2b8925194431ebbdb55f0c8b377fec5cd422cd2bd257e2d9328e3 ./ha-addon/Dockerfile
|
0ca6e58170c2b8925194431ebbdb55f0c8b377fec5cd422cd2bd257e2d9328e3 ./ha-addon/Dockerfile
|
||||||
0951696a2c2e66d4d969f8e7f8641184e3012f261583b31074bc38a7a1c0cc1a ./ha-addon/README.md
|
0951696a2c2e66d4d969f8e7f8641184e3012f261583b31074bc38a7a1c0cc1a ./ha-addon/README.md
|
||||||
@@ -21,7 +21,7 @@ b7f0873109c52be9d7f09bea3dffc416103c50085e1f0680d11661a969479898 ./ha-addon/hom
|
|||||||
6509286c7b7de638dcc5d40ce53181e65d1eddc5dc36a13306dc6d87220fedbb ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py
|
6509286c7b7de638dcc5d40ce53181e65d1eddc5dc36a13306dc6d87220fedbb ./ha-addon/home-assistant/custom_components/gree_controller/coordinator.py
|
||||||
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py
|
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./ha-addon/home-assistant/custom_components/gree_controller/entity_map.py
|
||||||
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png
|
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./ha-addon/home-assistant/custom_components/gree_controller/icon.png
|
||||||
00fc79f5195f43cd8d076b3d9a7a782263f0bde2a6b58b310c2d7a8b307a9f88 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
62e7bb884b342349bb175ce874d11ccb845e8219512ff6cf4d6c9ed03cb12294 ./ha-addon/home-assistant/custom_components/gree_controller/manifest.json
|
||||||
c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py
|
c52a484b671ce738ecc00228a1db19a5d703f69ab27530c2896a5c2ae3b4a96f ./ha-addon/home-assistant/custom_components/gree_controller/number.py
|
||||||
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py
|
39c4309001b75abb56234f05662bc06e077054986876f1927937edbce528ec95 ./ha-addon/home-assistant/custom_components/gree_controller/select.py
|
||||||
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py
|
cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/home-assistant/custom_components/gree_controller/sensor.py
|
||||||
@@ -29,10 +29,10 @@ cca65482e36d48035aca178121a378fe7d578d600acff267ae81a6399c0da653 ./ha-addon/hom
|
|||||||
a745cc458c235158d5d70b6a33d8000fd8b40ff93bfe2f2796af0f4b3a9eef9b ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
a745cc458c235158d5d70b6a33d8000fd8b40ff93bfe2f2796af0f4b3a9eef9b ./ha-addon/home-assistant/custom_components/gree_controller/translations/en.json
|
||||||
50b7d6fe6329ad4ea6bee9612ab141c2f73f8382fcc2af0bb076a8e8d4dd4465 ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json
|
50b7d6fe6329ad4ea6bee9612ab141c2f73f8382fcc2af0bb076a8e8d4dd4465 ./ha-addon/home-assistant/custom_components/gree_controller/translations/pl.json
|
||||||
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json
|
c5fc5c87273d82d2834a0a5a365c821a416413ed509613441e24e4dc3507c4d2 ./ha-addon/home-assistant/generated/gree_controller_entities.example.json
|
||||||
2976822c9663e505d22297e2749192b4ca119bb4e46ccc1c29c813522c84b3c5 ./ha-addon/repository/gree-controller/CHANGELOG.md
|
9cb2e827567e8cb06050e24fd3811dda6f51350d8bec9b605189fa8ab85b331a ./ha-addon/repository/gree-controller/CHANGELOG.md
|
||||||
50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md
|
50b8ba050c6b4919e4d057f7b38c1e23d3913b5b7c87552626452e9695d37251 ./ha-addon/repository/gree-controller/DOCS.md
|
||||||
5f0bd0d1ea4aca47844f9e870e4ba1676ad8b90369577db75a1bb0d00228a9da ./ha-addon/repository/gree-controller/README.md
|
0bd253135a67d295dfca355b735b350dbf36ed396a6c8e30387d5218d4e5afcf ./ha-addon/repository/gree-controller/README.md
|
||||||
f43723e7e356c7cc637b36b5ade0e34cfb69c50c4cf653cca7fc013e926cd7c9 ./ha-addon/repository/gree-controller/config.yaml
|
cb348473b59a76dfd29adc04d281128a9b8848b92107b928073aac4e047fdc4b ./ha-addon/repository/gree-controller/config.yaml
|
||||||
3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png
|
3aae6cdae4c3aaab7786a9575e7093b7e2c5b69288167075f4b7475611691a6b ./ha-addon/repository/gree-controller/topologia-ha-vlan-gree-pl.png
|
||||||
0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png
|
0d0b42a7639b128946eec379175436fa1f150294f39d2dfd169077bfa87fd14d ./ha-addon/repository/gree-controller/topology-ha-vlan-gree-en.png
|
||||||
2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml
|
2272740b185e9520e948f74b8f45cf98b8b5fd28c93bdee760da626f91fe5c2a ./ha-addon/repository/gree-controller/translations/en.yaml
|
||||||
@@ -42,8 +42,8 @@ cea12bc74c066a588a5ef0ef7bad9d4906bdbcea99f2d275429b0eefc94b4b5f ./ha-addon/run
|
|||||||
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
|
081e27b6e43070d44f865335c30a42a5a29668eaa6a5ea5ab6967ac4447b8e06 ./ha-addon/sync-repository.sh
|
||||||
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
|
f9e31de3d109ead32bf531c074d46a087df398d62488caee0b46b5e0a1c027f7 ./ha_addon.md
|
||||||
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
253a0bc912786e67ea7fc92a64e4a510ad973bec343a88ccfb1f28fca3e8cf01 ./lang/README.md
|
||||||
37d24f8de343b4796a0a8cfff3a839e55bdb779add0c8119799787d87ad8b953 ./lang/en.json
|
e9f8da620e017a3c06363f9b3d8709d9abaee9bbac4e53e60f27db7c072bb647 ./lang/en.json
|
||||||
1610da0b2bbb9424006647adfb95e1318b7670aaed5a2798db1251db82635317 ./lang/pl.json
|
efc35994018e526ed829ee26b3cb1c74df2ac3655433e3b9bd0bf12f087e3223 ./lang/pl.json
|
||||||
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
d8459024f04ca514bd8e9d6bd3af872fb942fd85a7cdd5583f4a9d28aab6faba ./make_zip.py
|
||||||
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
b14233a8987e53bbbdd6770386ba10fa166ec25c1e098275c173c544b37846fc ./presets/bedroom_window_night.json
|
||||||
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
1960841119c0b673fb2f03621b32237552fd828045304cd221401214b997c910 ./presets/device_resilience.json
|
||||||
@@ -84,7 +84,7 @@ fdcd9a5055d08037278b842e7ab69265345c5811f0a06867136c511d140bb191 ./presets/wind
|
|||||||
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
|
804f22123cd3e8db0fac791826c8dd9f758fb866c8e3b5655fb6d25d259dccf1 ./presets/workday_comfort.json
|
||||||
fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh
|
fd7390d64a2378352c09382accf6efff43903d952d111b22a982f06c5b2a3b4b ./regenerate-sha.sh
|
||||||
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
|
c64b1c6deeb24f20af662bfce7fa3683703914987241107ce664fa2f6d1a9dcb ./scripts/README.md
|
||||||
3a3287a4ca516f91a1b1bf14257aeef7b1b3027d592200c0a6da1cc70afa9c32 ./scripts/api_dev_test.py
|
863f087740abee259d51dc5935c6b60dfe25b31a6a9fe64657d25a05d200a6fa ./scripts/api_dev_test.py
|
||||||
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
5bc736c7bc76ca80aaa406bb171d2aa91baf4c3aa8695dce0e09b888b6ab3146 ./scripts/common.sh
|
||||||
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
|
6403786610ee6d2f628193c25aee0dd058d62e904aa1a31d5f62fdaae0e94b4f ./scripts/configure-gree-network.sh
|
||||||
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
|
1d6e14e26e49aa9d3527f30a23668bf8d9c48b67e6628ef686c3155c012155de ./scripts/dev.sh
|
||||||
@@ -135,7 +135,7 @@ d6b9b70a04c774173c97f7426487d5e2932d7713e12ae3fb7f64cf2b4e0b3c10 ./src/db/sched
|
|||||||
28ee42d08147b0f154af818065d7d014d74ee8ddd59fdef98115f5c5368056cc ./src/db/tests.rs
|
28ee42d08147b0f154af818065d7d014d74ee8ddd59fdef98115f5c5368056cc ./src/db/tests.rs
|
||||||
f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs
|
f90fbb14f29a20b799b3856bdaa22812a9f0d9dfa46693f1341c160dccda8a97 ./src/db/zone_history.rs
|
||||||
0e49ea4a52017b0a8501d5101b1906a7a1e8d5158acb0bb65caa43cde2836ec0 ./src/engine.rs
|
0e49ea4a52017b0a8501d5101b1906a7a1e8d5158acb0bb65caa43cde2836ec0 ./src/engine.rs
|
||||||
76dbf72600a48a6ba5b2273777bd8f2c8a6bd212aa236d419cc59eedcb8ab22b ./src/engine/automations.rs
|
a0d60ad81d024488dfec586b0d9e9f447e9a3485985604415465cacba59d645f ./src/engine/automations.rs
|
||||||
1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs
|
1dccb8779f87dfdbd4ccda3f02ec51273e7be2e34e85166861668685528eca79 ./src/engine/commands.rs
|
||||||
357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs
|
357ef92c3cc42b243af4da31ac52bb360d7444ae423f58d7a13bdee79bf0a748 ./src/engine/connectivity.rs
|
||||||
ad53cd7be24deb759e03cbc6472522b69ebd36d8fac35398a9b19c899d3b17f2 ./src/engine/control_plan.rs
|
ad53cd7be24deb759e03cbc6472522b69ebd36d8fac35398a9b19c899d3b17f2 ./src/engine/control_plan.rs
|
||||||
@@ -151,7 +151,7 @@ c73691c3637465ab0f79aecf7f5428d0317799a8ab5329ed564cf57963bbe86e ./src/engine/r
|
|||||||
b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs
|
b0189f28d0f337dfd3ce0934da7a565e4a5535afc1c242214c26ed584b3b7840 ./src/engine/targets.rs
|
||||||
682c8b3956c657b655746cbd19ee97ee2f4cf953bc9de67d6d70a1b14f3dd746 ./src/engine/temperature.rs
|
682c8b3956c657b655746cbd19ee97ee2f4cf953bc9de67d6d70a1b14f3dd746 ./src/engine/temperature.rs
|
||||||
833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs
|
833b72907375e75c3f42b3921ad70a8e298f5a93743bf0d0c19059aa272462d1 ./src/engine/temporary_thermostat.rs
|
||||||
af4edaf12faf294f44a8992d120a99b09edd1cbdd66981390e027d13fd6b6790 ./src/engine/tests.rs
|
0404e040c5d6f388682edb3d103a658e0b963a402e4e847fc92fd0aea1c0e773 ./src/engine/tests.rs
|
||||||
c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs
|
c89ff36c6a67bf4fbf68c43e18aa0a556de2dc69263fd79db376363afc741a4f ./src/engine/zone_actions.rs
|
||||||
fb169bcc20f95abe9892367df6dd3a683584bdbc721792d6ffb9311b81ca30d9 ./src/engine/zone_control.rs
|
fb169bcc20f95abe9892367df6dd3a683584bdbc721792d6ffb9311b81ca30d9 ./src/engine/zone_control.rs
|
||||||
bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs
|
bfac95cec87f0df6ce562153cd3cfe6731359d703e6c592d2c2f7ba01ea62d99 ./src/error.rs
|
||||||
@@ -162,7 +162,7 @@ da4068295f37c23222bb13bebac022d88ba7729604a49ad25438c55e45458a49 ./src/influxdb
|
|||||||
67e42a92295480d2f1d364958f184654cfb5d4b77e7e1df47f588bdec18868a0 ./src/influxdb/write.rs
|
67e42a92295480d2f1d364958f184654cfb5d4b77e7e1df47f588bdec18868a0 ./src/influxdb/write.rs
|
||||||
62f9da6ef4f5fd92701d0857773f9f6236ef25356f21b9b42236abab6ba7da53 ./src/main.rs
|
62f9da6ef4f5fd92701d0857773f9f6236ef25356f21b9b42236abab6ba7da53 ./src/main.rs
|
||||||
23688a5cb68efded89471ce959328d27adff370d451bd9f378a283ab51437e76 ./src/models.rs
|
23688a5cb68efded89471ce959328d27adff370d451bd9f378a283ab51437e76 ./src/models.rs
|
||||||
920e028d96a14cca9f74a964bb296e68a109cfead314d3164c180201e606d151 ./src/models/automation.rs
|
e7b6e49d369aaf665a8aea2464950fd795957de0ad7528a002e11c55c7983f35 ./src/models/automation.rs
|
||||||
fc5f2802a073a175a8985e56bcb87eb1ff481226d5b4eeabad73dbba1839fc45 ./src/models/control_plan.rs
|
fc5f2802a073a175a8985e56bcb87eb1ff481226d5b4eeabad73dbba1839fc45 ./src/models/control_plan.rs
|
||||||
06357b352ff6494d83e72796b869573a8b8f36b835985a5900ac571c922b8da9 ./src/models/defaults.rs
|
06357b352ff6494d83e72796b869573a8b8f36b835985a5900ac571c922b8da9 ./src/models/defaults.rs
|
||||||
698dcbeba72037a251c25d07c95833f121716d949f9217f1f9429e8cd40638e5 ./src/models/device.rs
|
698dcbeba72037a251c25d07c95833f121716d949f9217f1f9429e8cd40638e5 ./src/models/device.rs
|
||||||
@@ -198,10 +198,10 @@ f13f1be3d5789539eba3fc1b59c14835f8c681eca7634e118fa3763a53aeee2b ./src/queries/
|
|||||||
8c34b70fb5f80e3249c2e0c63686e60ce1697cf44a7e6f11b3ad6f689ce17792 ./src/state.rs
|
8c34b70fb5f80e3249c2e0c63686e60ce1697cf44a7e6f11b3ad6f689ce17792 ./src/state.rs
|
||||||
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
b92a6cb158b494fe145b43c7641e65f6fafff47201d7d76edbec2cfd8b94835c ./systemd/gree-controller.service
|
||||||
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
544a31cb2e5374227026afd6abd0d910381f3fca1638154ebd74e3c3d96a35d1 ./web/404.html
|
||||||
1043dbcfb24a474ce088acb6aa86479e84db74688fb4802febeb3f3f564ca65f ./web/css/styles.css
|
c60667f270dc66ee463f425ca5eda676363fcd2390e5d652220f6b4af9ded6c3 ./web/css/styles.css
|
||||||
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
|
183d122ba366303d9952ac0e68aff4bced4e0d9a468fbe51316c13f15227402b ./web/custom-chart.html
|
||||||
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.svg
|
||||||
cc1509fd76f0b0bd4c069db63e1e104210c2bed05f32a3eebac7642f94ef8a99 ./web/index.html
|
ca887343ec089cbbb83c4ce5685edbade1266c89a29b5570c02f75c9c50b1c10 ./web/index.html
|
||||||
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js-dynamic/README.md
|
9ba3f6fa05b72aa989139b2a909982571b2a02055052e4c40104f1e81a9aa7eb ./web/js-dynamic/README.md
|
||||||
e74bb06dba646f298a4d118b5c2dca6ba24f1179ce8ea8d53c1acd6000ca332c ./web/js-dynamic/bootstrap.js
|
e74bb06dba646f298a4d118b5c2dca6ba24f1179ce8ea8d53c1acd6000ca332c ./web/js-dynamic/bootstrap.js
|
||||||
5d57253a013fd1cb79e4b8701d4be6e394e3f2acbce73dfcd726a86e5878f1a1 ./web/js-dynamic/charts.js
|
5d57253a013fd1cb79e4b8701d4be6e394e3f2acbce73dfcd726a86e5878f1a1 ./web/js-dynamic/charts.js
|
||||||
@@ -209,7 +209,7 @@ e74bb06dba646f298a4d118b5c2dca6ba24f1179ce8ea8d53c1acd6000ca332c ./web/js-dynam
|
|||||||
752b3c3b59558b42d867d4a15329238f0213f354b76f7011c10e31b4e6dbb311 ./web/js-dynamic/dashboard.js
|
752b3c3b59558b42d867d4a15329238f0213f354b76f7011c10e31b4e6dbb311 ./web/js-dynamic/dashboard.js
|
||||||
6b166a9311e30fe948bd3236189436eea769c238f963ba00acec5cdb51ff1dd7 ./web/js-dynamic/entities.js
|
6b166a9311e30fe948bd3236189436eea769c238f963ba00acec5cdb51ff1dd7 ./web/js-dynamic/entities.js
|
||||||
b4204241eecf387256364f4230b23ed50d947a362f4ceb1e528d69494e93d7cf ./web/js-dynamic/events.js
|
b4204241eecf387256364f4230b23ed50d947a362f4ceb1e528d69494e93d7cf ./web/js-dynamic/events.js
|
||||||
879a92e95575ace4775b2f9a7f189e78ad0082ca5c12d012ef456e6477010886 ./web/js-dynamic/flows.js
|
e0f604f54eafb9a6d6ac1c6c24eb27f3e3c1e31ed80ead57ac0340c7228318b8 ./web/js-dynamic/flows.js
|
||||||
3ae714de6b71c068ba665c81a7689f994b1716284e8d937db669ad500f12cf63 ./web/js-dynamic/forms.js
|
3ae714de6b71c068ba665c81a7689f994b1716284e8d937db669ad500f12cf63 ./web/js-dynamic/forms.js
|
||||||
92d013bb5d64f64aa8c55380704540a5f72732bde7ccd16554965e401daf50ea ./web/js-dynamic/history.js
|
92d013bb5d64f64aa8c55380704540a5f72732bde7ccd16554965e401daf50ea ./web/js-dynamic/history.js
|
||||||
511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js-dynamic/main.js
|
511c4e7cc4c48fbbdab64558a9d1c27e6759c9b6dca255d11f301b9708e955c7 ./web/js-dynamic/main.js
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
# GREE Controller API reference
|
# GREE Controller API reference
|
||||||
|
|
||||||
HTTP and WebSocket API for GREE Controller **0.15.8**.
|
HTTP and WebSocket API for GREE Controller **0.15.9**.
|
||||||
|
|
||||||
[← Main documentation](../README.md)
|
[← Main documentation](../README.md)
|
||||||
|
|
||||||
@@ -241,7 +241,7 @@ Response:
|
|||||||
{
|
{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"name": "gree-controller",
|
"name": "gree-controller",
|
||||||
"version": "0.15.8",
|
"version": "0.15.9",
|
||||||
"uptime_seconds": 1234,
|
"uptime_seconds": 1234,
|
||||||
"control_ready": true,
|
"control_ready": true,
|
||||||
"time": "2026-08-30T06:54:00Z"
|
"time": "2026-08-30T06:54:00Z"
|
||||||
@@ -304,7 +304,7 @@ Returns the initial Web UI snapshot:
|
|||||||
"control_plan": {"generated_at": "2026-09-04T08:00:00Z", "zones": [], "rules": []},
|
"control_plan": {"generated_at": "2026-09-04T08:00:00Z", "zones": [], "rules": []},
|
||||||
"control_plan_revision": 42,
|
"control_plan_revision": 42,
|
||||||
"system": {
|
"system": {
|
||||||
"version": "0.15.8",
|
"version": "0.15.9",
|
||||||
"uptime_seconds": 1234,
|
"uptime_seconds": 1234,
|
||||||
"auth_required": false,
|
"auth_required": false,
|
||||||
"control_ready": true,
|
"control_ready": true,
|
||||||
|
|||||||
+2
-2
@@ -60,7 +60,7 @@ Dry-run never mutates thermostats, devices, groups, schedules or automations. Fo
|
|||||||
|
|
||||||
## Blocks
|
## Blocks
|
||||||
|
|
||||||
Condition/source blocks include weekday, time range, date range, optional 5-field CRON triggers, application Night mode, outdoor/device/zone temperature, house mode, device state, thermostat/zone state, group state, Home Assistant state/numeric/attribute/availability, rolling mean/median windows, numeric oscillation detection, a diagnostic constant, and `shared_input`. Stateful gates include `stable_for`, bounded `state_duration`, `on_change` (condition-result or raw-value edge), `rate_limit` (maximum successful executions in a rolling period), and `delay`. Their runtime state, together with rolling statistics and oscillation samples, is persisted in the generated automation payload so restarts do not silently reset timing/history. Editing a stateful block resets only that block's incompatible runtime state. Flow state continues to be observed while the action itself is in cooldown, preventing stale continuity/change state. A `rate_limit` block must be placed directly before an action and consumes quota only after a successful execution; suppressed or failed actions do not consume it. Shared inputs are configured centrally in Home Assistant / Sensors and referenced by ID. Shared inputs store reusable value sources only; operators and comparison values are configured exclusively in each Flow reference. Logic blocks are AND, OR and NOT.
|
Condition/source blocks include weekday, time range, date range, optional 5-field CRON triggers, application Night mode, outdoor/device/zone temperature, house mode, device state, thermostat/zone state, group state, Home Assistant state/numeric/attribute/availability, rolling mean/median windows, numeric oscillation detection, a diagnostic constant, and `shared_input`. Stateful gates include `stable_for`, bounded `state_duration`, `on_change` (condition-result or raw-value edge), `rate_limit` (maximum successful executions in a rolling period), and `delay`. Their runtime state, together with rolling statistics and oscillation samples, is persisted in the generated automation payload so restarts do not silently reset timing/history. Editing a stateful block resets only that block's incompatible runtime state. Flow state continues to be observed while the action itself is in cooldown. `on_change` latches a detected event while its upstream condition remains true and clears it only after a successful downstream action, so cooldown, ownership suppression or same-cycle target arbitration cannot silently consume the edge. Rolling mean/median blocks wait for one complete configured time window before matching and restart that warm-up after their source becomes unavailable. A `rate_limit` block must be placed directly before an action and consumes quota only after a successful execution; suppressed or failed actions do not consume it. Shared inputs are configured centrally in Home Assistant / Sensors and referenced by ID. Shared inputs store reusable value sources only; operators and comparison values are configured exclusively in each Flow reference. Logic blocks are AND, OR and NOT.
|
||||||
|
|
||||||
Actions include thermostat zone, full GREE device command, single GREE function, climate group, and generic Home Assistant service calls (`domain`, `service`, optional `entity_id`, JSON service data). Direct GREE actions expose power, HVAC mode, target temperature, fan speed, vertical/horizontal louver position, quiet, turbo, light, air, xfan, health and sleep. The single-function block writes exactly one of those fields.
|
Actions include thermostat zone, full GREE device command, single GREE function, climate group, and generic Home Assistant service calls (`domain`, `service`, optional `entity_id`, JSON service data). Direct GREE actions expose power, HVAC mode, target temperature, fan speed, vertical/horizontal louver position, quiet, turbo, light, air, xfan, health and sleep. The single-function block writes exactly one of those fields.
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ Shared Flow inputs show the Flows that reference them and link directly to those
|
|||||||
|
|
||||||
Presets are data files, not JavaScript definitions. Every preset lives in `presets/<id>.json` and contains `id`, `category`, localized `name`, localized `description`, and the complete `flow` graph (`nodes` + `edges`). `build.rs` validates and embeds the directory, exposes `/presets/index.json` plus `/presets/<file>.json`, and the Flow editor loads that library dynamically. Device/zone/group placeholders such as `$zone1`, `$zone2`, `$device1` and `$group1` are resolved when a preset is applied. Adding a preset therefore does not require adding graph-building code to `web/js-dynamic/flows.js`.
|
Presets are data files, not JavaScript definitions. Every preset lives in `presets/<id>.json` and contains `id`, `category`, localized `name`, localized `description`, and the complete `flow` graph (`nodes` + `edges`). `build.rs` validates and embeds the directory, exposes `/presets/index.json` plus `/presets/<file>.json`, and the Flow editor loads that library dynamically. Device/zone/group placeholders such as `$zone1`, `$zone2`, `$device1` and `$group1` are resolved when a preset is applied. Adding a preset therefore does not require adding graph-building code to `web/js-dynamic/flows.js`.
|
||||||
|
|
||||||
The canvas supports multi-selection (Shift/Ctrl/Cmd), `Ctrl/Cmd+A`, Select all / Clear selection toolbar actions, and dragging the whole selected group while preserving relative positions.
|
The canvas supports drag-box multi-selection, additive Shift/Ctrl/Cmd selection, `Ctrl/Cmd+A`, Select all / Clear selection toolbar actions, and dragging the whole selected group while preserving relative positions. Selected blocks can be duplicated from the toolbar or with `Ctrl/Cmd+D`; `Ctrl/Cmd+C` and `Ctrl/Cmd+V` copy/paste the selected subgraph and preserve connections whose endpoints are both selected.
|
||||||
|
|
||||||
## Good next stateful blocks
|
## Good next stateful blocks
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@
|
|||||||
"openapi": "3.1.0",
|
"openapi": "3.1.0",
|
||||||
"info": {
|
"info": {
|
||||||
"title": "GREE Controller API",
|
"title": "GREE Controller API",
|
||||||
"version": "0.15.8",
|
"version": "0.15.9",
|
||||||
"summary": "Local HTTP/WebSocket API for GREE Controller",
|
"summary": "Local HTTP/WebSocket API for GREE Controller",
|
||||||
"description": "Local API used by the GREE Controller Web UI and Home Assistant integration.",
|
"description": "Local API used by the GREE Controller Web UI and Home Assistant integration.",
|
||||||
"license": {
|
"license": {
|
||||||
@@ -5967,7 +5967,7 @@
|
|||||||
},
|
},
|
||||||
"version": {
|
"version": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "0.15.8"
|
"example": "0.15.9"
|
||||||
},
|
},
|
||||||
"uptime_seconds": {
|
"uptime_seconds": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"domain": "gree_controller",
|
"domain": "gree_controller",
|
||||||
"name": "GREE Controller",
|
"name": "GREE Controller",
|
||||||
"version": "0.15.8",
|
"version": "0.15.9",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.15.9
|
||||||
|
|
||||||
|
- Visual Flow `on_change` events are now latched while their upstream condition remains true and are acknowledged only after the downstream action executes successfully, so cooldowns, ownership suppression and same-cycle target conflicts no longer silently consume a change event.
|
||||||
|
- Rolling mean/median blocks now require one complete configured time window after their runtime window starts or restarts before they can match; unavailable sources reset the rolling window instead of reusing partial history.
|
||||||
|
- Thermostat action summaries now show explicit power and HVAC mode alongside the preset/target, so `power=false` is displayed as OFF instead of looking like a preset-only Auto action.
|
||||||
|
- The Flow canvas adds drag-box multi-selection, keeps Shift/Ctrl/Cmd additive selection and moves the whole selection together.
|
||||||
|
- Selected Flow blocks can be copied/pasted or duplicated with internal connections preserved; Ctrl/Cmd+C, Ctrl/Cmd+V and Ctrl/Cmd+D are supported.
|
||||||
|
|
||||||
## 0.15.8
|
## 0.15.8
|
||||||
|
|
||||||
- Visual Flow exports now include the Shared Input definitions referenced by the graph.
|
- Visual Flow exports now include the Shared Input definitions referenced by the graph.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Local GREE HVAC controller packaged as a Home Assistant add-on, with Web UI, HTTP/WebSocket API, built-in Swagger API documentation, UDP discovery/control, `amd64` and `aarch64` support, ingress, and database backup from `/data/gree-controller.db`.
|
Local GREE HVAC controller packaged as a Home Assistant add-on, with Web UI, HTTP/WebSocket API, built-in Swagger API documentation, UDP discovery/control, `amd64` and `aarch64` support, ingress, and database backup from `/data/gree-controller.db`.
|
||||||
|
|
||||||
Version 0.15.8 makes Visual Flow exports portable with referenced Shared Inputs and keeps the Interpretation / Flow cycle details collapsed until opened.
|
Version 0.15.9 strengthens Visual Flow execution and editing with latched change events, full rolling-stat windows, clearer thermostat action summaries, box selection and multi-block copy/paste/duplicate.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: "GREE Controller"
|
name: "GREE Controller"
|
||||||
version: "0.15.8"
|
version: "0.15.9"
|
||||||
slug: "gree_controller"
|
slug: "gree_controller"
|
||||||
description: "Local GREE HVAC controller with Web UI, API, and Home Assistant integration"
|
description: "Local GREE HVAC controller with Web UI, API, and Home Assistant integration"
|
||||||
url: "https://git.linuxiarz.pl/gru/gree-controller-ha-addon/"
|
url: "https://git.linuxiarz.pl/gru/gree-controller-ha-addon/"
|
||||||
|
|||||||
@@ -1019,6 +1019,10 @@
|
|||||||
"flow.selectAll": "Select all",
|
"flow.selectAll": "Select all",
|
||||||
"flow.clearSelection": "Clear selection",
|
"flow.clearSelection": "Clear selection",
|
||||||
"flow.selectedCount": "Selected: {count}",
|
"flow.selectedCount": "Selected: {count}",
|
||||||
|
"flow.duplicateSelection": "Duplicate",
|
||||||
|
"flow.blocksCopied": "Copied blocks: {count}",
|
||||||
|
"flow.blocksPasted": "Pasted blocks: {count}",
|
||||||
|
"flow.blocksDuplicated": "Duplicated blocks: {count}",
|
||||||
"flow.node.sharedInput": "Shared input",
|
"flow.node.sharedInput": "Shared input",
|
||||||
"flow.sharedInputMissing": "Shared input missing",
|
"flow.sharedInputMissing": "Shared input missing",
|
||||||
"flow.sharedInputTitle": "Shared Flow input",
|
"flow.sharedInputTitle": "Shared Flow input",
|
||||||
|
|||||||
@@ -1019,6 +1019,10 @@
|
|||||||
"flow.selectAll": "Zaznacz wszystko",
|
"flow.selectAll": "Zaznacz wszystko",
|
||||||
"flow.clearSelection": "Wyczyść zaznaczenie",
|
"flow.clearSelection": "Wyczyść zaznaczenie",
|
||||||
"flow.selectedCount": "Zaznaczono: {count}",
|
"flow.selectedCount": "Zaznaczono: {count}",
|
||||||
|
"flow.duplicateSelection": "Duplikuj",
|
||||||
|
"flow.blocksCopied": "Skopiowano bloki: {count}",
|
||||||
|
"flow.blocksPasted": "Wklejono bloki: {count}",
|
||||||
|
"flow.blocksDuplicated": "Zduplikowano bloki: {count}",
|
||||||
"flow.node.sharedInput": "Wspólne wejście",
|
"flow.node.sharedInput": "Wspólne wejście",
|
||||||
"flow.sharedInputMissing": "Brak wspólnego wejścia",
|
"flow.sharedInputMissing": "Brak wspólnego wejścia",
|
||||||
"flow.sharedInputTitle": "Wspólne wejście Flow",
|
"flow.sharedInputTitle": "Wspólne wejście Flow",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Developer smoke/integration tests for GREE Controller API 0.15.8.
|
"""Developer smoke/integration tests for GREE Controller API 0.15.9.
|
||||||
|
|
||||||
Default mode is read-only and safe to run against a real controller.
|
Default mode is read-only and safe to run against a real controller.
|
||||||
Use --settings-write to additionally round-trip all split settings resources and
|
Use --settings-write to additionally round-trip all split settings resources and
|
||||||
@@ -26,7 +26,7 @@ from typing import Any, Callable, Iterable, Optional
|
|||||||
|
|
||||||
DEFAULT_BASE_URL = os.environ.get("GREE_API_URL", "http://127.0.0.1:8787")
|
DEFAULT_BASE_URL = os.environ.get("GREE_API_URL", "http://127.0.0.1:8787")
|
||||||
DEFAULT_TOKEN = os.environ.get("GREE_API_TOKEN", "")
|
DEFAULT_TOKEN = os.environ.get("GREE_API_TOKEN", "")
|
||||||
DEFAULT_EXPECTED_VERSION = "0.15.8"
|
DEFAULT_EXPECTED_VERSION = "0.15.9"
|
||||||
|
|
||||||
SETTINGS_PATHS: dict[str, set[str]] = {
|
SETTINGS_PATHS: dict[str, set[str]] = {
|
||||||
"/api/settings/application": {"simulator_enabled"},
|
"/api/settings/application": {"simulator_enabled"},
|
||||||
@@ -599,7 +599,7 @@ def run_suite(args: argparse.Namespace) -> int:
|
|||||||
print(f"Settings write tests: {'ENABLED' if args.settings_write else 'disabled'}\n")
|
print(f"Settings write tests: {'ENABLED' if args.settings_write else 'disabled'}\n")
|
||||||
|
|
||||||
runner.run("public health", lambda: test_health(client, args.expected_version))
|
runner.run("public health", lambda: test_health(client, args.expected_version))
|
||||||
runner.run("OpenAPI 0.15.8 contract", lambda: test_openapi(client, args.expected_version))
|
runner.run("OpenAPI 0.15.9 contract", lambda: test_openapi(client, args.expected_version))
|
||||||
runner.run("bootstrap snapshot contract", lambda: test_bootstrap_contract(client))
|
runner.run("bootstrap snapshot contract", lambda: test_bootstrap_contract(client))
|
||||||
runner.run("protected API requires auth", lambda: test_protected_auth(client))
|
runner.run("protected API requires auth", lambda: test_protected_auth(client))
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ async fn run_automations(state: &AppState) -> Result<()> {
|
|||||||
Ok(true) => {
|
Ok(true) => {
|
||||||
for device_id in target_devices { claimed_devices.insert(device_id); }
|
for device_id in target_devices { claimed_devices.insert(device_id); }
|
||||||
let fired_at = Utc::now();
|
let fired_at = Utc::now();
|
||||||
|
flow_acknowledge_change_gates(&mut item);
|
||||||
flow_record_rate_limited_execution(&mut item, fired_at.clone());
|
flow_record_rate_limited_execution(&mut item, fired_at.clone());
|
||||||
item.last_fired_at = Some(fired_at);
|
item.last_fired_at = Some(fired_at);
|
||||||
item.updated_at = Utc::now();
|
item.updated_at = Utc::now();
|
||||||
@@ -405,10 +406,27 @@ fn flow_state_duration_update(
|
|||||||
(within_min && within_max, elapsed)
|
(within_min && within_max, elapsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flow_change_gate_update(state: &mut crate::models::FlowRuntimeNodeState, current: Value) -> bool {
|
fn flow_change_gate_update(
|
||||||
|
state: &mut crate::models::FlowRuntimeNodeState,
|
||||||
|
current: Value,
|
||||||
|
input: bool,
|
||||||
|
) -> (bool, bool) {
|
||||||
let changed = state.last_value.as_ref().is_some_and(|previous| previous != ¤t);
|
let changed = state.last_value.as_ref().is_some_and(|previous| previous != ¤t);
|
||||||
state.last_value = Some(current);
|
state.last_value = Some(current);
|
||||||
changed
|
if !input {
|
||||||
|
state.pending = false;
|
||||||
|
} else if changed {
|
||||||
|
state.pending = true;
|
||||||
|
}
|
||||||
|
(changed, state.pending)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flow_acknowledge_change_gates(item: &mut Automation) {
|
||||||
|
for condition in item.flow_conditions.iter().filter(|condition| condition.kind == "on_change") {
|
||||||
|
if let Some(state) = item.flow_runtime.get_mut(&condition.id) {
|
||||||
|
state.pending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flow_rate_limit_status(
|
fn flow_rate_limit_status(
|
||||||
@@ -444,9 +462,13 @@ fn flow_rolling_stat_update(
|
|||||||
statistic: &str,
|
statistic: &str,
|
||||||
now: DateTime<Utc>,
|
now: DateTime<Utc>,
|
||||||
) -> Option<f64> {
|
) -> Option<f64> {
|
||||||
|
let started_at = state.since.get_or_insert_with(|| now.clone()).clone();
|
||||||
let cutoff = now.clone() - chrono::Duration::seconds(window_seconds as i64);
|
let cutoff = now.clone() - chrono::Duration::seconds(window_seconds as i64);
|
||||||
state.samples.retain(|item| item.at >= cutoff);
|
state.samples.retain(|item| item.at >= cutoff);
|
||||||
state.samples.push(crate::models::FlowRuntimeSample { at: now, value: sample });
|
state.samples.push(crate::models::FlowRuntimeSample { at: now.clone(), value: sample });
|
||||||
|
if now.signed_duration_since(started_at).num_seconds().max(0) < window_seconds as i64 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let mut values = state.samples.iter().map(|item| item.value).collect::<Vec<_>>();
|
let mut values = state.samples.iter().map(|item| item.value).collect::<Vec<_>>();
|
||||||
if values.is_empty() { return None; }
|
if values.is_empty() { return None; }
|
||||||
if statistic == "median" {
|
if statistic == "median" {
|
||||||
@@ -730,13 +752,14 @@ pub async fn evaluate_flow_conditions_trace(
|
|||||||
let mode = condition.config.get("mode").and_then(Value::as_str).unwrap_or("result");
|
let mode = condition.config.get("mode").and_then(Value::as_str).unwrap_or("result");
|
||||||
let observed = if mode == "value" { actual_values.get(&input_id).cloned() } else { Some(json!(input)) };
|
let observed = if mode == "value" { actual_values.get(&input_id).cloned() } else { Some(json!(input)) };
|
||||||
let mut changed = false;
|
let mut changed = false;
|
||||||
|
let mut pending = false;
|
||||||
let mut previous = None;
|
let mut previous = None;
|
||||||
if let (Some(current), Some(map)) = (observed.clone(), runtime.as_deref_mut()) {
|
if let (Some(current), Some(map)) = (observed.clone(), runtime.as_deref_mut()) {
|
||||||
let state = map.entry(condition.id.clone()).or_default();
|
let state = map.entry(condition.id.clone()).or_default();
|
||||||
previous = state.last_value.clone();
|
previous = state.last_value.clone();
|
||||||
changed = flow_change_gate_update(state, current);
|
(changed, pending) = flow_change_gate_update(state, current, input);
|
||||||
}
|
}
|
||||||
(input && changed, json!({"input": input, "mode": mode, "previous": previous, "current": observed, "changed": changed}))
|
(input && pending, json!({"input": input, "mode": mode, "previous": previous, "current": observed, "changed": changed, "pending": pending}))
|
||||||
}
|
}
|
||||||
"rate_limit" => {
|
"rate_limit" => {
|
||||||
let input = condition.inputs.len() == 1 && values.get(&condition.inputs[0]).copied().unwrap_or(false);
|
let input = condition.inputs.len() == 1 && values.get(&condition.inputs[0]).copied().unwrap_or(false);
|
||||||
@@ -772,12 +795,17 @@ pub async fn evaluate_flow_conditions_trace(
|
|||||||
let window = condition.config.get("window_seconds").and_then(Value::as_u64).unwrap_or(60);
|
let window = condition.config.get("window_seconds").and_then(Value::as_u64).unwrap_or(60);
|
||||||
let mut aggregate = None;
|
let mut aggregate = None;
|
||||||
let mut count = 0usize;
|
let mut count = 0usize;
|
||||||
if let (Some(sample), Some(map)) = (sample, runtime.as_deref_mut()) {
|
if let Some(map) = runtime.as_deref_mut() {
|
||||||
let node_state = map.entry(condition.id.clone()).or_default();
|
let node_state = map.entry(condition.id.clone()).or_default();
|
||||||
|
if let Some(sample) = sample {
|
||||||
aggregate = flow_rolling_stat_update(
|
aggregate = flow_rolling_stat_update(
|
||||||
node_state, sample, window, condition.config.get("statistic").and_then(Value::as_str).unwrap_or("mean"), now.with_timezone(&Utc),
|
node_state, sample, window, condition.config.get("statistic").and_then(Value::as_str).unwrap_or("mean"), now.with_timezone(&Utc),
|
||||||
);
|
);
|
||||||
count = node_state.samples.len();
|
count = node_state.samples.len();
|
||||||
|
} else {
|
||||||
|
node_state.since = None;
|
||||||
|
node_state.samples.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let expected = condition.config.get("value").and_then(Value::as_f64);
|
let expected = condition.config.get("value").and_then(Value::as_f64);
|
||||||
let matched = predecessors_match && aggregate.zip(expected).map(|(a,e)| flow_compare(a, condition.config.get("operator").and_then(Value::as_str).unwrap_or("lt"), e)).unwrap_or(false);
|
let matched = predecessors_match && aggregate.zip(expected).map(|(a,e)| flow_compare(a, condition.config.get("operator").and_then(Value::as_str).unwrap_or("lt"), e)).unwrap_or(false);
|
||||||
|
|||||||
+17
-27
@@ -306,12 +306,15 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flow_change_gate_ignores_first_observation_and_detects_later_change() {
|
fn flow_change_gate_latches_until_action_ack_or_input_reset() {
|
||||||
let mut state = crate::models::FlowRuntimeNodeState::default();
|
let mut state = crate::models::FlowRuntimeNodeState::default();
|
||||||
assert!(!flow_change_gate_update(&mut state, json!("off")));
|
assert_eq!(flow_change_gate_update(&mut state, json!(false), false), (false, false));
|
||||||
assert!(!flow_change_gate_update(&mut state, json!("off")));
|
assert_eq!(flow_change_gate_update(&mut state, json!(true), true), (true, true));
|
||||||
assert!(flow_change_gate_update(&mut state, json!("on")));
|
assert_eq!(flow_change_gate_update(&mut state, json!(true), true), (false, true));
|
||||||
assert!(!flow_change_gate_update(&mut state, json!("on")));
|
state.pending = false;
|
||||||
|
assert_eq!(flow_change_gate_update(&mut state, json!(true), true), (false, false));
|
||||||
|
state.pending = true;
|
||||||
|
assert_eq!(flow_change_gate_update(&mut state, json!(false), false), (true, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -337,30 +340,27 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn flow_rolling_statistics_compute_mean_median_and_prune_window() {
|
fn flow_rolling_statistics_require_a_full_window_and_prune_samples() {
|
||||||
let mut state = crate::models::FlowRuntimeNodeState::default();
|
let mut state = crate::models::FlowRuntimeNodeState::default();
|
||||||
let start = Utc.with_ymd_and_hms(2026, 9, 2, 12, 0, 0).unwrap();
|
let start = Utc.with_ymd_and_hms(2026, 9, 2, 12, 0, 0).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(flow_rolling_stat_update(&mut state, 10.0, 60, "mean", start), None);
|
||||||
flow_rolling_stat_update(&mut state, 10.0, 60, "mean", start),
|
|
||||||
Some(10.0)
|
|
||||||
);
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
flow_rolling_stat_update(
|
flow_rolling_stat_update(
|
||||||
&mut state,
|
&mut state,
|
||||||
20.0,
|
20.0,
|
||||||
60,
|
60,
|
||||||
"mean",
|
"mean",
|
||||||
start + chrono::Duration::seconds(10)
|
start + chrono::Duration::seconds(30)
|
||||||
),
|
),
|
||||||
Some(15.0)
|
None
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
flow_rolling_stat_update(
|
flow_rolling_stat_update(
|
||||||
&mut state,
|
&mut state,
|
||||||
30.0,
|
30.0,
|
||||||
60,
|
60,
|
||||||
"median",
|
"mean",
|
||||||
start + chrono::Duration::seconds(20)
|
start + chrono::Duration::seconds(60)
|
||||||
),
|
),
|
||||||
Some(20.0)
|
Some(20.0)
|
||||||
);
|
);
|
||||||
@@ -370,21 +370,11 @@ mod tests {
|
|||||||
40.0,
|
40.0,
|
||||||
60,
|
60,
|
||||||
"median",
|
"median",
|
||||||
start + chrono::Duration::seconds(30)
|
start + chrono::Duration::seconds(90)
|
||||||
),
|
),
|
||||||
Some(25.0)
|
Some(30.0)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(state.samples.len(), 3);
|
||||||
flow_rolling_stat_update(
|
|
||||||
&mut state,
|
|
||||||
100.0,
|
|
||||||
60,
|
|
||||||
"mean",
|
|
||||||
start + chrono::Duration::seconds(100)
|
|
||||||
),
|
|
||||||
Some(100.0)
|
|
||||||
);
|
|
||||||
assert_eq!(state.samples.len(), 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ pub struct FlowRuntimeNodeState {
|
|||||||
/// Last observed raw/result value for edge/change detection blocks.
|
/// Last observed raw/result value for edge/change detection blocks.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub last_value: Option<Value>,
|
pub last_value: Option<Value>,
|
||||||
|
/// Latched change event waiting for its downstream action to execute successfully.
|
||||||
|
#[serde(default)]
|
||||||
|
pub pending: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
|||||||
@@ -6270,6 +6270,15 @@ textarea[aria-invalid="true"] {
|
|||||||
box-shadow: 0 0 0 2px var(--accent-soft), 0 12px 30px rgba(0, 0, 0, .22);
|
box-shadow: 0 0 0 2px var(--accent-soft), 0 12px 30px rgba(0, 0, 0, .22);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flow-selection-marquee {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 4;
|
||||||
|
pointer-events: none;
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--accent-soft);
|
||||||
|
}
|
||||||
|
|
||||||
.flow-editor .flow-node-time {
|
.flow-editor .flow-node-time {
|
||||||
border-top: 3px solid var(--blue);
|
border-top: 3px solid var(--blue);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -998,6 +998,8 @@
|
|||||||
data-i18n="flow.addBlock">Add
|
data-i18n="flow.addBlock">Add
|
||||||
block</span></button><button type="button" class="secondary flow-selection-action"
|
block</span></button><button type="button" class="secondary flow-selection-action"
|
||||||
data-action="flow-select-all" data-i18n="flow.selectAll">Zaznacz wszystko</button><button type="button"
|
data-action="flow-select-all" data-i18n="flow.selectAll">Zaznacz wszystko</button><button type="button"
|
||||||
|
class="secondary flow-selection-action" data-action="flow-duplicate-selection"
|
||||||
|
data-i18n="flow.duplicateSelection" disabled>Duplikuj</button><button type="button"
|
||||||
class="secondary flow-selection-action" data-action="flow-clear-selection"
|
class="secondary flow-selection-action" data-action="flow-clear-selection"
|
||||||
data-i18n="flow.clearSelection">Wyczyść zaznaczenie</button><span id="flowSelectionCount"
|
data-i18n="flow.clearSelection">Wyczyść zaznaczenie</button><span id="flowSelectionCount"
|
||||||
class="badge"></span></div>
|
class="badge"></span></div>
|
||||||
@@ -1015,6 +1017,7 @@
|
|||||||
<div id="flowWorkspace" class="flow-workspace" tabindex="0">
|
<div id="flowWorkspace" class="flow-workspace" tabindex="0">
|
||||||
<div id="flowCanvas" class="flow-canvas"><svg id="flowEdges" class="flow-edges" aria-hidden="true"></svg>
|
<div id="flowCanvas" class="flow-canvas"><svg id="flowEdges" class="flow-edges" aria-hidden="true"></svg>
|
||||||
<div id="flowNodes" class="flow-nodes"></div>
|
<div id="flowNodes" class="flow-nodes"></div>
|
||||||
|
<div id="flowSelectionMarquee" class="flow-selection-marquee" hidden aria-hidden="true"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="flowEmptyHint" class="flow-empty-hint"><strong data-i18n="flow.emptyCanvas">Start by adding
|
<div id="flowEmptyHint" class="flow-empty-hint"><strong data-i18n="flow.emptyCanvas">Start by adding
|
||||||
blocks</strong><span data-i18n="flow.emptyCanvasHint">Conditions go on the left, actions on the
|
blocks</strong><span data-i18n="flow.emptyCanvasHint">Conditions go on the left, actions on the
|
||||||
|
|||||||
+146
-4
@@ -357,7 +357,13 @@ function flowNodeSummary(node) {
|
|||||||
if (node.kind === 'logic_and') return tr('flow.allConditions');
|
if (node.kind === 'logic_and') return tr('flow.allConditions');
|
||||||
if (node.kind === 'logic_or') return tr('flow.anyCondition');
|
if (node.kind === 'logic_or') return tr('flow.anyCondition');
|
||||||
if (node.kind === 'logic_not') return tr('flow.invertCondition');
|
if (node.kind === 'logic_not') return tr('flow.invertCondition');
|
||||||
if (node.kind === 'zone_thermostat') { const zone = app.zones.find(z => z.id === c.zone_id); return `${zone?.name || tr('common.noZone')} · ${c.preset === 'custom' ? fmtTemp(c.setpoint) : zonePresetLabel(c.preset || 'comfort')}`; }
|
if (node.kind === 'zone_thermostat') {
|
||||||
|
const zone = app.zones.find(z => z.id === c.zone_id);
|
||||||
|
const power = c.power == null ? null : (c.power ? tr('common.on') : tr('common.off'));
|
||||||
|
const mode = c.mode ? (c.mode === 'auto' ? 'Auto' : modeLabel(c.mode)) : null;
|
||||||
|
const target = c.preset === 'custom' ? fmtTemp(c.setpoint) : zonePresetLabel(c.preset || 'comfort');
|
||||||
|
return [zone?.name || tr('common.noZone'), power, mode, target].filter(Boolean).join(' · ');
|
||||||
|
}
|
||||||
if (node.kind === 'device_action') { const d = app.devices.find(v => v.id === c.device_id); return `${d?.name || tr('common.noDevice')} · ${c.power == null ? tr('actions.noChange') : c.power ? tr('common.on') : tr('common.off')}`; }
|
if (node.kind === 'device_action') { const d = app.devices.find(v => v.id === c.device_id); return `${d?.name || tr('common.noDevice')} · ${c.power == null ? tr('actions.noChange') : c.power ? tr('common.on') : tr('common.off')}`; }
|
||||||
if (node.kind === 'device_feature_action') { const d = app.devices.find(v => v.id === c.device_id); return `${d?.name || tr('common.noDevice')} · ${deviceCommandFieldLabel(c.feature || 'light')} → ${flowDeviceFeatureValueLabel(c.feature, c.value)}`; }
|
if (node.kind === 'device_feature_action') { const d = app.devices.find(v => v.id === c.device_id); return `${d?.name || tr('common.noDevice')} · ${deviceCommandFieldLabel(c.feature || 'light')} → ${flowDeviceFeatureValueLabel(c.feature, c.value)}`; }
|
||||||
if (node.kind === 'group_action') { const g = app.groups.find(v => v.id === c.group_id); return `${g?.name || tr('groups.group')} · ${c.power == null ? tr('actions.noChange') : c.power ? tr('common.on') : tr('common.off')}`; }
|
if (node.kind === 'group_action') { const g = app.groups.find(v => v.id === c.group_id); return `${g?.name || tr('groups.group')} · ${c.power == null ? tr('actions.noChange') : c.power ? tr('common.on') : tr('common.off')}`; }
|
||||||
@@ -470,7 +476,9 @@ function renderFlowEditor() {
|
|||||||
</article>`;
|
</article>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
$('#flowEmptyHint').hidden = draft.nodes.length > 0;
|
$('#flowEmptyHint').hidden = draft.nodes.length > 0;
|
||||||
const selectionCount = $('#flowSelectionCount'); if (selectionCount) selectionCount.textContent = (app.flowSelectedNodeIds || []).length ? tr('flow.selectedCount', { count: (app.flowSelectedNodeIds || []).length }) : '';
|
const selectedCount = (app.flowSelectedNodeIds || []).length;
|
||||||
|
const selectionCount = $('#flowSelectionCount'); if (selectionCount) selectionCount.textContent = selectedCount ? tr('flow.selectedCount', { count: selectedCount }) : '';
|
||||||
|
const duplicateButton = $('[data-action="flow-duplicate-selection"]'); if (duplicateButton) duplicateButton.disabled = !selectedCount;
|
||||||
renderFlowNameMode(); renderFlowEnabledLabel(); setFlowZoom(app.flowZoom || 1, { render: false }); renderFlowEdges(); renderFlowInspector(); renderFlowInterpretation(); renderFlowRuntimeInfo(); renderFlowSaveStatus(); refreshFlowSharedInputCurrentValues();
|
renderFlowNameMode(); renderFlowEnabledLabel(); setFlowZoom(app.flowZoom || 1, { render: false }); renderFlowEdges(); renderFlowInspector(); renderFlowInterpretation(); renderFlowRuntimeInfo(); renderFlowSaveStatus(); refreshFlowSharedInputCurrentValues();
|
||||||
const status = $('#flowCompileStatus');
|
const status = $('#flowCompileStatus');
|
||||||
status.textContent = draft.draft ? tr('flow.draftStatus') : tr('flow.compileCount', { schedules: draft.compiled_schedule_ids?.length || 0, automations: draft.compiled_automation_ids?.length || 0 });
|
status.textContent = draft.draft ? tr('flow.draftStatus') : tr('flow.compileCount', { schedules: draft.compiled_schedule_ids?.length || 0, automations: draft.compiled_automation_ids?.length || 0 });
|
||||||
@@ -1189,6 +1197,59 @@ function selectAllFlowNodes() {
|
|||||||
|
|
||||||
function clearFlowSelection() { setFlowSelection([], null); }
|
function clearFlowSelection() { setFlowSelection([], null); }
|
||||||
|
|
||||||
|
let flowClipboard = null;
|
||||||
|
let flowClipboardPasteCount = 0;
|
||||||
|
|
||||||
|
function copySelectedFlowNodes({ notify = true } = {}) {
|
||||||
|
if (!app.flowDraft || !(app.flowSelectedNodeIds || []).length) return false;
|
||||||
|
const selected = new Set(app.flowSelectedNodeIds);
|
||||||
|
const nodes = app.flowDraft.nodes
|
||||||
|
.filter(node => selected.has(node.id))
|
||||||
|
.map(node => ({ ...node, config: JSON.parse(JSON.stringify(node.config || {})) }));
|
||||||
|
const edges = app.flowDraft.edges
|
||||||
|
.filter(edge => selected.has(edge.from) && selected.has(edge.to))
|
||||||
|
.map(edge => ({ ...edge }));
|
||||||
|
if (!nodes.length) return false;
|
||||||
|
flowClipboard = { nodes, edges };
|
||||||
|
flowClipboardPasteCount = 0;
|
||||||
|
if (notify) toast(tr('flow.blocksCopied', { count: nodes.length }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pasteFlowNodes({ notify = true } = {}) {
|
||||||
|
if (!app.flowDraft || !flowClipboard?.nodes?.length) return false;
|
||||||
|
flowClipboardPasteCount += 1;
|
||||||
|
const offset = 36 * flowClipboardPasteCount;
|
||||||
|
const idMap = new Map(flowClipboard.nodes.map(node => [node.id, newFlowId('node')]));
|
||||||
|
const copies = flowClipboard.nodes.map(node => ({
|
||||||
|
...node,
|
||||||
|
id: idMap.get(node.id),
|
||||||
|
config: JSON.parse(JSON.stringify(node.config || {})),
|
||||||
|
x: Math.max(12, Number(node.x || 0) + offset),
|
||||||
|
y: Math.max(12, Number(node.y || 0) + offset),
|
||||||
|
}));
|
||||||
|
const copiedEdges = flowClipboard.edges.map(edge => ({
|
||||||
|
...edge,
|
||||||
|
id: newFlowId('edge'),
|
||||||
|
from: idMap.get(edge.from),
|
||||||
|
to: idMap.get(edge.to),
|
||||||
|
}));
|
||||||
|
app.flowDraft.nodes.push(...copies);
|
||||||
|
app.flowDraft.edges.push(...copiedEdges);
|
||||||
|
app.flowSelectedNodeIds = copies.map(node => node.id);
|
||||||
|
app.flowSelectedNodeId = copies[copies.length - 1]?.id || null;
|
||||||
|
app.flowDirty = true;
|
||||||
|
renderFlowEditor();
|
||||||
|
if (notify) toast(tr('flow.blocksPasted', { count: copies.length }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function duplicateSelectedFlowNodes() {
|
||||||
|
const count = (app.flowSelectedNodeIds || []).length;
|
||||||
|
if (!count || !copySelectedFlowNodes({ notify: false })) return;
|
||||||
|
if (pasteFlowNodes({ notify: false })) toast(tr('flow.blocksDuplicated', { count }));
|
||||||
|
}
|
||||||
|
|
||||||
function removeSelectedFlowNodes() {
|
function removeSelectedFlowNodes() {
|
||||||
if (!app.flowDraft || !(app.flowSelectedNodeIds || []).length) return;
|
if (!app.flowDraft || !(app.flowSelectedNodeIds || []).length) return;
|
||||||
const selected = new Set(app.flowSelectedNodeIds);
|
const selected = new Set(app.flowSelectedNodeIds);
|
||||||
@@ -1250,11 +1311,68 @@ function updateFlowConfig(input) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let flowDrag = null;
|
let flowDrag = null;
|
||||||
|
let flowMarquee = null;
|
||||||
|
|
||||||
|
function flowCanvasPoint(event) {
|
||||||
|
const workspace = $('#flowWorkspace');
|
||||||
|
if (!workspace) return { x: 0, y: 0 };
|
||||||
|
const rect = workspace.getBoundingClientRect();
|
||||||
|
const zoom = app.flowZoom || 1;
|
||||||
|
return {
|
||||||
|
x: (event.clientX - rect.left + workspace.scrollLeft) / zoom,
|
||||||
|
y: (event.clientY - rect.top + workspace.scrollTop) / zoom,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFlowMarqueeSelection(event) {
|
||||||
|
if (!flowMarquee || !app.flowDraft) return;
|
||||||
|
const current = flowCanvasPoint(event);
|
||||||
|
const left = Math.min(flowMarquee.start.x, current.x);
|
||||||
|
const top = Math.min(flowMarquee.start.y, current.y);
|
||||||
|
const right = Math.max(flowMarquee.start.x, current.x);
|
||||||
|
const bottom = Math.max(flowMarquee.start.y, current.y);
|
||||||
|
const marquee = $('#flowSelectionMarquee');
|
||||||
|
if (marquee) {
|
||||||
|
marquee.hidden = false;
|
||||||
|
marquee.style.left = `${left}px`;
|
||||||
|
marquee.style.top = `${top}px`;
|
||||||
|
marquee.style.width = `${right - left}px`;
|
||||||
|
marquee.style.height = `${bottom - top}px`;
|
||||||
|
}
|
||||||
|
const hits = app.flowDraft.nodes.filter(node => {
|
||||||
|
const el = $(`[data-flow-node="${CSS.escape(node.id)}"]`);
|
||||||
|
const nodeLeft = Number(node.x || 0), nodeTop = Number(node.y || 0);
|
||||||
|
const nodeRight = nodeLeft + Number(el?.offsetWidth || 170);
|
||||||
|
const nodeBottom = nodeTop + Number(el?.offsetHeight || 96);
|
||||||
|
return nodeRight >= left && nodeLeft <= right && nodeBottom >= top && nodeTop <= bottom;
|
||||||
|
}).map(node => node.id);
|
||||||
|
const selected = [...new Set([...flowMarquee.baseSelection, ...hits])];
|
||||||
|
app.flowSelectedNodeIds = selected;
|
||||||
|
app.flowSelectedNodeId = selected[selected.length - 1] || null;
|
||||||
|
$$('[data-flow-node]').forEach(el => el.classList.toggle('selected', selected.includes(el.dataset.flowNode)));
|
||||||
|
const count = $('#flowSelectionCount'); if (count) count.textContent = selected.length ? tr('flow.selectedCount', { count: selected.length }) : '';
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('pointerdown', event => {
|
document.addEventListener('pointerdown', event => {
|
||||||
if ($('#flowEditor')?.hidden) return;
|
if ($('#flowEditor')?.hidden) return;
|
||||||
|
if (event.button !== 0) return;
|
||||||
const nodeEl = event.target.closest?.('[data-flow-node]');
|
const nodeEl = event.target.closest?.('[data-flow-node]');
|
||||||
if (!nodeEl) {
|
if (!nodeEl) {
|
||||||
if (event.target.closest?.('#flowWorkspace') && !event.target.closest('button,input,select,label')) clearFlowSelection();
|
const workspace = event.target.closest?.('#flowWorkspace');
|
||||||
|
if (!workspace || event.target.closest?.('button,input,select,label,[data-flow-edge]')) return;
|
||||||
|
const additive = event.shiftKey || event.ctrlKey || event.metaKey;
|
||||||
|
flowMarquee = {
|
||||||
|
start: flowCanvasPoint(event),
|
||||||
|
clientX: event.clientX,
|
||||||
|
clientY: event.clientY,
|
||||||
|
baseSelection: additive ? [...(app.flowSelectedNodeIds || [])] : [],
|
||||||
|
additive,
|
||||||
|
moved: false,
|
||||||
|
pointerId: event.pointerId,
|
||||||
|
};
|
||||||
|
workspace.setPointerCapture?.(event.pointerId);
|
||||||
|
const marquee = $('#flowSelectionMarquee'); if (marquee) marquee.hidden = true;
|
||||||
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.target.closest('button,input,select,label')) return;
|
if (event.target.closest('button,input,select,label')) return;
|
||||||
@@ -1271,6 +1389,12 @@ document.addEventListener('pointerdown', event => {
|
|||||||
nodeEl.setPointerCapture?.(event.pointerId); event.preventDefault();
|
nodeEl.setPointerCapture?.(event.pointerId); event.preventDefault();
|
||||||
});
|
});
|
||||||
document.addEventListener('pointermove', event => {
|
document.addEventListener('pointermove', event => {
|
||||||
|
if (flowMarquee && app.flowDraft) {
|
||||||
|
flowMarquee.moved = flowMarquee.moved || Math.abs(event.clientX - flowMarquee.clientX) > 3 || Math.abs(event.clientY - flowMarquee.clientY) > 3;
|
||||||
|
if (flowMarquee.moved) updateFlowMarqueeSelection(event);
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!flowDrag || !app.flowDraft) return;
|
if (!flowDrag || !app.flowDraft) return;
|
||||||
let dx = (event.clientX - flowDrag.x) / (app.flowZoom || 1), dy = (event.clientY - flowDrag.y) / (app.flowZoom || 1);
|
let dx = (event.clientX - flowDrag.x) / (app.flowZoom || 1), dy = (event.clientY - flowDrag.y) / (app.flowZoom || 1);
|
||||||
const minLeft = Math.min(...flowDrag.starts.map(item => item.left));
|
const minLeft = Math.min(...flowDrag.starts.map(item => item.left));
|
||||||
@@ -1283,7 +1407,21 @@ document.addEventListener('pointermove', event => {
|
|||||||
});
|
});
|
||||||
app.flowDirty = true; renderFlowSaveStatus(); renderFlowEdges();
|
app.flowDirty = true; renderFlowSaveStatus(); renderFlowEdges();
|
||||||
});
|
});
|
||||||
document.addEventListener('pointerup', () => { flowDrag = null; });
|
document.addEventListener('pointerup', event => {
|
||||||
|
if (flowMarquee) {
|
||||||
|
if (!flowMarquee.moved) {
|
||||||
|
app.flowSelectedNodeIds = flowMarquee.additive ? [...flowMarquee.baseSelection] : [];
|
||||||
|
app.flowSelectedNodeId = app.flowSelectedNodeIds[app.flowSelectedNodeIds.length - 1] || null;
|
||||||
|
}
|
||||||
|
const workspace = $('#flowWorkspace'); workspace?.releasePointerCapture?.(flowMarquee.pointerId);
|
||||||
|
const marquee = $('#flowSelectionMarquee'); if (marquee) marquee.hidden = true;
|
||||||
|
flowMarquee = null;
|
||||||
|
renderFlowEditor();
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
flowDrag = null;
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener('keydown', event => {
|
document.addEventListener('keydown', event => {
|
||||||
const flowCard = event.target.closest?.('[data-flow-card-id]');
|
const flowCard = event.target.closest?.('[data-flow-card-id]');
|
||||||
@@ -1291,6 +1429,9 @@ document.addEventListener('keydown', event => {
|
|||||||
if ($('#flowEditor')?.hidden || !app.flowDraft) return;
|
if ($('#flowEditor')?.hidden || !app.flowDraft) return;
|
||||||
if (event.target.closest?.('input,select,textarea,[contenteditable="true"]')) return;
|
if (event.target.closest?.('input,select,textarea,[contenteditable="true"]')) return;
|
||||||
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a') { event.preventDefault(); selectAllFlowNodes(); return; }
|
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a') { event.preventDefault(); selectAllFlowNodes(); return; }
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'c') { event.preventDefault(); copySelectedFlowNodes(); return; }
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'v') { event.preventDefault(); pasteFlowNodes(); return; }
|
||||||
|
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'd') { event.preventDefault(); duplicateSelectedFlowNodes(); return; }
|
||||||
if ((event.key === 'Delete' || event.key === 'Backspace') && (app.flowSelectedNodeIds || []).length) { event.preventDefault(); removeSelectedFlowNodes(); }
|
if ((event.key === 'Delete' || event.key === 'Backspace') && (app.flowSelectedNodeIds || []).length) { event.preventDefault(); removeSelectedFlowNodes(); }
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1333,6 +1474,7 @@ document.addEventListener('click', event => {
|
|||||||
else if (action === 'run-flow-dry-run') runFlowDryRun();
|
else if (action === 'run-flow-dry-run') runFlowDryRun();
|
||||||
else if (action === 'flow-logs') openFlowLogs();
|
else if (action === 'flow-logs') openFlowLogs();
|
||||||
else if (action === 'flow-select-all') selectAllFlowNodes();
|
else if (action === 'flow-select-all') selectAllFlowNodes();
|
||||||
|
else if (action === 'flow-duplicate-selection') duplicateSelectedFlowNodes();
|
||||||
else if (action === 'flow-clear-selection') clearFlowSelection();
|
else if (action === 'flow-clear-selection') clearFlowSelection();
|
||||||
else if (action === 'edit-flow-name') editFlowName();
|
else if (action === 'edit-flow-name') editFlowName();
|
||||||
else if (action === 'flow-shared-input-settings') {
|
else if (action === 'flow-shared-input-settings') {
|
||||||
|
|||||||
Reference in New Issue
Block a user